From 6bdd065888848a329a0fbf5f825f44104c049a3c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 15 Mar 2021 12:00:32 +0530 Subject: [PATCH 001/599] lint(persistence): Apply team linter Signed-off-by: Sahil Kumar --- .../analysis_options.yaml | 147 ++++++ .../lib/src/converter/list_converter.dart | 4 +- .../lib/src/converter/map_converter.dart | 4 +- .../lib/src/dao/channel_dao.dart | 52 +- .../lib/src/dao/channel_query_dao.dart | 49 +- .../lib/src/dao/connection_event_dao.dart | 63 ++- .../lib/src/dao/dao.dart | 8 +- .../lib/src/dao/member_dao.dart | 56 +-- .../lib/src/dao/message_dao.dart | 54 +-- .../lib/src/dao/pinned_message_dao.dart | 56 +-- .../lib/src/dao/reaction_dao.dart | 59 ++- .../lib/src/dao/read_dao.dart | 44 +- .../lib/src/dao/user_dao.dart | 18 +- .../lib/src/db/moor_chat_database.dart | 15 +- .../lib/src/db/shared/native_db.dart | 24 +- .../lib/src/db/shared/unsupported_db.dart | 6 +- .../lib/src/db/shared/web_db.dart | 2 +- .../lib/src/entity/entity.dart | 8 +- .../lib/src/entity/pinned_messages.dart | 2 +- .../lib/src/mapper/channel_mapper.dart | 47 +- .../lib/src/mapper/event_mapper.dart | 12 +- .../lib/src/mapper/mapper.dart | 6 +- .../lib/src/mapper/member_mapper.dart | 56 +-- .../lib/src/mapper/message_mapper.dart | 118 +++-- .../lib/src/mapper/pinned_message_mapper.dart | 118 +++-- .../lib/src/mapper/reaction_mapper.dart | 8 +- .../lib/src/mapper/read_mapper.dart | 8 +- .../lib/src/mapper/user_mapper.dart | 44 +- .../src/stream_chat_persistence_client.dart | 447 ++++++++---------- packages/stream_chat_persistence/pubspec.yaml | 2 +- 30 files changed, 789 insertions(+), 748 deletions(-) create mode 100644 packages/stream_chat_persistence/analysis_options.yaml diff --git a/packages/stream_chat_persistence/analysis_options.yaml b/packages/stream_chat_persistence/analysis_options.yaml new file mode 100644 index 00000000..7e4b00fa --- /dev/null +++ b/packages/stream_chat_persistence/analysis_options.yaml @@ -0,0 +1,147 @@ +analyzer: + exclude: + - lib/**/*.g.dart + - lib/**/*.freezed.dart + - example/* + - test/* +linter: + rules: + - always_use_package_imports + - avoid_empty_else + - avoid_relative_lib_imports + - avoid_slow_async_io + - avoid_types_as_parameter_names + - cancel_subscriptions + - close_sinks + - control_flow_in_finally + - diagnostic_describe_all_properties + - empty_statements + - hash_and_equals + - invariant_booleans + - iterable_contains_unrelated_type + - list_remove_unrelated_type + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - no_duplicate_case_values + - no_logic_in_create_state + - prefer_void_to_null + - test_types_in_equals + - throw_in_finally + - unnecessary_statements + - unrelated_type_equality_checks + - omit_local_variable_types + - use_key_in_widget_constructors + - valid_regexps + - always_declare_return_types + - always_put_required_named_parameters_first + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catching_errors + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_private_typedef_functions + - avoid_redundant_argument_values + - avoid_return_types_on_setters + - avoid_returning_null + - avoid_returning_null_for_void + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_extensions + - camel_case_types + - cascade_invocations + + - constant_identifier_names + - curly_braces_in_flow_control_structures + - directives_ordering + - empty_catches + - empty_constructor_bodies + - exhaustive_cases + - file_names + - implementation_imports + - join_return_with_assignment + - leading_newlines_in_multiline_strings + - library_names + - library_prefixes + - lines_longer_than_80_chars + - missing_whitespace_between_adjacent_strings + - non_constant_identifier_names + - null_closures + - one_member_abstracts + - only_throw_errors + - package_api_docs + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_asserts_with_message + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_equal_for_default_values + - prefer_expression_function_bodies + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_int_literals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - provide_deprecation_message + - public_member_api_docs + - recursive_getters + - sized_box_for_whitespace + - slash_for_doc_comments + - sort_child_properties_last + - sort_constructors_first + - sort_unnamed_constructors_first + + - type_annotate_public_apis + - type_init_formals + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_parenthesis + - unnecessary_raw_strings + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - use_is_even_rather_than_modulo + - use_late_for_private_fields_and_variables + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_to_and_as_if_applicable + - package_names + - sort_pub_dependencies + + # To be added when null-safe: + # - 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 diff --git a/packages/stream_chat_persistence/lib/src/converter/list_converter.dart b/packages/stream_chat_persistence/lib/src/converter/list_converter.dart index d14642ce..cbb8ec05 100644 --- a/packages/stream_chat_persistence/lib/src/converter/list_converter.dart +++ b/packages/stream_chat_persistence/lib/src/converter/list_converter.dart @@ -6,7 +6,7 @@ import 'package:moor/moor.dart'; /// by the sqlite backend. class ListConverter extends TypeConverter, String> { @override - List mapToDart(fromDb) { + List mapToDart(String fromDb) { if (fromDb == null) { return null; } @@ -14,7 +14,7 @@ class ListConverter extends TypeConverter, String> { } @override - String mapToSql(value) { + String mapToSql(List value) { if (value == null) { return null; } diff --git a/packages/stream_chat_persistence/lib/src/converter/map_converter.dart b/packages/stream_chat_persistence/lib/src/converter/map_converter.dart index 6e7a648d..b11eb5b8 100644 --- a/packages/stream_chat_persistence/lib/src/converter/map_converter.dart +++ b/packages/stream_chat_persistence/lib/src/converter/map_converter.dart @@ -6,7 +6,7 @@ import 'package:moor/moor.dart'; /// by the sqlite backend. class MapConverter extends TypeConverter, String> { @override - Map mapToDart(fromDb) { + Map mapToDart(String fromDb) { if (fromDb == null) { return null; } @@ -14,7 +14,7 @@ class MapConverter extends TypeConverter, String> { } @override - String mapToSql(value) { + String mapToSql(Map value) { if (value == null) { return null; } diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart index 57ec95d3..e75cead6 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart @@ -3,7 +3,7 @@ import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/entity/channels.dart'; import 'package:stream_chat_persistence/src/entity/users.dart'; -import '../mapper/mapper.dart'; +import 'package:stream_chat_persistence/src/mapper/mapper.dart'; part 'channel_dao.g.dart'; @@ -15,15 +15,14 @@ class ChannelDao extends DatabaseAccessor ChannelDao(MoorChatDatabase db) : super(db); /// Get channel by cid - Future getChannelByCid(String cid) async { - return (select(channels)..where((c) => c.cid.equals(cid))).join([ - leftOuterJoin(users, channels.createdById.equalsExp(users.id)), - ]).map((rows) { - final channel = rows.readTable(channels); - final createdBy = rows.readTable(users); - return channel.toChannelModel(createdBy: createdBy?.toUser()); - }).getSingle(); - } + Future getChannelByCid(String cid) async => + (select(channels)..where((c) => c.cid.equals(cid))).join([ + leftOuterJoin(users, channels.createdById.equalsExp(users.id)), + ]).map((rows) { + final channel = rows.readTable(channels); + final createdBy = rows.readTable(users); + return channel.toChannelModel(createdBy: createdBy?.toUser()); + }).getSingle(); /// Delete all channels by matching cid in [cids] /// @@ -31,27 +30,22 @@ class ChannelDao extends DatabaseAccessor /// 1. Channel Reads /// 2. Channel Members /// 3. Channel Messages -> Messages Reactions - Future deleteChannelByCids(List cids) async { - return (delete(channels)..where((tbl) => tbl.cid.isIn(cids))).go(); - } + Future deleteChannelByCids(List cids) async => + (delete(channels)..where((tbl) => tbl.cid.isIn(cids))).go(); /// Get the channel cids saved in the storage - Future> get cids { - return (select(channels) - ..orderBy([(c) => OrderingTerm.desc(c.lastMessageAt)]) - ..limit(250)) - .map((c) => c.cid) - .get(); - } + Future> get cids => (select(channels) + ..orderBy([(c) => OrderingTerm.desc(c.lastMessageAt)]) + ..limit(250)) + .map((c) => c.cid) + .get(); /// Updates all the channels using the new [channelList] data - Future updateChannels(List channelList) { - return batch( - (it) => it.insertAll( - channels, - channelList.map((c) => c.toEntity()).toList(), - mode: InsertMode.insertOrReplace, - ), - ); - } + Future updateChannels(List channelList) => batch( + (it) => it.insertAll( + channels, + channelList.map((c) => c.toEntity()).toList(), + mode: InsertMode.insertOrReplace, + ), + ); } diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart index b5f6fe59..785b5946 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart @@ -7,7 +7,7 @@ import 'package:stream_chat_persistence/src/entity/channel_queries.dart'; import 'package:stream_chat_persistence/src/entity/channels.dart'; import 'package:stream_chat_persistence/src/entity/users.dart'; -import '../mapper/mapper.dart'; +import 'package:stream_chat_persistence/src/mapper/mapper.dart'; part 'channel_query_dao.g.dart'; @@ -31,31 +31,31 @@ class ChannelQueryDao extends DatabaseAccessor /// the list of matching rows will be deleted Future updateChannelQueries( Map filter, - List cids, + List cids, { bool clearQueryCache, - ) async { - return transaction(() async { - final hash = _computeHash(filter); - if (clearQueryCache) { + }) async => + transaction(() async { + final hash = _computeHash(filter); + if (clearQueryCache) { + await batch((it) { + it.deleteWhere( + channelQueries, + (c) => c.queryHash.equals(hash), + ); + }); + } + await batch((it) { - it.deleteWhere( + it.insertAll( channelQueries, - (c) => c.queryHash.equals(hash), + cids + .map((cid) => + ChannelQueryEntity(queryHash: hash, channelCid: cid)) + .toList(), + mode: InsertMode.insertOrReplace, ); }); - } - - await batch((it) { - it.insertAll( - channelQueries, - cids.map((cid) { - return ChannelQueryEntity(queryHash: hash, channelCid: cid); - }).toList(), - mode: InsertMode.insertOrReplace, - ); }); - }); - } /// Get list of channels by filter, sort and paginationParams Future> getChannels({ @@ -70,7 +70,7 @@ class ChannelQueryDao extends DatabaseAccessor ); } return true; - }()); + }(), ''); final hash = _computeHash(filter); final cachedChannelCids = await (select(channelQueries) @@ -89,10 +89,11 @@ class ChannelQueryDao extends DatabaseAccessor })).get(); final possibleSortingFields = cachedChannels.fold>( - ChannelModel.topLevelFields, (previousValue, element) { - return {...previousValue, ...element.extraData.keys}.toList(); - }); + ChannelModel.topLevelFields, + (previousValue, element) => + {...previousValue, ...element.extraData.keys}.toList()); + // ignore: parameter_assignments sort = sort ?.where((s) => possibleSortingFields.contains(s.field)) ?.toList(growable: false); diff --git a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart index 92764b10..94b0d333 100644 --- a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart @@ -4,7 +4,7 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/entity/connection_events.dart'; import 'package:stream_chat_persistence/src/entity/users.dart'; -import '../mapper/mapper.dart'; +import 'package:stream_chat_persistence/src/mapper/mapper.dart'; part 'connection_event_dao.g.dart'; @@ -16,43 +16,38 @@ class ConnectionEventDao extends DatabaseAccessor ConnectionEventDao(MoorChatDatabase db) : super(db); /// Get the latest stored connection event - Future get connectionEvent { - return select(connectionEvents).map((eventEntity) { - return eventEntity.toEvent(); - }).getSingle(); - } + Future get connectionEvent => select(connectionEvents) + .map((eventEntity) => eventEntity.toEvent()) + .getSingle(); /// Get the latest stored lastSyncAt - Future get lastSyncAt { - return select(connectionEvents).getSingle().then((r) => r?.lastSyncAt); - } + Future get lastSyncAt => + select(connectionEvents).getSingle().then((r) => r?.lastSyncAt); /// Update stored connection event with latest data - Future updateConnectionEvent(Event event) async { - return transaction(() async { - final connectionInfo = await select(connectionEvents).getSingle(); - await into(connectionEvents).insert( - ConnectionEventEntity( - id: 1, - lastSyncAt: connectionInfo?.lastSyncAt, - lastEventAt: event.createdAt ?? connectionInfo?.lastEventAt, - totalUnreadCount: - event.totalUnreadCount ?? connectionInfo?.totalUnreadCount, - ownUser: event.me?.toJson() ?? connectionInfo?.ownUser, - unreadChannels: - event.unreadChannels ?? connectionInfo?.unreadChannels, - ), - mode: InsertMode.insertOrReplace, - ); - }); - } + Future updateConnectionEvent(Event event) async => + transaction(() async { + final connectionInfo = await select(connectionEvents).getSingle(); + await into(connectionEvents).insert( + ConnectionEventEntity( + id: 1, + lastSyncAt: connectionInfo?.lastSyncAt, + lastEventAt: event.createdAt ?? connectionInfo?.lastEventAt, + totalUnreadCount: + event.totalUnreadCount ?? connectionInfo?.totalUnreadCount, + ownUser: event.me?.toJson() ?? connectionInfo?.ownUser, + unreadChannels: + event.unreadChannels ?? connectionInfo?.unreadChannels, + ), + mode: InsertMode.insertOrReplace, + ); + }); /// Update stored lastSyncAt with latest data - Future updateLastSyncAt(DateTime lastSyncAt) async { - return (update(connectionEvents)..where((tbl) => tbl.id.equals(1))).write( - ConnectionEventsCompanion( - lastSyncAt: Value(lastSyncAt), - ), - ); - } + Future updateLastSyncAt(DateTime lastSyncAt) async => + (update(connectionEvents)..where((tbl) => tbl.id.equals(1))).write( + ConnectionEventsCompanion( + lastSyncAt: Value(lastSyncAt), + ), + ); } diff --git a/packages/stream_chat_persistence/lib/src/dao/dao.dart b/packages/stream_chat_persistence/lib/src/dao/dao.dart index 31953630..53c2f372 100644 --- a/packages/stream_chat_persistence/lib/src/dao/dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/dao.dart @@ -1,9 +1,9 @@ -export 'user_dao.dart'; export 'channel_dao.dart'; +export 'channel_query_dao.dart'; +export 'connection_event_dao.dart'; +export 'member_dao.dart'; export 'message_dao.dart'; export 'pinned_message_dao.dart'; -export 'member_dao.dart'; -export 'connection_event_dao.dart'; export 'reaction_dao.dart'; export 'read_dao.dart'; -export 'channel_query_dao.dart'; +export 'user_dao.dart'; diff --git a/packages/stream_chat_persistence/lib/src/dao/member_dao.dart b/packages/stream_chat_persistence/lib/src/dao/member_dao.dart index ed30a931..e6c03996 100644 --- a/packages/stream_chat_persistence/lib/src/dao/member_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/member_dao.dart @@ -5,7 +5,7 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/entity/members.dart'; import 'package:stream_chat_persistence/src/entity/users.dart'; -import '../mapper/mapper.dart'; +import 'package:stream_chat_persistence/src/mapper/mapper.dart'; part 'member_dao.g.dart'; @@ -17,37 +17,33 @@ class MemberDao extends DatabaseAccessor MemberDao(MoorChatDatabase db) : super(db); /// Get all members where [Members.channelCid] matches [cid] - Future> getMembersByCid(String cid) async { - return (select(members).join([ - leftOuterJoin(users, members.userId.equalsExp(users.id)), - ]) - ..where(members.channelCid.equals(cid)) - ..orderBy([OrderingTerm.asc(members.createdAt)])) - .map((row) { - final userEntity = row.readTable(users); - final memberEntity = row.readTable(members); - return memberEntity.toMember(user: userEntity?.toUser()); - }).get(); - } + Future> getMembersByCid(String cid) async => + (select(members).join([ + leftOuterJoin(users, members.userId.equalsExp(users.id)), + ]) + ..where(members.channelCid.equals(cid)) + ..orderBy([OrderingTerm.asc(members.createdAt)])) + .map((row) { + final userEntity = row.readTable(users); + final memberEntity = row.readTable(members); + return memberEntity.toMember(user: userEntity?.toUser()); + }).get(); /// Updates all the members using the new [memberList] data - Future updateMembers(String cid, List memberList) async { - return batch( - (it) => it.insertAll( - members, - memberList.map((m) => m.toEntity(cid: cid)).toList(), - mode: InsertMode.insertOrReplace, - ), - ); - } + Future updateMembers(String cid, List memberList) async => + batch( + (it) => it.insertAll( + members, + memberList.map((m) => m.toEntity(cid: cid)).toList(), + mode: InsertMode.insertOrReplace, + ), + ); /// Deletes all the members whose [Members.channelCid] is present in [cids] - Future deleteMemberByCids(List cids) async { - return batch((it) { - it.deleteWhere( - members, - (m) => m.channelCid.isIn(cids), - ); - }); - } + Future deleteMemberByCids(List cids) async => batch((it) { + it.deleteWhere( + members, + (m) => m.channelCid.isIn(cids), + ); + }); } 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 204daaed..0be4446b 100644 --- a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart @@ -4,7 +4,7 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/entity/messages.dart'; import 'package:stream_chat_persistence/src/entity/users.dart'; -import '../mapper/mapper.dart'; +import 'package:stream_chat_persistence/src/mapper/mapper.dart'; part 'message_dao.g.dart'; @@ -25,17 +25,15 @@ class MessageDao extends DatabaseAccessor /// /// This will automatically delete the following linked records /// 1. Message Reactions - Future deleteMessageByIds(List messageIds) { - return (delete(messages)..where((tbl) => tbl.id.isIn(messageIds))).go(); - } + Future deleteMessageByIds(List messageIds) => + (delete(messages)..where((tbl) => tbl.id.isIn(messageIds))).go(); /// Removes all the messages by matching [Messages.channelCid] in [cids] /// /// This will automatically delete the following linked records /// 1. Message Reactions - Future deleteMessageByCids(List cids) async { - return (delete(messages)..where((tbl) => tbl.channelCid.isIn(cids))).go(); - } + Future deleteMessageByCids(List cids) async => + (delete(messages)..where((tbl) => tbl.channelCid.isIn(cids))).go(); Future _messageFromJoinRow(TypedResult rows) async { final userEntity = rows.readTable(_users); @@ -60,31 +58,29 @@ class MessageDao extends DatabaseAccessor } /// Returns a single message by matching the [Messages.id] with [id] - Future getMessageById(String id) async { - return await (select(messages).join([ - leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), - ]) - ..where(messages.id.equals(id))) - .map(_messageFromJoinRow) - .getSingle(); - } + Future getMessageById(String id) async => + await (select(messages).join([ + leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), + leftOuterJoin(_pinnedByUsers, + messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), + ]) + ..where(messages.id.equals(id))) + .map(_messageFromJoinRow) + .getSingle(); /// Returns all the messages of a particular thread by matching /// [Messages.channelCid] with [cid] - Future> getThreadMessages(String cid) async { - return Future.wait(await (select(messages).join([ - leftOuterJoin(users, messages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), - ]) - ..where(messages.channelCid.equals(cid)) - ..where(isNotNull(messages.parentId)) - ..orderBy([OrderingTerm.asc(messages.createdAt)])) - .map(_messageFromJoinRow) - .get()); - } + 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)), + ]) + ..where(messages.channelCid.equals(cid)) + ..where(isNotNull(messages.parentId)) + ..orderBy([OrderingTerm.asc(messages.createdAt)])) + .map(_messageFromJoinRow) + .get()); /// Returns all the messages of a particular thread by matching /// [Messages.parentId] with [parentId] 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 edf8e438..5e0ae721 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 @@ -4,7 +4,7 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/entity/pinned_messages.dart'; import 'package:stream_chat_persistence/src/entity/users.dart'; -import '../mapper/mapper.dart'; +import 'package:stream_chat_persistence/src/mapper/mapper.dart'; part 'pinned_message_dao.g.dart'; @@ -25,19 +25,15 @@ class PinnedMessageDao extends DatabaseAccessor /// /// This will automatically delete the following linked records /// 1. Message Reactions - Future deleteMessageByIds(List messageIds) { - return (delete(pinnedMessages)..where((tbl) => tbl.id.isIn(messageIds))) - .go(); - } + Future deleteMessageByIds(List messageIds) => + (delete(pinnedMessages)..where((tbl) => tbl.id.isIn(messageIds))).go(); /// Removes all the messages by matching [PinnedMessages.channelCid] in [cids] /// /// This will automatically delete the following linked records /// 1. Message Reactions - Future deleteMessageByCids(List cids) async { - return (delete(pinnedMessages)..where((tbl) => tbl.channelCid.isIn(cids))) - .go(); - } + Future deleteMessageByCids(List cids) async => + (delete(pinnedMessages)..where((tbl) => tbl.channelCid.isIn(cids))).go(); Future _messageFromJoinRow(TypedResult rows) async { final userEntity = rows.readTable(users); @@ -62,31 +58,29 @@ class PinnedMessageDao extends DatabaseAccessor } /// Returns a single message by matching the [PinnedMessages.id] with [id] - Future getMessageById(String id) async { - return await (select(pinnedMessages).join([ - leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - leftOuterJoin(_pinnedByUsers, - pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), - ]) - ..where(pinnedMessages.id.equals(id))) - .map(_messageFromJoinRow) - .getSingle(); - } + Future getMessageById(String id) async => + await (select(pinnedMessages).join([ + leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), + leftOuterJoin(_pinnedByUsers, + pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), + ]) + ..where(pinnedMessages.id.equals(id))) + .map(_messageFromJoinRow) + .getSingle(); /// Returns all the messages of a particular thread by matching /// [PinnedMessages.channelCid] with [cid] - Future> getThreadMessages(String cid) async { - return Future.wait(await (select(pinnedMessages).join([ - leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - leftOuterJoin(_pinnedByUsers, - pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), - ]) - ..where(pinnedMessages.channelCid.equals(cid)) - ..where(isNotNull(pinnedMessages.parentId)) - ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) - .map(_messageFromJoinRow) - .get()); - } + 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)), + ]) + ..where(pinnedMessages.channelCid.equals(cid)) + ..where(isNotNull(pinnedMessages.parentId)) + ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) + .map(_messageFromJoinRow) + .get()); /// Returns all the messages of a particular thread by matching /// [PinnedMessages.parentId] with [parentId] diff --git a/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart b/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart index 99053fca..ef06326a 100644 --- a/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart @@ -3,7 +3,7 @@ import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/entity/reactions.dart'; import 'package:stream_chat_persistence/src/entity/users.dart'; -import '../mapper/mapper.dart'; +import 'package:stream_chat_persistence/src/mapper/mapper.dart'; part 'reaction_dao.g.dart'; @@ -16,18 +16,17 @@ class ReactionDao extends DatabaseAccessor /// Returns all the reactions of a particular message by matching /// [Reactions.messageId] with [messageId] - Future> getReactions(String messageId) { - return (select(reactions).join([ - leftOuterJoin(users, reactions.userId.equalsExp(users.id)), - ]) - ..where(reactions.messageId.equals(messageId)) - ..orderBy([OrderingTerm.asc(reactions.createdAt)])) - .map((rows) { - final userEntity = rows.readTable(users); - final reactionEntity = rows.readTable(reactions); - return reactionEntity.toReaction(user: userEntity?.toUser()); - }).get(); - } + Future> getReactions(String messageId) => + (select(reactions).join([ + leftOuterJoin(users, reactions.userId.equalsExp(users.id)), + ]) + ..where(reactions.messageId.equals(messageId)) + ..orderBy([OrderingTerm.asc(reactions.createdAt)])) + .map((rows) { + final userEntity = rows.readTable(users); + final reactionEntity = rows.readTable(reactions); + return reactionEntity.toReaction(user: userEntity?.toUser()); + }).get(); /// Returns all the reactions of a particular message /// added by a particular user by matching @@ -42,23 +41,21 @@ class ReactionDao extends DatabaseAccessor } /// Updates the reactions data with the new [reactionList] data - Future updateReactions(List reactionList) { - return batch((it) { - it.insertAll( - reactions, - reactionList.map((r) => r.toEntity()).toList(), - mode: InsertMode.insertOrReplace, - ); - }); - } + Future updateReactions(List reactionList) => batch((it) { + it.insertAll( + reactions, + reactionList.map((r) => r.toEntity()).toList(), + mode: InsertMode.insertOrReplace, + ); + }); - /// Deletes all the reactions whose [Reactions.messageId] is present in [messageIds] - Future deleteReactionsByMessageIds(List messageIds) { - return batch((it) { - it.deleteWhere( - reactions, - (r) => r.messageId.isIn(messageIds), - ); - }); - } + /// Deletes all the reactions whose [Reactions.messageId] is + /// present in [messageIds] + Future deleteReactionsByMessageIds(List messageIds) => + batch((it) { + it.deleteWhere( + reactions, + (r) => r.messageId.isIn(messageIds), + ); + }); } diff --git a/packages/stream_chat_persistence/lib/src/dao/read_dao.dart b/packages/stream_chat_persistence/lib/src/dao/read_dao.dart index c78cab07..95e23d8b 100644 --- a/packages/stream_chat_persistence/lib/src/dao/read_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/read_dao.dart @@ -3,7 +3,7 @@ import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/entity/reads.dart'; import 'package:stream_chat_persistence/src/entity/users.dart'; -import '../mapper/mapper.dart'; +import 'package:stream_chat_persistence/src/mapper/mapper.dart'; part 'read_dao.g.dart'; @@ -14,30 +14,26 @@ class ReadDao extends DatabaseAccessor with _$ReadDaoMixin { ReadDao(MoorChatDatabase db) : super(db); /// Get all reads where [Reads.channelCid] matches [cid] - Future> getReadsByCid(String cid) async { - return (select(reads).join([ - leftOuterJoin(users, reads.userId.equalsExp(users.id)), - ]) - ..where(reads.channelCid.equals(cid)) - ..orderBy([ - OrderingTerm.asc(reads.lastRead), - ])) - .map((row) { - final userEntity = row.readTable(users); - final readEntity = row.readTable(reads); - return readEntity.toRead(user: userEntity?.toUser()); - }).get(); - } + Future> getReadsByCid(String cid) async => (select(reads).join([ + leftOuterJoin(users, reads.userId.equalsExp(users.id)), + ]) + ..where(reads.channelCid.equals(cid)) + ..orderBy([ + OrderingTerm.asc(reads.lastRead), + ])) + .map((row) { + final userEntity = row.readTable(users); + final readEntity = row.readTable(reads); + return readEntity.toRead(user: userEntity?.toUser()); + }).get(); /// Updates the read data of a particular channel with /// the new [readList] data - Future updateReads(String cid, List readList) { - return batch( - (it) => it.insertAll( - reads, - readList.map((r) => r.toEntity(cid: cid)).toList(), - mode: InsertMode.insertOrReplace, - ), - ); - } + Future updateReads(String cid, List readList) => batch( + (it) => it.insertAll( + reads, + readList.map((r) => r.toEntity(cid: cid)).toList(), + mode: InsertMode.insertOrReplace, + ), + ); } diff --git a/packages/stream_chat_persistence/lib/src/dao/user_dao.dart b/packages/stream_chat_persistence/lib/src/dao/user_dao.dart index 795c7968..65ce1521 100644 --- a/packages/stream_chat_persistence/lib/src/dao/user_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/user_dao.dart @@ -2,7 +2,7 @@ import 'package:moor/moor.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/entity/users.dart'; -import '../mapper/user_mapper.dart'; +import 'package:stream_chat_persistence/src/mapper/user_mapper.dart'; part 'user_dao.g.dart'; @@ -13,13 +13,11 @@ class UserDao extends DatabaseAccessor with _$UserDaoMixin { UserDao(MoorChatDatabase db) : super(db); /// Updates the users data with the new [userList] data - Future updateUsers(List userList) { - return batch( - (it) => it.insertAll( - users, - userList.map((u) => u.toEntity()).toList(), - mode: InsertMode.insertOrReplace, - ), - ); - } + Future updateUsers(List userList) => batch( + (it) => it.insertAll( + users, + userList.map((u) => u.toEntity()).toList(), + mode: InsertMode.insertOrReplace, + ), + ); } 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 57348330..cc3e86b3 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 @@ -13,15 +13,12 @@ LazyDatabase _openConnection( String userId, { bool logStatements = false, bool persistOnDisk = true, -}) { - return LazyDatabase(() async { - return await SharedDB.constructDatabase( - userId, - logStatements: logStatements, - persistOnDisk: persistOnDisk, - ); - }); -} +}) => + LazyDatabase(() async => SharedDB.constructDatabase( + userId, + logStatements: logStatements, + persistOnDisk: persistOnDisk, + )); /// A chat database implemented using moor @UseMoor(tables: [ diff --git a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart index 9580ae50..7592247e 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart @@ -9,7 +9,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart'; import 'package:stream_chat_persistence/stream_chat_persistence.dart'; -import '../moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// A Helper class to construct new instances of [MoorChatDatabase] specifically /// for native platform applications @@ -40,12 +40,10 @@ class SharedDB { } static void _startBackground(_IsolateStartRequest request) { - final executor = LazyDatabase(() async { - return VmDatabase( - File(request.targetPath), - logStatements: request.logStatements, - ); - }); + final executor = LazyDatabase(() async => VmDatabase( + File(request.targetPath), + logStatements: request.logStatements, + )); final moorIsolate = MoorIsolate.inCurrent( () => DatabaseConnection.fromExecutor(executor), ); @@ -69,7 +67,7 @@ class SharedDB { ), ); - return (await receivePort.first as MoorIsolate); + return await receivePort.first as MoorIsolate; } /// Returns a new instance of [MoorChatDatabase] using the factory constructor @@ -84,7 +82,7 @@ class SharedDB { return MoorChatDatabase.connect( userId, DatabaseConnection.delayed(Future(() async { - MoorIsolate isolate = await _createMoorIsolate( + final isolate = await _createMoorIsolate( dbName, logStatements: logStatements, ); @@ -95,13 +93,13 @@ class SharedDB { } class _IsolateStartRequest { - final SendPort sendMoorIsolate; - final String targetPath; - final bool logStatements; - const _IsolateStartRequest( this.sendMoorIsolate, this.targetPath, { this.logStatements = false, }); + + final SendPort sendMoorIsolate; + final String targetPath; + final bool logStatements; } diff --git a/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart index 811e5cc7..3a764d24 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart @@ -12,7 +12,8 @@ class SharedDB { bool logStatements = false, bool persistOnDisk = true, }) { - throw 'Unsupported Platform'; + throw UnsupportedError( + 'No implementation of the constructDatabase api provided'); } /// Return a new instance of moor chat database. @@ -22,6 +23,7 @@ class SharedDB { String userId, { bool logStatements = false, }) { - throw 'Unsupported Platform'; + throw UnsupportedError( + 'No implementation of the constructMoorChatDatabase api provided'); } } diff --git a/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart index f44a41ec..32b108ba 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart @@ -1,7 +1,7 @@ import 'package:moor/moor_web.dart'; import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart'; -import '../moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// A Helper class to construct new instances of [MoorChatDatabase] specifically /// for Web applications diff --git a/packages/stream_chat_persistence/lib/src/entity/entity.dart b/packages/stream_chat_persistence/lib/src/entity/entity.dart index ca9d04cb..d5218768 100644 --- a/packages/stream_chat_persistence/lib/src/entity/entity.dart +++ b/packages/stream_chat_persistence/lib/src/entity/entity.dart @@ -1,9 +1,9 @@ +export 'channel_queries.dart'; export 'channels.dart'; +export 'connection_events.dart'; +export 'members.dart'; export 'messages.dart'; export 'pinned_messages.dart'; export 'reactions.dart'; -export 'users.dart'; -export 'members.dart'; export 'reads.dart'; -export 'channel_queries.dart'; -export 'connection_events.dart'; +export 'users.dart'; diff --git a/packages/stream_chat_persistence/lib/src/entity/pinned_messages.dart b/packages/stream_chat_persistence/lib/src/entity/pinned_messages.dart index f6956f54..de081552 100644 --- a/packages/stream_chat_persistence/lib/src/entity/pinned_messages.dart +++ b/packages/stream_chat_persistence/lib/src/entity/pinned_messages.dart @@ -1,6 +1,6 @@ import 'package:moor/moor.dart'; -import 'messages.dart'; +import 'package:stream_chat_persistence/src/entity/messages.dart'; /// Represents a [PinnedMessages] table in [MoorChatDatabase]. @DataClassName('PinnedMessageEntity') diff --git a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart index 3e2520fb..5c8e3e2a 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart @@ -29,34 +29,31 @@ extension ChannelEntityX on ChannelEntity { List reads, List messages, List pinnedMessages, - }) { - return ChannelState( - members: members, - read: reads, - messages: messages, - pinnedMessages: pinnedMessages, - channel: toChannelModel(createdBy: createdBy), - ); - } + }) => + ChannelState( + members: members, + read: reads, + messages: messages, + pinnedMessages: pinnedMessages, + channel: toChannelModel(createdBy: createdBy), + ); } /// Useful mapping functions for [ChannelModel] extension ChannelModelX on ChannelModel { /// Maps a [ChannelModel] into [ChannelEntity] - ChannelEntity toEntity() { - return ChannelEntity( - id: id, - type: type, - cid: cid, - config: config.toJson(), - frozen: frozen, - lastMessageAt: lastMessageAt, - createdAt: createdAt, - updatedAt: updatedAt, - deletedAt: deletedAt, - memberCount: memberCount, - createdById: createdBy.id, - extraData: extraData, - ); - } + ChannelEntity toEntity() => ChannelEntity( + id: id, + type: type, + cid: cid, + config: config.toJson(), + frozen: frozen, + lastMessageAt: lastMessageAt, + createdAt: createdAt, + updatedAt: updatedAt, + deletedAt: deletedAt, + memberCount: memberCount, + createdById: createdBy.id, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart index 7bc1739c..fc2a57ba 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart @@ -4,11 +4,9 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// Useful mapping functions for [ConnectionEventEntity] extension ConnectionEventX on ConnectionEventEntity { /// Maps a [ConnectionEventEntity] into [Event] - Event toEvent() { - return Event( - me: ownUser != null ? OwnUser.fromJson(ownUser) : null, - totalUnreadCount: totalUnreadCount, - unreadChannels: unreadChannels, - ); - } + Event toEvent() => Event( + me: ownUser != null ? OwnUser.fromJson(ownUser) : null, + totalUnreadCount: totalUnreadCount, + unreadChannels: unreadChannels, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/mapper.dart index cdc31e05..66bf3cda 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/mapper.dart @@ -1,8 +1,8 @@ -export 'user_mapper.dart'; -export 'reaction_mapper.dart'; export 'channel_mapper.dart'; export 'event_mapper.dart'; export 'member_mapper.dart'; -export 'read_mapper.dart'; export 'message_mapper.dart'; export 'pinned_message_mapper.dart'; +export 'reaction_mapper.dart'; +export 'read_mapper.dart'; +export 'user_mapper.dart'; diff --git a/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart index 2d32c9f1..cbdcc297 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart @@ -4,39 +4,35 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// Useful mapping functions for [MemberEntity] extension MemberEntityX on MemberEntity { /// Maps a [MemberEntity] into [Member] - Member toMember({User user}) { - return Member( - user: user, - userId: userId, - banned: banned, - shadowBanned: shadowBanned, - updatedAt: updatedAt, - createdAt: createdAt, - role: role, - inviteAcceptedAt: inviteAcceptedAt, - invited: invited, - inviteRejectedAt: inviteRejectedAt, - isModerator: isModerator, - ); - } + Member toMember({User user}) => Member( + user: user, + userId: userId, + banned: banned, + shadowBanned: shadowBanned, + updatedAt: updatedAt, + createdAt: createdAt, + role: role, + inviteAcceptedAt: inviteAcceptedAt, + invited: invited, + inviteRejectedAt: inviteRejectedAt, + isModerator: isModerator, + ); } /// Useful mapping functions for [Member] extension MemberX on Member { /// Maps a [Member] into [MemberEntity] - MemberEntity toEntity({String cid}) { - return MemberEntity( - userId: user?.id, - banned: banned, - shadowBanned: shadowBanned, - channelCid: cid, - createdAt: createdAt, - isModerator: isModerator, - inviteRejectedAt: inviteRejectedAt, - invited: invited, - inviteAcceptedAt: inviteAcceptedAt, - role: role, - updatedAt: updatedAt, - ); - } + MemberEntity toEntity({String cid}) => MemberEntity( + userId: user?.id, + banned: banned, + shadowBanned: shadowBanned, + channelCid: cid, + createdAt: createdAt, + isModerator: isModerator, + inviteRejectedAt: inviteRejectedAt, + invited: invited, + inviteAcceptedAt: inviteAcceptedAt, + role: role, + updatedAt: updatedAt, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart index 5acf1a84..fdf93cc5 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart @@ -12,70 +12,66 @@ extension MessageEntityX on MessageEntity { List latestReactions, List ownReactions, Message quotedMessage, - }) { - return Message( - shadowed: shadowed, - latestReactions: latestReactions, - ownReactions: ownReactions, - attachments: attachments?.map((it) { - final json = jsonDecode(it); - return Attachment.fromData(json); - })?.toList(), - createdAt: createdAt, - extraData: extraData, - updatedAt: updatedAt, - id: id, - type: type, - status: status, - command: command, - parentId: parentId, - quotedMessageId: quotedMessageId, - quotedMessage: quotedMessage, - reactionCounts: reactionCounts, - reactionScores: reactionScores, - replyCount: replyCount, - showInChannel: showInChannel, - text: messageText, - user: user, - deletedAt: deletedAt, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedBy: pinnedBy, - ); - } + }) => + Message( + shadowed: shadowed, + latestReactions: latestReactions, + ownReactions: ownReactions, + attachments: attachments?.map((it) { + final json = jsonDecode(it); + return Attachment.fromData(json); + })?.toList(), + createdAt: createdAt, + extraData: extraData, + updatedAt: updatedAt, + id: id, + type: type, + status: status, + command: command, + parentId: parentId, + quotedMessageId: quotedMessageId, + quotedMessage: quotedMessage, + reactionCounts: reactionCounts, + reactionScores: reactionScores, + replyCount: replyCount, + showInChannel: showInChannel, + text: messageText, + user: user, + deletedAt: deletedAt, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedBy: pinnedBy, + ); } /// Useful mapping functions for [Message] extension MessageX on Message { /// Maps a [Message] into [MessageEntity] - MessageEntity toEntity({String cid}) { - return MessageEntity( - id: id, - attachments: attachments?.map((it) { - return jsonEncode(it.toData()); - })?.toList(), - channelCid: cid, - type: type, - parentId: parentId, - quotedMessageId: quotedMessageId, - command: command, - createdAt: createdAt, - shadowed: shadowed, - showInChannel: showInChannel, - replyCount: replyCount, - reactionScores: reactionScores, - reactionCounts: reactionCounts, - status: status, - updatedAt: updatedAt, - extraData: extraData, - userId: user?.id, - deletedAt: deletedAt, - messageText: text, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedBy?.id, - ); - } + MessageEntity toEntity({String cid}) => MessageEntity( + id: id, + attachments: + attachments?.map((it) => jsonEncode(it.toData()))?.toList(), + channelCid: cid, + type: type, + parentId: parentId, + quotedMessageId: quotedMessageId, + command: command, + createdAt: createdAt, + shadowed: shadowed, + showInChannel: showInChannel, + replyCount: replyCount, + reactionScores: reactionScores, + reactionCounts: reactionCounts, + status: status, + updatedAt: updatedAt, + extraData: extraData, + userId: user?.id, + deletedAt: deletedAt, + messageText: text, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedBy?.id, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart index 1abe896f..ec2a0e91 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart @@ -12,70 +12,66 @@ extension PinnedMessageEntityX on PinnedMessageEntity { List latestReactions, List ownReactions, Message quotedMessage, - }) { - return Message( - shadowed: shadowed, - latestReactions: latestReactions, - ownReactions: ownReactions, - attachments: attachments?.map((it) { - final json = jsonDecode(it); - return Attachment.fromData(json); - })?.toList(), - createdAt: createdAt, - extraData: extraData, - updatedAt: updatedAt, - id: id, - type: type, - status: status, - command: command, - parentId: parentId, - quotedMessageId: quotedMessageId, - quotedMessage: quotedMessage, - reactionCounts: reactionCounts, - reactionScores: reactionScores, - replyCount: replyCount, - showInChannel: showInChannel, - text: messageText, - user: user, - deletedAt: deletedAt, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedBy: pinnedBy, - ); - } + }) => + Message( + shadowed: shadowed, + latestReactions: latestReactions, + ownReactions: ownReactions, + attachments: attachments?.map((it) { + final json = jsonDecode(it); + return Attachment.fromData(json); + })?.toList(), + createdAt: createdAt, + extraData: extraData, + updatedAt: updatedAt, + id: id, + type: type, + status: status, + command: command, + parentId: parentId, + quotedMessageId: quotedMessageId, + quotedMessage: quotedMessage, + reactionCounts: reactionCounts, + reactionScores: reactionScores, + replyCount: replyCount, + showInChannel: showInChannel, + text: messageText, + user: user, + deletedAt: deletedAt, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedBy: pinnedBy, + ); } /// Useful mapping functions for [Message] extension PMessageX on Message { /// Maps a [Message] into [PinnedMessageEntity] - PinnedMessageEntity toPinnedEntity({String cid}) { - return PinnedMessageEntity( - id: id, - attachments: attachments?.map((it) { - return jsonEncode(it.toData()); - })?.toList(), - channelCid: cid, - type: type, - parentId: parentId, - quotedMessageId: quotedMessageId, - command: command, - createdAt: createdAt, - shadowed: shadowed, - showInChannel: showInChannel, - replyCount: replyCount, - reactionScores: reactionScores, - reactionCounts: reactionCounts, - status: status, - updatedAt: updatedAt, - extraData: extraData, - userId: user?.id, - deletedAt: deletedAt, - messageText: text, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedBy?.id, - ); - } + PinnedMessageEntity toPinnedEntity({String cid}) => PinnedMessageEntity( + id: id, + attachments: + attachments?.map((it) => jsonEncode(it.toData()))?.toList(), + channelCid: cid, + type: type, + parentId: parentId, + quotedMessageId: quotedMessageId, + command: command, + createdAt: createdAt, + shadowed: shadowed, + showInChannel: showInChannel, + replyCount: replyCount, + reactionScores: reactionScores, + reactionCounts: reactionCounts, + status: status, + updatedAt: updatedAt, + extraData: extraData, + userId: user?.id, + deletedAt: deletedAt, + messageText: text, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedBy?.id, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart index d265fcb7..3f8533db 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart @@ -4,8 +4,7 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// Useful mapping functions for [ReactionEntity] extension ReactionEntityX on ReactionEntity { /// Maps a [ReactionEntity] into [Reaction] - Reaction toReaction({User user}) { - return Reaction( + Reaction toReaction({User user}) => Reaction( extraData: extraData, type: type, createdAt: createdAt, @@ -14,14 +13,12 @@ extension ReactionEntityX on ReactionEntity { messageId: messageId, score: score, ); - } } /// Useful mapping functions for [Reaction] extension ReactionX on Reaction { /// Maps a [Reaction] into [ReactionEntity] - ReactionEntity toEntity() { - return ReactionEntity( + ReactionEntity toEntity() => ReactionEntity( extraData: extraData, type: type, createdAt: createdAt, @@ -29,5 +26,4 @@ extension ReactionX on Reaction { messageId: messageId, score: score, ); - } } diff --git a/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart index c79cf8d5..5493c5da 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart @@ -4,24 +4,20 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// Useful mapping functions for [ReadEntity] extension ReadEntityX on ReadEntity { /// Maps a [ReadEntity] into [Read] - Read toRead({User user}) { - return Read( + Read toRead({User user}) => Read( user: user, lastRead: lastRead, unreadMessages: unreadMessages, ); - } } /// Useful mapping functions for [Read] extension ReadX on Read { /// Maps a [Read] into [ReadEntity] - ReadEntity toEntity({String cid}) { - return ReadEntity( + ReadEntity toEntity({String cid}) => ReadEntity( lastRead: lastRead, userId: user?.id, channelCid: cid, unreadMessages: unreadMessages, ); - } } diff --git a/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart index b1e53786..533a45b1 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart @@ -4,33 +4,29 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// Useful mapping functions for [UserEntity] extension UserEntityX on UserEntity { /// Maps a [UserEntity] into [User] - User toUser() { - return User( - id: id, - updatedAt: updatedAt, - role: role, - online: online, - lastActive: lastActive, - extraData: extraData, - banned: banned, - createdAt: createdAt, - ); - } + User toUser() => User( + id: id, + updatedAt: updatedAt, + role: role, + online: online, + lastActive: lastActive, + extraData: extraData, + banned: banned, + createdAt: createdAt, + ); } /// Useful mapping functions for [User] extension UserX on User { /// Maps a [User] into [UserEntity] - UserEntity toEntity() { - return UserEntity( - id: id, - role: role, - createdAt: createdAt, - updatedAt: updatedAt, - lastActive: lastActive, - online: online, - banned: banned, - extraData: extraData, - ); - } + UserEntity toEntity() => UserEntity( + id: id, + role: role, + createdAt: createdAt, + updatedAt: updatedAt, + lastActive: lastActive, + online: online, + banned: banned, + extraData: extraData, + ); } 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 832eabbd..e7ee060b 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 @@ -3,8 +3,8 @@ import 'package:meta/meta.dart'; import 'package:mutex/mutex.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'db/moor_chat_database.dart'; -import 'db/shared/shared_db.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/db/shared/shared_db.dart'; /// Various connection modes on which [StreamChatPersistenceClient] can work enum ConnectionMode { @@ -15,7 +15,7 @@ enum ConnectionMode { background, } -final levelEmojiMapper = { +final _levelEmojiMapper = { Level.INFO: 'â„šī¸', Level.WARNING: 'âš ī¸', Level.SEVERE: '🚨', @@ -29,8 +29,8 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { ConnectionMode connectionMode = ConnectionMode.regular, Level logLevel = Level.WARNING, LogHandlerFunction logHandlerFunction, - }) : assert(connectionMode != null), - assert(logLevel != null), + }) : assert(connectionMode != null, 'ConnectionMode cannot be null'), + assert(logLevel != null, 'LogLevel cannot be null'), _connectionMode = connectionMode, _logger = Logger.detached('đŸ’Ŋ')..level = logLevel { _logger.onRecord.listen(logHandlerFunction ?? _defaultLogHandler); @@ -56,8 +56,10 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { ///``` LogHandlerFunction logHandlerFunction; + /// [MoorChatDatabase] instance used by this client. @visibleForTesting MoorChatDatabase db; + final Logger _logger; final ConnectionMode _connectionMode; final _mutex = ReadWriteMutex(); @@ -65,13 +67,13 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { void _defaultLogHandler(LogRecord record) { print( '(${record.time}) ' - '${levelEmojiMapper[record.level] ?? record.level.name} ' + '${_levelEmojiMapper[record.level] ?? record.level.name} ' '${record.loggerName} ${record.message}', ); if (record.stackTrace != null) print(record.stackTrace); } - Future readProtected(Future Function() f) async { + Future _readProtected(Future Function() f) async { T ret; await _mutex.protectRead(() async { if (db == null) { @@ -103,299 +105,256 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { } @override - Future getConnectionInfo() { - return readProtected(() { - _logger.info('getConnectionInfo'); - return db.connectionEventDao.connectionEvent; - }); - } + Future getConnectionInfo() => _readProtected(() { + _logger.info('getConnectionInfo'); + return db.connectionEventDao.connectionEvent; + }); @override - Future updateConnectionInfo(Event event) { - return readProtected(() { - _logger.info('updateConnectionInfo'); - return db.connectionEventDao.updateConnectionEvent(event); - }); - } + Future updateConnectionInfo(Event event) => _readProtected(() { + _logger.info('updateConnectionInfo'); + return db.connectionEventDao.updateConnectionEvent(event); + }); @override - Future updateLastSyncAt(DateTime lastSyncAt) { - return readProtected(() { - _logger.info('updateLastSyncAt'); - return db.connectionEventDao.updateLastSyncAt(lastSyncAt); - }); - } + Future updateLastSyncAt(DateTime lastSyncAt) => _readProtected(() { + _logger.info('updateLastSyncAt'); + return db.connectionEventDao.updateLastSyncAt(lastSyncAt); + }); @override - Future getLastSyncAt() { - return readProtected(() { - _logger.info('getLastSyncAt'); - return db.connectionEventDao.lastSyncAt; - }); - } + Future getLastSyncAt() => _readProtected(() { + _logger.info('getLastSyncAt'); + return db.connectionEventDao.lastSyncAt; + }); @override - Future deleteChannels(List cids) { - return readProtected(() { - _logger.info('deleteChannels'); - return db.channelDao.deleteChannelByCids(cids); - }); - } + Future deleteChannels(List cids) => _readProtected(() { + _logger.info('deleteChannels'); + return db.channelDao.deleteChannelByCids(cids); + }); @override - Future> getChannelCids() { - return readProtected(() { - _logger.info('getChannelCids'); - return db.channelDao.cids; - }); - } + Future> getChannelCids() => _readProtected(() { + _logger.info('getChannelCids'); + return db.channelDao.cids; + }); @override - Future deleteMessageByIds(List messageIds) { - return readProtected(() { - _logger.info('deleteMessageByIds'); - return db.messageDao.deleteMessageByIds(messageIds); - }); - } + Future deleteMessageByIds(List messageIds) => + _readProtected(() { + _logger.info('deleteMessageByIds'); + return db.messageDao.deleteMessageByIds(messageIds); + }); @override - Future deletePinnedMessageByIds(List messageIds) { - return readProtected(() { - _logger.info('deletePinnedMessageByIds'); - return db.pinnedMessageDao.deleteMessageByIds(messageIds); - }); - } + Future deletePinnedMessageByIds(List messageIds) => + _readProtected(() { + _logger.info('deletePinnedMessageByIds'); + return db.pinnedMessageDao.deleteMessageByIds(messageIds); + }); @override - Future deleteMessageByCids(List cids) { - return readProtected(() { - _logger.info('deleteMessageByCids'); - return db.messageDao.deleteMessageByCids(cids); - }); - } + Future deleteMessageByCids(List cids) => _readProtected(() { + _logger.info('deleteMessageByCids'); + return db.messageDao.deleteMessageByCids(cids); + }); @override - Future deletePinnedMessageByCids(List cids) { - return readProtected(() { - _logger.info('deletePinnedMessageByCids'); - return db.pinnedMessageDao.deleteMessageByCids(cids); - }); - } + Future deletePinnedMessageByCids(List cids) => + _readProtected(() { + _logger.info('deletePinnedMessageByCids'); + return db.pinnedMessageDao.deleteMessageByCids(cids); + }); @override - Future> getMembersByCid(String cid) { - return readProtected(() { - _logger.info('getMembersByCid'); - return db.memberDao.getMembersByCid(cid); - }); - } + Future> getMembersByCid(String cid) => _readProtected(() { + _logger.info('getMembersByCid'); + return db.memberDao.getMembersByCid(cid); + }); @override - Future getChannelByCid(String cid) { - return readProtected(() { - _logger.info('getChannelByCid'); - return db.channelDao.getChannelByCid(cid); - }); - } + Future getChannelByCid(String cid) => _readProtected(() { + _logger.info('getChannelByCid'); + return db.channelDao.getChannelByCid(cid); + }); @override Future> getMessagesByCid( String cid, { PaginationParams messagePagination, - }) { - return readProtected(() { - _logger.info('getMessagesByCid'); - return db.messageDao.getMessagesByCid( - cid, - messagePagination: messagePagination, - ); - }); - } + }) => + _readProtected(() { + _logger.info('getMessagesByCid'); + return db.messageDao.getMessagesByCid( + cid, + messagePagination: messagePagination, + ); + }); @override Future> getPinnedMessagesByCid( String cid, { PaginationParams messagePagination, - }) { - return readProtected(() { - _logger.info('getPinnedMessagesByCid'); - return db.pinnedMessageDao.getMessagesByCid( - cid, - messagePagination: messagePagination, - ); - }); - } + }) => + _readProtected(() { + _logger.info('getPinnedMessagesByCid'); + return db.pinnedMessageDao.getMessagesByCid( + cid, + messagePagination: messagePagination, + ); + }); @override - Future> getReadsByCid(String cid) { - return readProtected(() { - _logger.info('getReadsByCid'); - return db.readDao.getReadsByCid(cid); - }); - } + Future> getReadsByCid(String cid) => _readProtected(() { + _logger.info('getReadsByCid'); + return db.readDao.getReadsByCid(cid); + }); @override - Future>> getChannelThreads(String cid) async { - return readProtected(() async { - _logger.info('getChannelThreads'); - final messages = await db.messageDao.getThreadMessages(cid); - final messageByParentIdDictionary = >{}; - for (final message in messages) { - final parentId = message.parentId; - messageByParentIdDictionary[parentId] = [ - ...messageByParentIdDictionary[parentId] ?? [], - message - ]; - } - return messageByParentIdDictionary; - }); - } + Future>> getChannelThreads(String cid) async => + _readProtected(() async { + _logger.info('getChannelThreads'); + final messages = await db.messageDao.getThreadMessages(cid); + final messageByParentIdDictionary = >{}; + for (final message in messages) { + final parentId = message.parentId; + messageByParentIdDictionary[parentId] = [ + ...messageByParentIdDictionary[parentId] ?? [], + message + ]; + } + return messageByParentIdDictionary; + }); @override Future> getReplies( String parentId, { PaginationParams options, - }) { - return readProtected(() async { - _logger.info('getReplies'); - return db.messageDao.getThreadMessagesByParentId( - parentId, - options: options, - ); - }); - } + }) => + _readProtected(() async { + _logger.info('getReplies'); + return db.messageDao.getThreadMessagesByParentId( + parentId, + options: options, + ); + }); @override Future> getChannelStates({ Map filter, List> sort = const [], PaginationParams paginationParams, - }) async { - return readProtected(() async { - _logger.info('getChannelStates'); - final channels = await db.channelQueryDao.getChannels( - filter: filter, - sort: sort, - paginationParams: paginationParams, - ); - return Future.wait(channels.map((e) => getChannelStateByCid(e.cid))); - }); - } + }) async => + _readProtected(() async { + _logger.info('getChannelStates'); + final channels = await db.channelQueryDao.getChannels( + filter: filter, + sort: sort, + paginationParams: paginationParams, + ); + return Future.wait(channels.map((e) => getChannelStateByCid(e.cid))); + }); @override Future updateChannelQueries( Map filter, List cids, bool clearQueryCache, - ) { - return readProtected(() async { - _logger.info('updateChannelQueries'); - return db.channelQueryDao.updateChannelQueries( - filter, - cids, - clearQueryCache, - ); - }); - } - - @override - Future updateChannels(List channels) { - return readProtected(() async { - _logger.info('updateChannels'); - return db.channelDao.updateChannels(channels); - }); - } - - @override - Future updateMembers(String cid, List members) { - return readProtected(() async { - _logger.info('updateMembers'); - return db.memberDao.updateMembers(cid, members); - }); - } - - @override - Future updateMessages(String cid, List messages) { - return readProtected(() async { - _logger.info('updateMessages'); - return db.messageDao.updateMessages(cid, messages); - }); - } - - @override - Future updatePinnedMessages(String cid, List messages) { - return readProtected(() async { - _logger.info('updatePinnedMessages'); - return db.pinnedMessageDao.updateMessages(cid, messages); - }); - } - - @override - Future updateReactions(List reactions) { - return readProtected(() async { - _logger.info('updateReactions'); - return db.reactionDao.updateReactions(reactions); - }); - } - - @override - Future updateReads(String cid, List reads) { - return readProtected(() async { - _logger.info('updateReads'); - return db.readDao.updateReads(cid, reads); - }); - } - - @override - Future updateUsers(List users) { - return readProtected(() async { - _logger.info('updateUsers'); - return db.userDao.updateUsers(users); - }); - } - - @override - Future deleteReactionsByMessageId(List messageIds) { - return readProtected(() async { - _logger.info('deleteReactionsByMessageId'); - return db.reactionDao.deleteReactionsByMessageIds(messageIds); - }); - } - - @override - Future deleteMembersByCids(List cids) { - return readProtected(() async { - _logger.info('deleteMembersByCids'); - return db.memberDao.deleteMemberByCids(cids); - }); - } - - @override - Future updateChannelStates(List channelStates) { - return readProtected(() async { - return db.transaction(() async { - await super.updateChannelStates(channelStates); + ) => + _readProtected(() async { + _logger.info('updateChannelQueries'); + return db.channelQueryDao.updateChannelQueries( + filter, + cids, + clearQueryCache: clearQueryCache, + ); }); - }); - } @override - Future disconnect({bool flush = false}) async { - return _mutex.protectWrite(() async { - _logger.info('disconnect'); - if (db != null) { - _logger.info('Disconnecting'); - if (flush) { - _logger.info('Flushing'); - await db.batch((batch) { - db.allTables.forEach((table) { - db.delete(table).go(); + Future updateChannels(List channels) => + _readProtected(() async { + _logger.info('updateChannels'); + return db.channelDao.updateChannels(channels); + }); + + @override + Future updateMembers(String cid, List members) => + _readProtected(() async { + _logger.info('updateMembers'); + return db.memberDao.updateMembers(cid, members); + }); + + @override + Future updateMessages(String cid, List messages) => + _readProtected(() async { + _logger.info('updateMessages'); + return db.messageDao.updateMessages(cid, messages); + }); + + @override + Future updatePinnedMessages(String cid, List messages) => + _readProtected(() async { + _logger.info('updatePinnedMessages'); + return db.pinnedMessageDao.updateMessages(cid, messages); + }); + + @override + Future updateReactions(List reactions) => + _readProtected(() async { + _logger.info('updateReactions'); + return db.reactionDao.updateReactions(reactions); + }); + + @override + Future updateReads(String cid, List reads) => + _readProtected(() async { + _logger.info('updateReads'); + return db.readDao.updateReads(cid, reads); + }); + + @override + Future updateUsers(List users) => _readProtected(() async { + _logger.info('updateUsers'); + return db.userDao.updateUsers(users); + }); + + @override + Future deleteReactionsByMessageId(List messageIds) => + _readProtected(() async { + _logger.info('deleteReactionsByMessageId'); + return db.reactionDao.deleteReactionsByMessageIds(messageIds); + }); + + @override + Future deleteMembersByCids(List cids) => + _readProtected(() async { + _logger.info('deleteMembersByCids'); + return db.memberDao.deleteMemberByCids(cids); + }); + + @override + Future updateChannelStates(List channelStates) => + _readProtected(() async => db.transaction(() async { + await super.updateChannelStates(channelStates); + })); + + @override + Future disconnect({bool flush = false}) async => + _mutex.protectWrite(() async { + _logger.info('disconnect'); + if (db != null) { + _logger.info('Disconnecting'); + if (flush) { + _logger.info('Flushing'); + await db.batch((batch) { + db.allTables.forEach((table) { + db.delete(table).go(); + }); }); - }); + } + await db.disconnect(); + db = null; } - await db.disconnect(); - db = null; - } - }); - } + }); } diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 06f991e1..a91ac666 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: path_provider: ^1.6.27 sqlite3_flutter_libs: ^0.4.0+1 stream_chat: - git: + git: url: https://github.com/GetStream/stream-chat-flutter.git ref: develop path: packages/stream_chat From 8c0da62f792e68d0b08e1bff4f9bfd355130aef7 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 15 Mar 2021 12:05:36 +0530 Subject: [PATCH 002/599] style: flutter format Signed-off-by: Sahil Kumar --- .../lib/src/mapper/reaction_mapper.dart | 30 +++++++++---------- .../lib/src/mapper/read_mapper.dart | 18 +++++------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart index 3f8533db..20842307 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart @@ -5,25 +5,25 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; extension ReactionEntityX on ReactionEntity { /// Maps a [ReactionEntity] into [Reaction] Reaction toReaction({User user}) => Reaction( - extraData: extraData, - type: type, - createdAt: createdAt, - userId: userId, - user: user, - messageId: messageId, - score: score, - ); + extraData: extraData, + type: type, + createdAt: createdAt, + userId: userId, + user: user, + messageId: messageId, + score: score, + ); } /// Useful mapping functions for [Reaction] extension ReactionX on Reaction { /// Maps a [Reaction] into [ReactionEntity] ReactionEntity toEntity() => ReactionEntity( - extraData: extraData, - type: type, - createdAt: createdAt, - userId: userId, - messageId: messageId, - score: score, - ); + extraData: extraData, + type: type, + createdAt: createdAt, + userId: userId, + messageId: messageId, + score: score, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart index 5493c5da..9664030e 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart @@ -5,19 +5,19 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; extension ReadEntityX on ReadEntity { /// Maps a [ReadEntity] into [Read] Read toRead({User user}) => Read( - user: user, - lastRead: lastRead, - unreadMessages: unreadMessages, - ); + user: user, + lastRead: lastRead, + unreadMessages: unreadMessages, + ); } /// Useful mapping functions for [Read] extension ReadX on Read { /// Maps a [Read] into [ReadEntity] ReadEntity toEntity({String cid}) => ReadEntity( - lastRead: lastRead, - userId: user?.id, - channelCid: cid, - unreadMessages: unreadMessages, - ); + lastRead: lastRead, + userId: user?.id, + channelCid: cid, + unreadMessages: unreadMessages, + ); } From 4cbbc059d3a8e76e78c2e770144380cf468ba498 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 15 Mar 2021 14:17:01 +0530 Subject: [PATCH 003/599] test(persistence): Add tests for channel dao Signed-off-by: Sahil Kumar --- .../lib/src/db/moor_chat_database.dart | 19 +-- .../lib/src/db/shared/native_db.dart | 21 ++- .../lib/src/db/shared/unsupported_db.dart | 1 - .../lib/src/db/shared/web_db.dart | 1 - .../lib/src/mapper/channel_mapper.dart | 4 +- .../test/src/dao/channel_dao_test.dart | 139 ++++++++++++++++++ 6 files changed, 160 insertions(+), 25 deletions(-) create mode 100644 packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart 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 cc3e86b3..2edaed4f 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 @@ -1,23 +1,22 @@ -import 'package:moor/isolate.dart'; +import 'package:meta/meta.dart'; +import 'package:moor/ffi.dart'; import 'package:moor/moor.dart'; import 'package:stream_chat/stream_chat.dart'; -import '../converter/converter.dart'; -import '../dao/dao.dart'; -import '../entity/entity.dart'; -import 'shared/shared_db.dart'; +import 'package:stream_chat_persistence/src/converter/converter.dart'; +import 'package:stream_chat_persistence/src/dao/dao.dart'; +import 'package:stream_chat_persistence/src/entity/entity.dart'; +import 'package:stream_chat_persistence/src/db/shared/shared_db.dart'; part 'moor_chat_database.g.dart'; LazyDatabase _openConnection( String userId, { bool logStatements = false, - bool persistOnDisk = true, }) => LazyDatabase(() async => SharedDB.constructDatabase( userId, logStatements: logStatements, - persistOnDisk: persistOnDisk, )); /// A chat database implemented using moor @@ -47,11 +46,9 @@ class MoorChatDatabase extends _$MoorChatDatabase { MoorChatDatabase( this._userId, { logStatements = false, - bool persistOnDisk = true, }) : super(_openConnection( _userId, logStatements: logStatements, - persistOnDisk: persistOnDisk, )); /// Instantiate a new database instance @@ -60,6 +57,10 @@ class MoorChatDatabase extends _$MoorChatDatabase { DatabaseConnection connection, ) : super.connect(connection); + /// Custom constructor used only for testing + @visibleForTesting + MoorChatDatabase.testable(this._userId) : super(VmDatabase.memory()); + final String _userId; /// User id to which the database is connected diff --git a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart index 7592247e..a3b1ce8e 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart @@ -21,20 +21,17 @@ class SharedDB { static Future constructDatabase( String userId, { bool logStatements = false, - bool persistOnDisk = true, }) async { final dbName = 'db_$userId'; - if (persistOnDisk) { - if (Platform.isIOS || Platform.isAndroid) { - final dir = await getApplicationDocumentsDirectory(); - final path = join(dir.path, '$dbName.sqlite'); - final file = File(path); - return VmDatabase(file, logStatements: logStatements); - } - if (Platform.isMacOS || Platform.isLinux) { - final file = File('$dbName.sqlite'); - return VmDatabase(file, logStatements: logStatements); - } + if (Platform.isIOS || Platform.isAndroid) { + final dir = await getApplicationDocumentsDirectory(); + final path = join(dir.path, '$dbName.sqlite'); + final file = File(path); + return VmDatabase(file, logStatements: logStatements); + } + if (Platform.isMacOS || Platform.isLinux) { + final file = File('$dbName.sqlite'); + return VmDatabase(file, logStatements: logStatements); } return VmDatabase.memory(logStatements: logStatements); } diff --git a/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart index 3a764d24..15734a7c 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart @@ -10,7 +10,6 @@ class SharedDB { static Future constructDatabase( String userId, { bool logStatements = false, - bool persistOnDisk = true, }) { throw UnsupportedError( 'No implementation of the constructDatabase api provided'); diff --git a/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart index 32b108ba..7388543e 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart @@ -12,7 +12,6 @@ class SharedDB { static Future constructDatabase( String userId, { bool logStatements = false, - bool persistOnDisk = true, // ignored on web }) async { final dbName = 'db_$userId'; return WebDatabase(dbName, logStatements: logStatements); diff --git a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart index 5c8e3e2a..65315519 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart @@ -46,14 +46,14 @@ extension ChannelModelX on ChannelModel { id: id, type: type, cid: cid, - config: config.toJson(), + config: config?.toJson(), frozen: frozen, lastMessageAt: lastMessageAt, createdAt: createdAt, updatedAt: updatedAt, deletedAt: deletedAt, memberCount: memberCount, - createdById: createdBy.id, + createdById: createdBy?.id, extraData: extraData, ); } diff --git a/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart new file mode 100644 index 00000000..9d6a3070 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart @@ -0,0 +1,139 @@ +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/dao/channel_dao.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:test/test.dart'; + +void main() { + ChannelDao channelDao; + MoorChatDatabase database; + + setUp(() { + database = MoorChatDatabase.testable('testUserId'); + channelDao = database.channelDao; + }); + + test('getChannelByCid', () async { + const id = 'testId'; + const cid = 'testCid'; + const type = 'testType'; + + // Should be null initially + final channel = await channelDao.getChannelByCid(cid); + expect(channel, isNull); + + // Saving a dummy channel + final dummyChannel = ChannelModel( + id: id, + type: type, + cid: cid, + config: ChannelConfig(), + ); + await channelDao.updateChannels([dummyChannel]); + + // Should match the dummy channel + final updatedChannel = await channelDao.getChannelByCid(cid); + expect(updatedChannel.id, id); + expect(updatedChannel.cid, cid); + expect(updatedChannel.type, type); + }); + + test('deleteChannelByCids', () async { + const id = 'testId'; + const cid = 'testCid'; + const type = 'testType'; + + // Saving a dummy channel + final dummyChannel = ChannelModel( + id: id, + type: type, + cid: cid, + config: ChannelConfig(), + ); + await channelDao.updateChannels([dummyChannel]); + + // Should match the dummy channel + final updatedChannel = await channelDao.getChannelByCid(cid); + expect(updatedChannel.id, id); + expect(updatedChannel.cid, cid); + expect(updatedChannel.type, type); + + // Deleting the dummyChannel using cid + await channelDao.deleteChannelByCids([cid]); + + // Fetched channel Should be null + final channel = await channelDao.getChannelByCid(cid); + expect(channel, isNull); + }); + + test('cids', () async { + // Should be empty initially + final cids = await channelDao.cids; + expect(cids, []); + + const id = 'testId'; + const cid = 'testCid'; + const type = 'testType'; + + // Saving a dummy channel + final dummyChannel = ChannelModel( + id: id, + type: type, + cid: cid, + config: ChannelConfig(), + ); + await channelDao.updateChannels([dummyChannel]); + + // Should return the cid of the dummy channel + final updatedCids = await channelDao.cids; + expect(updatedCids, [cid]); + }); + +// /// Updates all the channels using the new [channelList] data +// Future updateChannels(List channelList) => batch( +// (it) => it.insertAll( +// channels, +// channelList.map((c) => c.toEntity()).toList(), +// mode: InsertMode.insertOrReplace, +// ), +// ); + + test('updateChannels', () async { + const id = 'testId'; + const cid = 'testCid'; + const type = 'testType'; + + // Should be null initially + final channel = await channelDao.getChannelByCid(cid); + expect(channel, isNull); + + // Saving a dummy channel + final dummyChannel = ChannelModel( + id: id, + type: type, + cid: cid, + config: ChannelConfig(), + ); + await channelDao.updateChannels([dummyChannel]); + + // Should match the dummy channel + final updatedChannel = await channelDao.getChannelByCid(cid); + expect(updatedChannel.id, id); + expect(updatedChannel.cid, cid); + expect(updatedChannel.type, type); + + // Updating the previously saved channel + const newType = 'newTestType'; + final newChannel = dummyChannel.copyWith(type: newType); + await channelDao.updateChannels([newChannel]); + + // Should match the new channel + final newUpdatedChannel = await channelDao.getChannelByCid(cid); + expect(newUpdatedChannel.id, id); + expect(newUpdatedChannel.cid, cid); + expect(newUpdatedChannel.type, newType); + }); + + tearDown(() async { + await database.disconnect(); + }); +} From ae65b2b8b2d9c14125784107ce8e4359257a4b52 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 15 Mar 2021 14:20:00 +0530 Subject: [PATCH 004/599] refactor(persistence): remove dummy comments Signed-off-by: Sahil Kumar --- .../test/src/dao/channel_dao_test.dart | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart index 9d6a3070..47c655a8 100644 --- a/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart @@ -88,15 +88,6 @@ void main() { expect(updatedCids, [cid]); }); -// /// Updates all the channels using the new [channelList] data -// Future updateChannels(List channelList) => batch( -// (it) => it.insertAll( -// channels, -// channelList.map((c) => c.toEntity()).toList(), -// mode: InsertMode.insertOrReplace, -// ), -// ); - test('updateChannels', () async { const id = 'testId'; const cid = 'testCid'; From 083ca282837e495054790bf058f356562e373337 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 15 Mar 2021 16:48:56 +0530 Subject: [PATCH 005/599] test(persistence): Add test for channel query dao Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/client.dart | 3 +- .../lib/src/db/chat_persistence_client.dart | 7 +- .../lib/src/dao/channel_query_dao.dart | 17 +- .../test/src/dao/channel_query_dao_test.dart | 270 ++++++++++++++++++ 4 files changed, 285 insertions(+), 12 deletions(-) create mode 100644 packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 11619b35..8b9d3a75 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -738,7 +738,8 @@ class StreamChatClient { await _chatPersistenceClient?.updateChannelQueries( filter, channels.map((c) => c.channel.cid).toList(), - paginationParams?.offset == null || paginationParams.offset == 0, + clearQueryCache: + paginationParams?.offset == null || paginationParams.offset == 0, ); state.channels = updateData.key; diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index d90872a2..d15772df 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -100,10 +100,9 @@ abstract class ChatPersistenceClient { /// the list of matching rows will be deleted Future updateChannelQueries( Map filter, - List cids, - // ignore: avoid_positional_boolean_parameters - bool clearQueryCache, - ); + List cids, { + bool clearQueryCache = false, + }); /// Remove a message by [messageId] Future deleteMessageById(String messageId) => diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart index 785b5946..233fd4ef 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart @@ -32,7 +32,7 @@ class ChannelQueryDao extends DatabaseAccessor Future updateChannelQueries( Map filter, List cids, { - bool clearQueryCache, + bool clearQueryCache = false, }) async => transaction(() async { final hash = _computeHash(filter); @@ -57,6 +57,14 @@ class ChannelQueryDao extends DatabaseAccessor }); }); + /// + Future> getCachedChannelCids(Map filter) { + final hash = _computeHash(filter); + return (select(channelQueries)..where((c) => c.queryHash.equals(hash))) + .map((c) => c.channelCid) + .get(); + } + /// Get list of channels by filter, sort and paginationParams Future> getChannels({ Map filter, @@ -72,12 +80,7 @@ class ChannelQueryDao extends DatabaseAccessor return true; }(), ''); - final hash = _computeHash(filter); - final cachedChannelCids = await (select(channelQueries) - ..where((c) => c.queryHash.equals(hash))) - .map((c) => c.channelCid) - .get(); - + final cachedChannelCids = await getCachedChannelCids(filter); final query = select(channels)..where((c) => c.cid.isIn(cachedChannelCids)); final cachedChannels = await (query.join([ 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 new file mode 100644 index 00000000..1a437dfc --- /dev/null +++ b/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart @@ -0,0 +1,270 @@ +import 'dart:math' as math; + +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/dao/channel_query_dao.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:test/test.dart'; + +void main() { + MoorChatDatabase database; + ChannelQueryDao channelQueryDao; + + setUp(() { + database = MoorChatDatabase.testable('testUserId'); + channelQueryDao = database.channelQueryDao; + }); + + test('updateChannelQueries', () async { + const filter = { + 'members': { + r'$in': ['testUserId'], + }, + }; + + const cids = ['testCid1', 'testCid2', 'testCid3']; + + final cachedCids = await channelQueryDao.getCachedChannelCids(filter); + expect(cachedCids, []); + + // Updating channel queries + await channelQueryDao.updateChannelQueries(filter, cids); + + final updatedCids = await channelQueryDao.getCachedChannelCids(filter); + expect(updatedCids, cids); + }); + + test('getCachedChannelCids', () async { + const filter = { + 'members': { + r'$in': ['testUserId'], + }, + }; + + const cids = ['testCid1', 'testCid2', 'testCid3']; + + final cachedCids = await channelQueryDao.getCachedChannelCids(filter); + expect(cachedCids, []); + + // Updating channel queries + await channelQueryDao.updateChannelQueries(filter, cids); + + final updatedCids = await channelQueryDao.getCachedChannelCids(filter); + expect(updatedCids, cids); + }); + + Future> _insertTestDataForGetChannel( + Map filter) async { + final now = DateTime.now(); + final userDao = database.userDao; + final channelDao = database.channelDao; + + const cids = ['testCid0', 'testCid1', 'testCid2']; + final users = List.generate(3, (index) => User(id: 'testId$index')); + final channels = List.generate( + 3, + (index) => ChannelModel( + id: 'testId$index', + type: 'testType$index', + cid: cids[index], + createdBy: users[index], + config: ChannelConfig(), + extraData: {'test_custom_field': math.Random().nextInt(100)}, + createdAt: now, + memberCount: math.Random().nextInt(100), + lastMessageAt: now.add(Duration(hours: index)), + ), + ).reversed.toList(growable: false); + + await userDao.updateUsers(users); + await channelDao.updateChannels(channels); + await channelQueryDao.updateChannelQueries(filter, cids); + + return channels; + } + + group('getChannels', () { + const filter = { + 'members': { + r'$in': ['testUserId'], + }, + }; + + test('should return empty list of channels', () async { + final channels = await channelQueryDao.getChannels(filter: filter); + expect(channels, []); + }); + + test('should return all the inserted channels', () async { + // Inserting test data for get channels + final insertedChannels = await _insertTestDataForGetChannel(filter); + + // Should match with the inserted channels + final updatedChannels = await channelQueryDao.getChannels(filter: filter); + expect(updatedChannels.length, insertedChannels.length); + for (var i = 0; i < updatedChannels.length; i++) { + final updatedChannel = updatedChannels[i]; + final insertedChannel = insertedChannels[i]; + + // Should match all the basic details + expect(updatedChannel.id, insertedChannel.id); + expect(updatedChannel.type, insertedChannel.type); + expect(updatedChannel.cid, insertedChannel.cid); + expect(updatedChannel.memberCount, insertedChannel.memberCount); + + // Should match createdAt date + expect( + updatedChannel.createdAt.hour, + insertedChannel.createdAt.hour, + ); + expect( + updatedChannel.createdAt.minute, + insertedChannel.createdAt.minute, + ); + expect( + updatedChannel.createdAt.second, + insertedChannel.createdAt.second, + ); + + // Should match lastMessageAt date + expect( + updatedChannel.lastMessageAt.hour, + insertedChannel.lastMessageAt.hour, + ); + expect( + updatedChannel.lastMessageAt.minute, + insertedChannel.lastMessageAt.minute, + ); + expect( + updatedChannel.lastMessageAt.second, + insertedChannel.lastMessageAt.second, + ); + } + }); + + test('should return sorted channels using member count', () async { + int sortComparator(ChannelModel a, ChannelModel b) => + b.memberCount.compareTo(a.memberCount); + + // Inserting test data for get channels + final insertedChannels = await _insertTestDataForGetChannel(filter); + insertedChannels.sort(sortComparator); + + // Should match with the inserted channels + final updatedChannels = await channelQueryDao.getChannels( + filter: filter, + sort: [SortOption('member_count', comparator: sortComparator)], + ); + + expect(updatedChannels.length, insertedChannels.length); + for (var i = 0; i < updatedChannels.length; i++) { + final updatedChannel = updatedChannels[i]; + final insertedChannel = insertedChannels[i]; + + // Should match all the basic details + expect(updatedChannel.id, insertedChannel.id); + expect(updatedChannel.type, insertedChannel.type); + expect(updatedChannel.cid, insertedChannel.cid); + expect(updatedChannel.memberCount, insertedChannel.memberCount); + + // Should match createdAt date + expect( + updatedChannel.createdAt.hour, + insertedChannel.createdAt.hour, + ); + expect( + updatedChannel.createdAt.minute, + insertedChannel.createdAt.minute, + ); + expect( + updatedChannel.createdAt.second, + insertedChannel.createdAt.second, + ); + + // Should match lastMessageAt date + expect( + updatedChannel.lastMessageAt.hour, + insertedChannel.lastMessageAt.hour, + ); + expect( + updatedChannel.lastMessageAt.minute, + insertedChannel.lastMessageAt.minute, + ); + expect( + updatedChannel.lastMessageAt.second, + insertedChannel.lastMessageAt.second, + ); + } + }); + + test('should throw if comparator is not provided in sort list', () { + expect( + () => channelQueryDao.getChannels( + sort: [const SortOption('test_custom_field')], + ), + throwsArgumentError, + ); + }); + + test('should return sorted channels using custom field', () async { + int sortComparator(ChannelModel a, ChannelModel b) { + final aData = a.extraData['test_custom_field'] as int; + final bData = b.extraData['test_custom_field'] as int; + return bData.compareTo(aData); + } + + // Inserting test data for get channels + final insertedChannels = await _insertTestDataForGetChannel(filter); + insertedChannels.sort(sortComparator); + + // Should match with the inserted channels + final updatedChannels = await channelQueryDao.getChannels( + filter: filter, + sort: [SortOption('test_custom_field', comparator: sortComparator)], + ); + + expect(updatedChannels.length, insertedChannels.length); + for (var i = 0; i < updatedChannels.length; i++) { + final updatedChannel = updatedChannels[i]; + final insertedChannel = insertedChannels[i]; + + // Should match all the basic details + expect(updatedChannel.id, insertedChannel.id); + expect(updatedChannel.type, insertedChannel.type); + expect(updatedChannel.cid, insertedChannel.cid); + expect(updatedChannel.memberCount, insertedChannel.memberCount); + + // Should match createdAt date + expect( + updatedChannel.createdAt.hour, + insertedChannel.createdAt.hour, + ); + expect( + updatedChannel.createdAt.minute, + insertedChannel.createdAt.minute, + ); + expect( + updatedChannel.createdAt.second, + insertedChannel.createdAt.second, + ); + + // Should match lastMessageAt date + expect( + updatedChannel.lastMessageAt.hour, + insertedChannel.lastMessageAt.hour, + ); + expect( + updatedChannel.lastMessageAt.minute, + insertedChannel.lastMessageAt.minute, + ); + expect( + updatedChannel.lastMessageAt.second, + insertedChannel.lastMessageAt.second, + ); + } + }); + }); + + tearDown(() async { + await database.disconnect(); + }); +} From edc8c4d1814526a15e5a086666ed39b78dbe2984 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 16 Mar 2021 15:51:41 +0530 Subject: [PATCH 006/599] test(persistence): add tests for connection event dao Signed-off-by: Sahil Kumar --- .../stream_chat/lib/src/models/event.dart | 40 ++++++ packages/stream_chat_persistence/pubspec.yaml | 7 +- .../test/src/dao/channel_query_dao_test.dart | 80 +++--------- .../src/dao/connection_event_dao_test.dart | 114 ++++++++++++++++++ .../test/src/utils/date_matcher.dart | 26 ++++ 5 files changed, 202 insertions(+), 65 deletions(-) create mode 100644 packages/stream_chat_persistence/test/src/dao/connection_event_dao_test.dart create mode 100644 packages/stream_chat_persistence/test/src/utils/date_matcher.dart diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index b1140ebc..28cf1b38 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -121,6 +121,46 @@ class Event { _$EventToJson(this), topLevelFields, ); + + /// Creates a copy of [Event] with specified attributes overridden. + Event copyWith({ + String type, + String cid, + String channelId, + String channelType, + String connectionId, + DateTime createdAt, + OwnUser me, + User user, + Message message, + EventChannel channel, + Member member, + Reaction reaction, + int totalUnreadCount, + int unreadChannels, + bool online, + String parentId, + Map extraData, + }) => + Event( + type: type ?? this.type, + cid: cid ?? this.cid, + connectionId: connectionId ?? this.connectionId, + createdAt: createdAt ?? this.createdAt, + me: me ?? this.me, + user: user ?? this.user, + message: message ?? this.message, + totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, + unreadChannels: unreadChannels ?? this.unreadChannels, + reaction: reaction ?? this.reaction, + online: online ?? this.online, + channel: channel ?? this.channel, + member: member ?? this.member, + channelId: channelId ?? this.channelId, + channelType: channelType ?? this.channelType, + parentId: parentId ?? this.parentId, + extraData: extraData ?? this.extraData, + ); } /// The channel embedded in the event object diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index a91ac666..8b235710 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -22,8 +22,11 @@ dependencies: ref: develop path: packages/stream_chat +dependency_overrides: + stream_chat: + path: ../stream_chat + dev_dependencies: test: ^1.15.7 build_runner: ^1.11.0 - moor_generator: ^3.4.1 - pedantic: ^1.9.2 + moor_generator: ^3.4.1 \ No newline at end of file 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 1a437dfc..dd9ecf0b 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 @@ -5,6 +5,8 @@ import 'package:stream_chat_persistence/src/dao/channel_query_dao.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; +import '../utils/date_matcher.dart'; + void main() { MoorChatDatabase database; ChannelQueryDao channelQueryDao; @@ -24,7 +26,7 @@ void main() { const cids = ['testCid1', 'testCid2', 'testCid3']; final cachedCids = await channelQueryDao.getCachedChannelCids(filter); - expect(cachedCids, []); + expect(cachedCids, isEmpty); // Updating channel queries await channelQueryDao.updateChannelQueries(filter, cids); @@ -43,7 +45,7 @@ void main() { const cids = ['testCid1', 'testCid2', 'testCid3']; final cachedCids = await channelQueryDao.getCachedChannelCids(filter); - expect(cachedCids, []); + expect(cachedCids, isEmpty); // Updating channel queries await channelQueryDao.updateChannelQueries(filter, cids); @@ -91,7 +93,7 @@ void main() { test('should return empty list of channels', () async { final channels = await channelQueryDao.getChannels(filter: filter); - expect(channels, []); + expect(channels, isEmpty); }); test('should return all the inserted channels', () async { @@ -113,30 +115,14 @@ void main() { // Should match createdAt date expect( - updatedChannel.createdAt.hour, - insertedChannel.createdAt.hour, - ); - expect( - updatedChannel.createdAt.minute, - insertedChannel.createdAt.minute, - ); - expect( - updatedChannel.createdAt.second, - insertedChannel.createdAt.second, + updatedChannel.createdAt, + isSameDateAs(insertedChannel.createdAt), ); // Should match lastMessageAt date expect( - updatedChannel.lastMessageAt.hour, - insertedChannel.lastMessageAt.hour, - ); - expect( - updatedChannel.lastMessageAt.minute, - insertedChannel.lastMessageAt.minute, - ); - expect( - updatedChannel.lastMessageAt.second, - insertedChannel.lastMessageAt.second, + updatedChannel.lastMessageAt, + isSameDateAs(insertedChannel.lastMessageAt), ); } }); @@ -168,30 +154,14 @@ void main() { // Should match createdAt date expect( - updatedChannel.createdAt.hour, - insertedChannel.createdAt.hour, - ); - expect( - updatedChannel.createdAt.minute, - insertedChannel.createdAt.minute, - ); - expect( - updatedChannel.createdAt.second, - insertedChannel.createdAt.second, + updatedChannel.createdAt, + isSameDateAs(insertedChannel.createdAt), ); // Should match lastMessageAt date expect( - updatedChannel.lastMessageAt.hour, - insertedChannel.lastMessageAt.hour, - ); - expect( - updatedChannel.lastMessageAt.minute, - insertedChannel.lastMessageAt.minute, - ); - expect( - updatedChannel.lastMessageAt.second, - insertedChannel.lastMessageAt.second, + updatedChannel.lastMessageAt, + isSameDateAs(insertedChannel.lastMessageAt), ); } }); @@ -235,30 +205,14 @@ void main() { // Should match createdAt date expect( - updatedChannel.createdAt.hour, - insertedChannel.createdAt.hour, - ); - expect( - updatedChannel.createdAt.minute, - insertedChannel.createdAt.minute, - ); - expect( - updatedChannel.createdAt.second, - insertedChannel.createdAt.second, + updatedChannel.createdAt, + isSameDateAs(insertedChannel.createdAt), ); // Should match lastMessageAt date expect( - updatedChannel.lastMessageAt.hour, - insertedChannel.lastMessageAt.hour, - ); - expect( - updatedChannel.lastMessageAt.minute, - insertedChannel.lastMessageAt.minute, - ); - expect( - updatedChannel.lastMessageAt.second, - insertedChannel.lastMessageAt.second, + updatedChannel.lastMessageAt, + isSameDateAs(insertedChannel.lastMessageAt), ); } }); diff --git a/packages/stream_chat_persistence/test/src/dao/connection_event_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/connection_event_dao_test.dart new file mode 100644 index 00000000..2969751b --- /dev/null +++ b/packages/stream_chat_persistence/test/src/dao/connection_event_dao_test.dart @@ -0,0 +1,114 @@ +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/dao/connection_event_dao.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:test/test.dart'; + +import '../utils/date_matcher.dart'; + +void main() { + ConnectionEventDao eventDao; + MoorChatDatabase database; + + setUp(() { + database = MoorChatDatabase.testable('testUserId'); + eventDao = database.connectionEventDao; + }); + + test('connectionEvent', () async { + // Should be null initially + final event = await eventDao.connectionEvent; + expect(event, isNull); + + // Adding a new event + final newEvent = Event( + createdAt: DateTime.now(), + totalUnreadCount: 33, + unreadChannels: 3, + me: OwnUser(id: 'testUserId'), + ); + await eventDao.updateConnectionEvent(newEvent); + + // Should match the added event + final updatedEvent = await eventDao.connectionEvent; + expect(updatedEvent.me.id, newEvent.me.id); + expect(updatedEvent.totalUnreadCount, newEvent.totalUnreadCount); + expect(updatedEvent.unreadChannels, newEvent.unreadChannels); + }); + + test('lastSyncAt', () async { + // Should be null initially + final lastSyncAt = await eventDao.lastSyncAt; + expect(lastSyncAt, isNull); + + // Adding an event for testing + final event = Event( + createdAt: DateTime.now(), + totalUnreadCount: 33, + unreadChannels: 3, + me: OwnUser(id: 'testUserId'), + ); + await eventDao.updateConnectionEvent(event); + + // Updating it's last sync + final now = DateTime.now(); + await eventDao.updateLastSyncAt(now); + + // Should match the updated last sync + final updatedLastSyncAt = await eventDao.lastSyncAt; + expect(updatedLastSyncAt, isSameDateAs(now)); + }); + + test('updateConnectionEvent', () async { + // Adding and event for testing + final event = Event( + createdAt: DateTime.now(), + totalUnreadCount: 33, + unreadChannels: 3, + me: OwnUser(id: 'testUserId'), + ); + await eventDao.updateConnectionEvent(event); + + // Should match the previously added event + final fetchedEvent = await eventDao.connectionEvent; + expect(fetchedEvent.me.id, event.me.id); + expect(fetchedEvent.totalUnreadCount, event.totalUnreadCount); + expect(fetchedEvent.unreadChannels, event.unreadChannels); + + // Updating the added event + final newEvent = event.copyWith(unreadChannels: 4); + await eventDao.updateConnectionEvent(newEvent); + + // Should match the updated event + final fetchedNewEvent = await eventDao.connectionEvent; + expect(fetchedNewEvent.me.id, event.me.id); + expect(fetchedNewEvent.totalUnreadCount, event.totalUnreadCount); + expect(fetchedNewEvent.unreadChannels, newEvent.unreadChannels); + }); + + test('updateLastSyncAt', () async { + // Should be null initially + final lastSyncAt = await eventDao.lastSyncAt; + expect(lastSyncAt, isNull); + + // Adding an event just for testing + final event = Event( + createdAt: DateTime.now(), + totalUnreadCount: 33, + unreadChannels: 3, + me: OwnUser(id: 'testUserId'), + ); + await eventDao.updateConnectionEvent(event); + + // Updating it's last sync + final now = DateTime.now(); + await eventDao.updateLastSyncAt(now); + + // Should match the last sync + final updatedLastSyncAt = await eventDao.lastSyncAt; + expect(updatedLastSyncAt, isSameDateAs(now)); + }); + + tearDown(() async { + await database.disconnect(); + }); +} diff --git a/packages/stream_chat_persistence/test/src/utils/date_matcher.dart b/packages/stream_chat_persistence/test/src/utils/date_matcher.dart new file mode 100644 index 00000000..909e58b0 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/utils/date_matcher.dart @@ -0,0 +1,26 @@ +import 'package:test/test.dart'; +import 'package:meta/meta.dart'; + +Matcher isSameDateAs(DateTime targetDate) => + _IsSameDateAs(targetDate: targetDate); + +class _IsSameDateAs extends Matcher { + const _IsSameDateAs({ + @required this.targetDate, + }) : assert(targetDate != null, ''); + + final DateTime targetDate; + + @override + bool matches(covariant DateTime date, Map matchState) => + date.year == targetDate.year && + date.month == targetDate.month && + date.day == targetDate.day && + date.hour == targetDate.hour && + date.minute == targetDate.minute && + date.second == targetDate.second; + + @override + Description describe(Description description) => + description.add('is same date as $targetDate'); +} From 2e2061614554a27bcd479d6505081dc448395097 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 16 Mar 2021 16:42:47 +0530 Subject: [PATCH 007/599] test(persistence): add test for member dao Signed-off-by: Sahil Kumar --- .../test/src/dao/member_dao_test.dart | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/dao/member_dao_test.dart diff --git a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart new file mode 100644 index 00000000..6671c902 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart @@ -0,0 +1,127 @@ +import 'dart:math' as math; + +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/dao/dao.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:test/test.dart'; + +import '../utils/date_matcher.dart'; + +void main() { + MemberDao memberDao; + MoorChatDatabase database; + + setUp(() { + database = MoorChatDatabase.testable('testUserId'); + memberDao = database.memberDao; + }); + + Future> _prepareTestData(String cid) async { + final users = List.generate(3, (index) => User(id: 'testUserId$index')); + final memberList = List.generate( + 3, + (index) => Member( + user: users[index], + banned: math.Random().nextBool(), + shadowBanned: math.Random().nextBool(), + createdAt: DateTime.now(), + isModerator: math.Random().nextBool(), + invited: math.Random().nextBool(), + inviteAcceptedAt: DateTime.now(), + role: 'testRole', + updatedAt: DateTime.now(), + ), + ); + await database.userDao.updateUsers(users); + await memberDao.updateMembers(cid, memberList); + return memberList; + } + + test('getMembersByCid', () async { + const cid = 'testCid'; + + // Should be empty initially + final members = await memberDao.getMembersByCid(cid); + expect(members, isEmpty); + + // Preparing test data + final memberList = await _prepareTestData(cid); + + // Should match the previous test data + final fetchedMembers = await memberDao.getMembersByCid(cid); + expect(fetchedMembers.length, memberList.length); + for (var i = 0; i < fetchedMembers.length; i++) { + final member = memberList[i]; + final fetchedMember = fetchedMembers[i]; + expect(fetchedMember.user.id, member.user.id); + expect(fetchedMember.banned, member.banned); + expect(fetchedMember.shadowBanned, member.shadowBanned); + expect(fetchedMember.createdAt, isSameDateAs(member.createdAt)); + expect(fetchedMember.isModerator, member.isModerator); + expect(fetchedMember.invited, member.invited); + expect(fetchedMember.role, member.role); + expect(fetchedMember.updatedAt, isSameDateAs(member.updatedAt)); + expect( + fetchedMember.inviteAcceptedAt, + isSameDateAs(member.inviteAcceptedAt), + ); + } + }); + + test('updateMembers', () async { + const cid = 'testCid'; + + // Preparing test data + final memberList = await _prepareTestData(cid); + + // Should match the previous test data + final fetchedMembers = await memberDao.getMembersByCid(cid); + expect(fetchedMembers.length, memberList.length); + for (var i = 0; i < fetchedMembers.length; i++) { + final member = memberList[i]; + final fetchedMember = fetchedMembers[i]; + expect(fetchedMember.user.id, member.user.id); + expect(fetchedMember.banned, member.banned); + expect(fetchedMember.shadowBanned, member.shadowBanned); + expect(fetchedMember.createdAt, isSameDateAs(member.createdAt)); + expect(fetchedMember.isModerator, member.isModerator); + expect(fetchedMember.invited, member.invited); + expect(fetchedMember.role, member.role); + expect(fetchedMember.updatedAt, isSameDateAs(member.updatedAt)); + expect( + fetchedMember.inviteAcceptedAt, + isSameDateAs(member.inviteAcceptedAt), + ); + } + + // Updating the last added member + final updatedMember = fetchedMembers.last.copyWith(banned: true); + await memberDao.updateMembers(cid, [updatedMember]); + + // Last member banned field should match the previous updated user + final newFetchedMembers = await memberDao.getMembersByCid(cid); + expect(newFetchedMembers.length, fetchedMembers.length); + final lastMember = newFetchedMembers.last; + expect(lastMember.banned, true); + }); + + test('deleteMemberByCids', () async { + const cid = 'testCid'; + + // Preparing test data + final members = await _prepareTestData(cid); + final fetchedMembers = await memberDao.getMembersByCid(cid); + expect(members.length, fetchedMembers.length); + + // Deleting all the members + await memberDao.deleteMemberByCids([cid]); + + // Fetched member list should be empty + final newFetchedMembers = await memberDao.getMembersByCid(cid); + expect(newFetchedMembers, isEmpty); + }); + + tearDown(() async { + await database.disconnect(); + }); +} From a24ec99a44ff21a0c5643e4852106d6f0bfdedb0 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 16 Mar 2021 18:42:06 +0530 Subject: [PATCH 008/599] test(persistence): add tests for message dao Signed-off-by: Sahil Kumar --- .../lib/src/dao/message_dao.dart | 7 +- .../test/src/dao/message_dao_test.dart | 253 ++++++++++++++++++ 2 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 packages/stream_chat_persistence/test/src/dao/message_dao_test.dart 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 0be4446b..ae238069 100644 --- a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart @@ -72,7 +72,7 @@ class MessageDao extends DatabaseAccessor /// [Messages.channelCid] with [cid] Future> getThreadMessages(String cid) async => Future.wait(await (select(messages).join([ - leftOuterJoin(users, messages.userId.equalsExp(_users.id)), + leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), leftOuterJoin(_pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), ]) @@ -89,10 +89,11 @@ class MessageDao extends DatabaseAccessor PaginationParams options, }) async { final msgList = await Future.wait(await (select(messages).join([ - innerJoin(_users, messages.userId.equalsExp(_users.id)), - innerJoin( + leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), + leftOuterJoin( _pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), ]) + ..where(isNotNull(messages.parentId)) ..where(messages.parentId.equals(parentId)) ..orderBy([OrderingTerm.asc(messages.createdAt)])) .map(_messageFromJoinRow) diff --git a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart new file mode 100644 index 00000000..6ee8ca2f --- /dev/null +++ b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart @@ -0,0 +1,253 @@ +import 'dart:math' as math; + +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/dao/dao.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:test/test.dart'; + +void main() { + MessageDao messageDao; + MoorChatDatabase database; + + setUp(() { + database = MoorChatDatabase.testable('testUserId'); + messageDao = database.messageDao; + }); + + Future> _prepareTestData( + String cid, { + bool threads = false, + int count = 3, + }) async { + final messages = List.generate( + count, + (index) => Message( + id: 'testMessageId$cid$index', + type: 'testType', + user: User(id: 'testUserId$index'), + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + showInChannel: math.Random().nextBool(), + replyCount: index, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text #$index', + pinned: math.Random().nextBool(), + pinnedAt: DateTime.now(), + pinnedBy: User(id: 'testUserId$index'), + ), + ); + final threadMessages = List.generate( + count, + (index) => Message( + id: 'testThreadMessageId$cid$index', + type: 'testType', + user: User(id: 'testUserId$index'), + parentId: messages[index].id, + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + showInChannel: math.Random().nextBool(), + replyCount: index, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text #$index', + pinned: math.Random().nextBool(), + pinnedAt: DateTime.now(), + pinnedBy: User(id: 'testUserId$index'), + ), + ); + final allMessages = [...messages, if (threads) ...threadMessages]; + await messageDao.updateMessages(cid, allMessages); + return allMessages; + } + + test('deleteMessageByIds', () async { + const cid = 'testCid'; + + // Preparing test data + final insertedMessages = await _prepareTestData(cid); + + // Fetched message list should match the test message list length + final messages = await messageDao.getMessagesByCid(cid); + expect(messages.length, insertedMessages.length); + + // Deleting 2 messages from DB + await messageDao.deleteMessageByIds( + ['testMessageId${cid}0', 'testMessageId${cid}1'], + ); + + // New fetched messages length should 2 less than the + // previous fetched messages + final newMessages = await messageDao.getMessagesByCid(cid); + expect(newMessages.length, messages.length - 2); + }); + + group('deleteMessageByCids', () { + const cid1 = 'testCid1'; + const cid2 = 'testCid2'; + + test('should delete all the messages of first channel', () async { + // Preparing test data + final cid1InsertedMessages = await _prepareTestData(cid1); + final cid2InsertedMessages = await _prepareTestData(cid2); + + // Fetched message list should match the test message list length + final cid1Messages = await messageDao.getMessagesByCid(cid1); + final cid2Messages = await messageDao.getMessagesByCid(cid2); + expect(cid1Messages.length, cid1InsertedMessages.length); + expect(cid2Messages.length, cid2InsertedMessages.length); + + // Deleting all the messages of cid1 + await messageDao.deleteMessageByCids([cid1]); + + // Fetched messages length of only cid1 should be empty + final cid1FetchedMessages = await messageDao.getMessagesByCid(cid1); + final cid2FetchedMessages = await messageDao.getMessagesByCid(cid2); + expect(cid1FetchedMessages, isEmpty); + expect(cid2FetchedMessages, isNotEmpty); + }); + + test('should delete all the messages of both channel', () async { + // Preparing test data + final cid1InsertedMessages = await _prepareTestData(cid1); + final cid2InsertedMessages = await _prepareTestData(cid2); + + // Fetched message list should match the test message list length + final cid1Messages = await messageDao.getMessagesByCid(cid1); + final cid2Messages = await messageDao.getMessagesByCid(cid2); + expect(cid1Messages.length, cid1InsertedMessages.length); + expect(cid2Messages.length, cid2InsertedMessages.length); + + // Deleting all the messages of cid1 + await messageDao.deleteMessageByCids([cid1, cid2]); + + // Fetched messages length of both cid1 and cid2 should be empty + final cid1FetchedMessages = await messageDao.getMessagesByCid(cid1); + final cid2FetchedMessages = await messageDao.getMessagesByCid(cid2); + expect(cid1FetchedMessages, isEmpty); + expect(cid2FetchedMessages, isEmpty); + }); + }); + + test('getMessageById', () async { + const cid = 'testCid'; + const id = 'testMessageId${cid}0'; + + // Should be null initially + final message = await messageDao.getMessageById(id); + expect(message, isNull); + + // Adding test message with the cid and id + final insertedMessages = await _prepareTestData(cid, count: 1); + expect(insertedMessages.first.id, id); + + // Fetched message id should match the inserted message id + final fetchedMessage = await messageDao.getMessageById(id); + expect(fetchedMessage.id, insertedMessages.first.id); + }); + + test('getThreadMessages', () async { + const cid = 'testCid'; + + // Messages should be empty initially + final messages = await messageDao.getThreadMessages(cid); + expect(messages, isEmpty); + + // Preparing test data + final insertedMessages = await _prepareTestData(cid, threads: true); + expect(insertedMessages, isNotEmpty); + + // Should fetch all the thread messages of cid + final threadMessages = await messageDao.getThreadMessages(cid); + expect(threadMessages, isNotEmpty); + for (final message in threadMessages) { + expect(message.parentId, isNotNull); + } + }); + + test('getThreadMessagesByParentId', () async { + const cid = 'testCid'; + const parentId = 'testMessageId${cid}0'; + + // Messages should be empty initially + final messages = await messageDao.getThreadMessagesByParentId(parentId); + expect(messages, isEmpty); + + // Preparing test data + final insertedMessages = await _prepareTestData(cid, threads: true); + expect(insertedMessages, isNotEmpty); + + // Should fetch all the thread messages of parentId + final threadMessages = + await messageDao.getThreadMessagesByParentId(parentId); + expect(threadMessages.length, 1); + expect(threadMessages.first.parentId, parentId); + }); + + test('getMessagesByCid', () async { + const cid = 'testCid'; + + // Should be empty initially + final messages = await messageDao.getMessagesByCid(cid); + expect(messages, isEmpty); + + // Preparing test data + final insertedMessages = await _prepareTestData(cid); + expect(insertedMessages, isNotEmpty); + + // Fetched message should match the inserted messages + final fetchedMessages = await messageDao.getMessagesByCid(cid); + expect(fetchedMessages.length, insertedMessages.length); + for (var i = 0; i < fetchedMessages.length; i++) { + final fetchedMessage = fetchedMessages[i]; + final insertedMessage = insertedMessages[i]; + expect(fetchedMessage.id, insertedMessage.id); + } + }); + + test('updateMessages', () async { + const cid = 'testCid'; + + // Preparing test data + final insertedMessages = await _prepareTestData(cid); + expect(insertedMessages, isNotEmpty); + + // Modifying one of the message and also adding one new + final copyMessage = insertedMessages.first.copyWith(showInChannel: false); + final newMessage = Message( + id: 'testMessageId${cid}4', + type: 'testType', + user: User(id: 'testUserId4'), + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + showInChannel: math.Random().nextBool(), + replyCount: 4, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text #4', + pinned: math.Random().nextBool(), + pinnedAt: DateTime.now(), + pinnedBy: User(id: 'testUserId4'), + ); + + await messageDao.updateMessages(cid, [copyMessage, newMessage]); + + // Fetched messages length should be one more than inserted message. + // copyMessage `showInChannel` modified field should be false. + // Fetched messages should contain the newMessage. + final fetchedMessages = await messageDao.getMessagesByCid(cid); + expect(fetchedMessages.length, insertedMessages.length + 1); + expect( + fetchedMessages.firstWhere((it) => it.id == copyMessage.id).showInChannel, + false, + ); + expect( + fetchedMessages.map((it) => it.id).contains('testMessageId${cid}4'), + true, + ); + }); + + tearDown(() async { + await database.disconnect(); + }); +} From 8fefa3c1293b629293890c3f19fe801959a8865c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 16 Mar 2021 18:53:07 +0530 Subject: [PATCH 009/599] test(persistence): add test for pinned message dao Signed-off-by: Sahil Kumar --- .../lib/src/dao/pinned_message_dao.dart | 5 +- .../test/src/dao/pinned_message_dao_test.dart | 254 ++++++++++++++++++ 2 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart 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 5e0ae721..cb634703 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 @@ -89,10 +89,11 @@ class PinnedMessageDao extends DatabaseAccessor PaginationParams options, }) async { final msgList = await Future.wait(await (select(pinnedMessages).join([ - innerJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - innerJoin(_pinnedByUsers, + leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), + leftOuterJoin(_pinnedByUsers, pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), ]) + ..where(isNotNull(pinnedMessages.parentId)) ..where(pinnedMessages.parentId.equals(parentId)) ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) .map(_messageFromJoinRow) diff --git a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart new file mode 100644 index 00000000..1af3276e --- /dev/null +++ b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart @@ -0,0 +1,254 @@ +import 'dart:math' as math; + +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/dao/dao.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:test/test.dart'; + +void main() { + PinnedMessageDao pinnedMessageDao; + MoorChatDatabase database; + + setUp(() { + database = MoorChatDatabase.testable('testUserId'); + pinnedMessageDao = database.pinnedMessageDao; + }); + + Future> _prepareTestData( + String cid, { + bool threads = false, + int count = 3, + }) async { + final messages = List.generate( + count, + (index) => Message( + id: 'testMessageId$cid$index', + type: 'testType', + user: User(id: 'testUserId$index'), + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + showInChannel: math.Random().nextBool(), + replyCount: index, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text #$index', + pinned: math.Random().nextBool(), + pinnedAt: DateTime.now(), + pinnedBy: User(id: 'testUserId$index'), + ), + ); + final threadMessages = List.generate( + count, + (index) => Message( + id: 'testThreadMessageId$cid$index', + type: 'testType', + user: User(id: 'testUserId$index'), + parentId: messages[index].id, + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + showInChannel: math.Random().nextBool(), + replyCount: index, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text #$index', + pinned: math.Random().nextBool(), + pinnedAt: DateTime.now(), + pinnedBy: User(id: 'testUserId$index'), + ), + ); + final allMessages = [...messages, if (threads) ...threadMessages]; + await pinnedMessageDao.updateMessages(cid, allMessages); + return allMessages; + } + + test('deleteMessageByIds', () async { + const cid = 'testCid'; + + // Preparing test data + final insertedMessages = await _prepareTestData(cid); + + // Fetched message list should match the test message list length + final messages = await pinnedMessageDao.getMessagesByCid(cid); + expect(messages.length, insertedMessages.length); + + // Deleting 2 messages from DB + await pinnedMessageDao.deleteMessageByIds( + ['testMessageId${cid}0', 'testMessageId${cid}1'], + ); + + // New fetched messages length should 2 less than the + // previous fetched messages + final newMessages = await pinnedMessageDao.getMessagesByCid(cid); + expect(newMessages.length, messages.length - 2); + }); + + group('deleteMessageByCids', () { + const cid1 = 'testCid1'; + const cid2 = 'testCid2'; + + test('should delete all the messages of first channel', () async { + // Preparing test data + final cid1InsertedMessages = await _prepareTestData(cid1); + final cid2InsertedMessages = await _prepareTestData(cid2); + + // Fetched message list should match the test message list length + final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1); + final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2); + expect(cid1Messages.length, cid1InsertedMessages.length); + expect(cid2Messages.length, cid2InsertedMessages.length); + + // Deleting all the messages of cid1 + await pinnedMessageDao.deleteMessageByCids([cid1]); + + // Fetched messages length of only cid1 should be empty + final cid1FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid1); + final cid2FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid2); + expect(cid1FetchedMessages, isEmpty); + expect(cid2FetchedMessages, isNotEmpty); + }); + + test('should delete all the messages of both channel', () async { + // Preparing test data + final cid1InsertedMessages = await _prepareTestData(cid1); + final cid2InsertedMessages = await _prepareTestData(cid2); + + // Fetched message list should match the test message list length + final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1); + final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2); + expect(cid1Messages.length, cid1InsertedMessages.length); + expect(cid2Messages.length, cid2InsertedMessages.length); + + // Deleting all the messages of cid1 + await pinnedMessageDao.deleteMessageByCids([cid1, cid2]); + + // Fetched messages length of both cid1 and cid2 should be empty + final cid1FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid1); + final cid2FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid2); + expect(cid1FetchedMessages, isEmpty); + expect(cid2FetchedMessages, isEmpty); + }); + }); + + test('getMessageById', () async { + const cid = 'testCid'; + const id = 'testMessageId${cid}0'; + + // Should be null initially + final message = await pinnedMessageDao.getMessageById(id); + expect(message, isNull); + + // Adding test message with the cid and id + final insertedMessages = await _prepareTestData(cid, count: 1); + expect(insertedMessages.first.id, id); + + // Fetched message id should match the inserted message id + final fetchedMessage = await pinnedMessageDao.getMessageById(id); + expect(fetchedMessage.id, insertedMessages.first.id); + }); + + test('getThreadMessages', () async { + const cid = 'testCid'; + + // Messages should be empty initially + final messages = await pinnedMessageDao.getThreadMessages(cid); + expect(messages, isEmpty); + + // Preparing test data + final insertedMessages = await _prepareTestData(cid, threads: true); + expect(insertedMessages, isNotEmpty); + + // Should fetch all the thread messages of cid + final threadMessages = await pinnedMessageDao.getThreadMessages(cid); + expect(threadMessages, isNotEmpty); + for (final message in threadMessages) { + expect(message.parentId, isNotNull); + } + }); + + test('getThreadMessagesByParentId', () async { + const cid = 'testCid'; + const parentId = 'testMessageId${cid}0'; + + // Messages should be empty initially + final messages = + await pinnedMessageDao.getThreadMessagesByParentId(parentId); + expect(messages, isEmpty); + + // Preparing test data + final insertedMessages = await _prepareTestData(cid, threads: true); + expect(insertedMessages, isNotEmpty); + + // Should fetch all the thread messages of parentId + final threadMessages = + await pinnedMessageDao.getThreadMessagesByParentId(parentId); + expect(threadMessages.length, 1); + expect(threadMessages.first.parentId, parentId); + }); + + test('getMessagesByCid', () async { + const cid = 'testCid'; + + // Should be empty initially + final messages = await pinnedMessageDao.getMessagesByCid(cid); + expect(messages, isEmpty); + + // Preparing test data + final insertedMessages = await _prepareTestData(cid); + expect(insertedMessages, isNotEmpty); + + // Fetched message should match the inserted messages + final fetchedMessages = await pinnedMessageDao.getMessagesByCid(cid); + expect(fetchedMessages.length, insertedMessages.length); + for (var i = 0; i < fetchedMessages.length; i++) { + final fetchedMessage = fetchedMessages[i]; + final insertedMessage = insertedMessages[i]; + expect(fetchedMessage.id, insertedMessage.id); + } + }); + + test('updateMessages', () async { + const cid = 'testCid'; + + // Preparing test data + final insertedMessages = await _prepareTestData(cid); + expect(insertedMessages, isNotEmpty); + + // Modifying one of the message and also adding one new + final copyMessage = insertedMessages.first.copyWith(showInChannel: false); + final newMessage = Message( + id: 'testMessageId${cid}4', + type: 'testType', + user: User(id: 'testUserId4'), + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + showInChannel: math.Random().nextBool(), + replyCount: 4, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text #4', + pinned: math.Random().nextBool(), + pinnedAt: DateTime.now(), + pinnedBy: User(id: 'testUserId4'), + ); + + await pinnedMessageDao.updateMessages(cid, [copyMessage, newMessage]); + + // Fetched messages length should be one more than inserted message. + // copyMessage `showInChannel` modified field should be false. + // Fetched messages should contain the newMessage. + final fetchedMessages = await pinnedMessageDao.getMessagesByCid(cid); + expect(fetchedMessages.length, insertedMessages.length + 1); + expect( + fetchedMessages.firstWhere((it) => it.id == copyMessage.id).showInChannel, + false, + ); + expect( + fetchedMessages.map((it) => it.id).contains('testMessageId${cid}4'), + true, + ); + }); + + tearDown(() async { + await database.disconnect(); + }); +} From 125931d89e4a1e404f5bf2315731330221fccb49 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 16 Mar 2021 20:02:24 +0530 Subject: [PATCH 010/599] test(persistence): add test for reaction dao Signed-off-by: Sahil Kumar --- .../test/src/dao/reaction_dao_test.dart | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart diff --git a/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart new file mode 100644 index 00000000..42273efa --- /dev/null +++ b/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart @@ -0,0 +1,187 @@ +import 'dart:math' as math; + +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/dao/reaction_dao.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:test/test.dart'; + +void main() { + ReactionDao reactionDao; + MoorChatDatabase database; + + setUp(() { + database = MoorChatDatabase.testable('testUserId'); + reactionDao = database.reactionDao; + }); + + Future> _prepareReactionData( + String messageId, { + String userId, + int count = 3, + }) async { + final users = List.generate(count, (index) => User(id: 'testUserId$index')); + final message = Message( + id: messageId, + type: 'testType', + user: users.first, + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + showInChannel: math.Random().nextBool(), + replyCount: 3, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text', + pinned: math.Random().nextBool(), + pinnedAt: DateTime.now(), + pinnedBy: users.first, + ); + final reactions = List.generate( + count, + (index) => Reaction( + type: 'testType$index', + createdAt: DateTime.now(), + userId: userId ?? users[index].id, + messageId: message.id, + score: count + 3, + extraData: {'extra_test_field': 'extraTestData'}, + ), + ); + + await database.userDao.updateUsers(users); + await database.messageDao.updateMessages('testCid', [message]); + await reactionDao.updateReactions(reactions); + + return reactions; + } + + test('getReactions', () async { + const messageId = 'testMessageId'; + + // Should be empty initially + final reactions = await reactionDao.getReactions(messageId); + expect(reactions, isEmpty); + + // Adding sample reactions + final insertedReactions = await _prepareReactionData(messageId); + expect(insertedReactions, isNotEmpty); + + // Fetched reaction length should match inserted reactions length. + // Every reaction messageId should match the provided messageId. + final fetchedReactions = await reactionDao.getReactions(messageId); + expect(fetchedReactions.length, insertedReactions.length); + expect(fetchedReactions.every((it) => it.messageId == messageId), true); + }); + + test('getReactionsByUserId', () async { + const messageId = 'testMessageId'; + const userId = 'testUserId'; + + // Should be empty initially + final reactions = await reactionDao.getReactionsByUserId(messageId, userId); + expect(reactions, isEmpty); + + // Adding sample reactions + final insertedReactions = + await _prepareReactionData(messageId, userId: userId); + expect(insertedReactions, isNotEmpty); + + // Fetched reaction length should match inserted reactions length. + // Every reaction messageId should match the provided messageId. + // Every reaction userId should match the provided userId. + final fetchedReactions = + await reactionDao.getReactionsByUserId(messageId, userId); + expect(fetchedReactions.length, insertedReactions.length); + expect(fetchedReactions.every((it) => it.messageId == messageId), true); + expect(fetchedReactions.every((it) => it.userId == userId), true); + }); + + test('updateReactions', () async { + const messageId = 'testMessageId'; + + // Preparing test data + final reactions = await _prepareReactionData(messageId); + + // Modifying one of the reaction and also adding one new + final copyReaction = reactions.first.copyWith(score: 33); + final newReaction = Reaction( + type: 'testType3', + createdAt: DateTime.now(), + userId: 'testUserId3', + messageId: messageId, + score: 30, + extraData: {'extra_test_field': 'extraTestData'}, + ); + + await reactionDao.updateReactions([copyReaction, newReaction]); + + // Fetched reaction length should be one more than inserted reactions. + // copyReaction `score` modified field should be 33. + // Fetched reactions should contain the newReaction. + final fetchedReactions = await reactionDao.getReactions(messageId); + expect(fetchedReactions.length, reactions.length + 1); + expect( + fetchedReactions + .firstWhere((it) => + it.userId == copyReaction.userId && it.type == copyReaction.type) + .score, + 33, + ); + expect( + fetchedReactions + .where((it) => it.userId == 'testUserId3' && it.type == 'testType3') + .isNotEmpty, + true, + ); + }); + + group('deleteReactionsByMessageIds', () { + const messageId1 = 'testMessageId1'; + const messageId2 = 'testMessageId2'; + test('should delete all the reactions of first message', () async { + // Preparing test data + final insertedReactions1 = await _prepareReactionData(messageId1); + final insertedReactions2 = await _prepareReactionData(messageId2); + + // Fetched reaction list length should match + // the inserted reactions list length + final reactions1 = await reactionDao.getReactions(messageId1); + final reactions2 = await reactionDao.getReactions(messageId2); + expect(reactions1.length, insertedReactions1.length); + expect(reactions2.length, insertedReactions2.length); + + // Deleting all the reactions of messageId1 + await reactionDao.deleteReactionsByMessageIds([messageId1]); + + // Fetched reactions length of only messageId1 should be empty + final fetchedReactions1 = await reactionDao.getReactions(messageId1); + final fetchedReactions2 = await reactionDao.getReactions(messageId2); + expect(fetchedReactions1, isEmpty); + expect(fetchedReactions2, isNotEmpty); + }); + test('should delete all the messages of both message', () async { + // Preparing test data + final insertedReactions1 = await _prepareReactionData(messageId1); + final insertedReactions2 = await _prepareReactionData(messageId2); + + // Fetched reaction list length should match + // the inserted reactions list length + final reactions1 = await reactionDao.getReactions(messageId1); + final reactions2 = await reactionDao.getReactions(messageId2); + expect(reactions1.length, insertedReactions1.length); + expect(reactions2.length, insertedReactions2.length); + + // Deleting all the reactions of messageId1 and messageId2 + await reactionDao.deleteReactionsByMessageIds([messageId1, messageId2]); + + // Fetched reactions length of both messages should be empty + final fetchedReactions1 = await reactionDao.getReactions(messageId1); + final fetchedReactions2 = await reactionDao.getReactions(messageId2); + expect(fetchedReactions1, isEmpty); + expect(fetchedReactions2, isEmpty); + }); + }); + + tearDown(() async { + await database.disconnect(); + }); +} From 45870d7fab7ce5daeeffe2c21de227cbd072c171 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 16 Mar 2021 20:45:11 +0530 Subject: [PATCH 011/599] test(persistence): add test for read dao Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/models/read.dart | 12 +++ .../test/src/dao/read_dao_test.dart | 97 +++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/dao/read_dao_test.dart diff --git a/packages/stream_chat/lib/src/models/read.dart b/packages/stream_chat/lib/src/models/read.dart index 2be639d5..fc8ef1bc 100644 --- a/packages/stream_chat/lib/src/models/read.dart +++ b/packages/stream_chat/lib/src/models/read.dart @@ -27,4 +27,16 @@ class Read { /// Serialize to json Map toJson() => _$ReadToJson(this); + + /// Creates a copy of [Read] with specified attributes overridden. + Read copyWith({ + DateTime lastRead, + User user, + int unreadMessages, + }) => + Read( + lastRead: lastRead ?? this.lastRead, + user: user ?? this.user, + unreadMessages: unreadMessages ?? this.unreadMessages, + ); } diff --git a/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart new file mode 100644 index 00000000..52207f68 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart @@ -0,0 +1,97 @@ +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/dao/dao.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:test/test.dart'; + +import '../utils/date_matcher.dart'; + +void main() { + ReadDao readDao; + MoorChatDatabase database; + + setUp(() { + database = MoorChatDatabase.testable('testUserId'); + readDao = database.readDao; + }); + + Future> _prepareReadData(String cid, {int count = 3}) async { + final users = List.generate(count, (index) => User(id: 'testUserId$index')); + final reads = List.generate( + count, + (index) => Read( + lastRead: DateTime.now(), + user: users[index], + unreadMessages: index + 10, + ), + ); + + await database.userDao.updateUsers(users); + await readDao.updateReads(cid, reads); + return reads; + } + + test('getReadsByCid', () async { + const cid = 'testCid'; + + // Should be empty initially + final reads = await readDao.getReadsByCid(cid); + expect(reads, isEmpty); + + // Preparing test data + final insertedReads = await _prepareReadData(cid); + expect(insertedReads, isNotEmpty); + + // Fetched reads should be equal to inserted reads + final fetchedReads = await readDao.getReadsByCid(cid); + expect(fetchedReads.length, insertedReads.length); + for (var i = 0; i < fetchedReads.length; i++) { + final fetchedRead = fetchedReads[i]; + final insertedRead = insertedReads[i]; + expect(fetchedRead.user.id, insertedRead.user.id); + expect(fetchedRead.lastRead, isSameDateAs(insertedRead.lastRead)); + expect(fetchedRead.unreadMessages, insertedRead.unreadMessages); + } + }); + + test('updateReads', () async { + const cid = 'testCid'; + + // Preparing test data + final insertedReads = await _prepareReadData(cid); + + // Modifying one of the read and also adding one new + final copyRead = insertedReads.first.copyWith(unreadMessages: 33); + final newUser = User(id: 'testUserId3'); + final newRead = Read( + lastRead: DateTime.now(), + user: newUser, + unreadMessages: 30, + ); + await database.userDao.updateUsers([newUser]); + await readDao.updateReads(cid, [copyRead, newRead]); + + // Fetched reads length should be one more than inserted reads. + // copyRead `unreadMessages` modified field should be 33. + // Fetched reads should contain the newRead. + final fetchedReads = await readDao.getReadsByCid(cid); + expect(fetchedReads.length, insertedReads.length + 1); + expect( + fetchedReads + .firstWhere((it) => it.user.id == copyRead.user.id) + .unreadMessages, + 33, + ); + expect( + fetchedReads + .where((it) => + it.user.id == newRead.user.id && + it.unreadMessages == newRead.unreadMessages) + .isNotEmpty, + true, + ); + }); + + tearDown(() async { + await database.disconnect(); + }); +} From 66a577901c70a56c453a1fd105d042cbf9cfafc9 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 16 Mar 2021 20:45:41 +0530 Subject: [PATCH 012/599] test(persistence): minor improvement in update function tests Signed-off-by: Sahil Kumar --- .../test/src/dao/member_dao_test.dart | 39 +++++++++++++++---- .../test/src/dao/message_dao_test.dart | 2 +- .../test/src/dao/pinned_message_dao_test.dart | 2 +- .../test/src/dao/reaction_dao_test.dart | 3 +- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart index 6671c902..dbb8c73c 100644 --- a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart @@ -94,15 +94,40 @@ void main() { ); } - // Updating the last added member - final updatedMember = fetchedMembers.last.copyWith(banned: true); - await memberDao.updateMembers(cid, [updatedMember]); + // Modifying one of the member and also adding one new + final copyMember = fetchedMembers.first.copyWith(banned: true); + final newUser = User(id: 'testUserId3'); + final newMember = Member( + user: newUser, + banned: math.Random().nextBool(), + shadowBanned: math.Random().nextBool(), + createdAt: DateTime.now(), + isModerator: math.Random().nextBool(), + invited: math.Random().nextBool(), + inviteAcceptedAt: DateTime.now(), + role: 'testRole', + updatedAt: DateTime.now(), + ); + await database.userDao.updateUsers([newUser]); + await memberDao.updateMembers(cid, [copyMember, newMember]); - // Last member banned field should match the previous updated user + // Fetched member length should be one more than inserted members. + // copyMember `banned` modified field should be true. + // Fetched members should contain the newMember. final newFetchedMembers = await memberDao.getMembersByCid(cid); - expect(newFetchedMembers.length, fetchedMembers.length); - final lastMember = newFetchedMembers.last; - expect(lastMember.banned, true); + expect(newFetchedMembers.length, fetchedMembers.length + 1); + expect( + newFetchedMembers + .firstWhere((it) => it.user.id == copyMember.user.id) + .banned, + true, + ); + expect( + newFetchedMembers + .where((it) => it.user.id == newMember.user.id) + .isNotEmpty, + true, + ); }); test('deleteMemberByCids', () async { diff --git a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart index 6ee8ca2f..dccb414c 100644 --- a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart @@ -242,7 +242,7 @@ void main() { false, ); expect( - fetchedMessages.map((it) => it.id).contains('testMessageId${cid}4'), + fetchedMessages.map((it) => it.id).contains(newMessage.id), true, ); }); diff --git a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart index 1af3276e..5c1bce46 100644 --- a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart @@ -243,7 +243,7 @@ void main() { false, ); expect( - fetchedMessages.map((it) => it.id).contains('testMessageId${cid}4'), + fetchedMessages.map((it) => it.id).contains(newMessage.id), true, ); }); diff --git a/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart index 42273efa..94f8ebdd 100644 --- a/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart @@ -128,7 +128,8 @@ void main() { ); expect( fetchedReactions - .where((it) => it.userId == 'testUserId3' && it.type == 'testType3') + .where((it) => + it.userId == newReaction.userId && it.type == newReaction.type) .isNotEmpty, true, ); From e87a8a07a15cab50b0e0853bbb8b9c33607a4b44 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 16 Mar 2021 21:11:07 +0530 Subject: [PATCH 013/599] test(persistence): add tests for user dao Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/models/user.dart | 24 ++++++ .../lib/src/dao/user_dao.dart | 6 ++ .../test/src/dao/user_dao_test.dart | 77 +++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/dao/user_dao_test.dart diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/models/user.dart index 618c1986..3b9f9add 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/models/user.dart @@ -100,4 +100,28 @@ class User { /// Serialize to json Map toJson() => Serialization.moveFromExtraDataToRoot(_$UserToJson(this), topLevelFields); + + /// Creates a copy of [User] with specified attributes overridden. + User copyWith({ + String id, + String role, + DateTime createdAt, + DateTime updatedAt, + DateTime lastActive, + bool online, + Map extraData, + bool banned, + List teams, + }) => + User( + id: id ?? this.id, + role: role ?? this.role, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + lastActive: lastActive ?? this.lastActive, + online: online ?? this.online, + extraData: extraData ?? this.extraData, + banned: banned ?? this.banned, + teams: teams ?? this.teams, + ); } diff --git a/packages/stream_chat_persistence/lib/src/dao/user_dao.dart b/packages/stream_chat_persistence/lib/src/dao/user_dao.dart index 65ce1521..9cdff8e7 100644 --- a/packages/stream_chat_persistence/lib/src/dao/user_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/user_dao.dart @@ -20,4 +20,10 @@ class UserDao extends DatabaseAccessor with _$UserDaoMixin { mode: InsertMode.insertOrReplace, ), ); + + /// Returns the list of all the users stored in db + Future> getUsers() => + (select(users)..orderBy([(u) => OrderingTerm.desc(u.createdAt)])) + .map((it) => it.toUser()) + .get(); } diff --git a/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart new file mode 100644 index 00000000..8c5898a0 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart @@ -0,0 +1,77 @@ +import 'dart:math' as math; + +import 'package:stream_chat_persistence/src/dao/dao.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:test/test.dart'; +import 'package:stream_chat/stream_chat.dart'; + +void main() { + UserDao userDao; + MoorChatDatabase database; + + setUp(() { + database = MoorChatDatabase.testable('testUserId'); + userDao = database.userDao; + }); + + Future> _prepareUserData({int count = 3}) async { + final users = List.generate( + count, + (index) => User( + id: 'testUserId$index', + role: 'testRole', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + lastActive: DateTime.now(), + online: math.Random().nextBool(), + banned: math.Random().nextBool(), + ), + ); + await userDao.updateUsers(users); + return users; + } + + test('updateUsers', () async { + // Preparing test data + final insertedUsers = await _prepareUserData(); + + // Modifying one of the user and also adding one new + final copyUser = insertedUsers.first.copyWith(online: false); + final newUser = User( + id: 'testUserId3', + role: 'testRole', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + lastActive: DateTime.now(), + online: math.Random().nextBool(), + banned: math.Random().nextBool(), + ); + await userDao.updateUsers([copyUser, newUser]); + + // Fetched users length should be one more than inserted users. + // copyUser `online` modified field should be `false`. + // Fetched users should contain the newUser. + final fetchedUsers = await userDao.getUsers(); + expect(fetchedUsers.length, insertedUsers.length + 1); + expect(fetchedUsers.firstWhere((it) => it.id == copyUser.id).online, false); + expect(fetchedUsers.contains(newUser), true); + }); + + test('getUsers', () async { + // Should be empty initially + final users = await userDao.getUsers(); + expect(users, isEmpty); + + // Preparing test data + final insertedUsers = await _prepareUserData(); + expect(insertedUsers, isNotEmpty); + + // Fetched user list should match inserted user list length + final fetchedUsers = await userDao.getUsers(); + expect(fetchedUsers.length, insertedUsers.length); + }); + + tearDown(() async { + await database.disconnect(); + }); +} From 3854bfcdc93b0d7b22150d48ae8a7337ddc42a3f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Mar 2021 14:01:39 +0530 Subject: [PATCH 014/599] test(persistence): add test for list converter Signed-off-by: Sahil Kumar --- .../src/converter/list_coverter_test.dart | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/converter/list_coverter_test.dart diff --git a/packages/stream_chat_persistence/test/src/converter/list_coverter_test.dart b/packages/stream_chat_persistence/test/src/converter/list_coverter_test.dart new file mode 100644 index 00000000..a5bedd40 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/converter/list_coverter_test.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; + +import 'package:test/test.dart'; +import 'package:stream_chat_persistence/src/converter/list_converter.dart'; + +void main() { + group('mapToDart', () { + final listConverter = ListConverter(); + + test('should return null if nothing is provided', () { + final res = listConverter.mapToDart(null); + expect(res, isNull); + }); + + test('should throw type error if the provided json is not a list', () { + final json = {'test_key': 'testData'}; + expect( + () => listConverter.mapToDart(jsonEncode(json)), + throwsA(isA()), + ); + }); + + test('should throw type error if the provided json is not a list of String', + () { + final json = [22, 33, 44]; + expect( + () => listConverter.mapToDart(jsonEncode(json)), + throwsA(isA()), + ); + }); + + test('should return list of String if json data list is provided', () { + final data = ['data1', 'data2', 'data3']; + final res = listConverter.mapToDart(jsonEncode(data)); + expect(res.length, data.length); + }); + }); + + group('mapToSql', () { + final listConverter = ListConverter(); + + test('should return null if nothing is provided', () { + final res = listConverter.mapToSql(null); + expect(res, isNull); + }); + + test('should return json string if data list is provided', () { + final data = ['data1', 'data2', 'data3']; + final res = listConverter.mapToSql(data); + expect(res, jsonEncode(data)); + }); + }); +} From 28e3e5edc4265cd5a0fc20f9f6c91fedf0a5b5e5 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Mar 2021 14:11:45 +0530 Subject: [PATCH 015/599] test(persistence): add test for map converter Signed-off-by: Sahil Kumar --- .../src/converter/map_converter_test.dart | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/converter/map_converter_test.dart diff --git a/packages/stream_chat_persistence/test/src/converter/map_converter_test.dart b/packages/stream_chat_persistence/test/src/converter/map_converter_test.dart new file mode 100644 index 00000000..4727cb0d --- /dev/null +++ b/packages/stream_chat_persistence/test/src/converter/map_converter_test.dart @@ -0,0 +1,64 @@ +import 'dart:convert'; + +import 'package:test/test.dart'; +import 'package:stream_chat_persistence/src/converter/map_converter.dart'; + +void main() { + group('mapToDart', () { + final mapConverter = MapConverter(); + + test('should return null if nothing is provided', () { + final res = mapConverter.mapToDart(null); + expect(res, isNull); + }); + + test('should throw type error if the provided json is not a map', () { + const json = ['testData1', 'testData2', 'testData3']; + expect( + () => mapConverter.mapToDart(jsonEncode(json)), + throwsA(isA()), + ); + }); + + test( + 'should throw type error if the provided json is not a ' + 'map of String, String', + () { + const json = {'test_key': 22, 'test_key2': 33, 'test_key3': 44}; + expect( + () => mapConverter.mapToDart(jsonEncode(json)), + throwsA(isA()), + ); + }, + ); + + test('should return map of String, String if json data is provided', () { + const data = { + 'test_key': 'testValue', + 'test_key2': 'testValue2', + 'test_key3': 'testValue3', + }; + final res = mapConverter.mapToDart(jsonEncode(data)); + expect(res, data); + }); + }); + + group('mapToSql', () { + final mapConverter = MapConverter(); + + test('should return null if nothing is provided', () { + final res = mapConverter.mapToSql(null); + expect(res, isNull); + }); + + test('should return json string if data map is provided', () { + const data = { + 'test_key': 'testValue', + 'test_key2': 'testValue2', + 'test_key3': 'testValue3', + }; + final res = mapConverter.mapToSql(data); + expect(res, jsonEncode(data)); + }); + }); +} From b738a47c2aa9949644105e9390ba3197b80424f7 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Mar 2021 14:19:06 +0530 Subject: [PATCH 016/599] test(persistence): add tests for message sending status converter Signed-off-by: Sahil Kumar --- ...message_sending_status_converter_test.dart | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/converter/message_sending_status_converter_test.dart diff --git a/packages/stream_chat_persistence/test/src/converter/message_sending_status_converter_test.dart b/packages/stream_chat_persistence/test/src/converter/message_sending_status_converter_test.dart new file mode 100644 index 00000000..1035a759 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/converter/message_sending_status_converter_test.dart @@ -0,0 +1,33 @@ +import 'package:test/test.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/converter/message_sending_status_converter.dart'; + +void main() { + group('mapToDart', () { + final statusConverter = MessageSendingStatusConverter(); + + test('should return null if nothing is provided', () { + final res = statusConverter.mapToDart(null); + expect(res, isNull); + }); + + test('should return expected status if status code is provided', () { + final res = statusConverter.mapToDart(3); + expect(res, MessageSendingStatus.updating); + }); + }); + + group('mapToSql', () { + final statusConverter = MessageSendingStatusConverter(); + + test('should return null if nothing is provided', () { + final res = statusConverter.mapToSql(null); + expect(res, isNull); + }); + + test('should return expected code if the status is provided', () { + final res = statusConverter.mapToSql(MessageSendingStatus.updating); + expect(res, 3); + }); + }); +} From 830cb0c281155ff9faeee1118b16ba878ba844d2 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Mar 2021 14:57:26 +0530 Subject: [PATCH 017/599] test(persistence): add tests for channel mapper Signed-off-by: Sahil Kumar --- .../test/src/mapper/channel_mapper_test.dart | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart diff --git a/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart new file mode 100644 index 00000000..dc02ee4b --- /dev/null +++ b/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart @@ -0,0 +1,111 @@ +import 'dart:math' as math; + +import 'package:test/test.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/mapper/channel_mapper.dart'; +import '../utils/date_matcher.dart'; + +void main() { + group('ChannelEntity', () { + final user = User(id: 'testUserId'); + final entity = ChannelEntity( + id: 'testId', + type: 'testType', + cid: 'testCid', + config: {'max_message_length': 33}, + frozen: math.Random().nextBool(), + lastMessageAt: DateTime.now(), + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + deletedAt: DateTime.now(), + memberCount: 33, + createdById: user.id, + extraData: {'test_extra_data': 'testData'}, + ); + + test('toChannelModel should map entity into ChannelModel', () { + final channelModel = entity.toChannelModel(createdBy: user); + expect(channelModel, isA()); + expect(channelModel.id, entity.id); + expect(channelModel.config.toJson()['max_message_length'], 33); + expect(channelModel.frozen, entity.frozen); + expect(channelModel.createdAt, isSameDateAs(entity.createdAt)); + expect(channelModel.updatedAt, isSameDateAs(entity.updatedAt)); + expect(channelModel.memberCount, entity.memberCount); + expect(channelModel.cid, entity.cid); + expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt)); + expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt)); + expect(channelModel.extraData, entity.extraData); + expect(channelModel.createdBy.id, entity.createdById); + }); + + test('toChannelState should map entity into ChannelState ', () { + final members = List.generate(3, (index) => Member()); + final reads = List.generate(3, (index) => Read()); + final messages = List.generate(3, (index) => Message()); + + final channelState = entity.toChannelState( + createdBy: user, + members: members, + reads: reads, + messages: messages, + pinnedMessages: messages, + ); + + expect(channelState, isA()); + expect(channelState.members.length, members.length); + expect(channelState.read.length, reads.length); + expect(channelState.messages.length, messages.length); + expect(channelState.pinnedMessages.length, messages.length); + + final channelModel = channelState.channel; + expect(channelModel.id, entity.id); + expect(channelModel.config.toJson()['max_message_length'], 33); + expect(channelModel.frozen, entity.frozen); + expect(channelModel.createdAt, isSameDateAs(entity.createdAt)); + expect(channelModel.updatedAt, isSameDateAs(entity.updatedAt)); + expect(channelModel.memberCount, entity.memberCount); + expect(channelModel.cid, entity.cid); + expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt)); + expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt)); + expect(channelModel.extraData, entity.extraData); + expect(channelModel.createdBy.id, entity.createdById); + }); + }); + + test('toEntity should map model into ChannelEntity', () { + final createdBy = User(id: 'testUserId'); + final model = ChannelModel( + id: 'testId', + type: 'testType', + cid: 'testCid', + config: ChannelConfig(maxMessageLength: 33), + frozen: math.Random().nextBool(), + lastMessageAt: DateTime.now(), + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + deletedAt: DateTime.now(), + memberCount: 33, + createdBy: createdBy, + extraData: {'test_extra_data': 'testData'}, + ); + + final channelEntity = model.toEntity(); + expect(channelEntity, isA()); + expect(channelEntity.id, model.id); + expect( + channelEntity.config['max_message_length'], + model.config.maxMessageLength, + ); + expect(channelEntity.frozen, model.frozen); + expect(channelEntity.createdAt, isSameDateAs(model.createdAt)); + expect(channelEntity.updatedAt, isSameDateAs(model.updatedAt)); + expect(channelEntity.memberCount, model.memberCount); + expect(channelEntity.cid, model.cid); + expect(channelEntity.lastMessageAt, isSameDateAs(model.lastMessageAt)); + expect(channelEntity.deletedAt, isSameDateAs(model.deletedAt)); + expect(channelEntity.extraData, model.extraData); + expect(channelEntity.createdById, model.createdBy.id); + }); +} From 60613d44bf0d9b019bb7d8b8082b6d50f3205d3c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Mar 2021 15:03:40 +0530 Subject: [PATCH 018/599] test(persistence): add test for event mapper Signed-off-by: Sahil Kumar --- .../test/src/mapper/event_mapper_test.dart | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart diff --git a/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart new file mode 100644 index 00000000..240ce1a7 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart @@ -0,0 +1,23 @@ +import 'package:test/test.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/mapper/event_mapper.dart'; + +void main() { + test('toEvent should map entity into Event', () { + final ownUser = OwnUser(id: 'testUserId'); + final entity = ConnectionEventEntity( + id: 3, + ownUser: ownUser.toJson(), + totalUnreadCount: 33, + unreadChannels: 33, + lastSyncAt: DateTime.now(), + lastEventAt: DateTime.now(), + ); + final event = entity.toEvent(); + expect(event, isA()); + expect(event.me.id, ownUser.id); + expect(event.totalUnreadCount, entity.totalUnreadCount); + expect(event.unreadChannels, entity.unreadChannels); + }); +} From 323c5b6cba9f874e736cb6221467da19a32d6fa8 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Mar 2021 15:43:50 +0530 Subject: [PATCH 019/599] test(persistence): add tests for member mapper Signed-off-by: Sahil Kumar --- .../test/src/mapper/member_mapper_test.dart | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart diff --git a/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart new file mode 100644 index 00000000..0f7d4f11 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart @@ -0,0 +1,69 @@ +import 'dart:math' as math; + +import 'package:test/test.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/mapper/member_mapper.dart'; + +import '../utils/date_matcher.dart'; + +void main() { + test('toMember should map entity into Member', () { + final user = User(id: 'testUserId'); + final entity = MemberEntity( + userId: user.id, + channelCid: 'testCid', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + role: 'testRole', + inviteAcceptedAt: DateTime.now(), + inviteRejectedAt: DateTime.now(), + invited: math.Random().nextBool(), + banned: math.Random().nextBool(), + shadowBanned: math.Random().nextBool(), + isModerator: math.Random().nextBool(), + ); + final member = entity.toMember(user: user); + expect(member, isA()); + expect(member.user.id, entity.userId); + expect(member.createdAt, isSameDateAs(entity.createdAt)); + expect(member.updatedAt, isSameDateAs(entity.updatedAt)); + expect(member.role, entity.role); + expect(member.inviteAcceptedAt, isSameDateAs(entity.inviteAcceptedAt)); + expect(member.inviteRejectedAt, isSameDateAs(entity.inviteRejectedAt)); + expect(member.invited, entity.invited); + expect(member.banned, entity.banned); + expect(member.shadowBanned, entity.shadowBanned); + expect(member.isModerator, entity.isModerator); + }); + + test('toEntity show map member into MemberEntity', () { + const cid = 'testCid'; + final user = User(id: 'testUserId'); + final member = Member( + user: user, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + role: 'testRole', + inviteAcceptedAt: DateTime.now(), + inviteRejectedAt: DateTime.now(), + invited: math.Random().nextBool(), + banned: math.Random().nextBool(), + shadowBanned: math.Random().nextBool(), + isModerator: math.Random().nextBool(), + ); + final entity = member.toEntity(cid: cid); + expect(entity, isA()); + expect(entity.channelCid, cid); + expect(entity.userId, member.user.id); + expect(entity.createdAt, isSameDateAs(member.createdAt)); + expect(entity.updatedAt, isSameDateAs(member.updatedAt)); + expect(entity.role, member.role); + expect(entity.inviteAcceptedAt, isSameDateAs(member.inviteAcceptedAt)); + expect(entity.inviteRejectedAt, isSameDateAs(member.inviteRejectedAt)); + expect(entity.invited, member.invited); + expect(entity.banned, member.banned); + expect(entity.shadowBanned, member.shadowBanned); + expect(entity.isModerator, member.isModerator); + }); +} From b77c4dfc5d87c4b51e28b452a12bef0a7ec571eb Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 17 Mar 2021 11:50:01 +0100 Subject: [PATCH 020/599] remove useless dependencies --- packages/stream_chat_flutter/pubspec.yaml | 3 --- packages/stream_chat_flutter_core/pubspec.yaml | 1 - packages/stream_chat_persistence/pubspec.yaml | 1 - 3 files changed, 5 deletions(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 24f83efe..867178d6 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -30,16 +30,13 @@ dependencies: flutter_keyboard_visibility: ^4.0.2 video_compress: ^2.1.1 visibility_detector: ^0.1.5 - http_parser: ^3.1.4 meta: ^1.2.4 lottie: ^0.7.0+1 substring_highlight: ^0.1.2 flutter_slidable: ^0.5.7 - clipboard: ^0.1.2+8 image_gallery_saver: ^1.6.7 share_plus: ^1.2.0 photo_manager: ^1.0.0 - transparent_image: ^1.0.0 ezanimation: ^0.4.1 synchronized: ^2.1.0 characters: ^1.0.0 diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 5a5fc7da..cc36f149 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -20,5 +20,4 @@ dev_dependencies: flutter_test: sdk: flutter fake_async: ^1.1.0 - pedantic: ^1.9.2 \ No newline at end of file diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 12d117b1..399d1278 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -23,5 +23,4 @@ dependencies: dev_dependencies: build_runner: ^1.11.0 moor_generator: ^3.4.1 - pedantic: ^1.9.2 test: ^1.15.7 From 6804660c61ebef39eb2ff6176dd5e4532330e322 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Mar 2021 17:21:57 +0530 Subject: [PATCH 021/599] test(persistence): add tests for message mapper Signed-off-by: Sahil Kumar --- .../test/src/mapper/message_mapper_test.dart | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart new file mode 100644 index 00000000..0554a0ce --- /dev/null +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -0,0 +1,157 @@ +import 'dart:math' as math; + +import 'package:test/test.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/mapper/message_mapper.dart'; + +import '../utils/date_matcher.dart'; + +void main() { + test('toMessage should map the entity into Message', () { + final user = User(id: 'testUserId'); + final quotedMessage = Message(id: 'testQuotedMessageId'); + final reactions = List.generate( + 3, + (index) => Reaction( + messageId: 'testMessageId', + createdAt: DateTime.now(), + type: 'testType$index', + user: user, + score: math.Random().nextInt(50), + ), + ); + final entity = MessageEntity( + id: 'testMessageId', + attachments: [], + channelCid: 'testCid', + type: 'testType', + parentId: 'testParentId', + quotedMessageId: quotedMessage.id, + command: 'testCommand', + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + showInChannel: math.Random().nextBool(), + replyCount: 33, + reactionScores: {for (final r in reactions) r.type: r.score}, + reactionCounts: reactions.fold( + {}, + (prev, curr) => + prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1), + ), + status: MessageSendingStatus.sent, + updatedAt: DateTime.now(), + extraData: {'extra_test_data': 'extraData'}, + userId: user.id, + deletedAt: DateTime.now(), + messageText: 'dummy text', + pinned: true, + pinExpires: DateTime.now().toUtc(), + pinnedAt: DateTime.now(), + pinnedByUserId: user.id, + ); + final message = entity.toMessage( + user: user, + pinnedBy: user, + latestReactions: reactions, + ownReactions: reactions, + quotedMessage: quotedMessage, + ); + + expect(message, isA()); + expect(message.id, entity.id); + expect(message.attachments, entity.attachments); + expect(message.type, entity.type); + expect(message.parentId, entity.parentId); + expect(message.quotedMessageId, entity.quotedMessageId); + expect(message.command, entity.command); + expect(message.createdAt, isSameDateAs(entity.createdAt)); + expect(message.shadowed, entity.shadowed); + expect(message.showInChannel, entity.showInChannel); + expect(message.replyCount, entity.replyCount); + expect(message.reactionScores, entity.reactionScores); + expect(message.reactionCounts, entity.reactionCounts); + expect(message.status, entity.status); + expect(message.updatedAt, isSameDateAs(entity.updatedAt)); + expect(message.extraData, entity.extraData); + expect(message.user.id, entity.userId); + expect(message.deletedAt, isSameDateAs(entity.deletedAt)); + expect(message.text, entity.messageText); + expect(message.pinned, entity.pinned); + expect(message.pinExpires, isSameDateAs(entity.pinExpires)); + expect(message.pinnedAt, isSameDateAs(entity.pinnedAt)); + expect(message.pinnedBy.id, entity.pinnedByUserId); + expect(message.reactionCounts, entity.reactionCounts); + expect(message.reactionScores, entity.reactionScores); + }); + + test('toEntity should map message into MessageEntity', () { + const cid = 'testCid'; + final user = User(id: 'testUserId'); + final quotedMessage = Message(id: 'testQuotedMessageId'); + final reactions = List.generate( + 3, + (index) => Reaction( + messageId: 'testMessageId', + createdAt: DateTime.now(), + type: 'testType$index', + user: user, + score: math.Random().nextInt(50), + ), + ); + final message = Message( + id: 'testMessageId', + attachments: [], + type: 'testType', + parentId: 'testParentId', + quotedMessageId: quotedMessage.id, + command: 'testCommand', + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + showInChannel: math.Random().nextBool(), + replyCount: 33, + reactionScores: {for (final r in reactions) r.type: r.score}, + reactionCounts: reactions.fold( + {}, + (prev, curr) => + prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1), + ), + status: MessageSendingStatus.sending, + updatedAt: DateTime.now(), + extraData: {'extra_test_data': 'extraData'}, + user: user, + deletedAt: DateTime.now(), + text: 'dummy text', + pinned: true, + pinExpires: DateTime.now(), + pinnedAt: DateTime.now(), + pinnedBy: user, + ); + final entity = message.toEntity(cid: cid); + expect(entity, isA()); + expect(entity.id, message.id); + expect(entity.attachments, message.attachments); + expect(entity.type, message.type); + expect(entity.parentId, message.parentId); + expect(entity.quotedMessageId, message.quotedMessageId); + expect(entity.command, message.command); + expect(entity.createdAt, isSameDateAs(message.createdAt)); + expect(entity.shadowed, message.shadowed); + expect(entity.showInChannel, message.showInChannel); + expect(entity.replyCount, message.replyCount); + expect(entity.reactionScores, message.reactionScores); + expect(entity.reactionCounts, message.reactionCounts); + expect(entity.status, message.status); + expect(entity.updatedAt, isSameDateAs(message.updatedAt)); + expect(entity.extraData, message.extraData); + expect(entity.userId, message.user.id); + expect(entity.deletedAt, isSameDateAs(message.deletedAt)); + expect(entity.messageText, message.text); + expect(entity.pinned, message.pinned); + expect(entity.pinExpires, isSameDateAs(message.pinExpires)); + expect(entity.pinnedAt, isSameDateAs(message.pinnedAt)); + expect(entity.pinnedByUserId, message.pinnedBy.id); + expect(entity.reactionCounts, message.reactionCounts); + expect(entity.reactionScores, message.reactionScores); + }); +} From a78c230ef0e900e6b3d44e2ebed9b8d3720b572f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Mar 2021 17:25:25 +0530 Subject: [PATCH 022/599] test(persistence): add tests for pinned message mapper Signed-off-by: Sahil Kumar --- .../mapper/pinned_message_mapper_test.dart | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart diff --git a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart new file mode 100644 index 00000000..e79dd051 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart @@ -0,0 +1,157 @@ +import 'dart:math' as math; + +import 'package:test/test.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/mapper/pinned_message_mapper.dart'; + +import '../utils/date_matcher.dart'; + +void main() { + test('toMessage should map the entity into Message', () { + final user = User(id: 'testUserId'); + final quotedMessage = Message(id: 'testQuotedMessageId'); + final reactions = List.generate( + 3, + (index) => Reaction( + messageId: 'testMessageId', + createdAt: DateTime.now(), + type: 'testType$index', + user: user, + score: math.Random().nextInt(50), + ), + ); + final entity = PinnedMessageEntity( + id: 'testMessageId', + attachments: [], + channelCid: 'testCid', + type: 'testType', + parentId: 'testParentId', + quotedMessageId: quotedMessage.id, + command: 'testCommand', + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + showInChannel: math.Random().nextBool(), + replyCount: 33, + reactionScores: {for (final r in reactions) r.type: r.score}, + reactionCounts: reactions.fold( + {}, + (prev, curr) => + prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1), + ), + status: MessageSendingStatus.sent, + updatedAt: DateTime.now(), + extraData: {'extra_test_data': 'extraData'}, + userId: user.id, + deletedAt: DateTime.now(), + messageText: 'dummy text', + pinned: true, + pinExpires: DateTime.now().toUtc(), + pinnedAt: DateTime.now(), + pinnedByUserId: user.id, + ); + final message = entity.toMessage( + user: user, + pinnedBy: user, + latestReactions: reactions, + ownReactions: reactions, + quotedMessage: quotedMessage, + ); + + expect(message, isA()); + expect(message.id, entity.id); + expect(message.attachments, entity.attachments); + expect(message.type, entity.type); + expect(message.parentId, entity.parentId); + expect(message.quotedMessageId, entity.quotedMessageId); + expect(message.command, entity.command); + expect(message.createdAt, isSameDateAs(entity.createdAt)); + expect(message.shadowed, entity.shadowed); + expect(message.showInChannel, entity.showInChannel); + expect(message.replyCount, entity.replyCount); + expect(message.reactionScores, entity.reactionScores); + expect(message.reactionCounts, entity.reactionCounts); + expect(message.status, entity.status); + expect(message.updatedAt, isSameDateAs(entity.updatedAt)); + expect(message.extraData, entity.extraData); + expect(message.user.id, entity.userId); + expect(message.deletedAt, isSameDateAs(entity.deletedAt)); + expect(message.text, entity.messageText); + expect(message.pinned, entity.pinned); + expect(message.pinExpires, isSameDateAs(entity.pinExpires)); + expect(message.pinnedAt, isSameDateAs(entity.pinnedAt)); + expect(message.pinnedBy.id, entity.pinnedByUserId); + expect(message.reactionCounts, entity.reactionCounts); + expect(message.reactionScores, entity.reactionScores); + }); + + test('toPinnedEntity should map message into PinnedMessageEntity', () { + const cid = 'testCid'; + final user = User(id: 'testUserId'); + final quotedMessage = Message(id: 'testQuotedMessageId'); + final reactions = List.generate( + 3, + (index) => Reaction( + messageId: 'testMessageId', + createdAt: DateTime.now(), + type: 'testType$index', + user: user, + score: math.Random().nextInt(50), + ), + ); + final message = Message( + id: 'testMessageId', + attachments: [], + type: 'testType', + parentId: 'testParentId', + quotedMessageId: quotedMessage.id, + command: 'testCommand', + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + showInChannel: math.Random().nextBool(), + replyCount: 33, + reactionScores: {for (final r in reactions) r.type: r.score}, + reactionCounts: reactions.fold( + {}, + (prev, curr) => + prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1), + ), + status: MessageSendingStatus.sending, + updatedAt: DateTime.now(), + extraData: {'extra_test_data': 'extraData'}, + user: user, + deletedAt: DateTime.now(), + text: 'dummy text', + pinned: true, + pinExpires: DateTime.now(), + pinnedAt: DateTime.now(), + pinnedBy: user, + ); + final entity = message.toPinnedEntity(cid: cid); + expect(entity, isA()); + expect(entity.id, message.id); + expect(entity.attachments, message.attachments); + expect(entity.type, message.type); + expect(entity.parentId, message.parentId); + expect(entity.quotedMessageId, message.quotedMessageId); + expect(entity.command, message.command); + expect(entity.createdAt, isSameDateAs(message.createdAt)); + expect(entity.shadowed, message.shadowed); + expect(entity.showInChannel, message.showInChannel); + expect(entity.replyCount, message.replyCount); + expect(entity.reactionScores, message.reactionScores); + expect(entity.reactionCounts, message.reactionCounts); + expect(entity.status, message.status); + expect(entity.updatedAt, isSameDateAs(message.updatedAt)); + expect(entity.extraData, message.extraData); + expect(entity.userId, message.user.id); + expect(entity.deletedAt, isSameDateAs(message.deletedAt)); + expect(entity.messageText, message.text); + expect(entity.pinned, message.pinned); + expect(entity.pinExpires, isSameDateAs(message.pinExpires)); + expect(entity.pinnedAt, isSameDateAs(message.pinnedAt)); + expect(entity.pinnedByUserId, message.pinnedBy.id); + expect(entity.reactionCounts, message.reactionCounts); + expect(entity.reactionScores, message.reactionScores); + }); +} From d688d554ce07af0c1de6450363d995f26456a7f6 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Mar 2021 17:32:12 +0530 Subject: [PATCH 023/599] test(persistence): add tests for reaction mapper Signed-off-by: Sahil Kumar --- .../test/src/mapper/reaction_mapper_test.dart | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/mapper/reaction_mapper_test.dart diff --git a/packages/stream_chat_persistence/test/src/mapper/reaction_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/reaction_mapper_test.dart new file mode 100644 index 00000000..8754ba10 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/mapper/reaction_mapper_test.dart @@ -0,0 +1,54 @@ +import 'dart:math' as math; + +import 'package:test/test.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/mapper/reaction_mapper.dart'; + +import '../utils/date_matcher.dart'; + +void main() { + test('toReaction should map the entity into Reaction', () { + final user = User(id: 'testUserId'); + final message = Message(id: 'testMessageId'); + final entity = ReactionEntity( + userId: user.id, + messageId: message.id, + type: 'haha', + score: 33, + createdAt: DateTime.now(), + extraData: {'extra_test_data': 'extraData'}, + ); + + final reaction = entity.toReaction(user: user); + expect(reaction, isA()); + expect(reaction.userId, entity.userId); + expect(reaction.messageId, entity.messageId); + expect(reaction.type, entity.type); + expect(reaction.score, entity.score); + expect(reaction.createdAt, isSameDateAs(entity.createdAt)); + expect(reaction.extraData, entity.extraData); + }); + + test('toEntity should map reaction into ReactionEntity', () { + final user = User(id: 'testUserId'); + final message = Message(id: 'testMessageId'); + final reaction = Reaction( + userId: user.id, + messageId: message.id, + type: 'haha', + score: 33, + createdAt: DateTime.now(), + extraData: {'extra_test_data': 'extraData'}, + ); + + final entity = reaction.toEntity(); + expect(entity, isA()); + expect(entity.userId, reaction.userId); + expect(entity.messageId, reaction.messageId); + expect(entity.type, reaction.type); + expect(entity.score, reaction.score); + expect(entity.createdAt, isSameDateAs(reaction.createdAt)); + expect(entity.extraData, reaction.extraData); + }); +} From 818e91a106ea91376582a989fac70d70ee3e4c7c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Mar 2021 17:38:14 +0530 Subject: [PATCH 024/599] test(persistence): add tests for read mapper Signed-off-by: Sahil Kumar --- .../test/src/mapper/reaction_mapper_test.dart | 2 - .../test/src/mapper/read_mapper_test.dart | 41 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 packages/stream_chat_persistence/test/src/mapper/read_mapper_test.dart diff --git a/packages/stream_chat_persistence/test/src/mapper/reaction_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/reaction_mapper_test.dart index 8754ba10..cb5d231e 100644 --- a/packages/stream_chat_persistence/test/src/mapper/reaction_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/reaction_mapper_test.dart @@ -1,5 +1,3 @@ -import 'dart:math' as math; - import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; diff --git a/packages/stream_chat_persistence/test/src/mapper/read_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/read_mapper_test.dart new file mode 100644 index 00000000..73c87da3 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/mapper/read_mapper_test.dart @@ -0,0 +1,41 @@ +import 'package:test/test.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/mapper/read_mapper.dart'; + +import '../utils/date_matcher.dart'; + +void main() { + test('toRead should map entity into Read', () { + const cid = 'testCid'; + final user = User(id: 'testUserId'); + final entity = ReadEntity( + lastRead: DateTime.now(), + userId: user.id, + channelCid: cid, + unreadMessages: 33, + ); + + final read = entity.toRead(user: user); + expect(read, isA()); + expect(read.lastRead, isSameDateAs(entity.lastRead)); + expect(read.user.id, entity.userId); + expect(read.unreadMessages, entity.unreadMessages); + }); + + test('toEntity should map read into ReadEntity', () { + const cid = 'testCid'; + final user = User(id: 'testUserId'); + final read = Read( + lastRead: DateTime.now(), + user: user, + unreadMessages: 33, + ); + + final entity = read.toEntity(cid: cid); + expect(entity, isA()); + expect(entity.lastRead, isSameDateAs(read.lastRead)); + expect(entity.userId, read.user.id); + expect(entity.unreadMessages, read.unreadMessages); + }); +} From 2233528609e7a6a031522a5945d0d0c5815fb56c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Mar 2021 17:48:39 +0530 Subject: [PATCH 025/599] test(persistence): add tests for user mapper Signed-off-by: Sahil Kumar --- .../test/src/mapper/user_mapper_test.dart | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart diff --git a/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart new file mode 100644 index 00000000..5e40f237 --- /dev/null +++ b/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart @@ -0,0 +1,55 @@ +import 'dart:math' as math; +import 'package:test/test.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/mapper/user_mapper.dart'; + +import '../utils/date_matcher.dart'; + +void main() { + test('toUser should map entity into User', () { + final entity = UserEntity( + id: 'testUserId', + role: 'testType', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + lastActive: DateTime.now(), + online: math.Random().nextBool(), + banned: math.Random().nextBool(), + extraData: {'test_extra_data': 'extraData'}, + ); + final user = entity.toUser(); + expect(user, isA()); + expect(user.id, entity.id); + expect(user.role, entity.role); + expect(user.createdAt, isSameDateAs(entity.createdAt)); + expect(user.updatedAt, isSameDateAs(entity.updatedAt)); + expect(user.lastActive, isSameDateAs(entity.lastActive)); + expect(user.online, entity.online); + expect(user.banned, entity.banned); + expect(user.extraData, entity.extraData); + }); + + test('toEntity should map user into UserEntity', () { + final user = User( + id: 'testUserId', + role: 'testType', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + lastActive: DateTime.now(), + online: math.Random().nextBool(), + banned: math.Random().nextBool(), + extraData: {'test_extra_data': 'extraData'}, + ); + final entity = user.toEntity(); + expect(entity, isA()); + expect(entity.id, user.id); + expect(entity.role, user.role); + expect(entity.createdAt, isSameDateAs(user.createdAt)); + expect(entity.updatedAt, isSameDateAs(user.updatedAt)); + expect(entity.lastActive, isSameDateAs(user.lastActive)); + expect(entity.online, user.online); + expect(entity.banned, user.banned); + expect(entity.extraData, user.extraData); + }); +} From 827e6a5dbd45823ed821a087838679f91a726b20 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Mar 2021 19:52:40 +0530 Subject: [PATCH 026/599] refactor(persistence): remove redundant UsersTable getter from ConnectionEventDao Signed-off-by: Sahil Kumar --- .../lib/src/dao/connection_event_dao.dart | 3 +-- .../lib/src/dao/connection_event_dao.g.dart | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart index 94b0d333..50528bd2 100644 --- a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart @@ -2,14 +2,13 @@ import 'package:moor/moor.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/entity/connection_events.dart'; -import 'package:stream_chat_persistence/src/entity/users.dart'; import 'package:stream_chat_persistence/src/mapper/mapper.dart'; part 'connection_event_dao.g.dart'; /// The Data Access Object for operations in [ConnectionEvents] table. -@UseDao(tables: [ConnectionEvents, Users]) +@UseDao(tables: [ConnectionEvents]) class ConnectionEventDao extends DatabaseAccessor with _$ConnectionEventDaoMixin { /// Creates a new connection event dao instance diff --git a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart index a8245982..28eb0913 100644 --- a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart +++ b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart @@ -9,5 +9,4 @@ part of 'connection_event_dao.dart'; mixin _$ConnectionEventDaoMixin on DatabaseAccessor { $ConnectionEventsTable get connectionEvents => attachedDatabase.connectionEvents; - $UsersTable get users => attachedDatabase.users; } From bea545b154f752497f7227f7f3f105fd446f68d5 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 18 Mar 2021 20:38:26 +0530 Subject: [PATCH 027/599] test(persistence): add tests for stream chat persistence client Signed-off-by: Sahil Kumar --- .../lib/src/db/moor_chat_database.dart | 21 +- .../lib/src/db/shared/native_db.dart | 58 +- .../lib/src/db/shared/unsupported_db.dart | 22 +- .../lib/src/db/shared/web_db.dart | 22 +- .../src/stream_chat_persistence_client.dart | 53 +- packages/stream_chat_persistence/pubspec.yaml | 5 +- .../test/mock_chat_database.dart | 71 +++ .../stream_chat_persistence_client_test.dart | 27 - .../stream_chat_persistence_client_test.dart | 524 ++++++++++++++++++ 9 files changed, 662 insertions(+), 141 deletions(-) create mode 100644 packages/stream_chat_persistence/test/mock_chat_database.dart delete mode 100644 packages/stream_chat_persistence/test/src/stream_chat_persistence_client_test.dart create mode 100644 packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart 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 2edaed4f..73c7b0fa 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 @@ -6,19 +6,11 @@ import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/converter/converter.dart'; import 'package:stream_chat_persistence/src/dao/dao.dart'; import 'package:stream_chat_persistence/src/entity/entity.dart'; -import 'package:stream_chat_persistence/src/db/shared/shared_db.dart'; + +export 'shared/shared_db.dart'; part 'moor_chat_database.g.dart'; -LazyDatabase _openConnection( - String userId, { - bool logStatements = false, -}) => - LazyDatabase(() async => SharedDB.constructDatabase( - userId, - logStatements: logStatements, - )); - /// A chat database implemented using moor @UseMoor(tables: [ Channels, @@ -44,12 +36,9 @@ LazyDatabase _openConnection( class MoorChatDatabase extends _$MoorChatDatabase { /// Creates a new moor chat database instance MoorChatDatabase( - this._userId, { - logStatements = false, - }) : super(_openConnection( - _userId, - logStatements: logStatements, - )); + this._userId, + QueryExecutor executor, + ) : super(executor); /// Instantiate a new database instance MoorChatDatabase.connect( diff --git a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart index a3b1ce8e..0868ad69 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart @@ -14,15 +14,40 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// A Helper class to construct new instances of [MoorChatDatabase] specifically /// for native platform applications class SharedDB { - /// Returns a new instance of [VmDatabase] created using [userId] - /// on a regular isolate. - /// - /// Generally used with [ConnectionMode.regular]. - static Future constructDatabase( + /// Returns a new instance of [MoorChatDatabase]. + static MoorChatDatabase constructDatabase( String userId, { bool logStatements = false, - }) async { + ConnectionMode connectionMode = ConnectionMode.regular, + }) { final dbName = 'db_$userId'; + if (connectionMode == ConnectionMode.background) { + return MoorChatDatabase.connect( + userId, + DatabaseConnection.delayed(Future(() async { + final isolate = await _createMoorIsolate( + dbName, + logStatements: logStatements, + ); + return isolate.connect(); + })), + ); + } + return MoorChatDatabase( + userId, + LazyDatabase( + () async => _constructDatabase( + dbName, + logStatements: logStatements, + ), + ), + ); + } + + static Future _constructDatabase( + String dbName, { + bool logStatements = false, + }) async { if (Platform.isIOS || Platform.isAndroid) { final dir = await getApplicationDocumentsDirectory(); final path = join(dir.path, '$dbName.sqlite'); @@ -66,27 +91,6 @@ class SharedDB { return await receivePort.first as MoorIsolate; } - - /// Returns a new instance of [MoorChatDatabase] using the factory constructor - /// [MoorChatDatabase.connect] created on a background isolate. - /// - /// Generally used with [ConnectionMode.background]. - static MoorChatDatabase constructMoorChatDatabase( - String userId, { - bool logStatements = false, - }) { - final dbName = 'db_$userId'; - return MoorChatDatabase.connect( - userId, - DatabaseConnection.delayed(Future(() async { - final isolate = await _createMoorIsolate( - dbName, - logStatements: logStatements, - ); - return isolate.connect(); - })), - ); - } } class _IsolateStartRequest { diff --git a/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart index 15734a7c..e2877030 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart @@ -1,28 +1,16 @@ -import 'package:moor/backends.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/stream_chat_persistence.dart'; /// A Helper class to construct new instances of [MoorChatDatabase] class SharedDB { - /// Returns a new instance of database. - /// - /// Generally used with [ConnectionMode.regular]. - static Future constructDatabase( + /// Returns a new instance of [MoorChatDatabase]. + static MoorChatDatabase constructDatabase( String userId, { bool logStatements = false, + ConnectionMode connectionMode = ConnectionMode.regular, }) { throw UnsupportedError( - 'No implementation of the constructDatabase api provided'); - } - - /// Return a new instance of moor chat database. - /// - /// Generally used with [ConnectionMode.background]. - static MoorChatDatabase constructMoorChatDatabase( - String userId, { - bool logStatements = false, - }) { - throw UnsupportedError( - 'No implementation of the constructMoorChatDatabase api provided'); + 'No implementation of the constructDatabase api provided', + ); } } diff --git a/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart index 7388543e..9c504a11 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart @@ -6,26 +6,14 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// A Helper class to construct new instances of [MoorChatDatabase] specifically /// for Web applications class SharedDB { - /// Returns a new instance of [WebDatabase] created using [userId]. - /// - /// Generally used with [ConnectionMode.regular]. - static Future constructDatabase( - String userId, { - bool logStatements = false, - }) async { - final dbName = 'db_$userId'; - return WebDatabase(dbName, logStatements: logStatements); - } - - /// Returns a new instance of [MoorChatDatabase] creating using the - /// default constructor. - /// - /// Generally used with [ConnectionMode.background]. - static MoorChatDatabase constructMoorChatDatabase( + /// Returns a new instance of [MoorChatDatabase]. + static MoorChatDatabase constructDatabase( String userId, { bool logStatements = false, + ConnectionMode connectionMode = ConnectionMode.regular, // Ignored on web }) { final dbName = 'db_$userId'; - return MoorChatDatabase(dbName, logStatements: logStatements); + final queryExecutor = WebDatabase(dbName, logStatements: logStatements); + return MoorChatDatabase(userId, queryExecutor); } } 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 e7ee060b..2c9d28fb 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 @@ -4,7 +4,6 @@ import 'package:mutex/mutex.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; -import 'package:stream_chat_persistence/src/db/shared/shared_db.dart'; /// Various connection modes on which [StreamChatPersistenceClient] can work enum ConnectionMode { @@ -15,6 +14,9 @@ enum ConnectionMode { background, } +/// Signature for a function which provides instance of [MoorChatDatabase] +typedef DatabaseProvider = MoorChatDatabase Function(String, ConnectionMode); + final _levelEmojiMapper = { Level.INFO: 'â„šī¸', Level.WARNING: 'âš ī¸', @@ -36,26 +38,6 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { _logger.onRecord.listen(logHandlerFunction ?? _defaultLogHandler); } - /// A function that has a parameter of type [LogRecord]. - /// This is called on every new log record. - /// By default the client will use the handler returned by - /// [_getDefaultLogHandler]. - /// Setting it you can handle the log messages directly instead of have them - /// written to stdout, - /// this is very convenient if you use an error tracking tool or if you want - /// to centralize your logs into one facility. - /// - /// ```dart - /// myLogHandlerFunction = (LogRecord record) { - /// // do something with the record (ie. send it to Sentry or Fabric) - /// } - /// - /// final client = StreamChatPersistenceClient( - /// logHandlerFunction: myLogHandlerFunction, - /// ); - ///``` - LogHandlerFunction logHandlerFunction; - /// [MoorChatDatabase] instance used by this client. @visibleForTesting MoorChatDatabase db; @@ -84,24 +66,25 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { return ret; } + MoorChatDatabase _defaultDatabaseProvider( + String userId, + ConnectionMode mode, + ) => + SharedDB.constructDatabase(userId, connectionMode: mode); + @override - Future connect(String userId) async { + Future connect( + String userId, { + DatabaseProvider databaseProvider, // Used only for testing + }) async { if (db != null) { throw Exception( 'An instance of StreamChatDatabase is already connected.\n' 'disconnect the previous instance before connecting again.', ); } - switch (_connectionMode) { - case ConnectionMode.regular: - _logger.info('Connecting on a regular isolate'); - db = MoorChatDatabase(userId); - return; - case ConnectionMode.background: - _logger.info('Connecting on background isolate'); - db = SharedDB.constructMoorChatDatabase(userId); - return; - } + db = databaseProvider?.call(userId, _connectionMode) ?? + _defaultDatabaseProvider(userId, _connectionMode); } @override @@ -259,9 +242,9 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { @override Future updateChannelQueries( Map filter, - List cids, - bool clearQueryCache, - ) => + List cids, { + bool clearQueryCache = false, + }) => _readProtected(() async { _logger.info('updateChannelQueries'); return db.channelQueryDao.updateChannelQueries( diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 8b235710..5328e1a2 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -27,6 +27,7 @@ dependency_overrides: path: ../stream_chat dev_dependencies: - test: ^1.15.7 build_runner: ^1.11.0 - moor_generator: ^3.4.1 \ No newline at end of file + mockito: ^4.1.3 + moor_generator: ^3.4.1 + test: ^1.15.7 diff --git a/packages/stream_chat_persistence/test/mock_chat_database.dart b/packages/stream_chat_persistence/test/mock_chat_database.dart new file mode 100644 index 00000000..a60bd39b --- /dev/null +++ b/packages/stream_chat_persistence/test/mock_chat_database.dart @@ -0,0 +1,71 @@ +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_persistence/src/dao/dao.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; + +class MockChatDatabase extends Mock implements MoorChatDatabase { + UserDao _userDao; + + @override + UserDao get userDao => _userDao ??= MockUserDao(); + + ChannelDao _channelDao; + + @override + ChannelDao get channelDao => _channelDao ??= MockChannelDao(); + + MessageDao _messageDao; + + @override + MessageDao get messageDao => _messageDao ??= MockMessageDao(); + + PinnedMessageDao _pinnedMessageDao; + + @override + PinnedMessageDao get pinnedMessageDao => + _pinnedMessageDao ??= MockPinnedMessageDao(); + + MemberDao _memberDao; + + @override + MemberDao get memberDao => _memberDao ??= MockMemberDao(); + + ReactionDao _reactionDao; + + @override + ReactionDao get reactionDao => _reactionDao ??= MockReactionDao(); + + ReadDao _readDao; + + @override + ReadDao get readDao => _readDao ??= MockReadDao(); + + ChannelQueryDao _channelQueryDao; + + @override + ChannelQueryDao get channelQueryDao => + _channelQueryDao ??= MockChannelQueryDao(); + + ConnectionEventDao _connectionEventDao; + + @override + ConnectionEventDao get connectionEventDao => + _connectionEventDao ??= MockConnectionEventDao(); +} + +class MockUserDao extends Mock implements UserDao {} + +class MockChannelDao extends Mock implements ChannelDao {} + +class MockMessageDao extends Mock implements MessageDao {} + +class MockPinnedMessageDao extends Mock implements PinnedMessageDao {} + +class MockMemberDao extends Mock implements MemberDao {} + +class MockReactionDao extends Mock implements ReactionDao {} + +class MockReadDao extends Mock implements ReadDao {} + +class MockChannelQueryDao extends Mock implements ChannelQueryDao {} + +class MockConnectionEventDao extends Mock implements ConnectionEventDao {} diff --git a/packages/stream_chat_persistence/test/src/stream_chat_persistence_client_test.dart b/packages/stream_chat_persistence/test/src/stream_chat_persistence_client_test.dart deleted file mode 100644 index 561ca04d..00000000 --- a/packages/stream_chat_persistence/test/src/stream_chat_persistence_client_test.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; -import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart'; -import 'package:test/test.dart'; - -void main() { - group('connect', () { - test('throws exception because already connected', () { - final streamChatPersistenceClient = StreamChatPersistenceClient( - connectionMode: ConnectionMode.background, - logLevel: Level.INFO, - )..db = MoorChatDatabase( - 'test', - persistOnDisk: false, - ); - - expect( - () => streamChatPersistenceClient.connect('test'), - throwsA(allOf(isException, predicate((e) { - return e.message == - 'An instance of StreamChatDatabase is already connected.\n' - 'disconnect the previous instance before connecting again.'; - }))), - ); - }); - }); -} diff --git a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart new file mode 100644 index 00000000..ef7fd020 --- /dev/null +++ b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart @@ -0,0 +1,524 @@ +import 'package:mockito/mockito.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart'; +import 'package:test/test.dart'; +import 'mock_chat_database.dart'; +import 'src/utils/date_matcher.dart'; + +MoorChatDatabase _testDatabaseProvider(String userId, ConnectionMode mode) => + MoorChatDatabase.testable(userId); + +void main() { + group('connect', () { + const userId = 'testUserId'; + test('successfully connects with the Database', () async { + final client = StreamChatPersistenceClient(logLevel: Level.ALL); + expect(client.db, isNull); + await client.connect(userId, databaseProvider: _testDatabaseProvider); + expect(client.db, isNotNull); + expect(client.db, isA()); + expect(client.db.userId, userId); + + addTearDown(() async { + await client.disconnect(); + }); + }); + + test('throws if already connected', () async { + final client = StreamChatPersistenceClient(logLevel: Level.ALL); + expect(client.db, isNull); + await client.connect(userId, databaseProvider: _testDatabaseProvider); + expect(client.db, isNotNull); + expect(client.db, isNotNull); + expect(client.db, isA()); + expect(client.db.userId, userId); + expect( + () => client.connect(userId, databaseProvider: _testDatabaseProvider), + throwsException, + ); + + addTearDown(() async { + await client.disconnect(); + }); + }); + }); + + test('disconnect', () async { + const userId = 'testUserId'; + final client = StreamChatPersistenceClient(logLevel: Level.ALL); + await client.connect(userId, databaseProvider: _testDatabaseProvider); + expect(client.db, isNotNull); + await client.disconnect(flush: true); + expect(client.db, isNull); + }); + + group('client functions', () { + const userId = 'testUserId'; + final mockDatabase = MockChatDatabase(); + MoorChatDatabase _mockDatabaseProvider(_, __) => mockDatabase; + StreamChatPersistenceClient client; + + setUp(() async { + client = StreamChatPersistenceClient(logLevel: Level.ALL); + await client.connect(userId, databaseProvider: _mockDatabaseProvider); + }); + + test('getReplies', () async { + const parentId = 'testParentId'; + final replies = List.generate(3, (index) => Message(id: 'testId$index')); + + when(mockDatabase.messageDao.getThreadMessagesByParentId(parentId)) + .thenAnswer((_) async => replies); + + final fetchedReplies = await client.getReplies(parentId); + expect(fetchedReplies.length, replies.length); + verify(mockDatabase.messageDao.getThreadMessagesByParentId(parentId)) + .called(1); + }); + + test('getConnectionInfo', () async { + final event = Event(type: 'testEvent'); + when(mockDatabase.connectionEventDao.connectionEvent) + .thenAnswer((_) async => event); + + final fetchedEvent = await client.getConnectionInfo(); + expect(fetchedEvent.type, event.type); + verify(mockDatabase.connectionEventDao.connectionEvent).called(1); + }); + + test('getLastSyncAt', () async { + final lastSync = DateTime.now(); + when(mockDatabase.connectionEventDao.lastSyncAt) + .thenAnswer((_) async => lastSync); + + final fetchedLastSync = await client.getLastSyncAt(); + expect(fetchedLastSync, isSameDateAs(lastSync)); + verify(mockDatabase.connectionEventDao.lastSyncAt).called(1); + }); + + test('updateConnectionInfo', () async { + final event = Event(type: 'testEvent'); + when(mockDatabase.connectionEventDao.updateConnectionEvent(event)) + .thenAnswer((_) async { + return; + }); + + await client.updateConnectionInfo(event); + verify(mockDatabase.connectionEventDao.updateConnectionEvent(event)) + .called(1); + }); + + test('updateLastSyncAt', () async { + final lastSync = DateTime.now(); + when(mockDatabase.connectionEventDao.updateLastSyncAt(lastSync)) + .thenAnswer((_) { + return; + }); + + await client.updateLastSyncAt(lastSync); + verify(mockDatabase.connectionEventDao.updateLastSyncAt(lastSync)) + .called(1); + }); + + test('getChannelCids', () async { + final channelCids = List.generate(3, (index) => 'testCid$index'); + when(mockDatabase.channelDao.cids).thenAnswer((_) async => channelCids); + + final fetchedChannelCids = await client.getChannelCids(); + expect(fetchedChannelCids.length, channelCids.length); + verify(mockDatabase.channelDao.cids).called(1); + }); + + test('getChannelByCid', () async { + const cid = 'testCid'; + final channelModel = ChannelModel(cid: cid); + when(mockDatabase.channelDao.getChannelByCid(cid)) + .thenAnswer((_) async => channelModel); + + final fetchedChannelModel = await client.getChannelByCid(cid); + expect(fetchedChannelModel.cid, channelModel.cid); + verify(mockDatabase.channelDao.getChannelByCid(cid)).called(1); + }); + + test('getMembersByCid', () async { + const cid = 'testCid'; + final members = List.generate(3, (index) => Member()); + when(mockDatabase.memberDao.getMembersByCid(cid)) + .thenAnswer((_) async => members); + + final fetchedMembers = await client.getMembersByCid(cid); + expect(fetchedMembers.length, members.length); + verify(mockDatabase.memberDao.getMembersByCid(cid)).called(1); + }); + + test('getReadsByCid', () async { + const cid = 'testCid'; + final reads = List.generate(3, (index) => Read()); + when(mockDatabase.readDao.getReadsByCid(cid)) + .thenAnswer((_) async => reads); + + final fetchedReads = await client.getReadsByCid(cid); + expect(fetchedReads.length, reads.length); + verify(mockDatabase.readDao.getReadsByCid(cid)).called(1); + }); + + test('getMessagesByCid', () async { + const cid = 'testCid'; + final messages = List.generate(3, (index) => Message()); + when(mockDatabase.messageDao.getMessagesByCid(cid)) + .thenAnswer((_) async => messages); + + final fetchedMessages = await client.getMessagesByCid(cid); + expect(fetchedMessages.length, messages.length); + verify(mockDatabase.messageDao.getMessagesByCid(cid)).called(1); + }); + + test('getPinnedMessagesByCid', () async { + const cid = 'testCid'; + final messages = List.generate(3, (index) => Message()); + when(mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) + .thenAnswer((_) async => messages); + + final fetchedMessages = await client.getPinnedMessagesByCid(cid); + expect(fetchedMessages.length, messages.length); + verify(mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).called(1); + }); + + test('getChannelStateByCid', () async { + const cid = 'testCid'; + final messages = List.generate(3, (index) => Message()); + final members = List.generate(3, (index) => Member()); + final reads = List.generate(3, (index) => Read()); + final channel = ChannelModel(cid: cid); + + when(mockDatabase.memberDao.getMembersByCid(cid)) + .thenAnswer((_) async => members); + when(mockDatabase.readDao.getReadsByCid(cid)) + .thenAnswer((_) async => reads); + when(mockDatabase.channelDao.getChannelByCid(cid)) + .thenAnswer((_) async => channel); + when(mockDatabase.messageDao.getMessagesByCid(cid)) + .thenAnswer((_) async => messages); + when(mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) + .thenAnswer((_) async => messages); + + final fetchedChannelState = await client.getChannelStateByCid(cid); + expect(fetchedChannelState.messages.length, messages.length); + expect(fetchedChannelState.pinnedMessages.length, messages.length); + expect(fetchedChannelState.members.length, members.length); + expect(fetchedChannelState.read.length, reads.length); + expect(fetchedChannelState.channel.cid, channel.cid); + + verify(mockDatabase.memberDao.getMembersByCid(cid)).called(1); + verify(mockDatabase.readDao.getReadsByCid(cid)).called(1); + verify(mockDatabase.channelDao.getChannelByCid(cid)).called(1); + verify(mockDatabase.messageDao.getMessagesByCid(cid)).called(1); + verify(mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).called(1); + }); + + test('getChannelStates', () async { + const cid = 'testCid'; + final channels = List.generate(3, (index) => ChannelModel(cid: cid)); + final messages = List.generate(3, (index) => Message()); + final members = List.generate(3, (index) => Member()); + final reads = List.generate(3, (index) => Read()); + final channel = ChannelModel(cid: cid); + final channelStates = channels + .map( + (channel) => ChannelState( + channel: channel, + messages: messages, + pinnedMessages: messages, + members: members, + read: reads, + ), + ) + .toList(growable: false); + + when(mockDatabase.channelQueryDao.getChannels()) + .thenAnswer((_) async => channels); + when(mockDatabase.memberDao.getMembersByCid(cid)) + .thenAnswer((_) async => members); + when(mockDatabase.readDao.getReadsByCid(cid)) + .thenAnswer((_) async => reads); + when(mockDatabase.channelDao.getChannelByCid(cid)) + .thenAnswer((_) async => channel); + when(mockDatabase.messageDao.getMessagesByCid(cid)) + .thenAnswer((_) async => messages); + when(mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) + .thenAnswer((_) async => messages); + + final fetchedChannelStates = await client.getChannelStates(); + expect(fetchedChannelStates.length, channelStates.length); + + for (var i = 0; i < fetchedChannelStates.length; i++) { + final original = channelStates[i]; + final fetched = fetchedChannelStates[i]; + expect(fetched.members.length, original.members.length); + expect(fetched.messages.length, original.messages.length); + expect(fetched.pinnedMessages.length, original.pinnedMessages.length); + expect(fetched.read.length, original.read.length); + expect(fetched.channel.cid, original.channel.cid); + } + + verify(mockDatabase.channelQueryDao.getChannels()).called(1); + verify(mockDatabase.memberDao.getMembersByCid(cid)).called(3); + verify(mockDatabase.readDao.getReadsByCid(cid)).called(3); + verify(mockDatabase.channelDao.getChannelByCid(cid)).called(3); + verify(mockDatabase.messageDao.getMessagesByCid(cid)).called(3); + verify(mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).called(3); + }); + + test('updateChannelQueries', () async { + const filter = {}; + const cids = []; + when(mockDatabase.channelQueryDao.updateChannelQueries(filter, cids)) + .thenAnswer((realInvocation) async { + return; + }); + + await client.updateChannelQueries(filter, cids); + verify(mockDatabase.channelQueryDao.updateChannelQueries(filter, cids)) + .called(1); + }); + + test('deleteMessageById', () async { + const messageId = 'testMessageId'; + when(mockDatabase.messageDao.deleteMessageByIds([messageId])) + .thenAnswer((_) async { + return; + }); + + await client.deleteMessageById(messageId); + verify(mockDatabase.messageDao.deleteMessageByIds([messageId])).called(1); + }); + + test('deletePinnedMessageById', () async { + const messageId = 'testMessageId'; + when(mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId])) + .thenAnswer((_) async { + return; + }); + + await client.deletePinnedMessageById(messageId); + verify(mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId])) + .called(1); + }); + + test('deleteMessageByIds', () async { + const messageIds = []; + when(mockDatabase.messageDao.deleteMessageByIds(messageIds)) + .thenAnswer((_) async { + return; + }); + + await client.deleteMessageByIds(messageIds); + verify(mockDatabase.messageDao.deleteMessageByIds(messageIds)).called(1); + }); + + test('deletePinnedMessageByIds', () async { + const messageIds = []; + when(mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds)) + .thenAnswer((_) async { + return; + }); + + await client.deletePinnedMessageByIds(messageIds); + verify(mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds)) + .called(1); + }); + + test('deleteMessageByCid', () async { + const cid = 'testCid'; + when(mockDatabase.messageDao.deleteMessageByCids([cid])) + .thenAnswer((_) async { + return; + }); + + await client.deleteMessageByCid(cid); + verify(mockDatabase.messageDao.deleteMessageByCids([cid])).called(1); + }); + + test('deletePinnedMessageByCid', () async { + const cid = 'testCid'; + when(mockDatabase.pinnedMessageDao.deleteMessageByCids([cid])) + .thenAnswer((_) async { + return; + }); + + await client.deletePinnedMessageByCid(cid); + verify(mockDatabase.pinnedMessageDao.deleteMessageByCids([cid])) + .called(1); + }); + + test('deleteMessageByCids', () async { + const cids = []; + when(mockDatabase.messageDao.deleteMessageByCids(cids)) + .thenAnswer((_) async { + return; + }); + + await client.deleteMessageByCids(cids); + verify(mockDatabase.messageDao.deleteMessageByCids(cids)).called(1); + }); + + test('deletePinnedMessageByCids', () async { + const cids = []; + when(mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)) + .thenAnswer((_) async { + return; + }); + + await client.deletePinnedMessageByCids(cids); + verify(mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)).called(1); + }); + + test('deleteChannels', () async { + const cids = []; + when(mockDatabase.channelDao.deleteChannelByCids(cids)) + .thenAnswer((_) async { + return; + }); + + await client.deleteChannels(cids); + verify(mockDatabase.channelDao.deleteChannelByCids(cids)).called(1); + }); + + test('updateMessages', () async { + const cid = 'testCid'; + final messages = List.generate(3, (index) => Message()); + when(mockDatabase.messageDao.updateMessages(cid, messages)) + .thenAnswer((_) async { + return; + }); + + await client.updateMessages(cid, messages); + verify(mockDatabase.messageDao.updateMessages(cid, messages)).called(1); + }); + + test('updatePinnedMessages', () async { + const cid = 'testCid'; + final messages = List.generate(3, (index) => Message()); + when(mockDatabase.pinnedMessageDao.updateMessages(cid, messages)) + .thenAnswer((_) async { + return; + }); + + await client.updatePinnedMessages(cid, messages); + verify(mockDatabase.pinnedMessageDao.updateMessages(cid, messages)) + .called(1); + }); + + test('getChannelThreads', () async { + const cid = 'testCid'; + final messages = + List.generate(3, (index) => Message(parentId: 'testParentId$index')); + final threads = messages.fold>>( + {}, + (prev, curr) { + return prev + ..update( + curr.parentId, + (value) => [...value, curr], + ifAbsent: () => [], + ); + }, + ); + when(mockDatabase.messageDao.getThreadMessages(cid)) + .thenAnswer((realInvocation) async => messages); + + final fetchedThreads = await client.getChannelThreads(cid); + expect(fetchedThreads.length, threads.length); + for (var i = 0; i < fetchedThreads.length; i++) { + final original = threads.entries.elementAt(i); + final fetched = fetchedThreads.entries.elementAt(i); + expect(fetched.key, original.key); + } + + verify(mockDatabase.messageDao.getThreadMessages(cid)).called(1); + }); + + test('updateChannels', () async { + final channels = List.generate(3, (index) => ChannelModel()); + when(mockDatabase.channelDao.updateChannels(channels)) + .thenAnswer((_) async { + return; + }); + + await client.updateChannels(channels); + verify(mockDatabase.channelDao.updateChannels(channels)).called(1); + }); + + test('updateMembers', () async { + const cid = 'testCid'; + final members = List.generate(3, (index) => Member()); + when(mockDatabase.memberDao.updateMembers(cid, members)) + .thenAnswer((_) async { + return; + }); + + await client.updateMembers(cid, members); + verify(mockDatabase.memberDao.updateMembers(cid, members)).called(1); + }); + + test('updateReads', () async { + const cid = 'testCid'; + final reads = List.generate(3, (index) => Read()); + when(mockDatabase.readDao.updateReads(cid, reads)).thenAnswer((_) async { + return; + }); + + await client.updateReads(cid, reads); + verify(mockDatabase.readDao.updateReads(cid, reads)).called(1); + }); + + test('updateUsers', () async { + final users = List.generate(3, (index) => User()); + when(mockDatabase.userDao.updateUsers(users)).thenAnswer((_) async { + return; + }); + + await client.updateUsers(users); + verify(mockDatabase.userDao.updateUsers(users)).called(1); + }); + + test('updateReactions', () async { + final reactions = List.generate(3, (index) => Reaction()); + when(mockDatabase.reactionDao.updateReactions(reactions)) + .thenAnswer((_) async { + return; + }); + + await client.updateReactions(reactions); + verify(mockDatabase.reactionDao.updateReactions(reactions)).called(1); + }); + + test('deleteReactionsByMessageId', () async { + final messageIds = []; + when(mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds)) + .thenAnswer((_) async { + return; + }); + + await client.deleteReactionsByMessageId(messageIds); + verify(mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds)) + .called(1); + }); + + test('deleteMembersByCids', () async { + final cids = []; + when(mockDatabase.memberDao.deleteMemberByCids(cids)) + .thenAnswer((_) async { + return; + }); + + await client.deleteMembersByCids(cids); + verify(mockDatabase.memberDao.deleteMemberByCids(cids)).called(1); + }); + + tearDown(() async { + await client.disconnect(flush: true); + }); + }); +} From 66edb127368a775afc14ed961f300e1bae9e4290 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 14:35:46 +0530 Subject: [PATCH 028/599] test(persistence): add tests moor chat database Signed-off-by: Sahil Kumar --- .../test/src/db/moor_chat_database_test.dart | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/stream_chat_persistence/test/src/db/moor_chat_database_test.dart diff --git a/packages/stream_chat_persistence/test/src/db/moor_chat_database_test.dart b/packages/stream_chat_persistence/test/src/db/moor_chat_database_test.dart new file mode 100644 index 00000000..2ec4c76b --- /dev/null +++ b/packages/stream_chat_persistence/test/src/db/moor_chat_database_test.dart @@ -0,0 +1,43 @@ +import 'package:test/test.dart'; +import 'package:moor/ffi.dart'; +import 'package:moor/isolate.dart'; +import 'package:moor/moor.dart' hide isNotNull; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; + +DatabaseConnection _backgroundConnection() => + DatabaseConnection.fromExecutor(VmDatabase.memory()); + +void main() { + test( + 'default constructor should create a new instance of MoorChatDatabase', + () async { + const userId = 'testUserId'; + final executor = VmDatabase.memory(); + final database = MoorChatDatabase(userId, executor); + expect(database, isNotNull); + expect(database.userId, userId); + + addTearDown(() async { + await database.disconnect(); + }); + }, + ); + + test( + 'connect constructor should create a new instance of MoorChatDatabase', + () async { + const userId = 'testUserId'; + final isolate = await MoorIsolate.spawn(_backgroundConnection); + final connection = DatabaseConnection.delayed(isolate.connect()); + + final database = MoorChatDatabase.connect(userId, connection); + expect(database, isNotNull); + expect(database.userId, userId); + + addTearDown(() async { + await database.disconnect(); + await isolate.shutdownAll(); + }); + }, + ); +} From d1e3cda2ec9b4db45b8c0b586902fcb41997b504 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 16:11:47 +0530 Subject: [PATCH 029/599] test(persistence): add moor schemas for future migration tests Signed-off-by: Sahil Kumar --- .../moor_schemas/moor_schema_v1.json | 798 ++++++++++++ .../moor_schemas/moor_schema_v2.json | 1089 +++++++++++++++++ 2 files changed, 1887 insertions(+) create mode 100644 packages/stream_chat_persistence/moor_schemas/moor_schema_v1.json create mode 100644 packages/stream_chat_persistence/moor_schemas/moor_schema_v2.json diff --git a/packages/stream_chat_persistence/moor_schemas/moor_schema_v1.json b/packages/stream_chat_persistence/moor_schemas/moor_schema_v1.json new file mode 100644 index 00000000..1a1b39a9 --- /dev/null +++ b/packages/stream_chat_persistence/moor_schemas/moor_schema_v1.json @@ -0,0 +1,798 @@ +{ + "_meta": { + "description": "This file contains a serialized version of schema entities for moor.", + "version": "0.1.0-dev-preview" + }, + "entities": [ + { + "id": 0, + "references": [], + "type": "table", + "data": { + "name": "channels", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "cid", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "config", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + }, + { + "name": "frozen", + "moor_type": "ColumnType.boolean", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(false)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "last_message_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "member_count", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_by_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "extra_data", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "explicit_pk": [ + "cid" + ] + } + }, + { + "id": 1, + "references": [], + "type": "table", + "data": { + "name": "messages", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "message_text", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "attachments", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "ListConverter()", + "dart_type_name": "List" + } + }, + { + "name": "status", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MessageSendingStatusConverter()", + "dart_type_name": "MessageSendingStatus" + } + }, + { + "name": "type", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "mentioned_users", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "ListConverter()", + "dart_type_name": "List" + } + }, + { + "name": "reaction_counts", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + }, + { + "name": "reaction_scores", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + }, + { + "name": "parent_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "quoted_message_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "reply_count", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "show_in_channel", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "shadowed", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "command", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "moor_type": "ColumnType.datetime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "user_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "channel_cid", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": "NULLABLE REFERENCES channels(cid) ON DELETE CASCADE", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "extra_data", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 2, + "references": [], + "type": "table", + "data": { + "name": "reactions", + "was_declared_in_moor": false, + "columns": [ + { + "name": "user_id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "message_id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": "REFERENCES messages(id) ON DELETE CASCADE", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "moor_type": "ColumnType.datetime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "score", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "extra_data", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "explicit_pk": [ + "message_id", + "type", + "user_id" + ] + } + }, + { + "id": 3, + "references": [], + "type": "table", + "data": { + "name": "users", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "role", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "last_active", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "online", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "banned", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "extra_data", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 4, + "references": [], + "type": "table", + "data": { + "name": "members", + "was_declared_in_moor": false, + "columns": [ + { + "name": "user_id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "channel_cid", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": "REFERENCES channels(cid) ON DELETE CASCADE", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "role", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "invite_accepted_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "invite_rejected_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "invited", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "banned", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "shadow_banned", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_moderator", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "moor_type": "ColumnType.datetime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "explicit_pk": [ + "user_id", + "channel_cid" + ] + } + }, + { + "id": 5, + "references": [], + "type": "table", + "data": { + "name": "reads", + "was_declared_in_moor": false, + "columns": [ + { + "name": "last_read", + "moor_type": "ColumnType.datetime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "user_id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "channel_cid", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": "REFERENCES channels(cid) ON DELETE CASCADE", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "unread_messages", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "explicit_pk": [ + "user_id", + "channel_cid" + ] + } + }, + { + "id": 6, + "references": [], + "type": "table", + "data": { + "name": "channel_queries", + "was_declared_in_moor": false, + "columns": [ + { + "name": "query_hash", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "channel_cid", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "explicit_pk": [ + "query_hash", + "channel_cid" + ] + } + }, + { + "id": 7, + "references": [], + "type": "table", + "data": { + "name": "connection_events", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "moor_type": "ColumnType.integer", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "own_user", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + }, + { + "name": "total_unread_count", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "unread_channels", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "last_event_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "last_sync_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "explicit_pk": [ + "id" + ] + } + } + ] +} \ No newline at end of file diff --git a/packages/stream_chat_persistence/moor_schemas/moor_schema_v2.json b/packages/stream_chat_persistence/moor_schemas/moor_schema_v2.json new file mode 100644 index 00000000..0f1caee5 --- /dev/null +++ b/packages/stream_chat_persistence/moor_schemas/moor_schema_v2.json @@ -0,0 +1,1089 @@ +{ + "_meta": { + "description": "This file contains a serialized version of schema entities for moor.", + "version": "0.1.0-dev-preview" + }, + "entities": [ + { + "id": 0, + "references": [], + "type": "table", + "data": { + "name": "channels", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "cid", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "config", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + }, + { + "name": "frozen", + "moor_type": "ColumnType.boolean", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(false)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "last_message_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "member_count", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_by_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "extra_data", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "explicit_pk": [ + "cid" + ] + } + }, + { + "id": 1, + "references": [], + "type": "table", + "data": { + "name": "messages", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "message_text", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "attachments", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "ListConverter()", + "dart_type_name": "List" + } + }, + { + "name": "status", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MessageSendingStatusConverter()", + "dart_type_name": "MessageSendingStatus" + } + }, + { + "name": "type", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "mentioned_users", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "ListConverter()", + "dart_type_name": "List" + } + }, + { + "name": "reaction_counts", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + }, + { + "name": "reaction_scores", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + }, + { + "name": "parent_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "quoted_message_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "reply_count", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "show_in_channel", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "shadowed", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "command", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "moor_type": "ColumnType.datetime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "user_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pinned", + "moor_type": "ColumnType.boolean", + "nullable": false, + "customConstraints": null, + "default_dart": "const Constant(false)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pinned_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pin_expires", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pinned_by_user_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "channel_cid", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": "NULLABLE REFERENCES channels(cid) ON DELETE CASCADE", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "extra_data", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 2, + "references": [], + "type": "table", + "data": { + "name": "pinned_messages", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "message_text", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "attachments", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "ListConverter()", + "dart_type_name": "List" + } + }, + { + "name": "status", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MessageSendingStatusConverter()", + "dart_type_name": "MessageSendingStatus" + } + }, + { + "name": "type", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "mentioned_users", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "ListConverter()", + "dart_type_name": "List" + } + }, + { + "name": "reaction_counts", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + }, + { + "name": "reaction_scores", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + }, + { + "name": "parent_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "quoted_message_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "reply_count", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "show_in_channel", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "shadowed", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "command", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "moor_type": "ColumnType.datetime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "user_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pinned", + "moor_type": "ColumnType.boolean", + "nullable": false, + "customConstraints": null, + "default_dart": "const Constant(false)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pinned_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pin_expires", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pinned_by_user_id", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "channel_cid", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": "NULLABLE REFERENCES channels(cid) ON DELETE CASCADE", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "extra_data", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 3, + "references": [], + "type": "table", + "data": { + "name": "reactions", + "was_declared_in_moor": false, + "columns": [ + { + "name": "user_id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "message_id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": "REFERENCES messages(id) ON DELETE CASCADE", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "moor_type": "ColumnType.datetime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "score", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "extra_data", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "explicit_pk": [ + "message_id", + "type", + "user_id" + ] + } + }, + { + "id": 4, + "references": [], + "type": "table", + "data": { + "name": "users", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "role", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "last_active", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "online", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "banned", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "extra_data", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 5, + "references": [], + "type": "table", + "data": { + "name": "members", + "was_declared_in_moor": false, + "columns": [ + { + "name": "user_id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "channel_cid", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": "REFERENCES channels(cid) ON DELETE CASCADE", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "role", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "invite_accepted_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "invite_rejected_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "invited", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "banned", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "shadow_banned", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_moderator", + "moor_type": "ColumnType.boolean", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "moor_type": "ColumnType.datetime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "explicit_pk": [ + "user_id", + "channel_cid" + ] + } + }, + { + "id": 6, + "references": [], + "type": "table", + "data": { + "name": "reads", + "was_declared_in_moor": false, + "columns": [ + { + "name": "last_read", + "moor_type": "ColumnType.datetime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "user_id", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "channel_cid", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": "REFERENCES channels(cid) ON DELETE CASCADE", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "unread_messages", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "explicit_pk": [ + "user_id", + "channel_cid" + ] + } + }, + { + "id": 7, + "references": [], + "type": "table", + "data": { + "name": "channel_queries", + "was_declared_in_moor": false, + "columns": [ + { + "name": "query_hash", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "channel_cid", + "moor_type": "ColumnType.text", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "explicit_pk": [ + "query_hash", + "channel_cid" + ] + } + }, + { + "id": 8, + "references": [], + "type": "table", + "data": { + "name": "connection_events", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "moor_type": "ColumnType.integer", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "own_user", + "moor_type": "ColumnType.text", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "MapConverter()", + "dart_type_name": "Map" + } + }, + { + "name": "total_unread_count", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "unread_channels", + "moor_type": "ColumnType.integer", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "last_event_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "last_sync_at", + "moor_type": "ColumnType.datetime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "explicit_pk": [ + "id" + ] + } + } + ] +} \ No newline at end of file From 4c2a748291452c4707f9c3aec18942ce453e8116 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 16:14:23 +0530 Subject: [PATCH 030/599] test(persistence): add clear queryCache test in channel query dao Signed-off-by: Sahil Kumar --- .../test/src/dao/channel_query_dao_test.dart | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) 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 dd9ecf0b..8ee0e0c0 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 @@ -35,6 +35,29 @@ void main() { expect(updatedCids, cids); }); + test('clear queryCache before updateChannelQueries', () async { + const filter = { + 'members': { + r'$in': ['testUserId'], + }, + }; + + const cids = ['testCid1', 'testCid2', 'testCid3']; + + final cachedCids = await channelQueryDao.getCachedChannelCids(filter); + expect(cachedCids, isEmpty); + + // Updating channel queries + await channelQueryDao.updateChannelQueries( + filter, + cids, + clearQueryCache: true, + ); + + final updatedCids = await channelQueryDao.getCachedChannelCids(filter); + expect(updatedCids, cids); + }); + test('getCachedChannelCids', () async { const filter = { 'members': { From e00881fbdd57a2850eccb7391135603608c85974 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 16:42:16 +0530 Subject: [PATCH 031/599] test(persistence): add getThreadMessagesByParentId along with pagination test in message, pinned message dao. Signed-off-by: Sahil Kumar --- .../lib/src/dao/message_dao.dart | 57 ++++++++++++------- .../lib/src/dao/pinned_message_dao.dart | 57 ++++++++++++------- .../test/src/dao/message_dao_test.dart | 38 ++++++++++++- .../test/src/dao/pinned_message_dao_test.dart | 38 ++++++++++++- 4 files changed, 150 insertions(+), 40 deletions(-) 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 ae238069..8d36b758 100644 --- a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart @@ -99,9 +99,26 @@ class MessageDao extends DatabaseAccessor .map(_messageFromJoinRow) .get()); - if (options?.lessThan != null && msgList.isNotEmpty) { - final lessThanIndex = msgList.indexWhere((m) => m.id == options.lessThan); - msgList.removeRange(lessThanIndex, msgList.length); + if (msgList.isNotEmpty) { + if (options?.lessThan != null) { + final lessThanIndex = msgList.indexWhere( + (m) => m.id == options.lessThan, + ); + if (lessThanIndex != -1) { + msgList.removeRange(lessThanIndex, msgList.length); + } + } + if (options?.greaterThanOrEqual != null) { + final greaterThanIndex = msgList.indexWhere( + (m) => m.id == options.greaterThanOrEqual, + ); + if (greaterThanIndex != -1) { + msgList.removeRange(0, greaterThanIndex); + } + } + if (options?.limit != null) { + return msgList.take(options.limit).toList(); + } } return msgList; } @@ -124,24 +141,26 @@ class MessageDao extends DatabaseAccessor .map(_messageFromJoinRow) .get()); - if (messagePagination?.lessThan != null) { - final lessThanIndex = msgList.indexWhere( - (m) => m.id == messagePagination.lessThan, - ); - if (lessThanIndex != -1) { - msgList.removeRange(lessThanIndex, msgList.length); + if (msgList.isNotEmpty) { + if (messagePagination?.lessThan != null) { + final lessThanIndex = msgList.indexWhere( + (m) => m.id == messagePagination.lessThan, + ); + if (lessThanIndex != -1) { + msgList.removeRange(lessThanIndex, msgList.length); + } } - } - if (messagePagination?.greaterThanOrEqual != null) { - final greaterThanIndex = msgList.indexWhere( - (m) => m.id == messagePagination.greaterThanOrEqual, - ); - if (greaterThanIndex != -1) { - msgList.removeRange(0, greaterThanIndex); + if (messagePagination?.greaterThanOrEqual != null) { + final greaterThanIndex = msgList.indexWhere( + (m) => m.id == messagePagination.greaterThanOrEqual, + ); + if (greaterThanIndex != -1) { + msgList.removeRange(0, greaterThanIndex); + } + } + if (messagePagination?.limit != null) { + return msgList.take(messagePagination.limit).toList(); } - } - if (messagePagination?.limit != null) { - return msgList.take(messagePagination.limit).toList(); } return msgList; } 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 cb634703..34ee4c28 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 @@ -99,9 +99,26 @@ class PinnedMessageDao extends DatabaseAccessor .map(_messageFromJoinRow) .get()); - if (options?.lessThan != null) { - final lessThanIndex = msgList.indexWhere((m) => m.id == options.lessThan); - msgList.removeRange(lessThanIndex, msgList.length); + if (msgList.isNotEmpty) { + if (options?.lessThan != null) { + final lessThanIndex = msgList.indexWhere( + (m) => m.id == options.lessThan, + ); + if (lessThanIndex != -1) { + msgList.removeRange(lessThanIndex, msgList.length); + } + } + if (options?.greaterThanOrEqual != null) { + final greaterThanIndex = msgList.indexWhere( + (m) => m.id == options.greaterThanOrEqual, + ); + if (greaterThanIndex != -1) { + msgList.removeRange(0, greaterThanIndex); + } + } + if (options?.limit != null) { + return msgList.take(options.limit).toList(); + } } return msgList; } @@ -124,24 +141,26 @@ class PinnedMessageDao extends DatabaseAccessor .map(_messageFromJoinRow) .get()); - if (messagePagination?.lessThan != null) { - final lessThanIndex = msgList.indexWhere( - (m) => m.id == messagePagination.lessThan, - ); - if (lessThanIndex != -1) { - msgList.removeRange(lessThanIndex, msgList.length); + if (msgList.isNotEmpty) { + if (messagePagination?.lessThan != null) { + final lessThanIndex = msgList.indexWhere( + (m) => m.id == messagePagination.lessThan, + ); + if (lessThanIndex != -1) { + msgList.removeRange(lessThanIndex, msgList.length); + } } - } - if (messagePagination?.greaterThanOrEqual != null) { - final greaterThanIndex = msgList.indexWhere( - (m) => m.id == messagePagination.greaterThanOrEqual, - ); - if (greaterThanIndex != -1) { - msgList.removeRange(0, greaterThanIndex); + if (messagePagination?.greaterThanOrEqual != null) { + final greaterThanIndex = msgList.indexWhere( + (m) => m.id == messagePagination.greaterThanOrEqual, + ); + if (greaterThanIndex != -1) { + msgList.removeRange(0, greaterThanIndex); + } + } + if (messagePagination?.limit != null) { + return msgList.take(messagePagination.limit).toList(); } - } - if (messagePagination?.limit != null) { - return msgList.take(messagePagination.limit).toList(); } return msgList; } diff --git a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart index dccb414c..7207002e 100644 --- a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart @@ -17,6 +17,7 @@ void main() { Future> _prepareTestData( String cid, { bool threads = false, + bool mapAllThreadToFirstMessage = false, int count = 3, }) async { final messages = List.generate( @@ -43,7 +44,8 @@ void main() { id: 'testThreadMessageId$cid$index', type: 'testType', user: User(id: 'testUserId$index'), - parentId: messages[index].id, + parentId: + mapAllThreadToFirstMessage ? messages[0].id : messages[index].id, createdAt: DateTime.now(), shadowed: math.Random().nextBool(), showInChannel: math.Random().nextBool(), @@ -184,6 +186,40 @@ void main() { expect(threadMessages.first.parentId, parentId); }); + test('getThreadMessagesByParentId along with pagination', () async { + const cid = 'testCid'; + const parentId = 'testMessageId${cid}0'; + const options = PaginationParams( + limit: 15, + lessThan: 'testThreadMessageId${cid}25', + greaterThanOrEqual: 'testThreadMessageId${cid}5', + ); + + // Messages should be empty initially + final messages = await messageDao.getThreadMessagesByParentId( + parentId, + options: options, + ); + expect(messages, isEmpty); + + // Preparing test data + final insertedMessages = await _prepareTestData( + cid, + threads: true, + mapAllThreadToFirstMessage: true, + count: 30, + ); + expect(insertedMessages, isNotEmpty); + + // Should fetch all the thread messages of parentId and apply the pagination + final threadMessages = await messageDao.getThreadMessagesByParentId( + parentId, + options: options, + ); + expect(threadMessages.length, 15); + expect(threadMessages.first.parentId, parentId); + }); + test('getMessagesByCid', () async { const cid = 'testCid'; diff --git a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart index 5c1bce46..3e197e2b 100644 --- a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart @@ -17,6 +17,7 @@ void main() { Future> _prepareTestData( String cid, { bool threads = false, + bool mapAllThreadToFirstMessage = false, int count = 3, }) async { final messages = List.generate( @@ -43,7 +44,8 @@ void main() { id: 'testThreadMessageId$cid$index', type: 'testType', user: User(id: 'testUserId$index'), - parentId: messages[index].id, + parentId: + mapAllThreadToFirstMessage ? messages[0].id : messages[index].id, createdAt: DateTime.now(), shadowed: math.Random().nextBool(), showInChannel: math.Random().nextBool(), @@ -185,6 +187,40 @@ void main() { expect(threadMessages.first.parentId, parentId); }); + test('getThreadMessagesByParentId along with pagination', () async { + const cid = 'testCid'; + const parentId = 'testMessageId${cid}0'; + const options = PaginationParams( + limit: 15, + lessThan: 'testThreadMessageId${cid}25', + greaterThanOrEqual: 'testThreadMessageId${cid}5', + ); + + // Messages should be empty initially + final messages = await pinnedMessageDao.getThreadMessagesByParentId( + parentId, + options: options, + ); + expect(messages, isEmpty); + + // Preparing test data + final insertedMessages = await _prepareTestData( + cid, + threads: true, + mapAllThreadToFirstMessage: true, + count: 30, + ); + expect(insertedMessages, isNotEmpty); + + // Should fetch all the thread messages of parentId and apply the pagination + final threadMessages = await pinnedMessageDao.getThreadMessagesByParentId( + parentId, + options: options, + ); + expect(threadMessages.length, 15); + expect(threadMessages.first.parentId, parentId); + }); + test('getMessagesByCid', () async { const cid = 'testCid'; From 252783adf64a37df926db4f08a5b6134056fe3fb Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 16:52:08 +0530 Subject: [PATCH 032/599] test(persistence): add getMessagesByCid along with pagination test in message, pinned message dao. Signed-off-by: Sahil Kumar --- .../test/src/dao/message_dao_test.dart | 27 +++++++++++++++++++ .../test/src/dao/pinned_message_dao_test.dart | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart index 7207002e..377c949e 100644 --- a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart @@ -241,6 +241,33 @@ void main() { } }); + test('getMessagesByCid along with pagination', () async { + const cid = 'testCid'; + const pagination = PaginationParams( + limit: 15, + lessThan: 'testMessageId${cid}25', + greaterThanOrEqual: 'testMessageId${cid}5', + ); + + // Should be empty initially + final messages = await messageDao.getMessagesByCid( + cid, + messagePagination: pagination, + ); + expect(messages, isEmpty); + + // Preparing test data + final insertedMessages = await _prepareTestData(cid, count: 30); + expect(insertedMessages, isNotEmpty); + + // Fetched message should match the inserted messages + final fetchedMessages = await messageDao.getMessagesByCid( + cid, + messagePagination: pagination, + ); + expect(fetchedMessages.length, 15); + }); + test('updateMessages', () async { const cid = 'testCid'; diff --git a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart index 3e197e2b..66875990 100644 --- a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart @@ -242,6 +242,33 @@ void main() { } }); + test('getMessagesByCid along with pagination', () async { + const cid = 'testCid'; + const pagination = PaginationParams( + limit: 15, + lessThan: 'testMessageId${cid}25', + greaterThanOrEqual: 'testMessageId${cid}5', + ); + + // Should be empty initially + final messages = await pinnedMessageDao.getMessagesByCid( + cid, + messagePagination: pagination, + ); + expect(messages, isEmpty); + + // Preparing test data + final insertedMessages = await _prepareTestData(cid, count: 30); + expect(insertedMessages, isNotEmpty); + + // Fetched message should match the inserted messages + final fetchedMessages = await pinnedMessageDao.getMessagesByCid( + cid, + messagePagination: pagination, + ); + expect(fetchedMessages.length, 15); + }); + test('updateMessages', () async { const cid = 'testCid'; From 2b17fcfac89604874e0730a276bce2f7acfff70b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 19 Mar 2021 12:25:43 +0100 Subject: [PATCH 033/599] update ci --- .github/workflows/stream_flutter_workflow.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 52a504fe..a25bbdb3 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -8,6 +8,7 @@ on: push: branches: - master + - develop paths-ignore: - 'docs/**' From 8d2934f14220c18def11f79bad007e10ff7fd5ad Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 19 Mar 2021 12:26:08 +0100 Subject: [PATCH 034/599] apply locale to jiffy --- packages/stream_chat_flutter/lib/src/channel_preview.dart | 4 ++-- packages/stream_chat_flutter/lib/src/date_divider.dart | 6 +++--- .../stream_chat_flutter/lib/src/full_screen_media.dart | 2 +- .../stream_chat_flutter/lib/src/message_search_item.dart | 4 ++-- packages/stream_chat_flutter/lib/src/stream_chat.dart | 8 ++++++++ 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 9ba34f3a..3243988b 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -170,14 +170,14 @@ class ChannelPreview extends StatelessWidget { if (lastMessageAt.millisecondsSinceEpoch >= startOfDay.millisecondsSinceEpoch) { - stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm'); + stringDate = Jiffy(lastMessageAt.toLocal()).jm; } else if (lastMessageAt.millisecondsSinceEpoch >= startOfDay.subtract(Duration(days: 1)).millisecondsSinceEpoch) { stringDate = 'Yesterday'; } else if (startOfDay.difference(lastMessageAt).inDays < 7) { stringDate = Jiffy(lastMessageAt.toLocal()).EEEE; } else { - stringDate = Jiffy(lastMessageAt.toLocal()).format('dd/MM/yyyy'); + stringDate = Jiffy(lastMessageAt.toLocal()).yMd; } return Text( diff --git a/packages/stream_chat_flutter/lib/src/date_divider.dart b/packages/stream_chat_flutter/lib/src/date_divider.dart index c7cfe296..20b8151d 100644 --- a/packages/stream_chat_flutter/lib/src/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/date_divider.dart @@ -28,14 +28,14 @@ class DateDivider extends StatelessWidget { now.subtract(Duration(days: 7)), Units.DAY, )) { - dayInfo = createdAt.format('EEEE'); + dayInfo = createdAt.EEEE; } else if (Jiffy(createdAt).isAfter( Jiffy(now).subtract(years: 1), Units.DAY, )) { - dayInfo = createdAt.format('MMMM d'); + dayInfo = createdAt.MMMd; } else { - dayInfo = createdAt.format('MMMM d'); + dayInfo = createdAt.MMMd; } if (uppercase) dayInfo = dayInfo.toUpperCase(); 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 88c20a51..91c66ca4 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -220,7 +220,7 @@ class _FullScreenMediaState extends State 24) { return 'yesterday'; } else { - return 'on ${Jiffy(dateTime).format("MMM do")}'; + return 'on ${Jiffy(dateTime).MMMd}'; } } 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 335f7ff4..4cf7b5ed 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_item.dart @@ -84,9 +84,9 @@ class MessageSearchItem extends StatelessWidget { if (now.year != createdAt.year || now.month != createdAt.month || now.day != createdAt.day) { - stringDate = Jiffy(createdAt.toLocal()).format('dd/MM/yyyy'); + stringDate = Jiffy(createdAt.toLocal()).yMd; } else { - stringDate = Jiffy(createdAt.toLocal()).format('HH:mm'); + stringDate = Jiffy(createdAt.toLocal()).jm; } return Text( diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 66b49d88..8ad751b0 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; +import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -121,4 +122,11 @@ class StreamChatState extends State { void initState() { super.initState(); } + + @override + void didChangeDependencies() { + final locals = Localizations.localeOf(context); + Jiffy.locale(locals.languageCode); + super.didChangeDependencies(); + } } From 1f503fbc98f3d96645a2cf79f9909268d0fbbd59 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 19 Mar 2021 13:00:39 +0100 Subject: [PATCH 035/599] fix test --- packages/stream_chat_flutter/test/src/channel_preview_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/test/src/channel_preview_test.dart b/packages/stream_chat_flutter/test/src/channel_preview_test.dart index 34b9bf92..094c74a5 100644 --- a/packages/stream_chat_flutter/test/src/channel_preview_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_preview_test.dart @@ -69,7 +69,7 @@ void main() { ), )); - expect(find.text('22/06/2020'), findsOneWidget); + expect(find.text('6/22/2020'), findsOneWidget); expect(find.text('test name'), findsOneWidget); expect(find.text('1'), findsOneWidget); expect(find.text('hello'), findsOneWidget); From ab9990e5bdd5f27c964340c84ff31549eb20fe78 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 17:34:41 +0530 Subject: [PATCH 036/599] test(persistence): add getMessagesByCid along with quotedMessage test in message, pinned message dao. Signed-off-by: Sahil Kumar --- .../test/src/dao/message_dao_test.dart | 51 +++++++++++++++++-- .../test/src/dao/pinned_message_dao_test.dart | 51 +++++++++++++++++-- 2 files changed, 92 insertions(+), 10 deletions(-) diff --git a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart index 377c949e..85e0a795 100644 --- a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart @@ -16,19 +16,20 @@ void main() { Future> _prepareTestData( String cid, { + bool quoted = false, bool threads = false, bool mapAllThreadToFirstMessage = false, int count = 3, }) async { + final users = List.generate(count, (index) => User(id: 'testUserId$index')); final messages = List.generate( count, (index) => Message( id: 'testMessageId$cid$index', type: 'testType', - user: User(id: 'testUserId$index'), + user: users[index], createdAt: DateTime.now(), shadowed: math.Random().nextBool(), - showInChannel: math.Random().nextBool(), replyCount: index, updatedAt: DateTime.now(), extraData: {'extra_test_field': 'extraTestData'}, @@ -38,17 +39,34 @@ void main() { pinnedBy: User(id: 'testUserId$index'), ), ); + final quotedMessages = List.generate( + count, + (index) => Message( + id: 'testQuotedMessageId$cid$index', + type: 'testType', + user: users[index], + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + replyCount: index, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text #$index', + quotedMessageId: messages[index].id, + pinned: math.Random().nextBool(), + pinnedAt: DateTime.now(), + pinnedBy: User(id: 'testUserId$index'), + ), + ); final threadMessages = List.generate( count, (index) => Message( id: 'testThreadMessageId$cid$index', type: 'testType', - user: User(id: 'testUserId$index'), + user: users[index], parentId: mapAllThreadToFirstMessage ? messages[0].id : messages[index].id, createdAt: DateTime.now(), shadowed: math.Random().nextBool(), - showInChannel: math.Random().nextBool(), replyCount: index, updatedAt: DateTime.now(), extraData: {'extra_test_field': 'extraTestData'}, @@ -58,7 +76,12 @@ void main() { pinnedBy: User(id: 'testUserId$index'), ), ); - final allMessages = [...messages, if (threads) ...threadMessages]; + final allMessages = [ + ...messages, + if (quoted) ...quotedMessages, + if (threads) ...threadMessages + ]; + await database.userDao.updateUsers(users); await messageDao.updateMessages(cid, allMessages); return allMessages; } @@ -241,6 +264,24 @@ void main() { } }); + test('getMessagesByCid along with quotedMessage', () async { + const cid = 'testCid'; + + // Should be empty initially + final messages = await messageDao.getMessagesByCid(cid); + expect(messages, isEmpty); + + // Preparing test data + final insertedMessages = await _prepareTestData(cid, quoted: true); + expect(insertedMessages, isNotEmpty); + + // Fetched message should match the inserted messages + final fetchedMessages = await messageDao.getMessagesByCid(cid); + expect(fetchedMessages.length, insertedMessages.length); + final quoted = fetchedMessages.where((it) => it.quotedMessage != null); + expect(quoted.length, insertedMessages.length / 2); + }); + test('getMessagesByCid along with pagination', () async { const cid = 'testCid'; const pagination = PaginationParams( diff --git a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart index 66875990..26c652db 100644 --- a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart @@ -16,19 +16,20 @@ void main() { Future> _prepareTestData( String cid, { + bool quoted = false, bool threads = false, bool mapAllThreadToFirstMessage = false, int count = 3, }) async { + final users = List.generate(count, (index) => User(id: 'testUserId$index')); final messages = List.generate( count, (index) => Message( id: 'testMessageId$cid$index', type: 'testType', - user: User(id: 'testUserId$index'), + user: users[index], createdAt: DateTime.now(), shadowed: math.Random().nextBool(), - showInChannel: math.Random().nextBool(), replyCount: index, updatedAt: DateTime.now(), extraData: {'extra_test_field': 'extraTestData'}, @@ -38,17 +39,34 @@ void main() { pinnedBy: User(id: 'testUserId$index'), ), ); + final quotedMessages = List.generate( + count, + (index) => Message( + id: 'testQuotedMessageId$cid$index', + type: 'testType', + user: users[index], + createdAt: DateTime.now(), + shadowed: math.Random().nextBool(), + replyCount: index, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text #$index', + quotedMessageId: messages[index].id, + pinned: math.Random().nextBool(), + pinnedAt: DateTime.now(), + pinnedBy: User(id: 'testUserId$index'), + ), + ); final threadMessages = List.generate( count, (index) => Message( id: 'testThreadMessageId$cid$index', type: 'testType', - user: User(id: 'testUserId$index'), + user: users[index], parentId: mapAllThreadToFirstMessage ? messages[0].id : messages[index].id, createdAt: DateTime.now(), shadowed: math.Random().nextBool(), - showInChannel: math.Random().nextBool(), replyCount: index, updatedAt: DateTime.now(), extraData: {'extra_test_field': 'extraTestData'}, @@ -58,7 +76,12 @@ void main() { pinnedBy: User(id: 'testUserId$index'), ), ); - final allMessages = [...messages, if (threads) ...threadMessages]; + final allMessages = [ + ...messages, + if (quoted) ...quotedMessages, + if (threads) ...threadMessages + ]; + await database.userDao.updateUsers(users); await pinnedMessageDao.updateMessages(cid, allMessages); return allMessages; } @@ -242,6 +265,24 @@ void main() { } }); + test('getMessagesByCid along with quotedMessage', () async { + const cid = 'testCid'; + + // Should be empty initially + final messages = await pinnedMessageDao.getMessagesByCid(cid); + expect(messages, isEmpty); + + // Preparing test data + final insertedMessages = await _prepareTestData(cid, quoted: true); + expect(insertedMessages, isNotEmpty); + + // Fetched message should match the inserted messages + final fetchedMessages = await pinnedMessageDao.getMessagesByCid(cid); + expect(fetchedMessages.length, insertedMessages.length); + final quoted = fetchedMessages.where((it) => it.quotedMessage != null); + expect(quoted.length, insertedMessages.length / 2); + }); + test('getMessagesByCid along with pagination', () async { const cid = 'testCid'; const pagination = PaginationParams( From 2e3baa020d150fcef44c304ebc9d87ef6a3562f2 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 17:38:07 +0530 Subject: [PATCH 037/599] test(persistence): add extra checks in getMessagesByCid along with pagination test in message, pinned message dao. Signed-off-by: Sahil Kumar --- .../test/src/dao/message_dao_test.dart | 13 +++++++++---- .../test/src/dao/pinned_message_dao_test.dart | 13 +++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart index 85e0a795..f2440ae1 100644 --- a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart @@ -284,10 +284,13 @@ void main() { test('getMessagesByCid along with pagination', () async { const cid = 'testCid'; + const limit = 15; + const lessThan = 'testMessageId${cid}25'; + const greaterThanOrEqual = 'testMessageId${cid}5'; const pagination = PaginationParams( - limit: 15, - lessThan: 'testMessageId${cid}25', - greaterThanOrEqual: 'testMessageId${cid}5', + limit: limit, + lessThan: lessThan, + greaterThanOrEqual: greaterThanOrEqual, ); // Should be empty initially @@ -306,7 +309,9 @@ void main() { cid, messagePagination: pagination, ); - expect(fetchedMessages.length, 15); + expect(fetchedMessages.length, limit); + expect(fetchedMessages.first.id, greaterThanOrEqual); + expect(fetchedMessages.last.id != lessThan, true); }); test('updateMessages', () async { diff --git a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart index 26c652db..de7c8976 100644 --- a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart @@ -285,10 +285,13 @@ void main() { test('getMessagesByCid along with pagination', () async { const cid = 'testCid'; + const limit = 15; + const lessThan = 'testMessageId${cid}25'; + const greaterThanOrEqual = 'testMessageId${cid}5'; const pagination = PaginationParams( - limit: 15, - lessThan: 'testMessageId${cid}25', - greaterThanOrEqual: 'testMessageId${cid}5', + limit: limit, + lessThan: lessThan, + greaterThanOrEqual: greaterThanOrEqual, ); // Should be empty initially @@ -307,7 +310,9 @@ void main() { cid, messagePagination: pagination, ); - expect(fetchedMessages.length, 15); + expect(fetchedMessages.length, limit); + expect(fetchedMessages.first.id, greaterThanOrEqual); + expect(fetchedMessages.last.id != lessThan, true); }); test('updateMessages', () async { From abbe72709e905632372d3d12bedad6fee7b899e9 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 17:49:50 +0530 Subject: [PATCH 038/599] test(persistence): add return channel with pagination applies test in channel query dao Signed-off-by: Sahil Kumar --- .../test/src/dao/channel_query_dao_test.dart | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) 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 8ee0e0c0..4566ca94 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 @@ -78,15 +78,17 @@ void main() { }); Future> _insertTestDataForGetChannel( - Map filter) async { + Map filter, { + int count = 3, + }) async { final now = DateTime.now(); final userDao = database.userDao; final channelDao = database.channelDao; - const cids = ['testCid0', 'testCid1', 'testCid2']; - final users = List.generate(3, (index) => User(id: 'testId$index')); + final cids = List.generate(count, (index) => 'testCid$index'); + final users = List.generate(count, (index) => User(id: 'testId$index')); final channels = List.generate( - 3, + count, (index) => ChannelModel( id: 'testId$index', type: 'testType$index', @@ -150,6 +152,30 @@ void main() { } }); + test( + 'should return all the inserted channels along with pagination applied', + () async { + const offset = 5; + const limit = 15; + const pagination = PaginationParams(offset: offset, limit: limit); + + // Inserting test data for get channels + final insertedChannels = await _insertTestDataForGetChannel( + filter, + count: 30, + ); + + // Should match with the inserted channels + final updatedChannels = await channelQueryDao.getChannels( + filter: filter, + paginationParams: pagination, + ); + expect(updatedChannels.length, limit); + expect(updatedChannels.first.id, 'testId24'); + expect(updatedChannels.first.cid, 'testCid24'); + }, + ); + test('should return sorted channels using member count', () async { int sortComparator(ChannelModel a, ChannelModel b) => b.memberCount.compareTo(a.memberCount); From b570d3332af615cb48fbfebf6fbcb519f1033653 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 18:03:00 +0530 Subject: [PATCH 039/599] test(persistence): improve message mapper, pinned message mapper test Signed-off-by: Sahil Kumar --- .../test/src/mapper/message_mapper_test.dart | 35 ++++++++++++++++--- .../mapper/pinned_message_mapper_test.dart | 35 ++++++++++++++++--- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart index 0554a0ce..21a05da8 100644 --- a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:math' as math; import 'package:test/test.dart'; @@ -21,9 +22,17 @@ void main() { score: math.Random().nextInt(50), ), ); + final attachments = List.generate( + 3, + (index) => Attachment( + id: 'testAttachmentId', + type: 'testAttachmentType', + assetUrl: 'testAssetUrl', + ), + ); final entity = MessageEntity( id: 'testMessageId', - attachments: [], + attachments: attachments?.map((it) => jsonEncode(it.toData()))?.toList(), channelCid: 'testCid', type: 'testType', parentId: 'testParentId', @@ -60,7 +69,6 @@ void main() { expect(message, isA()); expect(message.id, entity.id); - expect(message.attachments, entity.attachments); expect(message.type, entity.type); expect(message.parentId, entity.parentId); expect(message.quotedMessageId, entity.quotedMessageId); @@ -83,6 +91,14 @@ void main() { expect(message.pinnedBy.id, entity.pinnedByUserId); expect(message.reactionCounts, entity.reactionCounts); expect(message.reactionScores, entity.reactionScores); + for (var i = 0; i < message.attachments.length; i++) { + final messageAttachment = message.attachments[i]; + final entityAttachmentData = jsonDecode(entity.attachments[i]); + final entityAttachment = Attachment.fromData(entityAttachmentData); + expect(messageAttachment.id, entityAttachment.id); + expect(messageAttachment.type, entityAttachment.type); + expect(messageAttachment.assetUrl, entityAttachment.assetUrl); + } }); test('toEntity should map message into MessageEntity', () { @@ -99,9 +115,17 @@ void main() { score: math.Random().nextInt(50), ), ); + final attachments = List.generate( + 3, + (index) => Attachment( + id: 'testAttachmentId', + type: 'testAttachmentType', + assetUrl: 'testAssetUrl', + ), + ); final message = Message( id: 'testMessageId', - attachments: [], + attachments: attachments, type: 'testType', parentId: 'testParentId', quotedMessageId: quotedMessage.id, @@ -130,7 +154,6 @@ void main() { final entity = message.toEntity(cid: cid); expect(entity, isA()); expect(entity.id, message.id); - expect(entity.attachments, message.attachments); expect(entity.type, message.type); expect(entity.parentId, message.parentId); expect(entity.quotedMessageId, message.quotedMessageId); @@ -153,5 +176,9 @@ void main() { expect(entity.pinnedByUserId, message.pinnedBy.id); expect(entity.reactionCounts, message.reactionCounts); expect(entity.reactionScores, message.reactionScores); + expect( + entity.attachments, + message.attachments?.map((it) => jsonEncode(it.toData()))?.toList(), + ); }); } diff --git a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart index e79dd051..0aadf08a 100644 --- a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:math' as math; import 'package:test/test.dart'; @@ -21,9 +22,17 @@ void main() { score: math.Random().nextInt(50), ), ); + final attachments = List.generate( + 3, + (index) => Attachment( + id: 'testAttachmentId', + type: 'testAttachmentType', + assetUrl: 'testAssetUrl', + ), + ); final entity = PinnedMessageEntity( id: 'testMessageId', - attachments: [], + attachments: attachments?.map((it) => jsonEncode(it.toData()))?.toList(), channelCid: 'testCid', type: 'testType', parentId: 'testParentId', @@ -60,7 +69,6 @@ void main() { expect(message, isA()); expect(message.id, entity.id); - expect(message.attachments, entity.attachments); expect(message.type, entity.type); expect(message.parentId, entity.parentId); expect(message.quotedMessageId, entity.quotedMessageId); @@ -83,6 +91,14 @@ void main() { expect(message.pinnedBy.id, entity.pinnedByUserId); expect(message.reactionCounts, entity.reactionCounts); expect(message.reactionScores, entity.reactionScores); + for (var i = 0; i < message.attachments.length; i++) { + final messageAttachment = message.attachments[i]; + final entityAttachmentData = jsonDecode(entity.attachments[i]); + final entityAttachment = Attachment.fromData(entityAttachmentData); + expect(messageAttachment.id, entityAttachment.id); + expect(messageAttachment.type, entityAttachment.type); + expect(messageAttachment.assetUrl, entityAttachment.assetUrl); + } }); test('toPinnedEntity should map message into PinnedMessageEntity', () { @@ -99,9 +115,17 @@ void main() { score: math.Random().nextInt(50), ), ); + final attachments = List.generate( + 3, + (index) => Attachment( + id: 'testAttachmentId', + type: 'testAttachmentType', + assetUrl: 'testAssetUrl', + ), + ); final message = Message( id: 'testMessageId', - attachments: [], + attachments: attachments, type: 'testType', parentId: 'testParentId', quotedMessageId: quotedMessage.id, @@ -130,7 +154,6 @@ void main() { final entity = message.toPinnedEntity(cid: cid); expect(entity, isA()); expect(entity.id, message.id); - expect(entity.attachments, message.attachments); expect(entity.type, message.type); expect(entity.parentId, message.parentId); expect(entity.quotedMessageId, message.quotedMessageId); @@ -153,5 +176,9 @@ void main() { expect(entity.pinnedByUserId, message.pinnedBy.id); expect(entity.reactionCounts, message.reactionCounts); expect(entity.reactionScores, message.reactionScores); + expect( + entity.attachments, + message.attachments?.map((it) => jsonEncode(it.toData()))?.toList(), + ); }); } From cc9388816ab396da00ba2c27c1a8589dc8253793 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 18:10:25 +0530 Subject: [PATCH 040/599] test(persistence): add client constructor test in stream chat persistence client Signed-off-by: Sahil Kumar --- .../stream_chat_persistence_client_test.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart index ef7fd020..55a5113a 100644 --- a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart +++ b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart @@ -10,6 +10,22 @@ MoorChatDatabase _testDatabaseProvider(String userId, ConnectionMode mode) => MoorChatDatabase.testable(userId); void main() { + group('client constructor', () { + test('throws assertion error if null connectionMode is provided', () { + expect( + () => StreamChatPersistenceClient(connectionMode: null), + throwsA(isA()), + ); + }); + + test('throws assertion error if null logLevel is provided', () { + expect( + () => StreamChatPersistenceClient(logLevel: null), + throwsA(isA()), + ); + }); + }); + group('connect', () { const userId = 'testUserId'; test('successfully connects with the Database', () async { From 74f1752ceb3009c8cc01e0fd7a364c073e370a33 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 18:21:59 +0530 Subject: [PATCH 041/599] test(persistence): add coverage:ignore-file for entities and db/shared directory Signed-off-by: Sahil Kumar --- .../stream_chat_persistence/lib/src/db/shared/native_db.dart | 1 + .../lib/src/db/shared/unsupported_db.dart | 1 + packages/stream_chat_persistence/lib/src/db/shared/web_db.dart | 1 + .../stream_chat_persistence/lib/src/entity/channel_queries.dart | 1 + packages/stream_chat_persistence/lib/src/entity/channels.dart | 1 + .../lib/src/entity/connection_events.dart | 1 + packages/stream_chat_persistence/lib/src/entity/members.dart | 1 + packages/stream_chat_persistence/lib/src/entity/messages.dart | 1 + .../stream_chat_persistence/lib/src/entity/pinned_messages.dart | 1 + packages/stream_chat_persistence/lib/src/entity/reactions.dart | 1 + packages/stream_chat_persistence/lib/src/entity/reads.dart | 1 + packages/stream_chat_persistence/lib/src/entity/users.dart | 1 + 12 files changed, 12 insertions(+) diff --git a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart index 0868ad69..27aa23b5 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'dart:io'; import 'dart:isolate'; diff --git a/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart index e2877030..1054a1df 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/stream_chat_persistence.dart'; diff --git a/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart index 9c504a11..bb664443 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:moor/moor_web.dart'; import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart'; diff --git a/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart b/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart index 301e3592..61985d26 100644 --- a/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart +++ b/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:moor/moor.dart'; /// Represents a [ChannelQueries] table in [MoorChatDatabase]. diff --git a/packages/stream_chat_persistence/lib/src/entity/channels.dart b/packages/stream_chat_persistence/lib/src/entity/channels.dart index 38a40081..89842036 100644 --- a/packages/stream_chat_persistence/lib/src/entity/channels.dart +++ b/packages/stream_chat_persistence/lib/src/entity/channels.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:moor/moor.dart'; import 'package:stream_chat_persistence/src/converter/map_converter.dart'; diff --git a/packages/stream_chat_persistence/lib/src/entity/connection_events.dart b/packages/stream_chat_persistence/lib/src/entity/connection_events.dart index 7caa0407..69538edc 100644 --- a/packages/stream_chat_persistence/lib/src/entity/connection_events.dart +++ b/packages/stream_chat_persistence/lib/src/entity/connection_events.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:moor/moor.dart'; import 'package:stream_chat_persistence/src/converter/map_converter.dart'; diff --git a/packages/stream_chat_persistence/lib/src/entity/members.dart b/packages/stream_chat_persistence/lib/src/entity/members.dart index 9eafd4aa..c7316640 100644 --- a/packages/stream_chat_persistence/lib/src/entity/members.dart +++ b/packages/stream_chat_persistence/lib/src/entity/members.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:moor/moor.dart'; /// Represents a [Members] table in [MoorChatDatabase]. diff --git a/packages/stream_chat_persistence/lib/src/entity/messages.dart b/packages/stream_chat_persistence/lib/src/entity/messages.dart index 0350d9de..8840d85f 100644 --- a/packages/stream_chat_persistence/lib/src/entity/messages.dart +++ b/packages/stream_chat_persistence/lib/src/entity/messages.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:moor/moor.dart'; import 'package:stream_chat_persistence/src/converter/list_converter.dart'; import 'package:stream_chat_persistence/src/converter/map_converter.dart'; diff --git a/packages/stream_chat_persistence/lib/src/entity/pinned_messages.dart b/packages/stream_chat_persistence/lib/src/entity/pinned_messages.dart index de081552..043f69c5 100644 --- a/packages/stream_chat_persistence/lib/src/entity/pinned_messages.dart +++ b/packages/stream_chat_persistence/lib/src/entity/pinned_messages.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:moor/moor.dart'; import 'package:stream_chat_persistence/src/entity/messages.dart'; diff --git a/packages/stream_chat_persistence/lib/src/entity/reactions.dart b/packages/stream_chat_persistence/lib/src/entity/reactions.dart index b7e6342f..53cbdd11 100644 --- a/packages/stream_chat_persistence/lib/src/entity/reactions.dart +++ b/packages/stream_chat_persistence/lib/src/entity/reactions.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:moor/moor.dart'; import 'package:stream_chat_persistence/src/converter/map_converter.dart'; diff --git a/packages/stream_chat_persistence/lib/src/entity/reads.dart b/packages/stream_chat_persistence/lib/src/entity/reads.dart index 4b9ce6fb..7943ed9e 100644 --- a/packages/stream_chat_persistence/lib/src/entity/reads.dart +++ b/packages/stream_chat_persistence/lib/src/entity/reads.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:moor/moor.dart'; /// Represents a [Reads] table in [MoorChatDatabase]. diff --git a/packages/stream_chat_persistence/lib/src/entity/users.dart b/packages/stream_chat_persistence/lib/src/entity/users.dart index 4916ad28..7ece9600 100644 --- a/packages/stream_chat_persistence/lib/src/entity/users.dart +++ b/packages/stream_chat_persistence/lib/src/entity/users.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:moor/moor.dart'; import 'package:stream_chat_persistence/src/converter/map_converter.dart'; From 8f079a992435cdeaa3bdb6bc20de0abeb3d185db Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 18:59:47 +0530 Subject: [PATCH 042/599] skip pub publish --dry-run for develop branch Signed-off-by: Sahil Kumar --- .github/workflows/stream_flutter_workflow.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index a25bbdb3..5f192320 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -33,6 +33,7 @@ jobs: melos exec -c 3 --ignore="*example*" -- \ tuneup check - name: 'Pub Check' + if: github.ref == 'refs/heads/master' run: | melos exec -c 1 --no-private --ignore="*example*" -- \ pub publish --dry-run @@ -89,7 +90,7 @@ jobs: - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_persistence/coverage/lcov.info - min_coverage: 0.2 + min_coverage: 95 - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_flutter_core/coverage/lcov.info From 6dd9f6af5eb8711deec7d1757755cfca0cccb4e9 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 19:37:50 +0530 Subject: [PATCH 043/599] add remove from coverage script Signed-off-by: Sahil Kumar --- .github/workflows/scripts/coverage.sh | 6 ++++++ .github/workflows/stream_flutter_workflow.yml | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100755 .github/workflows/scripts/coverage.sh diff --git a/.github/workflows/scripts/coverage.sh b/.github/workflows/scripts/coverage.sh new file mode 100755 index 00000000..2710a45d --- /dev/null +++ b/.github/workflows/scripts/coverage.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Fast fail the script on failures. +set -e + +pub global run remove_from_coverage:remove_from_coverage -f coverage/lcov.info -r '.g.dart$' \ No newline at end of file diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 5f192320..f4b17371 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -70,6 +70,7 @@ jobs: run: | ./.github/workflows/scripts/install-tools.sh flutter pub global activate coverage + flutter pub global activate remove_from_coverage - name: 'Bootstrap Workspace' run: melos bootstrap - name: 'Dart Test' @@ -82,7 +83,9 @@ jobs: melos exec -c 3 --flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ flutter test --coverage - name: CodeCov - run: bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }} + run: | + melos exec -c 3 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/coverage.sh" + bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }} - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat/lcov.info From 98b75d64ef1fe9ca25885790cfdc1475df3cecfe Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 19:45:41 +0530 Subject: [PATCH 044/599] fix Signed-off-by: Sahil Kumar --- .github/workflows/stream_flutter_workflow.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index f4b17371..827d3f22 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -84,7 +84,8 @@ jobs: flutter test --coverage - name: CodeCov run: | - melos exec -c 3 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/coverage.sh" + melos exec -c 3 --fail-fast --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ + "\$MELOS_ROOT_PATH/.github/workflows/scripts/coverage.sh" bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }} - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: From f31539ac872c7ab8469e9d0ea352a137cb6355ab Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 20:00:58 +0530 Subject: [PATCH 045/599] fix Signed-off-by: Sahil Kumar --- .github/workflows/stream_flutter_workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 827d3f22..3b7661f9 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -77,7 +77,7 @@ jobs: run: | cd packages/stream_chat flutter pub run test --coverage coverage/ - format_coverage --lcov --in=coverage/ --out=lcov.info --packages=.packages --report-on=lib + format_coverage --lcov --in=coverage/ --out=coverage/lcov.info --packages=.packages --report-on=lib - name: 'Flutter Test' run: | melos exec -c 3 --flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ From ccb47602e8cce37b4689bce7f7c172f7222e79ab Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 20:06:23 +0530 Subject: [PATCH 046/599] fix Signed-off-by: Sahil Kumar --- .github/workflows/stream_flutter_workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 3b7661f9..02ea415e 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -89,7 +89,7 @@ jobs: bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }} - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: - path: packages/stream_chat/lcov.info + path: packages/stream_chat/coverage/lcov.info min_coverage: 50 - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: From b9f955d3b203b7b428345c05a2c3acbce55dd82d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Mar 2021 20:21:04 +0530 Subject: [PATCH 047/599] fix Signed-off-by: Sahil Kumar --- .github/workflows/stream_flutter_workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 02ea415e..91808df2 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -90,7 +90,7 @@ jobs: - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat/coverage/lcov.info - min_coverage: 50 + min_coverage: 40 - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_persistence/coverage/lcov.info From 06718b84fd689af4784478f016215048206443a8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 19 Mar 2021 16:37:59 +0100 Subject: [PATCH 048/599] fix locale --- packages/stream_chat_flutter/lib/src/stream_chat.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 8ad751b0..470591e7 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -7,6 +7,7 @@ import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'dart:ui' as ui; /// Widget used to provide information about the chat to the widget tree /// @@ -125,8 +126,8 @@ class StreamChatState extends State { @override void didChangeDependencies() { - final locals = Localizations.localeOf(context); - Jiffy.locale(locals.languageCode); + final locale = ui.window.locale; + Jiffy.locale(locale.languageCode); super.didChangeDependencies(); } } From c448a7ceef6e14e2716345611590146ac47c82d3 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 19 Mar 2021 17:49:01 +0100 Subject: [PATCH 049/599] fix message input _parseExistingMessage --- packages/stream_chat_flutter/lib/src/message_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 4a18bf02..fc79b56c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -2233,7 +2233,7 @@ class MessageInputState extends State { void _parseExistingMessage(Message message) { textEditingController.text = message.text; _messageIsPresent = true; - for (final attachment in message.attachments) { + for (final attachment in message?.attachments) { _attachments[attachment.id] = attachment.copyWith( uploadState: attachment.uploadState ?? UploadState.success(), ); From 67f62d06926b32e2e6df47ce707c743c1554196c Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sun, 21 Mar 2021 23:27:26 -0500 Subject: [PATCH 050/599] feat(stream_chat): upgrade to null safe dependencies --- packages/stream_chat/lib/src/api/channel.dart | 6 +- .../stream_chat/lib/src/api/retry_queue.dart | 2 +- packages/stream_chat/lib/src/client.dart | 41 +- .../lib/src/models/attachment.dart | 2 +- .../stream_chat/lib/src/models/message.dart | 2 +- packages/stream_chat/pubspec.yaml | 32 +- .../test/src/api/channel_test.dart | 974 +++++++++------ .../test/src/api/requests_test.dart | 4 +- .../test/src/api/responses_test.dart | 17 +- .../test/src/api/web_socket_stub_test.dart | 4 +- .../test/src/api/websocket_test.dart | 180 ++- .../stream_chat/test/src/client_test.dart | 1042 ++++++++++------- .../test/src/models/action_test.dart | 2 +- .../test/src/models/attachment_test.dart | 29 +- .../test/src/models/channel_state_test.dart | 24 +- .../test/src/models/channel_test.dart | 26 +- .../test/src/models/command_test.dart | 8 +- .../test/src/models/device_test.dart | 2 +- .../test/src/models/event_test.dart | 2 +- .../test/src/models/member_test.dart | 4 +- .../test/src/models/message_test.dart | 47 +- .../test/src/models/reaction_test.dart | 30 +- .../test/src/models/read_test.dart | 4 +- .../test/src/models/user_test.dart | 8 +- 24 files changed, 1460 insertions(+), 1032 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 1701fed8..16551aa4 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -352,7 +352,7 @@ class Channel { state?.addMessage(response.message); return response; } catch (error) { - if (error is DioError && error.type != DioErrorType.RESPONSE) { + if (error is DioError && error.type != DioErrorType.response) { state?.retryQueue?.add([message]); } rethrow; @@ -405,7 +405,7 @@ class Channel { )); return response; } catch (error) { - if (error is DioError && error.type != DioErrorType.RESPONSE) { + if (error is DioError && error.type != DioErrorType.response) { state?.retryQueue?.add([message]); } rethrow; @@ -446,7 +446,7 @@ class Channel { return response; } catch (error) { - if (error is DioError && error.type != DioErrorType.RESPONSE) { + if (error is DioError && error.type != DioErrorType.response) { state?.retryQueue?.add([message]); } rethrow; diff --git a/packages/stream_chat/lib/src/api/retry_queue.dart b/packages/stream_chat/lib/src/api/retry_queue.dart index ac0e14d7..1049e0e9 100644 --- a/packages/stream_chat/lib/src/api/retry_queue.dart +++ b/packages/stream_chat/lib/src/api/retry_queue.dart @@ -74,7 +74,7 @@ class RetryQueue { } catch (error) { ApiError apiError; if (error is DioError) { - if (error.type == DioErrorType.RESPONSE) { + if (error.type == DioErrorType.response) { _messageQueue.remove(message); return; } diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 11619b35..bd7d05c8 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -252,7 +252,7 @@ class StreamChatClient { this.httpClient.options.connectTimeout = connectTimeout.inMilliseconds; this.httpClient.interceptors.add( InterceptorsWrapper( - onRequest: (options) async { + onRequest: (options, handler) async { options.queryParameters.addAll(_commonQueryParams); options.headers.addAll(_httpHeaders); @@ -288,7 +288,10 @@ class StreamChatClient { ); } - Future _tokenExpiredInterceptor(DioError err) async { + Future _tokenExpiredInterceptor( + DioError err, + ErrorInterceptorHandler handler, + ) async { final apiError = ApiError( err.response?.data, err.response?.statusCode, @@ -313,13 +316,29 @@ class StreamChatClient { try { return await httpClient.request( - err.request.path, - cancelToken: err.request.cancelToken, - data: err.request.data, - onReceiveProgress: err.request.onReceiveProgress, - onSendProgress: err.request.onSendProgress, - queryParameters: err.request.queryParameters, - options: err.request, + err.requestOptions.path, + cancelToken: err.requestOptions.cancelToken, + data: err.requestOptions.data, + onReceiveProgress: err.requestOptions.onReceiveProgress, + onSendProgress: err.requestOptions.onSendProgress, + queryParameters: err.requestOptions.queryParameters, + options: Options( + method: err.requestOptions.method, + sendTimeout: err.requestOptions.sendTimeout, + receiveTimeout: err.requestOptions.receiveTimeout, + extra: err.requestOptions.extra, + headers: err.requestOptions.headers, + responseType: err.requestOptions.responseType, + contentType: err.requestOptions.contentType, + validateStatus: err.requestOptions.validateStatus, + receiveDataWhenStatusError: + err.requestOptions.receiveDataWhenStatusError, + followRedirects: err.requestOptions.followRedirects, + maxRedirects: err.requestOptions.maxRedirects, + requestEncoder: err.requestOptions.requestEncoder, + responseDecoder: err.requestOptions.responseDecoder, + listFormat: err.requestOptions.listFormat, + ), ); } catch (err) { return err; @@ -783,7 +802,7 @@ class StreamChatClient { } Object _parseError(DioError error) { - if (error.type == DioErrorType.RESPONSE) { + if (error.type == DioErrorType.response) { final apiError = ApiError(error.response?.data, error.response?.statusCode); logger.severe('apiError: ${apiError.toString()}'); @@ -930,7 +949,7 @@ class StreamChatClient { _connectCompleter = Completer(); _anonymous = true; - final uuid = Uuid(); + const uuid = Uuid(); state.user = OwnUser(id: uuid.v4()); return connect().then((event) { diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/models/attachment.dart index de5149db..551d70e4 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/models/attachment.dart @@ -35,7 +35,7 @@ class Attachment { this.extraData, this.file, UploadState uploadState, - }) : id = id ?? Uuid().v4(), + }) : id = id ?? const Uuid().v4(), title = title ?? file?.name, localUri = file?.path != null ? Uri.parse(file.path) : null { this.uploadState = uploadState ?? diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index a89324a7..de441dff 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -73,7 +73,7 @@ class Message { this.deletedAt, this.status = MessageSendingStatus.sent, this.skipPush, - }) : id = id ?? Uuid().v4(), + }) : id = id ?? const Uuid().v4(), pinExpires = pinExpires?.toUtc(); /// Create a new instance from a json diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 81aef23e..74310a86 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -9,22 +9,22 @@ environment: sdk: ">=2.7.0 <3.0.0" dependencies: - async: ^2.4.2 - collection: ^1.14.13 - dio: ^3.0.10 - freezed_annotation: ^0.12.0 - http_parser: ^3.1.4 - json_annotation: ^3.0.1 - logging: ^0.11.4 - meta: ^1.2.4 - mime: ^0.9.7 - rxdart: ^0.25.0 - uuid: ^2.2.2 - web_socket_channel: ^1.1.0 + async: ^2.5.0 + collection: ^1.15.0 + dio: ">=4.0.0-prev3 <4.0.0" + freezed_annotation: ^0.14.0 + http_parser: ^4.0.0 + json_annotation: ^4.0.0 + logging: ^1.0.0 + meta: ^1.3.0 + mime: ^1.0.0 + rxdart: ^0.26.0 + uuid: ^3.0.0 + web_socket_channel: ^2.0.0 dev_dependencies: build_runner: ^1.10.0 - freezed: ^0.12.7 - json_serializable: ^3.3.0 - mockito: ^4.1.1 - test: ^1.15.7 + freezed: ^0.14.0 + json_serializable: ^4.0.0 + mocktail: ^0.1.0 + test: ^1.16.0 diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 755a3f3b..ae4d5296 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -1,6 +1,6 @@ import 'package:dio/dio.dart'; import 'package:dio/native_imp.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/src/api/requests.dart'; import 'package:stream_chat/src/client.dart'; import 'package:stream_chat/src/event_type.dart'; @@ -14,6 +14,8 @@ import 'package:stream_chat/stream_chat.dart'; class MockDio extends Mock implements DioForNative {} +class FakeRequestOptions extends Fake implements RequestOptions {} + class MockAttachmentUploader extends Mock implements AttachmentFileUploader {} class MockHttpClientAdapter extends Mock implements HttpClientAdapter {} @@ -24,8 +26,8 @@ void main() { test('sendMessage', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -33,28 +35,34 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message( - text: 'hey', - id: 'test', - ); + final message = Message(text: 'hey', id: 'test'); - when(mockDio.post('/channels/messaging/testid/message', data: { - 'message': message.toJson(), - })).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid/message', + data: {'message': message.toJson()}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.sendMessage(message); - verify( + verify(() => mockDio.post('/channels/messaging/testid/message', data: { - 'message': message.toJson(), - })).called(1); + 'message': message.toJson(), + })).called(1); }); test('markRead', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -64,29 +72,40 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.post( - any, - data: anyNamed('data'), - )).thenAnswer((_) async => Response( + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( data: '{}', statusCode: 200, + requestOptions: FakeRequestOptions(), )); await channelClient.watch(); - when(mockDio.post('/channels/messaging/testid/read', data: {})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid/read', + data: {}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.markRead(); - verify(mockDio.post('/channels/messaging/testid/read', + verify(() => mockDio.post('/channels/messaging/testid/read', data: {})).called(1); }); test('getReplies', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -94,24 +113,28 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final pagination = PaginationParams(); + const pagination = PaginationParams(); - when(mockDio.get('/messages/messageid/replies', - queryParameters: pagination.toJson())) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when(() => mockDio.get('/messages/messageid/replies', + queryParameters: pagination.toJson())).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.getReplies('messageid', pagination); - verify(mockDio.get('/messages/messageid/replies', - queryParameters: pagination.toJson())) - .called(1); + verify(() => mockDio.get('/messages/messageid/replies', + queryParameters: pagination.toJson())).called(1); }); test('sendAction', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -120,39 +143,46 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.post( - any, - data: anyNamed('data'), - )).thenAnswer((_) async => Response( - data: '{}', - statusCode: 200, - )); + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.watch(); - final Map data = {'test': true}; + final data = {'test': true}; - when(mockDio.post('/messages/messageid/action', data: { - 'id': 'testid', - 'type': 'messaging', - 'form_data': data, - 'message_id': 'messageid', - })).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when(() => mockDio.post('/messages/messageid/action', data: { + 'id': 'testid', + 'type': 'messaging', + 'form_data': data, + 'message_id': 'messageid', + })).thenAnswer((_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); await channelClient.sendAction(Message(id: 'messageid'), data); - verify(mockDio.post('/messages/messageid/action', data: { - 'id': 'testid', - 'type': 'messaging', - 'form_data': data, - 'message_id': 'messageid', - })).called(1); + verify(() => mockDio.post('/messages/messageid/action', data: { + 'id': 'testid', + 'type': 'messaging', + 'form_data': data, + 'message_id': 'messageid', + })).called(1); }); test('getMessagesById', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -162,13 +192,18 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); final messageIds = ['a', 'b']; - when(mockDio.get('/channels/messaging/testid/messages', - queryParameters: {'ids': messageIds.join(',')})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when(() => mockDio.get('/channels/messaging/testid/messages', + queryParameters: {'ids': messageIds.join(',')})).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.getMessagesById(messageIds); - verify(mockDio.get('/channels/messaging/testid/messages', + verify(() => mockDio.get('/channels/messaging/testid/messages', queryParameters: {'ids': messageIds.join(',')})).called(1); }); @@ -176,12 +211,12 @@ void main() { final mockDio = MockDio(); final mockUploader = MockAttachmentUploader(); - final file = AttachmentFile(path: 'filePath/fileName.pdf'); - final channelId = 'testId'; - final channelType = 'messaging'; + const file = AttachmentFile(path: 'filePath/fileName.pdf'); + const channelId = 'testId'; + const channelType = 'messaging'; - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -191,24 +226,25 @@ void main() { ); final channelClient = client.channel(channelType, id: channelId); - when(mockUploader.sendFile(file, channelId, channelType)) + when(() => mockUploader.sendFile(file, channelId, channelType)) .thenAnswer((_) async => SendFileResponse()); await channelClient.sendFile(file); - verify(mockUploader.sendFile(file, channelId, channelType)).called(1); + verify(() => mockUploader.sendFile(file, channelId, channelType)) + .called(1); }); test('sendImage', () async { final mockDio = MockDio(); final mockUploader = MockAttachmentUploader(); - final image = AttachmentFile(path: 'imagePath/imageName.jpeg'); - final channelId = 'testId'; - final channelType = 'messaging'; + const image = AttachmentFile(path: 'imagePath/imageName.jpeg'); + const channelId = 'testId'; + const channelType = 'messaging'; - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -218,19 +254,20 @@ void main() { ); final channelClient = client.channel(channelType, id: channelId); - when(mockUploader.sendImage(image, channelId, channelType)) + when(() => mockUploader.sendImage(image, channelId, channelType)) .thenAnswer((_) async => SendImageResponse()); await channelClient.sendImage(image); - verify(mockUploader.sendImage(image, channelId, channelType)).called(1); + verify(() => mockUploader.sendImage(image, channelId, channelType)) + .called(1); }); test('deleteFile', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -238,23 +275,32 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final url = 'url'; + const url = 'url'; - when(mockDio.delete('/channels/messaging/testid/file', - queryParameters: {'url': url})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.delete( + '/channels/messaging/testid/file', + queryParameters: {'url': url}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.deleteFile(url); - verify(mockDio.delete('/channels/messaging/testid/file', + verify(() => mockDio.delete('/channels/messaging/testid/file', queryParameters: {'url': url})).called(1); }); test('deleteImage', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -262,15 +308,24 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final url = 'url'; + const url = 'url'; - when(mockDio.delete('/channels/messaging/testid/image', - queryParameters: {'url': url})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.delete( + '/channels/messaging/testid/image', + queryParameters: {'url': url}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.deleteImage(url); - verify(mockDio.delete('/channels/messaging/testid/image', + verify(() => mockDio.delete('/channels/messaging/testid/image', queryParameters: {'url': url})).called(1); }); @@ -290,60 +345,68 @@ void main() { test('should be pinned successfully', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', ); - final channelClient = client.channel('messaging', id: 'testid'); + final message = Message(text: 'Hello', id: 'test'); - final message = Message( - text: 'Hello', - id: 'test', + when( + () => mockDio.post( + '/messages/${message.id}', + data: anything, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.post( - '/messages/${message.id}', - data: anything, - )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await channelClient.pinMessage(message, 30); - verify(mockDio.post('/messages/${message.id}', data: anything)) + verify(() => + mockDio.post('/messages/${message.id}', data: anything)) .called(1); }); test('should be unpinned successfully', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', ); - final channelClient = client.channel('messaging', id: 'testid'); + final message = Message(text: 'Hello', id: 'test'); - final message = Message( - text: 'Hello', - id: 'test', + when( + () => mockDio.post( + '/messages/${message.id}', + data: anything, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.post( - '/messages/${message.id}', - data: anything, - )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await channelClient.unpinMessage(message); - verify(mockDio.post('/messages/${message.id}', data: anything)) + verify(() => + mockDio.post('/messages/${message.id}', data: anything)) .called(1); }); }); @@ -351,8 +414,8 @@ void main() { test('sendEvent', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -361,32 +424,46 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.post( - any, - data: anyNamed('data'), - )).thenAnswer((_) async => Response( - data: '{}', - statusCode: 200, - )); + when( + () => mockDio.post( + any(), + data: any(named: 'data'), + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.watch(); final event = Event(type: EventType.any); - when(mockDio.post('/channels/messaging/testid/event', - data: {'event': event.toJson()})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid/event', + data: {'event': event.toJson()}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.sendEvent(event); - verify(mockDio.post('/channels/messaging/testid/event', + verify(() => mockDio.post('/channels/messaging/testid/event', data: {'event': event.toJson()})).called(1); }); test('keyStroke', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -395,32 +472,46 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.post( - any, - data: anyNamed('data'), - )).thenAnswer((_) async => Response( - data: '{}', - statusCode: 200, - )); + when( + () => mockDio.post( + any(), + data: any(named: 'data'), + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.watch(); final event = Event(type: EventType.typingStart); - when(mockDio.post('/channels/messaging/testid/event', - data: {'event': event.toJson()})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid/event', + data: {'event': event.toJson()}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.keyStroke(); - verify(mockDio.post('/channels/messaging/testid/event', + verify(() => mockDio.post('/channels/messaging/testid/event', data: {'event': event.toJson()})).called(1); }); test('stopTyping', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -429,24 +520,36 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.post( - any, - data: anyNamed('data'), - )).thenAnswer((_) async => Response( - data: '{}', - statusCode: 200, - )); + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.watch(); final event = Event(type: EventType.typingStop); - when(mockDio.post('/channels/messaging/testid/event', - data: {'event': event.toJson()})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid/event', + data: {'event': event.toJson()}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.stopTyping(); - verify(mockDio.post('/channels/messaging/testid/event', + verify(() => mockDio.post('/channels/messaging/testid/event', data: {'event': event.toJson()})).called(1); }); @@ -454,8 +557,8 @@ void main() { test('sendReaction', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -464,17 +567,25 @@ void main() { )..state.user = OwnUser(id: 'test-id'); final channelClient = client.channel('messaging', id: 'testid'); - final reactionType = 'test'; + const reactionType = 'test'; - when(mockDio.post( - '/messages/messageid/reaction', - data: { - 'reaction': { - 'type': reactionType, + when( + () => mockDio.post( + '/messages/messageid/reaction', + data: { + 'reaction': { + 'type': reactionType, + }, + 'enforce_unique': false, }, - 'enforce_unique': false, - }, - )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.sendReaction( Message( @@ -487,19 +598,20 @@ void main() { reactionType, ); - verify(mockDio.post('/messages/messageid/reaction', data: { - 'reaction': { - 'type': reactionType, - }, - 'enforce_unique': false, - })).called(1); + verify( + () => mockDio.post('/messages/messageid/reaction', data: { + 'reaction': { + 'type': reactionType, + }, + 'enforce_unique': false, + })).called(1); }); test('deleteReaction', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -509,8 +621,15 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.delete('/messages/messageid/reaction/test')) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.delete('/messages/messageid/reaction/test'), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.deleteReaction( Message( @@ -523,15 +642,16 @@ void main() { Reaction(type: 'test'), ); - verify(mockDio.delete('/messages/messageid/reaction/test')) + verify(() => + mockDio.delete('/messages/messageid/reaction/test')) .called(1); }); test('getReactions', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -539,17 +659,25 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final pagination = PaginationParams(); + const pagination = PaginationParams(); - when(mockDio.get('/messages/messageid/reactions', - queryParameters: pagination.toJson())) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.get( + '/messages/messageid/reactions', + queryParameters: pagination.toJson(), + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.getReactions('messageid', pagination); - verify(mockDio.get('/messages/messageid/reactions', - queryParameters: pagination.toJson())) - .called(1); + verify(() => mockDio.get('/messages/messageid/reactions', + queryParameters: pagination.toJson())).called(1); }); }); @@ -557,8 +685,8 @@ void main() { test('addMembers', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -569,13 +697,22 @@ void main() { final members = ['vishal']; final message = Message(text: 'test'); - when(mockDio.post('/channels/messaging/testid', - data: {'add_members': members, 'message': message.toJson()})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid', + data: {'add_members': members, 'message': message.toJson()}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.addMembers(members, message); - verify(mockDio.post('/channels/messaging/testid', + verify(() => mockDio.post('/channels/messaging/testid', data: {'add_members': members, 'message': message.toJson()})) .called(1); }); @@ -583,8 +720,8 @@ void main() { test('acceptInvite', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -594,13 +731,22 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); final message = Message(text: 'test'); - when(mockDio.post('/channels/messaging/testid', - data: {'accept_invite': true, 'message': message.toJson()})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid', + data: {'accept_invite': true, 'message': message.toJson()}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.acceptInvite(message); - verify(mockDio.post('/channels/messaging/testid', + verify(() => mockDio.post('/channels/messaging/testid', data: {'accept_invite': true, 'message': message.toJson()})) .called(1); }); @@ -609,8 +755,8 @@ void main() { test('without id', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -618,15 +764,16 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging'); - final Map options = { + final options = { 'watch': true, 'state': false, 'presence': true, }; - when(mockDio.post('/channels/messaging/query', data: options)) - .thenAnswer((_) async { - return Response(data: r''' + when(() => mockDio.post('/channels/messaging/query', + data: options)).thenAnswer( + (_) async => Response( + data: r''' { "channel": { "id": "!members-0LOcD0mZtTan60zHobLmELjdndXsonnBVNzZnB5mTt0", @@ -912,14 +1059,16 @@ void main() { } ] } - ''', statusCode: 200); - }); + ''', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); final response = await channelClient.query(options: options); - verify(mockDio.post('/channels/messaging/query', - data: options)) - .called(1); + verify(() => mockDio.post('/channels/messaging/query', + data: options)).called(1); expect(channelClient.id, response.channel.id); expect(channelClient.cid, response.channel.cid); }); @@ -927,8 +1076,8 @@ void main() { test('with id', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -936,13 +1085,12 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final Map options = { - 'state': false, - }; + final options = {'state': false}; - when(mockDio.post('/channels/messaging/testid/query', - data: options)) - .thenAnswer((_) async => Response(data: r''' + when(() => mockDio.post('/channels/messaging/testid/query', + data: options)).thenAnswer( + (_) async => Response( + data: r''' { "channel": { "id": "!members-0LOcD0mZtTan60zHobLmELjdndXsonnBVNzZnB5mTt0", @@ -1228,21 +1376,24 @@ void main() { } ] } - ''', statusCode: 200)); + ''', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.query(options: options); - verify(mockDio.post('/channels/messaging/testid/query', - data: options)) - .called(1); + verify(() => mockDio.post('/channels/messaging/testid/query', + data: options)).called(1); }); }); test('create', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -1250,14 +1401,16 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging'); - final Map options = { + final options = { 'watch': false, 'state': false, 'presence': false, }; - when(mockDio.post('/channels/messaging/query', data: options)) - .thenAnswer((_) async => Response(data: r''' + when(() => mockDio.post('/channels/messaging/query', + data: options)).thenAnswer( + (_) async => Response( + data: r''' { "channel": { "id": "!members-0LOcD0mZtTan60zHobLmELjdndXsonnBVNzZnB5mTt0", @@ -1543,12 +1696,16 @@ void main() { } ] } - ''', statusCode: 200)); + ''', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); final response = await channelClient.create(); - verify(mockDio.post('/channels/messaging/query', data: options)) - .called(1); + verify(() => mockDio.post('/channels/messaging/query', + data: options)).called(1); expect(channelClient.id, response.channel.id); expect(channelClient.cid, response.channel.cid); }); @@ -1556,8 +1713,8 @@ void main() { test('watch', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -1571,8 +1728,10 @@ void main() { 'presence': true, }; - when(mockDio.post('/channels/messaging/query', data: options)) - .thenAnswer((_) async => Response(data: r''' + when(() => mockDio.post('/channels/messaging/query', + data: options)).thenAnswer( + (_) async => Response( + data: r''' { "channel": { "id": "!members-0LOcD0mZtTan60zHobLmELjdndXsonnBVNzZnB5mTt0", @@ -1858,12 +2017,16 @@ void main() { } ] } - ''', statusCode: 200)); + ''', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); final response = await channelClient.watch({'presence': true}); - verify(mockDio.post('/channels/messaging/query', data: options)) - .called(1); + verify(() => mockDio.post('/channels/messaging/query', + data: options)).called(1); expect(channelClient.id, response.channel.id); expect(channelClient.cid, response.channel.cid); }); @@ -1871,8 +2034,8 @@ void main() { test('stopWatching', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -1881,24 +2044,32 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.post( - '/channels/messaging/testid/stop-watching', - data: {}, - )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid/stop-watching', + data: {}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.stopWatching(); - verify(mockDio.post( - '/channels/messaging/testid/stop-watching', - data: {}, - )).called(1); + verify(() => mockDio.post( + '/channels/messaging/testid/stop-watching', + data: {}, + )).called(1); }); test('update', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -1908,24 +2079,37 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); final message = Message(text: 'test'); - when(mockDio.post('/channels/messaging/testid', data: { - 'message': message.toJson(), - 'data': {'test': true}, - })).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid', + data: { + 'message': message.toJson(), + 'data': {'test': true}, + }, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.update({'test': true}, message); - verify(mockDio.post('/channels/messaging/testid', data: { - 'message': message.toJson(), - 'data': {'test': true}, - })).called(1); + verify( + () => mockDio.post('/channels/messaging/testid', data: { + 'message': message.toJson(), + 'data': {'test': true}, + }), + ).called(1); }); test('delete', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -1934,19 +2118,27 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.delete('/channels/messaging/testid')) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.delete('/channels/messaging/testid'), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.delete(); - verify(mockDio.delete('/channels/messaging/testid')).called(1); + verify(() => mockDio.delete('/channels/messaging/testid')) + .called(1); }); test('truncate', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -1955,20 +2147,28 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.post('/channels/messaging/testid/truncate')) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post('/channels/messaging/testid/truncate'), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.truncate(); - verify(mockDio.post('/channels/messaging/testid/truncate')) + verify(() => + mockDio.post('/channels/messaging/testid/truncate')) .called(1); }); test('rejectInvite', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -1978,13 +2178,22 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); final message = Message(text: 'test'); - when(mockDio.post('/channels/messaging/testid', - data: {'reject_invite': true, 'message': message.toJson()})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid', + data: {'reject_invite': true, 'message': message.toJson()}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.rejectInvite(message); - verify(mockDio.post('/channels/messaging/testid', + verify(() => mockDio.post('/channels/messaging/testid', data: {'reject_invite': true, 'message': message.toJson()})) .called(1); }); @@ -1992,8 +2201,8 @@ void main() { test('inviteMembers', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -2004,21 +2213,30 @@ void main() { final members = ['vishal']; final message = Message(text: 'test'); - when(mockDio.post('/channels/messaging/testid', - data: {'invites': members, 'message': message.toJson()})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid', + data: {'invites': members, 'message': message.toJson()}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.inviteMembers(members, message); - verify(mockDio.post('/channels/messaging/testid', + verify(() => mockDio.post('/channels/messaging/testid', data: {'invites': members, 'message': message.toJson()})).called(1); }); test('removeMembers', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -2029,13 +2247,22 @@ void main() { final members = ['vishal']; final message = Message(text: 'test'); - when(mockDio.post('/channels/messaging/testid', - data: {'remove_members': members, 'message': message.toJson()})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid', + data: {'remove_members': members, 'message': message.toJson()}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.removeMembers(members, message); - verify(mockDio.post('/channels/messaging/testid', + verify(() => mockDio.post('/channels/messaging/testid', data: {'remove_members': members, 'message': message.toJson()})) .called(1); }); @@ -2043,8 +2270,8 @@ void main() { test('hide', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -2053,30 +2280,44 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.post( - any, - data: anyNamed('data'), - )).thenAnswer((_) async => Response( - data: '{}', - statusCode: 200, - )); + when( + () => mockDio.post( + any(), + data: any(named: 'data'), + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.watch(); - when(mockDio.post('/channels/messaging/testid/hide', - data: {'clear_history': true})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/channels/messaging/testid/hide', + data: {'clear_history': true}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.hide(clearHistory: true); - verify(mockDio.post('/channels/messaging/testid/hide', + verify(() => mockDio.post('/channels/messaging/testid/hide', data: {'clear_history': true})).called(1); }); test('show', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -2085,29 +2326,41 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.post( - any, - data: anyNamed('data'), - )).thenAnswer((_) async => Response( - data: '{}', - statusCode: 200, - )); + when( + () => mockDio.post( + any(), + data: any(named: 'data'), + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.watch(); - when(mockDio.post('/channels/messaging/testid/show')) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post('/channels/messaging/testid/show'), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.show(); - verify(mockDio.post('/channels/messaging/testid/show')) + verify(() => mockDio.post('/channels/messaging/testid/show')) .called(1); }); test('banUser', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -2116,38 +2369,51 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.post( - any, - data: anyNamed('data'), - )).thenAnswer((_) async => Response( - data: '{}', - statusCode: 200, - )); + when( + () => mockDio.post( + any(), + data: any(named: 'data'), + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.watch(); - when(mockDio.post('/moderation/ban', data: { - 'test': true, - 'target_user_id': 'test-id', - 'type': 'messaging', - 'id': 'testid', - })).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post('/moderation/ban', data: { + 'test': true, + 'target_user_id': 'test-id', + 'type': 'messaging', + 'id': 'testid', + }), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); - final Map options = {'test': true}; + final options = {'test': true}; await channelClient.banUser('test-id', options); - verify(mockDio.post('/moderation/ban', data: { - 'test': true, - 'target_user_id': 'test-id', - 'type': 'messaging', - 'id': 'testid', - })).called(1); + verify(() => mockDio.post('/moderation/ban', data: { + 'test': true, + 'target_user_id': 'test-id', + 'type': 'messaging', + 'id': 'testid', + })).called(1); }); test('unbanUser', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -2156,28 +2422,46 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); - when(mockDio.post( - any, - data: anyNamed('data'), - )).thenAnswer((_) async => Response( - data: '{}', - statusCode: 200, - )); + when( + () => mockDio.post( + any(), + data: any(named: 'data'), + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.watch(); - when(mockDio.delete('/moderation/ban', queryParameters: { - 'target_user_id': 'test-id', - 'type': 'messaging', - 'id': 'testid', - })).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.delete( + '/moderation/ban', + queryParameters: { + 'target_user_id': 'test-id', + 'type': 'messaging', + 'id': 'testid', + }, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.unbanUser('test-id'); - verify(mockDio.delete('/moderation/ban', queryParameters: { - 'target_user_id': 'test-id', - 'type': 'messaging', - 'id': 'testid', - })).called(1); + verify( + () => mockDio.delete('/moderation/ban', queryParameters: { + 'target_user_id': 'test-id', + 'type': 'messaging', + 'id': 'testid', + }), + ).called(1); }); }); }); diff --git a/packages/stream_chat/test/src/api/requests_test.dart b/packages/stream_chat/test/src/api/requests_test.dart index de8ddaf8..1e46fee0 100644 --- a/packages/stream_chat/test/src/api/requests_test.dart +++ b/packages/stream_chat/test/src/api/requests_test.dart @@ -4,13 +4,13 @@ import 'package:stream_chat/stream_chat.dart'; void main() { group('src/api/requests', () { test('SortOption', () { - final option = SortOption('name'); + const option = SortOption('name'); final j = option.toJson(); expect(j, {'field': 'name', 'direction': -1}); }); test('PaginationParams', () { - final option = PaginationParams(); + const option = PaginationParams(); final j = option.toJson(); expect(j, {'limit': 10, 'offset': 0}); }); diff --git a/packages/stream_chat/test/src/api/responses_test.dart b/packages/stream_chat/test/src/api/responses_test.dart index 8c7b57e5..ad81aeff 100644 --- a/packages/stream_chat/test/src/api/responses_test.dart +++ b/packages/stream_chat/test/src/api/responses_test.dart @@ -3284,7 +3284,7 @@ void main() { }); test('QueryReactionsResponse', () { - const jsonExample = r''' + const jsonExample = ''' {"reactions": [{"message_id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f","user_id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680","user": {"id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680","role": "user","created_at": "2020-01-28T22:17:30.83015Z","updated_at": "2020-01-28T22:17:31.19435Z","banned": false,"online": false,"image": "https://randomuser.me/api/portraits/women/2.jpg","name": "Mia Denys"},"type": "love","score": 1,"created_at": "2020-01-28T22:17:31.128376Z","updated_at": "2020-01-28T22:17:31.128376Z"}]} '''; final response = @@ -3402,31 +3402,31 @@ void main() { test('ListDevicesResponse', () { const jsonExample = - r'''{"devices":[{"push_provider":"firebase","id":"test"}],"duration":"0.35ms"}'''; + '''{"devices":[{"push_provider":"firebase","id":"test"}],"duration":"0.35ms"}'''; final response = ListDevicesResponse.fromJson(json.decode(jsonExample)); expect(response.devices, isA>()); }); test('SendFileResponse', () { - const jsonExample = r'''{"file": "file-url","duration":"0.35ms"}'''; + const jsonExample = '''{"file": "file-url","duration":"0.35ms"}'''; final response = SendFileResponse.fromJson(json.decode(jsonExample)); expect(response.file, isA()); }); test('SendImageResponse', () { - const jsonExample = r'''{"file": "file-url","duration":"0.35ms"}'''; + const jsonExample = '''{"file": "file-url","duration":"0.35ms"}'''; final response = SendImageResponse.fromJson(json.decode(jsonExample)); expect(response.file, isA()); }); test('SendImageResponse', () { - const jsonExample = r'''{"file": "file-url","duration":"0.35ms"}'''; + const jsonExample = '''{"file": "file-url","duration":"0.35ms"}'''; final response = SendImageResponse.fromJson(json.decode(jsonExample)); expect(response.file, isA()); }); test('EmptyResponse', () { - const jsonExample = r'''{"file": "file-url","duration":"0.35ms"}'''; + const jsonExample = '''{"file": "file-url","duration":"0.35ms"}'''; final response = EmptyResponse.fromJson(json.decode(jsonExample)); expect(response.duration, isA()); }); @@ -3481,8 +3481,7 @@ void main() { }); test('UpdateUsersResponse', () { - const jsonExample = - r'''{"users": {"bbb19d9a-ee50-45bc-84e5-0584e79d0c9e":{ + const jsonExample = '''{"users": {"bbb19d9a-ee50-45bc-84e5-0584e79d0c9e":{ "id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e", "role": "user", "created_at": "2020-01-28T22:17:30.826259Z", @@ -3498,7 +3497,7 @@ void main() { test('ConnectGuestUserResponse', () { const jsonExample = - r'{"user":{"id":"guest-ac612aee-25fe-49fb-b1af-969e41f452a0-wild-breeze-7","role":"guest","created_at":"2020-02-03T10:19:01.538434Z","updated_at":"2020-02-03T10:19:01.539543Z","banned":false,"online":false},"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZ3Vlc3QtYWM2MTJhZWUtMjVmZS00OWZiLWIxYWYtOTY5ZTQxZjQ1MmEwLXdpbGQtYnJlZXplLTcifQ.mmoFGu7oJjpFsp7nFN78UbIpO7gowbuIbyoppsuvbXA","duration":"4.66ms"}'; + '''{"user":{"id":"guest-ac612aee-25fe-49fb-b1af-969e41f452a0-wild-breeze-7","role":"guest","created_at":"2020-02-03T10:19:01.538434Z","updated_at":"2020-02-03T10:19:01.539543Z","banned":false,"online":false},"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZ3Vlc3QtYWM2MTJhZWUtMjVmZS00OWZiLWIxYWYtOTY5ZTQxZjQ1MmEwLXdpbGQtYnJlZXplLTcifQ.mmoFGu7oJjpFsp7nFN78UbIpO7gowbuIbyoppsuvbXA","duration":"4.66ms"}'''; final response = ConnectGuestUserResponse.fromJson(json.decode(jsonExample)); expect(response.user, isA()); diff --git a/packages/stream_chat/test/src/api/web_socket_stub_test.dart b/packages/stream_chat/test/src/api/web_socket_stub_test.dart index e60706dc..7f5bf4b1 100644 --- a/packages/stream_chat/test/src/api/web_socket_stub_test.dart +++ b/packages/stream_chat/test/src/api/web_socket_stub_test.dart @@ -4,6 +4,8 @@ import 'package:stream_chat/src/api/web_socket_channel_stub.dart'; void main() { test('src/api/web_socket_stub_test', () { expect( - () => connectWebSocket('fakeurl'), throwsA(isA())); + () => connectWebSocket('fakeurl'), + throwsA(isA()), + ); }); } diff --git a/packages/stream_chat/test/src/api/websocket_test.dart b/packages/stream_chat/test/src/api/websocket_test.dart index d9486477..9dcbd4e3 100644 --- a/packages/stream_chat/test/src/api/websocket_test.dart +++ b/packages/stream_chat/test/src/api/websocket_test.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:test/test.dart'; import 'package:logging/logging.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/src/api/connection_status.dart'; import 'package:stream_chat/src/api/websocket.dart'; import 'package:stream_chat/src/models/event.dart'; @@ -19,7 +19,7 @@ class Functions { }) => null; - void handleFunc(Event event) => null; + void handleFunc(Event event) {} } class MockFunctions extends Mock implements Functions {} @@ -28,45 +28,44 @@ class MockWSChannel extends Mock implements WebSocketChannel {} class MockWSSink extends Mock implements WebSocketSink {} +class FakeEvent extends Fake implements Event {} + void main() { group('src/api/websocket', () { + setUpAll(() { + registerFallbackValue(FakeEvent()); + }); + test('should connect with correct parameters', () async { final ConnectWebSocket connectFunc = MockFunctions().connectFunc; - final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, - handler: (e) { - print(e); - }, + handler: print, connectFunc: connectFunc, ); - final mockWSChannel = MockWSChannel(); - - final StreamController streamController = - StreamController.broadcast(); - - final computedUrl = + final streamController = StreamController.broadcast(); + const computedUrl = 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - when(connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(mockWSChannel.sink).thenAnswer((_) => MockWSSink()); - when(mockWSChannel.stream).thenAnswer((_) { - return streamController.stream; - }); + when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); + when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); + when(() => mockWSChannel.stream).thenAnswer( + (_) => streamController.stream, + ); final timer = Timer.periodic( - Duration(milliseconds: 100), + const Duration(milliseconds: 100), (_) => streamController.sink.add('{}'), ); await ws.connect(); - verify(connectFunc(computedUrl)).called(1); + verify(() => connectFunc(computedUrl)).called(1); expect(ws.connectionStatus, ConnectionStatus.connected); await streamController.close(); @@ -77,7 +76,6 @@ void main() { test('should connect with correct parameters and handle events', () async { final handleFunc = MockFunctions().handleFunc; final ConnectWebSocket connectFunc = MockFunctions().connectFunc; - final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), @@ -87,27 +85,21 @@ void main() { handler: handleFunc, connectFunc: connectFunc, ); - final mockWSChannel = MockWSChannel(); - - final StreamController streamController = - StreamController.broadcast(); - - final computedUrl = + final streamController = StreamController.broadcast(); + const computedUrl = 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - when(connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(mockWSChannel.sink).thenAnswer((_) => MockWSSink()); - when(mockWSChannel.stream).thenAnswer((_) { - return streamController.stream; - }); + when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); + when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); + when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); final connect = ws.connect().then((_) { streamController.sink.add('{}'); - return Future.delayed(Duration(milliseconds: 200)); + return Future.delayed(const Duration(milliseconds: 200)); }).then((value) { - verify(connectFunc(computedUrl)).called(1); - verify(handleFunc(any)).called(greaterThan(0)); + verify(() => connectFunc(computedUrl)).called(1); + verify(() => handleFunc(any())).called(greaterThan(0)); return streamController.close(); }); @@ -119,9 +111,7 @@ void main() { test('should close correctly the controller', () async { final handleFunc = MockFunctions().handleFunc; - final ConnectWebSocket connectFunc = MockFunctions().connectFunc; - final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), @@ -131,27 +121,21 @@ void main() { handler: handleFunc, connectFunc: connectFunc, ); - final mockWSChannel = MockWSChannel(); - - final StreamController streamController = - StreamController.broadcast(); - - final computedUrl = + final streamController = StreamController.broadcast(); + const computedUrl = 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - when(connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(mockWSChannel.sink).thenAnswer((_) => MockWSSink()); - when(mockWSChannel.stream).thenAnswer((_) { - return streamController.stream; - }); + when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); + when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); + when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); final connect = ws.connect().then((_) { streamController.sink.add('{}'); - return Future.delayed(Duration(milliseconds: 200)); + return Future.delayed(const Duration(milliseconds: 200)); }).then((value) { - verify(connectFunc(computedUrl)).called(1); - verify(handleFunc(any)).called(greaterThan(0)); + verify(() => connectFunc(computedUrl)).called(1); + verify(() => handleFunc(any())).called(greaterThan(0)); return streamController.close(); }); @@ -163,9 +147,7 @@ void main() { test('should run correctly health check', () async { final handleFunc = MockFunctions().handleFunc; - final ConnectWebSocket connectFunc = MockFunctions().connectFunc; - final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), @@ -175,32 +157,27 @@ void main() { handler: handleFunc, connectFunc: connectFunc, ); - final mockWSChannel = MockWSChannel(); final mockWSSink = MockWSSink(); - - final StreamController streamController = - StreamController.broadcast(); - - final computedUrl = + final streamController = StreamController.broadcast(); + const computedUrl = 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - when(connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(mockWSChannel.stream).thenAnswer((_) { - return streamController.stream; - }); - when(mockWSChannel.sink).thenReturn(mockWSSink); + when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); + when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); + when(() => mockWSChannel.sink).thenReturn(mockWSSink); final timer = Timer.periodic( - Duration(milliseconds: 1000), + const Duration(milliseconds: 1000), (_) => streamController.sink.add('{}'), ); final connect = ws.connect().then((_) { streamController.sink.add('{}'); - return Future.delayed(Duration(milliseconds: 200)); + return Future.delayed(const Duration(milliseconds: 200)); }).then((value) async { - verify(mockWSSink.add("{'type': 'health.check'}")).called(greaterThan(0)); + verify(() => mockWSSink.add("{'type': 'health.check'}")) + .called(greaterThan(0)); timer.cancel(); await streamController.close(); @@ -214,9 +191,7 @@ void main() { test('should run correctly reconnection check', () async { final handleFunc = MockFunctions().handleFunc; - final ConnectWebSocket connectFunc = MockFunctions().connectFunc; - Logger.root.level = Level.ALL; final ws = WebSocket( baseUrl: 'baseurl', @@ -227,34 +202,28 @@ void main() { handler: handleFunc, connectFunc: connectFunc, reconnectionMonitorTimeout: 1, - reconnectionMonitorInterval: 1, ); - final mockWSChannel = MockWSChannel(); final mockWSSink = MockWSSink(); - - StreamController streamController = - StreamController.broadcast(); - - final computedUrl = + var streamController = StreamController.broadcast(); + const computedUrl = 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - when(connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(mockWSChannel.stream).thenAnswer((_) { - return streamController.stream; - }); - when(mockWSChannel.sink).thenReturn(mockWSSink); + when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); + when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); + when(() => mockWSChannel.sink).thenReturn(mockWSSink); final connect = ws.connect().then((_) { streamController.sink.add('{}'); streamController.close(); streamController = StreamController.broadcast(); streamController.sink.add('{}'); - return Future.delayed(Duration(milliseconds: 200)); + return Future.delayed(const Duration(milliseconds: 200)); }).then((value) async { - verify(mockWSSink.add("{'type': 'health.check'}")).called(greaterThan(0)); + verify(() => mockWSSink.add("{'type': 'health.check'}")) + .called(greaterThan(0)); - verify(connectFunc(computedUrl)).called(2); + verify(() => connectFunc(computedUrl)).called(2); await streamController.close(); return mockWSSink.close(); @@ -267,9 +236,7 @@ void main() { test('should close correctly the controller', () async { final handleFunc = MockFunctions().handleFunc; - final ConnectWebSocket connectFunc = MockFunctions().connectFunc; - final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), @@ -279,28 +246,22 @@ void main() { handler: handleFunc, connectFunc: connectFunc, ); - final mockWSChannel = MockWSChannel(); final mockWSSink = MockWSSink(); - - final StreamController streamController = - StreamController.broadcast(); - - final computedUrl = + final streamController = StreamController.broadcast(); + const computedUrl = 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - when(connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(mockWSChannel.stream).thenAnswer((_) { - return streamController.stream; - }); - when(mockWSChannel.sink).thenReturn(mockWSSink); + when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); + when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); + when(() => mockWSChannel.sink).thenReturn(mockWSSink); final connect = ws.connect().then((_) { streamController.sink.add('{}'); - return Future.delayed(Duration(milliseconds: 200)); + return Future.delayed(const Duration(milliseconds: 200)); }).then((value) async { await ws.disconnect(); - verify(mockWSSink.close()).called(greaterThan(0)); + verify(mockWSSink.close).called(greaterThan(0)); await streamController.close(); await mockWSSink.close(); @@ -313,41 +274,34 @@ void main() { test('should throw an error', () async { final ConnectWebSocket connectFunc = MockFunctions().connectFunc; - final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, - handler: (e) { - print(e); - }, + handler: print, connectFunc: connectFunc, ); - final mockWSChannel = MockWSChannel(); - final streamController = StreamController.broadcast(); - - final computedUrl = + const computedUrl = 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - when(connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(mockWSChannel.sink).thenAnswer((_) => MockWSSink()); - when(mockWSChannel.stream).thenAnswer((_) { - return streamController.stream; - }); + when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); + when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); + when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); Future.delayed( - Duration(milliseconds: 1000), + const Duration(milliseconds: 1000), () => streamController.sink.addError('test error'), ); try { expect(await ws.connect(), throwsA(isA())); } catch (e) { - verify(connectFunc(computedUrl)).called(greaterThanOrEqualTo(1)); + verify(() => connectFunc(computedUrl)).called(greaterThanOrEqualTo(1)); + streamController.close(); } }); } diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 21b9d09b..6ae8ee3e 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -1,10 +1,11 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:dio/native_imp.dart'; import 'package:logging/logging.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/src/api/requests.dart'; import 'package:stream_chat/src/client.dart'; import 'package:stream_chat/src/exceptions.dart'; @@ -15,6 +16,8 @@ import 'package:test/test.dart'; class MockDio extends Mock implements DioForNative {} +class FakeRequestOptions extends Fake implements RequestOptions {} + class MockHttpClientAdapter extends Mock implements HttpClientAdapter {} class Functions { @@ -25,10 +28,16 @@ class MockFunctions extends Mock implements Functions {} void main() { group('src/client', () { - group('constructor', () { - final List log = []; + setUpAll(() { + registerFallbackValue(FakeRequestOptions()); + registerFallbackValue>(const Stream.empty()); + registerFallbackValue>(Future.value()); + }); - overridePrint(testFn()) => () { + group('constructor', () { + final log = []; + + dynamic overridePrint(testFn()) => () { log.clear(); final spec = ZoneSpecification(print: (_, __, ___, String msg) { // Add to log instead of printing to stdout @@ -37,9 +46,7 @@ void main() { return Zone.current.fork(specification: spec).run(testFn); }; - tearDown(() { - log.clear(); - }); + tearDown(log.clear); test('should create the object correctly', () { final client = StreamChatClient('api-key'); @@ -52,14 +59,14 @@ void main() { }); test('should create the object correctly', overridePrint(() { - final LogHandlerFunction logHandler = (LogRecord record) { + void logHandler(LogRecord record) { print(record.message); - }; + } final client = StreamChatClient( 'api-key', - connectTimeout: Duration(seconds: 10), - receiveTimeout: Duration(seconds: 12), + connectTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 12), logLevel: Level.INFO, baseURL: 'test.com', logHandlerFunction: logHandler, @@ -74,13 +81,15 @@ void main() { client.logger.warning('test'); client.logger.config('test config'); - expect([log[log.length - 2], log[log.length - 1]], - ['instantiating new client', 'test']); + expect( + [log[log.length - 2], log[log.length - 1]], + ['instantiating new client', 'test'], + ); })); test('Channel', () { final client = StreamChatClient('test'); - final Map data = {'test': 1}; + final data = {'test': 1}; final channelClient = client.channel('type', id: 'id', extraData: data); expect(channelClient.type, 'type'); expect(channelClient.id, 'id'); @@ -91,71 +100,73 @@ void main() { test('should pass right default parameters', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + final client = StreamChatClient('api-key', httpClient: mockDio); final queryParams = { 'payload': json.encode({ - "filter_conditions": null, - "sort": null, - "state": true, - "watch": true, - "presence": false, - "limit": 10, - "offset": 0, + 'filter_conditions': null, + 'sort': null, + 'state': true, + 'watch': true, + 'presence': false, + 'limit': 10, + 'offset': 0, }), }; - when(mockDio.get('/channels', queryParameters: queryParams)) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.get('/channels', queryParameters: queryParams), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await client.queryChannelsOnline(filter: null, waitForConnect: false); - verify(mockDio.get('/channels', queryParameters: queryParams)) + verify(() => + mockDio.get('/channels', queryParameters: queryParams)) .called(1); }); test('should pass right parameters', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + final client = StreamChatClient('api-key', httpClient: mockDio); final queryFilter = { - "id": { - "\$in": ["test"], + 'id': { + '\$in': ['test'], }, }; final sortOptions = >[]; - final options = {"state": false, "watch": false, "presence": true}; - final paginationParams = PaginationParams( - limit: 10, - offset: 2, - ); + final options = {'state': false, 'watch': false, 'presence': true}; + const paginationParams = PaginationParams(offset: 2); final queryParams = { 'payload': json.encode({ - "filter_conditions": queryFilter, - "sort": sortOptions, + 'filter_conditions': queryFilter, + 'sort': sortOptions, } ..addAll(options) ..addAll(paginationParams.toJson())), }; - when(mockDio.get('/channels', queryParameters: queryParams)) - .thenAnswer((_) async { - return Response(data: '{"channels":[]}', statusCode: 200); - }); + when( + () => mockDio.get('/channels', queryParameters: queryParams), + ).thenAnswer( + (_) async => Response( + data: '{"channels":[]}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await client.queryChannelsOnline( filter: queryFilter, @@ -165,7 +176,8 @@ void main() { waitForConnect: false, ); - verify(mockDio.get('/channels', queryParameters: queryParams)) + verify(() => + mockDio.get('/channels', queryParameters: queryParams)) .called(1); }); }); @@ -174,22 +186,16 @@ void main() { test('should pass right default parameters', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + final client = StreamChatClient('api-key', httpClient: mockDio); final filter = { 'cid': { r'$in': ['messaging:testId'] } }; - const query = 'hello'; - final queryParams = { 'payload': json.encode({ 'filter_conditions': filter, @@ -197,34 +203,37 @@ void main() { }), }; - when(mockDio.get('/search', queryParameters: queryParams)) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.get('/search', queryParameters: queryParams), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await client.search(filter, query: query); - verify(mockDio.get('/search', queryParameters: queryParams)) + verify(() => + mockDio.get('/search', queryParameters: queryParams)) .called(1); }); test('should pass right parameters', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + final client = StreamChatClient('api-key', httpClient: mockDio); final filters = { - "id": { - "\$in": ["test"], + 'id': { + '\$in': ['test'], }, }; - final sortOptions = [SortOption('name')]; - final query = 'query'; - + const sortOptions = [SortOption('name')]; + const query = 'query'; final queryParams = { 'payload': json.encode({ 'filter_conditions': filters, @@ -235,18 +244,26 @@ void main() { }), }; - when(mockDio.get('/search', queryParameters: queryParams)) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.get('/search', queryParameters: queryParams), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await client.search( filters, sort: sortOptions, query: query, - paginationParams: PaginationParams(), + paginationParams: const PaginationParams(), ); - verify(mockDio.get('/search', queryParameters: queryParams)) - .called(1); + verify( + () => mockDio.get('/search', queryParameters: queryParams), + ).called(1); }); }); @@ -254,23 +271,28 @@ void main() { test('addDevice', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + + when( + () => mockDio.post('/devices', data: { + 'id': 'test-id', + 'push_provider': 'firebase', + }), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.post('/devices', data: { - 'id': 'test-id', - 'push_provider': 'firebase', - })).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.addDevice('test-id', PushProvider.firebase); verify( - mockDio.post( + () => mockDio.post( '/devices', data: {'id': 'test-id', 'push_provider': 'firebase'}, ), @@ -280,41 +302,53 @@ void main() { test('getDevices', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + + when(() => mockDio.get('/devices')).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.get('/devices')) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.getDevices(); - verify(mockDio.get('/devices')).called(1); + verify(() => mockDio.get('/devices')).called(1); }); test('removeDevice', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + + when( + () => mockDio.delete( + '/devices', + queryParameters: {'id': 'test-id'}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio - .delete('/devices', queryParameters: {'id': 'test-id'})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.removeDevice('test-id'); - verify(mockDio.delete('/devices', - queryParameters: {'id': 'test-id'})).called(1); + verify( + () => mockDio.delete( + '/devices', + queryParameters: {'id': 'test-id'}, + ), + ).called(1); }); }); @@ -324,7 +358,7 @@ void main() { expect( token, - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidGVzdCJ9.devtoken', + '''eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidGVzdCJ9.devtoken''', ); }); @@ -332,62 +366,68 @@ void main() { test('should pass right default parameters', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + final client = StreamChatClient('api-key', httpClient: mockDio); final queryParams = { 'payload': json.encode({ - "filter_conditions": {}, - "sort": null, - "presence": false, + 'filter_conditions': {}, + 'sort': null, + 'presence': false, }), }; - when(mockDio.get('/users', queryParameters: queryParams)) - .thenAnswer( - (_) async => Response(data: '{"users":[]}', statusCode: 200)); + when( + () => mockDio.get('/users', queryParameters: queryParams), + ).thenAnswer( + (_) async => Response( + data: '{"users":[]}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await client.queryUsers(); - verify(mockDio.get('/users', queryParameters: queryParams)) - .called(1); + verify( + () => mockDio.get( + '/users', + queryParameters: queryParams, + ), + ).called(1); }); test('should pass right parameters', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); - - final Map queryFilter = { - "id": { - "\$in": ["test"], + final client = StreamChatClient('api-key', httpClient: mockDio); + final queryFilter = { + 'id': { + '\$in': ['test'], }, }; - final List sortOptions = []; - final options = {"presence": true}; - - final Map queryParams = { + const sortOptions = []; + final options = {'presence': true}; + final queryParams = { 'payload': json.encode({ - "filter_conditions": queryFilter, - "sort": sortOptions, + 'filter_conditions': queryFilter, + 'sort': sortOptions, }..addAll(options)), }; - when(mockDio.get('/users', queryParameters: queryParams)) - .thenAnswer((_) async { - return Response(data: '{"users":[]}', statusCode: 200); - }); + when( + () => mockDio.get('/users', queryParameters: queryParams), + ).thenAnswer( + (_) async => Response( + data: '{"users":[]}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await client.queryUsers( filter: queryFilter, @@ -395,7 +435,8 @@ void main() { options: options, ); - verify(mockDio.get('/users', queryParameters: queryParams)) + verify(() => + mockDio.get('/users', queryParameters: queryParams)) .called(1); }); }); @@ -404,34 +445,37 @@ void main() { test('connectUser should throw exception', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + + when( + () => mockDio.post( + '/moderation/flag', + data: {'target_user_id': 'test-id'}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.post('/moderation/flag', - data: {'target_user_id': 'test-id'})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.flagUser('test-id'); - verify(mockDio.post('/moderation/flag', + verify(() => mockDio.post('/moderation/flag', data: {'target_user_id': 'test-id'})).called(1); }); test('flagUser', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); + final client = StreamChatClient('api-key', httpClient: mockDio); expect(() => client.connectUserWithProvider(User(id: 'test-id')), throwsA(isA())); @@ -440,60 +484,62 @@ void main() { test('unflagUser', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + + when( + () => mockDio.post( + '/moderation/unflag', + data: {'target_user_id': 'test-id'}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.post('/moderation/unflag', - data: {'target_user_id': 'test-id'})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.unflagUser('test-id'); - verify(mockDio.post('/moderation/unflag', + verify(() => mockDio.post('/moderation/unflag', data: {'target_user_id': 'test-id'})).called(1); }); test('updateUser', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + final client = StreamChatClient('api-key', httpClient: mockDio); final user = User(id: 'test-id'); - final data = { 'users': {user.id: user.toJson()}, }; - when(mockDio.post('/users', data: data)) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when(() => mockDio.post('/users', data: data)).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await client.updateUser(user); - verify(mockDio.post('/users', data: data)).called(1); + verify(() => mockDio.post('/users', data: data)).called(1); }); test('updateUsers', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + final client = StreamChatClient('api-key', httpClient: mockDio); final user = User(id: 'test-id'); final user2 = User(id: 'test-id2'); @@ -504,53 +550,70 @@ void main() { }, }; - when(mockDio.post('/users', data: data)) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when(() => mockDio.post('/users', data: data)).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await client.updateUsers([user, user2]); - verify(mockDio.post('/users', data: data)).called(1); + verify(() => mockDio.post('/users', data: data)).called(1); }); test('banUser', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + + when( + () => mockDio.post( + '/moderation/ban', + data: {'test': true, 'target_user_id': 'test-id'}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.post('/moderation/ban', - data: {'test': true, 'target_user_id': 'test-id'})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.banUser('test-id', {'test': true}); - verify(mockDio.post('/moderation/ban', + verify(() => mockDio.post('/moderation/ban', data: {'test': true, 'target_user_id': 'test-id'})).called(1); }); test('unbanUser', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + + when( + () => mockDio.delete( + '/moderation/ban', + queryParameters: {'test': true, 'target_user_id': 'test-id'}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.delete('/moderation/ban', - queryParameters: {'test': true, 'target_user_id': 'test-id'})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.unbanUser('test-id', {'test': true}); - verify(mockDio.delete('/moderation/ban', + verify(() => mockDio.delete('/moderation/ban', queryParameters: {'test': true, 'target_user_id': 'test-id'})) .called(1); }); @@ -558,42 +621,54 @@ void main() { test('muteUser', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + + when( + () => mockDio.post( + '/moderation/mute', + data: {'target_id': 'test-id'}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.post('/moderation/mute', - data: {'target_id': 'test-id'})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.muteUser('test-id'); - verify(mockDio.post('/moderation/mute', + verify(() => mockDio.post('/moderation/mute', data: {'target_id': 'test-id'})).called(1); }); test('unmuteUser', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + + when( + () => mockDio.post( + '/moderation/unmute', + data: {'target_id': 'test-id'}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.post('/moderation/unmute', - data: {'target_id': 'test-id'})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.unmuteUser('test-id'); - verify(mockDio.post('/moderation/unmute', + verify(() => mockDio.post('/moderation/unmute', data: {'target_id': 'test-id'})).called(1); }); }); @@ -602,131 +677,148 @@ void main() { test('flagMessage', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + + when( + () => mockDio.post( + '/moderation/flag', + data: {'target_message_id': 'test-id'}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.post('/moderation/flag', - data: {'target_message_id': 'test-id'})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.flagMessage('test-id'); - verify(mockDio.post('/moderation/flag', + verify(() => mockDio.post('/moderation/flag', data: {'target_message_id': 'test-id'})).called(1); }); test('unflagMessage', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + + when( + () => mockDio.post( + '/moderation/unflag', + data: {'target_message_id': 'test-id'}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.post('/moderation/unflag', - data: {'target_message_id': 'test-id'})) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.unflagMessage('test-id'); - verify(mockDio.post('/moderation/unflag', + verify(() => mockDio.post('/moderation/unflag', data: {'target_message_id': 'test-id'})).called(1); }); test('updateMessage', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + final message = Message(id: 'test', updatedAt: DateTime.now()); + + when( + () => mockDio.post( + '/messages/${message.id}', + data: {'message': anything}, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - final message = Message( - id: 'test', - updatedAt: DateTime.now(), - ); - - when(mockDio.post( - '/messages/${message.id}', - data: {'message': anything}, - )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.updateMessage(message); - verify(mockDio.post('/messages/${message.id}', + verify(() => mockDio.post('/messages/${message.id}', data: {'message': anything})).called(1); }); test('deleteMessage', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + const messageId = 'test'; + + when(() => mockDio.delete('/messages/$messageId')).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - final messageId = 'test'; - - when(mockDio.delete('/messages/$messageId')) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.deleteMessage(Message(id: messageId)); - verify(mockDio.delete('/messages/$messageId')).called(1); + verify(() => mockDio.delete('/messages/$messageId')).called(1); }); test('getMessage', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + const messageId = 'test'; + + when(() => mockDio.get('/messages/$messageId')).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - final messageId = 'test'; - - when(mockDio.get('/messages/$messageId')) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.getMessage(messageId); - verify(mockDio.get('/messages/$messageId')).called(1); + verify(() => mockDio.get('/messages/$messageId')).called(1); }); test('markAllRead', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + + when(() => mockDio.post('/channels/read')).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - when(mockDio.post('/channels/read')) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.markAllRead(); - verify(mockDio.post('/channels/read')).called(1); + verify(() => mockDio.post('/channels/read')).called(1); }); }); @@ -735,43 +827,53 @@ void main() { test('should put the correct parameters', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + final queryParams = {'test': 1}; + + when( + () => mockDio.get('/test', queryParameters: queryParams), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - final Map queryParams = { - 'test': 1, - }; - - when(mockDio.get('/test', queryParameters: queryParams)) - .thenAnswer((_) async { - return Response(data: '{}', statusCode: 200); - }); - await client.get('/test', queryParameters: queryParams); - verify(mockDio.get('/test', queryParameters: queryParams)) + verify(() => + mockDio.get('/test', queryParameters: queryParams)) .called(1); }); test('should catch the error', () async { - final dioHttp = Dio(); - final mockHttpClientAdapter = MockHttpClientAdapter(); - dioHttp.httpClientAdapter = mockHttpClientAdapter; + final mockDio = MockDio(); - final client = StreamChatClient( - 'api-key', - httpClient: dioHttp, + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + + final client = StreamChatClient('api-key', httpClient: mockDio); + + when(() => mockDio.get(any())).thenThrow( + DioError( + type: DioErrorType.response, + response: Response( + data: 'test error', + statusCode: 400, + requestOptions: FakeRequestOptions(), + ), + requestOptions: FakeRequestOptions(), + ), ); - when(mockHttpClientAdapter.fetch(any, any, any)).thenAnswer( - (_) async => ResponseBody.fromString('test error', 400)); - - expect(client.get('/test'), throwsA(ApiError('test error', 400))); + expect( + client.get('/test'), + throwsA(ApiError('test error', 400)), + ); }); }); @@ -779,40 +881,48 @@ void main() { test('should put the correct parameters', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + + final client = StreamChatClient('api-key', httpClient: mockDio); + final data = {'test': 1}; + + when(() => mockDio.post('/test', data: data)).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); + + await client.post('/test', data: data); + + verify(() => mockDio.post('/test', data: data)).called(1); + }); + + test('should catch the error', () async { + final mockDio = MockDio(); + + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', httpClient: mockDio, ); - final Map data = { - 'test': 1, - }; - - when(mockDio.post('/test', data: data)).thenAnswer((_) async { - return Response(data: '{}', statusCode: 200); - }); - - await client.post('/test', data: data); - - verify(mockDio.post('/test', data: data)).called(1); - }); - - test('should catch the error', () async { - final dioHttp = Dio(); - final mockHttpClientAdapter = MockHttpClientAdapter(); - dioHttp.httpClientAdapter = mockHttpClientAdapter; - - final client = StreamChatClient( - 'api-key', - httpClient: dioHttp, + when(() => mockDio.post(any())).thenThrow( + DioError( + type: DioErrorType.response, + response: Response( + data: 'test error', + statusCode: 400, + requestOptions: FakeRequestOptions(), + ), + requestOptions: FakeRequestOptions(), + ), ); - when(mockHttpClientAdapter.fetch(any, any, any)).thenAnswer( - (_) async => ResponseBody.fromString('test error', 400)); - expect(client.post('/test'), throwsA(ApiError('test error', 400))); }); }); @@ -821,40 +931,45 @@ void main() { test('should put the correct parameters', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, + final client = StreamChatClient('api-key', httpClient: mockDio); + final data = {'test': 1}; + + when(() => mockDio.put('/test', data: data)).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), ); - final Map data = { - 'test': 1, - }; - - when(mockDio.put('/test', data: data)).thenAnswer((_) async { - return Response(data: '{}', statusCode: 200); - }); - await client.put('/test', data: data); - verify(mockDio.put('/test', data: data)).called(1); + verify(() => mockDio.put('/test', data: data)).called(1); }); test('should catch the error', () async { - final dioHttp = Dio(); - final mockHttpClientAdapter = MockHttpClientAdapter(); - dioHttp.httpClientAdapter = mockHttpClientAdapter; + final mockDio = MockDio(); - final client = StreamChatClient( - 'api-key', - httpClient: dioHttp, + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + + final client = StreamChatClient('api-key', httpClient: mockDio); + + when(() => mockDio.put(any())).thenThrow( + DioError( + type: DioErrorType.response, + response: Response( + data: 'test error', + statusCode: 400, + requestOptions: FakeRequestOptions(), + ), + requestOptions: FakeRequestOptions(), + ), ); - when(mockHttpClientAdapter.fetch(any, any, any)).thenAnswer( - (_) async => ResponseBody.fromString('test error', 400)); - expect(client.put('/test'), throwsA(ApiError('test error', 400))); }); }); @@ -863,41 +978,48 @@ void main() { test('should put the correct parameters', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); + final client = StreamChatClient('api-key', httpClient: mockDio); - final Map data = { + final data = { 'test': 1, }; - when(mockDio.patch('/test', data: data)) - .thenAnswer((_) async { - return Response(data: '{}', statusCode: 200); - }); + when(() => mockDio.patch('/test', data: data)).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await client.patch('/test', data: data); - verify(mockDio.patch('/test', data: data)).called(1); + verify(() => mockDio.patch('/test', data: data)).called(1); }); test('should catch the error', () async { - final dioHttp = Dio(); - final mockHttpClientAdapter = MockHttpClientAdapter(); - dioHttp.httpClientAdapter = mockHttpClientAdapter; + final mockDio = MockDio(); - final client = StreamChatClient( - 'api-key', - httpClient: dioHttp, + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + + final client = StreamChatClient('api-key', httpClient: mockDio); + + when(() => mockDio.patch(any())).thenThrow( + DioError( + type: DioErrorType.response, + response: Response( + data: 'test error', + statusCode: 400, + requestOptions: FakeRequestOptions(), + ), + requestOptions: FakeRequestOptions(), + ), ); - when(mockHttpClientAdapter.fetch(any, any, any)).thenAnswer( - (_) async => ResponseBody.fromString('test error', 400)); - expect(client.patch('/test'), throwsA(ApiError('test error', 400))); }); }); @@ -906,42 +1028,55 @@ void main() { test('should put the correct parameters', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', httpClient: mockDio, ); - final Map queryParams = { + final queryParams = { 'test': 1, }; - when(mockDio.delete('/test', queryParameters: queryParams)) - .thenAnswer((_) async { - return Response(data: '{}', statusCode: 200); - }); + when( + () => mockDio.delete('/test', queryParameters: queryParams), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await client.delete('/test', queryParameters: queryParams); - verify(mockDio.delete('/test', queryParameters: queryParams)) + verify(() => + mockDio.delete('/test', queryParameters: queryParams)) .called(1); }); test('should catch the error', () async { - final dioHttp = Dio(); - final mockHttpClientAdapter = MockHttpClientAdapter(); - dioHttp.httpClientAdapter = mockHttpClientAdapter; + final mockDio = MockDio(); - final client = StreamChatClient( - 'api-key', - httpClient: dioHttp, + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + + final client = StreamChatClient('api-key', httpClient: mockDio); + + when(() => mockDio.delete(any())).thenThrow( + DioError( + type: DioErrorType.response, + response: Response( + data: 'test error', + statusCode: 400, + requestOptions: FakeRequestOptions(), + ), + requestOptions: FakeRequestOptions(), + ), ); - when(mockHttpClientAdapter.fetch(any, any, any)).thenAnswer( - (_) async => ResponseBody.fromString('test error', 400)); - expect(client.delete('/test'), throwsA(ApiError('test error', 400))); }); }); @@ -949,8 +1084,8 @@ void main() { group('pin message', () { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -966,33 +1101,48 @@ void main() { }); test('should complete successfully', () async { - final timeout = 30; + const timeout = 30; final message = Message(text: 'Hello'); - when(mockDio.post( - '/messages/${message.id}', - data: anything, - )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/messages/${message.id}', + data: anything, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await client.pinMessage(message, timeout); - verify(mockDio.post('/messages/${message.id}', + verify(() => mockDio.post('/messages/${message.id}', data: {'message': anything})).called(1); }); test('should unpin message successfully', () async { final message = Message(text: 'Hello'); - when(mockDio.post( - '/messages/${message.id}', - data: anything, - )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.post( + '/messages/${message.id}', + data: anything, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await client.unpinMessage(message); - verify(mockDio.post('/messages/${message.id}', - data: anything)) - .called(1); + verify(() => mockDio.post('/messages/${message.id}', + data: anything)).called(1); }); }); }); @@ -1001,8 +1151,8 @@ void main() { test('should update channel', () async { final mockDio = MockDio(); - when(mockDio.options).thenReturn(BaseOptions()); - when(mockDio.interceptors).thenReturn(Interceptors()); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', @@ -1012,20 +1162,30 @@ void main() { final channelClient = client.channel('type', id: 'id', extraData: {'name': 'init'}); - var update = { + const update = { 'set': {'name': 'demo'} }; - when(mockDio.patch( - '/channels/${channelClient.type}/${channelClient.id}', - data: update, - )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when( + () => mockDio.patch( + '/channels/${channelClient.type}/${channelClient.id}', + data: update, + ), + ).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); await channelClient.updatePartial(update); - verify(mockDio.patch( - '/channels/${channelClient.type}/${channelClient.id}', - data: update)) - .called(1); + verify( + () => mockDio.patch( + '/channels/${channelClient.type}/${channelClient.id}', + data: update, + ), + ).called(1); }); }); }); diff --git a/packages/stream_chat/test/src/models/action_test.dart b/packages/stream_chat/test/src/models/action_test.dart index 4859fcbe..5d142953 100644 --- a/packages/stream_chat/test/src/models/action_test.dart +++ b/packages/stream_chat/test/src/models/action_test.dart @@ -5,7 +5,7 @@ import 'package:stream_chat/src/models/action.dart'; void main() { group('src/models/action', () { - const jsonExample = r'''{ + const jsonExample = '''{ "name": "name", "style": "style", "text": "text", diff --git a/packages/stream_chat/test/src/models/attachment_test.dart b/packages/stream_chat/test/src/models/attachment_test.dart index b15458ad..3124ddd2 100644 --- a/packages/stream_chat/test/src/models/attachment_test.dart +++ b/packages/stream_chat/test/src/models/attachment_test.dart @@ -1,12 +1,12 @@ +import 'dart:convert'; import 'package:stream_chat/src/models/attachment.dart'; import 'package:stream_chat/src/models/action.dart'; -import 'dart:convert'; import 'package:test/test.dart'; void main() { group('src/models/attachment', () { - const jsonExample = r'''{ + const jsonExample = '''{ "type": "giphy", "title": "awesome", "title_link": "https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti", @@ -38,22 +38,27 @@ void main() { test('should parse json correctly', () { final attachment = Attachment.fromJson(json.decode(jsonExample)); - expect(attachment.type, "giphy"); - expect(attachment.title, "awesome"); - expect(attachment.titleLink, - "https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti"); - expect(attachment.thumbUrl, - "https://media0.giphy.com/media/3o7TKnCdBx5cMg0qti/giphy.gif"); + expect(attachment.type, 'giphy'); + expect(attachment.title, 'awesome'); + expect( + attachment.titleLink, + 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', + ); + expect( + attachment.thumbUrl, + 'https://media0.giphy.com/media/3o7TKnCdBx5cMg0qti/giphy.gif', + ); expect(attachment.actions, hasLength(3)); expect(attachment.actions[0], isA()); }); test('should serialize to json correctly', () { final channel = Attachment( - type: "image", - title: "soo", - titleLink: - "https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti"); + type: 'image', + title: 'soo', + titleLink: + 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', + ); expect( channel.toJson(), diff --git a/packages/stream_chat/test/src/models/channel_state_test.dart b/packages/stream_chat/test/src/models/channel_state_test.dart index 0a14cb76..35dda66e 100644 --- a/packages/stream_chat/test/src/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/models/channel_state_test.dart @@ -10,7 +10,7 @@ import 'package:stream_chat/stream_chat.dart'; void main() { group('src/models/channel_state', () { - const jsonExample = r'''{ + const jsonExample = '''{ "channel": { "id": "dev", "type": "team", @@ -853,28 +853,32 @@ void main() { expect(channelState.channel.config.commands, hasLength(1)); expect(channelState.channel.config.commands[0], isA()); expect(channelState.channel.lastMessageAt, - DateTime.parse("2020-01-30T13:43:41.062362Z")); + DateTime.parse('2020-01-30T13:43:41.062362Z')); expect(channelState.channel.createdAt, - DateTime.parse("2019-04-03T18:43:33.213373Z")); + DateTime.parse('2019-04-03T18:43:33.213373Z')); expect(channelState.channel.updatedAt, - DateTime.parse("2019-04-03T18:43:33.213374Z")); + DateTime.parse('2019-04-03T18:43:33.213374Z')); expect(channelState.channel.createdBy, isA()); expect(channelState.channel.frozen, true); expect(channelState.channel.extraData['example'], 1); - expect(channelState.channel.extraData['name'], "#dev"); - expect(channelState.channel.extraData['image'], - "https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png"); + expect(channelState.channel.extraData['name'], '#dev'); + expect( + channelState.channel.extraData['image'], + 'https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png', + ); expect(channelState.messages, hasLength(25)); expect(channelState.messages[0], isA()); expect(channelState.messages[0], isNotNull); - expect(channelState.messages[0].createdAt, - DateTime.parse("2020-01-29T03:23:02.843948Z")); + expect( + channelState.messages[0].createdAt, + DateTime.parse('2,020-01-29T03:23:02.843948Z'), + ); expect(channelState.messages[0].user, isA()); expect(channelState.watcherCount, 5); }); test('should serialize to json correctly', () { - const toJsonExample = r''' + const toJsonExample = ''' { "channel": { "id": "dev", diff --git a/packages/stream_chat/test/src/models/channel_test.dart b/packages/stream_chat/test/src/models/channel_test.dart index 69b18197..9f6aa5b7 100644 --- a/packages/stream_chat/test/src/models/channel_test.dart +++ b/packages/stream_chat/test/src/models/channel_test.dart @@ -17,19 +17,19 @@ void main() { test('should parse json correctly', () { final channel = ChannelModel.fromJson(json.decode(jsonExample)); - expect(channel.id, equals("test")); - expect(channel.type, equals("livestream")); - expect(channel.cid, equals("test:livestream")); - expect(channel.extraData["cats"], equals(true)); - expect(channel.extraData["fruit"], equals(["bananas", "apples"])); + expect(channel.id, equals('test')); + expect(channel.type, equals('livestream')); + expect(channel.cid, equals('test:livestream')); + expect(channel.extraData['cats'], equals(true)); + expect(channel.extraData['fruit'], equals(['bananas', 'apples'])); }); test('should serialize to json correctly', () { final channel = ChannelModel( - type: "type", - id: "id", - cid: "a:a", - extraData: {"name": "cool"}, + type: 'type', + id: 'id', + cid: 'a:a', + extraData: {'name': 'cool'}, ); expect( @@ -40,10 +40,10 @@ void main() { test('should serialize to json correctly when frozen is provided', () { final channel = ChannelModel( - type: "type", - id: "id", - cid: "a:a", - extraData: {"name": "cool"}, + type: 'type', + id: 'id', + cid: 'a:a', + extraData: {'name': 'cool'}, frozen: false, ); diff --git a/packages/stream_chat/test/src/models/command_test.dart b/packages/stream_chat/test/src/models/command_test.dart index dc44f736..8fafd192 100644 --- a/packages/stream_chat/test/src/models/command_test.dart +++ b/packages/stream_chat/test/src/models/command_test.dart @@ -1,6 +1,6 @@ -import 'package:stream_chat/src/models/command.dart'; import 'dart:convert'; +import 'package:stream_chat/src/models/command.dart'; import 'package:test/test.dart'; void main() { @@ -30,9 +30,9 @@ void main() { expect( command.toJson(), { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]", + 'name': 'giphy', + 'description': 'Post a random gif to the channel', + 'args': '[text]', }, ); }); diff --git a/packages/stream_chat/test/src/models/device_test.dart b/packages/stream_chat/test/src/models/device_test.dart index 3f8de16e..27a94982 100644 --- a/packages/stream_chat/test/src/models/device_test.dart +++ b/packages/stream_chat/test/src/models/device_test.dart @@ -5,7 +5,7 @@ import 'package:stream_chat/src/models/device.dart'; void main() { group('src/models/device', () { - const jsonExample = r'''{ + const jsonExample = '''{ "id": "device-id", "push_provider": "push-provider" }'''; diff --git a/packages/stream_chat/test/src/models/event_test.dart b/packages/stream_chat/test/src/models/event_test.dart index db99b01a..a7c669df 100644 --- a/packages/stream_chat/test/src/models/event_test.dart +++ b/packages/stream_chat/test/src/models/event_test.dart @@ -55,7 +55,7 @@ void main() { type: 'type', cid: 'cid', connectionId: 'connectionId', - createdAt: DateTime.parse("2020-01-29T03:22:47.63613Z"), + createdAt: DateTime.parse('2020-01-29T03:22:47.63613Z'), me: OwnUser(id: 'id2'), totalUnreadCount: 1, unreadChannels: 1, diff --git a/packages/stream_chat/test/src/models/member_test.dart b/packages/stream_chat/test/src/models/member_test.dart index 237f0030..25e1d78e 100644 --- a/packages/stream_chat/test/src/models/member_test.dart +++ b/packages/stream_chat/test/src/models/member_test.dart @@ -28,8 +28,8 @@ void main() { final member = Member.fromJson(json.decode(jsonExample)); expect(member.user, isA()); expect(member.role, 'member'); - expect(member.createdAt, DateTime.parse("2020-01-28T22:17:30.95443Z")); - expect(member.updatedAt, DateTime.parse("2020-01-28T22:17:30.95443Z")); + expect(member.createdAt, DateTime.parse('2020-01-28T22:17:30.95443Z')); + expect(member.updatedAt, DateTime.parse('2020-01-28T22:17:30.95443Z')); }); }); } diff --git a/packages/stream_chat/test/src/models/message_test.dart b/packages/stream_chat/test/src/models/message_test.dart index 3541b2c0..0898e902 100644 --- a/packages/stream_chat/test/src/models/message_test.dart +++ b/packages/stream_chat/test/src/models/message_test.dart @@ -76,10 +76,10 @@ void main() { test('should parse json correctly', () { final message = Message.fromJson(json.decode(jsonExample)); - expect(message.id, "4637f7e4-a06b-42db-ba5a-8d8270dd926f"); + expect(message.id, '4637f7e4-a06b-42db-ba5a-8d8270dd926f'); expect(message.text, - "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA"); - expect(message.type, "regular"); + 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA'); + expect(message.type, 'regular'); expect(message.user, isA()); expect(message.silent, isA()); expect(message.attachments, isA>()); @@ -87,8 +87,8 @@ void main() { expect(message.ownReactions, isA>()); expect(message.reactionCounts, {'love': 1}); expect(message.reactionScores, {'love': 1}); - expect(message.createdAt, DateTime.parse("2020-01-28T22:17:31.107978Z")); - expect(message.updatedAt, DateTime.parse("2020-01-28T22:17:31.130506Z")); + expect(message.createdAt, DateTime.parse('2020-01-28T22:17:31.107978Z')); + expect(message.updatedAt, DateTime.parse('2020-01-28T22:17:31.130506Z')); expect(message.mentionedUsers, isA>()); expect(message.pinned, false); expect(message.pinnedAt, null); @@ -98,38 +98,37 @@ void main() { test('should serialize to json correctly', () { final message = Message( - id: "4637f7e4-a06b-42db-ba5a-8d8270dd926f", + id: '4637f7e4-a06b-42db-ba5a-8d8270dd926f', text: - "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", + 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA', silent: false, attachments: [ Attachment.fromJson({ - "type": "video", - "author_name": "GIPHY", - "title": "The Lion King Disney GIF - Find \u0026 Share on GIPHY", - "title_link": - "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "text": - "Discover \u0026 share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "image_url": - "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "thumb_url": - "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "asset_url": - "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4", - "og_scrape_url": - "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA" + 'type': 'video', + 'author_name': 'GIPHY', + 'title': 'The Lion King Disney GIF - Find \u0026 Share on GIPHY', + 'title_link': + 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', + 'text': + '''Discover \u0026 share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.''', + 'image_url': + 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', + 'thumb_url': + 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', + 'asset_url': + 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4', + 'og_scrape_url': + 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA' }) ], showInChannel: true, parentId: 'parentId', extraData: {'hey': 'test'}, - status: MessageSendingStatus.sent, ); expect( message.toJson(), - json.decode(r''' + json.decode(''' { "id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", "text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", diff --git a/packages/stream_chat/test/src/models/reaction_test.dart b/packages/stream_chat/test/src/models/reaction_test.dart index 8bc92081..2a1d5431 100644 --- a/packages/stream_chat/test/src/models/reaction_test.dart +++ b/packages/stream_chat/test/src/models/reaction_test.dart @@ -1,7 +1,7 @@ -import 'package:test/test.dart'; -import 'package:stream_chat/src/models/reaction.dart'; import 'dart:convert'; +import 'package:test/test.dart'; +import 'package:stream_chat/src/models/reaction.dart'; import 'package:stream_chat/src/models/user.dart'; void main() { @@ -30,13 +30,13 @@ void main() { test('should parse json correctly', () { final reaction = Reaction.fromJson(json.decode(jsonExample)); expect(reaction.messageId, '76cd8c82-b557-4e48-9d12-87995d3a0e04'); - expect(reaction.createdAt, DateTime.parse("2020-01-28T22:17:31.108742Z")); + expect(reaction.createdAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); expect(reaction.type, 'wow'); expect( reaction.user.toJson(), - User.init("2de0297c-f3f2-489d-b930-ef77342edccf", extraData: { - "image": "https://randomuser.me/api/portraits/women/45.jpg", - "name": "Daisy Morgan" + User.init('2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { + 'image': 'https://randomuser.me/api/portraits/women/45.jpg', + 'name': 'Daisy Morgan' }).toJson(), ); expect(reaction.score, 1); @@ -47,13 +47,13 @@ void main() { test('should serialize to json correctly', () { final reaction = Reaction( messageId: '76cd8c82-b557-4e48-9d12-87995d3a0e04', - createdAt: DateTime.parse("2020-01-28T22:17:31.108742Z"), + createdAt: DateTime.parse('2020-01-28T22:17:31.108742Z'), type: 'wow', - user: User.init("2de0297c-f3f2-489d-b930-ef77342edccf", extraData: { - "image": "https://randomuser.me/api/portraits/women/45.jpg", - "name": "Daisy Morgan" + user: User.init('2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { + 'image': 'https://randomuser.me/api/portraits/women/45.jpg', + 'name': 'Daisy Morgan' }), - userId: "2de0297c-f3f2-489d-b930-ef77342edccf", + userId: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: {'bananas': 'yes'}, score: 1, ); @@ -61,10 +61,10 @@ void main() { expect( reaction.toJson(), { - "message_id": "76cd8c82-b557-4e48-9d12-87995d3a0e04", - "type": "wow", - "score": 1, - "bananas": 'yes', + 'message_id': '76cd8c82-b557-4e48-9d12-87995d3a0e04', + 'type': 'wow', + 'score': 1, + 'bananas': 'yes', }, ); }); diff --git a/packages/stream_chat/test/src/models/read_test.dart b/packages/stream_chat/test/src/models/read_test.dart index b15ce66a..9fd68b6a 100644 --- a/packages/stream_chat/test/src/models/read_test.dart +++ b/packages/stream_chat/test/src/models/read_test.dart @@ -31,8 +31,8 @@ void main() { ); expect(read.toJson(), { - "user": {"id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e"}, - "last_read": "2020-01-28T22:17:30.966485Z", + 'user': {'id': 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'}, + 'last_read': '2020-01-28T22:17:30.966485Z', 'unread_messages': 10, }); }); diff --git a/packages/stream_chat/test/src/models/user_test.dart b/packages/stream_chat/test/src/models/user_test.dart index 6100b218..0a307126 100644 --- a/packages/stream_chat/test/src/models/user_test.dart +++ b/packages/stream_chat/test/src/models/user_test.dart @@ -17,11 +17,13 @@ void main() { }); test('should serialize to json correctly', () { - final user = - User(id: 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e', role: "abc"); + final user = User( + id: 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e', + role: 'abc', + ); expect(user.toJson(), { - 'id': "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e", + 'id': 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e', }); }); }); From 83b51625e001d8318ef6518bf7803aa0430279f6 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 22 Mar 2021 12:21:37 +0530 Subject: [PATCH 051/599] fix(UI-Kit): Show selected attachment file as file in message input widget Signed-off-by: Sahil Kumar --- packages/stream_chat_flutter/lib/src/message_input.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index fc79b56c..15bb3c3d 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -656,6 +656,7 @@ class MessageInputState extends State { Timer _debounce; String _previousValue; + void _onChanged(BuildContext context, String s) { if (s == _previousValue) { return; @@ -1962,12 +1963,6 @@ class MessageInputState extends State { final extraDataMap = {}; - if (mimeType?.type == 'video' || mimeType?.type == 'image') { - attachmentType = mimeType.type; - } else { - attachmentType = 'file'; - } - if (mimeType?.subtype != null) { extraDataMap['mime_type'] = mimeType.subtype.toLowerCase(); } From 6d259074eb62ef388bacbc25bd0250844001d4f9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 22 Mar 2021 09:41:52 +0100 Subject: [PATCH 052/599] fix: ws disconnection (#345) * update ws removing custom implementation; fixed disconnection while connecting * add test --- .../stream_chat/lib/src/api/websocket.dart | 22 +++++---- packages/stream_chat/pubspec.yaml | 2 +- .../test/src/api/web_socket_stub_test.dart | 9 ---- .../test/src/api/websocket_test.dart | 45 ++++++++++++++++--- 4 files changed, 55 insertions(+), 23 deletions(-) delete mode 100644 packages/stream_chat/test/src/api/web_socket_stub_test.dart diff --git a/packages/stream_chat/lib/src/api/websocket.dart b/packages/stream_chat/lib/src/api/websocket.dart index ea2bb291..0f78b1bf 100644 --- a/packages/stream_chat/lib/src/api/websocket.dart +++ b/packages/stream_chat/lib/src/api/websocket.dart @@ -6,9 +6,6 @@ import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/api/connection_status.dart'; -import 'package:stream_chat/src/api/web_socket_channel_stub.dart' - if (dart.library.html) 'web_socket_channel_html.dart' - if (dart.library.io) 'web_socket_channel_io.dart'; import 'package:stream_chat/src/models/event.dart'; import 'package:stream_chat/src/models/user.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; @@ -36,7 +33,7 @@ class WebSocket { this.connectPayload, this.handler, this.logger, - this.connectFunc = connectWebSocket, + this.connectFunc, this.reconnectionMonitorInterval = 1, this.healthCheckInterval = 20, this.reconnectionMonitorTimeout = 40, @@ -121,9 +118,9 @@ class WebSocket { WebSocketChannel _channel; Timer _healthCheck, _reconnectionMonitor; DateTime _lastEventAt; - bool _manuallyDisconnected = false, - _connecting = false, - _reconnecting = false; + bool _manuallyDisconnected = false; + bool _connecting = false; + bool _reconnecting = false; Event _decodeEvent(String source) => Event.fromJson(json.decode(source)); @@ -143,7 +140,8 @@ class WebSocket { logger.info('connecting to $_path'); - _channel = connectFunc(_path); + _channel = + connectFunc?.call(_path) ?? WebSocketChannel.connect(Uri.parse(_path)); _channel.stream.listen( (data) { final jsonData = json.decode(data); @@ -177,6 +175,10 @@ class WebSocket { } void _onData(data) { + if (_manuallyDisconnected) { + return; + } + final event = _decodeEvent(data); logger.info('received new event: $data'); @@ -302,6 +304,10 @@ class WebSocket { /// Disconnects the WS and releases eventual resources Future disconnect() async { + _connecting = false; + if (!_connectionCompleter.isCompleted) { + _connectionCompleter.complete(); + } if (_manuallyDisconnected) { return; } diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 81aef23e..05023530 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: mime: ^0.9.7 rxdart: ^0.25.0 uuid: ^2.2.2 - web_socket_channel: ^1.1.0 + web_socket_channel: ^1.2.0 dev_dependencies: build_runner: ^1.10.0 diff --git a/packages/stream_chat/test/src/api/web_socket_stub_test.dart b/packages/stream_chat/test/src/api/web_socket_stub_test.dart deleted file mode 100644 index e60706dc..00000000 --- a/packages/stream_chat/test/src/api/web_socket_stub_test.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:test/test.dart'; -import 'package:stream_chat/src/api/web_socket_channel_stub.dart'; - -void main() { - test('src/api/web_socket_stub_test', () { - expect( - () => connectWebSocket('fakeurl'), throwsA(isA())); - }); -} diff --git a/packages/stream_chat/test/src/api/websocket_test.dart b/packages/stream_chat/test/src/api/websocket_test.dart index d9486477..238cc850 100644 --- a/packages/stream_chat/test/src/api/websocket_test.dart +++ b/packages/stream_chat/test/src/api/websocket_test.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:test/test.dart'; import 'package:logging/logging.dart'; import 'package:mockito/mockito.dart'; import 'package:stream_chat/src/api/connection_status.dart'; @@ -8,6 +7,7 @@ import 'package:stream_chat/src/api/websocket.dart'; import 'package:stream_chat/src/models/event.dart'; import 'package:stream_chat/src/models/user.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; class Functions { @@ -47,10 +47,9 @@ void main() { final mockWSChannel = MockWSChannel(); - final StreamController streamController = - StreamController.broadcast(); + final streamController = StreamController.broadcast(); - final computedUrl = + const computedUrl = 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; when(connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); @@ -60,7 +59,7 @@ void main() { }); final timer = Timer.periodic( - Duration(milliseconds: 100), + const Duration(milliseconds: 100), (_) => streamController.sink.add('{}'), ); @@ -160,6 +159,42 @@ void main() { return connect; }); + test('should close correctly the controller while connecting', () async { + final handleFunc = MockFunctions().handleFunc; + + final ConnectWebSocket connectFunc = MockFunctions().connectFunc; + + final ws = WebSocket( + baseUrl: 'baseurl', + user: User(id: 'testid'), + logger: Logger('ws'), + connectParams: {'test': 'true'}, + connectPayload: {'payload': 'test'}, + handler: handleFunc, + connectFunc: connectFunc, + ); + + final mockWSChannel = MockWSChannel(); + + final StreamController streamController = + StreamController.broadcast(); + + final computedUrl = + 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; + + when(connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); + when(mockWSChannel.sink).thenAnswer((_) => MockWSSink()); + when(mockWSChannel.stream).thenAnswer((_) { + return streamController.stream; + }); + + ws.connect(); + await ws.disconnect(); + streamController.add('{}'); + + verify(connectFunc(computedUrl)).called(1); + verifyNever(handleFunc(any)); + }); test('should run correctly health check', () async { final handleFunc = MockFunctions().handleFunc; From cba0b1a391631a9ec2026727968acaeef5d10197 Mon Sep 17 00:00:00 2001 From: "Neevash Ramdial (Nash)" Date: Mon, 22 Mar 2021 04:49:27 -0400 Subject: [PATCH 053/599] feat: Apply team lints to core package (#334) * Add analyzer file to core package * Apply team lint rules to core package * clean up analyzer warnings in stream chat flutter * remove unused line * add ignore * sync analysis options * [Core -> MessageWidget] Fix variable dynamic types Signed-off-by: Sahil Kumar Co-authored-by: Salvatore Giordano Co-authored-by: Sahil Kumar --- packages/stream_chat/analysis_options.yaml | 8 +- packages/stream_chat/lib/src/api/channel.dart | 7 +- .../analysis_options.yaml | 145 +++++++++++++++++ .../lib/src/channel_list_core.dart | 91 ++++++----- .../lib/src/channels_bloc.dart | 15 +- .../lib/src/lazy_load_scroll_view.dart | 19 ++- .../lib/src/message_list_core.dart | 42 +++-- .../lib/src/message_search_bloc.dart | 5 +- .../lib/src/message_search_list_core.dart | 85 +++++----- .../lib/src/stream_channel.dart | 43 ++--- .../lib/src/stream_chat_core.dart | 24 ++- .../lib/src/typedef.dart | 4 +- .../lib/src/user_list_core.dart | 149 +++++++++--------- .../lib/src/users_bloc.dart | 11 +- .../lib/stream_chat_flutter_core.dart | 5 +- .../stream_chat_flutter_core/pubspec.yaml | 7 +- .../test/stream_chat_core_test.dart | 21 +-- 17 files changed, 425 insertions(+), 256 deletions(-) create mode 100644 packages/stream_chat_flutter_core/analysis_options.yaml diff --git a/packages/stream_chat/analysis_options.yaml b/packages/stream_chat/analysis_options.yaml index 26001160..ddc691d0 100644 --- a/packages/stream_chat/analysis_options.yaml +++ b/packages/stream_chat/analysis_options.yaml @@ -1,11 +1,11 @@ analyzer: - exclude: + exclude: - lib/**/*.g.dart - lib/**/*.freezed.dart - example/* - test/* -linter: - rules: +linter: + rules: - always_use_package_imports - avoid_empty_else - avoid_relative_lib_imports @@ -14,7 +14,6 @@ linter: - cancel_subscriptions - close_sinks - control_flow_in_finally - - diagnostic_describe_all_properties - empty_statements - hash_and_equals - invariant_booleans @@ -33,7 +32,6 @@ linter: - use_key_in_widget_constructors - valid_regexps - always_declare_return_types - - always_put_required_named_parameters_first - always_require_non_null_named_parameters - annotate_overrides - avoid_bool_literals_in_conditional_expressions diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 1701fed8..dcb11316 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1210,9 +1210,10 @@ class ChannelClientState { this._channel, ChannelState channelState, //ignore: unnecessary_parenthesis - ) : _debouncedUpdatePersistenceChannelState = ((ChannelState state) { - _channel?._client?.chatPersistenceClient?.updateChannelState(state); - }).debounced(const Duration(seconds: 1)) { + ) : _debouncedUpdatePersistenceChannelState = ((ChannelState state) => + _channel?._client?.chatPersistenceClient + ?.updateChannelState(state)) + .debounced(const Duration(seconds: 1)) { retryQueue = RetryQueue( channel: _channel, logger: Logger('RETRY QUEUE ${_channel.cid}'), diff --git a/packages/stream_chat_flutter_core/analysis_options.yaml b/packages/stream_chat_flutter_core/analysis_options.yaml new file mode 100644 index 00000000..545d5492 --- /dev/null +++ b/packages/stream_chat_flutter_core/analysis_options.yaml @@ -0,0 +1,145 @@ +analyzer: + exclude: + - lib/**/*.g.dart + - lib/**/*.freezed.dart + - example/* + - test/* +linter: + rules: + - always_use_package_imports + - avoid_empty_else + - avoid_relative_lib_imports + - avoid_slow_async_io + - avoid_types_as_parameter_names + - cancel_subscriptions + - close_sinks + - control_flow_in_finally + - empty_statements + - hash_and_equals + - invariant_booleans + - iterable_contains_unrelated_type + - list_remove_unrelated_type + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - no_duplicate_case_values + - no_logic_in_create_state + - prefer_void_to_null + - test_types_in_equals + - throw_in_finally + - unnecessary_statements + - unrelated_type_equality_checks + - omit_local_variable_types + - use_key_in_widget_constructors + - valid_regexps + - always_declare_return_types + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catching_errors + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_private_typedef_functions + - avoid_redundant_argument_values + - avoid_return_types_on_setters + - avoid_returning_null + - avoid_returning_null_for_void + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_extensions + - camel_case_types + - cascade_invocations + + - constant_identifier_names + - curly_braces_in_flow_control_structures + - directives_ordering + - empty_catches + - empty_constructor_bodies + - exhaustive_cases + - file_names + - implementation_imports + - join_return_with_assignment + - leading_newlines_in_multiline_strings + - library_names + - library_prefixes + - lines_longer_than_80_chars + - missing_whitespace_between_adjacent_strings + - non_constant_identifier_names + - null_closures + - one_member_abstracts + - only_throw_errors + - package_api_docs + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_asserts_with_message + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_equal_for_default_values + - prefer_expression_function_bodies + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_int_literals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - provide_deprecation_message + - public_member_api_docs + - recursive_getters + - sized_box_for_whitespace + - slash_for_doc_comments + - sort_child_properties_last + - sort_constructors_first + - sort_unnamed_constructors_first + + - type_annotate_public_apis + - type_init_formals + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_parenthesis + - unnecessary_raw_strings + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - use_is_even_rather_than_modulo + - use_late_for_private_fields_and_variables + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_to_and_as_if_applicable + - package_names + - sort_pub_dependencies + + # To be added when null-safe: + # - 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 diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 816bb052..8a41332d 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -5,11 +5,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter_core/src/channels_bloc.dart'; +import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart'; -import 'stream_chat_core.dart'; - -/// [ChannelListCore] is a simplified class that allows fetching a list of channels while exposing UI builders. +/// [ChannelListCore] is a simplified class that allows fetching a list of +/// channels while exposing UI builders. /// A [ChannelListController] is used to reload and paginate data. /// /// @@ -52,10 +52,11 @@ import 'stream_chat_core.dart'; /// } /// ``` /// -/// Make sure to have a [StreamChatCore] ancestor in order to provide the information about the channels. +/// Make sure to have a [StreamChatCore] ancestor in order to provide the +/// information about the channels. class ChannelListCore extends StatefulWidget { /// Instantiate a new ChannelListView - ChannelListCore({ + const ChannelListCore({ Key key, @required this.errorBuilder, @required this.emptyBuilder, @@ -65,18 +66,31 @@ class ChannelListCore extends StatefulWidget { this.options, this.sort, this.pagination = const PaginationParams( - offset: 0, limit: 25, ), this.channelListController, - }) : assert(errorBuilder != null), - assert(emptyBuilder != null), - assert(loadingBuilder != null), - assert(listBuilder != null), + }) : assert( + errorBuilder != null, + 'Parameter errorBuilder should not be null', + ), + assert( + emptyBuilder != null, + 'Parameter emptyBuilder should not be null', + ), + assert( + loadingBuilder != null, + 'Parameter loadingBuilder should not be null', + ), + assert( + listBuilder != null, + 'Parameter listBuilder should not be null', + ), super(key: key); /// A [ChannelListController] allows reloading and pagination. - /// Use [ChannelListController.loadData] and [ChannelListController.paginateData] respectively for reloading and pagination. + /// Use [ChannelListController.loadData] and + /// [ChannelListController.paginateData] respectively for reloading and + /// pagination. final ChannelListController channelListController; /// The builder that will be used in case of error @@ -103,9 +117,10 @@ class ChannelListCore extends StatefulWidget { final Map options; /// The sorting used for the channels matching the filters. - /// Sorting is based on field and direction, multiple sorting options can be provided. - /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. - /// Direction can be ascending or descending. + /// Sorting is based on field and direction, multiple sorting options can be + /// provided. + /// You can sort based on last_updated, last_message_at, updated_at, created + /// _at or member_count. Direction can be ascending or descending. final List> sort; /// Pagination parameters @@ -128,24 +143,23 @@ class _ChannelListCoreState extends State { StreamBuilder> _buildListView( ChannelsBlocState channelsBlocState, - ) { - return StreamBuilder>( - stream: channelsBlocState.channelsStream, - builder: (context, snapshot) { - if (snapshot.hasError) { - return _buildErrorWidget(snapshot, context, channelsBlocState); - } - if (!snapshot.hasData) { - return widget.loadingBuilder(context); - } - final channels = snapshot.data; - if (channels.isEmpty) { - return widget.emptyBuilder(context); - } - return widget.listBuilder(context, channels); - }, - ); - } + ) => + StreamBuilder>( + stream: channelsBlocState.channelsStream, + builder: (context, snapshot) { + if (snapshot.hasError) { + return _buildErrorWidget(snapshot, context, channelsBlocState); + } + if (!snapshot.hasData) { + return widget.loadingBuilder(context); + } + final channels = snapshot.data; + if (channels.isEmpty) { + return widget.emptyBuilder(context); + } + return widget.listBuilder(context, channels); + }, + ); Widget _buildErrorWidget( AsyncSnapshot> snapshot, @@ -223,14 +237,15 @@ class _ChannelListCoreState extends State { } } -/// Controller used for loading more data and controlling pagination in [ChannelListCore]. +/// Controller used for loading more data and controlling pagination in +/// [ChannelListCore]. class ChannelListController { - /// This function calls Stream's servers to load a list of channels. If there is existing data, - /// calling this function causes a reload. + /// This function calls Stream's servers to load a list of channels. + /// If there is existing data, calling this function causes a reload. AsyncCallback loadData; - /// This function is used to load another page of data. Note, [loadData] should be - /// used to populate the initial page of data. Calling [paginateData] performs a query - /// to load subsequent pages. + /// This function is used to load another page of data. Note, [loadData] + /// should be used to populate the initial page of data. Calling + /// [paginateData] performs a query to load subsequent pages. AsyncCallback paginateData; } 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 1ab84878..66c2b8ea 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; @@ -16,26 +17,29 @@ import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; /// /// API docs: https://getstream.io/chat/docs/flutter-dart/query_channels/ class ChannelsBloc extends StatefulWidget { - /// Creates a new [ChannelsBloc]. The parameter [child] must be supplied and not null. + /// Creates a new [ChannelsBloc]. The parameter [child] must be supplied and + /// not null. const ChannelsBloc({ Key key, @required this.child, this.lockChannelsOrder = false, this.channelsComparator, this.shouldAddChannel, - }) : assert(child != null), + }) : assert(child != null, 'Parameter child should not be null.'), super(key: key); /// The widget child final Widget child; - /// Set this to true to prevent channels to be brought to the top of the list when a new message arrives + /// Set this to true to prevent channels to be brought to the top of the list + /// when a new message arrives final bool lockChannelsOrder; /// Comparator used to sort the channels when a message.new event is received final Comparator channelsComparator; - /// Function used to evaluate if a channel should be added to the list when a message.new event is received + /// Function used to evaluate if a channel should be added to the list when a + /// message.new event is received final bool Function(Event) shouldAddChannel; @override @@ -170,13 +174,14 @@ class ChannelsBlocState extends State _channelsController.add(newChannels); } })); - + // ignore: cascade_invocations _subscriptions.add(client .on( EventType.channelDeleted, EventType.notificationRemovedFromChannel, ) .listen((e) { + // ignore: cascade_invocations final channel = e.channel; _channelsController.add(List.from( (channels ?? [])..removeWhere((c) => c.cid == channel.cid))); diff --git a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart index 1820827f..725e19f3 100644 --- a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart @@ -1,13 +1,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; +// ignore: constant_identifier_names enum _LoadingStatus { LOADING, STABLE } /// Wrapper around a [Scrollable] which triggers [onEndOfPage]/[onStartOfPage] the Scrollable /// reaches to the start or end of the view extent. class LazyLoadScrollView extends StatefulWidget { - /// Creates a new instance of [LazyLoadScrollView]. The parameter [child] must be - /// supplied and not null. + /// Creates a new instance of [LazyLoadScrollView]. The parameter [child] + /// must be supplied and not null. const LazyLoadScrollView({ Key key, @required this.child, @@ -17,7 +18,7 @@ class LazyLoadScrollView extends StatefulWidget { this.onPageScrollEnd, this.onInBetweenOfPage, this.scrollOffset = 100, - }) : assert(child != null), + }) : assert(child != null, 'Parameter child should not be null'), super(key: key); /// The [Widget] that this widget watches for changes on @@ -47,15 +48,13 @@ class LazyLoadScrollView extends StatefulWidget { class _LazyLoadScrollViewState extends State { _LoadingStatus _loadMoreStatus = _LoadingStatus.STABLE; - double _scrollPosition = 0.0; + double _scrollPosition = 0; @override - Widget build(BuildContext context) { - return NotificationListener( - child: widget.child, - onNotification: _onNotification, - ); - } + Widget build(BuildContext context) => NotificationListener( + onNotification: _onNotification, + child: widget.child, + ); bool _onNotification(Notification notification) { if (notification is ScrollStartNotification) { diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 87f821c1..a5e9587e 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -1,12 +1,15 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/stream_channel.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart'; -import 'stream_channel.dart'; -/// [MessageListCore] is a simplified class that allows fetching a list of messages while exposing UI builders. +/// [MessageListCore] is a simplified class that allows fetching a list of +/// messages while exposing UI builders. +/// /// A [MessageListController] is used to paginate data. /// /// ```dart @@ -50,12 +53,14 @@ import 'stream_channel.dart'; /// ``` /// /// -/// Make sure to have a [StreamChannel] ancestor in order to provide the information about the channels. +/// Make sure to have a [StreamChannel] ancestor in order to provide the +/// information about the channels. +/// /// The widget uses a [ListView.custom] to render the list of channels. /// class MessageListCore extends StatefulWidget { /// Instantiate a new [MessageListView]. - MessageListCore({ + const MessageListCore({ Key key, @required this.loadingBuilder, @required this.emptyBuilder, @@ -65,10 +70,16 @@ class MessageListCore extends StatefulWidget { this.parentMessage, this.messageListController, this.messageFilter, - }) : assert(loadingBuilder != null), - assert(emptyBuilder != null), - assert(messageListBuilder != null), - assert(errorWidgetBuilder != null), + }) : assert(loadingBuilder != null, 'loadingBuilder should not be null'), + assert(emptyBuilder != null, 'emptyBuilder should not be null'), + assert( + messageListBuilder != null, + 'messageListBuilder should not be null', + ), + assert( + errorWidgetBuilder != null, + 'errorWidgetBuilder should not be null', + ), super(key: key); /// A [MessageListController] allows pagination. @@ -84,12 +95,15 @@ class MessageListCore extends StatefulWidget { /// Function used to build an empty widget final WidgetBuilder emptyBuilder; - /// Callback triggered when an error occurs while performing the given request. - /// This parameter can be used to display an error message to users in the event - /// of a connection failure. + /// Callback triggered when an error occurs while performing the given + /// request. + /// + /// This parameter can be used to display an error message to users in the + /// event of a connection failure. final ErrorBuilder errorWidgetBuilder; - /// If true will show a scroll to bottom message when there are new messages and the scroll offset is not zero + /// If true will show a scroll to bottom message when there are new messages + /// and the scroll offset is not zero. final bool showScrollToBottom; /// If the current message belongs to a `thread`, this property represents the @@ -112,11 +126,7 @@ class _MessageListCoreState extends State { OwnUser get _currentUser => streamChannel.channel.client.state.user; - int initialIndex; - double initialAlignment; - List messages = []; - bool initialMessageHighlightComplete = false; @override 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 71488cc8..cf1e8d19 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 @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; - -import 'stream_chat_core.dart'; +import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; /// [MessageSearchBloc] is used to manage a list of messages with pagination. /// This class can be used to load messages, perform queries, etc. @@ -16,7 +15,7 @@ class MessageSearchBloc extends StatefulWidget { const MessageSearchBloc({ Key key, @required this.child, - }) : assert(child != null), + }) : assert(child != null, 'Parameter child should not be null.'), super(key: key); /// The widget child diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index e3d98426..5c4e74fe 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -1,13 +1,15 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/message_search_bloc.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart'; -import 'message_search_bloc.dart'; /// -/// [MessageSearchListCore] is a simplified class that allows searching for messages across channels while exposing UI builders. -/// A [MessageSearchListController] is used to load and paginate data. +/// [MessageSearchListCore] is a simplified class that allows searching for +/// messages across channels while exposing UI builders. +/// A [MessageSearchListController] is used to load and paginate data. /// /// ```dart /// class MessageSearchPage extends StatelessWidget { @@ -28,7 +30,8 @@ import 'message_search_bloc.dart'; /// } /// ``` /// -/// Make sure to have a [MessageSearchBloc] ancestor in order to provide the information about the messages. +/// Make sure to have a [MessageSearchBloc] ancestor in order to provide the +/// information about the messages. /// The widget uses a [ListView.separated] to render the list of messages. /// class MessageSearchListCore extends StatefulWidget { @@ -50,14 +53,16 @@ class MessageSearchListCore extends StatefulWidget { this.paginationParams, this.messageFilters, this.messageSearchListController, - }) : assert(emptyBuilder != null), - assert(errorBuilder != null), - assert(loadingBuilder != null), - assert(childBuilder != null), + }) : assert(emptyBuilder != null, 'emptyBuilder should not be null'), + assert(errorBuilder != null, 'errorBuilder should not be null'), + assert(loadingBuilder != null, 'loadingBuilder should not be null'), + assert(childBuilder != null, 'childBuilder should not be null'), super(key: key); /// A [MessageSearchListController] allows reloading and pagination. - /// Use [MessageSearchListController.loadData] and [MessageSearchListController.paginateData] respectively for reloading and pagination. + /// Use [MessageSearchListController.loadData] and + /// [MessageSearchListController.paginateData] respectively for reloading and + /// pagination. final MessageSearchListController messageSearchListController; /// Message String to search on @@ -69,9 +74,10 @@ class MessageSearchListCore extends StatefulWidget { final Map filters; /// The sorting used for the channels matching the filters. - /// Sorting is based on field and direction, multiple sorting options can be provided. - /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. - /// Direction can be ascending or descending. + /// Sorting is based on field and direction, multiple sorting options can be + /// provided. + /// You can sort based on last_updated, last_message_at, updated_at, created_ + /// at or member_count. Direction can be ascending or descending. final List sortOptions; /// Pagination parameters @@ -103,10 +109,9 @@ class MessageSearchListCore extends StatefulWidget { class _MessageSearchListCoreState extends State { @override - void initState() { - super.initState(); - final messageSearchBloc = MessageSearchBloc.of(context); - messageSearchBloc.search( + void didChangeDependencies() { + super.didChangeDependencies(); + MessageSearchBloc.of(context).search( filter: widget.filters, sort: widget.sortOptions, query: widget.messageQuery, @@ -126,37 +131,34 @@ class _MessageSearchListCoreState extends State { return _buildListView(messageSearchBloc); } - Widget _buildListView(MessageSearchBlocState messageSearchBloc) { - return StreamBuilder>( - stream: messageSearchBloc.messagesStream, - builder: (context, snapshot) { - if (snapshot.hasError) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); + Widget _buildListView(MessageSearchBlocState messageSearchBloc) => + StreamBuilder>( + stream: messageSearchBloc.messagesStream, + builder: (context, snapshot) { + if (snapshot.hasError) { + if (snapshot.error is Error) { + print((snapshot.error as Error).stackTrace); + } + + return widget.errorBuilder(context, snapshot.error); } - return widget.errorBuilder(context, snapshot.error); - } + if (!snapshot.hasData) { + return widget.loadingBuilder(context); + } - if (!snapshot.hasData) { - return widget.loadingBuilder(context); - } + final items = snapshot.data; - final items = snapshot.data; + if (items.isEmpty) { + return widget.emptyBuilder(context); + } - if (items.isEmpty) { - return widget.emptyBuilder(context); - } - - return widget.childBuilder(snapshot.data); - }, - ); - } + return widget.childBuilder(snapshot.data); + }, + ); void loadData() { - final messageSearchBloc = MessageSearchBloc.of(context); - - messageSearchBloc.search( + MessageSearchBloc.of(context).search( filter: widget.filters, sort: widget.sortOptions, query: widget.messageQuery, @@ -189,8 +191,7 @@ class _MessageSearchListCoreState extends State { widget.messageQuery?.toString() != oldWidget.messageQuery?.toString() || widget.messageFilters?.toString() != oldWidget.messageFilters?.toString()) { - final messageSearchBloc = MessageSearchBloc.of(context); - messageSearchBloc.search( + MessageSearchBloc.of(context).search( filter: widget.filters, sort: widget.sortOptions, query: widget.messageQuery, 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 c2333b05..05158c94 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; @@ -25,8 +26,8 @@ class StreamChannel extends StatefulWidget { @required this.channel, this.showLoading = true, this.initialMessageId, - }) : assert(child != null), - assert(channel != null), + }) : assert(child != null, 'Child should not be null'), + assert(channel != null, 'Channel should not be null'), super(key: key); // ignore: public_member_api_docs @@ -49,7 +50,8 @@ class StreamChannel extends StatefulWidget { if (streamChannelState == null) { throw Exception( - 'You must have a StreamChannel widget at the top of your widget tree'); + 'You must have a StreamChannel widget at the top of your widget tree', + ); } return streamChannelState; @@ -204,14 +206,13 @@ class StreamChannelState extends State { int before = 20, int after = 20, bool preferOffline = false, - }) { - return queryAtMessage( - messageId: messageId, - before: before, - after: after, - preferOffline: preferOffline, - ); - } + }) => + queryAtMessage( + messageId: messageId, + before: before, + after: after, + preferOffline: preferOffline, + ); /// Future queryAtMessage({ @@ -254,15 +255,14 @@ class StreamChannelState extends State { String messageId, { int limit = 20, bool preferOffline = false, - }) { - return channel.query( - messagesPagination: PaginationParams( - lessThan: messageId, - limit: limit, - ), - preferOffline: preferOffline, - ); - } + }) => + channel.query( + messagesPagination: PaginationParams( + lessThan: messageId, + limit: limit, + ), + preferOffline: preferOffline, + ); /// Future queryAfterMessage( @@ -366,9 +366,10 @@ class StreamChannelState extends State { ); } final initialized = snapshot.data[0]; + // ignore: avoid_bool_literals_in_conditional_expressions final dataLoaded = initialMessageId == null ? true : snapshot.data[1]; if (widget.showLoading && (!initialized || !dataLoaded)) { - return Center( + return const Center( child: CircularProgressIndicator(), ); } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index 8a8d0185..83699cc1 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -3,13 +3,12 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; - -import 'typedef.dart'; +import 'package:stream_chat_flutter_core/src/typedef.dart'; /// Widget used to provide information about the chat to the widget tree. /// This Widget is used to react to life cycle changes and system updates. -/// When the app goes into the background, the websocket connection is kept alive -/// for two minutes before being terminated. +/// When the app goes into the background, the websocket connection is kept +/// alive for two minutes before being terminated. /// /// Conversely, when app is resumed or restarted, a new connection is initiated. /// @@ -36,16 +35,16 @@ import 'typedef.dart'; class StreamChatCore extends StatefulWidget { /// Constructor used for creating a new instance of [StreamChatCore]. /// - /// [StreamChatCore] is a stateful widget which reacts to system events and updates - /// Stream's connection status accordingly. - StreamChatCore({ + /// [StreamChatCore] is a stateful widget which reacts to system events and + /// updates Stream's connection status accordingly. + const StreamChatCore({ Key key, @required this.client, @required this.child, this.onBackgroundEventReceived, this.backgroundKeepAlive = const Duration(minutes: 1), - }) : assert(client != null), - assert(child != null), + }) : assert(client != null, 'Stream Chat Client should not be null'), + assert(child != null, 'Child should not be null'), super(key: key); /// Instance of Stream Chat Client containing information about the current @@ -55,7 +54,8 @@ class StreamChatCore extends StatefulWidget { /// Widget descendant. final Widget child; - /// The amount of time that will pass before disconnecting the client in the background + /// The amount of time that will pass before disconnecting the client in + /// the background final Duration backgroundKeepAlive; /// Handler called whenever the [client] receives a new [Event] while the app @@ -90,9 +90,7 @@ class StreamChatCoreState extends State Timer _disconnectTimer; @override - Widget build(BuildContext context) { - return widget.child; - } + Widget build(BuildContext context) => widget.child; /// The current user User get user => widget.client.state.user; diff --git a/packages/stream_chat_flutter_core/lib/src/typedef.dart b/packages/stream_chat_flutter_core/lib/src/typedef.dart index 29fb2d31..ace8d7e6 100644 --- a/packages/stream_chat_flutter_core/lib/src/typedef.dart +++ b/packages/stream_chat_flutter_core/lib/src/typedef.dart @@ -2,8 +2,8 @@ import 'package:flutter/widgets.dart'; import 'package:stream_chat/stream_chat.dart'; /// A signature for a callback which exposes an error and returns a function. -/// This Callback can be used in cases where an API failure occurs and the widget -/// is unable to render data. +/// This Callback can be used in cases where an API failure occurs and the +/// widget is unable to render data. typedef ErrorBuilder = Widget Function(BuildContext context, Object error); /// A Signature for a handler function which will expose a [event]. diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 23140258..61169483 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -1,11 +1,13 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter_core/src/users_bloc.dart'; /// -/// [UserListCore] is a simplified class that allows fetching users while exposing UI builders. +/// [UserListCore] is a simplified class that allows fetching users while +/// exposing UI builders. /// A [UserListController] is used to load and paginate data. /// /// ```dart @@ -50,30 +52,31 @@ import 'package:stream_chat_flutter_core/src/users_bloc.dart'; /// [UsersBloc] must be the ancestor of this widget. This is necessary since /// [UserListCore] depends on functionality contained within [UsersBloc]. /// -/// The parameters [listBuilder], [loadingBuilder], [emptyBuilder] and [errorBuilder] must all be supplied -/// and not null. +/// The parameters [listBuilder], [loadingBuilder], [emptyBuilder] and +/// [errorBuilder] must all be supplied and not null. class UserListCore extends StatefulWidget { /// Instantiate a new [UserListCore] const UserListCore({ - Key key, @required this.errorBuilder, @required this.emptyBuilder, @required this.loadingBuilder, @required this.listBuilder, + Key key, this.filter, this.options, this.sort, this.pagination, this.groupAlphabetically = false, this.userListController, - }) : assert(errorBuilder != null), - assert(emptyBuilder != null), - assert(loadingBuilder != null), - assert(listBuilder != null), + }) : assert(errorBuilder != null, ''), + assert(emptyBuilder != null, ''), + assert(loadingBuilder != null, ''), + assert(listBuilder != null, ''), super(key: key); /// A [UserListController] allows reloading and pagination. - /// Use [UserListController.loadData] and [UserListController.paginateData] respectively for reloading and pagination. + /// Use [UserListController.loadData] and [UserListController.paginateData] + /// respectively for reloading and pagination. final UserListController userListController; /// The builder that will be used in case of error @@ -100,9 +103,9 @@ class UserListCore extends StatefulWidget { final Map options; /// The sorting used for the channels matching the filters. - /// Sorting is based on field and direction, multiple sorting options can be provided. - /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. - /// Direction can be ascending or descending. + /// Sorting is based on field and direction, multiple sorting options can be + /// provided. You can sort based on last_updated, last_message_at, updated_at, + /// created_at or member_count. Direction can be ascending or descending. final List sort; /// Pagination parameters @@ -123,10 +126,9 @@ class UserListCore extends StatefulWidget { class _UserListCoreState extends State with WidgetsBindingObserver { @override - void initState() { - super.initState(); - final usersBloc = UsersBloc.of(context); - usersBloc.queryUsers( + void didChangeDependencies() { + super.didChangeDependencies(); + UsersBloc.of(context).queryUsers( filter: widget.filter, sort: widget.sort, pagination: widget.pagination, @@ -151,68 +153,66 @@ class _UserListCoreState extends State Stream> _buildUserStream( UsersBlocState usersBlocState, - ) { - return usersBlocState.usersStream.map( - (users) { - if (widget.groupAlphabetically) { - var temp = users; - if (!isListAlreadySorted) { - temp = users..sort((curr, next) => curr.name.compareTo(next.name)); + ) => + usersBlocState.usersStream.map( + (users) { + if (widget.groupAlphabetically) { + var temp = users; + if (!isListAlreadySorted) { + temp = users + ..sort((curr, next) => curr.name.compareTo(next.name)); + } + final groupedUsers = >{}; + for (final e in temp) { + final alphabet = e.name[0]?.toUpperCase(); + groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e]; + } + final items = []; + for (final key in groupedUsers.keys) { + items + ..add(ListHeaderItem(key)) + ..addAll(groupedUsers[key].map((e) => ListUserItem(e))); + } + return items; } - final groupedUsers = >{}; - for (final e in temp) { - final alphabet = e.name[0]?.toUpperCase(); - groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e]; - } - final items = []; - for (final key in groupedUsers.keys) { - items.add(ListHeaderItem(key)); - items.addAll(groupedUsers[key].map((e) => ListUserItem(e))); - } - return items; - } - return users.map((e) => ListUserItem(e)).toList(); - }, - ); - } + return users.map((e) => ListUserItem(e)).toList(); + }, + ); StreamBuilder> _buildListView( UsersBlocState usersBlocState, - ) { - return StreamBuilder( - stream: _buildUserStream(usersBlocState), - builder: (context, snapshot) { - if (snapshot.hasError) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); + ) => + StreamBuilder( + stream: _buildUserStream(usersBlocState), + builder: (context, snapshot) { + if (snapshot.hasError) { + if (snapshot.error is Error) { + print((snapshot.error as Error).stackTrace); + } + + return widget.errorBuilder(snapshot.error); } - return widget.errorBuilder(snapshot.error); - } + if (!snapshot.hasData) { + return widget.loadingBuilder(context); + } - if (!snapshot.hasData) { - return widget.loadingBuilder(context); - } + final items = snapshot.data; - final items = snapshot.data; + if (items.isEmpty) { + return widget.emptyBuilder(context); + } - if (items.isEmpty) { - return widget.emptyBuilder(context); - } + if (items.isEmpty) { + return widget.emptyBuilder(context); + } - if (items.isEmpty) { - return widget.emptyBuilder(context); - } - - return widget.listBuilder(context, items); - }, - ); - } + return widget.listBuilder(context, items); + }, + ); void loadData() { - final _usersBloc = UsersBloc.of(context); - - _usersBloc.queryUsers( + UsersBloc.of(context).queryUsers( filter: widget.filter, sort: widget.sort, pagination: widget.pagination, @@ -241,8 +241,7 @@ class _UserListCoreState extends State widget.pagination?.toJson()?.toString() != oldWidget.pagination?.toJson()?.toString() || widget.options?.toString() != oldWidget.options?.toString()) { - final usersBloc = UsersBloc.of(context); - usersBloc.queryUsers( + UsersBloc.of(context).queryUsers( filter: widget.filter, sort: widget.sort, pagination: widget.pagination, @@ -253,8 +252,8 @@ class _UserListCoreState extends State } /// Represents an item in a the user stream list. -/// Header items are prefixed with the key `HEADER` While users are prefixed with -/// `USER`. +/// Header items are prefixed with the key `HEADER` While users are prefixed +/// with `USER`. abstract class ListItem { // ignore: public_member_api_docs String get key { @@ -280,26 +279,26 @@ abstract class ListItem { if (this is ListUserItem) { return userItem((this as ListUserItem).user); } - return SizedBox(); + return const SizedBox(); } } // ignore: public_member_api_docs class ListHeaderItem extends ListItem { // ignore: public_member_api_docs - final String heading; + ListHeaderItem(this.heading); // ignore: public_member_api_docs - ListHeaderItem(this.heading); + final String heading; } // ignore: public_member_api_docs class ListUserItem extends ListItem { // ignore: public_member_api_docs - final User user; + ListUserItem(this.user); // ignore: public_member_api_docs - ListUserItem(this.user); + final User user; } /// Controller used for paginating data in [ChannelListView] 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 19c4a026..f10a5aae 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -1,8 +1,8 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; - -import 'stream_chat_core.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget dedicated to the management of a users list with pagination. /// @@ -14,9 +14,12 @@ class UsersBloc extends StatefulWidget { /// Instantiate a new [UsersBloc]. The parameter [child] must be supplied and /// not null. const UsersBloc({ - Key key, @required this.child, - }) : assert(child != null), + Key key, + }) : assert( + child != null, + 'When constructing a UsersBloc, the parameter ' + 'child should not be null.'), super(key: key); /// The widget child diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index 48d31b45..b62101d1 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -1,5 +1,7 @@ library stream_chat_flutter_core; +export 'package:stream_chat/stream_chat.dart'; + export 'src/channel_list_core.dart'; export 'src/channels_bloc.dart'; export 'src/lazy_load_scroll_view.dart'; @@ -8,7 +10,6 @@ export 'src/message_search_bloc.dart'; export 'src/message_search_list_core.dart'; export 'src/stream_channel.dart'; export 'src/stream_chat_core.dart'; +export 'src/typedef.dart'; export 'src/user_list_core.dart'; export 'src/users_bloc.dart'; -export 'src/typedef.dart'; -export 'package:stream_chat/stream_chat.dart'; diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index cc36f149..09643899 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -10,14 +10,13 @@ environment: flutter: ">=1.17.0" dependencies: - stream_chat: ^1.5.0 flutter: sdk: flutter rxdart: ^0.25.0 + stream_chat: ^1.5.0 dev_dependencies: - mockito: ^4.1.4 + fake_async: ^1.1.0 flutter_test: sdk: flutter - fake_async: ^1.1.0 - \ No newline at end of file + mockito: ^4.1.4 \ No newline at end of file diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index 40880dfc..52860c3e 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:fake_async/fake_async.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'mocks.dart'; @@ -110,17 +110,14 @@ void main() { onBackgroundEventReceived: showLocalNotificationMock, backgroundKeepAlive: const Duration(seconds: 4), child: Builder( - builder: (context) { - return Container(); - }, + builder: (context) => Container(), ), ), ); - final sc = scKey.currentState; - sc.didChangeAppLifecycleState(AppLifecycleState.paused); + scKey.currentState.didChangeAppLifecycleState(AppLifecycleState.paused); - _async.elapse(Duration(seconds: 5)); + _async.elapse(const Duration(seconds: 5)); verify(client.disconnect()).called(1); eventStreamController.close(); @@ -129,7 +126,8 @@ void main() { ); testWidgets( - 'StreamChatCore should handle notifications when on background and connected', + 'StreamChatCore should handle notifications when on background and ' + 'connected', (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); @@ -154,15 +152,12 @@ void main() { onBackgroundEventReceived: showLocalNotificationMock, backgroundKeepAlive: const Duration(seconds: 4), child: Builder( - builder: (context) { - return Container(); - }, + builder: (context) => Container(), ), ), ); - final sc = scKey.currentState; - sc.didChangeAppLifecycleState(AppLifecycleState.paused); + scKey.currentState.didChangeAppLifecycleState(AppLifecycleState.paused); final event = Event( type: EventType.messageNew, message: Message(text: 'hey'), From 537bcf1abb38d9e69f7d842a97f60ec3f67c5135 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 24 Mar 2021 00:09:00 -0500 Subject: [PATCH 054/599] fix: interceptor handlers --- .../contents.xcworkspacedata | 2 +- packages/stream_chat/example/lib/main.dart | 31 ++++++----- packages/stream_chat/example/pubspec.yaml | 9 ++-- packages/stream_chat/lib/src/client.dart | 53 ++++++++++--------- .../test/src/models/channel_state_test.dart | 2 +- 5 files changed, 49 insertions(+), 48 deletions(-) diff --git a/packages/stream_chat/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/stream_chat/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16..919434a6 100644 --- a/packages/stream_chat/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/stream_chat/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/stream_chat/example/lib/main.dart b/packages/stream_chat/example/lib/main.dart index 2d0a529f..d13dda4c 100644 --- a/packages/stream_chat/example/lib/main.dart +++ b/packages/stream_chat/example/lib/main.dart @@ -2,12 +2,9 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; Future main() async { - /// Create a new instance of [StreamChatClient] passing the apikey obtained from your - /// project dashboard. - final client = StreamChatClient( - 'b67pax5b2wdq', - logLevel: Level.INFO, - ); + /// Create a new instance of [StreamChatClient] + /// by passing the apikey obtained from your project dashboard. + final client = StreamChatClient('b67pax5b2wdq', logLevel: Level.INFO); /// Set the current user. In a production scenario, this should be done using /// a backend to generate a user token using our server SDK. @@ -21,7 +18,7 @@ Future main() async { 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', }, ), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', + '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''', ); /// Creates a channel using the type `messaging` and `godevs`. @@ -44,15 +41,16 @@ Future main() async { /// Example using Stream's Low Level Dart client. class StreamExample extends StatelessWidget { - /// To initialize this example, an instance of [client] and [channel] is required. + /// To initialize this example, an instance of + /// [client] and [channel] is required. const StreamExample({ Key key, @required this.client, @required this.channel, }) : super(key: key); - /// Instance of [StreamChatClient] we created earlier. This contains information about - /// our application and connection state. + /// Instance of [StreamChatClient] we created earlier. + /// This contains information about our application and connection state. final StreamChatClient client; /// The channel we'd like to observe and participate. @@ -104,8 +102,8 @@ class HomeScreen extends StatelessWidget { } return const Center( child: SizedBox( - width: 100.0, - height: 100.0, + width: 100, + height: 100, child: CircularProgressIndicator(), ), ); @@ -180,7 +178,7 @@ class _MessageViewState extends State { return Align( alignment: Alignment.centerRight, child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Text(item.text), ), ); @@ -188,7 +186,7 @@ class _MessageViewState extends State { return Align( alignment: Alignment.centerLeft, child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Text(item.text), ), ); @@ -197,7 +195,7 @@ class _MessageViewState extends State { ), ), Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Row( children: [ Expanded( @@ -245,7 +243,8 @@ class _MessageViewState extends State { } } -/// Helper extension for quickly retrieving the current user id from a [StreamChatClient]. +/// Helper extension for quickly retrieving +/// the current user id from a [StreamChatClient]. extension on StreamChatClient { String get uid => state.user.id; } diff --git a/packages/stream_chat/example/pubspec.yaml b/packages/stream_chat/example/pubspec.yaml index 2a8b7eb2..2d010ab6 100644 --- a/packages/stream_chat/example/pubspec.yaml +++ b/packages/stream_chat/example/pubspec.yaml @@ -1,21 +1,22 @@ name: example description: A new Flutter project. -publish_to: 'none' +publish_to: "none" version: 1.0.0+1 environment: sdk: ">=2.7.0 <3.0.0" dependencies: + cupertino_icons: ^1.0.0 flutter: sdk: flutter - cupertino_icons: ^1.0.0 - stream_chat: + stream_chat: path: ../ dev_dependencies: flutter_test: sdk: flutter + flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index bd7d05c8..6a4a77f4 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -280,8 +280,7 @@ class StreamChatClient { data: $stringData '''); - - return options; + handler.next(options); }, onError: _tokenExpiredInterceptor, ), @@ -315,33 +314,35 @@ class StreamChatClient { await connectUser(User(id: userId), newToken); try { - return await httpClient.request( - err.requestOptions.path, - cancelToken: err.requestOptions.cancelToken, - data: err.requestOptions.data, - onReceiveProgress: err.requestOptions.onReceiveProgress, - onSendProgress: err.requestOptions.onSendProgress, - queryParameters: err.requestOptions.queryParameters, - options: Options( - method: err.requestOptions.method, - sendTimeout: err.requestOptions.sendTimeout, - receiveTimeout: err.requestOptions.receiveTimeout, - extra: err.requestOptions.extra, - headers: err.requestOptions.headers, - responseType: err.requestOptions.responseType, - contentType: err.requestOptions.contentType, - validateStatus: err.requestOptions.validateStatus, - receiveDataWhenStatusError: - err.requestOptions.receiveDataWhenStatusError, - followRedirects: err.requestOptions.followRedirects, - maxRedirects: err.requestOptions.maxRedirects, - requestEncoder: err.requestOptions.requestEncoder, - responseDecoder: err.requestOptions.responseDecoder, - listFormat: err.requestOptions.listFormat, + handler.resolve( + await httpClient.request( + err.requestOptions.path, + cancelToken: err.requestOptions.cancelToken, + data: err.requestOptions.data, + onReceiveProgress: err.requestOptions.onReceiveProgress, + onSendProgress: err.requestOptions.onSendProgress, + queryParameters: err.requestOptions.queryParameters, + options: Options( + method: err.requestOptions.method, + sendTimeout: err.requestOptions.sendTimeout, + receiveTimeout: err.requestOptions.receiveTimeout, + extra: err.requestOptions.extra, + headers: err.requestOptions.headers, + responseType: err.requestOptions.responseType, + contentType: err.requestOptions.contentType, + validateStatus: err.requestOptions.validateStatus, + receiveDataWhenStatusError: + err.requestOptions.receiveDataWhenStatusError, + followRedirects: err.requestOptions.followRedirects, + maxRedirects: err.requestOptions.maxRedirects, + requestEncoder: err.requestOptions.requestEncoder, + responseDecoder: err.requestOptions.responseDecoder, + listFormat: err.requestOptions.listFormat, + ), ), ); } catch (err) { - return err; + handler.reject(err); } } } diff --git a/packages/stream_chat/test/src/models/channel_state_test.dart b/packages/stream_chat/test/src/models/channel_state_test.dart index 35dda66e..c2d69b2b 100644 --- a/packages/stream_chat/test/src/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/models/channel_state_test.dart @@ -871,7 +871,7 @@ void main() { expect(channelState.messages[0], isNotNull); expect( channelState.messages[0].createdAt, - DateTime.parse('2,020-01-29T03:23:02.843948Z'), + DateTime.parse('2020-01-29T03:23:02.843948Z'), ); expect(channelState.messages[0].user, isA()); expect(channelState.watcherCount, 5); From f1c10a451f331391e1bc4e529a4e2528199866a6 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 24 Mar 2021 00:15:54 -0500 Subject: [PATCH 055/599] chore(stream_chat_flutter_core): use local stream_chat --- .../stream_chat_flutter_core/lib/src/stream_channel.dart | 2 +- packages/stream_chat_flutter_core/pubspec.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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 c2333b05..1160433d 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -355,7 +355,7 @@ class StreamChannelState extends State { var message = snapshot.error.toString(); if (snapshot.error is DioError) { final dioError = snapshot.error as DioError; - if (dioError.type == DioErrorType.RESPONSE) { + if (dioError.type == DioErrorType.response) { message = dioError.message; } else { message = 'Check your connection and retry'; diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 5a5fc7da..b0a72691 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -10,10 +10,11 @@ environment: flutter: ">=1.17.0" dependencies: - stream_chat: ^1.5.0 + stream_chat: + path: ../stream_chat flutter: sdk: flutter - rxdart: ^0.25.0 + rxdart: ^0.26.0 dev_dependencies: mockito: ^4.1.4 @@ -21,4 +22,3 @@ dev_dependencies: sdk: flutter fake_async: ^1.1.0 pedantic: ^1.9.2 - \ No newline at end of file From 8722e4dd0e6032b656f5d5b2301ea39784dc5bbb Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 24 Mar 2021 00:28:48 -0500 Subject: [PATCH 056/599] chore(stream_chat_persistence): update deps --- .../example/pubspec.yaml | 3 ++- packages/stream_chat_persistence/pubspec.yaml | 21 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/stream_chat_persistence/example/pubspec.yaml b/packages/stream_chat_persistence/example/pubspec.yaml index faa4fcd4..2586bea6 100644 --- a/packages/stream_chat_persistence/example/pubspec.yaml +++ b/packages/stream_chat_persistence/example/pubspec.yaml @@ -11,7 +11,8 @@ dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.0 - stream_chat: ^1.4.0 + stream_chat: + path: ../../stream_chat stream_chat_persistence: path: ../ diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 12d117b1..3242efc3 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -11,17 +11,18 @@ environment: dependencies: flutter: sdk: flutter - logging: ^0.11.4 - meta: ^1.2.4 - moor: ^3.4.0 - mutex: ^2.0.0 - path: ^1.7.0 - path_provider: ^1.6.27 + logging: ^1.0.0 + meta: ^1.3.0 + moor: ^4.2.0 + mutex: ^3.0.0 + path: ^1.8.0 + path_provider: ^2.0.0 sqlite3_flutter_libs: ^0.4.0+1 - stream_chat: ^1.5.0 + stream_chat: + path: ../stream_chat dev_dependencies: build_runner: ^1.11.0 - moor_generator: ^3.4.1 - pedantic: ^1.9.2 - test: ^1.15.7 + moor_generator: ^4.2.0 + pedantic: ^1.11.0 + test: ^1.16.0 From 427be0508368662b14cfae1784bf60606562e3f4 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 24 Mar 2021 00:29:52 -0500 Subject: [PATCH 057/599] chore(stream_chat_flutter): update deps --- packages/stream_chat_flutter/pubspec.yaml | 33 ++++++++++++----------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index b17073d8..f0ed42a5 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -11,40 +11,41 @@ environment: dependencies: flutter: sdk: flutter - stream_chat_flutter_core: ^1.5.0 + stream_chat_flutter_core: + path: ../stream_chat_flutter_core photo_view: ^0.11.0 - rxdart: ^0.25.0 + rxdart: ^0.26.0 scrollable_positioned_list: ^0.1.8 jiffy: ^3.0.1 - flutter_svg: ^0.19.3 + flutter_svg: ">=0.21.0-nullsafety.0 <0.21.0" flutter_portal: ^0.3.0 - cached_network_image: ^2.5.0 + cached_network_image: ">=3.0.0-nullsafety <3.0.0" shimmer: ^1.1.2 flutter_markdown: ^0.5.2 - url_launcher: ^5.7.10 + url_launcher: ^6.0.0 emojis: ^0.9.3 video_player: ^2.0.0 chewie: ^1.0.0 - file_picker: ^2.1.5 - image_picker: ^0.6.7+17 - flutter_keyboard_visibility: ^4.0.2 + file_picker: ^3.0.0 + image_picker: ^0.7.0 + flutter_keyboard_visibility: ^5.0.0 video_compress: ^2.1.1 visibility_detector: ^0.1.5 - http_parser: ^3.1.4 - meta: ^1.2.4 - lottie: ^0.7.0+1 + http_parser: ^4.0.0 + meta: ^1.3.0 + lottie: ^1.0.0 substring_highlight: ^0.1.2 flutter_slidable: ^0.5.7 clipboard: ^0.1.2+8 image_gallery_saver: ^1.6.7 - share_plus: ^1.2.0 + share_plus: ^2.0.0 photo_manager: ^1.0.0 transparent_image: ^1.0.0 ezanimation: ^0.4.1 - synchronized: ^2.1.0 + synchronized: ^3.0.0 characters: ^1.0.0 - dio: ^3.0.10 - path_provider: ^1.6.27 + dio: ">=4.0.0-prev3 <4.0.0" + path_provider: ^2.0.0 video_thumbnail: ^0.2.5+1 flutter: @@ -58,4 +59,4 @@ dev_dependencies: flutter_test: sdk: flutter mockito: ^4.1.3 - pedantic: ^1.9.2 \ No newline at end of file + pedantic: ^1.9.2 From 3815e53f7238034c4577669a4e8cc2fe5d9ac1ef Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 24 Mar 2021 00:52:49 -0500 Subject: [PATCH 058/599] fix(stream_chat_flutter): analysis --- packages/stream_chat_flutter/lib/src/user_list_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 77537e59..5347b78a 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -210,7 +210,7 @@ class _UserListViewState extends State var message = error.toString(); if (error is DioError) { final dioError = error as DioError; - if (dioError.type == DioErrorType.RESPONSE) { + if (dioError.type == DioErrorType.response) { message = dioError.message; } else { message = 'Check your connection and retry'; From b39d5e18110f306b90925e8f38e14daaa3b5bc6c Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 24 Mar 2021 00:54:55 -0500 Subject: [PATCH 059/599] chore(stream_chat_persistence): remove deprecated isNotNull use --- .../stream_chat_persistence/lib/src/dao/message_dao.dart | 8 ++++---- .../lib/src/dao/pinned_message_dao.dart | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) 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 1b27c763..7c000e8e 100644 --- a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart @@ -77,7 +77,7 @@ class MessageDao extends DatabaseAccessor messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), ]) ..where(messages.channelCid.equals(cid)) - ..where(isNotNull(messages.parentId)) + ..where(messages.parentId.isNotNull()) ..orderBy([OrderingTerm.asc(messages.createdAt)])) .map(_messageFromJoinRow) .get()); @@ -93,7 +93,7 @@ class MessageDao extends DatabaseAccessor leftOuterJoin( _pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), ]) - ..where(isNotNull(messages.parentId)) + ..where(messages.parentId.isNotNull()) ..where(messages.parentId.equals(parentId)) ..orderBy([OrderingTerm.asc(messages.createdAt)])) .map(_messageFromJoinRow) @@ -135,8 +135,8 @@ class MessageDao extends DatabaseAccessor _pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), ]) ..where(messages.channelCid.equals(cid)) - ..where( - isNull(messages.parentId) | messages.showInChannel.equals(true)) + ..where(messages.parentId.isNotNull() | + messages.showInChannel.equals(true)) ..orderBy([OrderingTerm.asc(messages.createdAt)])) .map(_messageFromJoinRow) .get()); 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 42974520..f6c5f596 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 @@ -77,7 +77,7 @@ class PinnedMessageDao extends DatabaseAccessor pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), ]) ..where(pinnedMessages.channelCid.equals(cid)) - ..where(isNotNull(pinnedMessages.parentId)) + ..where(pinnedMessages.parentId.isNotNull()) ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) .map(_messageFromJoinRow) .get()); @@ -93,7 +93,7 @@ class PinnedMessageDao extends DatabaseAccessor leftOuterJoin(_pinnedByUsers, pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), ]) - ..where(isNotNull(pinnedMessages.parentId)) + ..where(pinnedMessages.parentId.isNotNull()) ..where(pinnedMessages.parentId.equals(parentId)) ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) .map(_messageFromJoinRow) @@ -135,7 +135,7 @@ class PinnedMessageDao extends DatabaseAccessor pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), ]) ..where(pinnedMessages.channelCid.equals(cid)) - ..where(isNull(pinnedMessages.parentId) | + ..where(pinnedMessages.parentId.isNotNull() | pinnedMessages.showInChannel.equals(true)) ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) .map(_messageFromJoinRow) From c831a50753af3253190e8eced427cf990e230715 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 24 Mar 2021 09:25:39 -0500 Subject: [PATCH 060/599] fix(stream_chat_persistence): isNotNull -> isNull --- .../stream_chat_persistence/lib/src/dao/message_dao.dart | 5 +++-- .../lib/src/dao/pinned_message_dao.dart | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) 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 7c000e8e..3b7f8c2f 100644 --- a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart @@ -135,8 +135,9 @@ class MessageDao extends DatabaseAccessor _pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), ]) ..where(messages.channelCid.equals(cid)) - ..where(messages.parentId.isNotNull() | - messages.showInChannel.equals(true)) + ..where( + messages.parentId.isNull() | messages.showInChannel.equals(true), + ) ..orderBy([OrderingTerm.asc(messages.createdAt)])) .map(_messageFromJoinRow) .get()); 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 f6c5f596..4d56ba49 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 @@ -135,7 +135,7 @@ class PinnedMessageDao extends DatabaseAccessor pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), ]) ..where(pinnedMessages.channelCid.equals(cid)) - ..where(pinnedMessages.parentId.isNotNull() | + ..where(pinnedMessages.parentId.isNull() | pinnedMessages.showInChannel.equals(true)) ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) .map(_messageFromJoinRow) From f45fcd0bc281f20fa6eaf521c5de3e0153361055 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 24 Mar 2021 09:32:32 -0500 Subject: [PATCH 061/599] chore: publish_to: none --- packages/stream_chat_flutter/pubspec.yaml | 2 ++ packages/stream_chat_flutter_core/pubspec.yaml | 2 ++ packages/stream_chat_persistence/pubspec.yaml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index c95861d3..bdf7ae26 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -5,6 +5,8 @@ version: 1.5.1 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues +publish_to: none + environment: sdk: ">=2.7.0 <3.0.0" diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index b0a72691..6e514924 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -5,6 +5,8 @@ version: 1.5.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues +publish_to: none + environment: sdk: ">=2.7.0 <3.0.0" flutter: ">=1.17.0" diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 1dc11a9c..4e81ad51 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -5,6 +5,8 @@ version: 1.5.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues +publish_to: none + environment: sdk: ">=2.7.0 <3.0.0" From 420d6384c46408d84b1402c42caa6d1ab925ad41 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 30 Mar 2021 13:46:12 +0530 Subject: [PATCH 062/599] test: add tests for stream chat flutter core (#354) * test(core): add tests for channels bloc Signed-off-by: Sahil Kumar * test(core): refactor some tests due to changes in channels bloc Signed-off-by: Sahil Kumar * test(core): add tests for channel list core Signed-off-by: Sahil Kumar * test(core): add tests for users bloc, minor improvements Signed-off-by: Sahil Kumar * test(core): add tests for user list core, minor improvements Signed-off-by: Sahil Kumar * test(core): add tests for message search bloc, refactor Signed-off-by: Sahil Kumar * test(core): add tests for message search list core, refactor Signed-off-by: Sahil Kumar * test(core): add tests for lazy load scroll view Signed-off-by: Sahil Kumar * test(core): add tests for stream chat core Signed-off-by: Sahil Kumar * test(core): add tests for message list core Signed-off-by: Sahil Kumar * test(core): add tests for stream channel Signed-off-by: Sahil Kumar * Fix merge conflicts Signed-off-by: Sahil Kumar * flutter format Signed-off-by: Sahil Kumar * fixes Signed-off-by: Sahil Kumar * update workflow Signed-off-by: Sahil Kumar * chore(core): fix analyzer issues Signed-off-by: Sahil Kumar * chore(ui-kit): fix analyzer issues Signed-off-by: Sahil Kumar --- .github/workflows/stream_flutter_workflow.yml | 4 +- packages/stream_chat/lib/src/api/channel.dart | 2 +- .../stream_chat/lib/src/api/requests.dart | 21 + .../lib/src/channel_file_display_screen.dart | 2 +- .../lib/src/channel_media_display_screen.dart | 2 +- .../lib/src/message_search_list_view.dart | 4 +- .../lib/src/user_list_view.dart | 4 +- packages/stream_chat_flutter/pubspec.yaml | 5 + .../lib/src/channel_list_core.dart | 25 +- .../lib/src/channels_bloc.dart | 40 +- .../lib/src/lazy_load_scroll_view.dart | 52 +- .../lib/src/message_list_core.dart | 43 +- .../lib/src/message_search_bloc.dart | 77 +- .../lib/src/message_search_list_core.dart | 52 +- .../lib/src/stream_channel.dart | 17 +- .../lib/src/stream_chat_core.dart | 28 +- .../lib/src/user_list_core.dart | 79 +- .../lib/src/users_bloc.dart | 27 +- .../lib/stream_chat_flutter_core.dart | 8 +- .../stream_chat_flutter_core/pubspec.yaml | 7 +- .../test/channel_list_core_test.dart | 500 +++++++++++ .../test/channels_bloc_test.dart | 847 ++++++++++++++++++ .../test/lazy_load_scroll_view_test.dart | 294 ++++++ .../test/matchers/channel_matcher.dart | 49 + .../get_message_response_matcher.dart | 55 ++ .../test/matchers/message_matcher.dart | 49 + .../test/matchers/users_matcher.dart | 47 + .../test/message_list_core_test.dart | 413 +++++++++ .../test/message_search_bloc_test.dart | 344 +++++++ .../test/message_search_list_core_test.dart | 535 +++++++++++ .../stream_chat_flutter_core/test/mocks.dart | 35 +- .../test/stream_channel_test.dart | 343 +++++++ .../test/stream_chat_core_test.dart | 371 +++++--- .../test/user_list_core_test.dart | 572 ++++++++++++ .../test/users_bloc_test.dart | 327 +++++++ 35 files changed, 4867 insertions(+), 413 deletions(-) create mode 100644 packages/stream_chat_flutter_core/test/channel_list_core_test.dart create mode 100644 packages/stream_chat_flutter_core/test/channels_bloc_test.dart create mode 100644 packages/stream_chat_flutter_core/test/lazy_load_scroll_view_test.dart create mode 100644 packages/stream_chat_flutter_core/test/matchers/channel_matcher.dart create mode 100644 packages/stream_chat_flutter_core/test/matchers/get_message_response_matcher.dart create mode 100644 packages/stream_chat_flutter_core/test/matchers/message_matcher.dart create mode 100644 packages/stream_chat_flutter_core/test/matchers/users_matcher.dart create mode 100644 packages/stream_chat_flutter_core/test/message_list_core_test.dart create mode 100644 packages/stream_chat_flutter_core/test/message_search_bloc_test.dart create mode 100644 packages/stream_chat_flutter_core/test/message_search_list_core_test.dart create mode 100644 packages/stream_chat_flutter_core/test/stream_channel_test.dart create mode 100644 packages/stream_chat_flutter_core/test/user_list_core_test.dart create mode 100644 packages/stream_chat_flutter_core/test/users_bloc_test.dart diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 91808df2..aa621854 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -59,7 +59,7 @@ jobs: test: runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 15 steps: - uses: actions/checkout@v2 with: @@ -98,7 +98,7 @@ jobs: - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_flutter_core/coverage/lcov.info - min_coverage: 4.5 + min_coverage: 90 - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_flutter/coverage/lcov.info diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index dcb11316..96fb70db 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -146,7 +146,7 @@ class Channel { /// Channel extra data Map get extraData => - state?._channelState?.channel?.extraData; + state?._channelState?.channel?.extraData ?? _extraData; /// Channel extra data as a stream Stream> get extraDataStream => diff --git a/packages/stream_chat/lib/src/api/requests.dart b/packages/stream_chat/lib/src/api/requests.dart index 764d249a..51151fca 100644 --- a/packages/stream_chat/lib/src/api/requests.dart +++ b/packages/stream_chat/lib/src/api/requests.dart @@ -104,4 +104,25 @@ class PaginationParams { lessThan: lessThan ?? this.lessThan, lessThanOrEqual: lessThanOrEqual ?? this.lessThanOrEqual, ); + + @override + int get hashCode => + runtimeType.hashCode ^ + limit.hashCode ^ + offset.hashCode ^ + greaterThan.hashCode ^ + greaterThanOrEqual.hashCode ^ + lessThan.hashCode ^ + lessThanOrEqual.hashCode; + + @override + bool operator ==(covariant PaginationParams other) => + identical(this, other) || + runtimeType == other.runtimeType && + limit == other.limit && + offset == other.offset && + greaterThan == other.greaterThan && + greaterThanOrEqual == other.greaterThanOrEqual && + lessThan == other.lessThan && + lessThanOrEqual == other.lessThanOrEqual; } diff --git a/packages/stream_chat_flutter/lib/src/channel_file_display_screen.dart b/packages/stream_chat_flutter/lib/src/channel_file_display_screen.dart index 2813f300..083b238b 100644 --- a/packages/stream_chat_flutter/lib/src/channel_file_display_screen.dart +++ b/packages/stream_chat_flutter/lib/src/channel_file_display_screen.dart @@ -143,7 +143,7 @@ class _ChannelFileDisplayScreenState extends State { } return LazyLoadScrollView( - onEndOfPage: () => messageSearchBloc.loadMore( + onEndOfPage: () => messageSearchBloc.search( filter: { 'cid': { r'$in': [StreamChannel.of(context).channel.cid] diff --git a/packages/stream_chat_flutter/lib/src/channel_media_display_screen.dart b/packages/stream_chat_flutter/lib/src/channel_media_display_screen.dart index 251143ed..7b34e449 100644 --- a/packages/stream_chat_flutter/lib/src/channel_media_display_screen.dart +++ b/packages/stream_chat_flutter/lib/src/channel_media_display_screen.dart @@ -166,7 +166,7 @@ class _ChannelMediaDisplayScreenState extends State { } return LazyLoadScrollView( - onEndOfPage: () => messageSearchBloc.loadMore( + onEndOfPage: () => messageSearchBloc.search( filter: { 'cid': { r'$in': [StreamChannel.of(context).channel.cid] 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 316e27b3..50131b87 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 @@ -262,9 +262,7 @@ class _MessageSearchListViewState extends State { ); if (widget.pullToRefresh) { child = RefreshIndicator( - onRefresh: () async { - _messageSearchListController.loadData(); - }, + onRefresh: () => _messageSearchListController.loadData(), child: child, ); } 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 77537e59..5559f67e 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -193,9 +193,7 @@ class _UserListViewState extends State return child; } else { return RefreshIndicator( - onRefresh: () async { - _userListController.loadData(); - }, + onRefresh: () => _userListController.loadData(), child: child, ); } diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 93c6e4d1..37898bcf 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -44,6 +44,11 @@ dependencies: path_provider: ^1.6.27 video_thumbnail: ^0.2.5+1 + +dependency_overrides: + stream_chat_flutter_core: + path: ../stream_chat_flutter_core + flutter: assets: - images/ diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 8a41332d..3adea312 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -130,10 +130,11 @@ class ChannelListCore extends StatefulWidget { final PaginationParams pagination; @override - _ChannelListCoreState createState() => _ChannelListCoreState(); + ChannelListCoreState createState() => ChannelListCoreState(); } -class _ChannelListCoreState extends State { +/// The current state of the [ChannelListCore]. +class ChannelListCoreState extends State { @override Widget build(BuildContext context) { final channelsBloc = ChannelsBloc.of(context); @@ -148,7 +149,7 @@ class _ChannelListCoreState extends State { stream: channelsBlocState.channelsStream, builder: (context, snapshot) { if (snapshot.hasError) { - return _buildErrorWidget(snapshot, context, channelsBlocState); + return widget.errorBuilder(context, snapshot.error); } if (!snapshot.hasData) { return widget.loadingBuilder(context); @@ -161,18 +162,7 @@ class _ChannelListCoreState extends State { }, ); - Widget _buildErrorWidget( - AsyncSnapshot> snapshot, - BuildContext context, - ChannelsBlocState channelsBlocState, - ) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); - } - - return widget.errorBuilder(context, snapshot.error); - } - + /// Fetches initial channels and updates the widget Future loadData() { final channelsBloc = ChannelsBloc.of(context); return channelsBloc.queryChannels( @@ -183,6 +173,7 @@ class _ChannelListCoreState extends State { ); } + /// Fetches more channels with updated pagination and updates the widget Future paginateData() { final channelsBloc = ChannelsBloc.of(context); return channelsBloc.queryChannels( @@ -223,9 +214,9 @@ class _ChannelListCoreState extends State { if (widget.filter?.toString() != oldWidget.filter?.toString() || jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) || + widget.options?.toString() != oldWidget.options?.toString() || widget.pagination?.toJson()?.toString() != - oldWidget.pagination?.toJson()?.toString() || - widget.options?.toString() != oldWidget.options?.toString()) { + oldWidget.pagination?.toJson()?.toString()) { loadData(); } } 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 66c2b8ea..71f0d508 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -74,10 +74,9 @@ class ChannelsBlocState extends State /// The current channel list as a stream Stream> get channelsStream => _channelsController.stream; - final BehaviorSubject _queryChannelsLoadingController = - BehaviorSubject.seeded(false); + final _queryChannelsLoadingController = BehaviorSubject.seeded(false); - final BehaviorSubject> _channelsController = BehaviorSubject(); + final _channelsController = BehaviorSubject>(); /// The stream notifying the state of queryChannel call Stream get queryChannelsLoading => @@ -91,15 +90,14 @@ class ChannelsBlocState extends State List> sortOptions, PaginationParams paginationParams, Map options, - bool onlyOffline = false, }) async { final client = StreamChatCore.of(context).client; - if (client.state?.user == null || - _queryChannelsLoadingController.value == true) { - return; + if (_queryChannelsLoadingController.value == true) return; + + if (_channelsController.hasValue) { + _queryChannelsLoadingController.add(true); } - _queryChannelsLoadingController.sink.add(true); try { final clear = paginationParams == null || @@ -115,15 +113,20 @@ class ChannelsBlocState extends State if (clear) { _channelsController.add(channels); } else { - final l = oldChannels + channels; - _channelsController.add(l); + final temp = oldChannels + channels; + _channelsController.add(temp); + } + if (_channelsController.hasValue && + _queryChannelsLoadingController.value) { + _queryChannelsLoadingController.sink.add(false); } - _queryChannelsLoadingController.sink.add(false); } - } catch (err, stackTrace) { - print(err); - print(stackTrace); - _queryChannelsLoadingController.addError(err, stackTrace); + } catch (e, stk) { + if (_channelsController.hasValue) { + _queryChannelsLoadingController.addError(e, stk); + } else { + _channelsController.addError(e, stk); + } } } @@ -139,15 +142,14 @@ class ChannelsBlocState extends State _subscriptions.add(client.on(EventType.messageNew).listen((e) { final newChannels = List.from(channels ?? []); final index = newChannels.indexWhere((c) => c.cid == e.cid); - if (index > -1) { + if (index != -1) { if (index > 0) { final channel = newChannels.removeAt(index); newChannels.insert(0, channel); } - } else if (widget.shouldAddChannel != null && - widget.shouldAddChannel(e)) { + } else if (widget.shouldAddChannel?.call(e) == true) { final hiddenIndex = _hiddenChannels.indexWhere((c) => c.cid == e.cid); - if (hiddenIndex > -1) { + if (hiddenIndex != -1) { newChannels.insert(0, _hiddenChannels[hiddenIndex]); _hiddenChannels.removeAt(hiddenIndex); } else { diff --git a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart index 725e19f3..80afb2ea 100644 --- a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart @@ -1,8 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -// ignore: constant_identifier_names -enum _LoadingStatus { LOADING, STABLE } +enum _LoadingStatus { loading, stable } /// Wrapper around a [Scrollable] which triggers [onEndOfPage]/[onStartOfPage] the Scrollable /// reaches to the start or end of the view extent. @@ -47,16 +46,16 @@ class LazyLoadScrollView extends StatefulWidget { } class _LazyLoadScrollViewState extends State { - _LoadingStatus _loadMoreStatus = _LoadingStatus.STABLE; - double _scrollPosition = 0; + _LoadingStatus _loadMoreStatus = _LoadingStatus.stable; @override - Widget build(BuildContext context) => NotificationListener( + Widget build(BuildContext context) => + NotificationListener( onNotification: _onNotification, child: widget.child, ); - bool _onNotification(Notification notification) { + bool _onNotification(ScrollNotification notification) { if (notification is ScrollStartNotification) { if (widget.onPageScrollStart != null) { widget.onPageScrollStart(); @@ -73,7 +72,7 @@ class _LazyLoadScrollViewState extends State { final pixels = notification.metrics.pixels; final maxScrollExtent = notification.metrics.maxScrollExtent; final minScrollExtent = notification.metrics.minScrollExtent; - final scrollOffset = widget.scrollOffset; + final scrollOffset = widget.scrollOffset ?? 0; if (pixels > (minScrollExtent + scrollOffset) && pixels < (maxScrollExtent - scrollOffset)) { @@ -85,28 +84,15 @@ class _LazyLoadScrollViewState extends State { final extentBefore = notification.metrics.extentBefore; final extentAfter = notification.metrics.extentAfter; - final scrollingDown = _scrollPosition < pixels; - if (scrollOffset == null || scrollOffset == 0) { - if (extentAfter == 0) { - _onEndOfPage(); - } - if (extentBefore == 0) { - _onStartOfPage(); - } - } else { - if (scrollingDown) { - if (extentAfter <= scrollOffset) { - _onEndOfPage(); - } - } else { - if (extentBefore <= scrollOffset) { - _onStartOfPage(); - } - } + if (extentAfter <= scrollOffset) { + _onEndOfPage(); + return true; + } + if (extentBefore <= scrollOffset) { + _onStartOfPage(); + return true; } - _scrollPosition = pixels; - return true; } if (notification is OverscrollNotification) { if (notification.overscroll > 0) { @@ -121,22 +107,22 @@ class _LazyLoadScrollViewState extends State { } void _onEndOfPage() { - if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.STABLE) { - _loadMoreStatus = _LoadingStatus.LOADING; + if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) { + _loadMoreStatus = _LoadingStatus.loading; if (widget.onEndOfPage != null) { widget.onEndOfPage().whenComplete(() { - _loadMoreStatus = _LoadingStatus.STABLE; + _loadMoreStatus = _LoadingStatus.stable; }); } } } void _onStartOfPage() { - if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.STABLE) { - _loadMoreStatus = _LoadingStatus.LOADING; + if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) { + _loadMoreStatus = _LoadingStatus.loading; if (widget.onStartOfPage != null) { widget.onStartOfPage().whenComplete(() { - _loadMoreStatus = _LoadingStatus.STABLE; + _loadMoreStatus = _LoadingStatus.stable; }); } } diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index a5e9587e..53844447 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -114,28 +114,28 @@ class MessageListCore extends StatefulWidget { final bool Function(Message) messageFilter; @override - _MessageListCoreState createState() => _MessageListCoreState(); + MessageListCoreState createState() => MessageListCoreState(); } -class _MessageListCoreState extends State { - StreamChannelState streamChannel; +/// The current state of the [MessageListCore]. +class MessageListCoreState extends State { + StreamChannelState _streamChannel; - bool get _upToDate => streamChannel.channel.state.isUpToDate; + bool get _upToDate => _streamChannel.channel.state.isUpToDate; bool get _isThreadConversation => widget.parentMessage != null; - OwnUser get _currentUser => streamChannel.channel.client.state.user; + OwnUser get _currentUser => _streamChannel.channel.client.state.user; - List messages = []; - bool initialMessageHighlightComplete = false; + var _messages = []; @override Widget build(BuildContext context) { final messagesStream = _isThreadConversation - ? streamChannel.channel.state.threadsStream + ? _streamChannel.channel.state.threadsStream .where((threads) => threads.containsKey(widget.parentMessage.id)) .map((threads) => threads[widget.parentMessage.id]) - : streamChannel.channel.state?.messagesStream; + : _streamChannel.channel.state?.messagesStream; bool defaultFilter(Message m) { final isMyMessage = m.user.id == _currentUser.id; @@ -148,10 +148,10 @@ class _MessageListCoreState extends State { stream: messagesStream?.map((messages) => messages?.where(widget.messageFilter ?? defaultFilter)?.toList()), builder: (context, snapshot) { - if (!snapshot.hasData) { - return widget.loadingBuilder(context); - } else if (snapshot.hasError) { + if (snapshot.hasError) { return widget.errorWidgetBuilder(context, snapshot.error); + } else if (!snapshot.hasData) { + return widget.loadingBuilder(context); } else { final messageList = snapshot.data?.reversed?.toList() ?? []; if (messageList.isEmpty && !_isThreadConversation) { @@ -159,29 +159,32 @@ class _MessageListCoreState extends State { return widget.emptyBuilder(context); } } else { - messages = messageList; + _messages = messageList; } - return widget.messageListBuilder(context, messages); + return widget.messageListBuilder(context, _messages); } }, ); } + /// Fetches more messages with updated pagination and updates the widget. + /// + /// Optionally pass the fetch direction, defaults to [QueryDirection.bottom] Future paginateData( {QueryDirection direction = QueryDirection.bottom}) { if (!_isThreadConversation) { - return streamChannel.queryMessages(direction: direction); + return _streamChannel.queryMessages(direction: direction); } else { - return streamChannel.getReplies(widget.parentMessage.id); + return _streamChannel.getReplies(widget.parentMessage.id); } } @override void initState() { - streamChannel = StreamChannel.of(context); + _streamChannel = StreamChannel.of(context); if (_isThreadConversation) { - streamChannel.getReplies(widget.parentMessage.id); + _streamChannel.getReplies(widget.parentMessage.id); } if (widget.messageListController != null) { @@ -194,7 +197,7 @@ class _MessageListCoreState extends State { @override void dispose() { if (!_upToDate) { - streamChannel.reloadChannel(); + _streamChannel.reloadChannel(); } super.dispose(); } @@ -203,5 +206,5 @@ class _MessageListCoreState extends State { /// Controller used for paginating data in [ChannelListView] class MessageListController { /// Call this function to load further data - Function({QueryDirection direction}) paginateData; + Future Function({QueryDirection direction}) paginateData; } 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 cf1e8d19..1c5c78de 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 @@ -48,17 +48,16 @@ class MessageSearchBlocState extends State Stream> get messagesStream => _messageResponses.stream; - final BehaviorSubject> _messageResponses = - BehaviorSubject(); + final _messageResponses = BehaviorSubject>(); - final BehaviorSubject _queryMessagesLoadingController = - BehaviorSubject.seeded(false); + final _queryMessagesLoadingController = BehaviorSubject.seeded(false); /// The stream notifying the state of queryUsers call Stream get queryMessagesLoading => _queryMessagesLoadingController.stream; - /// Calls [StreamChatClient.search] updating [messageResponses] stream + /// Calls [StreamChatClient.search] updating + /// [messagesStream] and [queryMessagesLoading] stream Future search({ Map filter, Map messageFilter, @@ -66,33 +65,13 @@ class MessageSearchBlocState extends State String query, PaginationParams pagination, }) async { - _messageResponses.add(null); - try { - final messages = await _search( - filter: filter, - messageFilter: messageFilter, - sort: sort, - query: query, - pagination: pagination, - ); - _messageResponses.add(messages.results); - } catch (err, stk) { - _messageResponses.addError(err, stk); - } - } + final client = StreamChatCore.of(context).client; - /// Calls [StreamChatClient.search] updating [queryMessagesLoading] stream - Future loadMore({ - Map filter, - Map messageFilter, - List sort, - String query, - PaginationParams pagination, - }) async { - if (_queryMessagesLoadingController.value == true) { - return; + if (_queryMessagesLoadingController.value == true) return; + + if (_messageResponses.hasValue) { + _queryMessagesLoadingController.add(true); } - _queryMessagesLoadingController.add(true); try { final clear = pagination == null || pagination.offset == null || @@ -100,12 +79,12 @@ class MessageSearchBlocState extends State final oldMessages = List.from(messageResponses ?? []); - final messages = await _search( - filter: filter, - messageFilter: messageFilter, + final messages = await client.search( + filter, sort: sort, query: query, - pagination: pagination, + paginationParams: pagination, + messageFilters: messageFilter, ); if (clear) { @@ -114,30 +93,18 @@ class MessageSearchBlocState extends State final temp = oldMessages + messages.results; _messageResponses.add(temp); } - - _queryMessagesLoadingController.add(false); - } catch (err, stackTrace) { - _queryMessagesLoadingController.addError(err, stackTrace); + if (_messageResponses.hasValue && _queryMessagesLoadingController.value) { + _queryMessagesLoadingController.add(false); + } + } catch (e, stk) { + if (_messageResponses.hasValue) { + _queryMessagesLoadingController.addError(e, stk); + } else { + _messageResponses.addError(e, stk); + } } } - Future _search({ - Map filter, - Map messageFilter, - List sort, - String query, - PaginationParams pagination, - }) { - final client = StreamChatCore.of(context).client; - return client.search( - filter, - sort: sort, - query: query, - paginationParams: pagination, - messageFilters: messageFilter, - ); - } - @override Widget build(BuildContext context) { super.build(context); diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index 5c4e74fe..72c5ea86 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -104,21 +104,15 @@ class MessageSearchListCore extends StatefulWidget { final WidgetBuilder loadingBuilder; @override - _MessageSearchListCoreState createState() => _MessageSearchListCoreState(); + MessageSearchListCoreState createState() => MessageSearchListCoreState(); } -class _MessageSearchListCoreState extends State { +/// The current state of the [MessageSearchListCore]. +class MessageSearchListCoreState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - MessageSearchBloc.of(context).search( - filter: widget.filters, - sort: widget.sortOptions, - query: widget.messageQuery, - pagination: widget.paginationParams, - messageFilter: widget.messageFilters, - ); - + loadData(); if (widget.messageSearchListController != null) { widget.messageSearchListController.loadData = loadData; widget.messageSearchListController.paginateData = paginateData; @@ -136,29 +130,23 @@ class _MessageSearchListCoreState extends State { stream: messageSearchBloc.messagesStream, builder: (context, snapshot) { if (snapshot.hasError) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); - } - return widget.errorBuilder(context, snapshot.error); } - if (!snapshot.hasData) { return widget.loadingBuilder(context); } - final items = snapshot.data; - if (items.isEmpty) { return widget.emptyBuilder(context); } - return widget.childBuilder(snapshot.data); }, ); - void loadData() { - MessageSearchBloc.of(context).search( + /// Fetches initial messages and updates the widget + Future loadData() { + final messageSearchBloc = MessageSearchBloc.of(context); + return messageSearchBloc.search( filter: widget.filters, sort: widget.sortOptions, query: widget.messageQuery, @@ -167,10 +155,10 @@ class _MessageSearchListCoreState extends State { ); } - void paginateData() { + /// Fetches more messages with updated pagination and updates the widget + Future paginateData() { final messageSearchBloc = MessageSearchBloc.of(context); - - messageSearchBloc.loadMore( + return messageSearchBloc.search( filter: widget.filters, sort: widget.sortOptions, pagination: widget.paginationParams.copyWith( @@ -186,18 +174,12 @@ class _MessageSearchListCoreState extends State { super.didUpdateWidget(oldWidget); if (widget.filters?.toString() != oldWidget.filters?.toString() || jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) || - widget.paginationParams?.toJson()?.toString() != - oldWidget.paginationParams?.toJson()?.toString() || widget.messageQuery?.toString() != oldWidget.messageQuery?.toString() || widget.messageFilters?.toString() != - oldWidget.messageFilters?.toString()) { - MessageSearchBloc.of(context).search( - filter: widget.filters, - sort: widget.sortOptions, - query: widget.messageQuery, - pagination: widget.paginationParams, - messageFilter: widget.messageFilters, - ); + oldWidget.messageFilters?.toString() || + widget.paginationParams?.toJson()?.toString() != + oldWidget.paginationParams?.toJson()?.toString()) { + loadData(); } } } @@ -205,8 +187,8 @@ class _MessageSearchListCoreState extends State { /// Controller used for paginating data in [ChannelListView] class MessageSearchListController { /// Call this function to reload data - VoidCallback loadData; + AsyncCallback loadData; /// Call this function to load further data - VoidCallback paginateData; + AsyncCallback paginateData; } 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 05158c94..8f86fca4 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -207,15 +207,14 @@ class StreamChannelState extends State { int after = 20, bool preferOffline = false, }) => - queryAtMessage( + _queryAtMessage( messageId: messageId, before: before, after: after, preferOffline: preferOffline, ); - /// - Future queryAtMessage({ + Future _queryAtMessage({ String messageId, int before = 20, int after = 20, @@ -297,7 +296,7 @@ class StreamChannelState extends State { } /// Reloads the channel with latest message - Future reloadChannel() => queryAtMessage(before: 30); + Future reloadChannel() => _queryAtMessage(before: 30); List> _futures; @@ -305,8 +304,7 @@ class StreamChannelState extends State { try { await loadChannelAtMessage(initialMessageId); return true; - } catch (e, stk) { - print('Error: $e\nStack: $stk'); + } catch (_) { rethrow; } } @@ -349,9 +347,6 @@ class StreamChannelState extends State { ], builder: (context, snapshot) { if (snapshot.hasError) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); - } var message = snapshot.error.toString(); if (snapshot.error is DioError) { final dioError = snapshot.error as DioError; @@ -361,9 +356,7 @@ class StreamChannelState extends State { message = 'Check your connection and retry'; } } - return Center( - child: Text(message), - ); + return Center(child: Text(message)); } final initialized = snapshot.data[0]; // ignore: avoid_bool_literals_in_conditional_expressions diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index 83699cc1..15c2ddf3 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -93,10 +93,10 @@ class StreamChatCoreState extends State Widget build(BuildContext context) => widget.child; /// The current user - User get user => widget.client.state.user; + User get user => client.state?.user; /// The current user as a stream - Stream get userStream => widget.client.state.userStream; + Stream get userStream => client.state?.userStream; @override void initState() { @@ -108,21 +108,25 @@ class StreamChatCoreState extends State @override void didChangeAppLifecycleState(AppLifecycleState state) { - if (client.state?.user != null) { + if (user != null) { if (state == AppLifecycleState.paused) { - if (widget.onBackgroundEventReceived != null) { - _eventSubscription = - client.on().listen(widget.onBackgroundEventReceived); - _disconnectTimer = Timer( - widget.backgroundKeepAlive, - client.disconnect, - ); - } else { + if (widget.onBackgroundEventReceived == null) { + client.disconnect(); + return; + } + _eventSubscription = client.on().listen( + widget.onBackgroundEventReceived, + ); + + void onTimerComplete() { + _eventSubscription.cancel(); client.disconnect(); } + + _disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete); } else if (state == AppLifecycleState.resumed) { - _eventSubscription?.cancel(); if (_disconnectTimer?.isActive == true) { + _eventSubscription.cancel(); _disconnectTimer.cancel(); } else { if (client.wsConnectionStatus == ConnectionStatus.disconnected) { diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 61169483..f1bd68ee 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -80,7 +80,7 @@ class UserListCore extends StatefulWidget { final UserListController userListController; /// The builder that will be used in case of error - final Widget Function(Error error) errorBuilder; + final Widget Function(Object error) errorBuilder; /// The builder that will be used to build the list final Widget Function(BuildContext context, List users) listBuilder; @@ -120,21 +120,16 @@ class UserListCore extends StatefulWidget { final bool groupAlphabetically; @override - _UserListCoreState createState() => _UserListCoreState(); + UserListCoreState createState() => UserListCoreState(); } -class _UserListCoreState extends State +/// The current state of the [UserListCore]. +class UserListCoreState extends State with WidgetsBindingObserver { @override void didChangeDependencies() { super.didChangeDependencies(); - UsersBloc.of(context).queryUsers( - filter: widget.filter, - sort: widget.sort, - pagination: widget.pagination, - options: widget.options, - ); - + loadData(); if (widget.userListController != null) { widget.userListController.loadData = loadData; widget.userListController.paginateData = paginateData; @@ -144,11 +139,10 @@ class _UserListCoreState extends State @override Widget build(BuildContext context) { final _usersBloc = UsersBloc.of(context); - return _buildListView(_usersBloc); } - bool get isListAlreadySorted => + bool get _isListAlreadySorted => widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false; Stream> _buildUserStream( @@ -158,7 +152,7 @@ class _UserListCoreState extends State (users) { if (widget.groupAlphabetically) { var temp = users; - if (!isListAlreadySorted) { + if (!_isListAlreadySorted) { temp = users ..sort((curr, next) => curr.name.compareTo(next.name)); } @@ -186,33 +180,23 @@ class _UserListCoreState extends State stream: _buildUserStream(usersBlocState), builder: (context, snapshot) { if (snapshot.hasError) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); - } - return widget.errorBuilder(snapshot.error); } - if (!snapshot.hasData) { return widget.loadingBuilder(context); } - final items = snapshot.data; - if (items.isEmpty) { return widget.emptyBuilder(context); } - - if (items.isEmpty) { - return widget.emptyBuilder(context); - } - return widget.listBuilder(context, items); }, ); - void loadData() { - UsersBloc.of(context).queryUsers( + // ignore: public_member_api_docs + Future loadData() { + final _usersBloc = UsersBloc.of(context); + return _usersBloc.queryUsers( filter: widget.filter, sort: widget.sort, pagination: widget.pagination, @@ -220,10 +204,10 @@ class _UserListCoreState extends State ); } - void paginateData() { + // ignore: public_member_api_docs + Future paginateData() { final _usersBloc = UsersBloc.of(context); - - _usersBloc.queryUsers( + return _usersBloc.queryUsers( filter: widget.filter, sort: widget.sort, pagination: widget.pagination.copyWith( @@ -238,15 +222,10 @@ class _UserListCoreState extends State super.didUpdateWidget(oldWidget); if (widget.filter?.toString() != oldWidget.filter?.toString() || jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) || + widget.options?.toString() != oldWidget.options?.toString() || widget.pagination?.toJson()?.toString() != - oldWidget.pagination?.toJson()?.toString() || - widget.options?.toString() != oldWidget.options?.toString()) { - UsersBloc.of(context).queryUsers( - filter: widget.filter, - sort: widget.sort, - pagination: widget.pagination, - options: widget.options, - ); + oldWidget.pagination?.toJson()?.toString()) { + loadData(); } } } @@ -255,11 +234,11 @@ class _UserListCoreState extends State /// Header items are prefixed with the key `HEADER` While users are prefixed /// with `USER`. abstract class ListItem { - // ignore: public_member_api_docs + /// Unique key per list item String get key { if (this is ListHeaderItem) { final header = (this as ListHeaderItem).heading; - return 'HEADER-$header'; + return 'HEADER-${header.toLowerCase()}'; } if (this is ListUserItem) { final user = (this as ListUserItem).user; @@ -268,7 +247,8 @@ abstract class ListItem { return null; } - // ignore: public_member_api_docs + /// Helper function to build widget based on ListItem type + // ignore: missing_return Widget when({ @required Widget Function(String heading) headerItem, @required Widget Function(User user) userItem, @@ -279,33 +259,32 @@ abstract class ListItem { if (this is ListUserItem) { return userItem((this as ListUserItem).user); } - return const SizedBox(); } } -// ignore: public_member_api_docs +/// Header Item class ListHeaderItem extends ListItem { - // ignore: public_member_api_docs + /// Constructs a new [ListHeaderItem] ListHeaderItem(this.heading); - // ignore: public_member_api_docs + /// Heading used to build the item. final String heading; } -// ignore: public_member_api_docs +/// User Item class ListUserItem extends ListItem { - // ignore: public_member_api_docs + /// Constructs a new [ListUserItem] ListUserItem(this.user); - // ignore: public_member_api_docs + /// [User] used to build the item. final User user; } /// Controller used for paginating data in [ChannelListView] class UserListController { /// Call this function to reload data - VoidCallback loadData; + AsyncCallback loadData; /// Call this function to load further data - VoidCallback paginateData; + AsyncCallback paginateData; } 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 f10a5aae..92881619 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -51,10 +51,9 @@ class UsersBlocState extends State /// The current users list as a stream Stream> get usersStream => _usersController.stream; - final BehaviorSubject> _usersController = BehaviorSubject(); + final _usersController = BehaviorSubject>(); - final BehaviorSubject _queryUsersLoadingController = - BehaviorSubject.seeded(false); + final _queryUsersLoadingController = BehaviorSubject.seeded(false); /// The stream notifying the state of queryUsers call Stream get queryUsersLoading => _queryUsersLoadingController.stream; @@ -70,11 +69,12 @@ class UsersBlocState extends State }) async { final client = StreamChatCore.of(context).client; - if (client.state?.user == null || - _queryUsersLoadingController.value == true) { - return; + if (_queryUsersLoadingController.value == true) return; + + if (_usersController.hasValue) { + _queryUsersLoadingController.add(true); } - _queryUsersLoadingController.add(true); + try { final clear = pagination == null || pagination.offset == null || @@ -95,10 +95,15 @@ class UsersBlocState extends State final temp = oldUsers + usersResponse.users; _usersController.add(temp); } - - _queryUsersLoadingController.add(false); - } catch (err, stackTrace) { - _queryUsersLoadingController.addError(err, stackTrace); + if (_usersController.hasValue && _queryUsersLoadingController.value) { + _queryUsersLoadingController.add(false); + } + } catch (e, stk) { + if (_usersController.hasValue) { + _queryUsersLoadingController.addError(e, stk); + } else { + _usersController.addError(e, stk); + } } } diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index b62101d1..01463de0 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -2,14 +2,14 @@ library stream_chat_flutter_core; export 'package:stream_chat/stream_chat.dart'; -export 'src/channel_list_core.dart'; +export 'src/channel_list_core.dart' hide ChannelListCoreState; export 'src/channels_bloc.dart'; export 'src/lazy_load_scroll_view.dart'; -export 'src/message_list_core.dart'; +export 'src/message_list_core.dart' hide MessageListCoreState; export 'src/message_search_bloc.dart'; -export 'src/message_search_list_core.dart'; +export 'src/message_search_list_core.dart' hide MessageSearchListCoreState; export 'src/stream_channel.dart'; export 'src/stream_chat_core.dart'; export 'src/typedef.dart'; -export 'src/user_list_core.dart'; +export 'src/user_list_core.dart' hide UserListCoreState; export 'src/users_bloc.dart'; diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 09643899..cf8f9b00 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -15,8 +15,11 @@ dependencies: rxdart: ^0.25.0 stream_chat: ^1.5.0 +dependency_overrides: + stream_chat: + path: ../stream_chat + dev_dependencies: - fake_async: ^1.1.0 flutter_test: sdk: flutter - mockito: ^4.1.4 \ No newline at end of file + mockito: ^4.1.3 \ No newline at end of file diff --git a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart new file mode 100644 index 00000000..bf24e591 --- /dev/null +++ b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart @@ -0,0 +1,500 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/widgets.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter_core/src/channel_list_core.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +import 'mocks.dart'; + +void main() { + const pagination = PaginationParams(offset: 0, limit: 25); + + List _generateChannels( + StreamChatClient client, { + int count = 3, + int offset = 0, + }) { + return List.generate( + count, + (index) { + index = index + offset; + return Channel( + client, + 'testType$index', + 'testId$index', + {'extra_data_key': 'extra_data_value_$index'}, + ); + }, + ); + } + + test( + 'should throw assertion error in case listBuilder is null', + () { + final channelListCore = () => ChannelListCore( + listBuilder: null, + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + expect(channelListCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case loadingBuilder is null', + () { + final channelListCore = () => ChannelListCore( + listBuilder: (_, __) => Offstage(), + loadingBuilder: null, + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + expect(channelListCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case emptyBuilder is null', + () { + final channelListCore = () => ChannelListCore( + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: null, + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + expect(channelListCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case errorBuilder is null', + () { + final channelListCore = () => ChannelListCore( + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: null, + ); + expect(channelListCore, throwsA(isA())); + }, + ); + + testWidgets( + 'should throw if ChannelListCore is used where ChannelsBloc is not present ' + 'in the widget tree', + (tester) async { + const channelListCoreKey = Key('channelListCore'); + final channelListCore = ChannelListCore( + key: channelListCoreKey, + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + + await tester.pumpWidget(channelListCore); + + expect(find.byKey(channelListCoreKey), findsNothing); + expect(tester.takeException(), isInstanceOf()); + }, + ); + + testWidgets( + 'should render ChannelListCore if used with ChannelsBloc as an ancestor', + (tester) async { + const channelListCoreKey = Key('channelListCore'); + final channelListCore = ChannelListCore( + key: channelListCoreKey, + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + + final mockClient = MockClient(); + + when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: ChannelsBloc( + child: channelListCore, + ), + ), + ); + + expect(find.byKey(channelListCoreKey), findsOneWidget); + }, + ); + + testWidgets( + 'should assign loadData and paginateData callback to ' + 'ChannelListController if passed', + (tester) async { + const channelListCoreKey = Key('channelListCore'); + final controller = ChannelListController(); + final channelListCore = ChannelListCore( + key: channelListCoreKey, + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + channelListController: controller, + ); + + expect(controller.loadData, isNull); + expect(controller.paginateData, isNull); + + final mockClient = MockClient(); + + when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: ChannelsBloc( + child: channelListCore, + ), + ), + ); + + expect(find.byKey(channelListCoreKey), findsOneWidget); + expect(controller.loadData, isNotNull); + expect(controller.paginateData, isNotNull); + }, + ); + + testWidgets( + 'should build error widget if channelsBlocState.channelsStream emits error', + (tester) async { + const channelListCoreKey = Key('channelListCore'); + const errorWidgetKey = Key('errorWidget'); + final channelListCore = ChannelListCore( + key: channelListCoreKey, + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => + Container(key: errorWidgetKey), + ); + + final mockClient = MockClient(); + + when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + + const error = 'Error! Error! Error!'; + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: pagination, + )).thenThrow(error); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: ChannelsBloc( + child: channelListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(errorWidgetKey), findsOneWidget); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: pagination, + )).called(1); + }, + ); + + testWidgets( + 'should build empty widget if channelsBlocState.channelsStream emits empty data', + (tester) async { + const channelListCoreKey = Key('channelListCore'); + const emptyWidgetKey = Key('emptyWidget'); + final channelListCore = ChannelListCore( + key: channelListCoreKey, + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Container(key: emptyWidgetKey), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + + final mockClient = MockClient(); + + when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + + const channels = []; + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: pagination, + )).thenAnswer((_) => Stream.value(channels)); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: ChannelsBloc( + child: channelListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(emptyWidgetKey), findsOneWidget); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: pagination, + )).called(1); + }, + ); + + testWidgets( + 'should build list widget if channelsBlocState.channelsStream emits some data', + (tester) async { + const channelListCoreKey = Key('channelListCore'); + const listWidgetKey = Key('listWidget'); + final channelListCore = ChannelListCore( + key: channelListCoreKey, + listBuilder: (_, __) => Container(key: listWidgetKey), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + + final mockClient = MockClient(); + + when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + + final channels = _generateChannels(mockClient); + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: pagination, + )).thenAnswer((_) => Stream.value(channels)); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: ChannelsBloc( + child: channelListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(listWidgetKey), findsOneWidget); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: pagination, + )).called(1); + }, + ); + + testWidgets( + 'should build list widget with paginated data ' + 'on calling channelListCoreState.paginateData', + (tester) async { + const channelListCoreKey = Key('channelListCore'); + const listWidgetKey = Key('listWidget'); + final channelListCore = ChannelListCore( + key: channelListCoreKey, + listBuilder: (_, channels) { + return Container( + key: listWidgetKey, + child: Text( + channels.map((e) => e.cid).join(','), + ), + ); + }, + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + + final mockClient = MockClient(); + + when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + + final channels = _generateChannels(mockClient); + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: pagination, + )).thenAnswer((_) => Stream.value(channels)); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StreamChatCore( + client: mockClient, + child: ChannelsBloc( + child: channelListCore, + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(listWidgetKey), findsOneWidget); + expect(find.text(channels.map((e) => e.cid).join(',')), findsOneWidget); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: pagination, + )).called(1); + + final channelListCoreState = tester.state( + find.byKey(channelListCoreKey), + ); + + final offset = channels.length; + final paginatedChannels = _generateChannels(mockClient, offset: offset); + final updatedPagination = pagination.copyWith(offset: offset); + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: updatedPagination, + )).thenAnswer((_) => Stream.value(paginatedChannels)); + + await channelListCoreState.paginateData(); + + await tester.pumpAndSettle(); + + expect(find.byKey(listWidgetKey), findsOneWidget); + expect( + find.text([ + ...channels, + ...paginatedChannels, + ].map((e) => e.cid).join(',')), + findsOneWidget, + ); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: updatedPagination, + )).called(1); + }, + ); + + testWidgets( + 'should rebuild ChannelListCore with updated widget data ' + 'on calling setState()', + (tester) async { + StateSetter _stateSetter; + int limit = pagination.limit; + + const channelListCoreKey = Key('channelListCore'); + const listWidgetKey = Key('listWidget'); + + ChannelListCore channelListCoreBuilder(int limit) => ChannelListCore( + key: channelListCoreKey, + listBuilder: (_, channels) { + return Container( + key: listWidgetKey, + child: Text( + channels.map((e) => e.cid).join(','), + ), + ); + }, + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + pagination: pagination.copyWith(limit: limit), + ); + + final mockClient = MockClient(); + + when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + + final channels = _generateChannels(mockClient); + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: pagination, + )).thenAnswer((_) => Stream.value(channels)); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StreamChatCore( + client: mockClient, + child: ChannelsBloc( + child: StatefulBuilder(builder: (context, stateSetter) { + // Assigning stateSetter for rebuilding ChannelListCore + _stateSetter = stateSetter; + return channelListCoreBuilder(limit); + }), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(listWidgetKey), findsOneWidget); + expect(find.text(channels.map((e) => e.cid).join(',')), findsOneWidget); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: pagination, + )).called(1); + + // Rebuilding ChannelListCore with new pagination limit + _stateSetter(() => limit = 6); + + final updatedChannels = _generateChannels(mockClient, count: limit); + final updatedPagination = pagination.copyWith(limit: limit); + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: updatedPagination, + )).thenAnswer((_) => Stream.value(updatedChannels)); + + await tester.pumpAndSettle(); + + expect(find.byKey(listWidgetKey), findsOneWidget); + expect( + find.text(updatedChannels.map((e) => e.cid).join(',')), + findsOneWidget, + ); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: updatedPagination, + )).called(1); + }, + ); +} diff --git a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart new file mode 100644 index 00000000..e021e9b7 --- /dev/null +++ b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart @@ -0,0 +1,847 @@ +import 'dart:async'; + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +import 'matchers/channel_matcher.dart'; +import 'mocks.dart'; + +void main() { + List _generateChannels( + StreamChatClient client, { + int count = 3, + int offset = 0, + }) { + return List.generate( + count, + (index) { + index = index + offset; + return Channel( + client, + 'testType$index', + 'testId$index', + {'extra_data_key': 'extra_data_value_$index'}, + ); + }, + ); + } + + test( + 'should throw assertion error if child is null', + () async { + const channelsBlocKey = Key('channelsBloc'); + final channelsBloc = () => ChannelsBloc( + key: channelsBlocKey, + child: null, + ); + expect(channelsBloc, throwsA(isA())); + }, + ); + + testWidgets( + 'should throw if ChannelsBloc is used where StreamChat is not present in the widget tree', + (tester) async { + const channelsBlocKey = Key('channelsBloc'); + const childKey = Key('child'); + final channelsBloc = ChannelsBloc( + key: channelsBlocKey, + child: Offstage(key: childKey), + ); + + await tester.pumpWidget(channelsBloc); + + expect(find.byKey(channelsBlocKey), findsNothing); + expect(find.byKey(childKey), findsNothing); + expect(tester.takeException(), isInstanceOf()); + }, + ); + + testWidgets( + 'should render ChannelsBloc if used with StreamChatCore as an ancestor', + (tester) async { + const channelsBlocKey = Key('channelsBloc'); + const childKey = Key('child'); + final channelsBloc = ChannelsBloc( + key: channelsBlocKey, + child: Offstage(key: childKey), + ); + + final mockClient = MockClient(); + + when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: channelsBloc, + ), + ); + + expect(find.byKey(channelsBlocKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + }, + ); + + testWidgets( + 'channelsBlocState.queryChannels() should emit data through channelsStream', + (tester) async { + const channelsBlocKey = Key('channelsBloc'); + const childKey = Key('child'); + final channelsBloc = ChannelsBloc( + key: channelsBlocKey, + child: Builder( + key: childKey, + builder: (context) { + return Offstage(); + }, + ), + ); + + final mockClient = MockClient(); + + when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: channelsBloc, + ), + ); + + final channelsBlocState = tester.state( + find.byKey(channelsBlocKey), + ); + + final offlineChannels = _generateChannels(mockClient); + final onlineChannels = _generateChannels(mockClient, offset: 3); + + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer( + (_) => Stream.fromIterable([offlineChannels, onlineChannels]), + ); + + channelsBlocState.queryChannels(); + + await expectLater( + channelsBlocState.channelsStream, + emitsInOrder([ + isSameChannelListAs(offlineChannels), + isSameChannelListAs(onlineChannels), + ]), + ); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + }, + ); + + testWidgets( + 'channelsBlocState.channelsStream should emit error ' + 'if client.queryChannels() throws', + (tester) async { + const channelsBlocKey = Key('channelsBloc'); + const childKey = Key('child'); + final channelsBloc = ChannelsBloc( + key: channelsBlocKey, + child: Builder( + key: childKey, + builder: (context) { + return Offstage(); + }, + ), + ); + + final mockClient = MockClient(); + + when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: channelsBloc, + ), + ); + + final channelsBlocState = tester.state( + find.byKey(channelsBlocKey), + ); + + final error = 'Error! Error! Error!'; + + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).thenThrow(error); + + channelsBlocState.queryChannels(); + + await expectLater( + channelsBlocState.channelsStream, + emitsError(error), + ); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + }, + ); + + testWidgets( + 'calling channelsBlocState.queryChannels() again with an offset ' + 'should emit new data through channelsStream and also emit loading state ' + 'through queryChannelsLoading', + (tester) async { + const channelsBlocKey = Key('channelsBloc'); + final channelsBloc = ChannelsBloc( + key: channelsBlocKey, + child: Offstage(), + ); + + final mockClient = MockClient(); + + when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: channelsBloc, + ), + ); + + final channelsBlocState = tester.state( + find.byKey(channelsBlocKey), + ); + + final channels = _generateChannels(mockClient); + + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer((_) => Stream.value(channels)); + + channelsBlocState.queryChannels(); + + await expectLater( + channelsBlocState.channelsStream, + emits(isSameChannelListAs(channels)), + ); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + + final offset = channels.length; + final paginationParams = PaginationParams(offset: offset); + + final newChannels = _generateChannels(mockClient, offset: offset); + + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: paginationParams, + )).thenAnswer( + (_) => Stream.value(newChannels), + ); + + channelsBlocState.queryChannels(paginationParams: paginationParams); + + await Future.wait([ + expectLater( + channelsBlocState.queryChannelsLoading, + emitsInOrder([true, false]), + ), + expectLater( + channelsBlocState.channelsStream, + emits(isSameChannelListAs(channels + newChannels)), + ), + ]); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: paginationParams, + )).called(1); + }, + ); + + testWidgets( + 'calling channelsBlocState.queryChannels() again with an offset ' + 'should emit error through queryChannelsLoading if ' + 'client.queryChannels() throws', + (tester) async { + const channelsBlocKey = Key('channelsBloc'); + final channelsBloc = ChannelsBloc( + key: channelsBlocKey, + child: Offstage(), + ); + + final mockClient = MockClient(); + + when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: channelsBloc, + ), + ); + + final channelsBlocState = tester.state( + find.byKey(channelsBlocKey), + ); + + final channels = _generateChannels(mockClient); + + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer((_) => Stream.value(channels)); + + channelsBlocState.queryChannels(); + + await expectLater( + channelsBlocState.channelsStream, + emits(isSameChannelListAs(channels)), + ); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + + final offset = channels.length; + final paginationParams = PaginationParams(offset: offset); + + final error = 'Error! Error! Error!'; + + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: paginationParams, + )).thenThrow(error); + + channelsBlocState.queryChannels(paginationParams: paginationParams); + + await expectLater( + channelsBlocState.queryChannelsLoading, + emitsError(error), + ); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: paginationParams, + )).called(1); + }, + ); + + group('event controller test', () { + StreamController eventController; + setUp(() { + eventController = StreamController.broadcast(); + }); + + testWidgets( + 'channel should get hide when EventType.channelHidden event is received', + (tester) async { + final mockClient = MockClient(); + const channelsBlocKey = Key('channelsBloc'); + final channelsBloc = ChannelsBloc( + key: channelsBlocKey, + child: Offstage(), + ); + + when(mockClient.on(any, any, any, any)) + .thenAnswer((_) => Stream.empty()); + + when(mockClient.on( + EventType.channelHidden, + )).thenAnswer((_) => eventController.stream); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: channelsBloc, + ), + ); + + final channelsBlocState = tester.state( + find.byKey(channelsBlocKey), + ); + + final channels = _generateChannels(mockClient); + + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer( + (_) => Stream.value(channels), + ); + + await channelsBlocState.queryChannels(); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + + final channelHiddenEvent = Event( + type: EventType.channelHidden, + cid: channels.first.cid, + ); + + eventController.add(channelHiddenEvent); + + final newChannels = [...channels] + ..removeWhere((it) => it.cid == channelHiddenEvent.cid); + + await expectLater( + channelsBlocState.channelsStream, + emitsInOrder([ + isSameChannelListAs(channels), + isSameChannelListAs(newChannels), + ]), + ); + + verify(mockClient.on(EventType.channelHidden)).called(1); + }, + ); + + testWidgets( + 'channel should get removed when EventType.channelDeleted or ' + 'EventType.notificationRemovedFromChannel, event is received', + (tester) async { + final mockClient = MockClient(); + const channelsBlocKey = Key('channelsBloc'); + final channelsBloc = ChannelsBloc( + key: channelsBlocKey, + child: Offstage(), + ); + + when(mockClient.on(any, any, any, any)) + .thenAnswer((_) => Stream.empty()); + + when(mockClient.on( + EventType.channelDeleted, + EventType.notificationRemovedFromChannel, + )).thenAnswer((_) => eventController.stream); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: channelsBloc, + ), + ); + + final channelsBlocState = tester.state( + find.byKey(channelsBlocKey), + ); + + final channels = _generateChannels(mockClient); + + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer( + (_) => Stream.value(channels), + ); + + await channelsBlocState.queryChannels(); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + + final channelDeletedOrNotificationRemovedEvent = Event( + channel: EventChannel(cid: channels.first.cid), + ); + + eventController.add(channelDeletedOrNotificationRemovedEvent); + + final channelCid = channelDeletedOrNotificationRemovedEvent.channel.cid; + final newChannels = [...channels] + ..removeWhere((it) => it.cid == channelCid); + + await expectLater( + channelsBlocState.channelsStream, + emitsInOrder([ + isSameChannelListAs(channels), + isSameChannelListAs(newChannels), + ]), + ); + + verify(mockClient.on( + EventType.channelDeleted, + EventType.notificationRemovedFromChannel, + )).called(1); + }, + ); + + testWidgets( + 'event channel should be moved to top of the list if present when' + 'EventType.messageNew event is received', + (tester) async { + final mockClient = MockClient(); + const channelsBlocKey = Key('channelsBloc'); + final channelsBloc = ChannelsBloc( + key: channelsBlocKey, + child: Offstage(), + ); + + when(mockClient.on(any, any, any, any)) + .thenAnswer((_) => Stream.empty()); + + when(mockClient.on( + EventType.messageNew, + )).thenAnswer((_) => eventController.stream); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: channelsBloc, + ), + ); + + final channelsBlocState = tester.state( + find.byKey(channelsBlocKey), + ); + + final channels = _generateChannels(mockClient); + + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer( + (_) => Stream.value(channels), + ); + + await channelsBlocState.queryChannels(); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + + final messageNewEvent = Event( + type: EventType.messageNew, + cid: channels.last.cid, + ); + + eventController.add(messageNewEvent); + + final channelCid = messageNewEvent.cid; + final index = channels.indexWhere((it) => it.cid == channelCid); + final updatedChannel = channels[index]; + final newChannels = [...channels] + ..removeAt(index) + ..insert(0, updatedChannel); + + await expectLater( + channelsBlocState.channelsStream, + emitsInOrder([ + isSameChannelListAs(channels), + isSameChannelListAs(newChannels), + ]), + ); + + verify(mockClient.on(EventType.messageNew)).called(1); + }, + ); + + testWidgets( + 'event channel should be moved to top of the list if present inside ' + 'hiddenChannels list and shouldAddChannel is true when ' + 'EventType.messageNew event is received', + (tester) async { + final hiddenChannelEventController = StreamController(); + + addTearDown(() { + hiddenChannelEventController.close(); + }); + + final mockClient = MockClient(); + final channels = _generateChannels(mockClient); + const channelsBlocKey = Key('channelsBloc'); + final channelsBloc = ChannelsBloc( + key: channelsBlocKey, + child: Offstage(), + shouldAddChannel: (e) => channels.map((it) => it.cid).contains(e.cid), + ); + + when(mockClient.on(any, any, any, any)) + .thenAnswer((_) => Stream.empty()); + + when(mockClient.on( + EventType.channelHidden, + )).thenAnswer((_) => hiddenChannelEventController.stream); + + when(mockClient.on( + EventType.messageNew, + )).thenAnswer((_) => eventController.stream); + + final messageNewEvent = Event( + type: EventType.messageNew, + cid: channels.last.cid, + ); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: channelsBloc, + ), + ); + + final channelsBlocState = tester.state( + find.byKey(channelsBlocKey), + ); + + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer( + (_) => Stream.value(channels), + ); + + await channelsBlocState.queryChannels(); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + + final channelHiddenEvent = Event( + type: EventType.channelHidden, + cid: channels.last.cid, + ); + + // Hiding the channel before passing messageNew event + hiddenChannelEventController.add(channelHiddenEvent); + + final channelsAfterHiddenEvent = [...channels]..removeLast(); + + eventController.add(messageNewEvent); + + final channelCid = messageNewEvent.cid; + final index = channels.indexWhere((it) => it.cid == channelCid); + final newChannels = [...channels] + ..removeAt(index) + ..insert(0, channels[index]); + + await expectLater( + channelsBlocState.channelsStream, + emitsInOrder([ + isSameChannelListAs(channels), + isSameChannelListAs(channelsAfterHiddenEvent), + isSameChannelListAs(newChannels), + ]), + ); + + verify(mockClient.on(EventType.channelHidden)).called(1); + verify(mockClient.on(EventType.messageNew)).called(1); + }, + ); + + testWidgets( + 'event channel should be moved to top of the list if present inside ' + 'channel state and shouldAddChannel is true when ' + 'EventType.messageNew event is received', + (tester) async { + final mockClient = MockClient(); + final channels = _generateChannels(mockClient); + final stateChannels = { + for (var c in _generateChannels(mockClient, offset: 5)) c.cid: c + }; + const channelsBlocKey = Key('channelsBloc'); + final channelsBloc = ChannelsBloc( + key: channelsBlocKey, + child: Offstage(), + shouldAddChannel: (_) => true, + ); + + when(mockClient.state.channels).thenReturn(stateChannels); + + when(mockClient.on(any, any, any, any)) + .thenAnswer((_) => Stream.empty()); + + when(mockClient.on( + EventType.messageNew, + )).thenAnswer((_) => eventController.stream); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: channelsBloc, + ), + ); + + final channelsBlocState = tester.state( + find.byKey(channelsBlocKey), + ); + + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer( + (_) => Stream.value(channels), + ); + + await channelsBlocState.queryChannels(); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + + final messageNewEvent = Event( + type: EventType.messageNew, + cid: stateChannels.keys.first, + ); + + eventController.add(messageNewEvent); + + final newChannels = [...channels] + ..insert(0, stateChannels[stateChannels.keys.first]); + + await expectLater( + channelsBlocState.channelsStream, + emitsInOrder([ + isSameChannelListAs(channels), + isSameChannelListAs(newChannels), + ]), + ); + + verify(mockClient.on(EventType.messageNew)).called(1); + }, + ); + + testWidgets( + 'channels should get sorted according to channelsComparator when ' + 'EventType.messageNew event is received', + (tester) async { + final mockClient = MockClient(); + final channels = _generateChannels(mockClient); + int channelComparator(Channel a, Channel b) { + final aData = a.extraData['extra_data_key'] as String; + final bData = b.extraData['extra_data_key'] as String; + return bData.compareTo(aData); + } + + const channelsBlocKey = Key('channelsBloc'); + final channelsBloc = ChannelsBloc( + key: channelsBlocKey, + child: Offstage(), + shouldAddChannel: (_) => true, + channelsComparator: channelComparator, + ); + + when(mockClient.on(any, any, any, any)) + .thenAnswer((_) => Stream.empty()); + + when(mockClient.on( + EventType.messageNew, + )).thenAnswer((_) => eventController.stream); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: channelsBloc, + ), + ); + + final channelsBlocState = tester.state( + find.byKey(channelsBlocKey), + ); + + when(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer( + (_) => Stream.value(channels), + ); + + await channelsBlocState.queryChannels(); + + verify(mockClient.queryChannels( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + + final messageNewEvent = Event( + type: EventType.messageNew, + cid: channels.first.cid, + ); + + eventController.add(messageNewEvent); + + final newChannels = [...channels]..sort(channelComparator); + + await expectLater( + channelsBlocState.channelsStream, + emitsInOrder([ + isSameChannelListAs(channels), + isSameChannelListAs(newChannels), + ]), + ); + + verify(mockClient.on(EventType.messageNew)).called(1); + }, + ); + + tearDown(() { + eventController.close(); + }); + }); +} diff --git a/packages/stream_chat_flutter_core/test/lazy_load_scroll_view_test.dart b/packages/stream_chat_flutter_core/test/lazy_load_scroll_view_test.dart new file mode 100644 index 00000000..19c1f2a2 --- /dev/null +++ b/packages/stream_chat_flutter_core/test/lazy_load_scroll_view_test.dart @@ -0,0 +1,294 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter_core/src/lazy_load_scroll_view.dart'; + +void main() { + test( + 'should throw assertion error if child is null', + () async { + const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); + final lazyLoadScrollView = () => LazyLoadScrollView( + key: lazyLoadScrollViewKey, + child: null, + ); + expect(lazyLoadScrollView, throwsA(isA())); + }, + ); + + testWidgets( + 'should render LazyLoadScrollView if child is provided', + (tester) async { + const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); + final lazyLoadScrollView = LazyLoadScrollView( + key: lazyLoadScrollViewKey, + child: Offstage(), + ); + + await tester.pumpWidget(lazyLoadScrollView); + + expect(find.byKey(lazyLoadScrollViewKey), findsOneWidget); + }, + ); + + testWidgets( + 'should render LazyLoadScrollView if child is provided', + (tester) async { + const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); + const childKey = Key('childKey'); + final lazyLoadScrollView = LazyLoadScrollView( + key: lazyLoadScrollViewKey, + child: Offstage(key: childKey), + ); + + await tester.pumpWidget(lazyLoadScrollView); + + expect(find.byKey(lazyLoadScrollViewKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + }, + ); + + testWidgets( + 'should call onPageScrollStart if child scrollView scrolls', + (tester) async { + const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); + const childListViewKey = Key('childListView'); + + bool onPageScrollStartCalled = false; + + final lazyLoadScrollView = LazyLoadScrollView( + key: lazyLoadScrollViewKey, + onPageScrollStart: () { + onPageScrollStartCalled = true; + }, + child: ListView( + key: childListViewKey, + children: List.generate( + 12, + (index) => Container( + height: 100, + child: Text('Item #$index'), + ), + ), + ), + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: lazyLoadScrollView, + ), + ); + + expect(find.byKey(lazyLoadScrollViewKey), findsOneWidget); + expect(find.byKey(childListViewKey), findsOneWidget); + expect(onPageScrollStartCalled, isFalse); + + await tester.startGesture(const Offset(100.0, 100.0)); + await tester.pump(const Duration(seconds: 1)); + + expect(onPageScrollStartCalled, isTrue); + }, + ); + + testWidgets( + 'should call onPageScrollStart, onPageScrollEnd ' + 'if child scrollView starts scrolling and ends', + (tester) async { + const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); + const childListViewKey = Key('childListView'); + + bool onPageScrollStartCalled = false; + bool onPageScrollEndCalled = false; + + final lazyLoadScrollView = LazyLoadScrollView( + key: lazyLoadScrollViewKey, + onPageScrollStart: () { + onPageScrollStartCalled = true; + }, + onPageScrollEnd: () { + onPageScrollEndCalled = true; + }, + child: ListView( + key: childListViewKey, + children: List.generate( + 12, + (index) => Container( + height: 100, + child: Text('Item #$index'), + ), + ), + ), + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: lazyLoadScrollView, + ), + ); + + expect(find.byKey(lazyLoadScrollViewKey), findsOneWidget); + expect(find.byKey(childListViewKey), findsOneWidget); + expect(onPageScrollStartCalled, isFalse); + expect(onPageScrollEndCalled, isFalse); + + final gesture = await tester.createGesture(); + + await gesture.down(const Offset(100.0, 100.0)); + await tester.pump(const Duration(seconds: 1)); + + expect(onPageScrollStartCalled, isTrue); + + await gesture.up(); + await tester.pump(const Duration(seconds: 1)); + + expect(onPageScrollEndCalled, isTrue); + }, + ); + + testWidgets( + 'should call onInBetweenOfPage if child scrollView ' + 'scroll position is in-between of view', + (tester) async { + const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); + const childListViewKey = Key('childListView'); + + bool onInBetweenOfPageCalled = false; + + final lazyLoadScrollView = LazyLoadScrollView( + key: lazyLoadScrollViewKey, + onInBetweenOfPage: () async { + onInBetweenOfPageCalled = true; + }, + child: ListView( + key: childListViewKey, + children: List.generate( + 12, + (index) => Container( + height: 100, + child: Text('Item #$index'), + ), + ), + ), + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: lazyLoadScrollView, + ), + ); + + expect(find.byKey(lazyLoadScrollViewKey), findsOneWidget); + expect(find.byKey(childListViewKey), findsOneWidget); + expect(onInBetweenOfPageCalled, isFalse); + + final gesture = await tester.createGesture(); + + await gesture.down(const Offset(100.0, 100.0)); + await tester.pump(const Duration(seconds: 1)); + await gesture.moveBy(const Offset(-200.0, -200.0)); + await tester.pump(const Duration(seconds: 1)); + + expect(onInBetweenOfPageCalled, isTrue); + }, + ); + + testWidgets( + 'should call onStartOfPage if child scrollView ' + 'tends to reach the start', + (tester) async { + const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); + const childListViewKey = Key('childListView'); + + bool onStartOfPageCalled = false; + + final lazyLoadScrollView = LazyLoadScrollView( + key: lazyLoadScrollViewKey, + onStartOfPage: () async { + onStartOfPageCalled = true; + }, + child: ListView( + key: childListViewKey, + children: List.generate( + 12, + (index) => Container( + height: 100, + child: Text('Item #$index'), + ), + ), + ), + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: lazyLoadScrollView, + ), + ); + + expect(find.byKey(lazyLoadScrollViewKey), findsOneWidget); + expect(find.byKey(childListViewKey), findsOneWidget); + expect(onStartOfPageCalled, isFalse); + + final gesture = await tester.createGesture(); + + await gesture.down(const Offset(100.0, 100.0)); + await tester.pump(const Duration(seconds: 1)); + await gesture.moveBy(const Offset(-200.0, -200.0)); + await tester.pump(const Duration(seconds: 1)); + await gesture.moveBy(const Offset(201.0, 201.0)); + await tester.pump(const Duration(seconds: 1)); + + expect(onStartOfPageCalled, isTrue); + }, + ); + + testWidgets( + 'should call onEndOfPage if child scrollView ' + 'tends to reach the end', + (tester) async { + const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); + const childListViewKey = Key('childListView'); + + bool onEndOfPageCalled = false; + + final lazyLoadScrollView = LazyLoadScrollView( + key: lazyLoadScrollViewKey, + onEndOfPage: () async { + onEndOfPageCalled = true; + }, + child: ListView( + key: childListViewKey, + children: List.generate( + 12, + (index) => Container( + height: 100, + child: Text('Item #$index'), + ), + ), + ), + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: lazyLoadScrollView, + ), + ); + + expect(find.byKey(lazyLoadScrollViewKey), findsOneWidget); + expect(find.byKey(childListViewKey), findsOneWidget); + expect(onEndOfPageCalled, isFalse); + + final gesture = await tester.createGesture(); + + await gesture.down(const Offset(100.0, 100.0)); + await tester.pump(const Duration(seconds: 1)); + await gesture.moveBy(const Offset(-601.0, -601.0)); + await tester.pump(const Duration(seconds: 1)); + + expect(onEndOfPageCalled, isTrue); + }, + ); +} diff --git a/packages/stream_chat_flutter_core/test/matchers/channel_matcher.dart b/packages/stream_chat_flutter_core/test/matchers/channel_matcher.dart new file mode 100644 index 00000000..f371acff --- /dev/null +++ b/packages/stream_chat_flutter_core/test/matchers/channel_matcher.dart @@ -0,0 +1,49 @@ +import 'package:meta/meta.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +Matcher isSameChannelAs(Channel targetChannel) => + _IsSameChannelAs(targetChannel: targetChannel); + +class _IsSameChannelAs extends Matcher { + const _IsSameChannelAs({ + @required this.targetChannel, + }) : assert(targetChannel != null, ''); + + final Channel targetChannel; + + @override + bool matches(covariant Channel channel, Map matchState) => + channel.cid == targetChannel.cid; + + @override + Description describe(Description description) => + description.add('is same channel as $targetChannel'); +} + +Matcher isSameChannelListAs(List targetChannelList) => + _IsSameChannelListAs(targetChannelList: targetChannelList); + +class _IsSameChannelListAs extends Matcher { + const _IsSameChannelListAs({ + @required this.targetChannelList, + }) : assert(targetChannelList != null, ''); + + final List targetChannelList; + + @override + bool matches(covariant List channelList, Map matchState) { + bool matches = true; + for (var i = 0; i < channelList.length; i++) { + final channel = channelList[i]; + final targetChannel = targetChannelList[i]; + matches = isSameChannelAs(targetChannel).matches(channel, matchState); + if (!matches) break; + } + return matches; + } + + @override + Description describe(Description description) => + description.add('is same channelList as $targetChannelList'); +} diff --git a/packages/stream_chat_flutter_core/test/matchers/get_message_response_matcher.dart b/packages/stream_chat_flutter_core/test/matchers/get_message_response_matcher.dart new file mode 100644 index 00000000..bd89420b --- /dev/null +++ b/packages/stream_chat_flutter_core/test/matchers/get_message_response_matcher.dart @@ -0,0 +1,55 @@ +import 'package:meta/meta.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +Matcher isSameMessageResponseAs(GetMessageResponse targetResponse) => + _IsSameMessageResponseAs(targetResponse: targetResponse); + +class _IsSameMessageResponseAs extends Matcher { + const _IsSameMessageResponseAs({ + @required this.targetResponse, + }) : assert(targetResponse != null, ''); + + final GetMessageResponse targetResponse; + + @override + bool matches(covariant GetMessageResponse response, Map matchState) => + response.message.id == targetResponse.message.id && + response.channel.cid == targetResponse.channel.cid; + + @override + Description describe(Description description) => + description.add('is same message response as $targetResponse'); +} + +Matcher isSameMessageResponseListAs( + List targetResponseList) => + _IsSameMessageResponseListAs(targetResponseList: targetResponseList); + +class _IsSameMessageResponseListAs extends Matcher { + const _IsSameMessageResponseListAs({ + @required this.targetResponseList, + }) : assert(targetResponseList != null, ''); + + final List targetResponseList; + + @override + bool matches( + covariant List responseList, Map matchState) { + bool matches = true; + for (var i = 0; i < responseList.length; i++) { + final response = responseList[i]; + final targetResponse = targetResponseList[i]; + matches = isSameMessageResponseAs(targetResponse).matches( + response, + matchState, + ); + if (!matches) break; + } + return matches; + } + + @override + Description describe(Description description) => + description.add('is same userResponseList as $targetResponseList'); +} diff --git a/packages/stream_chat_flutter_core/test/matchers/message_matcher.dart b/packages/stream_chat_flutter_core/test/matchers/message_matcher.dart new file mode 100644 index 00000000..12a3e19d --- /dev/null +++ b/packages/stream_chat_flutter_core/test/matchers/message_matcher.dart @@ -0,0 +1,49 @@ +import 'package:meta/meta.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +Matcher isSameMessageAs(Message targetMessage) => + _IsSameMessageAs(targetMessage: targetMessage); + +class _IsSameMessageAs extends Matcher { + const _IsSameMessageAs({ + @required this.targetMessage, + }) : assert(targetMessage != null, ''); + + final Message targetMessage; + + @override + bool matches(covariant Message message, Map matchState) => + message.id == targetMessage.id; + + @override + Description describe(Description description) => + description.add('is same message as $targetMessage'); +} + +Matcher isSameMessageListAs(List targetMessageList) => + _IsSameMessageListAs(targetMessageList: targetMessageList); + +class _IsSameMessageListAs extends Matcher { + const _IsSameMessageListAs({ + @required this.targetMessageList, + }) : assert(targetMessageList != null, ''); + + final List targetMessageList; + + @override + bool matches(covariant List messageList, Map matchState) { + bool matches = true; + for (var i = 0; i < messageList.length; i++) { + final message = messageList[i]; + final targetMessage = targetMessageList[i]; + matches = isSameMessageAs(targetMessage).matches(message, matchState); + if (!matches) break; + } + return matches; + } + + @override + Description describe(Description description) => + description.add('is same messageList as $targetMessageList'); +} diff --git a/packages/stream_chat_flutter_core/test/matchers/users_matcher.dart b/packages/stream_chat_flutter_core/test/matchers/users_matcher.dart new file mode 100644 index 00000000..3816fa24 --- /dev/null +++ b/packages/stream_chat_flutter_core/test/matchers/users_matcher.dart @@ -0,0 +1,47 @@ +import 'package:meta/meta.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +Matcher isSameUserAs(User targetUser) => _IsSameUserAs(targetUser: targetUser); + +class _IsSameUserAs extends Matcher { + const _IsSameUserAs({ + @required this.targetUser, + }) : assert(targetUser != null, ''); + + final User targetUser; + + @override + bool matches(covariant User user, Map matchState) => user.id == targetUser.id; + + @override + Description describe(Description description) => + description.add('is same user as $targetUser'); +} + +Matcher isSameUserListAs(List targetUserList) => + _IsSameUserListAs(targetUserList: targetUserList); + +class _IsSameUserListAs extends Matcher { + const _IsSameUserListAs({ + @required this.targetUserList, + }) : assert(targetUserList != null, ''); + + final List targetUserList; + + @override + bool matches(covariant List userList, Map matchState) { + bool matches = true; + for (var i = 0; i < userList.length; i++) { + final user = userList[i]; + final targetUser = targetUserList[i]; + matches = isSameUserAs(targetUser).matches(user, matchState); + if (!matches) break; + } + return matches; + } + + @override + Description describe(Description description) => + description.add('is same userList as $targetUserList'); +} diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart new file mode 100644 index 00000000..95446f95 --- /dev/null +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -0,0 +1,413 @@ +import 'dart:async'; + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter_core/src/message_list_core.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +import 'mocks.dart'; + +void main() { + List _generateMessages({ + int count = 3, + int offset = 0, + bool threads = false, + }) { + final users = List.generate(count, (index) { + index = count + offset; + return User(id: 'testUserId$index'); + }); + final messages = List.generate( + count, + (index) { + index = index + offset; + return Message( + id: 'testMessageId$index', + type: 'testType', + user: users[index], + createdAt: DateTime.now(), + shadowed: false, + replyCount: index, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text #$index', + pinned: true, + pinnedAt: DateTime.now(), + pinnedBy: User(id: 'testUserId$index'), + ); + }, + ); + final threadMessages = List.generate( + count, + (index) { + index = index + offset; + return Message( + id: 'testThreadMessageId$index', + type: 'testType', + user: users[index], + parentId: messages[0].id, + createdAt: DateTime.now(), + shadowed: false, + replyCount: index, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text #$index', + pinned: true, + pinnedAt: DateTime.now(), + pinnedBy: User(id: 'testUserId$index'), + ); + }, + ); + return threads ? threadMessages : messages; + } + + test( + 'should throw assertion error in case messageListBuilder is null', + () { + final messageListCore = () => MessageListCore( + messageListBuilder: null, + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorWidgetBuilder: (BuildContext context, Object error) => + Offstage(), + ); + expect(messageListCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case loadingBuilder is null', + () { + final messageListCore = () => MessageListCore( + messageListBuilder: (_, __) => Offstage(), + loadingBuilder: null, + emptyBuilder: (BuildContext context) => Offstage(), + errorWidgetBuilder: (BuildContext context, Object error) => + Offstage(), + ); + expect(messageListCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case emptyBuilder is null', + () { + final messageListCore = () => MessageListCore( + messageListBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: null, + errorWidgetBuilder: (BuildContext context, Object error) => + Offstage(), + ); + expect(messageListCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case errorWidgetBuilder is null', + () { + final messageListCore = () => MessageListCore( + messageListBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorWidgetBuilder: null, + ); + expect(messageListCore, throwsA(isA())); + }, + ); + + testWidgets( + 'should throw if MessageListCore is used where StreamChannel is not present ' + 'in the widget tree', + (tester) async { + const messageListCoreKey = Key('messageListCore'); + final messageListCore = MessageListCore( + key: messageListCoreKey, + messageListBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + ); + + await tester.pumpWidget(messageListCore); + + expect(find.byKey(messageListCoreKey), findsNothing); + expect(tester.takeException(), isInstanceOf()); + }, + ); + + testWidgets( + 'should render MessageListCore if used with StreamChannel as an ancestor', + (tester) async { + const messageListCoreKey = Key('messageListCore'); + final messageListCore = MessageListCore( + key: messageListCoreKey, + messageListBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + ); + + final mockChannel = MockChannel(); + + when(mockChannel.state.isUpToDate).thenReturn(true); + + await tester.pumpWidget( + StreamChannel( + channel: mockChannel, + child: messageListCore, + ), + ); + + expect(find.byKey(messageListCoreKey), findsOneWidget); + }, + ); + + testWidgets( + 'should assign paginateData callback to MessageListController if passed', + (tester) async { + const messageListCoreKey = Key('messageListCore'); + final controller = MessageListController(); + final messageListCore = MessageListCore( + key: messageListCoreKey, + messageListBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + messageListController: controller, + ); + + expect(controller.paginateData, isNull); + + final mockChannel = MockChannel(); + + when(mockChannel.state.isUpToDate).thenReturn(true); + + await tester.pumpWidget( + StreamChannel( + channel: mockChannel, + child: messageListCore, + ), + ); + + expect(find.byKey(messageListCoreKey), findsOneWidget); + expect(controller.paginateData, isNotNull); + }, + ); + + testWidgets( + 'should build error widget if messagesStream emits error', + (tester) async { + const messageListCoreKey = Key('messageListCore'); + const errorWidgetKey = Key('errorWidget'); + final messageListCore = MessageListCore( + key: messageListCoreKey, + messageListBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorWidgetBuilder: (BuildContext context, Object error) => Offstage( + key: errorWidgetKey, + ), + ); + + final mockChannel = MockChannel(); + + when(mockChannel.state.isUpToDate).thenReturn(true); + when(mockChannel.initialized).thenAnswer((_) async => true); + + const error = 'Error! Error! Error!'; + when(mockChannel.state.messagesStream) + .thenAnswer((_) => Stream.error(error)); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StreamChannel( + channel: mockChannel, + child: messageListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(errorWidgetKey), findsOneWidget); + }, + ); + + testWidgets( + 'should build empty widget if the channel is upToDate and ' + 'messagesStream emits empty data', + (tester) async { + const messageListCoreKey = Key('messageListCore'); + const emptyWidgetKey = Key('emptyWidget'); + final messageListCore = MessageListCore( + key: messageListCoreKey, + messageListBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(key: emptyWidgetKey), + errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + ); + + final mockChannel = MockChannel(); + + when(mockChannel.state.isUpToDate).thenReturn(true); + when(mockChannel.initialized).thenAnswer((_) async => true); + + const messages = []; + when(mockChannel.state.messagesStream) + .thenAnswer((_) => Stream.value(messages)); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StreamChannel( + channel: mockChannel, + child: messageListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(emptyWidgetKey), findsOneWidget); + }, + ); + + testWidgets( + 'should build list widget if the channel is not upToDate and ' + 'messagesStream emits empty data', + (tester) async { + const messageListCoreKey = Key('messageListCore'); + const listWidgetKey = Key('listWidget'); + final messageListCore = MessageListCore( + key: messageListCoreKey, + messageListBuilder: (_, __) => Offstage(key: listWidgetKey), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + ); + + final mockChannel = MockChannel(); + + when(mockChannel.state.isUpToDate).thenReturn(false); + when(mockChannel.initialized).thenAnswer((_) async => true); + + const messages = []; + when(mockChannel.state.messagesStream) + .thenAnswer((_) => Stream.value(messages)); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StreamChannel( + channel: mockChannel, + child: messageListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(listWidgetKey), findsOneWidget); + }, + ); + + testWidgets( + 'should build list widget and save it for future rebuilds ' + 'if messagesStream emits some data', + (tester) async { + const messageListCoreKey = Key('messageListCore'); + const listWidgetKey = Key('listWidget'); + final messageListCore = MessageListCore( + key: messageListCoreKey, + messageListBuilder: (_, messages) => Container( + key: listWidgetKey, + child: Text( + messages.reversed.map((it) => it.id).join(','), + ), + ), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + ); + + final mockChannel = MockChannel(); + + when(mockChannel.state.isUpToDate).thenReturn(true); + when(mockChannel.initialized).thenAnswer((_) async => true); + + final messages = _generateMessages(); + when(mockChannel.state.messagesStream) + .thenAnswer((_) => Stream.value(messages)); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StreamChannel( + channel: mockChannel, + child: messageListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(listWidgetKey), findsOneWidget); + expect(find.text(messages.map((it) => it.id).join(',')), findsOneWidget); + }, + ); + + testWidgets( + 'should build list widget with thread messages if parentMessage is passed', + (tester) async { + final messages = _generateMessages(threads: true); + final parentMessage = Message(id: messages.first.parentId); + const messageListCoreKey = Key('messageListCore'); + const listWidgetKey = Key('listWidget'); + final messageListCore = MessageListCore( + key: messageListCoreKey, + messageListBuilder: (_, messages) => Container( + key: listWidgetKey, + child: Text( + messages.reversed.map((it) => '${it.parentId}-${it.id}').join(','), + ), + ), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + parentMessage: parentMessage, + ); + + final mockChannel = MockChannel(); + + when(mockChannel.state.isUpToDate).thenReturn(true); + when(mockChannel.initialized).thenAnswer((_) async => true); + + final threads = {parentMessage.id: messages}; + + when(mockChannel.state.threads).thenReturn(threads); + when(mockChannel.state.threadsStream) + .thenAnswer((_) => Stream.value(threads)); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StreamChannel( + channel: mockChannel, + child: messageListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(listWidgetKey), findsOneWidget); + expect( + find.text(messages.map((it) => '${it.parentId}-${it.id}').join(',')), + findsOneWidget, + ); + }, + ); +} diff --git a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart new file mode 100644 index 00000000..e639c03b --- /dev/null +++ b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart @@ -0,0 +1,344 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/message_search_bloc.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +import 'matchers/get_message_response_matcher.dart'; +import 'mocks.dart'; + +void main() { + List _generateMessages({ + int count = 3, + int offset = 0, + }) { + return List.generate( + count, + (index) { + index = index + offset; + return GetMessageResponse() + ..message = Message( + id: 'testId$index', + text: 'testTextData$index', + ) + ..channel = ChannelModel( + cid: 'testCid', + ); + }, + ); + } + + test( + 'should throw assertion error if child is null', + () async { + const messageSearchBlocKey = Key('messageSearchBloc'); + final messageSearchBloc = () => MessageSearchBloc( + key: messageSearchBlocKey, + child: null, + ); + expect(messageSearchBloc, throwsA(isA())); + }, + ); + + testWidgets( + 'messageSearchBlocState.search() should throw if used where ' + 'StreamChat is not present in the widget tree', + (tester) async { + const messageSearchBlocKey = Key('messageSearchBloc'); + const childKey = Key('child'); + final messageSearchBloc = MessageSearchBloc( + key: messageSearchBlocKey, + child: Offstage(key: childKey), + ); + + await tester.pumpWidget(messageSearchBloc); + + expect(find.byKey(messageSearchBlocKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + + final usersBlocState = tester.state( + find.byKey(messageSearchBlocKey), + ); + + try { + await usersBlocState.search(); + } catch (e) { + expect(e, isInstanceOf()); + } + }, + ); + + testWidgets( + 'messageSearchBlocState.search() should emit data through usersStream', + (tester) async { + const messageSearchBlocKey = Key('messageSearchBloc'); + const childKey = Key('child'); + final messageSearchBloc = MessageSearchBloc( + key: messageSearchBlocKey, + child: Offstage(key: childKey), + ); + + final mockClient = MockClient(); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: messageSearchBloc, + ), + ); + final messageSearchBlocState = tester.state( + find.byKey(messageSearchBlocKey), + ); + + final messageResponseList = _generateMessages(); + + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer( + (_) async => SearchMessagesResponse()..results = messageResponseList, + ); + + messageSearchBlocState.search(); + + await expectLater( + messageSearchBlocState.messagesStream, + emits(isSameMessageResponseListAs(messageResponseList)), + ); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + }, + ); + + testWidgets( + 'messageSearchBlocState.messagesStream should emit error ' + 'if client.search() throws', + (tester) async { + const messageSearchBlocKey = Key('messageSearchBloc'); + const childKey = Key('child'); + final messageSearchBloc = MessageSearchBloc( + key: messageSearchBlocKey, + child: Offstage(key: childKey), + ); + + final mockClient = MockClient(); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: messageSearchBloc, + ), + ); + final messageSearchBlocState = tester.state( + find.byKey(messageSearchBlocKey), + ); + + const error = 'Error! Error! Error!'; + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).thenThrow(error); + + messageSearchBlocState.search(); + + await expectLater( + messageSearchBlocState.messagesStream, + emitsError(error), + ); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + }, + ); + + testWidgets( + 'calling messageSearchBlocState.search() again with an offset ' + 'should emit new data through messagesStream and also emit loading state ' + 'through queryMessagesLoading', + (tester) async { + const messageSearchBlocKey = Key('messageSearchBloc'); + const childKey = Key('child'); + final messageSearchBloc = MessageSearchBloc( + key: messageSearchBlocKey, + child: Offstage(key: childKey), + ); + + final mockClient = MockClient(); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: messageSearchBloc, + ), + ); + + final messageSearchBlocState = tester.state( + find.byKey(messageSearchBlocKey), + ); + + final messageResponseList = _generateMessages(); + + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer( + (_) async => SearchMessagesResponse()..results = messageResponseList, + ); + + messageSearchBlocState.search(); + + await expectLater( + messageSearchBlocState.messagesStream, + emits(isSameMessageResponseListAs(messageResponseList)), + ); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + + final offset = messageResponseList.length; + final paginatedMessageResponseList = _generateMessages(offset: offset); + final pagination = PaginationParams(offset: offset); + + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: pagination, + )).thenAnswer( + (_) async => + SearchMessagesResponse()..results = paginatedMessageResponseList, + ); + + messageSearchBlocState.search(pagination: pagination); + + await Future.wait([ + expectLater( + messageSearchBlocState.queryMessagesLoading, + emitsInOrder([true, false]), + ), + expectLater( + messageSearchBlocState.messagesStream, + emits(isSameMessageResponseListAs( + messageResponseList + paginatedMessageResponseList, + )), + ), + ]); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: pagination, + )).called(1); + }, + ); + + testWidgets( + 'calling messageSearchBlocState.search() again with an offset ' + 'should emit error through queryUsersLoading if ' + 'client.search() throws', + (tester) async { + const messageSearchBlocKey = Key('messageSearchBloc'); + const childKey = Key('child'); + final messageSearchBloc = MessageSearchBloc( + key: messageSearchBlocKey, + child: Offstage(key: childKey), + ); + + final mockClient = MockClient(); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: messageSearchBloc, + ), + ); + + final messageSearchBlocState = tester.state( + find.byKey(messageSearchBlocKey), + ); + + final messageResponseList = _generateMessages(); + + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer( + (_) async => SearchMessagesResponse()..results = messageResponseList, + ); + + messageSearchBlocState.search(); + + await expectLater( + messageSearchBlocState.messagesStream, + emits(isSameMessageResponseListAs(messageResponseList)), + ); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + + final offset = messageResponseList.length; + final pagination = PaginationParams(offset: offset); + + const error = 'Error! Error! Error!'; + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: pagination, + )).thenThrow(error); + + messageSearchBlocState.search(pagination: pagination); + + await expectLater( + messageSearchBlocState.queryMessagesLoading, + emitsError(error), + ); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: pagination, + )).called(1); + }, + ); +} diff --git a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart new file mode 100644 index 00000000..aa4a47f9 --- /dev/null +++ b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart @@ -0,0 +1,535 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_chat_flutter_core/src/message_search_list_core.dart'; + +import 'mocks.dart'; + +void main() { + List _generateMessages({ + int count = 3, + int offset = 0, + }) { + return List.generate( + count, + (index) { + index = index + offset; + return GetMessageResponse() + ..message = Message( + id: 'testId$index', + text: 'testTextData$index', + ) + ..channel = ChannelModel( + cid: 'testCid', + ); + }, + ); + } + + test( + 'should throw assertion error in case childBuilder is null', + () { + final messageSearchListCore = () => MessageSearchListCore( + childBuilder: null, + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + expect(messageSearchListCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case loadingBuilder is null', + () { + final messageSearchListCore = () => MessageSearchListCore( + childBuilder: (List messages) => Offstage(), + loadingBuilder: null, + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + expect(messageSearchListCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case emptyBuilder is null', + () { + final messageSearchListCore = () => MessageSearchListCore( + childBuilder: (List messages) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: null, + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + expect(messageSearchListCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case errorBuilder is null', + () { + final messageSearchListCore = () => MessageSearchListCore( + childBuilder: (List messages) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: null, + ); + expect(messageSearchListCore, throwsA(isA())); + }, + ); + + testWidgets( + 'should throw if MessageSearchListCore is used where MessageSearchBloc ' + 'is not present in the widget tree', + (tester) async { + const messageSearchListCoreKey = Key('messageSearchListCore'); + final messageSearchListCore = MessageSearchListCore( + key: messageSearchListCoreKey, + childBuilder: (List messages) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + + await tester.pumpWidget(messageSearchListCore); + + expect(find.byKey(messageSearchListCoreKey), findsNothing); + expect(tester.takeException(), isInstanceOf()); + }, + ); + + testWidgets( + 'should render MessageSearchListCore if used with ' + 'MessageSearchListCore as an ancestor', + (tester) async { + const messageSearchListCoreKey = Key('messageSearchListCore'); + final messageSearchListCore = MessageSearchListCore( + key: messageSearchListCoreKey, + childBuilder: (List messages) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + + final mockClient = MockClient(); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: MessageSearchBloc( + child: messageSearchListCore, + ), + ), + ); + + expect(find.byKey(messageSearchListCoreKey), findsOneWidget); + }, + ); + + testWidgets( + 'should assign loadData and paginateData callback to ' + 'UserListController if passed', + (tester) async { + const messageSearchListCoreKey = Key('messageSearchListCore'); + final controller = MessageSearchListController(); + final messageSearchListCore = MessageSearchListCore( + key: messageSearchListCoreKey, + childBuilder: (List messages) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + messageSearchListController: controller, + ); + + expect(controller.loadData, isNull); + expect(controller.paginateData, isNull); + + final mockClient = MockClient(); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: MessageSearchBloc( + child: messageSearchListCore, + ), + ), + ); + + expect(find.byKey(messageSearchListCoreKey), findsOneWidget); + expect(controller.loadData, isNotNull); + expect(controller.paginateData, isNotNull); + }, + ); + + testWidgets( + 'should build error widget if messageSearchBloc.messagesStream emits error', + (tester) async { + const messageSearchListCoreKey = Key('messageSearchListCore'); + const errorWidgetKey = Key('errorWidget'); + final messageSearchListCore = MessageSearchListCore( + key: messageSearchListCoreKey, + childBuilder: (List messages) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage( + key: errorWidgetKey, + ), + ); + + final mockClient = MockClient(); + + const error = 'Error! Error! Error!'; + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).thenThrow(error); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: MessageSearchBloc( + child: messageSearchListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(errorWidgetKey), findsOneWidget); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + }, + ); + + testWidgets( + 'should build empty widget if messageSearchBloc.messagesStream ' + 'emits empty data', + (tester) async { + const messageSearchListCoreKey = Key('messageSearchListCore'); + const emptyWidgetKey = Key('emptyWidget'); + final messageSearchListCore = MessageSearchListCore( + key: messageSearchListCoreKey, + childBuilder: (List messages) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(key: emptyWidgetKey), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + + final mockClient = MockClient(); + + final messageResponseList = []; + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer( + (_) async => SearchMessagesResponse()..results = messageResponseList, + ); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: MessageSearchBloc( + child: messageSearchListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(emptyWidgetKey), findsOneWidget); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + }, + ); + + testWidgets( + 'should build child widget if usersBlocState.usersStream emits some data', + (tester) async { + const messageSearchListCoreKey = Key('messageSearchListCore'); + const childWidgetKey = Key('childWidget'); + final messageSearchListCore = MessageSearchListCore( + key: messageSearchListCoreKey, + childBuilder: (List messages) => Offstage( + key: childWidgetKey, + ), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + ); + + final mockClient = MockClient(); + + final messageResponseList = _generateMessages(); + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).thenAnswer( + (_) async => SearchMessagesResponse()..results = messageResponseList, + ); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: MessageSearchBloc( + child: messageSearchListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(childWidgetKey), findsOneWidget); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: anyNamed('paginationParams'), + )).called(1); + }, + ); + + testWidgets( + 'should build child widget with paginated data ' + 'on calling channelListCoreState.paginateData', + (tester) async { + const messageSearchListCoreKey = Key('messageSearchListCore'); + const childWidgetKey = Key('childWidget'); + const pagination = PaginationParams(); + final messageSearchListCore = MessageSearchListCore( + key: messageSearchListCoreKey, + childBuilder: (List messages) => Container( + key: childWidgetKey, + child: Text( + messages.map((e) => '${e.channel.cid}-${e.message.id}').join(','), + ), + ), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + paginationParams: pagination, + ); + + final mockClient = MockClient(); + + final messageResponseList = _generateMessages(); + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: pagination, + )).thenAnswer( + (_) async => SearchMessagesResponse()..results = messageResponseList, + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StreamChatCore( + client: mockClient, + child: MessageSearchBloc( + child: messageSearchListCore, + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(childWidgetKey), findsOneWidget); + expect( + find.text( + messageResponseList + .map((e) => '${e.channel.cid}-${e.message.id}') + .join(','), + ), + findsOneWidget, + ); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: pagination, + )).called(1); + + final messageSearchListCoreState = + tester.state( + find.byKey(messageSearchListCoreKey), + ); + + final offset = messageResponseList.length; + final paginatedMessageResponseList = _generateMessages(offset: offset); + final updatedPagination = pagination.copyWith(offset: offset); + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: updatedPagination, + )).thenAnswer( + (_) async => + SearchMessagesResponse()..results = paginatedMessageResponseList, + ); + + await messageSearchListCoreState.paginateData(); + + await tester.pumpAndSettle(); + + expect(find.byKey(childWidgetKey), findsOneWidget); + expect( + find.text([ + ...messageResponseList, + ...paginatedMessageResponseList, + ].map((e) => '${e.channel.cid}-${e.message.id}').join(',')), + findsOneWidget, + ); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: updatedPagination, + )).called(1); + }, + ); + + testWidgets( + 'should rebuild MessageSearchListCore with updated widget data ' + 'on calling setState()', + (tester) async { + const pagination = PaginationParams(); + + StateSetter _stateSetter; + int limit = pagination.limit; + + const messageSearchListCoreKey = Key('messageSearchListCore'); + const childWidgetKey = Key('childWidget'); + MessageSearchListCore messageSearchListCoreBuilder(int limit) => + MessageSearchListCore( + key: messageSearchListCoreKey, + childBuilder: (List messages) => Container( + key: childWidgetKey, + child: Text( + messages + .map((e) => '${e.channel.cid}-${e.message.id}') + .join(','), + ), + ), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), + paginationParams: pagination.copyWith(limit: limit), + ); + + final mockClient = MockClient(); + + final messageResponseList = _generateMessages(); + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: pagination, + )).thenAnswer( + (_) async => SearchMessagesResponse()..results = messageResponseList, + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StreamChatCore( + client: mockClient, + child: MessageSearchBloc( + child: StatefulBuilder(builder: (context, stateSetter) { + // Assigning stateSetter for rebuilding UserListCore + _stateSetter = stateSetter; + return messageSearchListCoreBuilder(limit); + }), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(childWidgetKey), findsOneWidget); + expect( + find.text( + messageResponseList + .map((e) => '${e.channel.cid}-${e.message.id}') + .join(','), + ), + findsOneWidget, + ); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: pagination, + )).called(1); + + // Rebuilding MessageSearchListCore with new pagination limit + _stateSetter(() => limit = 6); + + final updatedMessageResponseList = _generateMessages(count: limit); + final updatedPagination = pagination.copyWith(limit: limit); + when(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: updatedPagination, + )).thenAnswer( + (_) async => + SearchMessagesResponse()..results = updatedMessageResponseList, + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(childWidgetKey), findsOneWidget); + expect( + find.text(updatedMessageResponseList + .map((e) => '${e.channel.cid}-${e.message.id}') + .join(',')), + findsOneWidget, + ); + + verify(mockClient.search( + any, + query: anyNamed('query'), + sort: anyNamed('sort'), + messageFilters: anyNamed('messageFilters'), + paginationParams: updatedPagination, + )).called(1); + }, + ); +} diff --git a/packages/stream_chat_flutter_core/test/mocks.dart b/packages/stream_chat_flutter_core/test/mocks.dart index c23a4be4..a46b7b5c 100644 --- a/packages/stream_chat_flutter_core/test/mocks.dart +++ b/packages/stream_chat_flutter_core/test/mocks.dart @@ -1,10 +1,39 @@ import 'package:mockito/mockito.dart'; import 'package:stream_chat/stream_chat.dart'; -class MockClient extends Mock implements StreamChatClient {} +class MockLogger extends Mock implements Logger {} -class MockClientState extends Mock implements ClientState {} +class MockClient extends Mock implements StreamChatClient { + final Logger logger = MockLogger(); -class MockChannel extends Mock implements Channel {} + ClientState _state; + + @override + ClientState get state => _state ??= MockClientState(); +} + +class MockClientState extends Mock implements ClientState { + OwnUser _user; + + @override + OwnUser get user => _user ??= OwnUser( + id: 'testUserId', + role: 'admin', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ); +} + +class MockChannel extends Mock implements Channel { + ChannelClientState _state; + + @override + ChannelClientState get state => _state ??= MockChannelState(); + + StreamChatClient _client; + + @override + StreamChatClient get client => _client ??= MockClient(); +} class MockChannelState extends Mock implements ChannelClientState {} diff --git a/packages/stream_chat_flutter_core/test/stream_channel_test.dart b/packages/stream_chat_flutter_core/test/stream_channel_test.dart new file mode 100644 index 00000000..4387a918 --- /dev/null +++ b/packages/stream_chat_flutter_core/test/stream_channel_test.dart @@ -0,0 +1,343 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +import 'mocks.dart'; + +void main() { + List _generateMessages({ + int count = 3, + int offset = 0, + bool threads = false, + }) { + final users = List.generate(count, (index) { + index = count + offset; + return User(id: 'testUserId$index'); + }); + final messages = List.generate( + count, + (index) { + index = index + offset; + return Message( + id: 'testMessageId$index', + type: 'testType', + user: users[index], + createdAt: DateTime.now(), + shadowed: false, + replyCount: index, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text #$index', + pinned: true, + pinnedAt: DateTime.now(), + pinnedBy: User(id: 'testUserId$index'), + ); + }, + ); + final threadMessages = List.generate( + count, + (index) { + index = index + offset; + return Message( + id: 'testThreadMessageId$index', + type: 'testType', + user: users[index], + parentId: messages[0].id, + createdAt: DateTime.now(), + shadowed: false, + replyCount: index, + updatedAt: DateTime.now(), + extraData: {'extra_test_field': 'extraTestData'}, + text: 'Dummy text #$index', + pinned: true, + pinnedAt: DateTime.now(), + pinnedBy: User(id: 'testUserId$index'), + ); + }, + ); + return threads ? threadMessages : messages; + } + + test( + 'should throw assertion error if child is null', + () async { + final mockChannel = MockChannel(); + const streamChannelKey = Key('streamChannel'); + final streamChannel = () => StreamChannel( + key: streamChannelKey, + channel: mockChannel, + child: null, + ); + expect(streamChannel, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error if channel is null', + () async { + const streamChannelKey = Key('streamChannel'); + final streamChannel = () => StreamChannel( + key: streamChannelKey, + child: Offstage(), + channel: null, + ); + expect(streamChannel, throwsA(isA())); + }, + ); + + testWidgets( + 'should render StreamChannel if both channel and child is provided', + (tester) async { + final mockChannel = MockChannel(); + const streamChannelKey = Key('streamChannel'); + const childKey = Key('childKey'); + final streamChannel = StreamChannel( + key: streamChannelKey, + channel: mockChannel, + child: Offstage(key: childKey), + ); + + await tester.pumpWidget(streamChannel); + + expect(find.byKey(streamChannelKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + }, + ); + + testWidgets( + 'should build error widget if channel.initialized throws', + (tester) async { + final mockChannel = MockChannel(); + const streamChannelKey = Key('streamChannel'); + const childKey = Key('childKey'); + final streamChannel = StreamChannel( + key: streamChannelKey, + channel: mockChannel, + child: Offstage(key: childKey), + ); + + final errorMessage = 'Error! Error! Error!'; + final error = DioError(type: DioErrorType.RESPONSE, error: errorMessage); + when(mockChannel.initialized).thenAnswer((_) => Future.error(error)); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: streamChannel, + ), + ); + + await tester.pumpAndSettle(); + + expect(find.text(errorMessage), findsOneWidget); + + verify(mockChannel.initialized).called(1); + }, + ); + + testWidgets( + 'should show circular progress indicator showLoading is true ' + 'and channel is still not initialized', + (tester) async { + final mockChannel = MockChannel(); + const streamChannelKey = Key('streamChannel'); + const childKey = Key('childKey'); + final streamChannel = StreamChannel( + key: streamChannelKey, + channel: mockChannel, + child: Offstage(key: childKey), + showLoading: true, + ); + + when(mockChannel.initialized).thenAnswer((_) async => false); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: streamChannel, + ), + ); + + await tester.pump(const Duration(seconds: 1)); + + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + verify(mockChannel.initialized).called(1); + }, + ); + + testWidgets( + 'should preload extra messages if initialMessageId is provided', + (tester) async { + final mockChannel = MockChannel(); + const streamChannelKey = Key('streamChannel'); + const childKey = Key('childKey'); + final streamChannel = StreamChannel( + key: streamChannelKey, + channel: mockChannel, + child: Offstage(key: childKey), + initialMessageId: 'testInitialMessageId', + ); + + when(mockChannel.initialized).thenAnswer((_) async => true); + final messages = _generateMessages(); + when(mockChannel.query( + options: anyNamed('options'), + messagesPagination: anyNamed('messagesPagination'), + membersPagination: anyNamed('membersPagination'), + watchersPagination: anyNamed('watchersPagination'), + preferOffline: anyNamed('preferOffline'), + )).thenAnswer((_) async => ChannelState(messages: messages)); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: streamChannel, + ), + ); + + await tester.pumpAndSettle(); + + verify(mockChannel.initialized).called(1); + verify(mockChannel.query( + options: anyNamed('options'), + messagesPagination: anyNamed('messagesPagination'), + membersPagination: anyNamed('membersPagination'), + watchersPagination: anyNamed('watchersPagination'), + preferOffline: anyNamed('preferOffline'), + )).called( + 2, // Fetching After messages + Fetching Before messages, + ); + }, + ); + + testWidgets( + 'should rebuild StreamChannel with updated widget data ' + 'on calling setState()', + (tester) async { + StateSetter _stateSetter; + + var initialMessageId = 'testInitialMessageId'; + + final mockChannel = MockChannel(); + const streamChannelKey = Key('streamChannel'); + const childKey = Key('childKey'); + StreamChannel streamChannelBuilder(String initialMessageId) => + StreamChannel( + key: streamChannelKey, + channel: mockChannel, + child: Offstage(key: childKey), + initialMessageId: initialMessageId, + ); + + final beforePagination = PaginationParams( + lessThan: initialMessageId, + limit: 20, + ); + + final afterPagination = PaginationParams( + greaterThanOrEqual: initialMessageId, + limit: 20, + ); + + when(mockChannel.initialized).thenAnswer((_) async => true); + + final messages = _generateMessages(); + + when(mockChannel.query( + options: anyNamed('options'), + messagesPagination: beforePagination, + membersPagination: anyNamed('membersPagination'), + watchersPagination: anyNamed('watchersPagination'), + preferOffline: anyNamed('preferOffline'), + )).thenAnswer((_) async => ChannelState(messages: messages)); + + when(mockChannel.query( + options: anyNamed('options'), + messagesPagination: afterPagination, + membersPagination: anyNamed('membersPagination'), + watchersPagination: anyNamed('watchersPagination'), + preferOffline: anyNamed('preferOffline'), + )).thenAnswer((_) async => ChannelState(messages: messages)); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (context, stateSetter) { + // Assigning stateSetter for rebuilding StreamChannel + _stateSetter = stateSetter; + return streamChannelBuilder(initialMessageId); + }, + ), + ), + ); + + await tester.pumpAndSettle(); + + verify(mockChannel.query( + options: anyNamed('options'), + messagesPagination: beforePagination, + membersPagination: anyNamed('membersPagination'), + watchersPagination: anyNamed('watchersPagination'), + preferOffline: anyNamed('preferOffline'), + )).called(1); + + verify(mockChannel.query( + options: anyNamed('options'), + messagesPagination: afterPagination, + membersPagination: anyNamed('membersPagination'), + watchersPagination: anyNamed('watchersPagination'), + preferOffline: anyNamed('preferOffline'), + )).called(1); + + _stateSetter(() => initialMessageId = 'testInitialMessageId2'); + + final updatedBeforePagination = beforePagination.copyWith( + lessThan: initialMessageId, + ); + + final updatedAfterPagination = afterPagination.copyWith( + greaterThanOrEqual: initialMessageId, + ); + + when(mockChannel.query( + options: anyNamed('options'), + messagesPagination: updatedBeforePagination, + membersPagination: anyNamed('membersPagination'), + watchersPagination: anyNamed('watchersPagination'), + preferOffline: anyNamed('preferOffline'), + )).thenAnswer((_) async => ChannelState(messages: messages)); + + when(mockChannel.query( + options: anyNamed('options'), + messagesPagination: updatedAfterPagination, + membersPagination: anyNamed('membersPagination'), + watchersPagination: anyNamed('watchersPagination'), + preferOffline: anyNamed('preferOffline'), + )).thenAnswer((_) async => ChannelState(messages: messages)); + + await tester.pumpAndSettle(); + + verify(mockChannel.query( + options: anyNamed('options'), + messagesPagination: updatedBeforePagination, + membersPagination: anyNamed('membersPagination'), + watchersPagination: anyNamed('watchersPagination'), + preferOffline: anyNamed('preferOffline'), + )).called(1); + + verify(mockChannel.query( + options: anyNamed('options'), + messagesPagination: updatedAfterPagination, + membersPagination: anyNamed('membersPagination'), + watchersPagination: anyNamed('watchersPagination'), + preferOffline: anyNamed('preferOffline'), + )).called(1); + }, + ); +} diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index 52860c3e..4adc0062 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:fake_async/fake_async.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; @@ -8,169 +7,283 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'mocks.dart'; -class MockShowLocalNotifications extends Mock { +class MockOnBackgroundEventReceived extends Mock { void call(Event event); } void main() { - testWidgets( - 'StreamChatCore.of(context) should throw an exception', - (WidgetTester tester) async { - await tester.pumpWidget( - Builder( - builder: (context) { - expect(() => StreamChatCore.of(context), throwsException); - return Container(); - }, - ), - ); + test( + 'should throw assertion error in case client is null', + () { + final streamChatCore = () => StreamChatCore( + client: null, + child: Offstage(), + ); + expect(streamChatCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case child is null', + () { + final mockClient = MockClient(); + final streamChatCore = () => StreamChatCore( + client: mockClient, + child: null, + ); + expect(streamChatCore, throwsA(isA())); }, ); testWidgets( - 'StreamChatCore.of(context) should return the StreamChatCore ancestor', - (WidgetTester tester) async { - final client = MockClient(); - await tester.pumpWidget( - StreamChatCore( - client: client, - child: Builder( - builder: (context) { - final sc = StreamChatCore.of(context); - expect(sc, isNotNull); - return Container(); - }, - ), - ), + 'should render StreamChatCore if both client and child is provided', + (tester) async { + final mockClient = MockClient(); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), ); + + await tester.pumpWidget(streamChatCore); + + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); }, ); testWidgets( - 'StreamChatCore.of(context).client should return the client', - (WidgetTester tester) async { - final client = MockClient(); - final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn( - OwnUser( - id: 'test', - ), - ); - final userStream = Stream.value( - OwnUser( - id: 'test', - ), - ); - when(clientState.userStream).thenAnswer( - (_) => userStream, + 'should render StreamChatCore if both client and child is provided', + (tester) async { + final mockClient = MockClient(); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), ); - await tester.pumpWidget( - StreamChatCore( - client: client, - child: Builder( - builder: (context) { - final sc = StreamChatCore.of(context); - expect(sc.client, client); - expect(sc.user, client.state.user); - expect(sc.userStream, userStream); - return Container(); - }, - ), - ), - ); + await tester.pumpWidget(streamChatCore); + + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); }, ); testWidgets( - 'StreamChatCore should disconnect on background', - (WidgetTester tester) async { - fakeAsync((_async) { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn( - OwnUser( - id: 'test', - ), - ); - final showLocalNotificationMock = MockShowLocalNotifications().call; - final eventStreamController = StreamController(); - when(client.on()).thenAnswer((_) => eventStreamController.stream); + 'didChangeAppLifecycleState should call client.disconnect() and return ' + 'if onBackgroundEventReceived is null and the widget lifestyle changes to ' + 'AppLifecycleState.paused', + (tester) async { + final mockClient = MockClient(); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), + ); - when(client.channel('test', id: 'testid')).thenReturn(channel); + await tester.pumpWidget(streamChatCore); - final scKey = GlobalKey(); - tester.pumpWidget( - StreamChatCore( - key: scKey, - client: client, - onBackgroundEventReceived: showLocalNotificationMock, - backgroundKeepAlive: const Duration(seconds: 4), - child: Builder( - builder: (context) => Container(), - ), - ), + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + + when(mockClient.disconnect()).thenAnswer((_) async { + return; + }); + + final streamChatCoreState = tester.state( + find.byKey(streamChatCoreKey), + ); + + streamChatCoreState.didChangeAppLifecycleState(AppLifecycleState.paused); + + verify(mockClient.disconnect()).called(1); + }, + ); + + testWidgets( + 'didChangeAppLifecycleState should subscribe to client events for ' + 'backgroundKeepAlive duration if onBackgroundEventReceived is provided ' + 'and the widget lifestyle changes to AppLifecycleState.paused', + (tester) async { + await tester.runAsync(() async { + final mockClient = MockClient(); + final mockOnBackgroundEventReceived = MockOnBackgroundEventReceived(); + const backgroundKeepAlive = const Duration(seconds: 3); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), + onBackgroundEventReceived: mockOnBackgroundEventReceived, + backgroundKeepAlive: backgroundKeepAlive, ); - scKey.currentState.didChangeAppLifecycleState(AppLifecycleState.paused); + await tester.pumpWidget(streamChatCore); - _async.elapse(const Duration(seconds: 5)); + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); - verify(client.disconnect()).called(1); - eventStreamController.close(); + final event = Event(); + when(mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(mockClient.disconnect()).thenAnswer((_) async { + return; + }); + + final streamChatCoreState = tester.state( + find.byKey(streamChatCoreKey), + ); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.paused); + + await untilCalled(mockOnBackgroundEventReceived.call(event)); + + verify(mockOnBackgroundEventReceived.call(event)).called(1); + + await Future.delayed(backgroundKeepAlive); + + verify(mockClient.disconnect()).called(1); + verifyNever(mockOnBackgroundEventReceived.call(event)); }); }, ); testWidgets( - 'StreamChatCore should handle notifications when on background and ' - 'connected', - (WidgetTester tester) async { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn( - OwnUser( - id: 'test', - ), - ); - final showLocalNotificationMock = MockShowLocalNotifications().call; - final eventStreamController = StreamController(); - when(client.on()).thenAnswer((_) => eventStreamController.stream); + 'didChangeAppLifecycleState should cancel the backgroundKeepAlive timer ' + 'if it is currently running in case the widget lifestyle changes to ' + 'AppLifecycleState.resume', + (tester) async { + await tester.runAsync(() async { + final mockClient = MockClient(); + final mockOnBackgroundEventReceived = MockOnBackgroundEventReceived(); + const backgroundKeepAlive = const Duration(seconds: 3); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), + onBackgroundEventReceived: mockOnBackgroundEventReceived, + backgroundKeepAlive: backgroundKeepAlive, + ); - when(client.channel('test', id: 'testid')).thenReturn(channel); + await tester.pumpWidget(streamChatCore); - final scKey = GlobalKey(); - await tester.pumpWidget( - StreamChatCore( - key: scKey, - client: client, - onBackgroundEventReceived: showLocalNotificationMock, - backgroundKeepAlive: const Duration(seconds: 4), - child: Builder( - builder: (context) => Container(), - ), - ), - ); + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); - scKey.currentState.didChangeAppLifecycleState(AppLifecycleState.paused); - final event = Event( - type: EventType.messageNew, - message: Message(text: 'hey'), - channelType: 'test', - channelId: 'testid', - user: User(id: 'other user'), - ); - eventStreamController.add(event); + final event = Event(); + when(mockClient.on()).thenAnswer((_) => Stream.value(event)); - await untilCalled(showLocalNotificationMock(event)); + final streamChatCoreState = tester.state( + find.byKey(streamChatCoreKey), + ); - verify(showLocalNotificationMock(event)).called(1); - eventStreamController.close(); + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.paused); + + await untilCalled(mockOnBackgroundEventReceived.call(event)); + + verify(mockOnBackgroundEventReceived.call(event)).called(1); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.resumed); + + verifyNever(mockOnBackgroundEventReceived.call(event)); + }); + }, + ); + + testWidgets( + 'didChangeAppLifecycleState should call client.connect() ' + 'if the connectionStatus is ConnectionStatus.disconnected in case the ' + 'widget lifestyle changes to AppLifecycleState.resume', + (tester) async { + await tester.runAsync(() async { + final mockClient = MockClient(); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), + ); + + await tester.pumpWidget(streamChatCore); + + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + + final event = Event(); + when(mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(mockClient.connect()).thenAnswer((_) async => event); + when(mockClient.wsConnectionStatus) + .thenReturn(ConnectionStatus.disconnected); + + final streamChatCoreState = tester.state( + find.byKey(streamChatCoreKey), + ); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.paused); + + await Future.delayed(const Duration(seconds: 1)); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.resumed); + + verify(mockClient.connect()).called(1); + }); + }, + ); + + testWidgets( + 'streamChatCoreState.userStream should emit all the user events ' + 'provided by client', + (tester) async { + await tester.runAsync(() async { + final userController = StreamController(); + + final mockClient = MockClient(); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), + ); + + await tester.pumpWidget(streamChatCore); + + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + + when(mockClient.state.userStream) + .thenAnswer((_) => userController.stream); + + final streamChatCoreState = tester.state( + find.byKey(streamChatCoreKey), + ); + + final ownUser = OwnUser(id: 'testUserId'); + userController.add(ownUser); + + await expectLater( + streamChatCoreState.userStream, + emits(ownUser), + ); + + addTearDown(() { + userController.close(); + }); + }); }, ); } diff --git a/packages/stream_chat_flutter_core/test/user_list_core_test.dart b/packages/stream_chat_flutter_core/test/user_list_core_test.dart new file mode 100644 index 00000000..d1c7e83a --- /dev/null +++ b/packages/stream_chat_flutter_core/test/user_list_core_test.dart @@ -0,0 +1,572 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter_core/src/user_list_core.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +import 'mocks.dart'; + +void main() { + const alphabets = 'abcdefghijklmnopqrstuvwxyz'; + + List _generateUsers({ + int count = 3, + int offset = 0, + }) { + return List.generate( + count, + (index) { + index = index + offset; + return User( + id: 'testId$index', + role: 'testRole$index', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + lastActive: DateTime.now(), + online: true, + banned: false, + extraData: { + 'name': '${alphabets[index]}-testName', + }, + ); + }, + ); + } + + test( + 'should throw assertion error in case listBuilder is null', + () { + final userListCore = () => UserListCore( + listBuilder: null, + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (Object error) => Offstage(), + ); + expect(userListCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case loadingBuilder is null', + () { + final userListCore = () => UserListCore( + listBuilder: (_, __) => Offstage(), + loadingBuilder: null, + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (Object error) => Offstage(), + ); + expect(userListCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case emptyBuilder is null', + () { + final userListCore = () => UserListCore( + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: null, + errorBuilder: (Object error) => Offstage(), + ); + expect(userListCore, throwsA(isA())); + }, + ); + + test( + 'should throw assertion error in case errorBuilder is null', + () { + final userListCore = () => UserListCore( + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: null, + ); + expect(userListCore, throwsA(isA())); + }, + ); + + testWidgets( + 'should throw if UserListCore is used where UsersBloc is not present ' + 'in the widget tree', + (tester) async { + const userListCoreKey = Key('userListCore'); + final userListCore = UserListCore( + key: userListCoreKey, + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (Object error) => Offstage(), + ); + + await tester.pumpWidget(userListCore); + + expect(find.byKey(userListCoreKey), findsNothing); + expect(tester.takeException(), isInstanceOf()); + }, + ); + + testWidgets( + 'should render UserListCore if used with UsersBloc as an ancestor', + (tester) async { + const userListCoreKey = Key('userListCore'); + final userListCore = UserListCore( + key: userListCoreKey, + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (Object error) => Offstage(), + ); + + final mockClient = MockClient(); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: UsersBloc( + child: userListCore, + ), + ), + ); + + expect(find.byKey(userListCoreKey), findsOneWidget); + }, + ); + + testWidgets( + 'should assign loadData and paginateData callback to ' + 'UserListController if passed', + (tester) async { + const userListCoreKey = Key('userListCore'); + final controller = UserListController(); + final userListCore = UserListCore( + key: userListCoreKey, + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (Object error) => Offstage(), + userListController: controller, + ); + + expect(controller.loadData, isNull); + expect(controller.paginateData, isNull); + + final mockClient = MockClient(); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: UsersBloc( + child: userListCore, + ), + ), + ); + + expect(find.byKey(userListCoreKey), findsOneWidget); + expect(controller.loadData, isNotNull); + expect(controller.paginateData, isNotNull); + }, + ); + + testWidgets( + 'should build error widget if usersBlocState.usersStream emits error', + (tester) async { + const userListCoreKey = Key('userListCore'); + const errorWidgetKey = Key('errorWidget'); + final userListCore = UserListCore( + key: userListCoreKey, + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (Object error) => Container(key: errorWidgetKey), + ); + + final mockClient = MockClient(); + + const error = 'Error! Error! Error!'; + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).thenThrow(error); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: UsersBloc( + child: userListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(errorWidgetKey), findsOneWidget); + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).called(1); + }, + ); + + testWidgets( + 'should build empty widget if usersBlocState.usersStream emits empty data', + (tester) async { + const userListCoreKey = Key('userListCore'); + const emptyWidgetKey = Key('emptyWidget'); + final userListCore = UserListCore( + key: userListCoreKey, + listBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Container(key: emptyWidgetKey), + errorBuilder: (Object error) => Offstage(), + ); + + final mockClient = MockClient(); + + const users = []; + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: UsersBloc( + child: userListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(emptyWidgetKey), findsOneWidget); + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).called(1); + }, + ); + + testWidgets( + 'should build list widget if usersBlocState.usersStream emits some data', + (tester) async { + const userListCoreKey = Key('userListCore'); + const listWidgetKey = Key('listWidget'); + final userListCore = UserListCore( + key: userListCoreKey, + listBuilder: (_, __) => Container(key: listWidgetKey), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (Object error) => Offstage(), + ); + + final mockClient = MockClient(); + + final users = _generateUsers(); + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: UsersBloc( + child: userListCore, + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(listWidgetKey), findsOneWidget); + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).called(1); + }, + ); + + testWidgets( + 'should build list widget with grouped data if groupAlphabetically is true', + (tester) async { + const userListCoreKey = Key('userListCore'); + const listWidgetKey = Key('listWidget'); + final userListCore = UserListCore( + key: userListCoreKey, + listBuilder: (_, items) => Container( + key: listWidgetKey, + child: ListView( + children: items.map((e) { + return Container( + key: Key(e.key), + child: e.when( + headerItem: (heading) => Text(heading), + userItem: (user) => Text(user.id), + ), + ); + }).toList(growable: false), + ), + ), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (Object error) => Offstage(), + groupAlphabetically: true, + ); + + final mockClient = MockClient(); + + final users = _generateUsers(); + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StreamChatCore( + client: mockClient, + child: UsersBloc( + child: userListCore, + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(listWidgetKey), findsOneWidget); + for (final user in users) { + expect(find.byKey(Key('HEADER-${user.name[0]}')), findsOneWidget); + expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); + } + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).called(1); + }, + ); + + testWidgets( + 'should build list widget with paginated data ' + 'on calling channelListCoreState.paginateData', + (tester) async { + const userListCoreKey = Key('userListCore'); + const listWidgetKey = Key('listWidget'); + const pagination = PaginationParams(); + final userListCore = UserListCore( + key: userListCoreKey, + listBuilder: (_, items) => Container( + key: listWidgetKey, + child: ListView( + children: items.map((e) { + return Container( + key: Key(e.key), + child: e.when( + headerItem: (heading) => Text(heading), + userItem: (user) => Text(user.id), + ), + ); + }).toList(growable: false), + ), + ), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (Object error) => Offstage(), + pagination: pagination, + groupAlphabetically: true, + ); + + final mockClient = MockClient(); + + final users = _generateUsers(); + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StreamChatCore( + client: mockClient, + child: UsersBloc( + child: userListCore, + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(listWidgetKey), findsOneWidget); + for (final user in users) { + expect(find.byKey(Key('HEADER-${user.name[0]}')), findsOneWidget); + expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); + } + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).called(1); + + final userListCoreState = tester.state( + find.byKey(userListCoreKey), + ); + + final offset = users.length; + final paginatedUsers = _generateUsers(offset: offset); + final updatedPagination = pagination.copyWith(offset: offset); + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: updatedPagination, + )).thenAnswer((_) async => QueryUsersResponse()..users = paginatedUsers); + + await userListCoreState.paginateData(); + + await tester.pumpAndSettle(); + + for (final user in users + paginatedUsers) { + expect(find.byKey(Key('HEADER-${user.name[0]}')), findsOneWidget); + expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); + } + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: updatedPagination, + )).called(1); + }, + ); + + testWidgets( + 'should rebuild UserListCore with updated widget data ' + 'on calling setState()', + (tester) async { + const pagination = PaginationParams(); + + StateSetter _stateSetter; + int limit = pagination.limit; + + const userListCoreKey = Key('userListCore'); + const listWidgetKey = Key('listWidget'); + UserListCore userListCoreBuilder(int limit) => UserListCore( + key: userListCoreKey, + listBuilder: (_, items) => Container( + key: listWidgetKey, + child: ListView( + children: items.map((e) { + return Container( + key: Key(e.key), + child: e.when( + headerItem: (heading) => Text(heading), + userItem: (user) => Text(user.id), + ), + ); + }).toList(growable: false), + ), + ), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorBuilder: (Object error) => Offstage(), + pagination: pagination.copyWith(limit: limit), + groupAlphabetically: true, + ); + + final mockClient = MockClient(); + + final users = _generateUsers(); + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StreamChatCore( + client: mockClient, + child: UsersBloc( + child: StatefulBuilder(builder: (context, stateSetter) { + // Assigning stateSetter for rebuilding UserListCore + _stateSetter = stateSetter; + return userListCoreBuilder(limit); + }), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(listWidgetKey), findsOneWidget); + for (final user in users) { + expect(find.byKey(Key('HEADER-${user.name[0]}')), findsOneWidget); + expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); + } + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).called(1); + + // Rebuilding UserListCore with new pagination limit + _stateSetter(() => limit = 6); + + final updatedUsers = _generateUsers(count: limit); + final updatedPagination = pagination.copyWith(limit: limit); + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: updatedPagination, + )).thenAnswer((_) async => QueryUsersResponse()..users = updatedUsers); + + await tester.pumpAndSettle(); + + for (final user in updatedUsers) { + expect(find.byKey(Key('HEADER-${user.name[0]}')), findsOneWidget); + expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); + } + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: updatedPagination, + )).called(1); + }, + ); +} diff --git a/packages/stream_chat_flutter_core/test/users_bloc_test.dart b/packages/stream_chat_flutter_core/test/users_bloc_test.dart new file mode 100644 index 00000000..e9ccd2e7 --- /dev/null +++ b/packages/stream_chat_flutter_core/test/users_bloc_test.dart @@ -0,0 +1,327 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; +import 'package:stream_chat_flutter_core/src/users_bloc.dart'; +import 'package:mockito/mockito.dart'; + +import 'matchers/users_matcher.dart'; +import 'mocks.dart'; + +void main() { + List _generateUsers({ + int count = 3, + int offset = 0, + }) { + return List.generate( + count, + (index) { + index = index + offset; + return User( + id: 'testId$index', + role: 'testRole$index', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + lastActive: DateTime.now(), + online: true, + banned: false, + extraData: {'extra_data_key': 'extraDataValue'}, + ); + }, + ); + } + + test( + 'should throw assertion error if child is null', + () async { + const usersBlocKey = Key('usersBloc'); + final usersBloc = () => UsersBloc( + key: usersBlocKey, + child: null, + ); + expect(usersBloc, throwsA(isA())); + }, + ); + + testWidgets( + 'usersBlocState.queryUsers() should throw if used where ' + 'StreamChat is not present in the widget tree', + (tester) async { + const usersBlocKey = Key('usersBloc'); + const childKey = Key('child'); + final usersBloc = UsersBloc( + key: usersBlocKey, + child: Offstage(key: childKey), + ); + + await tester.pumpWidget(usersBloc); + + expect(find.byKey(usersBlocKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + + final usersBlocState = tester.state( + find.byKey(usersBlocKey), + ); + + try { + await usersBlocState.queryUsers(); + } catch (e) { + expect(e, isInstanceOf()); + } + }, + ); + + testWidgets( + 'usersBlocState.queryUsers() should emit data through usersStream', + (tester) async { + const usersBlocKey = Key('usersBloc'); + const childKey = Key('child'); + final usersBloc = UsersBloc( + key: usersBlocKey, + child: Offstage(key: childKey), + ); + + final mockClient = MockClient(); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: usersBloc, + ), + ); + + final usersBlocState = tester.state( + find.byKey(usersBlocKey), + ); + + final users = _generateUsers(); + + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); + + usersBlocState.queryUsers(); + + await expectLater( + usersBlocState.usersStream, + emits(isSameUserListAs(users)), + ); + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).called(1); + }, + ); + + testWidgets( + 'usersBlocState.usersStream should emit error ' + 'if client.queryUsers() throws', + (tester) async { + const usersBlocKey = Key('usersBloc'); + const childKey = Key('child'); + final usersBloc = UsersBloc( + key: usersBlocKey, + child: Offstage(key: childKey), + ); + + final mockClient = MockClient(); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: usersBloc, + ), + ); + + final usersBlocState = tester.state( + find.byKey(usersBlocKey), + ); + + final error = 'Error! Error! Error!'; + + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).thenThrow(error); + + usersBlocState.queryUsers(); + + await expectLater( + usersBlocState.usersStream, + emitsError(error), + ); + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).called(1); + }, + ); + + testWidgets( + 'calling usersBlocState.queryUsers() again with an offset ' + 'should emit new data through usersStream and also emit loading state ' + 'through queryUsersLoading', + (tester) async { + const usersBlocKey = Key('usersBloc'); + const childKey = Key('child'); + final usersBloc = UsersBloc( + key: usersBlocKey, + child: Offstage(key: childKey), + ); + + final mockClient = MockClient(); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: usersBloc, + ), + ); + + final usersBlocState = tester.state( + find.byKey(usersBlocKey), + ); + + final users = _generateUsers(); + + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); + + usersBlocState.queryUsers(); + + await expectLater( + usersBlocState.usersStream, + emits(isSameUserListAs(users)), + ); + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).called(1); + + final offset = users.length; + final paginatedUsers = _generateUsers(offset: offset); + final pagination = PaginationParams(offset: offset); + + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: pagination, + )).thenAnswer((_) async => QueryUsersResponse()..users = paginatedUsers); + + usersBlocState.queryUsers(pagination: pagination); + + await Future.wait([ + expectLater( + usersBlocState.queryUsersLoading, + emitsInOrder([true, false]), + ), + expectLater( + usersBlocState.usersStream, + emits(isSameUserListAs(users + paginatedUsers)), + ), + ]); + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: pagination, + )).called(1); + }, + ); + + testWidgets( + 'calling usersBlocState.queryUsers() again with an offset ' + 'should emit error through queryUsersLoading if ' + 'client.queryUsers() throws', + (tester) async { + const usersBlocKey = Key('usersBloc'); + const childKey = Key('child'); + final usersBloc = UsersBloc( + key: usersBlocKey, + child: Offstage(key: childKey), + ); + + final mockClient = MockClient(); + + await tester.pumpWidget( + StreamChatCore( + client: mockClient, + child: usersBloc, + ), + ); + + final usersBlocState = tester.state( + find.byKey(usersBlocKey), + ); + + final users = _generateUsers(); + + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); + + usersBlocState.queryUsers(); + + await expectLater( + usersBlocState.usersStream, + emits(isSameUserListAs(users)), + ); + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: anyNamed('pagination'), + )).called(1); + + final offset = users.length; + final pagination = PaginationParams(offset: offset); + + final error = 'Error! Error! Error!'; + + when(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: pagination, + )).thenThrow(error); + + usersBlocState.queryUsers(pagination: pagination); + + await expectLater( + usersBlocState.queryUsersLoading, + emitsError(error), + ); + + verify(mockClient.queryUsers( + filter: anyNamed('filter'), + sort: anyNamed('sort'), + options: anyNamed('options'), + pagination: pagination, + )).called(1); + }, + ); +} From 3dcf1f5eb4fd6ca7fa7cc59557bea801fadfcd17 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 30 Mar 2021 18:41:20 +0530 Subject: [PATCH 063/599] refactor(ui-kit): update giphy interaction (#361) * refactor(ui-kit): update giphy interaction Signed-off-by: Sahil Kumar * fix overflow Co-authored-by: Salvatore Giordano --- .../lib/src/attachment/giphy_attachment.dart | 205 +++++++----------- 1 file changed, 77 insertions(+), 128 deletions(-) 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 5bb810b4..8b8bb4c1 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -59,138 +59,57 @@ class GiphyAttachment extends AttachmentWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Stack( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onTap: () => onAttachmentTap ?? _onImageTap(context), - child: ClipRRect( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(8), - topRight: Radius.circular(8), - ), - child: CachedNetworkImage( - height: size?.height, - width: size?.width, - placeholder: (_, __) { - return Container( - width: size?.width, - height: size?.height, - child: Center( - child: CircularProgressIndicator(), - ), - ); - }, - imageUrl: imageUrl, - errorWidget: (context, url, error) { - return AttachmentError(size: size); - }, - fit: BoxFit.cover, - ), - ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + StreamSvgIcon.giphyIcon(), + SizedBox(width: 8), + Text( + 'Giphy', + style: TextStyle(fontWeight: FontWeight.bold), ), - ), - Positioned( - bottom: 16, - left: 16, - child: Material( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 4.0, - ), - child: Row( - children: [ - StreamSvgIcon.lightning( - color: - StreamChatTheme.of(context).colorTheme.white, - size: 16, - ), - Text( - 'GIPHY', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .white, - fontWeight: FontWeight.bold, - fontSize: 11, - ), - ), - ], + SizedBox(width: 8), + if (attachment.title != null) + Flexible( + child: Text( + attachment.title, + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, ), ), - ), - ), - ], + ], + ), ), - if (attachment.title != null) - Container( - alignment: Alignment.bottomCenter, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Card( - color: Colors.white, - elevation: 2, - shape: CircleBorder(), - child: IconButton( - padding: const EdgeInsets.all(0), - constraints: BoxConstraints.tight(Size(32, 32)), - icon: StreamSvgIcon.left( - size: 24.0, - ), - splashRadius: 16, - onPressed: () { - streamChannel.channel.sendAction(message, { - 'image_action': 'shuffle', - }); - }, - ), + Padding( + padding: const EdgeInsets.all(2.0), + child: GestureDetector( + onTap: () => onAttachmentTap ?? _onImageTap(context), + child: CachedNetworkImage( + height: size?.height, + width: size?.width, + placeholder: (_, __) { + return Container( + width: size?.width, + height: size?.height, + child: Center( + child: CircularProgressIndicator(), ), - Expanded( - child: Center( - child: Text( - '"${attachment.title}"', - style: TextStyle( - fontStyle: FontStyle.italic, - ), - ), - ), - ), - Card( - color: Colors.white, - elevation: 2, - shape: CircleBorder(), - child: IconButton( - padding: const EdgeInsets.all(0), - constraints: BoxConstraints.tight(Size(32, 32)), - icon: StreamSvgIcon.right( - size: 24.0, - ), - splashRadius: 16, - onPressed: () { - streamChannel.channel.sendAction(message, { - 'image_action': 'shuffle', - }); - }, - ), - ), - ], - ), + ); + }, + imageUrl: imageUrl, + errorWidget: (context, url, error) { + return AttachmentError(size: size); + }, + fit: BoxFit.cover, ), ), - SizedBox( - height: 4.0, ), Container( color: StreamChatTheme.of(context) @@ -236,6 +155,38 @@ class GiphyAttachment extends AttachmentWidget { .withOpacity(0.2), height: 50.0, ), + Expanded( + child: Container( + height: 50, + child: TextButton( + onPressed: () { + streamChannel.channel.sendAction(message, { + 'image_action': 'shuffle', + }); + }, + child: Text( + 'Shuffle', + style: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + ), + ), + ), + ), + ), + Container( + width: 0.5, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.2), + height: 50.0, + ), Expanded( child: Container( height: 50, @@ -261,9 +212,7 @@ class GiphyAttachment extends AttachmentWidget { ], ), ), - SizedBox( - height: 4.0, - ), + SizedBox(height: 4.0), Align( alignment: Alignment.centerRight, child: Padding( From 5ae5b49f9e20188385170284777f744983948a29 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 30 Mar 2021 18:52:24 +0530 Subject: [PATCH 064/599] fix: Fixed sendingbutton.outside case (#355) * fix: Fixed sendingbutton.outside case * fix: Fixed all actionbutton cases * fix review Co-authored-by: Salvatore Giordano --- packages/stream_chat_flutter/lib/src/message_input.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 15bb3c3d..758be5f1 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -491,10 +491,17 @@ class MessageInputState extends State { Expanded _buildTextInput(BuildContext context) { final theme = StreamChatTheme.of(context); + final margin = (widget.sendButtonLocation == SendButtonLocation.inside + ? const EdgeInsets.only(right: 8.0) + : EdgeInsets.zero) + + (widget.actionsLocation != ActionsLocation.left + ? const EdgeInsets.only(left: 8.0) + : EdgeInsets.zero); return Expanded( child: Center( child: Container( clipBehavior: Clip.antiAlias, + margin: margin, decoration: BoxDecoration( borderRadius: theme.messageInputTheme.borderRadius, gradient: _focusNode.hasFocus From fec7cfa572bf79a33fefed167eed30ef56b5f987 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 31 Mar 2021 12:50:41 +0530 Subject: [PATCH 065/599] fix(ui-kit): fix accessibility text size scaling in message widget Signed-off-by: Sahil Kumar --- packages/stream_chat_flutter/lib/src/extension.dart | 5 +++++ .../lib/src/message_search_list_view.dart | 6 ++---- .../stream_chat_flutter/lib/src/message_widget.dart | 7 +++++-- .../stream_chat_flutter/lib/src/user_list_view.dart | 10 +++------- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index 65b1b100..daa8a69c 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -97,3 +97,8 @@ extension InputDecorationX on InputDecoration { ); } } + +extension BuildContextX on BuildContext { + double get textScaleFactor => + MediaQuery.maybeOf(this)?.textScaleFactor ?? 1.0; +} 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 50131b87..f9dd0fa1 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 @@ -262,15 +262,13 @@ class _MessageSearchListViewState extends State { ); if (widget.pullToRefresh) { child = RefreshIndicator( - onRefresh: () => _messageSearchListController.loadData(), + onRefresh: _messageSearchListController.loadData, child: child, ); } child = LazyLoadScrollView( - onEndOfPage: () async { - return _messageSearchListController.paginateData(); - }, + onEndOfPage: _messageSearchListController.paginateData, child: child, ); diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index a0c1201a..f3c25c3d 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -585,7 +585,10 @@ class _MessageWidgetState extends State ), ], ), - if (showBottomRow) SizedBox(height: 20.0), + if (showBottomRow) + SizedBox( + height: context.textScaleFactor * 20.0, + ), ], ), if (showBottomRow) @@ -733,7 +736,7 @@ class _MessageWidgetState extends State transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, child: Container( - height: 16, + height: context.textScaleFactor * 16, child: Center( child: child, ), 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 5559f67e..ac211c68 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -193,7 +193,7 @@ class _UserListViewState extends State return child; } else { return RefreshIndicator( - onRefresh: () => _userListController.loadData(), + onRefresh: _userListController.loadData, child: child, ); } @@ -241,9 +241,7 @@ class _UserListViewState extends State child: Text(message), ), TextButton( - onPressed: () { - _userListController.loadData(); - }, + onPressed: () => _userListController.loadData(), child: Text('Retry'), ), ], @@ -298,9 +296,7 @@ class _UserListViewState extends State ); return LazyLoadScrollView( - onEndOfPage: () async { - return _userListController.paginateData(); - }, + onEndOfPage: _userListController.paginateData, child: child, ); } From 8b58ac06ffaf70ba1ce0a12d27c4c965cb82d8d8 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 31 Mar 2021 16:58:24 +0530 Subject: [PATCH 066/599] fix(ui-kit): fix message widget long username overflow Signed-off-by: Sahil Kumar --- .../lib/src/message_widget.dart | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index f3c25c3d..af6ea1ec 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -586,9 +586,7 @@ class _MessageWidgetState extends State ], ), if (showBottomRow) - SizedBox( - height: context.textScaleFactor * 20.0, - ), + SizedBox(height: context.textScaleFactor * 18.0), ], ), if (showBottomRow) @@ -685,6 +683,8 @@ class _MessageWidgetState extends State } }; + const usernameKey = Key('username'); + children.addAll([ if (showInChannel || showThreadReplyIndicator) ...[ if (showThreadParticipants) @@ -700,7 +700,10 @@ class _MessageWidgetState extends State if (showUsername) Text( widget.message.user.name, + maxLines: 1, + key: usernameKey, style: widget.messageTheme.messageAuthor, + overflow: TextOverflow.ellipsis, ), if (showTimeStamp) Text( @@ -713,18 +716,17 @@ class _MessageWidgetState extends State final showThreadTail = !(hasUrlAttachments || isGiphy || isOnlyEmoji) && (showThreadReplyIndicator || showInChannel); - return Flex( - direction: Axis.horizontal, - clipBehavior: Clip.none, + return Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ if (showThreadTail) Container( margin: EdgeInsets.only( - bottom: widget.messageTheme.replies.fontSize / 2, + bottom: context.textScaleFactor * + (widget.messageTheme.replies.fontSize / 2), ), child: CustomPaint( - size: const Size(16, 32), + size: Size(16, 32) * context.textScaleFactor, painter: _ThreadReplyPainter( context: context, color: widget.messageTheme.messageBorderColor, @@ -732,16 +734,20 @@ class _MessageWidgetState extends State ), ), ...children.map( - (child) => Transform( - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - alignment: Alignment.center, - child: Container( - height: context.textScaleFactor * 16, - child: Center( + (child) { + Widget mappedChild = Transform( + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + alignment: Alignment.center, + child: Container( + height: context.textScaleFactor * 14, child: child, ), - ), - ), + ); + if (child.key == usernameKey) { + mappedChild = Flexible(child: mappedChild); + } + return mappedChild; + }, ), ].insertBetween(const SizedBox(width: 8.0)), ); From 2014c2523530b9f29822c84d2b93c7c00d1a0a1e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 31 Mar 2021 17:05:07 +0530 Subject: [PATCH 067/599] fix(ui-kit): fix message input dm checkbox accessibility scaling issue Signed-off-by: Sahil Kumar --- .../lib/src/message_input.dart | 116 +++++++++--------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 15bb3c3d..2773c385 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -319,7 +319,11 @@ class MessageInputState extends State { ), if (widget.parentMessage != null && !widget.hideSendAsDm) Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), + padding: const EdgeInsets.only( + right: 12.0, + left: 12.0, + bottom: 12.0, + ), child: _buildDmCheckbox(), ), _buildFilePickerSection(), @@ -354,68 +358,68 @@ class MessageInputState extends State { } Widget _buildDmCheckbox() { - return Container( - height: 36, - padding: const EdgeInsets.only( - left: 12, - bottom: 12, - top: 8, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - height: 16, - width: 16, - foregroundDecoration: BoxDecoration( - border: _sendAsDm - ? null - : Border.all( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - width: 2, - ), + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 16, + width: 16, + foregroundDecoration: BoxDecoration( + border: _sendAsDm + ? null + : Border.all( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + width: 2, + ), + borderRadius: BorderRadius.circular(3), + ), + child: Center( + child: Material( borderRadius: BorderRadius.circular(3), - ), - child: Center( - child: Material( - borderRadius: BorderRadius.circular(3), - color: _sendAsDm - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context).colorTheme.white, - child: InkWell( - onTap: () { - setState(() { - _sendAsDm = !_sendAsDm; - }); - }, - child: AnimatedCrossFade( - duration: Duration(milliseconds: 300), - reverseDuration: Duration(milliseconds: 300), - crossFadeState: _sendAsDm - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: StreamSvgIcon.check( - size: 16.0, - color: StreamChatTheme.of(context).colorTheme.white, - ), - secondChild: SizedBox( - height: 16, - width: 16, - ), + color: _sendAsDm + ? StreamChatTheme.of(context).colorTheme.accentBlue + : StreamChatTheme.of(context).colorTheme.white, + child: InkWell( + onTap: () { + setState(() { + _sendAsDm = !_sendAsDm; + }); + }, + child: AnimatedCrossFade( + duration: Duration(milliseconds: 300), + reverseDuration: Duration(milliseconds: 300), + crossFadeState: _sendAsDm + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: StreamSvgIcon.check( + size: 16.0, + color: StreamChatTheme.of(context).colorTheme.white, + ), + secondChild: SizedBox( + height: 16, + width: 16, ), ), ), ), ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Text('Also send as direct message'), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Text( + 'Also send as direct message', + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + ), ), - ], - ), + ), + ], ); } From d455e3c96e666713e52df9b55744e484c06fdaef Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 31 Mar 2021 19:48:40 +0530 Subject: [PATCH 068/599] feat: exposed textbuilder callback (#367) * exposed textbuilder callback * exposed textbuilder callback --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 5b1c3a99..89cc8910 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -141,6 +141,7 @@ class MessageListView extends StatefulWidget { this.onMessageTap, this.onSystemMessageTap, this.onAttachmentTap, + this.textBuilder, }) : super(key: key); /// Function used to build a custom message widget @@ -233,6 +234,9 @@ class MessageListView extends StatefulWidget { // Customize onTap on attachment final void Function(Message message, Attachment attachment) onAttachmentTap; + // Customize the MessageWidget textBuilder + final void Function(BuildContext context, Message message) textBuilder; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -839,6 +843,7 @@ class _MessageListViewState extends State { } FocusScope.of(context).unfocus(); }, + textBuilder: widget.textBuilder, ); } @@ -1015,6 +1020,7 @@ class _MessageListViewState extends State { FocusScope.of(context).unfocus(); }, onAttachmentTap: widget.onAttachmentTap, + textBuilder: widget.textBuilder, ); if (!message.isDeleted && From d34ce810967a4fa4d5b1db766dfe53aa9aeb045d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 31 Mar 2021 20:04:01 +0530 Subject: [PATCH 069/599] fixes Signed-off-by: Sahil Kumar --- .../stream_chat_flutter/lib/src/message_search_list_view.dart | 4 ++-- packages/stream_chat_flutter/lib/src/user_list_view.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart index f9dd0fa1..6431a311 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 @@ -262,13 +262,13 @@ class _MessageSearchListViewState extends State { ); if (widget.pullToRefresh) { child = RefreshIndicator( - onRefresh: _messageSearchListController.loadData, + onRefresh: () => _messageSearchListController.loadData(), child: child, ); } child = LazyLoadScrollView( - onEndOfPage: _messageSearchListController.paginateData, + onEndOfPage: () => _messageSearchListController.paginateData(), child: child, ); 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 ac211c68..d4bc2f72 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -193,7 +193,7 @@ class _UserListViewState extends State return child; } else { return RefreshIndicator( - onRefresh: _userListController.loadData, + onRefresh: () => _userListController.loadData(), child: child, ); } @@ -296,7 +296,7 @@ class _UserListViewState extends State ); return LazyLoadScrollView( - onEndOfPage: _userListController.paginateData, + onEndOfPage: () => _userListController.paginateData(), child: child, ); } From 73660d376012549f4dc2f1e42bfd29a8dd166b3d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 1 Apr 2021 11:17:48 +0200 Subject: [PATCH 070/599] check if user.mutes is null --- packages/stream_chat/lib/src/api/channel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 96fb70db..83a470cd 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1598,7 +1598,7 @@ class ChannelClientState { bool _countMessageAsUnread(Message message) { final userId = _channel.client.state?.user?.id; - final userIsMuted = _channel.client.state.user.mutes.firstWhere( + final userIsMuted = _channel.client.state.user?.mutes?.firstWhere( (m) => m.user?.id == message.user.id, orElse: () => null, ) != From b32bf7a8f11cf052743e9d7948fbbd070e2e4524 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 1 Apr 2021 11:18:46 +0200 Subject: [PATCH 071/599] check if user.mutes is null --- packages/stream_chat/lib/src/api/channel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 83a470cd..b424f6f1 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1598,7 +1598,7 @@ class ChannelClientState { bool _countMessageAsUnread(Message message) { final userId = _channel.client.state?.user?.id; - final userIsMuted = _channel.client.state.user?.mutes?.firstWhere( + final userIsMuted = _channel.client.state?.user?.mutes?.firstWhere( (m) => m.user?.id == message.user.id, orElse: () => null, ) != From 425fd5c4d8d86f2a5dceb0ef1f70de61ffa34e3d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 1 Apr 2021 12:01:50 +0200 Subject: [PATCH 072/599] fixes #370: remove all controls on full screen media if message is of type ephemeral (#373) --- .../lib/src/full_screen_media.dart | 31 +++++----- .../lib/src/image_footer.dart | 7 ++- .../lib/src/image_header.dart | 61 ++++++++++--------- 3 files changed, 54 insertions(+), 45 deletions(-) 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 91c66ca4..aa35ed15 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -185,21 +185,22 @@ class _FullScreenMediaState extends State widget.message, StreamChannel.of(context).channel); }, ), - ImageFooter( - currentPage: _currentPage, - totalPages: widget.mediaAttachments.length, - mediaAttachments: widget.mediaAttachments, - message: widget.message, - mediaSelectedCallBack: (val) { - setState(() { - _currentPage = val; - _pageController.animateToPage(val, - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut); - Navigator.pop(context); - }); - }, - ), + if (widget.message.type != 'ephemeral') + ImageFooter( + currentPage: _currentPage, + totalPages: widget.mediaAttachments.length, + mediaAttachments: widget.mediaAttachments, + message: widget.message, + mediaSelectedCallBack: (val) { + setState(() { + _currentPage = val; + _pageController.animateToPage(val, + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut); + Navigator.pop(context); + }); + }, + ), ], ), ), diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/image_footer.dart index 1d0d7e78..6833a52f 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/image_footer.dart @@ -78,6 +78,7 @@ class _ImageFooterState extends State { @override Widget build(BuildContext context) { + final showShareButton = !kIsWeb; return SizedBox.fromSize( size: Size( MediaQuery.of(context).size.width, @@ -91,8 +92,10 @@ class _ImageFooterState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - kIsWeb - ? SizedBox() + !showShareButton + ? Container( + width: 48, + ) : IconButton( icon: StreamSvgIcon.iconShare( size: 24.0, diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/image_header.dart index bf4ec474..023c8eaa 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/image_header.dart @@ -63,38 +63,43 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { backgroundColor: StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, actions: [ - IconButton( - icon: StreamSvgIcon.iconMenuPoint( - color: StreamChatTheme.of(context).colorTheme.black, + if (message.type != 'ephemeral') + IconButton( + icon: StreamSvgIcon.iconMenuPoint( + color: StreamChatTheme.of(context).colorTheme.black, + ), + onPressed: () { + _showMessageActionModalBottomSheet(context); + }, ), - onPressed: () { - _showMessageActionModalBottomSheet(context); - }, - ), ], centerTitle: true, - title: InkWell( - onTap: onTitleTap, - child: Container( - height: preferredSize.height, - width: preferredSize.width, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - userName, - style: StreamChatTheme.of(context).textTheme.headlineBold, + title: message.type != 'ephemeral' + ? InkWell( + onTap: onTitleTap, + child: Container( + height: preferredSize.height, + width: preferredSize.width, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + userName, + style: StreamChatTheme.of(context).textTheme.headlineBold, + ), + Text( + sentAt, + style: StreamChatTheme.of(context) + .channelPreviewTheme + .subtitle, + ), + ], + ), ), - Text( - sentAt, - style: StreamChatTheme.of(context).channelPreviewTheme.subtitle, - ), - ], - ), - ), - ), + ) + : SizedBox(), ); } From 63705693dc863a6b08e267d91d617673080ac04e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 1 Apr 2021 16:55:12 +0530 Subject: [PATCH 073/599] fix: fix channel list pagination spinner (#375) Signed-off-by: Sahil Kumar --- .../lib/src/channel_list_view.dart | 15 +++++------ .../lib/src/channels_bloc.dart | 11 +++++++- .../lib/src/lazy_load_scroll_view.dart | 25 ++++++++++++------- .../test/channel_list_core_test.dart | 6 ++++- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 0396bffe..9751ad6d 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -628,13 +628,14 @@ class _ChannelListViewState extends State { ), ); } - return Container( - height: 100, - padding: EdgeInsets.all(32), - child: Center( - child: snapshot.data ? CircularProgressIndicator() : Container(), - ), - ); + return snapshot.data + ? Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: const CircularProgressIndicator(), + ), + ) + : Offstage(); }); } 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 71f0d508..2b4968eb 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -84,6 +84,8 @@ class ChannelsBlocState extends State final List _hiddenChannels = []; + bool _paginationEnded = false; + /// Calls [client.queryChannels] updating [queryChannelsLoading] stream Future queryChannels({ Map filter, @@ -93,7 +95,9 @@ class ChannelsBlocState extends State }) async { final client = StreamChatCore.of(context).client; - if (_queryChannelsLoadingController.value == true) return; + if (_paginationEnded || _queryChannelsLoadingController.value == true) { + return; + } if (_channelsController.hasValue) { _queryChannelsLoadingController.add(true); @@ -104,12 +108,14 @@ class ChannelsBlocState extends State paginationParams.offset == null || paginationParams.offset == 0; final oldChannels = List.from(channels ?? []); + var newChannels = []; await for (final channels in client.queryChannels( filter: filter, sort: sortOptions, options: options, paginationParams: paginationParams, )) { + newChannels = channels; if (clear) { _channelsController.add(channels); } else { @@ -121,6 +127,9 @@ class ChannelsBlocState extends State _queryChannelsLoadingController.sink.add(false); } } + if (newChannels.isEmpty || newChannels.length < paginationParams.limit) { + _paginationEnded = true; + } } catch (e, stk) { if (_channelsController.hasValue) { _queryChannelsLoadingController.addError(e, stk); diff --git a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart index 80afb2ea..808c1dae 100644 --- a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart @@ -47,6 +47,7 @@ class LazyLoadScrollView extends StatefulWidget { class _LazyLoadScrollViewState extends State { _LoadingStatus _loadMoreStatus = _LoadingStatus.stable; + double _scrollPosition = 0; @override Widget build(BuildContext context) => @@ -84,15 +85,21 @@ class _LazyLoadScrollViewState extends State { final extentBefore = notification.metrics.extentBefore; final extentAfter = notification.metrics.extentAfter; + final scrollingDown = _scrollPosition < pixels; - if (extentAfter <= scrollOffset) { - _onEndOfPage(); - return true; - } - if (extentBefore <= scrollOffset) { - _onStartOfPage(); - return true; + if (scrollingDown) { + if (extentAfter <= scrollOffset) { + _onEndOfPage(); + return true; + } + } else { + if (extentBefore <= scrollOffset) { + _onStartOfPage(); + return true; + } } + + _scrollPosition = pixels; } if (notification is OverscrollNotification) { if (notification.overscroll > 0) { @@ -108,8 +115,8 @@ class _LazyLoadScrollViewState extends State { void _onEndOfPage() { if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) { - _loadMoreStatus = _LoadingStatus.loading; if (widget.onEndOfPage != null) { + _loadMoreStatus = _LoadingStatus.loading; widget.onEndOfPage().whenComplete(() { _loadMoreStatus = _LoadingStatus.stable; }); @@ -119,8 +126,8 @@ class _LazyLoadScrollViewState extends State { void _onStartOfPage() { if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) { - _loadMoreStatus = _LoadingStatus.loading; if (widget.onStartOfPage != null) { + _loadMoreStatus = _LoadingStatus.loading; widget.onStartOfPage().whenComplete(() { _loadMoreStatus = _LoadingStatus.stable; }); diff --git a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart index bf24e591..b0fb9ded 100644 --- a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart @@ -9,7 +9,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'mocks.dart'; void main() { - const pagination = PaginationParams(offset: 0, limit: 25); + const pagination = PaginationParams(offset: 0, limit: 3); List _generateChannels( StreamChatClient client, { @@ -180,6 +180,7 @@ void main() { emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Container(key: errorWidgetKey), + pagination: pagination, ); final mockClient = MockClient(); @@ -227,6 +228,7 @@ void main() { loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Container(key: emptyWidgetKey), errorBuilder: (BuildContext context, Object error) => Offstage(), + pagination: pagination, ); final mockClient = MockClient(); @@ -274,6 +276,7 @@ void main() { loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), + pagination: pagination, ); final mockClient = MockClient(); @@ -329,6 +332,7 @@ void main() { loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), + pagination: pagination, ); final mockClient = MockClient(); From 2135fdb971acd69fea05460b15526096027d9117 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 2 Apr 2021 15:36:40 +0530 Subject: [PATCH 074/599] fix: attachments upload when size exceeds (#377) * fix: Fixed file upload error * fix: Fixed file upload error --- packages/stream_chat_flutter/lib/src/message_input.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index f22e127c..13da9b70 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1125,6 +1125,7 @@ class MessageInputState extends State { _showErrorAlert( 'The file is too large to upload. The file size limit is 20MB.', ); + return; } } @@ -1988,8 +1989,6 @@ class MessageInputState extends State { extraData: extraDataMap.isNotEmpty ? extraDataMap : null, ); - _attachments[attachment.id] = attachment; - if (file.size > _kMaxAttachmentSize) { if (attachmentType == 'Video') { final mediaInfo = await VideoService.compressVideo(file.path); @@ -1998,7 +1997,6 @@ class MessageInputState extends State { _showErrorAlert( 'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.', ); - _attachments.remove(attachment.id); return; } file = AttachmentFile( @@ -2011,9 +2009,12 @@ class MessageInputState extends State { _showErrorAlert( 'The file is too large to upload. The file size limit is 20MB.', ); + return; } } + _attachments[attachment.id] = attachment; + setState(() { _attachments.update(attachment.id, (it) { return it.copyWith( From b1396cd34fe6b064332feaf1dbba4d3a848fb6fe Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 2 Apr 2021 17:13:38 +0530 Subject: [PATCH 075/599] fix coverage script Signed-off-by: Sahil Kumar --- .github/workflows/scripts/coverage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scripts/coverage.sh b/.github/workflows/scripts/coverage.sh index 2710a45d..edff4ae4 100755 --- a/.github/workflows/scripts/coverage.sh +++ b/.github/workflows/scripts/coverage.sh @@ -3,4 +3,4 @@ # Fast fail the script on failures. set -e -pub global run remove_from_coverage:remove_from_coverage -f coverage/lcov.info -r '.g.dart$' \ No newline at end of file +pub global run remove_from_coverage:remove_from_coverage -f coverage/lcov.info -r '\.g\.dart$' -r '\.freezed\.dart$' \ No newline at end of file From 3ec389bd718006c8f8ca816bf68e29d7a9b358ac Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 2 Apr 2021 19:19:20 +0530 Subject: [PATCH 076/599] fix tests Signed-off-by: Sahil Kumar --- .../test/src/api/websocket_test.dart | 19 +++++++++---------- .../example/.fvm/flutter_sdk | 1 - .../example/.fvm/fvm_config.json | 3 --- .../stream_chat_flutter/example/pubspec.yaml | 5 ----- packages/stream_chat_flutter/pubspec.yaml | 5 +++-- .../example/pubspec.yaml | 4 ++++ .../test/stream_channel_test.dart | 2 +- .../example/pubspec.yaml | 7 +++++-- packages/stream_chat_persistence/pubspec.yaml | 3 +-- 9 files changed, 23 insertions(+), 26 deletions(-) delete mode 120000 packages/stream_chat_flutter/example/.fvm/flutter_sdk delete mode 100644 packages/stream_chat_flutter/example/.fvm/fvm_config.json diff --git a/packages/stream_chat/test/src/api/websocket_test.dart b/packages/stream_chat/test/src/api/websocket_test.dart index e14903e2..5993f0bf 100644 --- a/packages/stream_chat/test/src/api/websocket_test.dart +++ b/packages/stream_chat/test/src/api/websocket_test.dart @@ -162,24 +162,23 @@ void main() { final mockWSChannel = MockWSChannel(); - final StreamController streamController = - StreamController.broadcast(); + final streamController = StreamController.broadcast(); - final computedUrl = + const computedUrl = 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - when(connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(mockWSChannel.sink).thenAnswer((_) => MockWSSink()); - when(mockWSChannel.stream).thenAnswer((_) { - return streamController.stream; - }); + when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); + when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); + when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); ws.connect(); await ws.disconnect(); streamController.add('{}'); - verify(connectFunc(computedUrl)).called(1); - verifyNever(handleFunc(any)); + verify(() => connectFunc(computedUrl)).called(1); + verifyNever(() => handleFunc(any())); + + addTearDown(streamController.close); }); test('should run correctly health check', () async { diff --git a/packages/stream_chat_flutter/example/.fvm/flutter_sdk b/packages/stream_chat_flutter/example/.fvm/flutter_sdk deleted file mode 120000 index cdf17889..00000000 --- a/packages/stream_chat_flutter/example/.fvm/flutter_sdk +++ /dev/null @@ -1 +0,0 @@ -/Users/salvatoregiordano/fvm/versions/beta \ No newline at end of file diff --git a/packages/stream_chat_flutter/example/.fvm/fvm_config.json b/packages/stream_chat_flutter/example/.fvm/fvm_config.json deleted file mode 100644 index 6504dcd0..00000000 --- a/packages/stream_chat_flutter/example/.fvm/fvm_config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "flutterSdkVersion": "beta" -} \ No newline at end of file diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index 8a5270df..af8595d5 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -25,11 +25,6 @@ dependencies: sdk: flutter stream_chat_flutter: path: ../ - stream_chat_persistence: - git: - url: https://github.com/GetStream/stream-chat-flutter.git - ref: develop - path: packages/stream_chat_persistence # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index d3e003dd..4d8066f2 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -13,8 +13,7 @@ environment: dependencies: flutter: sdk: flutter - stream_chat_flutter_core: - path: ../stream_chat_flutter_core + stream_chat_flutter_core: ^1.5.0 photo_view: ^0.11.0 rxdart: ^0.26.0 scrollable_positioned_list: ^0.1.8 @@ -50,6 +49,8 @@ dependencies: dependency_overrides: + stream_chat: + path: ../stream_chat stream_chat_flutter_core: path: ../stream_chat_flutter_core diff --git a/packages/stream_chat_flutter_core/example/pubspec.yaml b/packages/stream_chat_flutter_core/example/pubspec.yaml index b0071176..91cf4a01 100644 --- a/packages/stream_chat_flutter_core/example/pubspec.yaml +++ b/packages/stream_chat_flutter_core/example/pubspec.yaml @@ -30,6 +30,10 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.0 +dependency_overrides: + stream_chat: + path: ../../stream_chat + dev_dependencies: flutter_test: sdk: flutter diff --git a/packages/stream_chat_flutter_core/test/stream_channel_test.dart b/packages/stream_chat_flutter_core/test/stream_channel_test.dart index 4387a918..ef8cb394 100644 --- a/packages/stream_chat_flutter_core/test/stream_channel_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_channel_test.dart @@ -121,7 +121,7 @@ void main() { ); final errorMessage = 'Error! Error! Error!'; - final error = DioError(type: DioErrorType.RESPONSE, error: errorMessage); + final error = DioError(type: DioErrorType.response, error: errorMessage); when(mockChannel.initialized).thenAnswer((_) => Future.error(error)); await tester.pumpWidget( diff --git a/packages/stream_chat_persistence/example/pubspec.yaml b/packages/stream_chat_persistence/example/pubspec.yaml index 2586bea6..7e3a1549 100644 --- a/packages/stream_chat_persistence/example/pubspec.yaml +++ b/packages/stream_chat_persistence/example/pubspec.yaml @@ -11,13 +11,16 @@ dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.0 - stream_chat: - path: ../../stream_chat stream_chat_persistence: path: ../ +dependency_overrides: + stream_chat: + path: ../../stream_chat + dev_dependencies: flutter_test: sdk: flutter + flutter: uses-material-design: true \ No newline at end of file diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 4e81ad51..9a9026f1 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -20,8 +20,7 @@ dependencies: path: ^1.8.0 path_provider: ^2.0.0 sqlite3_flutter_libs: ^0.4.0+1 - stream_chat: - path: ../stream_chat + stream_chat: ^1.5.0 dependency_overrides: stream_chat: From 3a5308a9c05c2b134d518e3341cf92c73fdb6cf7 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 2 Apr 2021 19:34:29 +0530 Subject: [PATCH 077/599] chore(core): sort pub dependency Signed-off-by: Sahil Kumar --- packages/stream_chat_flutter_core/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 98190f2c..edc23a77 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -22,8 +22,8 @@ dependency_overrides: path: ../stream_chat dev_dependencies: + fake_async: ^1.1.0 flutter_test: sdk: flutter - fake_async: ^1.1.0 mockito: ^4.1.3 From 13561dafaec10d04f7aa88e91dc4451366c04283 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 6 Apr 2021 17:57:59 +0530 Subject: [PATCH 078/599] fix: message input (#382) * feat: Added mentions suffix builder * feat: Added builders for mentions and theme element for message input expanded button * refactor: refactored PR to suit other style * fix: fixes trailing commas and changes names * doc: added documentation to new elements * doc: added documentation to new elements --- .../lib/src/mention_tile.dart | 99 +++++++++++++++++++ .../lib/src/message_input.dart | 93 ++++------------- .../lib/src/message_list_view.dart | 4 +- .../lib/src/stream_chat_theme.dart | 8 ++ .../lib/stream_chat_flutter.dart | 1 + 5 files changed, 127 insertions(+), 78 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/mention_tile.dart diff --git a/packages/stream_chat_flutter/lib/src/mention_tile.dart b/packages/stream_chat_flutter/lib/src/mention_tile.dart new file mode 100644 index 00000000..7d8b0ada --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/mention_tile.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; + +import '../stream_chat_flutter.dart'; + +/// This widget is used for showing user tiles for mentions +/// Use [title], [subtitle], [leading], [trailing] for substituting widgets in respective positions +class MentionTile extends StatelessWidget { + /// Member to display in the tile + final Member member; + + /// Widget to display as title + final Widget title; + + /// Widget to display below [title] + final Widget subtitle; + + /// Widget at the start of the tile + final Widget leading; + + /// Widget at the end of tile + final Widget trailing; + + MentionTile( + this.member, { + this.title, + this.subtitle, + this.leading, + this.trailing, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: 56.0, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 16.0, + ), + leading ?? + UserAvatar( + constraints: BoxConstraints.tight( + Size( + 40, + 40, + ), + ), + user: member.user, + ), + SizedBox( + width: 8.0, + ), + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title ?? + Text( + '${member.user.name}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: StreamChatTheme.of(context).textTheme.bodyBold, + ), + SizedBox( + height: 2.0, + ), + subtitle ?? + Text( + '@${member.userId}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: StreamChatTheme.of(context) + .textTheme + .footnoteBold + .copyWith( + color: + StreamChatTheme.of(context).colorTheme.grey, + ), + ), + ], + ), + ), + ), + trailing ?? + Padding( + padding: const EdgeInsets.only(right: 18.0, left: 8.0), + child: StreamSvgIcon.mentions( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + ), + ), + ], + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 13da9b70..385dc41f 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -16,7 +16,6 @@ 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/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; -import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:substring_highlight/substring_highlight.dart'; @@ -32,6 +31,13 @@ typedef AttachmentThumbnailBuilder = Widget Function( Attachment, ); +/// Builder function for building a mention tile +/// Use [MentionTile] for the default implementation +typedef MentionTileBuilder = Widget Function( + BuildContext context, + Member member, +); + enum ActionsLocation { left, right, @@ -125,6 +131,7 @@ class MessageInput extends StatefulWidget { this.idleSendButton, this.activeSendButton, this.showCommandsButton = true, + this.mentionsTileBuilder, }) : super(key: key); /// Message to edit @@ -191,6 +198,9 @@ class MessageInput extends StatefulWidget { /// Send button widget in an active state final Widget activeSendButton; + /// Customize the tile for the mentions overlay + final MentionTileBuilder mentionsTileBuilder; + @override MessageInputState createState() => MessageInputState(); @@ -458,7 +468,9 @@ class MessageInputState extends State { ? pi : 0, child: StreamSvgIcon.emptyCircleLeft( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: StreamChatTheme.of(context) + .messageInputTheme + .expandButtonColor, ), ), padding: const EdgeInsets.all(0), @@ -1314,80 +1326,9 @@ class MessageInputState extends State { _mentionsOverlay?.remove(); _mentionsOverlay = null; }, - child: Container( - height: 56.0, - child: Row( - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - SizedBox( - width: 16.0, - ), - UserAvatar( - constraints: BoxConstraints.tight( - Size( - 40, - 40, - ), - ), - user: m.user, - ), - SizedBox( - width: 8.0, - ), - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - '${m.user.name}', - maxLines: 1, - overflow: - TextOverflow.ellipsis, - style: StreamChatTheme.of( - context) - .textTheme - .bodyBold, - ), - SizedBox( - height: 2.0, - ), - Text( - '@${m.userId}', - maxLines: 1, - overflow: - TextOverflow.ellipsis, - style: StreamChatTheme.of( - context) - .textTheme - .footnoteBold - .copyWith( - color: StreamChatTheme - .of(context) - .colorTheme - .grey), - ), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.only( - right: 18.0, left: 8.0), - child: StreamSvgIcon.mentions( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), - ), - ], - ), - ), + child: widget.mentionsTileBuilder != null + ? widget.mentionsTileBuilder(context, m) + : MentionTile(m), ), ); }, 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 89cc8910..9af5bc4a 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -231,10 +231,10 @@ class MessageListView extends StatefulWidget { /// Called when system message is tapped final OnMessageTap onSystemMessageTap; - // Customize onTap on attachment + /// Customize onTap on attachment final void Function(Message message, Attachment attachment) onAttachmentTap; - // Customize the MessageWidget textBuilder + /// Customize the MessageWidget textBuilder final void Function(BuildContext context, Message message) textBuilder; @override 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 eafa12c5..e54c0fbe 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -272,6 +272,7 @@ class StreamChatThemeData { sendAnimationDuration: Duration(milliseconds: 300), actionButtonColor: colorTheme.accentBlue, actionButtonIdleColor: colorTheme.grey, + expandButtonColor: colorTheme.accentBlue, sendButtonColor: colorTheme.accentBlue, sendButtonIdleColor: colorTheme.greyGainsboro, inputBackground: colorTheme.white, @@ -935,6 +936,9 @@ class MessageInputTheme { /// Background color of [MessageInput] action buttons final Color actionButtonIdleColor; + /// Background color of [MessageInput] expand button + final Color expandButtonColor; + /// Background color of [MessageInput] final Color inputBackground; @@ -966,6 +970,7 @@ class MessageInputTheme { this.activeBorderGradient, this.idleBorderGradient, this.borderRadius, + this.expandButtonColor, }); /// Returns a new [MessageInputTheme] replacing some of its properties @@ -976,6 +981,7 @@ class MessageInputTheme { Color sendButtonColor, Color actionButtonIdleColor, Color sendButtonIdleColor, + Color expandButtonColor, TextStyle inputTextStyle, InputDecoration inputDecoration, Gradient activeBorderGradient, @@ -990,6 +996,7 @@ class MessageInputTheme { sendButtonColor: sendButtonColor ?? this.sendButtonColor, actionButtonIdleColor: actionButtonIdleColor ?? this.actionButtonIdleColor, + expandButtonColor: expandButtonColor ?? this.expandButtonColor, inputTextStyle: inputTextStyle ?? this.inputTextStyle, sendButtonIdleColor: sendButtonIdleColor ?? this.sendButtonIdleColor, inputDecoration: inputDecoration ?? this.inputDecoration, @@ -1014,6 +1021,7 @@ class MessageInputTheme { activeBorderGradient: other.activeBorderGradient, idleBorderGradient: other.idleBorderGradient, borderRadius: other.borderRadius, + expandButtonColor: other.expandButtonColor, ); } } diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 74c0c33e..b8062209 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -39,4 +39,5 @@ export 'src/channel_media_display_screen.dart'; export 'src/info_tile.dart'; export 'src/stream_chat.dart'; export 'src/connection_status_builder.dart'; +export 'src/mention_tile.dart'; export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; From c80fc0a6b4f0a2569f9edd7f106984a8826b3466 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 8 Apr 2021 15:03:16 +0530 Subject: [PATCH 079/599] added null safety for llc --- packages/stream_chat/lib/src/api/channel.dart | 591 +++++++++--------- .../stream_chat/lib/src/api/requests.dart | 22 +- .../stream_chat/lib/src/api/responses.dart | 204 +++--- .../stream_chat/lib/src/api/retry_policy.dart | 16 +- .../stream_chat/lib/src/api/retry_queue.dart | 43 +- .../lib/src/api/web_socket_channel_html.dart | 2 +- .../lib/src/api/web_socket_channel_io.dart | 2 +- .../lib/src/api/web_socket_channel_stub.dart | 6 +- .../stream_chat/lib/src/api/websocket.dart | 80 +-- .../lib/src/attachment_file_uploader.dart | 96 +-- packages/stream_chat/lib/src/client.dart | 390 ++++++------ .../lib/src/db/chat_persistence_client.dart | 102 +-- packages/stream_chat/lib/src/exceptions.dart | 16 +- .../lib/src/extensions/map_extension.dart | 4 +- .../lib/src/extensions/rate_limit.dart | 48 +- .../lib/src/extensions/string_extension.dart | 8 +- .../stream_chat/lib/src/models/action.dart | 10 +- .../lib/src/models/attachment.dart | 96 +-- .../lib/src/models/attachment_file.dart | 14 +- .../lib/src/models/channel_config.dart | 32 +- .../lib/src/models/channel_model.dart | 62 +- .../lib/src/models/channel_state.dart | 32 +- .../stream_chat/lib/src/models/command.dart | 6 +- .../stream_chat/lib/src/models/device.dart | 4 +- .../stream_chat/lib/src/models/event.dart | 104 +-- .../stream_chat/lib/src/models/member.dart | 44 +- .../stream_chat/lib/src/models/message.dart | 129 ++-- packages/stream_chat/lib/src/models/mute.dart | 8 +- .../stream_chat/lib/src/models/own_user.dart | 30 +- .../stream_chat/lib/src/models/reaction.dart | 34 +- packages/stream_chat/lib/src/models/read.dart | 12 +- .../lib/src/models/serialization.dart | 6 +- packages/stream_chat/lib/src/models/user.dart | 46 +- packages/stream_chat/pubspec.yaml | 20 +- .../test/src/api/channel_test.dart | 34 +- .../test/src/api/websocket_test.dart | 18 +- .../test/src/models/attachment_test.dart | 2 +- .../test/src/models/channel_state_test.dart | 43 +- .../test/src/models/channel_test.dart | 4 +- .../test/src/models/reaction_test.dart | 2 +- .../test/src/models/read_test.dart | 2 +- 41 files changed, 1222 insertions(+), 1202 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index aeacaf7d..3b6848f6 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; +import 'package:collection/collection.dart' show IterableExtension; import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; @@ -27,9 +28,9 @@ class Channel { /// Create a channel client instance from a [ChannelState] object Channel.fromState(this._client, ChannelState channelState) { - _cid = channelState.channel.cid; - _id = channelState.channel.id; - type = channelState.channel.type; + _cid = channelState.channel!.cid; + _id = channelState.channel!.id; + type = channelState.channel!.type; state = ChannelClientState(this, channelState); _initializedCompleter.complete(true); @@ -37,16 +38,16 @@ class Channel { } /// This client state - ChannelClientState state; + ChannelClientState? state; /// The channel type - String type; + String? type; - String _id; - String _cid; - Map _extraData; + String? _id; + String? _cid; + Map? _extraData; - set extraData(Map extraData) { + set extraData(Map? extraData) { if (_initializedCompleter.isCompleted) { throw Exception( 'Once the channel is initialized you should use channel.update ' @@ -57,13 +58,14 @@ class Channel { /// Returns true if the channel is muted bool get isMuted => - _client.state.user?.channelMutes - ?.any((element) => element.channel.cid == cid) == + _client.state!.user?.channelMutes + ?.any((element) => element.channel!.cid == cid) == true; /// Returns true if the channel is muted as a stream - Stream get isMutedStream => _client.state.userStream?.map((event) => - event.channelMutes?.any((element) => element.channel.cid == cid) == true); + Stream? get isMutedStream => _client.state!.userStream.map((event) => + event!.channelMutes?.any((element) => element.channel!.cid == cid) == + true); /// True if the channel is a group bool get isGroup => memberCount != 2; @@ -72,85 +74,85 @@ class Channel { bool get isDistinct => id?.startsWith('!members') == true; /// Channel configuration - ChannelConfig get config => state?._channelState?.channel?.config; + ChannelConfig? get config => state?._channelState?.channel?.config; /// Channel configuration as a stream - Stream get configStream => - state?.channelStateStream?.map((cs) => cs.channel?.config); + Stream? get configStream => + state?.channelStateStream?.map((cs) => cs!.channel?.config); /// Channel user creator - User get createdBy => state?._channelState?.channel?.createdBy; + User? get createdBy => state?._channelState?.channel?.createdBy; /// Channel user creator as a stream - Stream get createdByStream => - state?.channelStateStream?.map((cs) => cs.channel?.createdBy); + Stream? get createdByStream => + state?.channelStateStream?.map((cs) => cs!.channel?.createdBy); /// Channel frozen status - bool get frozen => state?._channelState?.channel?.frozen; + bool? get frozen => state?._channelState?.channel?.frozen; /// Channel frozen status as a stream - Stream get frozenStream => - state?.channelStateStream?.map((cs) => cs.channel?.frozen); + Stream? get frozenStream => + state?.channelStateStream?.map((cs) => cs!.channel?.frozen); /// Channel creation date - DateTime get createdAt => state?._channelState?.channel?.createdAt; + DateTime? get createdAt => state?._channelState?.channel?.createdAt; /// Channel creation date as a stream - Stream get createdAtStream => - state?.channelStateStream?.map((cs) => cs.channel?.createdAt); + Stream? get createdAtStream => + state?.channelStateStream?.map((cs) => cs!.channel?.createdAt); /// Channel last message date - DateTime get lastMessageAt => state?._channelState?.channel?.lastMessageAt; + DateTime? get lastMessageAt => state?._channelState?.channel?.lastMessageAt; /// Channel last message date as a stream - Stream get lastMessageAtStream => - state?.channelStateStream?.map((cs) => cs.channel?.lastMessageAt); + Stream? get lastMessageAtStream => + state?.channelStateStream?.map((cs) => cs!.channel?.lastMessageAt); /// Channel updated date - DateTime get updatedAt => state?._channelState?.channel?.updatedAt; + DateTime? get updatedAt => state?._channelState?.channel?.updatedAt; /// Channel updated date as a stream - Stream get updatedAtStream => - state?.channelStateStream?.map((cs) => cs.channel?.updatedAt); + Stream? get updatedAtStream => + state?.channelStateStream?.map((cs) => cs!.channel?.updatedAt); /// Channel deletion date - DateTime get deletedAt => state?._channelState?.channel?.deletedAt; + DateTime? get deletedAt => state?._channelState?.channel?.deletedAt; /// Channel deletion date as a stream - Stream get deletedAtStream => - state?.channelStateStream?.map((cs) => cs.channel?.deletedAt); + Stream? get deletedAtStream => + state?.channelStateStream?.map((cs) => cs!.channel?.deletedAt); /// Channel member count - int get memberCount => state?._channelState?.channel?.memberCount; + int? get memberCount => state?._channelState?.channel?.memberCount; /// Channel member count as a stream - Stream get memberCountStream => - state?.channelStateStream?.map((cs) => cs.channel?.memberCount); + Stream? get memberCountStream => + state?.channelStateStream?.map((cs) => cs!.channel?.memberCount); /// Channel id - String get id => state?._channelState?.channel?.id ?? _id; + String? get id => state?._channelState?.channel?.id ?? _id; /// Channel id as a stream - Stream get idStream => - state?.channelStateStream?.map((cs) => cs.channel?.id ?? _id); + Stream? get idStream => + state?.channelStateStream?.map((cs) => cs!.channel?.id ?? _id); /// Channel cid - String get cid => state?._channelState?.channel?.cid ?? _cid; + String? get cid => state?._channelState?.channel?.cid ?? _cid; /// Channel team - String get team => state?._channelState?.channel?.team; + String? get team => state?._channelState?.channel?.team; /// Channel cid as a stream - Stream get cidStream => - state?.channelStateStream?.map((cs) => cs.channel?.cid ?? _cid); + Stream? get cidStream => + state?.channelStateStream?.map((cs) => cs!.channel?.cid ?? _cid); /// Channel extra data - Map get extraData => + Map? get extraData => state?._channelState?.channel?.extraData ?? _extraData; /// Channel extra data as a stream - Stream> get extraDataStream => - state?.channelStateStream?.map((cs) => cs.channel?.extraData); + Stream?>? get extraDataStream => + state?.channelStateStream?.map((cs) => cs!.channel?.extraData); /// The main Stream chat client StreamChatClient get client => _client; @@ -174,7 +176,7 @@ class Channel { /// Optionally, provide a [reason] for the cancellation. void cancelAttachmentUpload( String attachmentId, { - String reason, + String? reason, }) { final cancelToken = _cancelableAttachmentUploadRequest[attachmentId]; if (cancelToken == null) { @@ -195,23 +197,22 @@ class Channel { String messageId, Iterable attachmentIds, ) { - final message = state.messages.firstWhere( + final message = state!.messages!.firstWhereOrNull( (it) => it.id == messageId, - orElse: () => null, ); if (message == null) { throw Exception('Error, Message not found'); } - final attachments = message.attachments.where((it) { + final attachments = message.attachments!.where((it) { if (it.uploadState.isSuccess) return false; return attachmentIds.contains(it.id); }); if (attachments.isEmpty) { client.logger.info('No attachments available to upload'); - if (message.attachments.every((it) => it.uploadState.isSuccess)) { + if (message.attachments!.every((it) => it.uploadState.isSuccess)) { _messageAttachmentsUploadCompleter.remove(messageId)?.complete(message); } return Future.value(); @@ -221,9 +222,9 @@ class Channel { void updateAttachment(Attachment attachment) { final index = - message.attachments.indexWhere((it) => it.id == attachment.id); + message.attachments!.indexWhere((it) => it.id == attachment.id); if (index != -1) { - message.attachments[index] = attachment; + message.attachments![index] = attachment; state?.addMessage(message); } } @@ -251,13 +252,13 @@ class Channel { it.file, onSendProgress: onSendProgress, cancelToken: cancelToken, - ).then((it) => it.file); + ).then((it) => it!.file!); } else { future = sendFile( it.file, onSendProgress: onSendProgress, cancelToken: cancelToken, - ).then((it) => it.file); + ).then((it) => it!.file!); } _cancelableAttachmentUploadRequest[it.id] = cancelToken; return future.then((url) { @@ -287,7 +288,7 @@ class Channel { _cancelableAttachmentUploadRequest.remove(it.id); }); })).whenComplete(() { - if (message.attachments.every((it) => it.uploadState.isSuccess)) { + if (message.attachments!.every((it) => it.uploadState.isSuccess)) { _messageAttachmentsUploadCompleter.remove(messageId)?.complete(message); } }); @@ -303,14 +304,13 @@ class Channel { .remove(message.id) ?.completeError('Message Cancelled'); - final quotedMessage = state?.messages?.firstWhere( + final quotedMessage = state?.messages?.firstWhereOrNull( (m) => m.id == message?.quotedMessageId, - orElse: () => null, ); // ignore: parameter_assignments message = message.copyWith( createdAt: message.createdAt ?? DateTime.now(), - user: _client.state.user, + user: _client.state!.user, quotedMessage: quotedMessage, status: MessageSendingStatus.sending, attachments: message.attachments?.map( @@ -323,10 +323,10 @@ class Channel { if (message.parentId != null && message.id == null) { final parentMessage = - state.messages.firstWhere((m) => m.id == message.parentId); + state!.messages!.firstWhere((m) => m.id == message.parentId); state?.addMessage(parentMessage.copyWith( - replyCount: parentMessage.replyCount + 1, + replyCount: parentMessage.replyCount! + 1, )); } @@ -341,15 +341,16 @@ class Channel { // ignore: unawaited_futures _uploadAttachments( message.id, - message.attachments.map((it) => it.id), + message.attachments!.map((it) => it.id), ); // ignore: parameter_assignments message = await attachmentsUploadCompleter.future; } - final response = await _client.sendMessage(message, id, type); - state?.addMessage(response.message); + final response = await (_client.sendMessage(message, id, type) + as FutureOr); + state?.addMessage(response.message!); return response; } catch (error) { if (error is DioError && error.type != DioErrorType.response) { @@ -362,7 +363,7 @@ class Channel { /// Updates the [message] in this channel. /// Waits for a [_messageAttachmentsUploadCompleter] to complete /// before actually updating the message. - Future updateMessage(Message message) async { + Future updateMessage(Message message) async { // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. _messageAttachmentsUploadCompleter @@ -392,7 +393,7 @@ class Channel { // ignore: unawaited_futures _uploadAttachments( message.id, - message.attachments.map((it) => it.id), + message.attachments!.map((it) => it.id), ); // ignore: parameter_assignments @@ -400,9 +401,15 @@ class Channel { } final response = await _client.updateMessage(message); - state?.addMessage(response?.message?.copyWith( + + final m = response?.message?.copyWith( ownReactions: message.ownReactions, - )); + ); + + if (m != null) { + state?.addMessage(m); + } + return response; } catch (error) { if (error is DioError && error.type != DioErrorType.response) { @@ -413,11 +420,11 @@ class Channel { } /// Deletes the [message] from the channel. - Future deleteMessage(Message message) async { + Future deleteMessage(Message message) async { // Directly deleting the local messages which are not yet sent to server if (message.status == MessageSendingStatus.sending || message.status == MessageSendingStatus.failed) { - state.addMessage(message.copyWith( + state!.addMessage(message.copyWith( type: 'deleted', status: MessageSendingStatus.sent, )); @@ -454,7 +461,7 @@ class Channel { } /// Pins provided message - Future pinMessage( + Future pinMessage( Message message, Object timeoutOrExpirationDate, ) { @@ -467,7 +474,7 @@ class Channel { return true; }(), 'Check for invalid token or expiration date'); - DateTime pinExpires; + DateTime? pinExpires; if (timeoutOrExpirationDate is DateTime) { pinExpires = timeoutOrExpirationDate; } else if (timeoutOrExpirationDate is num) { @@ -484,14 +491,14 @@ class Channel { } /// Unpins provided message - Future unpinMessage(Message message) => + Future unpinMessage(Message message) => updateMessage(message.copyWith(pinned: false)); /// Send a file to this channel - Future sendFile( - AttachmentFile file, { - ProgressCallback onSendProgress, - CancelToken cancelToken, + Future sendFile( + AttachmentFile? file, { + ProgressCallback? onSendProgress, + CancelToken? cancelToken, }) => _client.sendFile( file, @@ -502,10 +509,10 @@ class Channel { ); /// Send an image to this channel - Future sendImage( - AttachmentFile file, { - ProgressCallback onSendProgress, - CancelToken cancelToken, + Future sendImage( + AttachmentFile? file, { + ProgressCallback? onSendProgress, + CancelToken? cancelToken, }) => _client.sendImage( file, @@ -516,11 +523,11 @@ class Channel { ); /// A message search. - Future search({ - String query, - Map messageFilters, - List sort, - PaginationParams paginationParams, + Future search({ + String? query, + Map? messageFilters, + List? sort, + PaginationParams? paginationParams, }) => _client.search( { @@ -535,16 +542,16 @@ class Channel { ); /// Delete a file from this channel - Future deleteFile( + Future deleteFile( String url, { - CancelToken cancelToken, + CancelToken? cancelToken, }) => _client.deleteFile(url, id, type, cancelToken: cancelToken); /// Delete an image from this channel - Future deleteImage( + Future deleteImage( String url, { - CancelToken cancelToken, + CancelToken? cancelToken, }) => _client.deleteImage(url, id, type, cancelToken: cancelToken); @@ -554,12 +561,12 @@ class Channel { return _client.post( '$_channelURL/event', data: {'event': event.toJson()}, - ).then((res) => _client.decode(res.data, EmptyResponse.fromJson)); + ).then((res) => _client.decode(res.data, EmptyResponse.fromJson)!); } /// Send a reaction to this channel /// Set [enforceUnique] to true to remove the existing user reaction - Future sendReaction( + Future sendReaction( Message message, String type, { Map extraData = const {}, @@ -567,11 +574,11 @@ class Channel { }) async { final messageId = message.id; final now = DateTime.now(); - final user = _client.state.user; + final user = _client.state!.user; final latestReactions = [...message.latestReactions ?? []]; if (enforceUnique) { - latestReactions.removeWhere((it) => it.userId == user.id); + latestReactions.removeWhere((it) => it.userId == user!.id); } final newReaction = Reaction( @@ -586,7 +593,7 @@ class Channel { // Inserting at the 0th index as it's the latest reaction latestReactions.insert(0, newReaction); final ownReactions = [...latestReactions] - ..removeWhere((it) => it.userId != user.id); + ..removeWhere((it) => it.userId != user!.id); final newMessage = message.copyWith( reactionCounts: {...message?.reactionCounts ?? {}} @@ -629,10 +636,10 @@ class Channel { } /// Delete a reaction from this channel - Future deleteReaction( + Future deleteReaction( Message message, Reaction reaction) async { final type = reaction.type; - final user = _client.state.user; + final user = _client.state!.user; final reactionCounts = {...message.reactionCounts ?? {}}; if (reactionCounts.containsKey(type)) { @@ -650,7 +657,7 @@ class Channel { r.messageId == reaction.messageId); final ownReactions = [...latestReactions ?? []] - ..removeWhere((it) => it.userId != user.id); + ..removeWhere((it) => it.userId != user!.id); final newMessage = message.copyWith( reactionCounts: reactionCounts..removeWhere((_, value) => value == 0), @@ -673,9 +680,9 @@ class Channel { } /// Edit the channel custom data - Future update( + Future update( Map channelData, [ - Message updateMessage, + Message? updateMessage, ]) async { final response = await _client.post(_channelURL, data: { if (updateMessage != null) @@ -686,42 +693,42 @@ class Channel { } /// Edit the channel custom data - Future updatePartial( + Future updatePartial( Map channelData) async { final response = await _client.patch(_channelURL, data: channelData); return _client.decode(response.data, PartialUpdateChannelResponse.fromJson); } /// Delete this channel. Messages are permanently removed. - Future delete() async { + Future delete() async { final response = await _client.delete(_channelURL); return _client.decode(response.data, EmptyResponse.fromJson); } /// Removes all messages from the channel - Future truncate() async { + Future truncate() async { final response = await _client.post('$_channelURL/truncate'); return _client.decode(response.data, EmptyResponse.fromJson); } /// Accept invitation to the channel - Future acceptInvite([Message message]) async { + Future acceptInvite([Message? message]) async { final res = await _client.post(_channelURL, data: {'accept_invite': true, 'message': message?.toJson()}); return _client.decode(res.data, AcceptInviteResponse.fromJson); } /// Reject invitation to the channel - Future rejectInvite([Message message]) async { + Future rejectInvite([Message? message]) async { final res = await _client.post(_channelURL, data: {'reject_invite': true, 'message': message?.toJson()}); return _client.decode(res.data, RejectInviteResponse.fromJson); } /// Add members to the channel - Future addMembers( + Future addMembers( List memberIds, [ - Message message, + Message? message, ]) async { final res = await _client.post(_channelURL, data: { 'add_members': memberIds, @@ -731,9 +738,9 @@ class Channel { } /// Invite members to the channel - Future inviteMembers( + Future inviteMembers( List memberIds, [ - Message message, + Message? message, ]) async { final res = await _client.post(_channelURL, data: { 'invites': memberIds, @@ -743,9 +750,9 @@ class Channel { } /// Remove members from the channel - Future removeMembers( + Future removeMembers( List memberIds, [ - Message message, + Message? message, ]) async { final res = await _client.post(_channelURL, data: { 'remove_members': memberIds, @@ -769,34 +776,33 @@ class Channel { 'message_id': messageId, }); - final res = _client.decode(response.data, SendActionResponse.fromJson); + final res = _client.decode(response.data, SendActionResponse.fromJson)!; if (res.message != null) { - state.addMessage(res.message); + state!.addMessage(res.message!); } else { - final oldIndex = state.messages?.indexWhere((m) => m.id == messageId); + final oldIndex = state!.messages?.indexWhere((m) => m.id == messageId); - Message oldMessage; + Message? oldMessage; if (oldIndex != null && oldIndex != -1) { - oldMessage = state.messages[oldIndex]; - state.updateChannelState(state._channelState.copyWith( - messages: state.messages..remove(oldMessage), + oldMessage = state!.messages![oldIndex]; + state!.updateChannelState(state!._channelState!.copyWith( + messages: state?.messages?..remove(oldMessage), )); } else { - oldMessage = state.threads.values + oldMessage = state!.threads!.values .expand((messages) => messages) - .firstWhere((m) => m.id == messageId, orElse: () => null); + .firstWhereOrNull((m) => m.id == messageId); if (oldMessage?.parentId != null) { - final parentMessage = state.messages.firstWhere( - (element) => element.id == oldMessage.parentId, - orElse: () => null, + final parentMessage = state!.messages!.firstWhereOrNull( + (element) => element.id == oldMessage!.parentId, ); if (parentMessage != null) { - state.addMessage(parentMessage.copyWith( - replyCount: parentMessage.replyCount - 1)); + state!.addMessage(parentMessage.copyWith( + replyCount: parentMessage.replyCount! - 1)); } - state.updateThreadInfo(oldMessage.parentId, - state.threads[oldMessage.parentId]..remove(oldMessage)); + state!.updateThreadInfo(oldMessage!.parentId, + state!.threads![oldMessage.parentId!]!..remove(oldMessage)); } } @@ -807,11 +813,11 @@ class Channel { } /// Mark all channel messages as read - Future markRead() async { + Future markRead() async { _checkInitialized(); - client.state.totalUnreadCount = - max(0, (client.state.totalUnreadCount ?? 0) - (state.unreadCount ?? 0)); - state._unreadCountController.add(0); + client.state!.totalUnreadCount = max( + 0, (client.state!.totalUnreadCount ?? 0) - (state!.unreadCount ?? 0)); + state!._unreadCountController.add(0); final response = await _client.post('$_channelURL/read', data: {}); return _client.decode(response.data, EmptyResponse.fromJson); } @@ -828,7 +834,7 @@ class Channel { ChannelState response; try { - response = await query(options: watchOptions); + response = await query(options: watchOptions)!; } catch (error, stackTrace) { if (!_initializedCompleter.isCompleted) { _initializedCompleter.completeError(error, stackTrace); @@ -845,14 +851,14 @@ class Channel { void _initState(ChannelState channelState) { state = ChannelClientState(this, channelState); - client.state.channels[cid] = this; + client.state!.channels![cid] = this; if (!_initializedCompleter.isCompleted) { _initializedCompleter.complete(true); } } /// Stop watching the channel - Future stopWatching() async { + Future stopWatching() async { final response = await _client.post( '$_channelURL/stop-watching', data: {}, @@ -868,10 +874,10 @@ class Channel { PaginationParams options, { bool preferOffline = false, }) async { - final cachedReplies = await _client.chatPersistenceClient?.getReplies( + final cachedReplies = (await _client.chatPersistenceClient?.getReplies( parentId, options: options, - ); + ))!; if (cachedReplies != null && cachedReplies.isNotEmpty) { state?.updateThreadInfo(parentId, cachedReplies); if (preferOffline) { @@ -885,7 +891,7 @@ class Channel { final repliesResponse = _client.decode( response.data, QueryRepliesResponse.fromJson, - ); + )!; state?.updateThreadInfo(parentId, repliesResponse.messages); @@ -893,7 +899,7 @@ class Channel { } /// List the reactions for a message in the channel - Future getReactions( + Future getReactions( String messageID, PaginationParams options, ) async { @@ -916,7 +922,7 @@ class Channel { final res = _client.decode( response.data, GetMessagesByIdResponse.fromJson, - ); + )!; state?.updateChannelState(ChannelState(messages: res.messages)); @@ -924,7 +930,7 @@ class Channel { } /// Retrieves a list of messages by ID - Future translateMessage( + Future translateMessage( String messageId, String language, ) async { @@ -941,20 +947,20 @@ class Channel { } /// Creates a new channel - Future create() async => query(options: { + Future? create() async => query(options: { 'watch': false, 'state': false, 'presence': false, - }); + })!; /// Query the API, get messages, members or other channel fields /// Set [preferOffline] to true to avoid the api call if the data is already /// in the offline storage - Future query({ + Future? query({ Map options = const {}, - PaginationParams messagesPagination, - PaginationParams membersPagination, - PaginationParams watchersPagination, + PaginationParams? messagesPagination, + PaginationParams? membersPagination, + PaginationParams? watchersPagination, bool preferOffline = false, }) async { var path = '/channels/$type'; @@ -984,11 +990,11 @@ class Channel { if (preferOffline && cid != null) { final updatedState = - await _client.chatPersistenceClient?.getChannelStateByCid( + (await _client.chatPersistenceClient?.getChannelStateByCid( cid, messagePagination: messagesPagination, - ); - if (updatedState != null && updatedState.messages.isNotEmpty) { + ))!; + if (updatedState != null && updatedState.messages!.isNotEmpty) { if (state == null) { _initState(updatedState); } else { @@ -1000,11 +1006,12 @@ class Channel { try { final response = await _client.post(path, data: payload); - final updatedState = _client.decode(response.data, ChannelState.fromJson); + final updatedState = + _client.decode(response.data, ChannelState.fromJson)!; if (_id == null) { - _id = updatedState.channel.id; - _cid = updatedState.channel.cid; + _id = updatedState.channel!.id; + _cid = updatedState.channel!.cid; } state?.updateChannelState(updatedState); @@ -1013,7 +1020,7 @@ class Channel { if (!_client.persistenceEnabled) { rethrow; } - return _client.chatPersistenceClient?.getChannelStateByCid( + return _client.chatPersistenceClient!.getChannelStateByCid( cid, messagePagination: messagesPagination, ); @@ -1021,10 +1028,10 @@ class Channel { } /// Query channel members - Future queryMembers({ - Map filter, - List sort, - PaginationParams pagination, + Future queryMembers({ + Map? filter, + List? sort, + PaginationParams? pagination, }) async { final payload = { 'sort': sort, @@ -1039,7 +1046,7 @@ class Channel { if (id != null) { payload['id'] = id; } else if (state?.members?.isNotEmpty == true) { - payload['members'] = state.members; + payload['members'] = state!.members; } final rawRes = await _client.get('/members', queryParameters: { @@ -1050,7 +1057,7 @@ class Channel { } /// Mutes the channel - Future mute({Duration expiration}) async { + Future mute({Duration? expiration}) async { final response = await _client.post('/moderation/mute/channel', data: { 'channel_cid': cid, if (expiration != null) 'expiration': expiration.inMilliseconds, @@ -1059,7 +1066,7 @@ class Channel { } /// Unmutes the channel - Future unmute() async { + Future unmute() async { final response = await _client.post('/moderation/unmute/channel', data: { 'channel_cid': cid, }); @@ -1067,7 +1074,7 @@ class Channel { } /// Bans a user from the channel - Future banUser( + Future banUser( String userID, Map options, ) async { @@ -1081,7 +1088,7 @@ class Channel { } /// Remove the ban for a user in the channel - Future unbanUser(String userID) async { + Future unbanUser(String userID) async { _checkInitialized(); return _client.unbanUser(userID, { 'type': type, @@ -1090,7 +1097,7 @@ class Channel { } /// Shadow bans a user from the channel - Future shadowBan( + Future shadowBan( String userID, Map options, ) async { @@ -1104,7 +1111,7 @@ class Channel { } /// Remove the shadow ban for a user in the channel - Future removeShadowBan(String userID) async { + Future removeShadowBan(String userID) async { _checkInitialized(); return _client.removeShadowBan(userID, { 'type': type, @@ -1115,13 +1122,13 @@ class Channel { /// Hides the channel from [StreamChatClient.queryChannels] for the user /// until a message is added If [clearHistory] is set to true - all messages /// will be removed for the user - Future hide({bool clearHistory = false}) async { + Future hide({bool clearHistory = false}) async { _checkInitialized(); final response = await _client .post('$_channelURL/hide', data: {'clear_history': clearHistory}); if (clearHistory == true) { - state.truncate(); + state!.truncate(); await _client.chatPersistenceClient?.deleteMessageByCid(_cid); } @@ -1129,7 +1136,7 @@ class Channel { } /// Removes the hidden status for the channel - Future show() async { + Future show() async { _checkInitialized(); final response = await _client.post('$_channelURL/show'); return _client.decode(response.data, EmptyResponse.fromJson); @@ -1139,10 +1146,10 @@ class Channel { /// channel. Pass an eventType as parameter in order to filter just a type /// of event Stream on([ - String eventType, - String eventType2, - String eventType3, - String eventType4, + String? eventType, + String? eventType2, + String? eventType3, + String? eventType4, ]) => _client .on( @@ -1153,11 +1160,11 @@ class Channel { ) .where((e) => e.cid == cid); - DateTime _lastTypingEvent; + DateTime? _lastTypingEvent; /// First of the [EventType.typingStart] and [EventType.typingStop] events /// based on the users keystrokes. Call this on every keystroke. - Future keyStroke([String parentId]) async { + Future keyStroke([String? parentId]) async { if (config?.typingEvents == false) { return; } @@ -1166,7 +1173,7 @@ class Channel { final now = DateTime.now(); if (_lastTypingEvent == null || - now.difference(_lastTypingEvent).inSeconds >= 2) { + now.difference(_lastTypingEvent!).inSeconds >= 2) { _lastTypingEvent = now; await sendEvent(Event( type: EventType.typingStart, @@ -1176,7 +1183,7 @@ class Channel { } /// Sets last typing to null and sends the typing.stop event - Future stopTyping([String parentId]) async { + Future stopTyping([String? parentId]) async { if (config?.typingEvents == false) { return; } @@ -1191,7 +1198,7 @@ class Channel { /// Call this method to dispose the channel client void dispose() { - state.dispose(); + state!.dispose(); } void _checkInitialized() { @@ -1270,9 +1277,8 @@ class ChannelClientState { final _subscriptions = []; void _computeInitialUnread() { - final userRead = channelState?.read?.firstWhere( - (r) => r.user.id == _channel._client.state?.user?.id, - orElse: () => null, + final userRead = channelState?.read?.firstWhereOrNull( + (r) => r.user!.id == _channel._client.state?.user?.id, ); if (userRead != null) { _unreadCountController.add(userRead.unreadMessages ?? 0); @@ -1295,14 +1301,14 @@ class ChannelClientState { return false; } final expiration = - DateTime.parse(uri.queryParameters['Expires']); + DateTime.parse(uri.queryParameters['Expires']!); return expiration.isBefore(DateTime.now()); }) == true) ?.map((e) => e.id) ?.toList(); if (expiredAttachmentMessagesId?.isNotEmpty == true) { - _channel.getMessagesById(expiredAttachmentMessagesId); + _channel.getMessagesById(expiredAttachmentMessagesId!); _updatedMessagesIds.addAll(expiredAttachmentMessagesId); } } @@ -1310,9 +1316,9 @@ class ChannelClientState { void _listenMemberAdded() { _subscriptions.add(_channel.on(EventType.memberAdded).listen((Event e) { final member = e.member; - updateChannelState(channelState.copyWith( + updateChannelState(channelState!.copyWith( members: [ - ...channelState.members, + ...channelState!.members!, member, ], )); @@ -1322,17 +1328,17 @@ class ChannelClientState { void _listenMemberRemoved() { _subscriptions.add(_channel.on(EventType.memberRemoved).listen((Event e) { final user = e.user; - updateChannelState(channelState.copyWith( + updateChannelState(channelState!.copyWith( members: List.from( - channelState.members..removeWhere((m) => m.userId == user.id)), + channelState!.members!..removeWhere((m) => m!.userId == user!.id)), )); })); } void _listenChannelUpdated() { _subscriptions.add(_channel.on(EventType.channelUpdated).listen((Event e) { - final channel = e.channel; - updateChannelState(channelState.copyWith( + final channel = e.channel!; + updateChannelState(channelState!.copyWith( channel: channel, members: channel.members, )); @@ -1343,7 +1349,7 @@ class ChannelClientState { _subscriptions.add(_channel .on(EventType.channelTruncated, EventType.notificationChannelTruncated) .listen((event) async { - final channel = event.channel; + final channel = event.channel!; await _channel._client.chatPersistenceClient ?.deleteMessageByCid(channel.cid); truncate(); @@ -1354,28 +1360,28 @@ class ChannelClientState { /// This flag should be managed by UI sdks. /// When false, any new message (received by WebSocket event /// - [EventType.messageNew]) will not be pushed on to message list. - bool get isUpToDate => _isUpToDateController.value; + bool? get isUpToDate => _isUpToDateController.value; - set isUpToDate(bool isUpToDate) => _isUpToDateController.add(isUpToDate); + set isUpToDate(bool? isUpToDate) => _isUpToDateController.add(isUpToDate); /// [isUpToDate] flag count as a stream - Stream get isUpToDateStream => _isUpToDateController.stream; + Stream get isUpToDateStream => _isUpToDateController.stream; - final BehaviorSubject _isUpToDateController = + final BehaviorSubject _isUpToDateController = BehaviorSubject.seeded(true); /// The retry queue associated to this channel - RetryQueue retryQueue; + RetryQueue? retryQueue; /// Retry failed message Future retryFailedMessages() async { final failedMessages = - [...messages, ...threads.values.expand((v) => v)] + [...messages!, ...threads!.values.expand((v) => v)] .where( (message) => message.status != null && message.status != MessageSendingStatus.sent && - message.createdAt.isBefore( + message.createdAt!.isBefore( DateTime.now().subtract( const Duration( seconds: 1, @@ -1385,14 +1391,14 @@ class ChannelClientState { ) .toList(); - retryQueue.add(failedMessages); + retryQueue!.add(failedMessages); } void _listenReactionDeleted() { _subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) { - final userId = _channel.client.state.user.id; - final message = event.message.copyWith( - ownReactions: [...event.message.latestReactions] + final userId = _channel.client.state!.user!.id; + final message = event.message!.copyWith( + ownReactions: [...event.message!.latestReactions!] ..removeWhere((it) => it.userId != userId), ); addMessage(message); @@ -1401,9 +1407,9 @@ class ChannelClientState { void _listenReactions() { _subscriptions.add(_channel.on(EventType.reactionNew).listen((event) { - final userId = _channel.client.state.user.id; - final message = event.message.copyWith( - ownReactions: [...event.message.latestReactions] + final userId = _channel.client.state!.user!.id; + final message = event.message!.copyWith( + ownReactions: [...event.message!.latestReactions!] ..removeWhere((it) => it.userId != userId), ); addMessage(message); @@ -1417,17 +1423,17 @@ class ChannelClientState { EventType.reactionUpdated, ) .listen((event) { - final userId = _channel.client.state.user.id; - final message = event.message.copyWith( - ownReactions: [...event.message.latestReactions] + final userId = _channel.client.state!.user!.id; + final message = event.message!.copyWith( + ownReactions: [...event.message!.latestReactions!] ..removeWhere((it) => it.userId != userId), ); addMessage(message); if (message.pinned == true) { - _channelState = _channelState.copyWith( + _channelState = _channelState!.copyWith( pinnedMessages: [ - ..._channelState.pinnedMessages ?? [], + ..._channelState!.pinnedMessages ?? [], message, ], ); @@ -1437,7 +1443,7 @@ class ChannelClientState { void _listenMessageDeleted() { _subscriptions.add(_channel.on(EventType.messageDeleted).listen((event) { - final message = event.message; + final message = event.message!; addMessage(message); })); } @@ -1449,14 +1455,14 @@ class ChannelClientState { EventType.notificationMessageNew, ) .listen((event) { - final message = event.message; - if (isUpToDate || + final message = event.message!; + if (isUpToDate! || (message.parentId != null && message.showInChannel != true)) { addMessage(message); } if (_countMessageAsUnread(message)) { - _unreadCountController.add(_unreadCountController.value + 1); + _unreadCountController.add(_unreadCountController.value! + 1); } })); } @@ -1464,10 +1470,10 @@ class ChannelClientState { /// Add a message to this channel void addMessage(Message message) { if (message.parentId == null || message.showInChannel == true) { - final newMessages = List.from(_channelState.messages); + final newMessages = List.from(_channelState!.messages!); final oldIndex = newMessages.indexWhere((m) => m.id == message.id); if (oldIndex != -1) { - Message m; + Message? m; if (message.quotedMessageId != null && message.quotedMessage == null) { final oldMessage = newMessages[oldIndex]; m = message.copyWith( @@ -1479,9 +1485,9 @@ class ChannelClientState { newMessages.add(message); } - _channelState = _channelState.copyWith( + _channelState = _channelState!.copyWith( messages: newMessages, - channel: _channelState.channel.copyWith( + channel: _channelState!.channel!.copyWith( lastMessageAt: message.createdAt, ), ); @@ -1507,18 +1513,18 @@ class ChannelClientState { (event) { final readList = List.from(_channelState?.read ?? []); final userReadIndex = - read?.indexWhere((r) => r.user.id == event.user.id); + read?.indexWhere((r) => r.user!.id == event.user!.id); if (userReadIndex != null && userReadIndex != -1) { final userRead = readList.removeAt(userReadIndex); - if (userRead.user?.id == _channel._client.state.user.id) { + if (userRead.user?.id == _channel._client.state!.user!.id) { _unreadCountController.add(0); } readList.add(Read( user: event.user, lastRead: event.createdAt, )); - _channelState = _channelState.copyWith(read: readList); + _channelState = _channelState!.copyWith(read: readList); } }, ), @@ -1526,67 +1532,68 @@ class ChannelClientState { } /// Channel message list - List get messages => _channelState.messages; + List? get messages => _channelState!.messages; /// Channel message list as a stream - Stream> get messagesStream => - channelStateStream.map((cs) => cs.messages); + Stream?> get messagesStream => + channelStateStream.map((cs) => cs!.messages); /// Channel pinned message list - List get pinnedMessages => _channelState.pinnedMessages?.toList(); + List? get pinnedMessages => _channelState!.pinnedMessages?.toList(); /// Channel pinned message list as a stream - Stream> get pinnedMessagesStream => - channelStateStream.map((cs) => cs.pinnedMessages?.toList()); + Stream?> get pinnedMessagesStream => + channelStateStream.map((cs) => cs!.pinnedMessages?.toList()); /// Get channel last message - Message get lastMessage => _channelState.messages?.isNotEmpty == true - ? _channelState.messages.last + Message? get lastMessage => _channelState!.messages?.isNotEmpty == true + ? _channelState!.messages!.last : null; /// Get channel last message - Stream get lastMessageStream => messagesStream - .map((event) => event?.isNotEmpty == true ? event.last : null); + Stream get lastMessageStream => messagesStream + .map((event) => event?.isNotEmpty == true ? event!.last : null); /// Channel members list - List get members => _channelState.members - .map((e) => e.copyWith(user: _channel.client.state.users[e.user.id])) + List get members => _channelState!.members! + .map((e) => e!.copyWith(user: _channel.client.state!.users![e.user!.id!])) .toList(); /// Channel members list as a stream Stream> get membersStream => CombineLatestStream.combine2< - List, Map, List>( - channelStateStream.map((cs) => cs.members), - _channel.client.state.usersStream, + List?, Map, List>( + channelStateStream.map((cs) => cs!.members), + _channel.client.state!.usersStream, (members, users) => - members.map((e) => e.copyWith(user: users[e.user.id])).toList(), + members!.map((e) => e!.copyWith(user: users[e.user!.id])).toList(), ); /// Channel watcher count - int get watcherCount => _channelState.watcherCount; + int? get watcherCount => _channelState!.watcherCount; /// Channel watcher count as a stream - Stream get watcherCountStream => - channelStateStream.map((cs) => cs.watcherCount); + Stream get watcherCountStream => + channelStateStream.map((cs) => cs!.watcherCount); /// Channel watchers list - List get watchers => _channelState.watchers - .map((e) => _channel.client.state.users[e.id] ?? e) + List get watchers => _channelState!.watchers! + .map((e) => _channel.client.state!.users![e.id!] ?? e) .toList(); /// Channel watchers list as a stream - Stream> get watchersStream => - CombineLatestStream.combine2, Map, List>( - channelStateStream.map((cs) => cs.watchers), - _channel.client.state.usersStream, - (watchers, users) => watchers.map((e) => users[e.id] ?? e).toList(), + Stream> get watchersStream => CombineLatestStream.combine2< + List?, Map, List>( + channelStateStream.map((cs) => cs!.watchers), + _channel.client.state!.usersStream, + (watchers, users) => watchers!.map((e) => users[e.id] ?? e).toList(), ); /// Channel read list - List get read => _channelState.read; + List? get read => _channelState!.read; /// Channel read list as a stream - Stream> get readStream => channelStateStream.map((cs) => cs.read); + Stream?> get readStream => + channelStateStream.map((cs) => cs!.read); final BehaviorSubject _unreadCountController = BehaviorSubject.seeded(0); @@ -1594,36 +1601,36 @@ class ChannelClientState { Stream get unreadCountStream => _unreadCountController.stream; /// Unread count getter - int get unreadCount => _unreadCountController.value; + int? get unreadCount => _unreadCountController.value; bool _countMessageAsUnread(Message message) { final userId = _channel.client.state?.user?.id; - final userIsMuted = _channel.client.state?.user?.mutes?.firstWhere( - (m) => m.user?.id == message.user.id, - orElse: () => null, + final userIsMuted = _channel.client.state?.user?.mutes?.firstWhereOrNull( + (m) => m.user?.id == message.user!.id, ) != null; return message.silent != true && message.shadowed != true && - message.user.id != userId && + message.user!.id != userId && !userIsMuted; } /// Update threads with updated information about messages - void updateThreadInfo(String parentId, List messages) { - final newThreads = Map>.from(threads); + void updateThreadInfo(String? parentId, List? messages) { + final newThreads = Map?>.from(threads!); if (newThreads.containsKey(parentId)) { newThreads[parentId] = [ ...newThreads[parentId] - ?.where( - (newMessage) => !messages.any((m) => m.id == newMessage.id)) + ?.where((newMessage) => + !messages!.any((m) => m.id == newMessage.id)) ?.toList() ?? [], - ...messages, + ...messages!, ]; - newThreads[parentId].sort(_sortByCreatedAt); + newThreads[parentId]! + .sort(_sortByCreatedAt as int Function(Message, Message)?); } else { newThreads[parentId] = messages; } @@ -1633,7 +1640,7 @@ class ChannelClientState { /// Delete all channel messages void truncate() { - _channelState = _channelState.copyWith( + _channelState = _channelState!.copyWith( messages: [], ); } @@ -1651,7 +1658,7 @@ class ChannelClientState { true) ?.toList() ?? [], - ]..sort(_sortByCreatedAt); + ]..sort(_sortByCreatedAt as int Function(Message, Message)?); final newWatchers = [ ...updatedState?.watchers ?? [], @@ -1664,7 +1671,7 @@ class ChannelClientState { [], ]; - final newMembers = [ + final newMembers = [ ...updatedState?.members ?? [], ]; @@ -1673,7 +1680,7 @@ class ChannelClientState { ..._channelState?.read ?.where((r) => updatedState.read - ?.any((newRead) => newRead.user.id == r.user.id) != + ?.any((newRead) => newRead.user!.id == r.user!.id) != true) ?.toList() ?? [], @@ -1681,9 +1688,9 @@ class ChannelClientState { _checkExpiredAttachmentMessages(updatedState); - _channelState = _channelState.copyWith( + _channelState = _channelState!.copyWith( messages: newMessages, - channel: _channelState.channel?.merge(updatedState.channel), + channel: _channelState!.channel?.merge(updatedState.channel), watchers: newWatchers, watcherCount: updatedState.watcherCount, members: newMembers, @@ -1692,7 +1699,7 @@ class ChannelClientState { ); } - int _sortByCreatedAt(a, b) { + int? _sortByCreatedAt(a, b) { if (a.createdAt == null) { return 1; } @@ -1705,49 +1712,51 @@ class ChannelClientState { } /// The channel state related to this client - ChannelState get _channelState => _channelStateController.value; + ChannelState? get _channelState => _channelStateController.value; /// The channel state related to this client as a stream - Stream get channelStateStream => _channelStateController.stream; + Stream get channelStateStream => + _channelStateController.stream; /// The channel state related to this client - ChannelState get channelState => _channelStateController.value; - BehaviorSubject _channelStateController; + ChannelState? get channelState => _channelStateController.value; + late BehaviorSubject _channelStateController; final Debounce _debouncedUpdatePersistenceChannelState; - set _channelState(ChannelState v) { + set _channelState(ChannelState? v) { _channelStateController.add(v); _debouncedUpdatePersistenceChannelState?.call([v]); } /// The channel threads related to this channel - Map> get threads => _threadsController.value; + Map>? get threads => + _threadsController.value as Map>?; /// The channel threads related to this channel as a stream - Stream>> get threadsStream => + Stream?>> get threadsStream => _threadsController.stream; - final BehaviorSubject>> _threadsController = + final BehaviorSubject?>> _threadsController = BehaviorSubject.seeded({}); - set _threads(Map> v) { + set _threads(Map?> v) { _channel._client.chatPersistenceClient?.updateMessages( _channel.cid, - v.values.expand((v) => v).toList(), + v.values.expand((v) => v!).toList(), ); _threadsController.add(v); } /// Channel related typing users last value - List get typingEvents => _typingEventsController.value; + List? get typingEvents => _typingEventsController.value as List?; /// Channel related typing users stream - Stream> get typingEventsStream => _typingEventsController.stream; - final BehaviorSubject> _typingEventsController = + Stream> get typingEventsStream => _typingEventsController.stream; + final BehaviorSubject> _typingEventsController = BehaviorSubject.seeded([]); final Channel _channel; - final Map _typings = {}; + final Map _typings = {}; void _listenTypingEvents() { if (_channel.config?.typingEvents == false) { @@ -1758,7 +1767,7 @@ class ChannelClientState { ..add( _channel.on(EventType.typingStart).listen( (event) { - if (event.user.id != _channel.client.state.user.id) { + if (event.user!.id != _channel.client.state!.user!.id) { _typings[event.user] = DateTime.now(); _typingEventsController.add(_typings.keys.toList()); } @@ -1768,7 +1777,7 @@ class ChannelClientState { ..add( _channel.on(EventType.typingStop).listen( (event) { - if (event.user.id != _channel.client.state.user.id) { + if (event.user!.id != _channel.client.state!.user!.id) { _typings.remove(event.user); _typingEventsController.add(_typings.keys.toList()); } @@ -1780,12 +1789,12 @@ 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) == true) .listen( (event) { final newMembers = List.from(members); final oldMemberIndex = - newMembers.indexWhere((m) => m.userId == event.user.id); + newMembers.indexWhere((m) => m.userId == event.user!.id); if (oldMemberIndex > -1) { final oldMember = newMembers.removeAt(oldMemberIndex); updateChannelState(ChannelState( @@ -1802,7 +1811,7 @@ class ChannelClientState { ); } - Timer _cleaningTimer; + late Timer _cleaningTimer; void _startCleaning() { if (_channel.config?.typingEvents == false) { @@ -1813,7 +1822,7 @@ class ChannelClientState { final now = DateTime.now(); if (_channel._lastTypingEvent != null && - now.difference(_channel._lastTypingEvent).inSeconds > 1) { + now.difference(_channel._lastTypingEvent!).inSeconds > 1) { _channel.stopTyping(); } @@ -1821,12 +1830,12 @@ class ChannelClientState { }); } - Timer _pinnedMessagesTimer; + late Timer _pinnedMessagesTimer; void _startCleaningPinnedMessages() { _pinnedMessagesTimer = Timer.periodic(const Duration(seconds: 30), (_) { final now = DateTime.now(); - var expiredMessages = channelState.pinnedMessages + var expiredMessages = channelState!.pinnedMessages ?.where((m) => m.pinExpires?.isBefore(now) == true) ?.toList() ?? []; @@ -1838,8 +1847,8 @@ class ChannelClientState { )) .toList(); - updateChannelState(_channelState.copyWith( - pinnedMessages: pinnedMessages.where(_pinIsValid()).toList(), + updateChannelState(_channelState!.copyWith( + pinnedMessages: pinnedMessages!.where(_pinIsValid()).toList(), messages: expiredMessages, )); } @@ -1865,7 +1874,7 @@ class ChannelClientState { void dispose() { _debouncedUpdatePersistenceChannelState?.cancel(); _unreadCountController.close(); - retryQueue.dispose(); + retryQueue!.dispose(); _subscriptions.forEach((s) => s.cancel()); _channelStateController.close(); _isUpToDateController.close(); @@ -1878,5 +1887,5 @@ class ChannelClientState { bool Function(Message) _pinIsValid() { final now = DateTime.now(); - return (Message m) => m.pinExpires.isAfter(now); + return (Message m) => m.pinExpires!.isAfter(now); } diff --git a/packages/stream_chat/lib/src/api/requests.dart b/packages/stream_chat/lib/src/api/requests.dart index 51151fca..5e3c0741 100644 --- a/packages/stream_chat/lib/src/api/requests.dart +++ b/packages/stream_chat/lib/src/api/requests.dart @@ -34,7 +34,7 @@ class SortOption { /// Sorting field Comparator required for offline sorting @JsonKey(ignore: true) - final Comparator comparator; + final Comparator? comparator; /// Serialize model to json Map toJson() => _$SortOptionToJson(this); @@ -70,31 +70,31 @@ class PaginationParams { /// Filter on ids greater than the given value. @JsonKey(name: 'id_gt') - final String greaterThan; + final String? greaterThan; /// Filter on ids greater than or equal to the given value. @JsonKey(name: 'id_gte') - final String greaterThanOrEqual; + final String? greaterThanOrEqual; /// Filter on ids smaller than the given value. @JsonKey(name: 'id_lt') - final String lessThan; + final String? lessThan; /// Filter on ids smaller than or equal to the given value. @JsonKey(name: 'id_lte') - final String lessThanOrEqual; + final String? lessThanOrEqual; /// Serialize model to json Map toJson() => _$PaginationParamsToJson(this); /// Creates a copy of [PaginationParams] with specified attributes overridden. PaginationParams copyWith({ - int limit, - int offset, - String greaterThan, - String greaterThanOrEqual, - String lessThan, - String lessThanOrEqual, + int? limit, + int? offset, + String? greaterThan, + String? greaterThanOrEqual, + String? lessThan, + String? lessThanOrEqual, }) => PaginationParams( limit: limit ?? this.limit, diff --git a/packages/stream_chat/lib/src/api/responses.dart b/packages/stream_chat/lib/src/api/responses.dart index 431a57e7..a26fc626 100644 --- a/packages/stream_chat/lib/src/api/responses.dart +++ b/packages/stream_chat/lib/src/api/responses.dart @@ -13,192 +13,192 @@ import 'package:stream_chat/src/models/user.dart'; part 'responses.g.dart'; class _BaseResponse { - String duration; + String? duration; } /// Model response for [StreamChatClient.resync] api call @JsonSerializable(createToJson: false) class SyncResponse extends _BaseResponse { /// The list of events - List events; + List? events; /// Create a new instance from a json - static SyncResponse fromJson(Map json) => - _$SyncResponseFromJson(json); + static SyncResponse fromJson(Map? json) => + _$SyncResponseFromJson(json!); } /// Model response for [StreamChatClient.queryChannels] api call @JsonSerializable(createToJson: false) class QueryChannelsResponse extends _BaseResponse { /// List of channels state returned by the query - List channels; + List? channels; /// Create a new instance from a json - static QueryChannelsResponse fromJson(Map json) => - _$QueryChannelsResponseFromJson(json); + static QueryChannelsResponse fromJson(Map? json) => + _$QueryChannelsResponseFromJson(json!); } /// Model response for [StreamChatClient.queryChannels] api call @JsonSerializable(createToJson: false) class TranslateMessageResponse extends _BaseResponse { /// List of channels state returned by the query - TranslatedMessage message; + TranslatedMessage? message; /// Create a new instance from a json - static TranslateMessageResponse fromJson(Map json) => - _$TranslateMessageResponseFromJson(json); + static TranslateMessageResponse fromJson(Map? json) => + _$TranslateMessageResponseFromJson(json!); } /// Model response for [StreamChatClient.queryChannels] api call @JsonSerializable(createToJson: false) class QueryMembersResponse extends _BaseResponse { /// List of channels state returned by the query - List members; + List? members; /// Create a new instance from a json - static QueryMembersResponse fromJson(Map json) => - _$QueryMembersResponseFromJson(json); + static QueryMembersResponse fromJson(Map? json) => + _$QueryMembersResponseFromJson(json!); } /// Model response for [StreamChatClient.queryUsers] api call @JsonSerializable(createToJson: false) class QueryUsersResponse extends _BaseResponse { /// List of users returned by the query - List users; + List? users; /// Create a new instance from a json - static QueryUsersResponse fromJson(Map json) => - _$QueryUsersResponseFromJson(json); + static QueryUsersResponse fromJson(Map? json) => + _$QueryUsersResponseFromJson(json!); } /// Model response for [channel.getReactions] api call @JsonSerializable(createToJson: false) class QueryReactionsResponse extends _BaseResponse { /// List of reactions returned by the query - List reactions; + List? reactions; /// Create a new instance from a json - static QueryReactionsResponse fromJson(Map json) => - _$QueryReactionsResponseFromJson(json); + static QueryReactionsResponse fromJson(Map? json) => + _$QueryReactionsResponseFromJson(json!); } /// Model response for [Channel.getReplies] api call @JsonSerializable(createToJson: false) class QueryRepliesResponse extends _BaseResponse { /// List of messages returned by the api call - List messages; + List? messages; /// Create a new instance from a json - static QueryRepliesResponse fromJson(Map json) => - _$QueryRepliesResponseFromJson(json); + static QueryRepliesResponse fromJson(Map? json) => + _$QueryRepliesResponseFromJson(json!); } /// Model response for [StreamChatClient.getDevices] api call @JsonSerializable(createToJson: false) class ListDevicesResponse extends _BaseResponse { /// List of user devices - List devices; + List? devices; /// Create a new instance from a json - static ListDevicesResponse fromJson(Map json) => - _$ListDevicesResponseFromJson(json); + static ListDevicesResponse fromJson(Map? json) => + _$ListDevicesResponseFromJson(json!); } /// Model response for [Channel.sendFile] api call @JsonSerializable(createToJson: false) class SendFileResponse extends _BaseResponse { /// The url of the uploaded file - String file; + String? file; /// Create a new instance from a json - static SendFileResponse fromJson(Map json) => - _$SendFileResponseFromJson(json); + static SendFileResponse fromJson(Map? json) => + _$SendFileResponseFromJson(json!); } /// Model response for [Channel.sendImage] api call @JsonSerializable(createToJson: false) class SendImageResponse extends _BaseResponse { /// The url of the uploaded file - String file; + String? file; /// Create a new instance from a json - static SendImageResponse fromJson(Map json) => - _$SendImageResponseFromJson(json); + static SendImageResponse fromJson(Map? json) => + _$SendImageResponseFromJson(json!); } /// Model response for [Channel.sendReaction] api call @JsonSerializable(createToJson: false) class SendReactionResponse extends _BaseResponse { /// Message returned by the api call - Message message; + Message? message; /// The reaction created by the api call - Reaction reaction; + Reaction? reaction; /// Create a new instance from a json - static SendReactionResponse fromJson(Map json) => - _$SendReactionResponseFromJson(json); + static SendReactionResponse fromJson(Map? json) => + _$SendReactionResponseFromJson(json!); } /// Model response for [StreamChatClient.connectGuestUser] api call @JsonSerializable(createToJson: false) class ConnectGuestUserResponse extends _BaseResponse { /// Guest user access token - String accessToken; + String? accessToken; /// Guest user - User user; + User? user; /// Create a new instance from a json - static ConnectGuestUserResponse fromJson(Map json) => - _$ConnectGuestUserResponseFromJson(json); + static ConnectGuestUserResponse fromJson(Map? json) => + _$ConnectGuestUserResponseFromJson(json!); } /// Model response for [StreamChatClient.updateUser] api call @JsonSerializable(createToJson: false) class UpdateUsersResponse extends _BaseResponse { /// Updated users - Map users; + Map? users; /// Create a new instance from a json - static UpdateUsersResponse fromJson(Map json) => - _$UpdateUsersResponseFromJson(json); + static UpdateUsersResponse fromJson(Map? json) => + _$UpdateUsersResponseFromJson(json!); } /// Model response for [StreamChatClient.updateMessage] api call @JsonSerializable(createToJson: false) class UpdateMessageResponse extends _BaseResponse { /// Message returned by the api call - Message message; + Message? message; /// Create a new instance from a json - static UpdateMessageResponse fromJson(Map json) => - _$UpdateMessageResponseFromJson(json); + static UpdateMessageResponse fromJson(Map? json) => + _$UpdateMessageResponseFromJson(json!); } /// Model response for [Channel.sendMessage] api call @JsonSerializable(createToJson: false) class SendMessageResponse extends _BaseResponse { /// Message returned by the api call - Message message; + Message? message; /// Create a new instance from a json - static SendMessageResponse fromJson(Map json) => - _$SendMessageResponseFromJson(json); + static SendMessageResponse fromJson(Map? json) => + _$SendMessageResponseFromJson(json!); } /// Model response for [StreamChatClient.getMessage] api call @JsonSerializable(createToJson: false) class GetMessageResponse extends _BaseResponse { /// Message returned by the api call - Message message; + Message? message; /// Channel of the message - ChannelModel channel; + ChannelModel? channel; /// Create a new instance from a json - static GetMessageResponse fromJson(Map json) { - final res = _$GetMessageResponseFromJson(json); + static GetMessageResponse fromJson(Map? json) { + final res = _$GetMessageResponseFromJson(json!); final jsonChannel = res.message?.extraData?.remove('channel'); if (jsonChannel != null) { res.channel = ChannelModel.fromJson(jsonChannel); @@ -211,176 +211,176 @@ class GetMessageResponse extends _BaseResponse { @JsonSerializable(createToJson: false) class SearchMessagesResponse extends _BaseResponse { /// List of messages returned by the api call - List results; + List? results; /// Create a new instance from a json - static SearchMessagesResponse fromJson(Map json) => - _$SearchMessagesResponseFromJson(json); + static SearchMessagesResponse fromJson(Map? json) => + _$SearchMessagesResponseFromJson(json!); } /// Model response for [Channel.getMessagesById] api call @JsonSerializable(createToJson: false) class GetMessagesByIdResponse extends _BaseResponse { /// Message returned by the api call - List messages; + List? messages; /// Create a new instance from a json - static GetMessagesByIdResponse fromJson(Map json) => - _$GetMessagesByIdResponseFromJson(json); + static GetMessagesByIdResponse fromJson(Map? json) => + _$GetMessagesByIdResponseFromJson(json!); } /// Model response for [Channel.update] api call @JsonSerializable(createToJson: false) class UpdateChannelResponse extends _BaseResponse { /// Updated channel - ChannelModel channel; + ChannelModel? channel; /// Channel members - List members; + List? members; /// Message returned by the api call - Message message; + Message? message; /// Create a new instance from a json - static UpdateChannelResponse fromJson(Map json) => - _$UpdateChannelResponseFromJson(json); + static UpdateChannelResponse fromJson(Map? json) => + _$UpdateChannelResponseFromJson(json!); } /// Model response for [Channel.updatePartial] api call @JsonSerializable(createToJson: false) class PartialUpdateChannelResponse extends _BaseResponse { /// Updated channel - ChannelModel channel; + ChannelModel? channel; /// Channel members - List members; + List? members; /// Create a new instance from a json - static PartialUpdateChannelResponse fromJson(Map json) => - _$PartialUpdateChannelResponseFromJson(json); + static PartialUpdateChannelResponse fromJson(Map? json) => + _$PartialUpdateChannelResponseFromJson(json!); } /// Model response for [Channel.inviteMembers] api call @JsonSerializable(createToJson: false) class InviteMembersResponse extends _BaseResponse { /// Updated channel - ChannelModel channel; + ChannelModel? channel; /// Channel members - List members; + List? members; /// Message returned by the api call - Message message; + Message? message; /// Create a new instance from a json - static InviteMembersResponse fromJson(Map json) => - _$InviteMembersResponseFromJson(json); + static InviteMembersResponse fromJson(Map? json) => + _$InviteMembersResponseFromJson(json!); } /// Model response for [Channel.removeMembers] api call @JsonSerializable(createToJson: false) class RemoveMembersResponse extends _BaseResponse { /// Updated channel - ChannelModel channel; + ChannelModel? channel; /// Channel members - List members; + List? members; /// Message returned by the api call - Message message; + Message? message; /// Create a new instance from a json - static RemoveMembersResponse fromJson(Map json) => - _$RemoveMembersResponseFromJson(json); + static RemoveMembersResponse fromJson(Map? json) => + _$RemoveMembersResponseFromJson(json!); } /// Model response for [Channel.sendAction] api call @JsonSerializable(createToJson: false) class SendActionResponse extends _BaseResponse { /// Message returned by the api call - Message message; + Message? message; /// Create a new instance from a json - static SendActionResponse fromJson(Map json) => - _$SendActionResponseFromJson(json); + static SendActionResponse fromJson(Map? json) => + _$SendActionResponseFromJson(json!); } /// Model response for [Channel.addMembers] api call @JsonSerializable(createToJson: false) class AddMembersResponse extends _BaseResponse { /// Updated channel - ChannelModel channel; + ChannelModel? channel; /// Channel members - List members; + List? members; /// Message returned by the api call - Message message; + Message? message; /// Create a new instance from a json - static AddMembersResponse fromJson(Map json) => - _$AddMembersResponseFromJson(json); + static AddMembersResponse fromJson(Map? json) => + _$AddMembersResponseFromJson(json!); } /// Model response for [Channel.acceptInvite] api call @JsonSerializable(createToJson: false) class AcceptInviteResponse extends _BaseResponse { /// Updated channel - ChannelModel channel; + ChannelModel? channel; /// Channel members - List members; + List? members; /// Message returned by the api call - Message message; + Message? message; /// Create a new instance from a json - static AcceptInviteResponse fromJson(Map json) => - _$AcceptInviteResponseFromJson(json); + static AcceptInviteResponse fromJson(Map? json) => + _$AcceptInviteResponseFromJson(json!); } /// Model response for [Channel.rejectInvite] api call @JsonSerializable(createToJson: false) class RejectInviteResponse extends _BaseResponse { /// Updated channel - ChannelModel channel; + ChannelModel? channel; /// Channel members - List members; + List? members; /// Message returned by the api call - Message message; + Message? message; /// Create a new instance from a json - static RejectInviteResponse fromJson(Map json) => - _$RejectInviteResponseFromJson(json); + static RejectInviteResponse fromJson(Map? json) => + _$RejectInviteResponseFromJson(json!); } /// Model response for empty responses @JsonSerializable(createToJson: false) class EmptyResponse extends _BaseResponse { /// Create a new instance from a json - static EmptyResponse fromJson(Map json) => - _$EmptyResponseFromJson(json); + static EmptyResponse fromJson(Map? json) => + _$EmptyResponseFromJson(json!); } /// Model response for [Channel.query] api call @JsonSerializable(createToJson: false) class ChannelStateResponse extends _BaseResponse { /// Updated channel - ChannelModel channel; + ChannelModel? channel; /// List of messages returned by the api call - List messages; + List? messages; /// Channel members - List members; + List? members; /// Number of users watching the channel - int watcherCount; + int? watcherCount; /// List of read states - List read; + List? read; /// Create a new instance from a json static ChannelStateResponse fromJson(Map json) => diff --git a/packages/stream_chat/lib/src/api/retry_policy.dart b/packages/stream_chat/lib/src/api/retry_policy.dart index a4d1e7bd..2eaf6ab5 100644 --- a/packages/stream_chat/lib/src/api/retry_policy.dart +++ b/packages/stream_chat/lib/src/api/retry_policy.dart @@ -6,30 +6,30 @@ import 'package:stream_chat/src/exceptions.dart'; class RetryPolicy { /// Instantiate a new RetryPolicy RetryPolicy({ - @required this.shouldRetry, - @required this.retryTimeout, - this.attempt, + required this.shouldRetry, + required this.retryTimeout, + this.attempt = 0, }); /// The number of attempts tried so far int attempt = 0; /// This function evaluates if we should retry the failure - final bool Function(StreamChatClient client, int attempt, ApiError apiError) + final bool Function(StreamChatClient client, int attempt, ApiError? apiError) shouldRetry; /// In the case that we want to retry a failed request the retryTimeout /// method is called to determine the timeout final Duration Function( - StreamChatClient client, int attempt, ApiError apiError) retryTimeout; + StreamChatClient client, int attempt, ApiError? apiError) retryTimeout; /// Creates a copy of [RetryPolicy] with specified attributes overridden. RetryPolicy copyWith({ - bool Function(StreamChatClient client, int attempt, ApiError apiError) + bool Function(StreamChatClient client, int attempt, ApiError? apiError)? shouldRetry, - Duration Function(StreamChatClient client, int attempt, ApiError apiError) + Duration Function(StreamChatClient client, int attempt, ApiError? apiError)? retryTimeout, - int attempt, + int? attempt, }) => RetryPolicy( retryTimeout: retryTimeout ?? this.retryTimeout, diff --git a/packages/stream_chat/lib/src/api/retry_queue.dart b/packages/stream_chat/lib/src/api/retry_queue.dart index 1049e0e9..a63a7d3a 100644 --- a/packages/stream_chat/lib/src/api/retry_queue.dart +++ b/packages/stream_chat/lib/src/api/retry_queue.dart @@ -14,7 +14,7 @@ import 'package:stream_chat/stream_chat.dart'; class RetryQueue { /// Instantiate a new RetryQueue object RetryQueue({ - @required this.channel, + required this.channel, this.logger, }) { _retryPolicy = channel.client.retryPolicy; @@ -28,29 +28,29 @@ class RetryQueue { final Channel channel; /// The logger associated to this queue - final Logger logger; + final Logger? logger; final _subscriptions = []; void _listenConnectionRecovered() { _subscriptions .add(channel.client.on(EventType.connectionRecovered).listen((event) { - if (!_isRetrying && event.online) { + if (!_isRetrying && event.online!) { _startRetrying(); } })); } - final HeapPriorityQueue _messageQueue = HeapPriorityQueue(_byDate); + final HeapPriorityQueue _messageQueue = HeapPriorityQueue(_byDate); bool _isRetrying = false; - RetryPolicy _retryPolicy; + RetryPolicy? _retryPolicy; /// Add a list of messages - void add(List messages) { + void add(List messages) { logger?.info('added ${messages.length} messages'); final messageList = _messageQueue.toList(); _messageQueue.addAll(messages - .where((element) => !messageList.any((m) => m.id == element.id))); + .where((element) => !messageList.any((m) => m!.id == element!.id))); if (_messageQueue.isNotEmpty && !_isRetrying) { _startRetrying(); @@ -60,10 +60,10 @@ class RetryQueue { Future _startRetrying() async { logger?.info('start retrying'); _isRetrying = true; - final retryPolicy = _retryPolicy.copyWith(attempt: 0); + final retryPolicy = _retryPolicy!.copyWith(attempt: 0); while (_messageQueue.isNotEmpty) { - final message = _messageQueue.first; + final message = _messageQueue.first!; try { logger?.info('retry attempt ${retryPolicy.attempt}'); await _sendMessage(message); @@ -72,7 +72,7 @@ class RetryQueue { logger?.info('now ${_messageQueue.length} messages in the queue'); retryPolicy.attempt = 0; } catch (error) { - ApiError apiError; + ApiError? apiError; if (error is DioError) { if (error.type == DioErrorType.response) { _messageQueue.remove(message); @@ -84,7 +84,7 @@ class RetryQueue { ); } else if (error is ApiError) { apiError = error; - if (apiError.status?.toString()?.startsWith('4') == true) { + if (apiError.status?.toString().startsWith('4') == true) { _messageQueue.remove(message); return; } @@ -101,6 +101,7 @@ class RetryQueue { } retryPolicy.attempt++; + final timeout = retryPolicy.retryTimeout( channel.client, retryPolicy.attempt, @@ -112,13 +113,13 @@ class RetryQueue { _isRetrying = false; } - void _sendFailedEvent(Message message) { - final newStatus = message.status == MessageSendingStatus.sending + void _sendFailedEvent(Message? message) { + final newStatus = message!.status == MessageSendingStatus.sending ? MessageSendingStatus.failed : (message.status == MessageSendingStatus.updating ? MessageSendingStatus.failed_update : MessageSendingStatus.failed_delete); - channel.state.addMessage(message.copyWith( + channel.state!.addMessage(message.copyWith( status: newStatus, )); } @@ -141,20 +142,20 @@ class RetryQueue { final messageList = _messageQueue.toList(); if (event.message != null) { final messageIndex = - messageList.indexWhere((m) => m.id == event.message.id); + messageList.indexWhere((m) => m!.id == event.message!.id); if (messageIndex == -1 && [ MessageSendingStatus.failed_update, MessageSendingStatus.failed, MessageSendingStatus.failed_delete, - ].contains(event.message.status)) { + ].contains(event.message!.status)) { logger?.info('add message from events'); add([event.message]); } else if (messageIndex != -1 && [ MessageSendingStatus.sent, null, - ].contains(event.message.status)) { + ].contains(event.message!.status)) { _messageQueue.remove(messageList[messageIndex]); } } @@ -167,14 +168,14 @@ class RetryQueue { _subscriptions.forEach((s) => s.cancel()); } - static int _byDate(Message m1, Message m2) { - final date1 = _getMessageDate(m1); - final date2 = _getMessageDate(m2); + static int _byDate(Message? m1, Message? m2) { + final date1 = _getMessageDate(m1!)!; + final date2 = _getMessageDate(m2!)!; return date1.compareTo(date2); } - static DateTime _getMessageDate(Message m1) { + static DateTime? _getMessageDate(Message m1) { switch (m1.status) { case MessageSendingStatus.failed_delete: case MessageSendingStatus.deleting: diff --git a/packages/stream_chat/lib/src/api/web_socket_channel_html.dart b/packages/stream_chat/lib/src/api/web_socket_channel_html.dart index 9ddd83e5..ab821ca9 100644 --- a/packages/stream_chat/lib/src/api/web_socket_channel_html.dart +++ b/packages/stream_chat/lib/src/api/web_socket_channel_html.dart @@ -3,5 +3,5 @@ import 'package:web_socket_channel/web_socket_channel.dart'; /// Html version of websocket implementation /// Used in Flutter web version -WebSocketChannel connectWebSocket(String url, {Iterable protocols}) => +WebSocketChannel connectWebSocket(String url, {Iterable? protocols}) => HtmlWebSocketChannel.connect(url, protocols: protocols); diff --git a/packages/stream_chat/lib/src/api/web_socket_channel_io.dart b/packages/stream_chat/lib/src/api/web_socket_channel_io.dart index ed37ba7f..8402ca0c 100644 --- a/packages/stream_chat/lib/src/api/web_socket_channel_io.dart +++ b/packages/stream_chat/lib/src/api/web_socket_channel_io.dart @@ -3,5 +3,5 @@ import 'package:web_socket_channel/web_socket_channel.dart'; /// IO version of websocket implementation /// Used in Flutter mobile version -WebSocketChannel connectWebSocket(String url, {Iterable protocols}) => +WebSocketChannel connectWebSocket(String url, {Iterable? protocols}) => IOWebSocketChannel.connect(url, protocols: protocols); diff --git a/packages/stream_chat/lib/src/api/web_socket_channel_stub.dart b/packages/stream_chat/lib/src/api/web_socket_channel_stub.dart index 7e2e47bd..e2efaee6 100644 --- a/packages/stream_chat/lib/src/api/web_socket_channel_stub.dart +++ b/packages/stream_chat/lib/src/api/web_socket_channel_stub.dart @@ -3,7 +3,7 @@ import 'package:web_socket_channel/web_socket_channel.dart'; /// Stub version of websocket implementation /// Used just for conditional library import WebSocketChannel connectWebSocket(String url, - {Iterable protocols, - Map headers, - Duration pingInterval}) => + {Iterable? protocols, + Map? headers, + Duration? pingInterval}) => throw UnimplementedError(); diff --git a/packages/stream_chat/lib/src/api/websocket.dart b/packages/stream_chat/lib/src/api/websocket.dart index 0f78b1bf..ba4d4271 100644 --- a/packages/stream_chat/lib/src/api/websocket.dart +++ b/packages/stream_chat/lib/src/api/websocket.dart @@ -16,8 +16,8 @@ typedef EventHandler = void Function(Event); /// Typedef used for connecting to a websocket. Method returns a /// [WebSocketChannel] and accepts a connection [url] and an optional /// [Iterable] of `protocols`. -typedef ConnectWebSocket = WebSocketChannel Function(String url, - {Iterable protocols}); +typedef ConnectWebSocket = WebSocketChannel Function(String? url, + {Iterable? protocols}); // TODO: parse error even // TODO: if parsing an error into an event fails we should not hide the @@ -27,7 +27,7 @@ class WebSocket { /// Creates a new websocket /// To connect the WS call [connect] WebSocket({ - @required this.baseUrl, + required this.baseUrl, this.user, this.connectParams, this.connectPayload, @@ -38,22 +38,22 @@ class WebSocket { this.healthCheckInterval = 20, this.reconnectionMonitorTimeout = 40, }) { - final qs = Map.from(connectParams); + final qs = Map.from(connectParams!); - final data = Map.from(connectPayload); + final data = Map.from(connectPayload!); - data['user_details'] = user.toJson(); + data['user_details'] = user!.toJson(); qs['json'] = json.encode(data); if (baseUrl.startsWith('https')) { _path = baseUrl.replaceFirst('https://', ''); - _path = Uri.https(_path, 'connect', qs) + _path = Uri.https(_path!, 'connect', qs) .toString() .replaceFirst('https', 'wss'); } else if (baseUrl.startsWith('http')) { _path = baseUrl.replaceFirst('http://', ''); _path = - Uri.http(_path, 'connect', qs).toString().replaceFirst('http', 'ws'); + Uri.http(_path!, 'connect', qs).toString().replaceFirst('http', 'ws'); } else { _path = Uri.https(baseUrl, 'connect', qs) .toString() @@ -65,25 +65,25 @@ class WebSocket { final String baseUrl; /// User performing the WS connection - final User user; + final User? user; /// Querystring connection parameters - final Map connectParams; + final Map? connectParams; /// WS connection payload - final Map connectPayload; + final Map? connectPayload; /// Functions that will be called every time a new event is received from the /// connection - final EventHandler handler; + final EventHandler? handler; /// A WS specific logger instance - final Logger logger; + final Logger? logger; /// Connection function /// Used only for testing purpose @visibleForTesting - final ConnectWebSocket connectFunc; + final ConnectWebSocket? connectFunc; /// Interval of the reconnection monitor timer /// This checks that it received a new event in the last @@ -107,17 +107,17 @@ class WebSocket { _connectionStatusController.add(status); /// The current connection status value - ConnectionStatus get connectionStatus => _connectionStatusController.value; + ConnectionStatus? get connectionStatus => _connectionStatusController.value; /// This notifies of connection status changes Stream get connectionStatusStream => _connectionStatusController.stream; - String _path; + String? _path; int _retryAttempt = 1; - WebSocketChannel _channel; - Timer _healthCheck, _reconnectionMonitor; - DateTime _lastEventAt; + late WebSocketChannel _channel; + Timer? _healthCheck, _reconnectionMonitor; + DateTime? _lastEventAt; bool _manuallyDisconnected = false; bool _connecting = false; bool _reconnecting = false; @@ -127,23 +127,23 @@ class WebSocket { Completer _connectionCompleter = Completer(); /// Connect the WS using the parameters passed in the constructor - Future connect() { + Future? connect() { _manuallyDisconnected = false; if (_connecting) { - logger.severe('already connecting'); + logger!.severe('already connecting'); return null; } _connecting = true; _connectionStatus = ConnectionStatus.connecting; - logger.info('connecting to $_path'); + logger!.info('connecting to $_path'); _channel = - connectFunc?.call(_path) ?? WebSocketChannel.connect(Uri.parse(_path)); + connectFunc?.call(_path) ?? WebSocketChannel.connect(Uri.parse(_path!)); _channel.stream.listen( - (data) { + (data) async { final jsonData = json.decode(data); if (jsonData['error'] != null) { return _onConnectionError(jsonData['error']); @@ -166,7 +166,7 @@ class WebSocket { return; } - logger.info('connection closed | closeCode: ${_channel.closeCode} | ' + logger!.info('connection closed | closeCode: ${_channel.closeCode} | ' 'closedReason: ${_channel.closeReason}'); if (!_reconnecting) { @@ -180,10 +180,10 @@ class WebSocket { } final event = _decodeEvent(data); - logger.info('received new event: $data'); + logger!.info('received new event: $data'); if (_lastEventAt == null) { - logger.info('connection estabilished'); + logger!.info('connection estabilished'); _connecting = false; _reconnecting = false; _lastEventAt = DateTime.now(); @@ -199,14 +199,14 @@ class WebSocket { _startHealthCheck(); } - handler(event); + handler!(event); _lastEventAt = DateTime.now(); } Future _onConnectionError(error, [stacktrace]) async { - logger..severe('error connecting')..severe(error); + logger!..severe('error connecting')..severe(error); if (stacktrace != null) { - logger.severe(stacktrace); + logger!.severe(stacktrace); } _connecting = false; @@ -225,7 +225,7 @@ class WebSocket { void _reconnectionTimer(_) { final now = DateTime.now(); if (_lastEventAt != null && - now.difference(_lastEventAt).inSeconds > reconnectionMonitorTimeout) { + now.difference(_lastEventAt!).inSeconds > reconnectionMonitorTimeout) { _channel.sink.close(); } } @@ -244,18 +244,18 @@ class WebSocket { return; } if (_connecting) { - logger.info('already connecting'); + logger!.info('already connecting'); return; } - logger.info('reconnecting..'); + logger!.info('reconnecting..'); _cancelTimers(); try { await connect(); } catch (e) { - logger.log(Level.SEVERE, e.toString()); + logger!.log(Level.SEVERE, e.toString()); } await Future.delayed( Duration(seconds: min(_retryAttempt * 5, 25)), @@ -267,7 +267,7 @@ class WebSocket { } Future _reconnect() async { - logger.info('reconnect'); + logger!.info('reconnect'); if (!_reconnecting) { _reconnecting = true; _connectionStatus = ConnectionStatus.connecting; @@ -279,20 +279,20 @@ class WebSocket { void _cancelTimers() { _lastEventAt = null; if (_healthCheck != null) { - _healthCheck.cancel(); + _healthCheck!.cancel(); } if (_reconnectionMonitor != null) { - _reconnectionMonitor.cancel(); + _reconnectionMonitor!.cancel(); } } void _healthCheckTimer(_) { - logger.info('sending health.check'); + logger!.info('sending health.check'); _channel.sink.add("{'type': 'health.check'}"); } void _startHealthCheck() { - logger.info('start health check monitor'); + logger!.info('start health check monitor'); _healthCheck = Timer.periodic( Duration(seconds: healthCheckInterval), @@ -311,7 +311,7 @@ class WebSocket { if (_manuallyDisconnected) { return; } - logger.info('disconnecting'); + logger!.info('disconnecting'); _connectionCompleter = Completer(); _cancelTimers(); _reconnecting = false; diff --git a/packages/stream_chat/lib/src/attachment_file_uploader.dart b/packages/stream_chat/lib/src/attachment_file_uploader.dart index 7d9e8c40..42fab498 100644 --- a/packages/stream_chat/lib/src/attachment_file_uploader.dart +++ b/packages/stream_chat/lib/src/attachment_file_uploader.dart @@ -11,12 +11,12 @@ abstract class AttachmentFileUploader { /// /// Optionally, access upload progress using [onSendProgress] /// and cancel the request using [cancelToken] - Future sendImage( - AttachmentFile image, - String channelId, - String channelType, { - ProgressCallback onSendProgress, - CancelToken cancelToken, + Future sendImage( + AttachmentFile? image, + String? channelId, + String? channelType, { + ProgressCallback? onSendProgress, + CancelToken? cancelToken, }); /// Uploads a [file] to the given channel. @@ -24,34 +24,34 @@ abstract class AttachmentFileUploader { /// /// Optionally, access upload progress using [onSendProgress] /// and cancel the request using [cancelToken] - Future sendFile( - AttachmentFile file, - String channelId, - String channelType, { - ProgressCallback onSendProgress, - CancelToken cancelToken, + Future sendFile( + AttachmentFile? file, + String? channelId, + String? channelType, { + ProgressCallback? onSendProgress, + CancelToken? cancelToken, }); /// Deletes a image using its [url] from the given channel. /// Returns [EmptyResponse] once deleted successfully. /// /// Optionally, cancel the request using [cancelToken] - Future deleteImage( + Future deleteImage( String url, - String channelId, - String channelType, { - CancelToken cancelToken, + String? channelId, + String? channelType, { + CancelToken? cancelToken, }); /// Deletes a file using its [url] from the given channel. /// Returns [EmptyResponse] once deleted successfully. /// /// Optionally, cancel the request using [cancelToken] - Future deleteFile( + Future deleteFile( String url, - String channelId, - String channelType, { - CancelToken cancelToken, + String? channelId, + String? channelType, { + CancelToken? cancelToken, }); } @@ -63,26 +63,26 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { final StreamChatClient _client; @override - Future sendImage( - AttachmentFile file, - String channelId, - String channelType, { - ProgressCallback onSendProgress, - CancelToken cancelToken, + Future sendImage( + AttachmentFile? file, + String? channelId, + String? channelType, { + ProgressCallback? onSendProgress, + CancelToken? cancelToken, }) async { - final filename = file.path?.split('/')?.last ?? file.name; + final filename = file!.path?.split('/')?.last ?? file.name; final mimeType = filename.mimeType; - MultipartFile multiPartFile; + MultipartFile? multiPartFile; if (file.path != null) { multiPartFile = await MultipartFile.fromFile( - file.path, + file.path!, filename: filename, contentType: mimeType, ); } else if (file.bytes != null) { multiPartFile = MultipartFile.fromBytes( - file.bytes, + file.bytes!, filename: filename, contentType: mimeType, ); @@ -100,26 +100,26 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { } @override - Future sendFile( - AttachmentFile file, - String channelId, - String channelType, { - ProgressCallback onSendProgress, - CancelToken cancelToken, + Future sendFile( + AttachmentFile? file, + String? channelId, + String? channelType, { + ProgressCallback? onSendProgress, + CancelToken? cancelToken, }) async { - final filename = file.path?.split('/')?.last ?? file.name; + final filename = file!.path?.split('/')?.last ?? file.name; final mimeType = filename.mimeType; - MultipartFile multiPartFile; + MultipartFile? multiPartFile; if (file.path != null) { multiPartFile = await MultipartFile.fromFile( - file.path, + file.path!, filename: filename, contentType: mimeType, ); } else if (file.bytes != null) { multiPartFile = MultipartFile.fromBytes( - file.bytes, + file.bytes!, filename: filename, contentType: mimeType, ); @@ -137,11 +137,11 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { } @override - Future deleteImage( + Future deleteImage( String url, - String channelId, - String channelType, { - CancelToken cancelToken, + String? channelId, + String? channelType, { + CancelToken? cancelToken, }) async { final response = await _client.delete( '/channels/$channelType/$channelId/image', @@ -152,11 +152,11 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { } @override - Future deleteFile( + Future deleteFile( String url, - String channelId, - String channelType, { - CancelToken cancelToken, + String? channelId, + String? channelType, { + CancelToken? cancelToken, }) async { final response = await _client.delete( '/channels/$channelType/$channelId/file', diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index f46ba178..5ea3030a 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -34,11 +34,11 @@ import 'package:uuid/uuid.dart'; typedef LogHandlerFunction = void Function(LogRecord record); /// Used for decoding [Map] data to a generic type `T`. -typedef DecoderFunction = T Function(Map); +typedef DecoderFunction = T Function(Map?); /// A function which can be used to request a Stream Chat API token from your /// own backend server. Function requires a single [userId]. -typedef TokenProvider = Future Function(String userId); +typedef TokenProvider = Future Function(String? userId); /// Provider used to send push notifications. enum PushProvider { @@ -85,17 +85,18 @@ class StreamChatClient { this.logHandlerFunction, Duration connectTimeout = const Duration(seconds: 6), Duration receiveTimeout = const Duration(seconds: 6), - Dio httpClient, - RetryPolicy retryPolicy, + Dio? httpClient, + RetryPolicy? retryPolicy, this.attachmentFileUploader, }) { _retryPolicy = retryPolicy ?? RetryPolicy( retryTimeout: - (StreamChatClient client, int attempt, ApiError error) => + (StreamChatClient client, int attempt, ApiError? error) => Duration(seconds: 1 * attempt), - shouldRetry: (StreamChatClient client, int attempt, ApiError error) => - attempt < 5, + shouldRetry: + (StreamChatClient client, int attempt, ApiError? error) => + attempt < 5, ); attachmentFileUploader ??= StreamAttachmentFileUploader(this); @@ -108,32 +109,32 @@ class StreamChatClient { logger.info('instantiating new client'); } - set chatPersistenceClient(ChatPersistenceClient value) { + set chatPersistenceClient(ChatPersistenceClient? value) { _originalChatPersistenceClient = value; } - ChatPersistenceClient _originalChatPersistenceClient; + ChatPersistenceClient? _originalChatPersistenceClient; /// Chat persistence client - ChatPersistenceClient get chatPersistenceClient => _chatPersistenceClient; + ChatPersistenceClient? get chatPersistenceClient => _chatPersistenceClient; - ChatPersistenceClient _chatPersistenceClient; + ChatPersistenceClient? _chatPersistenceClient; /// Attachment uploader - AttachmentFileUploader attachmentFileUploader; + AttachmentFileUploader? attachmentFileUploader; /// Whether the chat persistence is available or not bool get persistenceEnabled => _chatPersistenceClient != null; - RetryPolicy _retryPolicy; + RetryPolicy? _retryPolicy; bool _synced = false; /// The retry policy options getter - RetryPolicy get retryPolicy => _retryPolicy; + RetryPolicy? get retryPolicy => _retryPolicy; /// This client state - ClientState state; + ClientState? state; /// By default the Chat client will write all messages with level Warn or /// Error to stdout. @@ -169,7 +170,7 @@ class StreamChatClient { /// final client = StreamChatClient("stream-chat-api-key", /// logHandlerFunction: myLogHandlerFunction); ///``` - LogHandlerFunction logHandlerFunction; + LogHandlerFunction? logHandlerFunction; /// Your project Stream Chat api key. /// Find your API keys here https://getstream.io/dashboard/ @@ -184,7 +185,7 @@ class StreamChatClient { /// The token will be the return value of the function. /// It's used by the client to refresh the token once expired or to connect /// the user without a predefined token using [connectUserWithProvider]. - final TokenProvider tokenProvider; + final TokenProvider? tokenProvider; /// [Dio] httpClient /// It's be chosen because it's easy to use and supports interesting features @@ -195,8 +196,8 @@ class StreamChatClient { static const _defaultBaseURL = 'chat-us-east-1.stream-io-api.com'; static const _tokenExpiredErrorCode = 40; - StreamSubscription _connectionStatusSubscription; - Future Function(ConnectionStatus) _connectionStatusHandler; + StreamSubscription? _connectionStatusSubscription; + Future Function(ConnectionStatus)? _connectionStatusHandler; final BehaviorSubject _controller = BehaviorSubject(); @@ -211,7 +212,7 @@ class StreamChatClient { _wsConnectionStatusController.add(status); /// The current status value of the websocket connection - ConnectionStatus get wsConnectionStatus => + ConnectionStatus? get wsConnectionStatus => _wsConnectionStatusController.value; /// This notifies the connection status of the websocket connection. @@ -220,19 +221,19 @@ class StreamChatClient { _wsConnectionStatusController.stream; /// The current user token - String token; + String? token; /// The id of the current websocket connection - String get connectionId => _connectionId; + String? get connectionId => _connectionId; bool _anonymous = false; - String _connectionId; - WebSocket _ws; + String? _connectionId; + late WebSocket _ws; bool get _hasConnectionId => _connectionId != null; void _setupDio( - Dio httpClient, + Dio? httpClient, Duration receiveTimeout, Duration connectTimeout, ) { @@ -301,11 +302,11 @@ class StreamChatClient { if (tokenProvider != null) { httpClient.lock(); - final userId = state.user.id; + final userId = state!.user!.id; await _disconnect(); - final newToken = await tokenProvider(userId); + final newToken = await tokenProvider!(userId); await Future.delayed(const Duration(seconds: 4)); token = newToken; @@ -341,13 +342,11 @@ class StreamChatClient { ), ), ); - } catch (err) { + } on DioError { handler.reject(err); } } } - - return err; } LogHandlerFunction _getDefaultLogHandler() { @@ -391,11 +390,11 @@ class StreamChatClient { await _disconnect(); httpClient.close(); await _controller.close(); - state.dispose(); + state!.dispose(); await _wsConnectionStatusController.close(); } - Map get _httpHeaders => { + Map get _httpHeaders => { 'Authorization': token, 'stream-auth-type': _authType, 'X-Stream-Client': _userAgent, @@ -409,8 +408,8 @@ class StreamChatClient { /// Connects the current user, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. - Future connectUser(User user, String token) async { - if (_connectCompleter != null && !_connectCompleter.isCompleted) { + Future connectUser(User user, String? token) async { + if (_connectCompleter != null && !_connectCompleter!.isCompleted) { logger.warning('Already connecting'); throw Exception('Already connecting'); } @@ -418,15 +417,15 @@ class StreamChatClient { _connectCompleter = Completer(); logger.info('connect user'); - state.user = OwnUser.fromJson(user.toJson()); + state!.user = OwnUser.fromJson(user.toJson()); this.token = token; _anonymous = false; return connect().then((event) { - _connectCompleter.complete(event); + _connectCompleter!.complete(event); return event; }).catchError((e, s) { - _connectCompleter.completeError(e, s); + _connectCompleter!.completeError(e, s); throw e; }); } @@ -447,17 +446,17 @@ class StreamChatClient { Use `connectUser` providing a token. '''); } - final token = await tokenProvider(user.id); + final token = await tokenProvider!(user.id); return connectUser(user, token); } /// Stream of [Event] coming from websocket connection /// Pass an eventType as parameter in order to filter just a type of event Stream on([ - String eventType, - String eventType2, - String eventType3, - String eventType4, + String? eventType, + String? eventType2, + String? eventType3, + String? eventType4, ]) => stream.where((event) => eventType == null || @@ -474,7 +473,7 @@ class StreamChatClient { _connectionId = event.connectionId; } - if (!event.isLocal) { + if (!event.isLocal!) { if (_synced && event.createdAt != null) { await _chatPersistenceClient?.updateConnectionInfo(event); await _chatPersistenceClient?.updateLastSyncAt(event.createdAt); @@ -482,16 +481,16 @@ class StreamChatClient { } if (event.user != null) { - state._updateUser(event.user); + state!._updateUser(event.user); } if (event.me != null) { - state.user = event.me; + state!.user = event.me; } _controller.add(event); } - Completer _connectCompleter; + Completer? _connectCompleter; /// Connect the client websocket Future connect() async { @@ -510,12 +509,12 @@ class StreamChatClient { if (_originalChatPersistenceClient != null) { _chatPersistenceClient = _originalChatPersistenceClient; - await _chatPersistenceClient.connect(state.user.id); + await _chatPersistenceClient!.connect(state!.user!.id); } _ws = WebSocket( baseUrl: baseURL, - user: state.user, + user: state!.user, connectParams: { 'api_key': apiKey, 'authorization': token, @@ -523,7 +522,7 @@ class StreamChatClient { 'X-Stream-Client': _userAgent, }, connectPayload: { - 'user_id': state.user.id, + 'user_id': state!.user!.id, 'server_determines_connection_id': true, }, handler: handleEvent, @@ -544,11 +543,11 @@ class StreamChatClient { type: EventType.connectionRecovered, online: true, )); - if (state.channels?.isNotEmpty == true) { + if (state!.channels?.isNotEmpty == true) { // ignore: unawaited_futures queryChannelsOnline(filter: { 'cid': { - '\$in': state.channels.keys.toList(), + '\$in': state!.channels!.keys.toList(), }, }).then( (_) async { @@ -564,9 +563,9 @@ class StreamChatClient { _connectionStatusSubscription = _ws.connectionStatusStream.listen(_connectionStatusHandler); - var event = await _chatPersistenceClient?.getConnectionInfo(); + var event = (await _chatPersistenceClient?.getConnectionInfo())!; - await _ws.connect().then((e) async { + await _ws.connect()!.then((e) async { await _chatPersistenceClient?.updateConnectionInfo(e); event = e; await resync(); @@ -582,8 +581,8 @@ class StreamChatClient { } /// Get the events missed while offline to sync the offline storage - Future resync([List cids]) async { - final lastSyncAt = await _chatPersistenceClient?.getLastSyncAt(); + Future resync([List? cids]) async { + final lastSyncAt = (await _chatPersistenceClient?.getLastSyncAt())!; if (lastSyncAt == null) { _synced = true; @@ -606,17 +605,17 @@ class StreamChatClient { final res = decode( rawRes.data, SyncResponse.fromJson, - ); + )!; - res.events.sort((a, b) => a.createdAt.compareTo(b.createdAt)); + res.events!.sort((a, b) => a.createdAt!.compareTo(b.createdAt!)); - res.events.forEach((element) { + res.events!.forEach((element) { logger ..fine('element.type: ${element.type}') ..fine('element.message.text: ${element.message?.text}'); }); - res.events.forEach(handleEvent); + res.events!.forEach(handleEvent); await _chatPersistenceClient?.updateLastSyncAt(DateTime.now()); _synced = true; @@ -625,17 +624,17 @@ class StreamChatClient { } } - String _asMap(sort) => sort?.map((s) => s.toJson().toString())?.join(''); + String? _asMap(sort) => sort?.map((s) => s.toJson().toString())?.join(''); final _queryChannelsStreams = >>{}; /// Requests channels with a given query. Stream> queryChannels({ - Map filter, - List> sort, - Map options, + Map? filter, + List>? sort, + Map? options, PaginationParams paginationParams = const PaginationParams(), - int messageLimit, + int? messageLimit, bool waitForConnect = true, }) async* { final hash = base64.encode(utf8.encode( @@ -644,7 +643,7 @@ class StreamChatClient { )); if (_queryChannelsStreams.containsKey(hash)) { - yield await _queryChannelsStreams[hash]; + yield await _queryChannelsStreams[hash]!; } else { final channels = await queryChannelsOffline( filter: filter, @@ -674,17 +673,17 @@ class StreamChatClient { /// Requests channels with a given query from the API. Future> queryChannelsOnline({ - @required Map filter, - List> sort, - Map options, - int messageLimit, + required Map? filter, + List>? sort, + Map? options, + int? messageLimit, PaginationParams paginationParams = const PaginationParams(), bool waitForConnect = true, }) async { if (waitForConnect) { - if (_connectCompleter != null && !_connectCompleter.isCompleted) { + if (_connectCompleter != null && !_connectCompleter!.isCompleted) { logger.info('awaiting connection completer'); - await _connectCompleter.future; + await _connectCompleter!.future; } if (wsConnectionStatus != ConnectionStatus.connected) { throw Exception( @@ -730,7 +729,7 @@ class StreamChatClient { final res = decode( response.data, QueryChannelsResponse.fromJson, - ); + )!; if ((res.channels ?? []).isEmpty && (paginationParams?.offset ?? 0) == 0) { logger.warning( @@ -742,14 +741,14 @@ class StreamChatClient { return []; } - final channels = res.channels; + final channels = res.channels!; final users = channels - .expand((it) => it.members) - .map((it) => it.user) + .expand((it) => it.members!) + .map((it) => it!.user) .toList(growable: false); - state._updateUsers(users); + state!._updateUsers(users); logger.info('Got ${res.channels?.length} channels from api'); @@ -757,39 +756,39 @@ class StreamChatClient { await _chatPersistenceClient?.updateChannelQueries( filter, - channels.map((c) => c.channel.cid).toList(), + channels.map((c) => c.channel!.cid).toList(), clearQueryCache: paginationParams?.offset == null || paginationParams.offset == 0, ); - state.channels = updateData.key; + state!.channels = updateData.key; return updateData.value; } /// Requests channels with a given query from the Persistence client. Future> queryChannelsOffline({ - @required Map filter, - @required List> sort, + required Map? filter, + required List>? sort, PaginationParams paginationParams = const PaginationParams(), }) async { - final offlineChannels = await _chatPersistenceClient?.getChannelStates( + final offlineChannels = (await _chatPersistenceClient?.getChannelStates( filter: filter, sort: sort, paginationParams: paginationParams, - ); + ))!; final updatedData = _mapChannelStateToChannel(offlineChannels); - state.channels = updatedData.key; + state!.channels = updatedData.key; return updatedData.value; } - MapEntry, List> _mapChannelStateToChannel( + MapEntry, List> _mapChannelStateToChannel( List channelStates, ) { - final channels = {...state.channels ?? {}}; + final channels = {...state!.channels ?? {}}; final newChannels = []; if (channelStates != null) { for (final channelState in channelStates) { - final channel = channels[channelState.channel.cid]; + final channel = channels[channelState.channel!.cid]; if (channel != null) { channel.state?.updateChannelState(channelState); newChannels.add(channel); @@ -817,7 +816,7 @@ class StreamChatClient { /// Handy method to make http GET request with error parsing. Future> get( String path, { - Map queryParameters, + Map? queryParameters, }) async { try { final response = await httpClient.get( @@ -835,8 +834,8 @@ class StreamChatClient { Future> post( String path, { dynamic data, - ProgressCallback onSendProgress, - CancelToken cancelToken, + ProgressCallback? onSendProgress, + CancelToken? cancelToken, }) async { try { final response = await httpClient.post( @@ -855,8 +854,8 @@ class StreamChatClient { /// Handy method to make http DELETE request with error parsing. Future> delete( String path, { - Map queryParameters, - CancelToken cancelToken, + Map? queryParameters, + CancelToken? cancelToken, }) async { try { final response = await httpClient.delete( @@ -874,7 +873,7 @@ class StreamChatClient { /// Handy method to make http PATCH request with error parsing. Future> patch( String path, { - Map queryParameters, + Map? queryParameters, dynamic data, }) async { try { @@ -893,7 +892,7 @@ class StreamChatClient { /// Handy method to make http PUT request with error parsing. Future> put( String path, { - Map queryParameters, + Map? queryParameters, dynamic data, }) async { try { @@ -910,7 +909,7 @@ class StreamChatClient { } /// Used to log errors and stacktrace in case of bad json deserialization - T decode(String j, DecoderFunction decoderFunction) { + T? decode(String? j, DecoderFunction decoderFunction) { try { if (j == null) { return null; @@ -927,8 +926,8 @@ class StreamChatClient { String get _userAgent => 'stream-chat-dart-client-${CurrentPlatform.name}-' '${PACKAGE_VERSION.split('+')[0]}'; - Map get _commonQueryParams => { - 'user_id': state.user?.id, + Map get _commonQueryParams => { + 'user_id': state!.user?.id, 'api_key': apiKey, 'connection_id': _connectionId, }; @@ -943,7 +942,7 @@ class StreamChatClient { /// to the API. It returns a [Future] that resolves when the connection is /// setup. Future connectAnonymousUser() async { - if (_connectCompleter != null && !_connectCompleter.isCompleted) { + if (_connectCompleter != null && !_connectCompleter!.isCompleted) { logger.warning('Already connecting'); throw Exception('Already connecting'); } @@ -952,13 +951,13 @@ class StreamChatClient { _anonymous = true; const uuid = Uuid(); - state.user = OwnUser(id: uuid.v4()); + state!.user = OwnUser(id: uuid.v4()); return connect().then((event) { - _connectCompleter.complete(event); + _connectCompleter!.complete(event); return event; }).catchError((e, s) { - _connectCompleter.completeError(e, s); + _connectCompleter!.completeError(e, s); throw e; }); } @@ -978,8 +977,8 @@ class StreamChatClient { res.data, ConnectGuestUserResponse.fromJson)) .whenComplete(() => _anonymous = false); return connectUser( - response.user, - response.accessToken, + (response?.user)!, + response?.accessToken, ); } @@ -999,7 +998,7 @@ class StreamChatClient { _connectCompleter = null; if (clearUser == true) { - state.dispose(); + state!.dispose(); state = ClientState(this); } @@ -1015,10 +1014,10 @@ class StreamChatClient { /// Requests users with a given query. Future queryUsers({ - Map filter, - List sort, - Map options, - PaginationParams pagination, + Map? filter, + List? sort, + Map? options, + PaginationParams? pagination, }) async { final defaultOptions = { 'presence': _hasConnectionId, @@ -1047,20 +1046,20 @@ class StreamChatClient { final response = decode( rawRes.data, QueryUsersResponse.fromJson, - ); + )!; - state?._updateUsers(response.users); + state?._updateUsers(response.users!); return response; } /// A message search. - Future search( + Future search( Map filters, { - String query, - List sort, - PaginationParams paginationParams, - Map messageFilters, + String? query, + List? sort, + PaginationParams? paginationParams, + Map? messageFilters, }) async { assert(() { if (filters == null || filters.isEmpty) { @@ -1094,14 +1093,14 @@ class StreamChatClient { } /// Send a [file] to the [channelId] of type [channelType] - Future sendFile( - AttachmentFile file, - String channelId, - String channelType, { - ProgressCallback onSendProgress, - CancelToken cancelToken, + Future sendFile( + AttachmentFile? file, + String? channelId, + String? channelType, { + ProgressCallback? onSendProgress, + CancelToken? cancelToken, }) => - attachmentFileUploader.sendFile( + attachmentFileUploader!.sendFile( file, channelId, channelType, @@ -1110,14 +1109,14 @@ class StreamChatClient { ); /// Send a [image] to the [channelId] of type [channelType] - Future sendImage( - AttachmentFile image, - String channelId, - String channelType, { - ProgressCallback onSendProgress, - CancelToken cancelToken, + Future sendImage( + AttachmentFile? image, + String? channelId, + String? channelType, { + ProgressCallback? onSendProgress, + CancelToken? cancelToken, }) => - attachmentFileUploader.sendImage( + attachmentFileUploader!.sendImage( image, channelId, channelType, @@ -1126,13 +1125,13 @@ class StreamChatClient { ); /// Delete a file from this channel - Future deleteFile( + Future deleteFile( String url, - String channelId, - String channelType, { - CancelToken cancelToken, + String? channelId, + String? channelType, { + CancelToken? cancelToken, }) => - attachmentFileUploader.deleteFile( + attachmentFileUploader!.deleteFile( url, channelId, channelType, @@ -1140,13 +1139,13 @@ class StreamChatClient { ); /// Delete an image from this channel - Future deleteImage( + Future deleteImage( String url, - String channelId, - String channelType, { - CancelToken cancelToken, + String? channelId, + String? channelType, { + CancelToken? cancelToken, }) => - attachmentFileUploader.deleteImage( + attachmentFileUploader!.deleteImage( url, channelId, channelType, @@ -1154,7 +1153,7 @@ class StreamChatClient { ); /// Add a device for Push Notifications. - Future addDevice(String id, PushProvider pushProvider) async { + Future addDevice(String id, PushProvider pushProvider) async { final response = await post('/devices', data: { 'id': id, 'push_provider': pushProvider.name, @@ -1163,14 +1162,14 @@ class StreamChatClient { } /// Gets a list of user devices. - Future getDevices() async { + Future getDevices() async { final response = await get('/devices'); return decode( response.data, ListDevicesResponse.fromJson); } /// Remove a user's device. - Future removeDevice(String id) async { + Future removeDevice(String id) async { final response = await delete('/devices', queryParameters: { 'id': id, }); @@ -1188,24 +1187,26 @@ class StreamChatClient { /// Returns a channel client with the given type, id and custom data. Channel channel( String type, { - String id, - Map extraData, + String? id, + Map? extraData, }) { if (type != null && id != null && - state.channels?.containsKey('$type:$id') == true) { - return state.channels['$type:$id']; + state!.channels?.containsKey('$type:$id') == true) { + if (state!.channels!['$type:$id'] != null) { + return state!.channels!['$type:$id'] as Channel; + } } return Channel(this, type, id, extraData); } /// Update or Create the given user object. - Future updateUser(User user) async => + Future updateUser(User user) async => updateUsers([user]); /// Batch update a list of users - Future updateUsers(List users) async { + Future updateUsers(List users) async { final response = await post('/users', data: { 'users': users.asMap().map((_, u) => MapEntry(u.id, u.toJson())), }); @@ -1216,7 +1217,7 @@ class StreamChatClient { } /// Bans a user from all channels - Future banUser( + Future banUser( String targetUserID, [ Map options = const {}, ]) async { @@ -1232,7 +1233,7 @@ class StreamChatClient { } /// Remove global ban for a user - Future unbanUser( + Future unbanUser( String targetUserID, [ Map options = const {}, ]) async { @@ -1248,7 +1249,7 @@ class StreamChatClient { } /// Shadow bans a user - Future shadowBan( + Future shadowBan( String targetID, [ Map options = const {}, ]) async => @@ -1258,7 +1259,7 @@ class StreamChatClient { }); /// Removes shadow ban from a user - Future removeShadowBan( + Future removeShadowBan( String targetID, [ Map options = const {}, ]) async => @@ -1268,7 +1269,7 @@ class StreamChatClient { }); /// Mutes a user - Future muteUser(String targetID) async { + Future muteUser(String targetID) async { final response = await post('/moderation/mute', data: { 'target_id': targetID, }); @@ -1276,7 +1277,7 @@ class StreamChatClient { } /// Unmutes a user - Future unmuteUser(String targetID) async { + Future unmuteUser(String targetID) async { final response = await post('/moderation/unmute', data: { 'target_id': targetID, }); @@ -1284,7 +1285,7 @@ class StreamChatClient { } /// Flag a message - Future flagMessage(String messageID) async { + Future flagMessage(String messageID) async { final response = await post('/moderation/flag', data: { 'target_message_id': messageID, }); @@ -1292,7 +1293,7 @@ class StreamChatClient { } /// Unflag a message - Future unflagMessage(String messageId) async { + Future unflagMessage(String messageId) async { final response = await post('/moderation/unflag', data: { 'target_message_id': messageId, }); @@ -1300,7 +1301,7 @@ class StreamChatClient { } /// Flag a user - Future flagUser(String userId) async { + Future flagUser(String userId) async { final response = await post('/moderation/flag', data: { 'target_user_id': userId, }); @@ -1308,7 +1309,7 @@ class StreamChatClient { } /// Unflag a message - Future unflagUser(String userId) async { + Future unflagUser(String userId) async { final response = await post('/moderation/unflag', data: { 'target_user_id': userId, }); @@ -1316,14 +1317,14 @@ class StreamChatClient { } /// Mark all channels for this user as read - Future markAllRead() async { + Future markAllRead() async { final response = await post('/channels/read'); return decode(response.data, EmptyResponse.fromJson); } /// Sends the message to the given channel - Future sendMessage( - Message message, String channelId, String channelType) async { + Future sendMessage( + Message message, String? channelId, String? channelType) async { final response = await post( '/channels/$channelType/$channelId/message', data: {'message': message.toJson()}, @@ -1332,7 +1333,7 @@ class StreamChatClient { } /// Update the given message - Future updateMessage(Message message) async { + Future updateMessage(Message message) async { final response = await post( '/messages/${message.id}', data: {'message': message.toJson()}, @@ -1341,19 +1342,19 @@ class StreamChatClient { } /// Deletes the given message - Future deleteMessage(Message message) async { + Future deleteMessage(Message message) async { final response = await delete('/messages/${message.id}'); return decode(response.data, EmptyResponse.fromJson); } /// Get a message by id - Future getMessage(String messageId) async { + Future getMessage(String messageId) async { final response = await get('/messages/$messageId'); return decode(response.data, GetMessageResponse.fromJson); } /// Pins provided message - Future pinMessage( + Future pinMessage( Message message, Object timeoutOrExpirationDate, ) { @@ -1366,7 +1367,7 @@ class StreamChatClient { return true; }(), 'Check whether time out is valid'); - DateTime pinExpires; + DateTime? pinExpires; if (timeoutOrExpirationDate is DateTime) { pinExpires = timeoutOrExpirationDate.toUtc(); } else if (timeoutOrExpirationDate is num) { @@ -1380,7 +1381,7 @@ class StreamChatClient { } /// Unpins provided message - Future unpinMessage(Message message) => + Future unpinMessage(Message message) => updateMessage(message.copyWith(pinned: false)); } @@ -1395,7 +1396,7 @@ class ClientState { .map((e) => e.me) .listen((user) { _userController.add(user); - if (user.totalUnreadCount != null) { + if (user!.totalUnreadCount != null) { _totalUnreadCountController.add(user.totalUnreadCount); } @@ -1425,7 +1426,7 @@ class ClientState { final _subscriptions = []; /// Used internally for optimistic update of unread count - set totalUnreadCount(int unreadCount) { + set totalUnreadCount(int? unreadCount) { _totalUnreadCountController?.add(unreadCount ?? 0); } @@ -1433,15 +1434,15 @@ class ClientState { _subscriptions.add(_client.on(EventType.channelHidden).listen((event) { _client.chatPersistenceClient?.deleteChannels([event.cid]); if (channels != null) { - channels = channels..removeWhere((cid, ch) => cid == event.cid); + channels = channels?..removeWhere((cid, ch) => cid == event.cid); } })); } void _listenUserUpdated() { _subscriptions.add(_client.on(EventType.userUpdated).listen((event) { - if (event.user.id == user.id) { - user = OwnUser.fromJson(event.user.toJson()); + if (event.user!.id == user!.id) { + user = OwnUser.fromJson(event.user!.toJson()); } _updateUser(event.user); })); @@ -1455,10 +1456,10 @@ class ClientState { EventType.notificationChannelDeleted, ) .listen((Event event) async { - final eventChannel = event.channel; + final eventChannel = event.channel!; await _client.chatPersistenceClient?.deleteChannels([eventChannel.cid]); if (channels != null) { - channels = channels..remove(eventChannel.cid); + channels = channels?..remove(eventChannel.cid); } })); } @@ -1466,61 +1467,62 @@ class ClientState { final StreamChatClient _client; /// Update user information - set user(OwnUser user) { + set user(OwnUser? user) { _userController.add(user); } - void _updateUsers(List userList) { + void _updateUsers(List userList) { final newUsers = { ...users ?? {}, - for (var user in userList) user.id: user, + for (var user in userList) user!.id: user, }; _usersController.add(newUsers); } - void _updateUser(User user) => _updateUsers([user]); + void _updateUser(User? user) => _updateUsers([user]); /// The current user - OwnUser get user => _userController.value; + OwnUser? get user => _userController.value; /// The current user as a stream - Stream get userStream => _userController.stream; + Stream get userStream => _userController.stream; /// The current user - Map get users => _usersController.value; + Map? get users => _usersController.value as Map?; /// The current user as a stream - Stream> get usersStream => _usersController.stream; + Stream> get usersStream => _usersController.stream; /// The current unread channels count - int get unreadChannels => _unreadChannelsController.value; + int? get unreadChannels => _unreadChannelsController.value; /// The current unread channels count as a stream - Stream get unreadChannelsStream => _unreadChannelsController.stream; + Stream get unreadChannelsStream => _unreadChannelsController.stream; /// The current total unread messages count - int get totalUnreadCount => _totalUnreadCountController.value; + int? get totalUnreadCount => _totalUnreadCountController.value; /// The current total unread messages count as a stream - Stream get totalUnreadCountStream => _totalUnreadCountController.stream; + Stream get totalUnreadCountStream => _totalUnreadCountController.stream; /// The current list of channels in memory as a stream - Stream> get channelsStream => _channelsController.stream; + Stream?> get channelsStream => + _channelsController.stream; /// The current list of channels in memory - Map get channels => _channelsController.value; + Map? get channels => _channelsController.value; - set channels(Map v) { + set channels(Map? v) { _channelsController.add(v); } - final BehaviorSubject> _channelsController = + final BehaviorSubject?> _channelsController = BehaviorSubject.seeded({}); - final BehaviorSubject _userController = BehaviorSubject(); - final BehaviorSubject> _usersController = + final BehaviorSubject _userController = BehaviorSubject(); + final BehaviorSubject> _usersController = BehaviorSubject.seeded({}); - final BehaviorSubject _unreadChannelsController = BehaviorSubject(); - final BehaviorSubject _totalUnreadCountController = BehaviorSubject(); + final BehaviorSubject _unreadChannelsController = BehaviorSubject(); + final BehaviorSubject _totalUnreadCountController = BehaviorSubject(); /// Call this method to dispose this object void dispose() { @@ -1528,7 +1530,7 @@ class ClientState { _userController.close(); _unreadChannelsController.close(); _totalUnreadCountController.close(); - channels.values.forEach((c) => c.dispose()); + channels!.values.forEach((c) => c.dispose()); _channelsController.close(); } } diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index d15772df..6036c649 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -11,7 +11,7 @@ import 'package:stream_chat/src/models/user.dart'; /// A simple client used for persisting chat data locally. abstract class ChatPersistenceClient { /// Creates a new connection to the client - Future connect(String userId); + Future connect(String? userId); /// Closes the client connection /// If [flush] is true, the data will also be deleted @@ -20,7 +20,7 @@ abstract class ChatPersistenceClient { /// Get stored replies by messageId Future> getReplies( String parentId, { - PaginationParams options, + PaginationParams? options, }); /// Get stored connection event @@ -33,40 +33,40 @@ abstract class ChatPersistenceClient { Future updateConnectionInfo(Event event); /// Update stored lastSyncAt - Future updateLastSyncAt(DateTime lastSyncAt); + Future updateLastSyncAt(DateTime? lastSyncAt); /// Get the channel cids saved in the offline storage Future> getChannelCids(); /// Get stored [ChannelModel]s by providing channel [cid] - Future getChannelByCid(String cid); + Future getChannelByCid(String? cid); /// Get stored channel [Member]s by providing channel [cid] - Future> getMembersByCid(String cid); + Future> getMembersByCid(String? cid); /// Get stored channel [Read]s by providing channel [cid] - Future> getReadsByCid(String cid); + Future> getReadsByCid(String? cid); /// Get stored [Message]s by providing channel [cid] /// /// Optionally, you can [messagePagination] /// for filtering out messages Future> getMessagesByCid( - String cid, { - PaginationParams messagePagination, + String? cid, { + PaginationParams? messagePagination, }); /// Get stored pinned [Message]s by providing channel [cid] Future> getPinnedMessagesByCid( - String cid, { - PaginationParams messagePagination, + String? cid, { + PaginationParams? messagePagination, }); /// Get [ChannelState] data by providing channel [cid] Future getChannelStateByCid( - String cid, { - PaginationParams messagePagination, - PaginationParams pinnedMessagePagination, + String? cid, { + PaginationParams? messagePagination, + PaginationParams? pinnedMessagePagination, }) async { final data = await Future.wait([ getMembersByCid(cid), @@ -76,11 +76,11 @@ abstract class ChatPersistenceClient { getPinnedMessagesByCid(cid, messagePagination: pinnedMessagePagination), ]); return ChannelState( - members: data[0], - read: data[1], - channel: data[2], - messages: data[3], - pinnedMessages: data[4], + members: data[0] as List?, + read: data[1] as List?, + channel: data[2] as ChannelModel?, + messages: data[3] as List?, + pinnedMessages: data[4] as List?, ); } @@ -89,9 +89,9 @@ abstract class ChatPersistenceClient { /// Optionally, pass [filter], [sort], [paginationParams] /// for filtering out states. Future> getChannelStates({ - Map filter, - List> sort = const [], - PaginationParams paginationParams, + Map? filter, + List>? sort = const [], + PaginationParams? paginationParams, }); /// Update list of channel queries. @@ -99,8 +99,8 @@ abstract class ChatPersistenceClient { /// If [clearQueryCache] is true before the insert /// the list of matching rows will be deleted Future updateChannelQueries( - Map filter, - List cids, { + Map? filter, + List cids, { bool clearQueryCache = false, }); @@ -119,46 +119,46 @@ abstract class ChatPersistenceClient { Future deletePinnedMessageByIds(List messageIds); /// Remove a message by channel [cid] - Future deleteMessageByCid(String cid) => deleteMessageByCids([cid]); + Future deleteMessageByCid(String? cid) => deleteMessageByCids([cid]); /// Remove a pinned message by channel [cid] Future deletePinnedMessageByCid(String cid) async => deletePinnedMessageByCids([cid]); /// Remove a message by message [cids] - Future deleteMessageByCids(List cids); + Future deleteMessageByCids(List cids); /// Remove a pinned message by message [cids] Future deletePinnedMessageByCids(List cids); /// Remove a channel by [cid] - Future deleteChannels(List cids); + Future deleteChannels(List cids); /// Updates the message data of a particular channel [cid] with /// the new [messages] data - Future updateMessages(String cid, List messages); + Future updateMessages(String? cid, List messages); /// Updates the pinned message data of a particular channel [cid] with /// the new [messages] data - Future updatePinnedMessages(String cid, List messages); + Future updatePinnedMessages(String? cid, List messages); /// Returns all the threads by parent message of a particular channel by /// providing channel [cid] - Future>> getChannelThreads(String cid); + Future>> getChannelThreads(String? cid); /// Updates all the channels using the new [channels] data. - Future updateChannels(List channels); + Future updateChannels(List channels); /// Updates all the members of a particular channle [cid] /// with the new [members] data - Future updateMembers(String cid, List members); + Future updateMembers(String? cid, List members); /// Updates the read data of a particular channel [cid] with /// the new [reads] data - Future updateReads(String cid, List reads); + Future updateReads(String? cid, List reads); /// Updates the users data with the new [users] data - Future updateUsers(List users); + Future updateUsers(List users); /// Updates the reactions data with the new [reactions] data Future updateReactions(List reactions); @@ -167,7 +167,7 @@ abstract class ChatPersistenceClient { Future deleteReactionsByMessageId(List messageIds); /// Deletes all the members by channel [cids] - Future deleteMembersByCids(List cids); + Future deleteMembersByCids(List cids); /// Update the channel state data using [channelState] Future updateChannelState(ChannelState channelState) => @@ -176,12 +176,12 @@ abstract class ChatPersistenceClient { /// Update list of channel states Future updateChannelStates(List channelStates) async { final deleteReactions = deleteReactionsByMessageId(channelStates - .expand((it) => it.messages) + .expand((it) => it.messages!) .map((m) => m.id) .toList(growable: false)); final deleteMembers = deleteMembersByCids( - channelStates.map((it) => it.channel.cid).toList(growable: false), + channelStates.map((it) => it.channel!.cid).toList(growable: false), ); await Future.wait([ @@ -193,54 +193,54 @@ abstract class ChatPersistenceClient { channelStates.map((it) => it.channel).where((it) => it != null); final reactions = channelStates - .expand((it) => it.messages) + .expand((it) => it.messages!) .expand((it) => [ if (it.ownReactions != null) - ...it.ownReactions.where((r) => r.userId != null), + ...it.ownReactions!.where((r) => r.userId != null), if (it.latestReactions != null) - ...it.latestReactions.where((r) => r.userId != null) + ...it.latestReactions!.where((r) => r.userId != null) ]) .where((it) => it != null); final users = channelStates .map((cs) => [ cs.channel?.createdBy, - ...cs.messages + ...?cs.messages ?.map((m) => [ m.user, if (m.latestReactions != null) - ...m.latestReactions.map((r) => r.user), + ...m.latestReactions!.map((r) => r.user), if (m.ownReactions != null) - ...m.ownReactions.map((r) => r.user), + ...m.ownReactions!.map((r) => r.user), ]) ?.expand((v) => v), - if (cs.read != null) ...cs.read.map((r) => r.user), - if (cs.members != null) ...cs.members.map((m) => m.user), + if (cs.read != null) ...cs.read!.map((r) => r.user), + if (cs.members != null) ...cs.members!.map((m) => m!.user), ]) .expand((it) => it) .where((it) => it != null); final updateMessagesFuture = channelStates.map((it) { - final cid = it.channel.cid; - final messages = it.messages.where((it) => it != null); + final cid = it.channel!.cid; + final messages = it.messages!.where((it) => it != null); return updateMessages(cid, messages.toList(growable: false)); }).toList(growable: false); final updatePinnedMessagesFuture = channelStates.map((it) { - final cid = it.channel.cid; - final messages = it.pinnedMessages.where((it) => it != null); + final cid = it.channel!.cid; + final messages = it.pinnedMessages!.where((it) => it != null); return updatePinnedMessages(cid, messages.toList(growable: false)); }).toList(growable: false); final updateReadsFuture = channelStates.map((it) { - final cid = it.channel.cid; + final cid = it.channel!.cid; final reads = it.read?.where((it) => it != null) ?? []; return updateReads(cid, reads.toList(growable: false)); }).toList(growable: false); final updateMembersFuture = channelStates.map((it) { - final cid = it.channel.cid; - final members = it.members.where((it) => it != null); + final cid = it.channel!.cid; + final members = it.members!.where((it) => it != null); return updateMembers(cid, members.toList(growable: false)); }).toList(growable: false); diff --git a/packages/stream_chat/lib/src/exceptions.dart b/packages/stream_chat/lib/src/exceptions.dart index 60dd6bdf..714bb386 100644 --- a/packages/stream_chat/lib/src/exceptions.dart +++ b/packages/stream_chat/lib/src/exceptions.dart @@ -4,25 +4,25 @@ import 'dart:convert'; class ApiError extends Error { /// Creates a new ApiError instance using the response body and status code ApiError(this.body, this.status) : jsonData = _decode(body) { - if (jsonData != null && jsonData.containsKey('code')) { - _code = jsonData['code']; + if (jsonData != null && jsonData!.containsKey('code')) { + _code = jsonData!['code']; } } /// Raw body of the response - final String body; + final String? body; /// Json parsed body - final Map jsonData; + final Map? jsonData; /// Http status code of the response - final int status; + final int? status; /// Stream specific error code - int get code => _code; - int _code; + int? get code => _code; + int? _code; - static Map _decode(String body) { + static Map? _decode(String? body) { try { if (body == null) { return null; diff --git a/packages/stream_chat/lib/src/extensions/map_extension.dart b/packages/stream_chat/lib/src/extensions/map_extension.dart index 1a3376ad..af57a35a 100644 --- a/packages/stream_chat/lib/src/extensions/map_extension.dart +++ b/packages/stream_chat/lib/src/extensions/map_extension.dart @@ -1,6 +1,6 @@ /// Useful extension functions for [Map] extension MapX on Map { /// Returns a new map with null keys or values removed - Map get nullProtected => - {...this}..removeWhere((key, value) => key == null || value == null); + Map get nullProtected => {...this as Map} + ..removeWhere((key, value) => key == null || value == null); } diff --git a/packages/stream_chat/lib/src/extensions/rate_limit.dart b/packages/stream_chat/lib/src/extensions/rate_limit.dart index c9f5934f..8ed91481 100644 --- a/packages/stream_chat/lib/src/extensions/rate_limit.dart +++ b/packages/stream_chat/lib/src/extensions/rate_limit.dart @@ -10,7 +10,7 @@ extension RateLimit on Function { Duration wait, { bool leading = false, bool trailing = true, - Duration maxWait, + Duration? maxWait, }) => Debounce( this, @@ -40,7 +40,7 @@ Debounce debounce( Duration wait, { bool leading = false, bool trailing = true, - Duration maxWait, + Duration? maxWait, }) => Debounce( func, @@ -121,13 +121,13 @@ class Debounce { Duration wait, { bool leading = false, bool trailing = true, - Duration maxWait, + Duration? maxWait, }) : _leading = leading, _trailing = trailing, _wait = wait?.inMilliseconds ?? 0, _maxing = maxWait != null { if (_maxing) { - _maxWait = math.max(maxWait.inMilliseconds, _wait); + _maxWait = math.max(maxWait!.inMilliseconds, _wait); } } @@ -137,15 +137,15 @@ class Debounce { final int _wait; final bool _maxing; - int _maxWait; - List _lastArgs; - Map _lastNamedArgs; - Timer _timer; - int _lastCallTime; - Object _result; - int _lastInvokeTime = 0; + late int _maxWait; + List? _lastArgs; + Map? _lastNamedArgs; + Timer? _timer; + int? _lastCallTime; + Object? _result; + int? _lastInvokeTime = 0; - Object _invokeFunc(int time) { + Object? _invokeFunc(int? time) { final args = _lastArgs; final namedArgs = _lastNamedArgs; _lastArgs = _lastNamedArgs = null; @@ -154,11 +154,11 @@ class Debounce { } Timer _startTimer(Function pendingFunc, int wait) => - Timer(Duration(milliseconds: wait), pendingFunc); + Timer(Duration(milliseconds: wait), pendingFunc as void Function()); bool _shouldInvoke(int time) { final timeSinceLastCall = time - (_lastCallTime ?? double.nan); - final timeSinceLastInvoke = time - _lastInvokeTime; + final timeSinceLastInvoke = time - _lastInvokeTime!; // Either this is the first call, activity has stopped and we're at the // trailing edge, the system time has gone backwards and we're treating @@ -169,7 +169,7 @@ class Debounce { (_maxing && timeSinceLastInvoke >= _maxWait); } - Object _trailingEdge(int time) { + Object? _trailingEdge(int time) { _timer = null; // Only invoke if we have `lastArgs` which means `func` has been @@ -182,8 +182,8 @@ class Debounce { } int _remainingWait(int time) { - final timeSinceLastCall = time - _lastCallTime; - final timeSinceLastInvoke = time - _lastInvokeTime; + final timeSinceLastCall = time - _lastCallTime!; + final timeSinceLastInvoke = time - _lastInvokeTime!; final timeWaiting = _wait - timeSinceLastCall; return _maxing @@ -201,7 +201,7 @@ class Debounce { } } - Object _leadingEdge(int time) { + Object? _leadingEdge(int? time) { // Reset any `maxWait` timer. _lastInvokeTime = time; // Start the timer for the trailing edge. @@ -218,7 +218,7 @@ class Debounce { } /// Immediately invokes all the remaining delayed functions. - Object flush() { + Object? flush() { final now = DateTime.now().millisecondsSinceEpoch; return _timer == null ? _result : _trailingEdge(now); } @@ -228,15 +228,15 @@ class Debounce { /// Calls/invokes this class like a function. /// Pass [args] and [namedArgs] to be used while invoking [_func]. - Object call( + Object? call( List args, { - Map namedArgs, + Map? namedArgs, }) { final time = DateTime.now().millisecondsSinceEpoch; final isInvoking = _shouldInvoke(time); _lastArgs = args; - _lastNamedArgs = namedArgs; + _lastNamedArgs = namedArgs as Map?; _lastCallTime = time; if (isInvoking) { @@ -323,13 +323,13 @@ class Throttle { void cancel() => _debounce.cancel(); /// Immediately invokes all the remaining delayed functions. - Object flush() => _debounce.flush(); + Object? flush() => _debounce.flush(); /// True if there are functions remaining to get invoked. bool get isPending => _debounce.isPending; /// Calls/invokes this class like a function. /// Pass [args] and [namedArgs] to be used while invoking `func`. - Object call(List args, {Map namedArgs}) => + Object? call(List args, {Map? namedArgs}) => _debounce.call(args, namedArgs: namedArgs); } diff --git a/packages/stream_chat/lib/src/extensions/string_extension.dart b/packages/stream_chat/lib/src/extensions/string_extension.dart index 49949412..e87eea7e 100644 --- a/packages/stream_chat/lib/src/extensions/string_extension.dart +++ b/packages/stream_chat/lib/src/extensions/string_extension.dart @@ -2,14 +2,14 @@ import 'package:http_parser/http_parser.dart' as http_parser; import 'package:mime/mime.dart'; /// Useful extension functions for [String] -extension StringX on String { +extension StringX on String? { /// Returns the mime type from the passed file name. - http_parser.MediaType get mimeType { + http_parser.MediaType? get mimeType { if (this == null) return null; - if (toLowerCase().endsWith('heic')) { + if (this!.toLowerCase().endsWith('heic')) { return http_parser.MediaType.parse('image/heic'); } else { - return http_parser.MediaType.parse(lookupMimeType(this)); + return http_parser.MediaType.parse(lookupMimeType(this!)!); } } } diff --git a/packages/stream_chat/lib/src/models/action.dart b/packages/stream_chat/lib/src/models/action.dart index 62d0f104..b91f1e13 100644 --- a/packages/stream_chat/lib/src/models/action.dart +++ b/packages/stream_chat/lib/src/models/action.dart @@ -12,19 +12,19 @@ class Action { factory Action.fromJson(Map json) => _$ActionFromJson(json); /// The name of the action - final String name; + final String? name; /// The style of the action - final String style; + final String? style; /// The test of the action - final String text; + final String? text; /// The type of the action - final String type; + final String? type; /// The value of the action - final String value; + final String? value; /// Serialize to json Map toJson() => _$ActionToJson(this); diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/models/attachment.dart index 551d70e4..ae169766 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/models/attachment.dart @@ -13,10 +13,10 @@ part 'attachment.g.dart'; class Attachment { /// Constructor used for json serialization Attachment({ - String id, + String? id, this.type, this.titleLink, - String title, + String? title, this.thumbUrl, this.text, this.pretext, @@ -34,10 +34,10 @@ class Attachment { this.actions, this.extraData, this.file, - UploadState uploadState, + UploadState? uploadState, }) : id = id ?? const Uuid().v4(), title = title ?? file?.name, - localUri = file?.path != null ? Uri.parse(file.path) : null { + localUri = file?.path != null ? Uri.parse(file!.path!) : null { this.uploadState = uploadState ?? ((assetUrl != null || imageUrl != null) ? const UploadState.success() @@ -47,68 +47,68 @@ class Attachment { /// Create a new instance from a json factory Attachment.fromJson(Map json) => _$AttachmentFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)); + Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); /// Create a new instance from a db data factory Attachment.fromData(Map json) => _$AttachmentFromJson(Serialization.moveToExtraDataFromRoot( - json, topLevelFields + dbSpecificTopLevelFields)); + json, topLevelFields + dbSpecificTopLevelFields)!); ///The attachment type based on the URL resource. This can be: audio, ///image or video - final String type; + final String? type; ///The link to which the attachment message points to. - final String titleLink; + final String? titleLink; /// The attachment title - final String title; + final String? title; /// The URL to the attached file thumbnail. You can use this to represent the /// attached link. - final String thumbUrl; + final String? thumbUrl; /// The attachment text. It will be displayed in the channel next to the /// original message. - final String text; + final String? text; /// Optional text that appears above the attachment block - final String pretext; + final String? pretext; /// The original URL that was used to scrape this attachment. - final String ogScrapeUrl; + final String? ogScrapeUrl; /// The URL to the attached image. This is present for URL pointing to an /// image article (eg. Unsplash) - final String imageUrl; - final String footerIcon; - final String footer; + final String? imageUrl; + final String? footerIcon; + final String? footer; final dynamic fields; - final String fallback; - final String color; + final String? fallback; + final String? color; /// The name of the author. - final String authorName; - final String authorLink; - final String authorIcon; + final String? authorName; + final String? authorLink; + final String? authorIcon; /// The URL to the audio, video or image related to the URL. - final String assetUrl; + final String? assetUrl; /// Actions from a command - final List actions; + final List? actions; - final Uri localUri; + final Uri? localUri; /// The file present inside this attachment. - final AttachmentFile file; + final AttachmentFile? file; /// The current upload state of the attachment - UploadState uploadState; + UploadState? uploadState; /// Map of custom channel extraData @JsonKey(includeIfNull: false) - final Map extraData; + final Map? extraData; /// The attachment ID. /// @@ -156,28 +156,28 @@ class Attachment { _$AttachmentToJson(this), topLevelFields + dbSpecificTopLevelFields); Attachment copyWith({ - String id, - String type, - String titleLink, - String title, - String thumbUrl, - String text, - String pretext, - String ogScrapeUrl, - String imageUrl, - String footerIcon, - String footer, + String? id, + String? type, + String? titleLink, + String? title, + String? thumbUrl, + String? text, + String? pretext, + String? ogScrapeUrl, + String? imageUrl, + String? footerIcon, + String? footer, dynamic fields, - String fallback, - String color, - String authorName, - String authorLink, - String authorIcon, - String assetUrl, - List actions, - AttachmentFile file, - UploadState uploadState, - Map extraData, + String? fallback, + String? color, + String? authorName, + String? authorLink, + String? authorIcon, + String? assetUrl, + List? actions, + AttachmentFile? file, + UploadState? uploadState, + Map? extraData, }) => Attachment( id: id ?? this.id, diff --git a/packages/stream_chat/lib/src/models/attachment_file.dart b/packages/stream_chat/lib/src/models/attachment_file.dart index bae7810f..46208e28 100644 --- a/packages/stream_chat/lib/src/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/models/attachment_file.dart @@ -19,7 +19,7 @@ abstract class UploadState with _$UploadState { const factory UploadState.success() = Success; /// Failed state of the union - const factory UploadState.failed({@required String error}) = Failed; + const factory UploadState.failed({required String error}) = Failed; /// Creates a new instance from a json factory UploadState.fromJson(Map json) => @@ -27,7 +27,7 @@ abstract class UploadState with _$UploadState { } /// Helper extension for UploadState -extension UploadStateX on UploadState { +extension UploadStateX on UploadState? { /// Returns true if state is [Preparing] bool get isPreparing => this is Preparing; @@ -65,21 +65,21 @@ class AttachmentFile { /// ``` /// final File myFile = File(platformFile.path); /// ``` - final String path; + final String? path; /// File name including its extension. - final String name; + final String? name; /// Byte data for this file. Particularly useful if you want to manipulate /// its data or easily upload to somewhere else. @JsonKey(toJson: _toString, fromJson: _fromString) - final Uint8List bytes; + final Uint8List? bytes; /// The file size in bytes. - final int size; + final int? size; /// File extension for this file. - String get extension => name?.split('.')?.last; + String? get extension => name?.split('.')?.last; /// Serialize to json Map toJson() => _$AttachmentFileToJson(this); diff --git a/packages/stream_chat/lib/src/models/channel_config.dart b/packages/stream_chat/lib/src/models/channel_config.dart index 3541573e..1500aaf2 100644 --- a/packages/stream_chat/lib/src/models/channel_config.dart +++ b/packages/stream_chat/lib/src/models/channel_config.dart @@ -30,52 +30,52 @@ class ChannelConfig { _$ChannelConfigFromJson(json); /// Moderation configuration - final String automod; + final String? automod; /// List of available commands - final List commands; + final List? commands; /// True if the channel should send connect events - final bool connectEvents; + final bool? connectEvents; /// Date of channel creation - final DateTime createdAt; + final DateTime? createdAt; /// Date of last channel update - final DateTime updatedAt; + final DateTime? updatedAt; /// Max channel message length - final int maxMessageLength; + final int? maxMessageLength; /// Duration of message retention - final String messageRetention; + final String? messageRetention; /// True if users can be muted - final bool mutes; + final bool? mutes; /// Name of the channel - final String name; + final String? name; /// True if reaction are active for this channel - final bool reactions; + final bool? reactions; /// True if readEvents are active for this channel - final bool readEvents; + final bool? readEvents; /// True if reply message are active for this channel - final bool replies; + final bool? replies; /// True if it's possible to perform a search in this channel - final bool search; + final bool? search; /// True if typing events should be sent for this channel - final bool typingEvents; + final bool? typingEvents; /// True if it's possible to upload files to this channel - final bool uploads; + final bool? uploads; /// True if urls appears as attachments - final bool urlEnrichment; + final bool? urlEnrichment; /// Serialize to json Map toJson() => _$ChannelConfigToJson(this); diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index 62ea1853..827f1246 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -26,59 +26,59 @@ class ChannelModel { }); /// Create a new instance from a json - factory ChannelModel.fromJson(Map json) => + factory ChannelModel.fromJson(Map? json) => _$ChannelModelFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)); + Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); /// The id of this channel - final String id; + final String? id; /// The type of this channel - final String type; + final String? type; /// The cid of this channel @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final String cid; + final String? cid; /// The channel configuration data @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final ChannelConfig config; + final ChannelConfig? config; /// The user that created this channel @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final User createdBy; + final User? createdBy; /// True if this channel is frozen @JsonKey(includeIfNull: false) - final bool frozen; + final bool? frozen; /// The date of the last message @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime lastMessageAt; + final DateTime? lastMessageAt; /// The date of channel creation @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime createdAt; + final DateTime? createdAt; /// The date of the last channel update @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime updatedAt; + final DateTime? updatedAt; /// The date of channel deletion @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime deletedAt; + final DateTime? deletedAt; /// The count of this channel members @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final int memberCount; + final int? memberCount; /// Map of custom channel extraData @JsonKey(includeIfNull: false) - final Map extraData; + final Map? extraData; /// The team the channel belongs to @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final String team; + final String? team; /// Known top level fields. /// Useful for [Serialization] methods. @@ -98,8 +98,8 @@ class ChannelModel { ]; /// Shortcut for channel name - String get name => - extraData?.containsKey('name') == true ? extraData['name'] : cid; + String? get name => + extraData?.containsKey('name') == true ? extraData!['name'] : cid; /// Serialize to json Map toJson() => Serialization.moveFromExtraDataToRoot( @@ -109,19 +109,19 @@ class ChannelModel { /// Creates a copy of [ChannelModel] with specified attributes overridden. ChannelModel copyWith({ - String id, - String type, - String cid, - ChannelConfig config, - User createdBy, - bool frozen, - DateTime lastMessageAt, - DateTime createdAt, - DateTime updatedAt, - DateTime deletedAt, - int memberCount, - Map extraData, - String team, + String? id, + String? type, + String? cid, + ChannelConfig? config, + User? createdBy, + bool? frozen, + DateTime? lastMessageAt, + DateTime? createdAt, + DateTime? updatedAt, + DateTime? deletedAt, + int? memberCount, + Map? extraData, + String? team, }) => ChannelModel( id: id ?? this.id, @@ -141,7 +141,7 @@ class ChannelModel { /// Returns a new [ChannelModel] that is a combination of this channelModel /// and the given [other] channelModel. - ChannelModel merge(ChannelModel other) { + ChannelModel merge(ChannelModel? other) { if (other == null) return this; return copyWith( id: other.id, diff --git a/packages/stream_chat/lib/src/models/channel_state.dart b/packages/stream_chat/lib/src/models/channel_state.dart index 3a1f3178..64c88af7 100644 --- a/packages/stream_chat/lib/src/models/channel_state.dart +++ b/packages/stream_chat/lib/src/models/channel_state.dart @@ -22,42 +22,42 @@ class ChannelState { }); /// The channel to which this state belongs - final ChannelModel channel; + final ChannelModel? channel; /// A paginated list of channel messages - final List messages; + final List? messages; /// A paginated list of channel members - final List members; + final List? members; /// A paginated list of pinned messages - final List pinnedMessages; + final List? pinnedMessages; /// The count of users watching the channel - final int watcherCount; + final int? watcherCount; /// A paginated list of users watching the channel - final List watchers; + final List? watchers; /// The list of channel reads - final List read; + final List? read; /// Create a new instance from a json - static ChannelState fromJson(Map json) => - _$ChannelStateFromJson(json); + static ChannelState fromJson(Map? json) => + _$ChannelStateFromJson(json!); /// Serialize to json Map toJson() => _$ChannelStateToJson(this); /// Creates a copy of [ChannelState] with specified attributes overridden. ChannelState copyWith({ - ChannelModel channel, - List messages, - List members, - List pinnedMessages, - int watcherCount, - List watchers, - List read, + ChannelModel? channel, + List? messages, + List? members, + List? pinnedMessages, + int? watcherCount, + List? watchers, + List? read, }) => ChannelState( channel: channel ?? this.channel, diff --git a/packages/stream_chat/lib/src/models/command.dart b/packages/stream_chat/lib/src/models/command.dart index a5ababd2..c8df72fd 100644 --- a/packages/stream_chat/lib/src/models/command.dart +++ b/packages/stream_chat/lib/src/models/command.dart @@ -17,13 +17,13 @@ class Command { _$CommandFromJson(json); /// The name of the command - final String name; + final String? name; /// The description explaining the command - final String description; + final String? description; /// The arguments of the command - final String args; + final String? args; /// Serialize to json Map toJson() => _$CommandToJson(this); diff --git a/packages/stream_chat/lib/src/models/device.dart b/packages/stream_chat/lib/src/models/device.dart index 150e6759..28481282 100644 --- a/packages/stream_chat/lib/src/models/device.dart +++ b/packages/stream_chat/lib/src/models/device.dart @@ -15,10 +15,10 @@ class Device { factory Device.fromJson(Map json) => _$DeviceFromJson(json); /// The id of the device - final String id; + final String? id; /// The notification push provider - final String pushProvider; + final String? pushProvider; /// Serialize to json Map toJson() => _$DeviceToJson(this); diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index 28cf1b38..ab97d86f 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -31,68 +31,68 @@ class Event { }) : isLocal = true; /// Create a new instance from a json - factory Event.fromJson(Map json) => + factory Event.fromJson(Map? json) => _$EventFromJson(Serialization.moveToExtraDataFromRoot( json, topLevelFields, - )) + )!) ..isLocal = false; /// The type of the event /// [EventType] contains some predefined constant types - final String type; + final String? type; /// The channel cid to which the event belongs - final String cid; + final String? cid; /// The channel id to which the event belongs - final String channelId; + final String? channelId; /// The channel type to which the event belongs - final String channelType; + final String? channelType; /// The connection id in which the event has been sent - final String connectionId; + final String? connectionId; /// The date of creation of the event - final DateTime createdAt; + final DateTime? createdAt; /// User object of the health check user - final OwnUser me; + final OwnUser? me; /// User object of the current user - final User user; + final User? user; /// The message sent with the event - final Message message; + final Message? message; /// The channel sent with the event - final EventChannel channel; + final EventChannel? channel; /// The member sent with the event - final Member member; + final Member? member; /// The reaction sent with the event - final Reaction reaction; + final Reaction? reaction; /// The number of unread messages for current user - final int totalUnreadCount; + final int? totalUnreadCount; /// User total unread channels - final int unreadChannels; + final int? unreadChannels; /// Online status - final bool online; + final bool? online; /// The id of the parent message of a thread - final String parentId; + final String? parentId; /// True if the event is generated by this client - bool isLocal; + bool? isLocal; /// Map of custom channel extraData @JsonKey(includeIfNull: false) - final Map extraData; + final Map? extraData; /// Known top level fields. /// Useful for [Serialization] methods. @@ -124,23 +124,23 @@ class Event { /// Creates a copy of [Event] with specified attributes overridden. Event copyWith({ - String type, - String cid, - String channelId, - String channelType, - String connectionId, - DateTime createdAt, - OwnUser me, - User user, - Message message, - EventChannel channel, - Member member, - Reaction reaction, - int totalUnreadCount, - int unreadChannels, - bool online, - String parentId, - Map extraData, + String? type, + String? cid, + String? channelId, + String? channelType, + String? connectionId, + DateTime? createdAt, + OwnUser? me, + User? user, + Message? message, + EventChannel? channel, + Member? member, + Reaction? reaction, + int? totalUnreadCount, + int? unreadChannels, + bool? online, + String? parentId, + Map? extraData, }) => Event( type: type ?? this.type, @@ -169,18 +169,18 @@ class EventChannel extends ChannelModel { /// Constructor used for json serialization EventChannel({ this.members, - String id, - String type, - String cid, - ChannelConfig config, - User createdBy, - bool frozen, - DateTime lastMessageAt, - DateTime createdAt, - DateTime updatedAt, - DateTime deletedAt, - int memberCount, - Map extraData, + String? id, + String? type, + String? cid, + ChannelConfig? config, + User? createdBy, + bool? frozen, + DateTime? lastMessageAt, + DateTime? createdAt, + DateTime? updatedAt, + DateTime? deletedAt, + int? memberCount, + Map? extraData, }) : super( id: id, type: type, @@ -197,14 +197,14 @@ class EventChannel extends ChannelModel { ); /// Create a new instance from a json - factory EventChannel.fromJson(Map json) => + factory EventChannel.fromJson(Map? json) => _$EventChannelFromJson(Serialization.moveToExtraDataFromRoot( json, topLevelFields, - )); + )!); /// A paginated list of channel members - final List members; + final List? members; /// Known top level fields. /// Useful for [Serialization] methods. diff --git a/packages/stream_chat/lib/src/models/member.dart b/packages/stream_chat/lib/src/models/member.dart index e99d8d34..bd2bed8a 100644 --- a/packages/stream_chat/lib/src/models/member.dart +++ b/packages/stream_chat/lib/src/models/member.dart @@ -31,51 +31,51 @@ class Member { } /// The interested user - final User user; + final User? user; /// The date in which the user accepted the invite to the channel - final DateTime inviteAcceptedAt; + final DateTime? inviteAcceptedAt; /// The date in which the user rejected the invite to the channel - final DateTime inviteRejectedAt; + final DateTime? inviteRejectedAt; /// True if the user has been invited to the channel - final bool invited; + final bool? invited; /// The role of the user in the channel - final String role; + final String? role; /// The id of the interested user - final String userId; + final String? userId; /// True if the user is a moderator of the channel - final bool isModerator; + final bool? isModerator; /// True if the member is banned from the channel - final bool banned; + final bool? banned; /// True if the member is shadow banned from the channel - final bool shadowBanned; + final bool? shadowBanned; /// The date of creation - final DateTime createdAt; + final DateTime? createdAt; /// The last date of update - final DateTime updatedAt; + final DateTime? updatedAt; /// Creates a copy of [Member] with specified attributes overridden. Member copyWith({ - User user, - DateTime inviteAcceptedAt, - DateTime inviteRejectedAt, - bool invited, - String role, - String userId, - bool isModerator, - DateTime createdAt, - DateTime updatedAt, - bool banned, - bool shadowBanned, + User? user, + DateTime? inviteAcceptedAt, + DateTime? inviteRejectedAt, + bool? invited, + String? role, + String? userId, + bool? isModerator, + DateTime? createdAt, + DateTime? updatedAt, + bool? banned, + bool? shadowBanned, }) => Member( user: user ?? this.user, diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index de441dff..0df5a7ca 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -44,7 +44,7 @@ enum MessageSendingStatus { class Message { /// Constructor used for json serialization Message({ - String id, + String? id, this.text, this.type, this.attachments, @@ -67,7 +67,7 @@ class Message { this.user, this.pinned = false, this.pinnedAt, - DateTime pinExpires, + DateTime? pinExpires, this.pinnedBy, this.extraData, this.deletedAt, @@ -77,15 +77,15 @@ class Message { pinExpires = pinExpires?.toUtc(); /// Create a new instance from a json - factory Message.fromJson(Map json) => _$MessageFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)); + factory Message.fromJson(Map? json) => _$MessageFromJson( + Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); /// The message ID. This is either created by Stream or set client side when /// the message is added. final String id; /// The text of this message - final String text; + final String? text; /// The status of a sending message @JsonKey(ignore: true) @@ -93,99 +93,99 @@ class Message { /// The message type @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final String type; + final String? type; /// The list of attachments, either provided by the user or generated from a /// command or as a result of URL scraping. @JsonKey(includeIfNull: false) - final List attachments; + final List? attachments; /// The list of user mentioned in the message @JsonKey(toJson: Serialization.userIds) - final List mentionedUsers; + final List? mentionedUsers; /// A map describing the count of number of every reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final Map reactionCounts; + final Map? reactionCounts; /// A map describing the count of score of every reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final Map reactionScores; + final Map? reactionScores; /// The latest reactions to the message created by any user. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final List latestReactions; + final List? latestReactions; /// The reactions added to the message by the current user. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final List ownReactions; + final List? ownReactions; /// The ID of the parent message, if the message is a thread reply. - final String parentId; + final String? parentId; /// A quoted reply message @JsonKey(toJson: Serialization.readOnly) - final Message quotedMessage; + final Message? quotedMessage; /// The ID of the quoted message, if the message is a quoted reply. - final String quotedMessageId; + final String? quotedMessageId; /// Reserved field indicating the number of replies for this message. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final int replyCount; + final int? replyCount; /// Reserved field indicating the thread participants for this message. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final List threadParticipants; + final List? threadParticipants; /// Check if this message needs to show in the channel. - final bool showInChannel; + final bool? showInChannel; /// If true the message is silent - final bool silent; + final bool? silent; /// If true the message will not send a push notification - final bool skipPush; + final bool? skipPush; /// If true the message is shadowed @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final bool shadowed; + final bool? shadowed; /// A used command name. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final String command; + final String? command; /// Reserved field indicating when the message was created. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime createdAt; + final DateTime? createdAt; /// Reserved field indicating when the message was updated last time. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime updatedAt; + final DateTime? updatedAt; /// User who sent the message @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final User user; + final User? user; /// If true the message is pinned - final bool pinned; + final bool? pinned; /// Reserved field indicating when the message was pinned @JsonKey(toJson: Serialization.readOnly) - final DateTime pinnedAt; + final DateTime? pinnedAt; /// Reserved field indicating when the message will expire /// /// if `null` message has no expiry - final DateTime pinExpires; + final DateTime? pinExpires; /// Reserved field indicating who pinned the message @JsonKey(toJson: Serialization.readOnly) - final User pinnedBy; + final User? pinnedBy; /// Message custom extraData @JsonKey(includeIfNull: false) - final Map extraData; + final Map? extraData; /// True if the message is a system info bool get isSystem => type == 'system'; @@ -198,7 +198,7 @@ class Message { /// Reserved field indicating when the message was deleted. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime deletedAt; + final DateTime? deletedAt; /// Known top level fields. /// Useful for [Serialization] methods. @@ -239,35 +239,35 @@ class Message { /// Creates a copy of [Message] with specified attributes overridden. Message copyWith({ - String id, - String text, - String type, - List attachments, - List mentionedUsers, - Map reactionCounts, - Map reactionScores, - List latestReactions, - List ownReactions, - String parentId, - Message quotedMessage, - String quotedMessageId, - int replyCount, - List threadParticipants, - bool showInChannel, - bool shadowed, - bool silent, - String command, - DateTime createdAt, - DateTime updatedAt, - DateTime deletedAt, - User user, - bool pinned, - DateTime pinnedAt, - Object pinExpires = _pinExpires, - User pinnedBy, - Map extraData, - MessageSendingStatus status, - bool skipPush, + String? id, + String? text, + String? type, + List? attachments, + List? mentionedUsers, + Map? reactionCounts, + Map? reactionScores, + List? latestReactions, + List? ownReactions, + String? parentId, + Message? quotedMessage, + String? quotedMessageId, + int? replyCount, + List? threadParticipants, + bool? showInChannel, + bool? shadowed, + bool? silent, + String? command, + DateTime? createdAt, + DateTime? updatedAt, + DateTime? deletedAt, + User? user, + bool? pinned, + DateTime? pinnedAt, + Object? pinExpires = _pinExpires, + User? pinnedBy, + Map? extraData, + MessageSendingStatus? status, + bool? skipPush, }) { assert(() { if (pinExpires is! DateTime && @@ -305,7 +305,8 @@ class Message { pinned: pinned ?? this.pinned, pinnedAt: pinnedAt ?? this.pinnedAt, pinnedBy: pinnedBy ?? this.pinnedBy, - pinExpires: pinExpires == _pinExpires ? this.pinExpires : pinExpires, + pinExpires: + pinExpires == _pinExpires ? this.pinExpires : pinExpires as DateTime?, skipPush: skipPush ?? this.skipPush, ); } @@ -355,13 +356,13 @@ class TranslatedMessage extends Message { TranslatedMessage(this.i18n); /// Create a new instance from a json - factory TranslatedMessage.fromJson(Map json) => + factory TranslatedMessage.fromJson(Map? json) => _$TranslatedMessageFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields), + Serialization.moveToExtraDataFromRoot(json, topLevelFields)!, ); /// A Map of - final Map i18n; + final Map? i18n; /// Known top level fields. /// Useful for [Serialization] methods. diff --git a/packages/stream_chat/lib/src/models/mute.dart b/packages/stream_chat/lib/src/models/mute.dart index e3d5e1a0..65300d4a 100644 --- a/packages/stream_chat/lib/src/models/mute.dart +++ b/packages/stream_chat/lib/src/models/mute.dart @@ -16,19 +16,19 @@ class Mute { /// The user that performed the muting action @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final User user; + final User? user; /// The target user @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final ChannelModel channel; + final ChannelModel? channel; /// The date in which the use was muted @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime createdAt; + final DateTime? createdAt; /// The date of the last update @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime updatedAt; + final DateTime? updatedAt; /// Serialize to json Map toJson() => _$MuteToJson(this); diff --git a/packages/stream_chat/lib/src/models/own_user.dart b/packages/stream_chat/lib/src/models/own_user.dart index 18ee2abf..041c4e95 100644 --- a/packages/stream_chat/lib/src/models/own_user.dart +++ b/packages/stream_chat/lib/src/models/own_user.dart @@ -17,14 +17,14 @@ class OwnUser extends User { this.totalUnreadCount, this.unreadChannels, this.channelMutes, - String id, - String role, - DateTime createdAt, - DateTime updatedAt, - DateTime lastActive, - bool online, - Map extraData, - bool banned, + String? id, + String? role, + DateTime? createdAt, + DateTime? updatedAt, + DateTime? lastActive, + bool? online, + Map? extraData, + bool? banned, }) : super( id: id, role: role, @@ -37,28 +37,28 @@ class OwnUser extends User { ); /// Create a new instance from a json - factory OwnUser.fromJson(Map json) => _$OwnUserFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)); + factory OwnUser.fromJson(Map? json) => _$OwnUserFromJson( + Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); /// List of user devices @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final List devices; + final List? devices; /// List of users muted by the user @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final List mutes; + final List? mutes; /// List of users muted by the user @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final List channelMutes; + final List? channelMutes; /// Total unread messages by the user @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final int totalUnreadCount; + final int? totalUnreadCount; /// Total unread channels by the user @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final int unreadChannels; + final int? unreadChannels; /// Known top level fields. /// Useful for [Serialization] methods. diff --git a/packages/stream_chat/lib/src/models/reaction.dart b/packages/stream_chat/lib/src/models/reaction.dart index 6792bf3f..42c23841 100644 --- a/packages/stream_chat/lib/src/models/reaction.dart +++ b/packages/stream_chat/lib/src/models/reaction.dart @@ -13,39 +13,39 @@ class Reaction { this.createdAt, this.type, this.user, - String userId, + String? userId, this.score, this.extraData, }) : userId = userId ?? user?.id; /// Create a new instance from a json - factory Reaction.fromJson(Map json) => _$ReactionFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)); + factory Reaction.fromJson(Map? json) => _$ReactionFromJson( + Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); /// The messageId to which the reaction belongs - final String messageId; + final String? messageId; /// The type of the reaction - final String type; + final String? type; /// The date of the reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime createdAt; + final DateTime? createdAt; /// The user that sent the reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final User user; + final User? user; /// The score of the reaction (ie. number of reactions sent) - final int score; + final int? score; /// The userId that sent the reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final String userId; + final String? userId; /// Reaction custom extraData @JsonKey(includeIfNull: false) - final Map extraData; + final Map? extraData; /// Map of custom user extraData static const topLevelFields = [ @@ -63,13 +63,13 @@ class Reaction { /// Creates a copy of [Reaction] with specified attributes overridden. Reaction copyWith({ - String messageId, - DateTime createdAt, - String type, - User user, - String userId, - int score, - Map extraData, + String? messageId, + DateTime? createdAt, + String? type, + User? user, + String? userId, + int? score, + Map? extraData, }) => Reaction( messageId: messageId ?? this.messageId, diff --git a/packages/stream_chat/lib/src/models/read.dart b/packages/stream_chat/lib/src/models/read.dart index fc8ef1bc..87b3dc89 100644 --- a/packages/stream_chat/lib/src/models/read.dart +++ b/packages/stream_chat/lib/src/models/read.dart @@ -17,22 +17,22 @@ class Read { factory Read.fromJson(Map json) => _$ReadFromJson(json); /// Date of the read event - final DateTime lastRead; + final DateTime? lastRead; /// User who sent the event - final User user; + final User? user; /// Number of unread messages - final int unreadMessages; + final int? unreadMessages; /// Serialize to json Map toJson() => _$ReadToJson(this); /// Creates a copy of [Read] with specified attributes overridden. Read copyWith({ - DateTime lastRead, - User user, - int unreadMessages, + DateTime? lastRead, + User? user, + int? unreadMessages, }) => Read( lastRead: lastRead ?? this.lastRead, diff --git a/packages/stream_chat/lib/src/models/serialization.dart b/packages/stream_chat/lib/src/models/serialization.dart index 18cdd545..48726d5e 100644 --- a/packages/stream_chat/lib/src/models/serialization.dart +++ b/packages/stream_chat/lib/src/models/serialization.dart @@ -10,12 +10,12 @@ class Serialization { static const Function readOnly = readonly; /// List of users to list of userIds - static List userIds(List users) => + static List? userIds(List? users) => users?.map((u) => u.id)?.toList(); /// Takes unknown json keys and puts them in the `extra_data` key - static Map moveToExtraDataFromRoot( - Map json, + static Map? moveToExtraDataFromRoot( + Map? json, List topLevelFields, ) { if (json == null) return null; diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/models/user.dart index 3b9f9add..97411a3e 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/models/user.dart @@ -20,8 +20,8 @@ class User { }); /// Create a new instance from a json - factory User.fromJson(Map json) => _$UserFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)); + factory User.fromJson(Map? json) => _$UserFromJson( + Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); /// Use this named constructor to create a new user instance User.init( @@ -49,47 +49,47 @@ class User { ]; /// User id - final String id; + final String? id; /// User role @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final String role; + final String? role; /// User role @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final List teams; + final List? teams; /// Date of user creation @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime createdAt; + final DateTime? createdAt; /// Date of last user update @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime updatedAt; + final DateTime? updatedAt; /// Date of last user connection @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime lastActive; + final DateTime? lastActive; /// True if user is online @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final bool online; + final bool? online; /// True if user is banned from the chat @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final bool banned; + final bool? banned; /// Map of custom user extraData @JsonKey(includeIfNull: false) - final Map extraData; + final Map? extraData; @override int get hashCode => id.hashCode; /// Shortcut for user name - String get name => - (extraData?.containsKey('name') == true && extraData['name'] != '') - ? extraData['name'] + String? get name => + (extraData?.containsKey('name') == true && extraData!['name'] != '') + ? extraData!['name'] : id; @override @@ -103,15 +103,15 @@ class User { /// Creates a copy of [User] with specified attributes overridden. User copyWith({ - String id, - String role, - DateTime createdAt, - DateTime updatedAt, - DateTime lastActive, - bool online, - Map extraData, - bool banned, - List teams, + String? id, + String? role, + DateTime? createdAt, + DateTime? updatedAt, + DateTime? lastActive, + bool? online, + Map? extraData, + bool? banned, + List? teams, }) => User( id: id ?? this.id, diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 74310a86..defc8db5 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -6,25 +6,25 @@ repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: async: ^2.5.0 collection: ^1.15.0 - dio: ">=4.0.0-prev3 <4.0.0" - freezed_annotation: ^0.14.0 + dio: ^4.0.0 + freezed_annotation: ^0.14.1 http_parser: ^4.0.0 - json_annotation: ^4.0.0 - logging: ^1.0.0 + json_annotation: ^4.0.1 + logging: ^1.0.1 meta: ^1.3.0 mime: ^1.0.0 rxdart: ^0.26.0 - uuid: ^3.0.0 + uuid: ^3.0.4 web_socket_channel: ^2.0.0 dev_dependencies: build_runner: ^1.10.0 - freezed: ^0.14.0 - json_serializable: ^4.0.0 - mocktail: ^0.1.0 - test: ^1.16.0 + freezed: ^0.14.1+2 + json_serializable: ^4.1.0 + mocktail: ^0.1.1 + test: ^1.16.8 diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index ae4d5296..058a8044 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -50,7 +50,7 @@ void main() { ), ); - await channelClient.sendMessage(message); + await channelClient?.sendMessage(message); verify(() => mockDio.post('/channels/messaging/testid/message', data: { @@ -80,7 +80,7 @@ void main() { statusCode: 200, requestOptions: FakeRequestOptions(), )); - await channelClient.watch(); + await channelClient?.watch(); when( () => mockDio.post( @@ -95,7 +95,7 @@ void main() { ), ); - await channelClient.markRead(); + await channelClient?.markRead(); verify(() => mockDio.post('/channels/messaging/testid/read', data: {})).called(1); @@ -124,7 +124,7 @@ void main() { ), ); - await channelClient.getReplies('messageid', pagination); + await channelClient?.getReplies('messageid', pagination); verify(() => mockDio.get('/messages/messageid/replies', queryParameters: pagination.toJson())).called(1); @@ -141,7 +141,7 @@ void main() { httpClient: mockDio, tokenProvider: (_) async => '', ); - final channelClient = client.channel('messaging', id: 'testid'); + Channel channelClient = client.channel('messaging', id: 'testid'); when(() => mockDio.post( any(), @@ -564,7 +564,11 @@ void main() { 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', - )..state.user = OwnUser(id: 'test-id'); + ); + + if (client != null) { + client.state?.user = OwnUser(id: 'test-id'); + } final channelClient = client.channel('messaging', id: 'testid'); const reactionType = 'test'; @@ -617,7 +621,11 @@ void main() { 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', - )..state.user = OwnUser(id: 'test-id'); + ); + + if (client != null) { + client.state?.user = OwnUser(id: 'test-id'); + } final channelClient = client.channel('messaging', id: 'testid'); @@ -1069,8 +1077,8 @@ void main() { verify(() => mockDio.post('/channels/messaging/query', data: options)).called(1); - expect(channelClient.id, response.channel.id); - expect(channelClient.cid, response.channel.cid); + expect(channelClient.id, response?.channel?.id); + expect(channelClient.cid, response?.channel?.cid); }); test('with id', () async { @@ -1706,8 +1714,8 @@ void main() { verify(() => mockDio.post('/channels/messaging/query', data: options)).called(1); - expect(channelClient.id, response.channel.id); - expect(channelClient.cid, response.channel.cid); + expect(channelClient.id, response?.channel?.id); + expect(channelClient.cid, response?.channel?.cid); }); test('watch', () async { @@ -2027,8 +2035,8 @@ void main() { verify(() => mockDio.post('/channels/messaging/query', data: options)).called(1); - expect(channelClient.id, response.channel.id); - expect(channelClient.cid, response.channel.cid); + expect(channelClient.id, response.channel?.id); + expect(channelClient.cid, response.channel?.cid); }); test('stopWatching', () async { diff --git a/packages/stream_chat/test/src/api/websocket_test.dart b/packages/stream_chat/test/src/api/websocket_test.dart index 5993f0bf..0f3bd5d6 100644 --- a/packages/stream_chat/test/src/api/websocket_test.dart +++ b/packages/stream_chat/test/src/api/websocket_test.dart @@ -12,12 +12,10 @@ import 'package:web_socket_channel/web_socket_channel.dart'; class Functions { WebSocketChannel connectFunc( - String url, { - Iterable protocols, - Map headers, - Duration pingInterval, + String? url, { + Iterable? protocols, }) => - null; + WebSocketChannel.connect(Uri()); void handleFunc(Event event) {} } @@ -94,7 +92,7 @@ void main() { when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - final connect = ws.connect().then((_) { + final connect = ws.connect()?.then((_) { streamController.sink.add('{}'); return Future.delayed(const Duration(milliseconds: 200)); }).then((value) { @@ -130,7 +128,7 @@ void main() { when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - final connect = ws.connect().then((_) { + final connect = ws.connect()?.then((_) { streamController.sink.add('{}'); return Future.delayed(const Duration(milliseconds: 200)); }).then((value) { @@ -208,7 +206,7 @@ void main() { (_) => streamController.sink.add('{}'), ); - final connect = ws.connect().then((_) { + final connect = ws.connect()?.then((_) { streamController.sink.add('{}'); return Future.delayed(const Duration(milliseconds: 200)); }).then((value) async { @@ -249,7 +247,7 @@ void main() { when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); when(() => mockWSChannel.sink).thenReturn(mockWSSink); - final connect = ws.connect().then((_) { + final connect = ws.connect()?.then((_) { streamController.sink.add('{}'); streamController.close(); streamController = StreamController.broadcast(); @@ -292,7 +290,7 @@ void main() { when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); when(() => mockWSChannel.sink).thenReturn(mockWSSink); - final connect = ws.connect().then((_) { + final connect = ws.connect()?.then((_) { streamController.sink.add('{}'); return Future.delayed(const Duration(milliseconds: 200)); }).then((value) async { diff --git a/packages/stream_chat/test/src/models/attachment_test.dart b/packages/stream_chat/test/src/models/attachment_test.dart index 3124ddd2..899aec73 100644 --- a/packages/stream_chat/test/src/models/attachment_test.dart +++ b/packages/stream_chat/test/src/models/attachment_test.dart @@ -49,7 +49,7 @@ void main() { 'https://media0.giphy.com/media/3o7TKnCdBx5cMg0qti/giphy.gif', ); expect(attachment.actions, hasLength(3)); - expect(attachment.actions[0], isA()); + expect(attachment.actions![0], isA()); }); test('should serialize to json correctly', () { diff --git a/packages/stream_chat/test/src/models/channel_state_test.dart b/packages/stream_chat/test/src/models/channel_state_test.dart index c2d69b2b..9aa2b2d6 100644 --- a/packages/stream_chat/test/src/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/models/channel_state_test.dart @@ -10,7 +10,8 @@ import 'package:stream_chat/stream_chat.dart'; void main() { group('src/models/channel_state', () { - const jsonExample = '''{ + const jsonExample = ''' + { "channel": { "id": "dev", "type": "team", @@ -844,36 +845,36 @@ void main() { test('should parse json correctly', () { final channelState = ChannelState.fromJson(json.decode(jsonExample)); - expect(channelState.channel.cid, 'team:dev'); - expect(channelState.channel.id, 'dev'); - expect(channelState.channel.team, 'test'); - expect(channelState.channel.type, 'team'); - expect(channelState.channel.config, isA()); - expect(channelState.channel.config, isNotNull); - expect(channelState.channel.config.commands, hasLength(1)); - expect(channelState.channel.config.commands[0], isA()); - expect(channelState.channel.lastMessageAt, + expect(channelState.channel?.cid, 'team:dev'); + expect(channelState.channel?.id, 'dev'); + expect(channelState.channel?.team, 'test'); + expect(channelState.channel?.type, 'team'); + expect(channelState.channel?.config, isA()); + expect(channelState.channel?.config, isNotNull); + expect(channelState.channel?.config?.commands, hasLength(1)); + expect(channelState.channel?.config?.commands![0], isA()); + expect(channelState.channel?.lastMessageAt, DateTime.parse('2020-01-30T13:43:41.062362Z')); - expect(channelState.channel.createdAt, + expect(channelState.channel?.createdAt, DateTime.parse('2019-04-03T18:43:33.213373Z')); - expect(channelState.channel.updatedAt, + expect(channelState.channel?.updatedAt, DateTime.parse('2019-04-03T18:43:33.213374Z')); - expect(channelState.channel.createdBy, isA()); - expect(channelState.channel.frozen, true); - expect(channelState.channel.extraData['example'], 1); - expect(channelState.channel.extraData['name'], '#dev'); + expect(channelState.channel?.createdBy, isA()); + expect(channelState.channel?.frozen, true); + expect(channelState.channel?.extraData!['example'], 1); + expect(channelState.channel?.extraData!['name'], '#dev'); expect( - channelState.channel.extraData['image'], + channelState.channel?.extraData!['image'], 'https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png', ); expect(channelState.messages, hasLength(25)); - expect(channelState.messages[0], isA()); - expect(channelState.messages[0], isNotNull); + expect(channelState.messages![0], isA()); + expect(channelState.messages![0], isNotNull); expect( - channelState.messages[0].createdAt, + channelState.messages![0].createdAt, DateTime.parse('2020-01-29T03:23:02.843948Z'), ); - expect(channelState.messages[0].user, isA()); + expect(channelState.messages![0].user, isA()); expect(channelState.watcherCount, 5); }); diff --git a/packages/stream_chat/test/src/models/channel_test.dart b/packages/stream_chat/test/src/models/channel_test.dart index 9f6aa5b7..4a221a8a 100644 --- a/packages/stream_chat/test/src/models/channel_test.dart +++ b/packages/stream_chat/test/src/models/channel_test.dart @@ -20,8 +20,8 @@ void main() { expect(channel.id, equals('test')); expect(channel.type, equals('livestream')); expect(channel.cid, equals('test:livestream')); - expect(channel.extraData['cats'], equals(true)); - expect(channel.extraData['fruit'], equals(['bananas', 'apples'])); + expect(channel.extraData!['cats'], equals(true)); + expect(channel.extraData!['fruit'], equals(['bananas', 'apples'])); }); test('should serialize to json correctly', () { diff --git a/packages/stream_chat/test/src/models/reaction_test.dart b/packages/stream_chat/test/src/models/reaction_test.dart index 2a1d5431..80d2964b 100644 --- a/packages/stream_chat/test/src/models/reaction_test.dart +++ b/packages/stream_chat/test/src/models/reaction_test.dart @@ -33,7 +33,7 @@ void main() { expect(reaction.createdAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); expect(reaction.type, 'wow'); expect( - reaction.user.toJson(), + reaction.user?.toJson(), User.init('2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { 'image': 'https://randomuser.me/api/portraits/women/45.jpg', 'name': 'Daisy Morgan' diff --git a/packages/stream_chat/test/src/models/read_test.dart b/packages/stream_chat/test/src/models/read_test.dart index 9fd68b6a..7575d35f 100644 --- a/packages/stream_chat/test/src/models/read_test.dart +++ b/packages/stream_chat/test/src/models/read_test.dart @@ -19,7 +19,7 @@ void main() { test('should parse json correctly', () { final read = Read.fromJson(json.decode(jsonExample)); expect(read.lastRead, DateTime.parse('2020-01-28T22:17:30.966485504Z')); - expect(read.user.id, 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'); + expect(read.user?.id, 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'); expect(read.unreadMessages, 10); }); From c289871a56a0afc6c3f77f847690d949b5dfff73 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 8 Apr 2021 15:30:57 +0530 Subject: [PATCH 080/599] chore(llc): re-generate all files Signed-off-by: Sahil Kumar --- .../stream_chat/lib/src/api/requests.g.dart | 8 +- .../stream_chat/lib/src/api/responses.g.dart | 314 +++++++---------- .../stream_chat/lib/src/models/action.g.dart | 10 +- .../lib/src/models/attachment.g.dart | 60 ++-- .../lib/src/models/attachment_file.dart | 16 +- .../src/models/attachment_file.freezed.dart | 331 ++++++++---------- .../lib/src/models/attachment_file.g.dart | 8 +- .../lib/src/models/channel_config.g.dart | 38 +- .../lib/src/models/channel_model.g.dart | 21 +- .../lib/src/models/channel_state.g.dart | 68 ++-- .../stream_chat/lib/src/models/command.g.dart | 6 +- .../stream_chat/lib/src/models/device.g.dart | 4 +- .../stream_chat/lib/src/models/event.g.dart | 67 ++-- .../stream_chat/lib/src/models/member.g.dart | 14 +- .../stream_chat/lib/src/models/message.dart | 4 +- .../stream_chat/lib/src/models/message.g.dart | 98 +++--- .../stream_chat/lib/src/models/mute.g.dart | 4 +- .../lib/src/models/own_user.g.dart | 44 +-- .../lib/src/models/reaction.g.dart | 12 +- .../stream_chat/lib/src/models/read.g.dart | 4 +- .../stream_chat/lib/src/models/user.g.dart | 12 +- 21 files changed, 507 insertions(+), 636 deletions(-) diff --git a/packages/stream_chat/lib/src/api/requests.g.dart b/packages/stream_chat/lib/src/api/requests.g.dart index f020c1ea..fd9a5a35 100644 --- a/packages/stream_chat/lib/src/api/requests.g.dart +++ b/packages/stream_chat/lib/src/api/requests.g.dart @@ -13,7 +13,10 @@ Map _$SortOptionToJson(SortOption instance) => }; Map _$PaginationParamsToJson(PaginationParams instance) { - final val = {}; + final val = { + 'limit': instance.limit, + 'offset': instance.offset, + }; void writeNotNull(String key, dynamic value) { if (value != null) { @@ -21,11 +24,10 @@ Map _$PaginationParamsToJson(PaginationParams instance) { } } - writeNotNull('limit', instance.limit); - writeNotNull('offset', instance.offset); writeNotNull('id_gt', instance.greaterThan); writeNotNull('id_gte', instance.greaterThanOrEqual); writeNotNull('id_lt', instance.lessThan); writeNotNull('id_lte', instance.lessThanOrEqual); + val['hash_code'] = instance.hashCode; return val; } diff --git a/packages/stream_chat/lib/src/api/responses.g.dart b/packages/stream_chat/lib/src/api/responses.g.dart index 1f290d6e..141534a5 100644 --- a/packages/stream_chat/lib/src/api/responses.g.dart +++ b/packages/stream_chat/lib/src/api/responses.g.dart @@ -8,392 +8,334 @@ part of 'responses.dart'; SyncResponse _$SyncResponseFromJson(Map json) { return SyncResponse() - ..duration = json['duration'] as String - ..events = (json['events'] as List) - ?.map((e) => e == null - ? null - : Event.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(); + ..duration = json['duration'] as String? + ..events = (json['events'] as List?) + ?.map((e) => Event.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList(); } QueryChannelsResponse _$QueryChannelsResponseFromJson(Map json) { return QueryChannelsResponse() - ..duration = json['duration'] as String - ..channels = (json['channels'] as List) - ?.map((e) => e == null ? null : ChannelState.fromJson(e as Map)) - ?.toList(); + ..duration = json['duration'] as String? + ..channels = (json['channels'] as List?) + ?.map((e) => ChannelState.fromJson(e as Map)) + .toList(); } TranslateMessageResponse _$TranslateMessageResponseFromJson(Map json) { return TranslateMessageResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : TranslatedMessage.fromJson((json['message'] as Map)?.map( + : TranslatedMessage.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } QueryMembersResponse _$QueryMembersResponseFromJson(Map json) { return QueryMembersResponse() - ..duration = json['duration'] as String - ..members = (json['members'] as List) - ?.map((e) => e == null - ? null - : Member.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(); + ..duration = json['duration'] as String? + ..members = (json['members'] as List?) + ?.map((e) => Member.fromJson(Map.from(e as Map))) + .toList(); } QueryUsersResponse _$QueryUsersResponseFromJson(Map json) { return QueryUsersResponse() - ..duration = json['duration'] as String - ..users = (json['users'] as List) - ?.map((e) => e == null - ? null - : User.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(); + ..duration = json['duration'] as String? + ..users = (json['users'] as List?) + ?.map((e) => User.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList(); } QueryReactionsResponse _$QueryReactionsResponseFromJson(Map json) { return QueryReactionsResponse() - ..duration = json['duration'] as String - ..reactions = (json['reactions'] as List) - ?.map((e) => e == null - ? null - : Reaction.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(); + ..duration = json['duration'] as String? + ..reactions = (json['reactions'] as List?) + ?.map((e) => Reaction.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList(); } QueryRepliesResponse _$QueryRepliesResponseFromJson(Map json) { return QueryRepliesResponse() - ..duration = json['duration'] as String - ..messages = (json['messages'] as List) - ?.map((e) => e == null - ? null - : Message.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(); + ..duration = json['duration'] as String? + ..messages = (json['messages'] as List?) + ?.map((e) => Message.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList(); } ListDevicesResponse _$ListDevicesResponseFromJson(Map json) { return ListDevicesResponse() - ..duration = json['duration'] as String - ..devices = (json['devices'] as List) - ?.map((e) => e == null - ? null - : Device.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(); + ..duration = json['duration'] as String? + ..devices = (json['devices'] as List?) + ?.map((e) => Device.fromJson(Map.from(e as Map))) + .toList(); } SendFileResponse _$SendFileResponseFromJson(Map json) { return SendFileResponse() - ..duration = json['duration'] as String - ..file = json['file'] as String; + ..duration = json['duration'] as String? + ..file = json['file'] as String?; } SendImageResponse _$SendImageResponseFromJson(Map json) { return SendImageResponse() - ..duration = json['duration'] as String - ..file = json['file'] as String; + ..duration = json['duration'] as String? + ..file = json['file'] as String?; } SendReactionResponse _$SendReactionResponseFromJson(Map json) { return SendReactionResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map)?.map( + : Message.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )) ..reaction = json['reaction'] == null ? null - : Reaction.fromJson((json['reaction'] as Map)?.map( + : Reaction.fromJson((json['reaction'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } ConnectGuestUserResponse _$ConnectGuestUserResponseFromJson(Map json) { return ConnectGuestUserResponse() - ..duration = json['duration'] as String - ..accessToken = json['access_token'] as String + ..duration = json['duration'] as String? + ..accessToken = json['access_token'] as String? ..user = json['user'] == null ? null - : User.fromJson((json['user'] as Map)?.map( + : User.fromJson((json['user'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } UpdateUsersResponse _$UpdateUsersResponseFromJson(Map json) { return UpdateUsersResponse() - ..duration = json['duration'] as String - ..users = (json['users'] as Map)?.map( + ..duration = json['duration'] as String? + ..users = (json['users'] as Map?)?.map( (k, e) => MapEntry( k as String, - e == null - ? null - : User.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))), + User.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))), ); } UpdateMessageResponse _$UpdateMessageResponseFromJson(Map json) { return UpdateMessageResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map)?.map( + : Message.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } SendMessageResponse _$SendMessageResponseFromJson(Map json) { return SendMessageResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map)?.map( + : Message.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } GetMessageResponse _$GetMessageResponseFromJson(Map json) { return GetMessageResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map)?.map( + : Message.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )) ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map)?.map( + : ChannelModel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } SearchMessagesResponse _$SearchMessagesResponseFromJson(Map json) { return SearchMessagesResponse() - ..duration = json['duration'] as String - ..results = (json['results'] as List) - ?.map((e) => e == null ? null : GetMessageResponse.fromJson(e as Map)) - ?.toList(); + ..duration = json['duration'] as String? + ..results = (json['results'] as List?) + ?.map((e) => GetMessageResponse.fromJson(e as Map)) + .toList(); } GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson(Map json) { return GetMessagesByIdResponse() - ..duration = json['duration'] as String - ..messages = (json['messages'] as List) - ?.map((e) => e == null - ? null - : Message.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(); + ..duration = json['duration'] as String? + ..messages = (json['messages'] as List?) + ?.map((e) => Message.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList(); } UpdateChannelResponse _$UpdateChannelResponseFromJson(Map json) { return UpdateChannelResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map)?.map( + : ChannelModel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )) - ..members = (json['members'] as List) - ?.map((e) => e == null - ? null - : Member.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList() + ..members = (json['members'] as List?) + ?.map((e) => Member.fromJson(Map.from(e as Map))) + .toList() ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map)?.map( + : Message.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } PartialUpdateChannelResponse _$PartialUpdateChannelResponseFromJson(Map json) { return PartialUpdateChannelResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map)?.map( + : ChannelModel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )) - ..members = (json['members'] as List) - ?.map((e) => e == null - ? null - : Member.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(); + ..members = (json['members'] as List?) + ?.map((e) => Member.fromJson(Map.from(e as Map))) + .toList(); } InviteMembersResponse _$InviteMembersResponseFromJson(Map json) { return InviteMembersResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map)?.map( + : ChannelModel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )) - ..members = (json['members'] as List) - ?.map((e) => e == null - ? null - : Member.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList() + ..members = (json['members'] as List?) + ?.map((e) => Member.fromJson(Map.from(e as Map))) + .toList() ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map)?.map( + : Message.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } RemoveMembersResponse _$RemoveMembersResponseFromJson(Map json) { return RemoveMembersResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map)?.map( + : ChannelModel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )) - ..members = (json['members'] as List) - ?.map((e) => e == null - ? null - : Member.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList() + ..members = (json['members'] as List?) + ?.map((e) => Member.fromJson(Map.from(e as Map))) + .toList() ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map)?.map( + : Message.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } SendActionResponse _$SendActionResponseFromJson(Map json) { return SendActionResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map)?.map( + : Message.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } AddMembersResponse _$AddMembersResponseFromJson(Map json) { return AddMembersResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map)?.map( + : ChannelModel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )) - ..members = (json['members'] as List) - ?.map((e) => e == null - ? null - : Member.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList() + ..members = (json['members'] as List?) + ?.map((e) => Member.fromJson(Map.from(e as Map))) + .toList() ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map)?.map( + : Message.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } AcceptInviteResponse _$AcceptInviteResponseFromJson(Map json) { return AcceptInviteResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map)?.map( + : ChannelModel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )) - ..members = (json['members'] as List) - ?.map((e) => e == null - ? null - : Member.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList() + ..members = (json['members'] as List?) + ?.map((e) => Member.fromJson(Map.from(e as Map))) + .toList() ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map)?.map( + : Message.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } RejectInviteResponse _$RejectInviteResponseFromJson(Map json) { return RejectInviteResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map)?.map( + : ChannelModel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )) - ..members = (json['members'] as List) - ?.map((e) => e == null - ? null - : Member.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList() + ..members = (json['members'] as List?) + ?.map((e) => Member.fromJson(Map.from(e as Map))) + .toList() ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map)?.map( + : Message.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )); } EmptyResponse _$EmptyResponseFromJson(Map json) { - return EmptyResponse()..duration = json['duration'] as String; + return EmptyResponse()..duration = json['duration'] as String?; } ChannelStateResponse _$ChannelStateResponseFromJson(Map json) { return ChannelStateResponse() - ..duration = json['duration'] as String + ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map)?.map( + : ChannelModel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )) - ..messages = (json['messages'] as List) - ?.map((e) => e == null - ? null - : Message.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList() - ..members = (json['members'] as List) - ?.map((e) => e == null - ? null - : Member.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList() - ..watcherCount = json['watcher_count'] as int - ..read = (json['read'] as List) - ?.map((e) => e == null - ? null - : Read.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(); + ..messages = (json['messages'] as List?) + ?.map((e) => Message.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList() + ..members = (json['members'] as List?) + ?.map((e) => Member.fromJson(Map.from(e as Map))) + .toList() + ..watcherCount = json['watcher_count'] as int? + ..read = (json['read'] as List?) + ?.map((e) => Read.fromJson(Map.from(e as Map))) + .toList(); } diff --git a/packages/stream_chat/lib/src/models/action.g.dart b/packages/stream_chat/lib/src/models/action.g.dart index 3c567843..64a5195b 100644 --- a/packages/stream_chat/lib/src/models/action.g.dart +++ b/packages/stream_chat/lib/src/models/action.g.dart @@ -8,11 +8,11 @@ part of 'action.dart'; Action _$ActionFromJson(Map json) { return Action( - name: json['name'] as String, - style: json['style'] as String, - text: json['text'] as String, - type: json['type'] as String, - value: json['value'] as String, + name: json['name'] as String?, + style: json['style'] as String?, + text: json['text'] as String?, + type: json['type'] as String?, + value: json['value'] as String?, ); } diff --git a/packages/stream_chat/lib/src/models/attachment.g.dart b/packages/stream_chat/lib/src/models/attachment.g.dart index c45771c2..a992096f 100644 --- a/packages/stream_chat/lib/src/models/attachment.g.dart +++ b/packages/stream_chat/lib/src/models/attachment.g.dart @@ -8,44 +8,38 @@ part of 'attachment.dart'; Attachment _$AttachmentFromJson(Map json) { return Attachment( - id: json['id'] as String, - type: json['type'] as String, - titleLink: json['title_link'] as String, - title: json['title'] as String, - thumbUrl: json['thumb_url'] as String, - text: json['text'] as String, - pretext: json['pretext'] as String, - ogScrapeUrl: json['og_scrape_url'] as String, - imageUrl: json['image_url'] as String, - footerIcon: json['footer_icon'] as String, - footer: json['footer'] as String, + id: json['id'] as String?, + type: json['type'] as String?, + titleLink: json['title_link'] as String?, + title: json['title'] as String?, + thumbUrl: json['thumb_url'] as String?, + text: json['text'] as String?, + pretext: json['pretext'] as String?, + ogScrapeUrl: json['og_scrape_url'] as String?, + imageUrl: json['image_url'] as String?, + footerIcon: json['footer_icon'] as String?, + footer: json['footer'] as String?, fields: json['fields'], - fallback: json['fallback'] as String, - color: json['color'] as String, - authorName: json['author_name'] as String, - authorLink: json['author_link'] as String, - authorIcon: json['author_icon'] as String, - assetUrl: json['asset_url'] as String, - actions: (json['actions'] as List) - ?.map((e) => e == null - ? null - : Action.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - extraData: (json['extra_data'] as Map)?.map( + fallback: json['fallback'] as String?, + color: json['color'] as String?, + authorName: json['author_name'] as String?, + authorLink: json['author_link'] as String?, + authorIcon: json['author_icon'] as String?, + assetUrl: json['asset_url'] as String?, + actions: (json['actions'] as List?) + ?.map((e) => Action.fromJson(Map.from(e as Map))) + .toList(), + extraData: (json['extra_data'] as Map?)?.map( (k, e) => MapEntry(k as String, e), ), file: json['file'] == null ? null - : AttachmentFile.fromJson((json['file'] as Map)?.map( - (k, e) => MapEntry(k as String, e), - )), + : AttachmentFile.fromJson( + Map.from(json['file'] as Map)), uploadState: json['upload_state'] == null ? null - : UploadState.fromJson((json['upload_state'] as Map)?.map( - (k, e) => MapEntry(k as String, e), - )), + : UploadState.fromJson( + Map.from(json['upload_state'] as Map)), ); } @@ -75,10 +69,10 @@ Map _$AttachmentToJson(Attachment instance) { writeNotNull('author_link', instance.authorLink); writeNotNull('author_icon', instance.authorIcon); writeNotNull('asset_url', instance.assetUrl); - writeNotNull('actions', instance.actions?.map((e) => e?.toJson())?.toList()); + writeNotNull('actions', instance.actions?.map((e) => e.toJson()).toList()); writeNotNull('file', instance.file?.toJson()); writeNotNull('upload_state', instance.uploadState?.toJson()); writeNotNull('extra_data', instance.extraData); - writeNotNull('id', instance.id); + val['id'] = instance.id; return val; } diff --git a/packages/stream_chat/lib/src/models/attachment_file.dart b/packages/stream_chat/lib/src/models/attachment_file.dart index 46208e28..8dac46f6 100644 --- a/packages/stream_chat/lib/src/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/models/attachment_file.dart @@ -4,16 +4,18 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:meta/meta.dart'; part 'attachment_file.freezed.dart'; + part 'attachment_file.g.dart'; /// Union class to hold various [UploadState] of a attachment. @freezed -abstract class UploadState with _$UploadState { +class UploadState with _$UploadState { /// Preparing state of the union const factory UploadState.preparing() = Preparing; /// InProgress state of the union - const factory UploadState.inProgress({int uploaded, int total}) = InProgress; + const factory UploadState.inProgress( + {required int uploaded, required int total}) = InProgress; /// Success state of the union const factory UploadState.success() = Success; @@ -41,9 +43,15 @@ extension UploadStateX on UploadState? { bool get isFailed => this is Failed; } -Uint8List _fromString(String bytes) => Uint8List.fromList(bytes.codeUnits); +Uint8List? _fromString(String? bytes) { + if (bytes == null) return null; + return Uint8List.fromList(bytes.codeUnits); +} -String _toString(Uint8List bytes) => String.fromCharCodes(bytes); +String? _toString(Uint8List? bytes) { + if (bytes == null) return null; + return String.fromCharCodes(bytes); +} /// The class that contains the information about an attachment file @JsonSerializable() diff --git a/packages/stream_chat/lib/src/models/attachment_file.freezed.dart b/packages/stream_chat/lib/src/models/attachment_file.freezed.dart index 4eea1f13..a6c5e0c6 100644 --- a/packages/stream_chat/lib/src/models/attachment_file.freezed.dart +++ b/packages/stream_chat/lib/src/models/attachment_file.freezed.dart @@ -1,5 +1,5 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides part of 'attachment_file.dart'; @@ -8,6 +8,10 @@ part of 'attachment_file.dart'; // ************************************************************************** T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + UploadState _$UploadStateFromJson(Map json) { switch (json['runtimeType'] as String) { case 'preparing': @@ -28,74 +32,72 @@ UploadState _$UploadStateFromJson(Map json) { class _$UploadStateTearOff { const _$UploadStateTearOff(); -// ignore: unused_element Preparing preparing() { return const Preparing(); } -// ignore: unused_element - InProgress inProgress({int uploaded, int total}) { + InProgress inProgress({required int uploaded, required int total}) { return InProgress( uploaded: uploaded, total: total, ); } -// ignore: unused_element Success success() { return const Success(); } -// ignore: unused_element - Failed failed({@required String error}) { + Failed failed({required String error}) { return Failed( error: error, ); } -// ignore: unused_element UploadState fromJson(Map json) { return UploadState.fromJson(json); } } /// @nodoc -// ignore: unused_element const $UploadState = _$UploadStateTearOff(); /// @nodoc mixin _$UploadState { @optionalTypeArgs - TResult when({ - @required TResult preparing(), - @required TResult inProgress(int uploaded, int total), - @required TResult success(), - @required TResult failed(String error), - }); + TResult when({ + required TResult Function() preparing, + required TResult Function(int uploaded, int total) inProgress, + required TResult Function() success, + required TResult Function(String error) failed, + }) => + throw _privateConstructorUsedError; @optionalTypeArgs - TResult maybeWhen({ - TResult preparing(), - TResult inProgress(int uploaded, int total), - TResult success(), - TResult failed(String error), - @required TResult orElse(), - }); + TResult maybeWhen({ + TResult Function()? preparing, + TResult Function(int uploaded, int total)? inProgress, + TResult Function()? success, + TResult Function(String error)? failed, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; @optionalTypeArgs - TResult map({ - @required TResult preparing(Preparing value), - @required TResult inProgress(InProgress value), - @required TResult success(Success value), - @required TResult failed(Failed value), - }); + TResult map({ + required TResult Function(Preparing value) preparing, + required TResult Function(InProgress value) inProgress, + required TResult Function(Success value) success, + required TResult Function(Failed value) failed, + }) => + throw _privateConstructorUsedError; @optionalTypeArgs - TResult maybeMap({ - TResult preparing(Preparing value), - TResult inProgress(InProgress value), - TResult success(Success value), - TResult failed(Failed value), - @required TResult orElse(), - }); - Map toJson(); + TResult maybeMap({ + TResult Function(Preparing value)? preparing, + TResult Function(InProgress value)? inProgress, + TResult Function(Success value)? success, + TResult Function(Failed value)? failed, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + Map toJson() => throw _privateConstructorUsedError; } /// @nodoc @@ -154,29 +156,24 @@ class _$Preparing implements Preparing { @override @optionalTypeArgs - TResult when({ - @required TResult preparing(), - @required TResult inProgress(int uploaded, int total), - @required TResult success(), - @required TResult failed(String error), + TResult when({ + required TResult Function() preparing, + required TResult Function(int uploaded, int total) inProgress, + required TResult Function() success, + required TResult Function(String error) failed, }) { - assert(preparing != null); - assert(inProgress != null); - assert(success != null); - assert(failed != null); return preparing(); } @override @optionalTypeArgs - TResult maybeWhen({ - TResult preparing(), - TResult inProgress(int uploaded, int total), - TResult success(), - TResult failed(String error), - @required TResult orElse(), + TResult maybeWhen({ + TResult Function()? preparing, + TResult Function(int uploaded, int total)? inProgress, + TResult Function()? success, + TResult Function(String error)? failed, + required TResult orElse(), }) { - assert(orElse != null); if (preparing != null) { return preparing(); } @@ -185,29 +182,24 @@ class _$Preparing implements Preparing { @override @optionalTypeArgs - TResult map({ - @required TResult preparing(Preparing value), - @required TResult inProgress(InProgress value), - @required TResult success(Success value), - @required TResult failed(Failed value), + TResult map({ + required TResult Function(Preparing value) preparing, + required TResult Function(InProgress value) inProgress, + required TResult Function(Success value) success, + required TResult Function(Failed value) failed, }) { - assert(preparing != null); - assert(inProgress != null); - assert(success != null); - assert(failed != null); return preparing(this); } @override @optionalTypeArgs - TResult maybeMap({ - TResult preparing(Preparing value), - TResult inProgress(InProgress value), - TResult success(Success value), - TResult failed(Failed value), - @required TResult orElse(), + TResult maybeMap({ + TResult Function(Preparing value)? preparing, + TResult Function(InProgress value)? inProgress, + TResult Function(Success value)? success, + TResult Function(Failed value)? failed, + required TResult orElse(), }) { - assert(orElse != null); if (preparing != null) { return preparing(this); } @@ -245,12 +237,18 @@ class _$InProgressCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res> @override $Res call({ - Object uploaded = freezed, - Object total = freezed, + Object? uploaded = freezed, + Object? total = freezed, }) { return _then(InProgress( - uploaded: uploaded == freezed ? _value.uploaded : uploaded as int, - total: total == freezed ? _value.total : total as int, + uploaded: uploaded == freezed + ? _value.uploaded + : uploaded // ignore: cast_nullable_to_non_nullable + as int, + total: total == freezed + ? _value.total + : total // ignore: cast_nullable_to_non_nullable + as int, )); } } @@ -259,7 +257,7 @@ class _$InProgressCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res> /// @nodoc class _$InProgress implements InProgress { - const _$InProgress({this.uploaded, this.total}); + const _$InProgress({required this.uploaded, required this.total}); factory _$InProgress.fromJson(Map json) => _$_$InProgressFromJson(json); @@ -298,29 +296,24 @@ class _$InProgress implements InProgress { @override @optionalTypeArgs - TResult when({ - @required TResult preparing(), - @required TResult inProgress(int uploaded, int total), - @required TResult success(), - @required TResult failed(String error), + TResult when({ + required TResult Function() preparing, + required TResult Function(int uploaded, int total) inProgress, + required TResult Function() success, + required TResult Function(String error) failed, }) { - assert(preparing != null); - assert(inProgress != null); - assert(success != null); - assert(failed != null); return inProgress(uploaded, total); } @override @optionalTypeArgs - TResult maybeWhen({ - TResult preparing(), - TResult inProgress(int uploaded, int total), - TResult success(), - TResult failed(String error), - @required TResult orElse(), + TResult maybeWhen({ + TResult Function()? preparing, + TResult Function(int uploaded, int total)? inProgress, + TResult Function()? success, + TResult Function(String error)? failed, + required TResult orElse(), }) { - assert(orElse != null); if (inProgress != null) { return inProgress(uploaded, total); } @@ -329,29 +322,24 @@ class _$InProgress implements InProgress { @override @optionalTypeArgs - TResult map({ - @required TResult preparing(Preparing value), - @required TResult inProgress(InProgress value), - @required TResult success(Success value), - @required TResult failed(Failed value), + TResult map({ + required TResult Function(Preparing value) preparing, + required TResult Function(InProgress value) inProgress, + required TResult Function(Success value) success, + required TResult Function(Failed value) failed, }) { - assert(preparing != null); - assert(inProgress != null); - assert(success != null); - assert(failed != null); return inProgress(this); } @override @optionalTypeArgs - TResult maybeMap({ - TResult preparing(Preparing value), - TResult inProgress(InProgress value), - TResult success(Success value), - TResult failed(Failed value), - @required TResult orElse(), + TResult maybeMap({ + TResult Function(Preparing value)? preparing, + TResult Function(InProgress value)? inProgress, + TResult Function(Success value)? success, + TResult Function(Failed value)? failed, + required TResult orElse(), }) { - assert(orElse != null); if (inProgress != null) { return inProgress(this); } @@ -365,15 +353,17 @@ class _$InProgress implements InProgress { } abstract class InProgress implements UploadState { - const factory InProgress({int uploaded, int total}) = _$InProgress; + const factory InProgress({required int uploaded, required int total}) = + _$InProgress; factory InProgress.fromJson(Map json) = _$InProgress.fromJson; - int get uploaded; - int get total; + int get uploaded => throw _privateConstructorUsedError; + int get total => throw _privateConstructorUsedError; @JsonKey(ignore: true) - $InProgressCopyWith get copyWith; + $InProgressCopyWith get copyWith => + throw _privateConstructorUsedError; } /// @nodoc @@ -416,29 +406,24 @@ class _$Success implements Success { @override @optionalTypeArgs - TResult when({ - @required TResult preparing(), - @required TResult inProgress(int uploaded, int total), - @required TResult success(), - @required TResult failed(String error), + TResult when({ + required TResult Function() preparing, + required TResult Function(int uploaded, int total) inProgress, + required TResult Function() success, + required TResult Function(String error) failed, }) { - assert(preparing != null); - assert(inProgress != null); - assert(success != null); - assert(failed != null); return success(); } @override @optionalTypeArgs - TResult maybeWhen({ - TResult preparing(), - TResult inProgress(int uploaded, int total), - TResult success(), - TResult failed(String error), - @required TResult orElse(), + TResult maybeWhen({ + TResult Function()? preparing, + TResult Function(int uploaded, int total)? inProgress, + TResult Function()? success, + TResult Function(String error)? failed, + required TResult orElse(), }) { - assert(orElse != null); if (success != null) { return success(); } @@ -447,29 +432,24 @@ class _$Success implements Success { @override @optionalTypeArgs - TResult map({ - @required TResult preparing(Preparing value), - @required TResult inProgress(InProgress value), - @required TResult success(Success value), - @required TResult failed(Failed value), + TResult map({ + required TResult Function(Preparing value) preparing, + required TResult Function(InProgress value) inProgress, + required TResult Function(Success value) success, + required TResult Function(Failed value) failed, }) { - assert(preparing != null); - assert(inProgress != null); - assert(success != null); - assert(failed != null); return success(this); } @override @optionalTypeArgs - TResult maybeMap({ - TResult preparing(Preparing value), - TResult inProgress(InProgress value), - TResult success(Success value), - TResult failed(Failed value), - @required TResult orElse(), + TResult maybeMap({ + TResult Function(Preparing value)? preparing, + TResult Function(InProgress value)? inProgress, + TResult Function(Success value)? success, + TResult Function(Failed value)? failed, + required TResult orElse(), }) { - assert(orElse != null); if (success != null) { return success(this); } @@ -506,10 +486,13 @@ class _$FailedCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res> @override $Res call({ - Object error = freezed, + Object? error = freezed, }) { return _then(Failed( - error: error == freezed ? _value.error : error as String, + error: error == freezed + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as String, )); } } @@ -518,7 +501,7 @@ class _$FailedCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res> /// @nodoc class _$Failed implements Failed { - const _$Failed({@required this.error}) : assert(error != null); + const _$Failed({required this.error}); factory _$Failed.fromJson(Map json) => _$_$FailedFromJson(json); @@ -550,29 +533,24 @@ class _$Failed implements Failed { @override @optionalTypeArgs - TResult when({ - @required TResult preparing(), - @required TResult inProgress(int uploaded, int total), - @required TResult success(), - @required TResult failed(String error), + TResult when({ + required TResult Function() preparing, + required TResult Function(int uploaded, int total) inProgress, + required TResult Function() success, + required TResult Function(String error) failed, }) { - assert(preparing != null); - assert(inProgress != null); - assert(success != null); - assert(failed != null); return failed(error); } @override @optionalTypeArgs - TResult maybeWhen({ - TResult preparing(), - TResult inProgress(int uploaded, int total), - TResult success(), - TResult failed(String error), - @required TResult orElse(), + TResult maybeWhen({ + TResult Function()? preparing, + TResult Function(int uploaded, int total)? inProgress, + TResult Function()? success, + TResult Function(String error)? failed, + required TResult orElse(), }) { - assert(orElse != null); if (failed != null) { return failed(error); } @@ -581,29 +559,24 @@ class _$Failed implements Failed { @override @optionalTypeArgs - TResult map({ - @required TResult preparing(Preparing value), - @required TResult inProgress(InProgress value), - @required TResult success(Success value), - @required TResult failed(Failed value), + TResult map({ + required TResult Function(Preparing value) preparing, + required TResult Function(InProgress value) inProgress, + required TResult Function(Success value) success, + required TResult Function(Failed value) failed, }) { - assert(preparing != null); - assert(inProgress != null); - assert(success != null); - assert(failed != null); return failed(this); } @override @optionalTypeArgs - TResult maybeMap({ - TResult preparing(Preparing value), - TResult inProgress(InProgress value), - TResult success(Success value), - TResult failed(Failed value), - @required TResult orElse(), + TResult maybeMap({ + TResult Function(Preparing value)? preparing, + TResult Function(InProgress value)? inProgress, + TResult Function(Success value)? success, + TResult Function(Failed value)? failed, + required TResult orElse(), }) { - assert(orElse != null); if (failed != null) { return failed(this); } @@ -617,11 +590,11 @@ class _$Failed implements Failed { } abstract class Failed implements UploadState { - const factory Failed({@required String error}) = _$Failed; + const factory Failed({required String error}) = _$Failed; factory Failed.fromJson(Map json) = _$Failed.fromJson; - String get error; + String get error => throw _privateConstructorUsedError; @JsonKey(ignore: true) - $FailedCopyWith get copyWith; + $FailedCopyWith get copyWith => throw _privateConstructorUsedError; } diff --git a/packages/stream_chat/lib/src/models/attachment_file.g.dart b/packages/stream_chat/lib/src/models/attachment_file.g.dart index c6716fba..8dc70ac3 100644 --- a/packages/stream_chat/lib/src/models/attachment_file.g.dart +++ b/packages/stream_chat/lib/src/models/attachment_file.g.dart @@ -8,10 +8,10 @@ part of 'attachment_file.dart'; AttachmentFile _$AttachmentFileFromJson(Map json) { return AttachmentFile( - path: json['path'] as String, - name: json['name'] as String, - bytes: _fromString(json['bytes'] as String), - size: json['size'] as int, + path: json['path'] as String?, + name: json['name'] as String?, + bytes: _fromString(json['bytes'] as String?), + size: json['size'] as int?, ); } diff --git a/packages/stream_chat/lib/src/models/channel_config.g.dart b/packages/stream_chat/lib/src/models/channel_config.g.dart index e8152c80..a1104888 100644 --- a/packages/stream_chat/lib/src/models/channel_config.g.dart +++ b/packages/stream_chat/lib/src/models/channel_config.g.dart @@ -8,39 +8,35 @@ part of 'channel_config.dart'; ChannelConfig _$ChannelConfigFromJson(Map json) { return ChannelConfig( - automod: json['automod'] as String, - commands: (json['commands'] as List) - ?.map((e) => e == null - ? null - : Command.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - connectEvents: json['connect_events'] as bool, + automod: json['automod'] as String?, + commands: (json['commands'] as List?) + ?.map((e) => Command.fromJson(Map.from(e as Map))) + .toList(), + connectEvents: json['connect_events'] as bool?, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), - maxMessageLength: json['max_message_length'] as int, - messageRetention: json['message_retention'] as String, - mutes: json['mutes'] as bool, - name: json['name'] as String, - reactions: json['reactions'] as bool, - readEvents: json['read_events'] as bool, - replies: json['replies'] as bool, - search: json['search'] as bool, - typingEvents: json['typing_events'] as bool, - uploads: json['uploads'] as bool, - urlEnrichment: json['url_enrichment'] as bool, + maxMessageLength: json['max_message_length'] as int?, + messageRetention: json['message_retention'] as String?, + mutes: json['mutes'] as bool?, + name: json['name'] as String?, + reactions: json['reactions'] as bool?, + readEvents: json['read_events'] as bool?, + replies: json['replies'] as bool?, + search: json['search'] as bool?, + typingEvents: json['typing_events'] as bool?, + uploads: json['uploads'] as bool?, + urlEnrichment: json['url_enrichment'] as bool?, ); } Map _$ChannelConfigToJson(ChannelConfig instance) => { 'automod': instance.automod, - 'commands': instance.commands?.map((e) => e?.toJson())?.toList(), + 'commands': instance.commands?.map((e) => e.toJson()).toList(), 'connect_events': instance.connectEvents, 'created_at': instance.createdAt?.toIso8601String(), 'updated_at': instance.updatedAt?.toIso8601String(), diff --git a/packages/stream_chat/lib/src/models/channel_model.g.dart b/packages/stream_chat/lib/src/models/channel_model.g.dart index 4f535f46..9b1a6aeb 100644 --- a/packages/stream_chat/lib/src/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/models/channel_model.g.dart @@ -8,20 +8,19 @@ part of 'channel_model.dart'; ChannelModel _$ChannelModelFromJson(Map json) { return ChannelModel( - id: json['id'] as String, - type: json['type'] as String, - cid: json['cid'] as String, + id: json['id'] as String?, + type: json['type'] as String?, + cid: json['cid'] as String?, config: json['config'] == null ? null - : ChannelConfig.fromJson((json['config'] as Map)?.map( - (k, e) => MapEntry(k as String, e), - )), + : ChannelConfig.fromJson( + Map.from(json['config'] as Map)), createdBy: json['created_by'] == null ? null - : User.fromJson((json['created_by'] as Map)?.map( + : User.fromJson((json['created_by'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - frozen: json['frozen'] as bool, + frozen: json['frozen'] as bool?, lastMessageAt: json['last_message_at'] == null ? null : DateTime.parse(json['last_message_at'] as String), @@ -34,11 +33,11 @@ ChannelModel _$ChannelModelFromJson(Map json) { deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), - memberCount: json['member_count'] as int, - extraData: (json['extra_data'] as Map)?.map( + memberCount: json['member_count'] as int?, + extraData: (json['extra_data'] as Map?)?.map( (k, e) => MapEntry(k as String, e), ), - team: json['team'] as String, + team: json['team'] as String?, ); } diff --git a/packages/stream_chat/lib/src/models/channel_state.g.dart b/packages/stream_chat/lib/src/models/channel_state.g.dart index a66899a4..2eaa02d2 100644 --- a/packages/stream_chat/lib/src/models/channel_state.g.dart +++ b/packages/stream_chat/lib/src/models/channel_state.g.dart @@ -10,56 +10,44 @@ ChannelState _$ChannelStateFromJson(Map json) { return ChannelState( channel: json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map)?.map( + : ChannelModel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - messages: (json['messages'] as List) + messages: (json['messages'] as List?) + ?.map((e) => Message.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList(), + members: (json['members'] as List?) ?.map((e) => e == null ? null - : Message.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - members: (json['members'] as List) - ?.map((e) => e == null - ? null - : Member.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - pinnedMessages: (json['pinned_messages'] as List) - ?.map((e) => e == null - ? null - : Message.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - watcherCount: json['watcher_count'] as int, - watchers: (json['watchers'] as List) - ?.map((e) => e == null - ? null - : User.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - read: (json['read'] as List) - ?.map((e) => e == null - ? null - : Read.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), + : Member.fromJson(Map.from(e as Map))) + .toList(), + pinnedMessages: (json['pinned_messages'] as List?) + ?.map((e) => Message.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList(), + watcherCount: json['watcher_count'] as int?, + watchers: (json['watchers'] as List?) + ?.map((e) => User.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList(), + read: (json['read'] as List?) + ?.map((e) => Read.fromJson(Map.from(e as Map))) + .toList(), ); } Map _$ChannelStateToJson(ChannelState instance) => { 'channel': instance.channel?.toJson(), - 'messages': instance.messages?.map((e) => e?.toJson())?.toList(), - 'members': instance.members?.map((e) => e?.toJson())?.toList(), + 'messages': instance.messages?.map((e) => e.toJson()).toList(), + 'members': instance.members?.map((e) => e?.toJson()).toList(), 'pinned_messages': - instance.pinnedMessages?.map((e) => e?.toJson())?.toList(), + instance.pinnedMessages?.map((e) => e.toJson()).toList(), 'watcher_count': instance.watcherCount, - 'watchers': instance.watchers?.map((e) => e?.toJson())?.toList(), - 'read': instance.read?.map((e) => e?.toJson())?.toList(), + 'watchers': instance.watchers?.map((e) => e.toJson()).toList(), + 'read': instance.read?.map((e) => e.toJson()).toList(), }; diff --git a/packages/stream_chat/lib/src/models/command.g.dart b/packages/stream_chat/lib/src/models/command.g.dart index f32e8e8a..b6c08c32 100644 --- a/packages/stream_chat/lib/src/models/command.g.dart +++ b/packages/stream_chat/lib/src/models/command.g.dart @@ -8,9 +8,9 @@ part of 'command.dart'; Command _$CommandFromJson(Map json) { return Command( - name: json['name'] as String, - description: json['description'] as String, - args: json['args'] as String, + name: json['name'] as String?, + description: json['description'] as String?, + args: json['args'] as String?, ); } diff --git a/packages/stream_chat/lib/src/models/device.g.dart b/packages/stream_chat/lib/src/models/device.g.dart index bac60856..ff4acc89 100644 --- a/packages/stream_chat/lib/src/models/device.g.dart +++ b/packages/stream_chat/lib/src/models/device.g.dart @@ -8,8 +8,8 @@ part of 'device.dart'; Device _$DeviceFromJson(Map json) { return Device( - id: json['id'] as String, - pushProvider: json['push_provider'] as String, + id: json['id'] as String?, + pushProvider: json['push_provider'] as String?, ); } diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index aaef8181..71566caf 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -8,52 +8,50 @@ part of 'event.dart'; Event _$EventFromJson(Map json) { return Event( - type: json['type'] as String, - cid: json['cid'] as String, - connectionId: json['connection_id'] as String, + type: json['type'] as String?, + cid: json['cid'] as String?, + connectionId: json['connection_id'] as String?, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), me: json['me'] == null ? null - : OwnUser.fromJson((json['me'] as Map)?.map( + : OwnUser.fromJson((json['me'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), user: json['user'] == null ? null - : User.fromJson((json['user'] as Map)?.map( + : User.fromJson((json['user'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), message: json['message'] == null ? null - : Message.fromJson((json['message'] as Map)?.map( + : Message.fromJson((json['message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - totalUnreadCount: json['total_unread_count'] as int, - unreadChannels: json['unread_channels'] as int, + totalUnreadCount: json['total_unread_count'] as int?, + unreadChannels: json['unread_channels'] as int?, reaction: json['reaction'] == null ? null - : Reaction.fromJson((json['reaction'] as Map)?.map( + : Reaction.fromJson((json['reaction'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - online: json['online'] as bool, + online: json['online'] as bool?, channel: json['channel'] == null ? null - : EventChannel.fromJson((json['channel'] as Map)?.map( + : EventChannel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), member: json['member'] == null ? null - : Member.fromJson((json['member'] as Map)?.map( - (k, e) => MapEntry(k as String, e), - )), - channelId: json['channel_id'] as String, - channelType: json['channel_type'] as String, - parentId: json['parent_id'] as String, - extraData: (json['extra_data'] as Map)?.map( + : Member.fromJson(Map.from(json['member'] as Map)), + channelId: json['channel_id'] as String?, + channelType: json['channel_type'] as String?, + parentId: json['parent_id'] as String?, + extraData: (json['extra_data'] as Map?)?.map( (k, e) => MapEntry(k as String, e), ), - )..isLocal = json['is_local'] as bool; + )..isLocal = json['is_local'] as bool?; } Map _$EventToJson(Event instance) { @@ -89,27 +87,22 @@ Map _$EventToJson(Event instance) { EventChannel _$EventChannelFromJson(Map json) { return EventChannel( - members: (json['members'] as List) - ?.map((e) => e == null - ? null - : Member.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - id: json['id'] as String, - type: json['type'] as String, - cid: json['cid'] as String, + members: (json['members'] as List?) + ?.map((e) => Member.fromJson(Map.from(e as Map))) + .toList(), + id: json['id'] as String?, + type: json['type'] as String?, + cid: json['cid'] as String?, config: json['config'] == null ? null - : ChannelConfig.fromJson((json['config'] as Map)?.map( - (k, e) => MapEntry(k as String, e), - )), + : ChannelConfig.fromJson( + Map.from(json['config'] as Map)), createdBy: json['created_by'] == null ? null - : User.fromJson((json['created_by'] as Map)?.map( + : User.fromJson((json['created_by'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - frozen: json['frozen'] as bool, + frozen: json['frozen'] as bool?, lastMessageAt: json['last_message_at'] == null ? null : DateTime.parse(json['last_message_at'] as String), @@ -122,8 +115,8 @@ EventChannel _$EventChannelFromJson(Map json) { deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), - memberCount: json['member_count'] as int, - extraData: (json['extra_data'] as Map)?.map( + memberCount: json['member_count'] as int?, + extraData: (json['extra_data'] as Map?)?.map( (k, e) => MapEntry(k as String, e), ), ); @@ -151,6 +144,6 @@ Map _$EventChannelToJson(EventChannel instance) { writeNotNull('deleted_at', readonly(instance.deletedAt)); writeNotNull('member_count', readonly(instance.memberCount)); writeNotNull('extra_data', instance.extraData); - val['members'] = instance.members?.map((e) => e?.toJson())?.toList(); + val['members'] = instance.members?.map((e) => e.toJson()).toList(); return val; } diff --git a/packages/stream_chat/lib/src/models/member.g.dart b/packages/stream_chat/lib/src/models/member.g.dart index 3ac8778e..52391699 100644 --- a/packages/stream_chat/lib/src/models/member.g.dart +++ b/packages/stream_chat/lib/src/models/member.g.dart @@ -10,7 +10,7 @@ Member _$MemberFromJson(Map json) { return Member( user: json['user'] == null ? null - : User.fromJson((json['user'] as Map)?.map( + : User.fromJson((json['user'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), inviteAcceptedAt: json['invite_accepted_at'] == null @@ -19,18 +19,18 @@ Member _$MemberFromJson(Map json) { inviteRejectedAt: json['invite_rejected_at'] == null ? null : DateTime.parse(json['invite_rejected_at'] as String), - invited: json['invited'] as bool, - role: json['role'] as String, - userId: json['user_id'] as String, - isModerator: json['is_moderator'] as bool, + invited: json['invited'] as bool?, + role: json['role'] as String?, + userId: json['user_id'] as String?, + isModerator: json['is_moderator'] as bool?, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), - banned: json['banned'] as bool, - shadowBanned: json['shadow_banned'] as bool, + banned: json['banned'] as bool?, + shadowBanned: json['shadow_banned'] as bool?, ); } diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index 0df5a7ca..f559ad1e 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -106,11 +106,11 @@ class Message { /// A map describing the count of number of every reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final Map? reactionCounts; + final Map? reactionCounts; /// A map describing the count of score of every reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final Map? reactionScores; + final Map? reactionScores; /// The latest reactions to the message created by any user. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) diff --git a/packages/stream_chat/lib/src/models/message.g.dart b/packages/stream_chat/lib/src/models/message.g.dart index 80df1589..72d2ea1f 100644 --- a/packages/stream_chat/lib/src/models/message.g.dart +++ b/packages/stream_chat/lib/src/models/message.g.dart @@ -8,62 +8,50 @@ part of 'message.dart'; Message _$MessageFromJson(Map json) { return Message( - id: json['id'] as String, - text: json['text'] as String, - type: json['type'] as String, - attachments: (json['attachments'] as List) - ?.map((e) => e == null - ? null - : Attachment.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - mentionedUsers: (json['mentioned_users'] as List) - ?.map((e) => e == null - ? null - : User.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - silent: json['silent'] as bool, - shadowed: json['shadowed'] as bool, - reactionCounts: (json['reaction_counts'] as Map)?.map( + id: json['id'] as String?, + text: json['text'] as String?, + type: json['type'] as String?, + attachments: (json['attachments'] as List?) + ?.map((e) => Attachment.fromJson(Map.from(e as Map))) + .toList(), + mentionedUsers: (json['mentioned_users'] as List?) + ?.map((e) => User.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList(), + silent: json['silent'] as bool?, + shadowed: json['shadowed'] as bool?, + reactionCounts: (json['reaction_counts'] as Map?)?.map( (k, e) => MapEntry(k as String, e as int), ), - reactionScores: (json['reaction_scores'] as Map)?.map( + reactionScores: (json['reaction_scores'] as Map?)?.map( (k, e) => MapEntry(k as String, e as int), ), - latestReactions: (json['latest_reactions'] as List) - ?.map((e) => e == null - ? null - : Reaction.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - ownReactions: (json['own_reactions'] as List) - ?.map((e) => e == null - ? null - : Reaction.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - parentId: json['parent_id'] as String, + latestReactions: (json['latest_reactions'] as List?) + ?.map((e) => Reaction.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList(), + ownReactions: (json['own_reactions'] as List?) + ?.map((e) => Reaction.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList(), + parentId: json['parent_id'] as String?, quotedMessage: json['quoted_message'] == null ? null - : Message.fromJson((json['quoted_message'] as Map)?.map( + : Message.fromJson((json['quoted_message'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - quotedMessageId: json['quoted_message_id'] as String, - replyCount: json['reply_count'] as int, - threadParticipants: (json['thread_participants'] as List) - ?.map((e) => e == null - ? null - : User.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - showInChannel: json['show_in_channel'] as bool, - command: json['command'] as String, + quotedMessageId: json['quoted_message_id'] as String?, + replyCount: json['reply_count'] as int?, + threadParticipants: (json['thread_participants'] as List?) + ?.map((e) => User.fromJson((e as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ))) + .toList(), + showInChannel: json['show_in_channel'] as bool?, + command: json['command'] as String?, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), @@ -72,10 +60,10 @@ Message _$MessageFromJson(Map json) { : DateTime.parse(json['updated_at'] as String), user: json['user'] == null ? null - : User.fromJson((json['user'] as Map)?.map( + : User.fromJson((json['user'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - pinned: json['pinned'] as bool, + pinned: json['pinned'] as bool?, pinnedAt: json['pinned_at'] == null ? null : DateTime.parse(json['pinned_at'] as String), @@ -84,16 +72,16 @@ Message _$MessageFromJson(Map json) { : DateTime.parse(json['pin_expires'] as String), pinnedBy: json['pinned_by'] == null ? null - : User.fromJson((json['pinned_by'] as Map)?.map( + : User.fromJson((json['pinned_by'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - extraData: (json['extra_data'] as Map)?.map( + extraData: (json['extra_data'] as Map?)?.map( (k, e) => MapEntry(k as String, e), ), deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), - skipPush: json['skip_push'] as bool, + skipPush: json['skip_push'] as bool?, ); } @@ -111,7 +99,7 @@ Map _$MessageToJson(Message instance) { writeNotNull('type', readonly(instance.type)); writeNotNull( - 'attachments', instance.attachments?.map((e) => e?.toJson())?.toList()); + 'attachments', instance.attachments?.map((e) => e.toJson()).toList()); val['mentioned_users'] = Serialization.userIds(instance.mentionedUsers); writeNotNull('reaction_counts', readonly(instance.reactionCounts)); writeNotNull('reaction_scores', readonly(instance.reactionScores)); @@ -141,7 +129,7 @@ Map _$MessageToJson(Message instance) { TranslatedMessage _$TranslatedMessageFromJson(Map json) { return TranslatedMessage( - (json['i18n'] as Map)?.map( + (json['i18n'] as Map?)?.map( (k, e) => MapEntry(k as String, e as String), ), ); diff --git a/packages/stream_chat/lib/src/models/mute.g.dart b/packages/stream_chat/lib/src/models/mute.g.dart index 9d0b9318..c8fe5444 100644 --- a/packages/stream_chat/lib/src/models/mute.g.dart +++ b/packages/stream_chat/lib/src/models/mute.g.dart @@ -10,12 +10,12 @@ Mute _$MuteFromJson(Map json) { return Mute( user: json['user'] == null ? null - : User.fromJson((json['user'] as Map)?.map( + : User.fromJson((json['user'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), channel: json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map)?.map( + : ChannelModel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), createdAt: json['created_at'] == null diff --git a/packages/stream_chat/lib/src/models/own_user.g.dart b/packages/stream_chat/lib/src/models/own_user.g.dart index 887e6b28..ce265308 100644 --- a/packages/stream_chat/lib/src/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/models/own_user.g.dart @@ -8,31 +8,19 @@ part of 'own_user.dart'; OwnUser _$OwnUserFromJson(Map json) { return OwnUser( - devices: (json['devices'] as List) - ?.map((e) => e == null - ? null - : Device.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - mutes: (json['mutes'] as List) - ?.map((e) => e == null - ? null - : Mute.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - totalUnreadCount: json['total_unread_count'] as int, - unreadChannels: json['unread_channels'] as int, - channelMutes: (json['channel_mutes'] as List) - ?.map((e) => e == null - ? null - : Mute.fromJson((e as Map)?.map( - (k, e) => MapEntry(k as String, e), - ))) - ?.toList(), - id: json['id'] as String, - role: json['role'] as String, + devices: (json['devices'] as List?) + ?.map((e) => Device.fromJson(Map.from(e as Map))) + .toList(), + mutes: (json['mutes'] as List?) + ?.map((e) => Mute.fromJson(Map.from(e as Map))) + .toList(), + totalUnreadCount: json['total_unread_count'] as int?, + unreadChannels: json['unread_channels'] as int?, + channelMutes: (json['channel_mutes'] as List?) + ?.map((e) => Mute.fromJson(Map.from(e as Map))) + .toList(), + id: json['id'] as String?, + role: json['role'] as String?, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), @@ -42,11 +30,11 @@ OwnUser _$OwnUserFromJson(Map json) { lastActive: json['last_active'] == null ? null : DateTime.parse(json['last_active'] as String), - online: json['online'] as bool, - extraData: (json['extra_data'] as Map)?.map( + online: json['online'] as bool?, + extraData: (json['extra_data'] as Map?)?.map( (k, e) => MapEntry(k as String, e), ), - banned: json['banned'] as bool, + banned: json['banned'] as bool?, ); } diff --git a/packages/stream_chat/lib/src/models/reaction.g.dart b/packages/stream_chat/lib/src/models/reaction.g.dart index a270af01..644c643a 100644 --- a/packages/stream_chat/lib/src/models/reaction.g.dart +++ b/packages/stream_chat/lib/src/models/reaction.g.dart @@ -8,19 +8,19 @@ part of 'reaction.dart'; Reaction _$ReactionFromJson(Map json) { return Reaction( - messageId: json['message_id'] as String, + messageId: json['message_id'] as String?, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), - type: json['type'] as String, + type: json['type'] as String?, user: json['user'] == null ? null - : User.fromJson((json['user'] as Map)?.map( + : User.fromJson((json['user'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - userId: json['user_id'] as String, - score: json['score'] as int, - extraData: (json['extra_data'] as Map)?.map( + userId: json['user_id'] as String?, + score: json['score'] as int?, + extraData: (json['extra_data'] as Map?)?.map( (k, e) => MapEntry(k as String, e), ), ); diff --git a/packages/stream_chat/lib/src/models/read.g.dart b/packages/stream_chat/lib/src/models/read.g.dart index d04ae146..83c822e5 100644 --- a/packages/stream_chat/lib/src/models/read.g.dart +++ b/packages/stream_chat/lib/src/models/read.g.dart @@ -13,10 +13,10 @@ Read _$ReadFromJson(Map json) { : DateTime.parse(json['last_read'] as String), user: json['user'] == null ? null - : User.fromJson((json['user'] as Map)?.map( + : User.fromJson((json['user'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - unreadMessages: json['unread_messages'] as int, + unreadMessages: json['unread_messages'] as int?, ); } diff --git a/packages/stream_chat/lib/src/models/user.g.dart b/packages/stream_chat/lib/src/models/user.g.dart index b27935a7..06989395 100644 --- a/packages/stream_chat/lib/src/models/user.g.dart +++ b/packages/stream_chat/lib/src/models/user.g.dart @@ -8,8 +8,8 @@ part of 'user.dart'; User _$UserFromJson(Map json) { return User( - id: json['id'] as String, - role: json['role'] as String, + id: json['id'] as String?, + role: json['role'] as String?, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), @@ -19,12 +19,12 @@ User _$UserFromJson(Map json) { lastActive: json['last_active'] == null ? null : DateTime.parse(json['last_active'] as String), - online: json['online'] as bool, - extraData: (json['extra_data'] as Map)?.map( + online: json['online'] as bool?, + extraData: (json['extra_data'] as Map?)?.map( (k, e) => MapEntry(k as String, e), ), - banned: json['banned'] as bool, - teams: (json['teams'] as List)?.map((e) => e as String)?.toList(), + banned: json['banned'] as bool?, + teams: (json['teams'] as List?)?.map((e) => e as String).toList(), ); } From 686dcf27e6d456f5d6057a50f814fca924dd928b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 8 Apr 2021 16:23:16 +0530 Subject: [PATCH 081/599] regenerated files and fixed errors --- packages/stream_chat/lib/src/api/channel.dart | 8 ++++++-- packages/stream_chat/lib/src/models/message.dart | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 3b6848f6..7606bc9a 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -643,11 +643,15 @@ class Channel { final reactionCounts = {...message.reactionCounts ?? {}}; if (reactionCounts.containsKey(type)) { - reactionCounts.update(type, (value) => value - 1); + if (type != null) { + reactionCounts.update(type, (value) => value - 1); + } } final reactionScores = {...message.reactionScores ?? {}}; if (reactionScores.containsKey(type)) { - reactionScores.update(type, (value) => value - 1); + if (type != null) { + reactionScores.update(type, (value) => value - 1); + } } final latestReactions = [...message.latestReactions ?? []] diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index f559ad1e..6c793f63 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -244,8 +244,8 @@ class Message { String? type, List? attachments, List? mentionedUsers, - Map? reactionCounts, - Map? reactionScores, + Map? reactionCounts, + Map? reactionScores, List? latestReactions, List? ownReactions, String? parentId, From a42b3de6f401645a6a9b40428b60d276e4d5ac44 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 8 Apr 2021 21:13:08 +0530 Subject: [PATCH 082/599] test: add tests for stream chat flutter (#335) * feat: Added tests * feat: Added header test * feat: Added new tests * fix * feat: Added test, fmt * feat: Added delete test * added system and unread ind tests * fix: analysis * added new tests * fix: analysis * added tests * add attachment actions modal tests * fix analysis * fix analysis * add backbutton tests * fix analysis * increase timeout * add channelheader tests * add infotile tests * add reactions modal and channel unread indicator tests * add golden test for reaction bubble * add dark tests * add system message golden * add deletd message golden * add message action modal tests * update goldens * update workflow runner * update goldens * remove channel unread indicator in favor of unread indicator * move file and media screen to sample app * add channel image tesst * add channel_list_header test * add thread_header test * bump up test threashold * fix review Co-authored-by: Salvatore Giordano --- .github/workflows/stream_flutter_workflow.yml | 4 +- .../lib/src/models/attachment.dart | 85 +-- .../stream_chat/lib/src/models/message.dart | 36 +- packages/stream_chat/lib/stream_chat.dart | 1 + packages/stream_chat/pubspec.yaml | 1 + packages/stream_chat_flutter/.gitignore | 5 +- .../lib/src/attachment_actions_modal.dart | 24 +- .../lib/src/channel_file_display_screen.dart | 182 ----- .../lib/src/channel_image.dart | 259 ++++--- .../lib/src/channel_media_display_screen.dart | 251 ------- .../lib/src/channel_preview.dart | 28 +- .../lib/src/channel_unread_indicator.dart | 49 -- .../lib/src/info_tile.dart | 17 +- .../lib/src/message_actions_modal.dart | 14 +- .../lib/src/message_input.dart | 12 +- .../lib/src/message_reactions_modal.dart | 4 - .../lib/src/message_widget.dart | 5 +- .../lib/src/reaction_bubble.dart | 114 +-- .../lib/src/unread_indicator.dart | 2 +- .../lib/stream_chat_flutter.dart | 27 +- packages/stream_chat_flutter/pubspec.yaml | 5 +- .../test/flutter_test_config.dart | 9 + .../src/attachment_actions_modal_test.dart | 510 +++++++++++++ .../test/src/attachment_widgets_test.dart | 50 ++ .../test/src/back_button_test.dart | 144 ++++ .../test/src/channel_header_test.dart | 388 ++++++++++ .../test/src/channel_image_test.dart | 263 +++++++ .../test/src/channel_list_header_test.dart | 207 ++++++ .../test/src/channel_name_test.dart | 73 ++ .../test/src/channel_preview_test.dart | 4 + .../test/src/date_divider_test.dart | 32 + .../test/src/deleted_message_test.dart | 201 +++++ .../test/src/full_screen_media_test.dart | 91 +++ .../src/goldens/deleted_message_custom.png | Bin 0 -> 2936 bytes .../test/src/goldens/deleted_message_dark.png | Bin 0 -> 2785 bytes .../src/goldens/deleted_message_light.png | Bin 0 -> 2750 bytes .../test/src/goldens/reaction_bubble_0.png | Bin 0 -> 410 bytes .../test/src/goldens/reaction_bubble_2.png | Bin 0 -> 3339 bytes .../src/goldens/reaction_bubble_3_dark.png | Bin 0 -> 1976 bytes .../src/goldens/reaction_bubble_3_light.png | Bin 0 -> 2067 bytes .../src/goldens/reaction_bubble_like_dark.png | Bin 0 -> 1494 bytes .../goldens/reaction_bubble_like_light.png | Bin 0 -> 1291 bytes .../test/src/goldens/system_message_dark.png | Bin 0 -> 1812 bytes .../test/src/goldens/system_message_light.png | Bin 0 -> 1866 bytes .../test/src/image_footer_test.dart | 50 ++ .../test/src/info_tile_test.dart | 69 ++ .../test/src/message_action_modal_test.dart | 701 +++++++++++++++++- .../test/src/message_input_test.dart | 81 ++ .../test/src/message_reaction_modal_test.dart | 102 --- .../src/message_reactions_modal_test.dart | 148 ++++ .../test/src/message_text_test.dart | 54 ++ .../stream_chat_flutter/test/src/mocks.dart | 7 + .../test/src/reaction_bubble_test.dart | 270 +++++-- .../test/src/simple_frame.dart | 19 + .../test/src/system_message_test.dart | 164 ++++ .../test/src/thread_header_test.dart | 140 ++++ .../test/src/typing_indicator_test.dart | 79 ++ .../test/src/unread_indicator_test.dart | 127 ++++ 58 files changed, 4140 insertions(+), 968 deletions(-) delete mode 100644 packages/stream_chat_flutter/lib/src/channel_file_display_screen.dart delete mode 100644 packages/stream_chat_flutter/lib/src/channel_media_display_screen.dart delete mode 100644 packages/stream_chat_flutter/lib/src/channel_unread_indicator.dart create mode 100644 packages/stream_chat_flutter/test/flutter_test_config.dart create mode 100644 packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart create mode 100644 packages/stream_chat_flutter/test/src/attachment_widgets_test.dart create mode 100644 packages/stream_chat_flutter/test/src/back_button_test.dart create mode 100644 packages/stream_chat_flutter/test/src/channel_header_test.dart create mode 100644 packages/stream_chat_flutter/test/src/channel_image_test.dart create mode 100644 packages/stream_chat_flutter/test/src/channel_list_header_test.dart create mode 100644 packages/stream_chat_flutter/test/src/channel_name_test.dart create mode 100644 packages/stream_chat_flutter/test/src/date_divider_test.dart create mode 100644 packages/stream_chat_flutter/test/src/deleted_message_test.dart create mode 100644 packages/stream_chat_flutter/test/src/full_screen_media_test.dart create mode 100644 packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png create mode 100644 packages/stream_chat_flutter/test/src/goldens/deleted_message_dark.png create mode 100644 packages/stream_chat_flutter/test/src/goldens/deleted_message_light.png create mode 100644 packages/stream_chat_flutter/test/src/goldens/reaction_bubble_0.png create mode 100644 packages/stream_chat_flutter/test/src/goldens/reaction_bubble_2.png create mode 100644 packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png create mode 100644 packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png create mode 100644 packages/stream_chat_flutter/test/src/goldens/reaction_bubble_like_dark.png create mode 100644 packages/stream_chat_flutter/test/src/goldens/reaction_bubble_like_light.png create mode 100644 packages/stream_chat_flutter/test/src/goldens/system_message_dark.png create mode 100644 packages/stream_chat_flutter/test/src/goldens/system_message_light.png create mode 100644 packages/stream_chat_flutter/test/src/image_footer_test.dart create mode 100644 packages/stream_chat_flutter/test/src/info_tile_test.dart create mode 100644 packages/stream_chat_flutter/test/src/message_input_test.dart delete mode 100644 packages/stream_chat_flutter/test/src/message_reaction_modal_test.dart create mode 100644 packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart create mode 100644 packages/stream_chat_flutter/test/src/message_text_test.dart create mode 100644 packages/stream_chat_flutter/test/src/simple_frame.dart create mode 100644 packages/stream_chat_flutter/test/src/system_message_test.dart create mode 100644 packages/stream_chat_flutter/test/src/thread_header_test.dart create mode 100644 packages/stream_chat_flutter/test/src/typing_indicator_test.dart create mode 100644 packages/stream_chat_flutter/test/src/unread_indicator_test.dart diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index aa621854..7b5df2ba 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -58,7 +58,7 @@ jobs: ./.github/workflows/scripts/validate-formatting.sh test: - runs-on: ubuntu-latest + runs-on: macos-latest timeout-minutes: 15 steps: - uses: actions/checkout@v2 @@ -102,4 +102,4 @@ jobs: - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_flutter/coverage/lcov.info - min_coverage: 16 + min_coverage: 35 diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/models/attachment.dart index de5149db..548a3e6d 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/models/attachment.dart @@ -1,5 +1,6 @@ // ignore_for_file: public_member_api_docs +import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/models/action.dart'; import 'package:stream_chat/src/models/attachment_file.dart'; @@ -10,7 +11,7 @@ part 'attachment.g.dart'; /// The class that contains the information about an attachment @JsonSerializable(includeIfNull: false) -class Attachment { +class Attachment extends Equatable { /// Constructor used for json serialization Attachment({ String id, @@ -37,12 +38,11 @@ class Attachment { UploadState uploadState, }) : id = id ?? Uuid().v4(), title = title ?? file?.name, - localUri = file?.path != null ? Uri.parse(file.path) : null { - this.uploadState = uploadState ?? - ((assetUrl != null || imageUrl != null) - ? const UploadState.success() - : const UploadState.preparing()); - } + localUri = file?.path != null ? Uri.parse(file.path) : null, + uploadState = uploadState ?? + ((assetUrl != null || imageUrl != null) + ? const UploadState.success() + : const UploadState.preparing()); /// Create a new instance from a json factory Attachment.fromJson(Map json) => @@ -104,7 +104,7 @@ class Attachment { final AttachmentFile file; /// The current upload state of the attachment - UploadState uploadState; + final UploadState uploadState; /// Map of custom channel extraData @JsonKey(includeIfNull: false) @@ -205,49 +205,28 @@ class Attachment { ); @override - bool operator ==(Object other) => - identical(this, other) || - other is Attachment && - runtimeType == other.runtimeType && - type == other.type && - titleLink == other.titleLink && - title == other.title && - thumbUrl == other.thumbUrl && - text == other.text && - pretext == other.pretext && - ogScrapeUrl == other.ogScrapeUrl && - imageUrl == other.imageUrl && - footerIcon == other.footerIcon && - footer == other.footer && - fields == other.fields && - fallback == other.fallback && - color == other.color && - authorName == other.authorName && - authorLink == other.authorLink && - authorIcon == other.authorIcon && - assetUrl == other.assetUrl && - actions == other.actions && - extraData == other.extraData; - - @override - int get hashCode => - type.hashCode ^ - titleLink.hashCode ^ - title.hashCode ^ - thumbUrl.hashCode ^ - text.hashCode ^ - pretext.hashCode ^ - ogScrapeUrl.hashCode ^ - imageUrl.hashCode ^ - footerIcon.hashCode ^ - footer.hashCode ^ - fields.hashCode ^ - fallback.hashCode ^ - color.hashCode ^ - authorName.hashCode ^ - authorLink.hashCode ^ - authorIcon.hashCode ^ - assetUrl.hashCode ^ - actions.hashCode ^ - extraData.hashCode; + List get props => [ + id, + type, + titleLink, + title, + thumbUrl, + text, + pretext, + ogScrapeUrl, + imageUrl, + footerIcon, + footer, + fields, + fallback, + color, + authorName, + authorLink, + authorIcon, + assetUrl, + actions, + file, + uploadState, + extraData, + ]; } diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index a89324a7..28790b15 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -1,3 +1,4 @@ +import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/models/attachment.dart'; import 'package:stream_chat/src/models/reaction.dart'; @@ -41,7 +42,7 @@ enum MessageSendingStatus { /// The class that contains the information about a message @JsonSerializable() -class Message { +class Message extends Equatable { /// Constructor used for json serialization Message({ String id, @@ -345,6 +346,39 @@ class Message { pinnedBy: other.pinnedBy, ); } + + @override + List get props => [ + id, + text, + type, + attachments, + mentionedUsers, + reactionCounts, + reactionScores, + latestReactions, + ownReactions, + parentId, + quotedMessage, + quotedMessageId, + replyCount, + threadParticipants, + showInChannel, + shadowed, + silent, + command, + createdAt, + updatedAt, + deletedAt, + user, + pinned, + pinnedAt, + pinExpires, + pinnedBy, + extraData, + status, + skipPush, + ]; } /// A translated message diff --git a/packages/stream_chat/lib/stream_chat.dart b/packages/stream_chat/lib/stream_chat.dart index 0e1c9ed2..f491c39a 100644 --- a/packages/stream_chat/lib/stream_chat.dart +++ b/packages/stream_chat/lib/stream_chat.dart @@ -15,6 +15,7 @@ export './src/attachment_file_uploader.dart' show AttachmentFileUploader; export './src/client.dart'; export './src/db/chat_persistence_client.dart'; export './src/event_type.dart'; +export './src/exceptions.dart'; export './src/extensions/rate_limit.dart'; export './src/extensions/string_extension.dart'; export './src/models/action.dart'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 24298ae1..7343b721 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: async: ^2.4.2 collection: ^1.14.13 dio: ^3.0.10 + equatable: ^2.0.0 freezed_annotation: ^0.12.0 http_parser: ^3.1.4 json_annotation: ^3.0.1 diff --git a/packages/stream_chat_flutter/.gitignore b/packages/stream_chat_flutter/.gitignore index 1d3bae26..fe82edc9 100644 --- a/packages/stream_chat_flutter/.gitignore +++ b/packages/stream_chat_flutter/.gitignore @@ -62,4 +62,7 @@ doc/api/ fvm google-services.json example/ios/dist -.vscode/ \ No newline at end of file +.vscode/ + +# don't check in golden failure output +**/failures/*.png \ No newline at end of file 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 d9b9c016..0c8eac7d 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart @@ -34,12 +34,12 @@ class AttachmentActionsModal extends StatelessWidget { /// Returns a new [AttachmentActionsModal] const AttachmentActionsModal({ + @required this.currentIndex, this.message, - this.currentIndex, this.onShowMessage, this.imageDownloader, this.fileDownloader, - }); + }) : assert(currentIndex != null, 'currentIndex cannot be null'); @override Widget build(BuildContext context) { @@ -145,19 +145,20 @@ class AttachmentActionsModal extends StatelessWidget { () { final channel = StreamChannel.of(context).channel; if (message.attachments.length > 1 || - message.text.isNotEmpty) { + message.text?.isNotEmpty == true) { final remainingAttachments = [...message.attachments] ..removeAt(currentIndex); channel.updateMessage(message.copyWith( attachments: remainingAttachments, )); - Navigator.pop(context); - Navigator.pop(context); + Navigator.of(context) + ..pop() + ..maybePop(); } else { - channel.deleteMessage(message).then((value) { - Navigator.pop(context); - Navigator.pop(context); - }); + channel.deleteMessage(message); + Navigator.of(context) + ..pop() + ..maybePop(); } }, color: theme.colorTheme.accentRed, @@ -185,8 +186,10 @@ class AttachmentActionsModal extends StatelessWidget { StreamSvgIcon icon, VoidCallback onTap, { Color color, + Key key, }) { return Material( + key: key, color: StreamChatTheme.of(context).colorTheme.white, child: InkWell( onTap: onTap, @@ -224,7 +227,7 @@ class AttachmentActionsModal extends StatelessWidget { if (progress == null || progress?.toProgressIndicatorValue == 1.0) { Future.delayed( const Duration(milliseconds: 500), - Navigator.of(context).pop, + Navigator.of(context).maybePop, ); } return Material( @@ -248,6 +251,7 @@ class AttachmentActionsModal extends StatelessWidget { ) : progress.toProgressIndicatorValue == 1.0 ? Container( + key: Key('completedIcon'), height: 160, width: 160, child: StreamSvgIcon.check( diff --git a/packages/stream_chat_flutter/lib/src/channel_file_display_screen.dart b/packages/stream_chat_flutter/lib/src/channel_file_display_screen.dart deleted file mode 100644 index 083b238b..00000000 --- a/packages/stream_chat_flutter/lib/src/channel_file_display_screen.dart +++ /dev/null @@ -1,182 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import 'attachment/attachment.dart'; - -class ChannelFileDisplayScreen extends StatefulWidget { - /// The sorting used for the channels matching the filters. - /// Sorting is based on field and direction, multiple sorting options can be provided. - /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. - /// Direction can be ascending or descending. - final List sortOptions; - - /// Pagination parameters - /// limit: the number of users to return (max is 30) - /// offset: the offset (max is 1000) - /// message_limit: how many messages should be included to each channel - final PaginationParams paginationParams; - - /// The builder used when the file list is empty. - final WidgetBuilder emptyBuilder; - - const ChannelFileDisplayScreen({ - this.sortOptions, - this.paginationParams, - this.emptyBuilder, - }); - - @override - _ChannelFileDisplayScreenState createState() => - _ChannelFileDisplayScreenState(); -} - -class _ChannelFileDisplayScreenState extends State { - @override - void initState() { - super.initState(); - final messageSearchBloc = MessageSearchBloc.of(context); - messageSearchBloc.search( - filter: { - 'cid': { - r'$in': [StreamChannel.of(context).channel.cid] - } - }, - messageFilter: { - 'attachments.type': { - r'$in': ['file'], - }, - }, - sort: widget.sortOptions, - pagination: widget.paginationParams, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, - appBar: AppBar( - brightness: Theme.of(context).brightness, - elevation: 1, - centerTitle: true, - title: Text( - 'Files', - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.black, - fontSize: 16.0), - ), - leading: Center( - child: InkWell( - onTap: () { - Navigator.of(context).pop(); - }, - child: Container( - width: 24.0, - height: 24.0, - child: StreamSvgIcon.left( - color: StreamChatTheme.of(context).colorTheme.black, - size: 24.0, - ), - ), - ), - ), - backgroundColor: StreamChatTheme.of(context).colorTheme.white, - ), - body: _buildMediaGrid(), - ); - } - - Widget _buildMediaGrid() { - final messageSearchBloc = MessageSearchBloc.of(context); - - return StreamBuilder>( - builder: (context, snapshot) { - if (snapshot.data == null) { - return Center( - child: const CircularProgressIndicator(), - ); - } - - if (snapshot.data.isEmpty) { - if (widget.emptyBuilder != null) { - return widget.emptyBuilder(context); - } - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - StreamSvgIcon.files( - size: 136.0, - color: StreamChatTheme.of(context).colorTheme.greyGainsboro, - ), - SizedBox(height: 16.0), - Text( - 'No Files', - style: TextStyle( - fontSize: 14.0, - color: StreamChatTheme.of(context).colorTheme.black, - ), - ), - SizedBox(height: 8.0), - Text( - 'Files sent in this chat will appear here', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14.0, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), - ), - ), - ], - ), - ); - } - - final media = {}; - - for (var item in snapshot.data) { - item.message.attachments.where((e) => e.type == 'file').forEach((e) { - media[e] = item.message; - }); - } - - return LazyLoadScrollView( - onEndOfPage: () => messageSearchBloc.search( - filter: { - 'cid': { - r'$in': [StreamChannel.of(context).channel.cid] - } - }, - messageFilter: { - 'attachments.type': { - r'$in': ['file'] - }, - }, - sort: widget.sortOptions, - pagination: widget.paginationParams.copyWith( - offset: messageSearchBloc.messageResponses?.length ?? 0, - ), - ), - child: ListView.builder( - itemBuilder: (context, position) { - return Padding( - padding: const EdgeInsets.all(1.0), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: FileAttachment( - message: media.values.toList()[position], - attachment: media.keys.toList()[position], - ), - ), - ); - }, - itemCount: media.length, - ), - ); - }, - stream: messageSearchBloc.messagesStream, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index e7e39d38..4b3edce3 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -50,7 +50,6 @@ class ChannelImage extends StatelessWidget { this.channel, this.constraints, this.onTap, - this.showOnlineStatus = true, this.borderRadius, this.selected = false, this.selectionColor, @@ -68,8 +67,6 @@ class ChannelImage extends StatelessWidget { /// The function called when the image is tapped final VoidCallback onTap; - final bool showOnlineStatus; - final bool selected; final Color selectionColor; @@ -81,142 +78,144 @@ class ChannelImage extends StatelessWidget { final streamChat = StreamChat.of(context); final channel = this.channel ?? StreamChannel.of(context).channel; return StreamBuilder>( - stream: channel.extraDataStream, - initialData: channel.extraData, - builder: (context, snapshot) { - String image; - if (snapshot.data?.containsKey('image') == true) { - image = snapshot.data['image']; - } else if (channel.state.members?.length == 2) { - final otherMember = channel.state.members - .firstWhere((member) => member.user.id != streamChat.user.id); - return StreamBuilder( - stream: streamChat.client.state.usersStream - .map((users) => users[otherMember.userId]), - initialData: otherMember.user, - builder: (context, snapshot) { - return UserAvatar( - borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - .borderRadius, - user: snapshot.data ?? otherMember.user, - constraints: constraints ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - .constraints, - onTap: onTap != null ? (_) => onTap() : null, - selected: selected, - selectionColor: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, - selectionThickness: selectionThickness, - ); - }); - } else { - final images = channel.state.members - .where((member) => - member.user.id != streamChat.user.id && - member.user.extraData['image'] != null) - .take(4) - .map((e) => e.user.extraData['image'] as String) - .toList(); - return GroupImage( - images: images, - borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - .borderRadius, - constraints: constraints ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - .constraints, - onTap: onTap, - selected: selected, - selectionColor: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, - selectionThickness: selectionThickness, - ); - } - - Widget child = ClipRRect( + stream: channel.extraDataStream, + initialData: channel.extraData, + builder: (context, snapshot) { + String image; + if (snapshot.data?.containsKey('image') == true) { + image = snapshot.data['image']; + } else if (channel.state.members?.length == 2) { + final otherMember = channel.state.members + .firstWhere((member) => member.user.id != streamChat.user.id); + return StreamBuilder( + stream: streamChat.client.state.usersStream + .map((users) => users[otherMember.userId]), + initialData: otherMember.user, + builder: (context, snapshot) { + return UserAvatar( + borderRadius: borderRadius ?? + StreamChatTheme.of(context) + .channelPreviewTheme + .avatarTheme + .borderRadius, + user: snapshot.data ?? otherMember.user, + constraints: constraints ?? + StreamChatTheme.of(context) + .channelPreviewTheme + .avatarTheme + .constraints, + onTap: onTap != null ? (_) => onTap() : null, + selected: selected, + selectionColor: selectionColor ?? + StreamChatTheme.of(context).colorTheme.accentBlue, + selectionThickness: selectionThickness, + ); + }); + } else { + final images = channel.state.members + .where((member) => + member.user.id != streamChat.user.id && + member.user.extraData['image'] != null) + .take(4) + .map((e) => e.user.extraData['image'] as String) + .toList(); + return GroupImage( + images: images, borderRadius: borderRadius ?? StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme .borderRadius, - child: Container( - constraints: constraints ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - .constraints, - decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ), - child: Stack( - alignment: Alignment.center, - fit: StackFit.expand, - children: [ - image != null - ? CachedNetworkImage( - imageUrl: image, - errorWidget: (_, __, ___) { - return Center( - child: Text( - snapshot.data?.containsKey('name') ?? false - ? snapshot.data['name'][0] - : '', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .white, - fontWeight: FontWeight.bold, - ), - ), - ); - }, - fit: BoxFit.cover, - ) - : StreamChatTheme.of(context) - .defaultChannelImage(context, channel), - Material( - color: Colors.transparent, - child: InkWell( - onTap: onTap, - ), - ), - ], - ), - ), + constraints: constraints ?? + StreamChatTheme.of(context) + .channelPreviewTheme + .avatarTheme + .constraints, + onTap: onTap, + selected: selected, + selectionColor: selectionColor ?? + StreamChatTheme.of(context).colorTheme.accentBlue, + selectionThickness: selectionThickness, ); - if (selected) { - child = ClipRRect( - borderRadius: (borderRadius ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - .borderRadius) + - BorderRadius.circular(selectionThickness), - child: Container( - constraints: constraints ?? + } + + Widget child = ClipRRect( + borderRadius: borderRadius ?? + StreamChatTheme.of(context) + .channelPreviewTheme + .avatarTheme + .borderRadius, + child: Container( + constraints: constraints ?? + StreamChatTheme.of(context) + .channelPreviewTheme + .avatarTheme + .constraints, + decoration: BoxDecoration( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + ), + child: Stack( + alignment: Alignment.center, + fit: StackFit.expand, + children: [ + image != null + ? CachedNetworkImage( + imageUrl: image, + errorWidget: (_, __, ___) { + return Center( + child: Text( + snapshot.data?.containsKey('name') ?? false + ? snapshot.data['name'][0] + : '', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .white, + fontWeight: FontWeight.bold, + ), + ), + ); + }, + fit: BoxFit.cover, + ) + : StreamChatTheme.of(context) + .defaultChannelImage(context, channel), + Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + ), + ), + ], + ), + ), + ); + if (selected) { + child = ClipRRect( + key: Key('selectedImage'), + borderRadius: (borderRadius ?? StreamChatTheme.of(context) .ownMessageTheme .avatarTheme - .constraints, - color: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, - child: Padding( - padding: EdgeInsets.all(selectionThickness), - child: child, - ), + .borderRadius) + + BorderRadius.circular(selectionThickness), + child: Container( + constraints: constraints ?? + StreamChatTheme.of(context) + .ownMessageTheme + .avatarTheme + .constraints, + color: selectionColor ?? + StreamChatTheme.of(context).colorTheme.accentBlue, + child: Padding( + padding: EdgeInsets.all(selectionThickness), + child: child, ), - ); - } - return child; - }); + ), + ); + } + return child; + }, + ); } } diff --git a/packages/stream_chat_flutter/lib/src/channel_media_display_screen.dart b/packages/stream_chat_flutter/lib/src/channel_media_display_screen.dart deleted file mode 100644 index 7b34e449..00000000 --- a/packages/stream_chat_flutter/lib/src/channel_media_display_screen.dart +++ /dev/null @@ -1,251 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:video_player/video_player.dart'; - -import 'attachment/attachment.dart'; - -class ChannelMediaDisplayScreen extends StatefulWidget { - /// The sorting used for the channels matching the filters. - /// Sorting is based on field and direction, multiple sorting options can be provided. - /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. - /// Direction can be ascending or descending. - final List sortOptions; - - /// Pagination parameters - /// limit: the number of users to return (max is 30) - /// offset: the offset (max is 1000) - /// message_limit: how many messages should be included to each channel - final PaginationParams paginationParams; - - /// The builder used when the file list is empty. - final WidgetBuilder emptyBuilder; - - final ShowMessageCallback onShowMessage; - - const ChannelMediaDisplayScreen({ - this.sortOptions, - this.paginationParams, - this.emptyBuilder, - this.onShowMessage, - }); - - @override - _ChannelMediaDisplayScreenState createState() => - _ChannelMediaDisplayScreenState(); -} - -class _ChannelMediaDisplayScreenState extends State { - Map controllerCache = {}; - - @override - void initState() { - super.initState(); - final messageSearchBloc = MessageSearchBloc.of(context); - messageSearchBloc.search( - filter: { - 'cid': { - r'$in': [StreamChannel.of(context).channel.cid], - } - }, - messageFilter: { - 'attachments.type': { - r'$in': ['image', 'video'] - }, - }, - sort: widget.sortOptions, - pagination: widget.paginationParams, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, - appBar: AppBar( - brightness: Theme.of(context).brightness, - elevation: 1, - centerTitle: true, - title: Text( - 'Photos & Videos', - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.black, - fontSize: 16.0, - ), - ), - leading: Center( - child: InkWell( - onTap: () { - Navigator.of(context).pop(); - }, - child: Container( - width: 24.0, - height: 24.0, - child: StreamSvgIcon.left( - color: StreamChatTheme.of(context).colorTheme.black, - size: 24.0, - ), - ), - ), - ), - backgroundColor: StreamChatTheme.of(context).colorTheme.white, - ), - body: _buildMediaGrid(), - ); - } - - Widget _buildMediaGrid() { - final messageSearchBloc = MessageSearchBloc.of(context); - - return StreamBuilder>( - builder: (context, snapshot) { - if (snapshot.data == null) { - return Center( - child: const CircularProgressIndicator(), - ); - } - - if (snapshot.data.isEmpty) { - if (widget.emptyBuilder != null) { - return widget.emptyBuilder(context); - } - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - StreamSvgIcon.pictures( - size: 136.0, - color: StreamChatTheme.of(context).colorTheme.greyGainsboro, - ), - SizedBox(height: 16.0), - Text( - 'No Media', - style: TextStyle( - fontSize: 14.0, - color: StreamChatTheme.of(context).colorTheme.black, - ), - ), - SizedBox(height: 8.0), - Text( - 'Photos or video sent in this chat will \nappear here', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14.0, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), - ), - ), - ], - ), - ); - } - - final media = <_AssetPackage>[]; - - for (var item in snapshot.data) { - item.message.attachments - .where((e) => - (e.type == 'image' || e.type == 'video') && - e.ogScrapeUrl == null) - .forEach((e) { - VideoPlayerController controller; - if (e.type == 'video') { - var cachedController = controllerCache[e.assetUrl]; - - if (cachedController == null) { - controller = VideoPlayerController.network(e.assetUrl); - controller.initialize(); - controllerCache[e.assetUrl] = controller; - } else { - controller = cachedController; - } - } - media.add(_AssetPackage(e, item.message, controller)); - }); - } - - return LazyLoadScrollView( - onEndOfPage: () => messageSearchBloc.search( - filter: { - 'cid': { - r'$in': [StreamChannel.of(context).channel.cid] - } - }, - messageFilter: { - 'attachments.type': { - r'$in': ['image', 'video'] - }, - }, - sort: widget.sortOptions, - pagination: widget.paginationParams.copyWith( - offset: messageSearchBloc.messageResponses?.length ?? 0, - ), - ), - child: GridView.builder( - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), - itemBuilder: (context, position) { - var channel = StreamChannel.of(context).channel; - return Padding( - padding: const EdgeInsets.all(1.0), - child: InkWell( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: FullScreenMedia( - mediaAttachments: - media.map((e) => e.attachment).toList(), - startIndex: position, - message: media[position].message, - sentAt: media[position].message.createdAt, - userName: media[position].message.user.name, - onShowMessage: widget.onShowMessage, - ), - ), - ), - ); - }, - child: media[position].attachment.type == 'image' - ? IgnorePointer( - child: ImageAttachment( - attachment: media[position].attachment, - message: media[position].message, - showTitle: false, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), - ), - ) - : VideoPlayer(media[position].videoPlayer), - ), - ); - }, - itemCount: media.length, - ), - ); - }, - stream: messageSearchBloc.messagesStream, - ); - } - - @override - void dispose() { - super.dispose(); - for (var c in controllerCache.values) { - c.dispose(); - } - } -} - -class _AssetPackage { - Attachment attachment; - Message message; - VideoPlayerController videoPlayer; - - _AssetPackage(this.attachment, this.message, this.videoPlayer); -} diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 3243988b..959342a6 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -6,7 +6,6 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import '../stream_chat_flutter.dart'; import 'channel_name.dart'; -import 'channel_unread_indicator.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png) @@ -98,19 +97,20 @@ class ChannelPreview extends StatelessWidget { ), ), StreamBuilder>( - stream: channel.state.membersStream, - initialData: channel.state.members, - builder: (context, snapshot) { - if (!snapshot.hasData || - snapshot.data.isEmpty || - !snapshot.data.any((Member e) => - e.user.id == channel.client.state.user.id)) { - return SizedBox(); - } - return ChannelUnreadIndicator( - channel: channel, - ); - }), + stream: channel.state.membersStream, + initialData: channel.state.members, + builder: (context, snapshot) { + if (!snapshot.hasData || + snapshot.data.isEmpty || + !snapshot.data.any((Member e) => + e.user.id == channel.client.state.user.id)) { + return SizedBox(); + } + return UnreadIndicator( + cid: channel.cid, + ); + }, + ), ], ), subtitle: Row( diff --git a/packages/stream_chat_flutter/lib/src/channel_unread_indicator.dart b/packages/stream_chat_flutter/lib/src/channel_unread_indicator.dart deleted file mode 100644 index cb9dc362..00000000 --- a/packages/stream_chat_flutter/lib/src/channel_unread_indicator.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; - -class ChannelUnreadIndicator extends StatelessWidget { - const ChannelUnreadIndicator({ - Key key, - @required this.channel, - }) : super(key: key); - - final Channel channel; - - @override - Widget build(BuildContext context) { - return StreamBuilder( - stream: channel.state.unreadCountStream, - initialData: channel.state.unreadCount, - builder: (context, snapshot) { - if (!snapshot.hasData || snapshot.data == 0) { - return SizedBox(); - } - - return Material( - borderRadius: BorderRadius.circular(8), - color: StreamChatTheme.of(context) - .channelPreviewTheme - .unreadCounterColor, - child: Padding( - padding: const EdgeInsets.only( - left: 5.0, - right: 5.0, - top: 2, - bottom: 1, - ), - child: Center( - child: Text( - '${snapshot.data > 99 ? '99+' : snapshot.data}', - style: TextStyle( - fontSize: 11, - color: Colors.white, - ), - ), - ), - ), - ); - }, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/info_tile.dart b/packages/stream_chat_flutter/lib/src/info_tile.dart index 2e12d6b3..72a077cc 100644 --- a/packages/stream_chat_flutter/lib/src/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/info_tile.dart @@ -11,14 +11,15 @@ class InfoTile extends StatelessWidget { final TextStyle textStyle; final Color backgroundColor; - InfoTile( - {this.message, - this.child, - this.showMessage, - this.tileAnchor, - this.childAnchor, - this.textStyle, - this.backgroundColor}); + InfoTile({ + this.message, + this.child, + this.showMessage, + this.tileAnchor, + this.childAnchor, + this.textStyle, + this.backgroundColor, + }); @override Widget build(BuildContext context) { 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 c997d09c..0fdabf7d 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -3,7 +3,6 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:stream_chat_flutter/src/message_action.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; @@ -18,11 +17,12 @@ import 'stream_chat_theme.dart'; class MessageActionsModal extends StatefulWidget { final Widget Function(BuildContext, Message) editMessageInputBuilder; - final void Function(Message) onThreadReplyTap; - final void Function(Message) onReplyTap; + final OnMessageTap onThreadReplyTap; + final OnMessageTap onReplyTap; final Message message; final MessageTheme messageTheme; final bool showReactions; + final OnMessageTap onCopyTap; final bool showDeleteMessage; final bool showCopyMessage; final bool showEditMessage; @@ -60,6 +60,7 @@ class MessageActionsModal extends StatefulWidget { this.reverse = false, this.customActions = const [], this.attachmentBorderRadiusGeometry, + this.onCopyTap, }) : super(key: key); @override @@ -211,10 +212,9 @@ class _MessageActionsModalState extends State { CrossAxisAlignment.stretch, children: [ if (widget.showReplyMessage && - (widget.message.status == + widget.message.status == MessageSendingStatus.sent || - widget.message.status == null) && - widget.message.parentId == null) + widget.message.status == null) _buildReplyButton(context), if (widget.showThreadReplyMessage && (widget.message.status == @@ -452,7 +452,7 @@ class _MessageActionsModalState extends State { Widget _buildCopyButton(BuildContext context) { return InkWell( onTap: () async { - await Clipboard.setData(ClipboardData(text: widget.message.text)); + widget.onCopyTap?.call(widget.message); Navigator.pop(context); }, child: Padding( diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 385dc41f..b4b2176c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -693,7 +693,7 @@ class MessageInputState extends State { if (!mounted) { return; } - StreamChannel.of(context).channel.keyStroke().catchError((e) {}); + StreamChannel.of(context).channel.keyStroke()?.catchError((e) {}); setState(() { _messageIsPresent = s.trim().isNotEmpty; @@ -2181,10 +2181,12 @@ class MessageInputState extends State { void _parseExistingMessage(Message message) { textEditingController.text = message.text; _messageIsPresent = true; - for (final attachment in message?.attachments) { - _attachments[attachment.id] = attachment.copyWith( - uploadState: attachment.uploadState ?? UploadState.success(), - ); + if (message.attachments != null) { + for (final attachment in message.attachments) { + _attachments[attachment.id] = attachment.copyWith( + uploadState: attachment.uploadState ?? UploadState.success(), + ); + } } } 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 c2a13ae7..e2171078 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -13,8 +13,6 @@ import 'message_widget.dart'; import 'stream_chat_theme.dart'; class MessageReactionsModal extends StatelessWidget { - final Widget Function(BuildContext, Message) editMessageInputBuilder; - final void Function(Message) onThreadTap; final Message message; final MessageTheme messageTheme; final bool reverse; @@ -30,8 +28,6 @@ class MessageReactionsModal extends StatelessWidget { @required this.message, @required this.messageTheme, this.showReactions = true, - this.onThreadTap, - this.editMessageInputBuilder, this.messageShape, this.attachmentShape, this.reverse = false, diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index af6ea1ec..09feeb74 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -5,6 +5,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/message_action.dart'; @@ -841,6 +842,8 @@ class _MessageWidgetState extends State return StreamChannel( channel: channel, child: MessageActionsModal( + onCopyTap: (message) => + Clipboard.setData(ClipboardData(text: message.text)), attachmentBorderRadiusGeometry: widget.attachmentBorderRadiusGeometry, showUserAvatar: @@ -903,8 +906,6 @@ class _MessageWidgetState extends State widget.attachmentShape ?? _getDefaultAttachmentShape(context), reverse: widget.reverse, message: widget.message, - editMessageInputBuilder: widget.editMessageInputBuilder, - onThreadTap: widget.onThreadTap, showReactions: widget.showReactions, ), ); diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index 4ddcca93..160c0579 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter/src/reaction_icon.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -36,67 +37,70 @@ class ReactionBubble extends StatelessWidget { return Transform( transform: Matrix4.rotationY(reverse ? pi : 0), alignment: Alignment.center, - child: Stack( - alignment: Alignment.center, - children: [ - Transform.translate( - offset: Offset(reverse ? offset : -offset, 0), - child: Container( - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - color: maskColor, - borderRadius: BorderRadius.all(Radius.circular(16)), - ), + child: Center( + child: Stack( + alignment: Alignment.center, + fit: StackFit.loose, + children: [ + Transform.translate( + offset: Offset(reverse ? offset : -offset, 0), child: Container( - padding: EdgeInsets.symmetric( - vertical: 4, - horizontal: totalReactions > 1 ? 4 : 0, - ), + padding: const EdgeInsets.all(2), decoration: BoxDecoration( - border: Border.all( - color: borderColor, - ), - color: backgroundColor, - borderRadius: BorderRadius.all(Radius.circular(14)), + color: maskColor, + borderRadius: BorderRadius.all(Radius.circular(16)), ), - child: LayoutBuilder( - builder: (context, constraints) { - return Flex( - direction: Axis.horizontal, - mainAxisSize: MainAxisSize.min, - children: [ - if (constraints.maxWidth < double.infinity) - ...reactions - .take((constraints.maxWidth) ~/ 24) - .map((reaction) { - return _buildReaction( - reactionIcons, - reaction, - context, - ); - }).toList(), - if (constraints.maxWidth == double.infinity) - ...reactions.map((reaction) { - return _buildReaction( - reactionIcons, - reaction, - context, - ); - }).toList(), - ], - ); - }, + child: Container( + padding: EdgeInsets.symmetric( + vertical: 4, + horizontal: totalReactions > 1 ? 4 : 0, + ), + decoration: BoxDecoration( + border: Border.all( + color: borderColor, + ), + color: backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(14)), + ), + child: LayoutBuilder( + builder: (context, constraints) { + return Flex( + direction: Axis.horizontal, + mainAxisSize: MainAxisSize.min, + children: [ + if (constraints.maxWidth < double.infinity) + ...reactions + .take((constraints.maxWidth) ~/ 24) + .map((reaction) { + return _buildReaction( + reactionIcons, + reaction, + context, + ); + }).toList(), + if (constraints.maxWidth == double.infinity) + ...reactions.map((reaction) { + return _buildReaction( + reactionIcons, + reaction, + context, + ); + }).toList(), + ], + ); + }, + ), ), ), ), - ), - Positioned( - bottom: 2, - left: reverse ? null : 13, - right: !reverse ? null : 13, - child: _buildReactionsTail(context), - ), - ], + Positioned( + bottom: 2, + left: reverse ? null : 13, + right: !reverse ? null : 13, + child: _buildReactionsTail(context), + ), + ], + ), ), ); } diff --git a/packages/stream_chat_flutter/lib/src/unread_indicator.dart b/packages/stream_chat_flutter/lib/src/unread_indicator.dart index 61a92987..0c30e9a1 100644 --- a/packages/stream_chat_flutter/lib/src/unread_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/unread_indicator.dart @@ -40,7 +40,7 @@ class UnreadIndicator extends StatelessWidget { ), child: Center( child: Text( - '${snapshot.data}', + '${snapshot.data > 99 ? '99+' : snapshot.data}', style: TextStyle( fontSize: 11, color: Colors.white, diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index b8062209..eb35c69b 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -1,3 +1,6 @@ +export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +export 'src/attachment/attachment.dart'; export 'src/back_button.dart'; export 'src/channel_header.dart'; export 'src/channel_image.dart'; @@ -5,39 +8,35 @@ export 'src/channel_list_header.dart'; export 'src/channel_list_view.dart'; export 'src/channel_name.dart'; export 'src/channel_preview.dart'; +export 'src/connection_status_builder.dart'; export 'src/date_divider.dart'; export 'src/deleted_message.dart'; -export 'src/message_action.dart'; -export 'src/attachment/attachment.dart'; export 'src/full_screen_media.dart'; -export 'src/image_header.dart'; export 'src/image_footer.dart'; +export 'src/image_header.dart'; +export 'src/info_tile.dart'; +export 'src/mention_tile.dart'; +export 'src/message_action.dart'; export 'src/message_input.dart'; export 'src/message_list_view.dart'; +export 'src/message_search_item.dart'; +export 'src/message_search_list_view.dart'; export 'src/message_text.dart'; export 'src/message_widget.dart'; +export 'src/option_list_tile.dart'; export 'src/reaction_picker.dart'; export 'src/sending_indicator.dart'; +export 'src/stream_chat.dart'; export 'src/stream_chat_theme.dart'; export 'src/stream_neumorphic_button.dart'; export 'src/stream_svg_icon.dart'; export 'src/system_message.dart'; export 'src/thread_header.dart'; export 'src/typing_indicator.dart'; +export 'src/unread_indicator.dart'; export 'src/user_avatar.dart'; export 'src/user_item.dart'; export 'src/user_item.dart'; export 'src/user_list_view.dart'; export 'src/user_list_view.dart'; export 'src/utils.dart'; -export 'src/message_search_item.dart'; -export 'src/message_search_list_view.dart'; -export 'src/unread_indicator.dart'; -export 'src/option_list_tile.dart'; -export 'src/channel_file_display_screen.dart'; -export 'src/channel_media_display_screen.dart'; -export 'src/info_tile.dart'; -export 'src/stream_chat.dart'; -export 'src/connection_status_builder.dart'; -export 'src/mention_tile.dart'; -export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 93401a9c..65c688d3 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -55,9 +55,12 @@ flutter: - svgs/ - lib/svgs/ - animations/ + uses-material-design: true dev_dependencies: flutter_test: sdk: flutter mockito: ^4.1.3 - pedantic: ^1.9.2 \ No newline at end of file + pedantic: ^1.9.2 + golden_toolkit: ^0.6.0 + \ No newline at end of file diff --git a/packages/stream_chat_flutter/test/flutter_test_config.dart b/packages/stream_chat_flutter/test/flutter_test_config.dart new file mode 100644 index 00000000..c09db700 --- /dev/null +++ b/packages/stream_chat_flutter/test/flutter_test_config.dart @@ -0,0 +1,9 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:golden_toolkit/golden_toolkit.dart'; + +Future testExecutable(FutureOr Function() testMain) async { + await loadAppFonts(); + return testMain(); +} diff --git a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart new file mode 100644 index 00000000..1ef02cd9 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart @@ -0,0 +1,510 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/src/attachment_actions_modal.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +class MockAttachmentDownloader extends Mock { + ProgressCallback progressCallback; + Completer completer = Completer(); + + Future call( + Attachment attachment, { + ProgressCallback progressCallback, + }) { + this.progressCallback = progressCallback; + return completer.future; + } +} + +void main() { + testWidgets( + 'it should show all the actions', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + await tester.pumpWidget( + MaterialApp( + theme: themeData, + home: StreamChat( + streamChatThemeData: streamTheme, + client: client, + child: AttachmentActionsModal( + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + attachments: [ + Attachment( + type: 'image', + title: 'text.jpg', + ), + ], + ), + currentIndex: 0, + ), + ), + ), + ); + expect(find.text('Reply'), findsOneWidget); + expect(find.text('Show in Chat'), findsOneWidget); + expect(find.text('Save Image'), findsOneWidget); + expect(find.text('Delete'), findsOneWidget); + }, + ); + + testWidgets( + 'it should hide delete if it\'s not my message', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id2')); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + await tester.pumpWidget( + MaterialApp( + theme: themeData, + home: StreamChat( + streamChatThemeData: streamTheme, + client: client, + child: AttachmentActionsModal( + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + attachments: [ + Attachment( + type: 'image', + title: 'text.jpg', + ), + ], + ), + currentIndex: 0, + ), + ), + ), + ); + expect(find.text('Reply'), findsOneWidget); + expect(find.text('Show in Chat'), findsOneWidget); + expect(find.text('Save Image'), findsOneWidget); + expect(find.text('Delete'), findsNothing); + }, + ); + + testWidgets( + 'it should show save video for videos', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + await tester.pumpWidget( + MaterialApp( + theme: themeData, + home: StreamChat( + streamChatThemeData: streamTheme, + client: client, + child: Container( + child: AttachmentActionsModal( + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + attachments: [ + Attachment( + type: 'video', + title: 'video.mp4', + ), + ], + ), + currentIndex: 0, + ), + ), + ), + ), + ); + expect(find.text('Save Video'), findsOneWidget); + }, + ); + + testWidgets( + 'tapping on reply should pop', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + final mockObserver = MockNavigatorObserver(); + + final message = Message( + text: 'test', + user: User( + id: 'user-id', + ), + attachments: [ + Attachment( + type: 'image', + title: 'image.jpg', + ), + ], + ); + await tester.pumpWidget( + MaterialApp( + theme: themeData, + navigatorObservers: [mockObserver], + home: StreamChat( + streamChatThemeData: streamTheme, + client: client, + child: Container( + child: AttachmentActionsModal( + message: message, + currentIndex: 0, + ), + ), + ), + ), + ); + await tester.tap(find.text('Reply')); + verify(mockObserver.didPop(any, any)); + }, + ); + + testWidgets( + 'tapping on show in chat should call onShowMessage', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final onShowMessage = MockVoidCallback(); + + await tester.pumpWidget( + MaterialApp( + theme: themeData, + home: StreamChat( + streamChatThemeData: streamTheme, + client: client, + child: Container( + child: AttachmentActionsModal( + onShowMessage: onShowMessage, + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + attachments: [ + Attachment( + type: 'image', + title: 'image.jpg', + ), + ]), + currentIndex: 0, + ), + ), + ), + ), + ); + await tester.tap(find.text('Show in Chat')); + verify(onShowMessage.call()).called(1); + }, + ); + + testWidgets( + 'tapping on delete in chat should remove the attachment', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final mockChannel = MockChannel(); + + when(mockChannel.updateMessage(any)).thenAnswer((_) { + return; + }); + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final message = Message( + text: 'test', + user: User( + id: 'user-id', + ), + attachments: [ + Attachment( + type: 'image', + title: 'image.jpg', + ), + Attachment( + type: 'image', + title: 'image.jpg', + ), + ], + ); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + child: child, + ); + }, + home: StreamChannel( + showLoading: false, + channel: mockChannel, + child: AttachmentActionsModal( + message: message, + currentIndex: 0, + ), + ), + ), + ); + await tester.tap(find.text('Delete')); + verify(mockChannel.updateMessage(message.copyWith( + attachments: [ + message.attachments[1], + ], + ))).called(1); + }, + ); + + testWidgets( + 'tapping on delete in chat should remove the attachment if there is text', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final mockChannel = MockChannel(); + + when(mockChannel.updateMessage(any)).thenAnswer((_) { + return; + }); + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final message = Message( + text: 'test', + user: User( + id: 'user-id', + ), + attachments: [ + Attachment( + type: 'image', + title: 'image.jpg', + ), + ], + ); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + child: child, + ); + }, + home: StreamChannel( + showLoading: false, + channel: mockChannel, + child: AttachmentActionsModal( + message: message, + currentIndex: 0, + ), + ), + ), + ); + await tester.tap(find.text('Delete')); + verify(mockChannel.updateMessage(message.copyWith( + attachments: [], + ))).called(1); + }, + ); + + testWidgets( + 'tapping on delete in chat should remove the message if that\'s the only attachment and there is no text', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final mockChannel = MockChannel(); + + when(mockChannel.deleteMessage(any)).thenAnswer((_) { + return; + }); + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final message = Message( + user: User( + id: 'user-id', + ), + attachments: [ + Attachment( + type: 'image', + title: 'image.jpg', + ), + ], + ); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + child: child, + ); + }, + home: StreamChannel( + showLoading: false, + channel: mockChannel, + child: AttachmentActionsModal( + message: message, + currentIndex: 0, + ), + ), + ), + ); + await tester.tap(find.text('Delete')); + verify(mockChannel.deleteMessage(message)).called(1); + }, + ); + + testWidgets( + 'tapping on save in chat should call image downloader', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final imageDownloader = MockAttachmentDownloader(); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + child: child, + ); + }, + home: Container( + child: AttachmentActionsModal( + imageDownloader: imageDownloader, + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + attachments: [ + Attachment( + type: 'image', + title: 'image.jpg', + ), + ]), + currentIndex: 0, + ), + ), + ), + ); + + await tester.tap(find.text('Save Image')); + + imageDownloader.progressCallback(0, 100); + await tester.pump(); + expect(find.text('0%'), findsOneWidget); + + imageDownloader.progressCallback(50, 100); + await tester.pump(); + expect(find.text('50%'), findsOneWidget); + + imageDownloader.progressCallback(100, 100); + imageDownloader.completer.complete('path'); + await tester.pump(); + expect(find.byKey(Key('completedIcon')), findsOneWidget); + await tester.pumpAndSettle(Duration(milliseconds: 500)); + }, + ); + + testWidgets( + 'tapping on save in chat should call file downloader', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final fileDownloader = MockAttachmentDownloader(); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + child: child, + ); + }, + home: Container( + child: AttachmentActionsModal( + fileDownloader: fileDownloader, + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + attachments: [ + Attachment( + type: 'video', + title: 'video.mp4', + ), + ]), + currentIndex: 0, + ), + ), + ), + ); + + await tester.tap(find.text('Save Video')); + + fileDownloader.progressCallback(0, 100); + await tester.pump(); + expect(find.text('0%'), findsOneWidget); + + fileDownloader.progressCallback(50, 100); + await tester.pump(); + expect(find.text('50%'), findsOneWidget); + + fileDownloader.progressCallback(100, 100); + fileDownloader.completer.complete('path'); + await tester.pump(); + expect(find.byKey(Key('completedIcon')), findsOneWidget); + await tester.pumpAndSettle(Duration(milliseconds: 500)); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart b/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart new file mode 100644 index 00000000..b547e80f --- /dev/null +++ b/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'it should show file details', + (WidgetTester tester) async { + final channel = MockChannel(); + final channelState = MockChannelState(); + + when(channel.state).thenReturn(channelState); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + await tester.pumpWidget( + MaterialApp( + home: StreamChatTheme( + data: streamTheme, + child: StreamChannel( + channel: channel, + child: Container( + child: FileAttachment( + size: Size( + 300.0, + 300.0, + ), + message: Message(), + attachment: Attachment( + type: 'file', + title: 'example.pdf', + extraData: { + 'mime_type': 'pdf', + }, + ), + ), + ), + ), + ), + ), + ); + + expect(find.text('example.pdf'), findsOneWidget); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/back_button_test.dart b/packages/stream_chat_flutter/test/src/back_button_test.dart new file mode 100644 index 00000000..3ee88ed1 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/back_button_test.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/src/back_button.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'BackButton control test', + (WidgetTester tester) async { + final theme = ThemeData(); + await tester.pumpWidget( + MaterialApp( + home: const Material(child: Text('Home')), + routes: { + '/next': (BuildContext context) { + return Material( + child: Center( + child: StreamChatTheme( + data: StreamChatThemeData.getDefaultTheme(theme), + child: StreamBackButton(), + ), + ), + ); + }, + }, + ), + ); + + // ignore: unawaited_futures + tester.state(find.byType(Navigator)).pushNamed('/next'); + + await tester.pumpAndSettle(); + + await tester.tap(find.byType(StreamBackButton)); + + await tester.pumpAndSettle(); + + expect(find.text('Home'), findsOneWidget); + }, + ); + + testWidgets( + 'it should not throw errors if cannot pop', + (WidgetTester tester) async { + final theme = ThemeData(); + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Center( + child: StreamChatTheme( + data: StreamChatThemeData.getDefaultTheme(theme), + child: StreamBackButton(), + ), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + await tester.tap(find.byType(StreamBackButton)); + + await tester.pumpAndSettle(); + + expect(find.byType(StreamBackButton), findsOneWidget); + }, + ); + + testWidgets( + 'BackButton onPressed overrides default pop behavior', + (WidgetTester tester) async { + final theme = ThemeData(); + var customCallbackWasCalled = false; + await tester.pumpWidget( + MaterialApp( + home: const Material(child: Text('Home')), + routes: { + '/next': (BuildContext context) { + return Material( + child: Center( + child: StreamChatTheme( + data: StreamChatThemeData.getDefaultTheme(theme), + child: StreamBackButton( + onPressed: () => customCallbackWasCalled = true, + ), + ), + ), + ); + }, + }, + ), + ); + + // ignore: unawaited_futures + tester.state(find.byType(Navigator)).pushNamed('/next'); + + await tester.pumpAndSettle(); + + expect(find.text('Home'), findsNothing); // Start off on the second page. + expect( + customCallbackWasCalled, + false, + ); // customCallbackWasCalled should still be false. + await tester.tap(find.byType(StreamBackButton)); + + await tester.pumpAndSettle(); + + // We're still on the second page. + expect(find.text('Home'), findsNothing); + // But the custom callback is called. + expect(customCallbackWasCalled, true); + }, + ); + + testWidgets( + 'it should show unread count', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Center( + child: StreamChat( + client: client, + child: StreamBackButton( + showUnreads: true, + ), + ), + ), + ), + ), + ); + + expect(find.byType(UnreadIndicator), findsOneWidget); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel_header_test.dart new file mode 100644 index 00000000..1a5cb79d --- /dev/null +++ b/packages/stream_chat_flutter/test/src/channel_header_test.dart @@ -0,0 +1,388 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/src/channel_info.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'it should show basic channel information', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.unreadCount).thenReturn(1); + when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: ChannelHeader(), + ), + ), + ), + )); + + expect(find.text('test'), findsOneWidget); + expect(find.byType(ChannelImage), findsOneWidget); + expect(find.byType(StreamBackButton), findsOneWidget); + expect(find.byType(ChannelInfo), findsOneWidget); + }, + ); + + testWidgets( + 'it should show the InfoTile message if disconnected', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.unreadCount).thenReturn(1); + when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + when(client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: ChannelHeader( + showConnectionStateTile: true, + ), + ), + ), + ), + )); + + expect(tester.widget(find.byType(InfoTile)).showMessage, true); + expect(tester.widget(find.byType(InfoTile)).message, + 'Disconnected'); + }, + ); + + testWidgets( + 'it should show the InfoTile message if connecting', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.unreadCount).thenReturn(1); + when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + when(client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + showLoading: false, + child: Scaffold( + body: ChannelHeader( + showConnectionStateTile: true, + ), + ), + ), + ), + )); + + await tester.pump(); + + expect(tester.widget(find.byType(InfoTile)).showMessage, true); + expect(tester.widget(find.byType(InfoTile)).message, + 'Reconnecting...'); + }, + ); + + testWidgets( + 'it should apply passed properties', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.unreadCount).thenReturn(1); + when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: ChannelHeader( + leading: Text('leading'), + subtitle: Text('subtitle'), + actions: [ + Text('action'), + ], + title: Text('title'), + ), + ), + ), + ), + )); + + expect(find.text('test'), findsNothing); + expect(find.byType(StreamBackButton), findsNothing); + expect(find.byType(ChannelImage), findsNothing); + expect(find.byType(ChannelInfo), findsNothing); + expect(find.text('leading'), findsOneWidget); + expect(find.text('title'), findsOneWidget); + expect(find.text('subtitle'), findsOneWidget); + expect(find.text('action'), findsOneWidget); + }, + ); + + testWidgets( + 'showBackButton: false should hide the StreamBackButton and ' + 'showTypingIndicator: false should hide the typing indicator and ' + 'showConnectionStateTile: false should be passed to the infotile', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.unreadCount).thenReturn(1); + when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + when(client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: ChannelHeader( + showTypingIndicator: false, + showBackButton: false, + showConnectionStateTile: false, + ), + ), + ), + ), + )); + + expect(find.byType(StreamBackButton), findsNothing); + expect( + tester + .widget(find.byType(ChannelInfo)) + .showTypingIndicator, + false); + expect(tester.widget(find.byType(InfoTile)).showMessage, false); + }, + ); + + testWidgets( + 'should apply passed callbacks', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.unreadCount).thenReturn(1); + when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + + var backPressed = false; + var imageTapped = false; + var titleTapped = false; + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: ChannelHeader( + onBackPressed: () => backPressed = true, + onImageTap: () => imageTapped = true, + onTitleTap: () => titleTapped = true, + ), + ), + ), + ), + )); + + await tester.tap(find.byType(StreamBackButton)); + await tester.tap(find.byType(ChannelImage)); + await tester.tap(find.byType(ChannelName)); + + expect(backPressed, true); + expect(imageTapped, true); + expect(titleTapped, true); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel_image_test.dart new file mode 100644 index 00000000..cea48d2f --- /dev/null +++ b/packages/stream_chat_flutter/test/src/channel_image_test.dart @@ -0,0 +1,263 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/src/group_image.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'it should show the image in channel.extraData', + (tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + 'image': 'imagetest', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + 'image': 'imagetest', + }); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: ChannelImage(), + ), + ), + ), + )); + + final image = + tester.widget(find.byType(CachedNetworkImage)); + expect(image.imageUrl, 'imagetest'); + }, + ); + + testWidgets( + 'it should show the the other member image', + (tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + Member( + userId: 'user-id2', + user: User( + id: 'user-id2', + extraData: { + 'image': 'testimage', + }, + ), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id2', + user: User( + id: 'user-id2', + extraData: { + 'image': 'testimage', + }, + ), + ), + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ]); + when(clientState.usersStream).thenAnswer((i) => Stream.value({ + 'user-id2': User( + id: 'user-id2', + extraData: { + 'image': 'testimage', + }, + ), + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: ChannelImage(), + ), + ), + ), + )); + + final image = + tester.widget(find.byType(CachedNetworkImage)); + expect(image.imageUrl, 'testimage'); + }, + ); + + testWidgets( + 'it should use a groupimage if more than 2 members', + (tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User( + id: 'user-id', + extraData: { + 'image': 'testimage1', + }, + ), + ), + Member( + userId: 'user-id2', + user: User( + id: 'user-id2', + extraData: { + 'image': 'testimage2', + }, + ), + ), + Member( + userId: 'user-id3', + user: User( + id: 'user-id3', + extraData: { + 'image': 'testimage3', + }, + ), + ), + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User( + id: 'user-id', + extraData: { + 'image': 'testimage1', + }, + ), + ), + Member( + userId: 'user-id2', + user: User( + id: 'user-id2', + extraData: { + 'image': 'testimage2', + }, + ), + ), + Member( + userId: 'user-id3', + user: User( + id: 'user-id3', + extraData: { + 'image': 'testimage3', + }, + ), + ), + ]); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: ChannelImage(), + ), + ), + ), + )); + + final image = tester.widget(find.byType(GroupImage)); + expect(image.images, [ + 'testimage2', + 'testimage3', + ]); + }, + ); + + testWidgets( + 'using select: true should show a selection border', + (tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + 'image': 'imagetest', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + 'image': 'imagetest', + }); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: ChannelImage( + selected: true, + ), + ), + ), + ), + )); + + expect(find.byKey(Key('selectedImage')), findsOneWidget); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/channel_list_header_test.dart b/packages/stream_chat_flutter/test/src/channel_list_header_test.dart new file mode 100644 index 00000000..6fc4d428 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/channel_list_header_test.dart @@ -0,0 +1,207 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'control test', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: Scaffold( + body: ChannelListHeader(), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + final userAvatar = tester.widget(find.byType(UserAvatar)); + expect(userAvatar.user, clientState.user); + expect(find.byType(StreamNeumorphicButton), findsOneWidget); + expect(find.text('Stream Chat'), findsOneWidget); + }, + ); + + testWidgets( + 'it should show the InfoTile message if disconnected', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: Scaffold( + body: ChannelListHeader( + showConnectionStateTile: true, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('Disconnected'), findsOneWidget); + }, + ); + + testWidgets( + 'it should show the InfoTile message if connecting', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: Scaffold( + body: ChannelListHeader( + showConnectionStateTile: true, + ), + ), + ), + ), + ); + await tester.pump(); + + expect(find.text('Reconnecting...'), findsOneWidget); + }, + ); + + testWidgets( + 'it should apply passed properties', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: Scaffold( + body: ChannelListHeader( + titleBuilder: (context, status, client) { + return Text('TITLE'); + }, + subtitle: Text('SUBTITLE'), + leading: Text('LEADING'), + actions: [ + Text('ACTION'), + ], + client: client, + ), + ), + ), + ), + ); + await tester.pump(); + + expect(find.text('TITLE'), findsOneWidget); + expect(find.text('SUBTITLE'), findsOneWidget); + expect(find.text('LEADING'), findsOneWidget); + expect(find.text('ACTION'), findsOneWidget); + }, + ); + + testWidgets( + 'it should apply prenavigationcallback', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + + var tapped = false; + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: Scaffold( + body: ChannelListHeader( + preNavigationCallback: () { + tapped = true; + }, + ), + ), + ), + ), + ); + await tester.pump(); + + await tester.tap(find.byType(UserAvatar)); + expect(tapped, true); + }, + ); + + testWidgets( + 'it should apply passed callbacks', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + + var tapped = 0; + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: Scaffold( + body: ChannelListHeader( + onUserAvatarTap: (u) { + tapped++; + }, + onNewChatButtonTap: () { + tapped++; + }, + ), + ), + ), + ), + ); + await tester.pump(); + + await tester.tap(find.byType(UserAvatar)); + await tester.tap(find.byType(StreamNeumorphicButton)); + expect(tapped, 2); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/channel_name_test.dart b/packages/stream_chat_flutter/test/src/channel_name_test.dart new file mode 100644 index 00000000..bf6f7e55 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/channel_name_test.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'it should show channel name', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.unreadCount).thenReturn(1); + when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + when(channelState.messages).thenReturn([ + Message( + text: 'hello', + user: User(id: 'other-user'), + ) + ]); + when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + Message( + text: 'hello', + user: User(id: 'other-user'), + ) + ])); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: ChannelName(), + ), + ), + ), + )); + + expect(find.text('test'), findsOneWidget); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/channel_preview_test.dart b/packages/stream_chat_flutter/test/src/channel_preview_test.dart index 094c74a5..d8ed9f72 100644 --- a/packages/stream_chat_flutter/test/src/channel_preview_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_preview_test.dart @@ -15,6 +15,7 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + when(channel.cid).thenReturn('cid'); when(client.state).thenReturn(clientState); when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(channel.lastMessageAt).thenReturn(lastMessageAt); @@ -28,6 +29,9 @@ void main() { when(channel.extraData).thenReturn({ 'name': 'test name', }); + when(clientState.channels).thenReturn({ + channel.cid: channel, + }); when(channelState.unreadCount).thenReturn(1); when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(channelState.membersStream).thenAnswer((i) => Stream.value([ diff --git a/packages/stream_chat_flutter/test/src/date_divider_test.dart b/packages/stream_chat_flutter/test/src/date_divider_test.dart new file mode 100644 index 00000000..4856a0d3 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/date_divider_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'it should show basic channel information', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: Scaffold( + body: DateDivider( + dateTime: DateTime.now(), + ), + ), + ), + )); + + expect(find.text('Today'), findsOneWidget); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/deleted_message_test.dart b/packages/stream_chat_flutter/test/src/deleted_message_test.dart new file mode 100644 index 00000000..bb93b28b --- /dev/null +++ b/packages/stream_chat_flutter/test/src/deleted_message_test.dart @@ -0,0 +1,201 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:golden_toolkit/golden_toolkit.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'control test', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: Scaffold( + body: DeletedMessage( + messageTheme: MessageTheme( + createdAt: TextStyle( + color: Colors.black, + ), + messageText: TextStyle(), + ), + ), + ), + ), + )); + + expect(find.text('Message deleted'), findsOneWidget); + }, + ); + + testGoldens( + 'control golden light', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + + when(clientState.totalUnreadCount).thenReturn(10); + when(clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(10)); + + final materialTheme = ThemeData.light(); + final theme = StreamChatThemeData.fromTheme(materialTheme); + await tester.pumpWidgetBuilder( + materialAppWrapper( + theme: materialTheme, + )( + StreamChat( + streamChatThemeData: theme, + client: client, + child: StreamChannel( + showLoading: false, + channel: channel, + child: Center( + child: DeletedMessage( + messageTheme: theme.ownMessageTheme, + ), + ), + ), + ), + ), + surfaceSize: Size.square(200), + ); + + await screenMatchesGolden(tester, 'deleted_message_light'); + }, + ); + + testGoldens( + 'control golden dark', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + + when(clientState.totalUnreadCount).thenReturn(10); + when(clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(10)); + + final materialTheme = ThemeData.dark(); + final theme = StreamChatThemeData.fromTheme(materialTheme); + await tester.pumpWidgetBuilder( + materialAppWrapper( + theme: materialTheme, + )( + StreamChat( + streamChatThemeData: theme, + client: client, + child: StreamChannel( + showLoading: false, + channel: channel, + child: Center( + child: DeletedMessage( + messageTheme: theme.ownMessageTheme, + ), + ), + ), + ), + ), + surfaceSize: Size.square(200), + ); + + await screenMatchesGolden(tester, 'deleted_message_dark'); + }, + ); + + testGoldens( + 'golden customization test', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + + when(clientState.totalUnreadCount).thenReturn(10); + when(clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(10)); + + final materialTheme = ThemeData.light(); + final theme = StreamChatThemeData.fromTheme(materialTheme); + await tester.pumpWidgetBuilder( + materialAppWrapper( + theme: materialTheme, + )( + StreamChat( + streamChatThemeData: theme, + client: client, + child: StreamChannel( + showLoading: false, + channel: channel, + child: Center( + child: DeletedMessage( + messageTheme: theme.ownMessageTheme, + reverse: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ), + ), + ), + surfaceSize: Size.square(200), + ); + + await screenMatchesGolden(tester, 'deleted_message_custom'); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart new file mode 100644 index 00000000..55adf7d3 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'it should show channel typing', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + when(channelState.messages).thenReturn([ + Message( + text: 'hello', + user: User(id: 'other-user'), + ) + ]); + when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + Message( + text: 'hello', + user: User(id: 'other-user'), + ) + ])); + + when(channelState.typingEvents).thenAnswer((i) => [ + User(id: 'other-user', extraData: {'name': 'demo'}) + ]); + when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([ + User(id: 'other-user', extraData: {'name': 'demo'}), + User(id: 'other-user', extraData: {'name': 'demo'}), + ])); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: FullScreenMedia( + mediaAttachments: [ + Attachment( + type: 'image', + title: 'demo image', + imageUrl: '', + ), + ], + message: Message( + createdAt: DateTime.now(), + ), + sentAt: DateTime.now(), + ), + ), + ), + )); + + expect(find.byType(PhotoView), findsOneWidget); + expect(find.byType(StreamSvgIcon), findsNWidgets(4)); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png new file mode 100644 index 0000000000000000000000000000000000000000..c464bc01de48fcef06c538bd284ed284370f55aa GIT binary patch literal 2936 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 literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_dark.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7db0a6ef17a3f5586f9a0f7d67b1f49dd6fd5b60 GIT binary patch literal 2785 zcmd^>`#;lv8^=+N6sd1VsZ>akiWFPUhnaGoDa@&ZB9_C3IaMCz?qJFx5tg)!*>-np zc5gyRNG!9NjIHir(;8o!DTnd>^z(h+f5G?tczizZ$LI0>{BXUl>v}!k*F9fvsE)S2 zwwjun4$Q;VAK1HpKUyH*O+HVl1UB^~f2gw>rQcu*_*k3d41;R{D?{tjKh)H;AHZCl z;Hl-Z(WQWZ=)GODKF-topToRuoLo$Is3(NbHgILkzsEriHQ?CW0BUTFK*H&*elVA6_k9*K87g?~ zXw)NBc8g|{6W7~()z9m%bY*mE{O=6irJFpuVN>sGI`?rl;yCe!%`zhUc-kW8@MX)= zMg_LoFY`ErBoGKr|Fe!4&U2bWtlgpwLVsIMQ%w~5jA!=b8@-P@p;ET5L)v7o5JadP z51aafIXP5iPlJ@{X^X40&m6(TqfXX+8mMml^x}Y)Kp?2(IFu(l!3ahok>bSQo|m44f*4vbpCJ;7$|@^YIyHZ>F7TW<1NhEG z^2;*eTG)N%eBF8T#ua<8C7W5?En70V^DMg2^*~kBYhnl;Mc3WgDx}_mds3vpZ{JsP zG|ftSg+Jq6rFYS z_N|*A+@TY?8;Sd;-p{?6L*w&{Pv{*`%Chef^4;bfvq5<)H4>avht_CraL*&JC4;MNK@TkpPS(J z6!N6p*R!)hIHh|vpki_UdmtcuA;VrJ8}!+>ajg-|rS##80is2=il%+XIW_|w7nh>5 zFXQ=Mybm7vmWXYpJbt{{%g5{RR_*2+^L7c#|28YW-@;6EJf+_{V5 z4%Ge>#M)&)%+Ko)%@X~nQlQ92wRCzApU*GgfCDN^E4ZuELY}-MH#j&f9)hTaCtu78 z>$&9L@#XrFht5@oR31qb5C9w^s(c^4Nhg`W7@^+xQbRLeKRtEoRB}d)X8}jZW#nsWuJo`dCV)?QmE}3cns7OQ% zqsAsB1$6d#b&3-1IPb4`S~(sWoo+DyNr_)zto1q#^>t;Mvy+oor024v z^KpTsK%L8$*qN~uW$qt`=C~8RX2rde!-8Ge#wCLvKHP2Zqn8KqhKf9l#uW>1hC_4S zq(Ap;V!3sF=rWdphWQzVk37rv@$r_W zoRO9sewi>HIaViK0|VoR@@Aypih$&oZg6j z1=y?rll>r(l&XATgnixg4_8f{1x=TDwaXjqsI1nhJ~m@x>J7}E{_TmjgjudmN8%+t}? zv`PB8%7vLRaUX;Xa&d8~gPQ?K)c<%1PUWSrSKMZg-XZ$7L!EqZLE-zooE(veB@qk9783R4kb8kI> z4KD+TNh^!TWYOnQs|sinBX@BrggW(eDUgilTln1-*ifD-{ZLryYUyN&{?(R21%6*P zXMVmh1_IIFy@%W0etX&>)er`@whVh*2ADh~AqI1b>npzxeosmE2* z!fRtMMv>=zt9(Bf9`XUSu&3VKZMg886pi>G^X=%l?#%=PXz4k3TCf*@C&g(Lz=P5y zddiWMl*lR>ul?X*$Vnp}n%2@n>GSU?2b!n#F0_E|;-LP`HcfsBYX6_nS|0Z^b;CGM z3#3su6mx7;bhat_Q$o}WRBhos69+y#C-of5A=EE9X2 z03^(HjFz`oJ$hww=rHM205b9H{bU-w^duGjbaeqPsgyR?<}zi>@SN#i2S z^CUduuIT-=KT>8qDA{Rs^L%Zq+i#oxtNc9NXRsQ1-7|U(TaT=B7=OikqNVD9|Nr>*u|%FB_)18Y7US2Z@A@{7gr}Yj=;@fJ0jb zJTmdP4Fi}T_xzIUrnE6vNp*wrR+SAusH+^^rXI0p+ko3YSZ8j_V^g6SHmVv(3bs6T@bI-cCU+oen1;E&mTL8Rs_)MRg2Eb>v@S&LjhC zUNKUPIaACjw?NoJAmJ(Rzkcm{{``3-g(CYQN;WG!fv>sd7RF)C&NlrBg;IKZvm#d6 z>|6A;1@9s}z6EY>_+;yPwv&@nXjmBB!^4BoJ~A?rYyi!5yT^MY5+zt!TCQ=oj7(Q@ zFpHil$?MV7F~K4|VJ7+PboNrUiJ6W@bGzg;1SKW+WUqFaH23!Q@fg{pju=!h7T%31pK$CpEvT|2KVxspLUAr%`<;YZ)8NOIhyBw(!#C%&y zo~~Q??50WGsa4R#ju%i=Hq)8ylO6 ziHRUb1GZdFUCX&5>quZr8iUMe%O4AQoP7kQg?>?NX69KMEtXyIV{@Q8qLym(88XTxj?x$*?P-Q0%g$42OkPMQlO>>&1)f$11OnRyz^T zw>OPc9PuutQmM8sE^QZWTbQn^KBZcd?D6pii3D>TUr(pIHolH2DlVS;{I&xpXsm!A zuE4G=M1sw{r{@yl<1ePAo%tRFx5(k<$jl-O5pH&eVZNi_;<T)R&s23zXcIXZ?&m+kHCJ9~SP(m0H8+^mPx3SN}tXh~oq{tl{G=(>%A zEobsslap<5bIOqtasN8{Nko7@MA7+C(hc67x-^f(NG`5yiv8<_|z1w^~)V|oBq zR#ujbLJ+Qe>#3@$N}Xd{`TF`c!_5WHi+1N(mU^;hJHrnVnBJv4 z9`B|fQ3i6}N~6)JwB$L7=<&30ik3yqkks{|$>-Z|lhCx5c>An~wxib8d;6!!Qa*Ep zFCD@d6crWy%{57I&V;MvBEk(MdjJ0Yv2k&&J&AE>SsZTR_`=3Me}2H`aIhbK-=z5RRGTG(+Um3B=Sj{+#W@NY0+#hS(#p!Jw1LNqf@MuF z??h*uJm_CDJ}}_MNXY3Aj~>1IN+@iJijFQHnK6NhL?ZKYFKU{oN>x>Ld}``&jQEW2 zM6bx(v~1%2dw6s-k%a0sg-yJ9Q_>*pGY1z_`uZN+xN$=mOj}qafqb-wyl|$ka*X>G zUy+P_1@^Bk6dn%izSq@dpMsqEZTj82*3r>X z;n4O1Pmn#p581E<`SYE}o#_iczP_-DEe{t;YTMfb6$-^d##acml0c{!-@%oDJ zoU>z7L%}^k(Z_bdN;z|F^r*Nib+w_Ltu6d<`AFm>JsSz?DW1X2 zN>ZFFV$38bqtd5;)r?zPUhY0l#szb7;cVmb#Pc%(7LRw|RX)2v)44`iPTdg*hytMf zjujrS#Q?Z4A?xESQoe5Spq+!mLBP3)qfIL-nVB^7seRig`!c036UwJ?BNvVVEoXru zA;HaqQFqU@!Uhze#UPVF|LFPCf_Nk_=pH)O+$<6$U5jG`^}q-2tuXLeYs8( zU>t%J_Nv+`eNjLp%Kzc=m>L+FNd{_~PtT1uoUX@Pm3x`%6{@*YzM|sLS1RVH z8mhmpGawU5H;vM;q$cOQl`HW;A-+o>Ky@5&y}Vo(gqOYiAz#h!A#`*7$0tOj_9MJF zaL_1i@5AqIWW0jqrXdV(Y1Qu7vATHs)}bNTBrOXermtSY8veo+L013u2_qDX$%p?4 z3}4>-Ki$jI8?L-IS-zeUEdSe?8E(=AV>@M`^5c+ycFO~ry{qfW2@Y?4bFTg8XV8Nx N!A|*klH4Qy{TBco{k#AG literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_0.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_0.png new file mode 100644 index 0000000000000000000000000000000000000000..12a654b9e6433499e1ff4a3a88d9540f98a8e299 GIT binary patch literal 410 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 literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..2bb180b9394599a9b6533c197f95903b93fbda39 GIT binary patch literal 1976 zcmd5-`8V5%7XMO9q%x(v3bnL!ky;v~idq{%<9V@Ht2Bt$ii};Pqe79YRw%8G>O{*# zbZSZMj4h=m$&^|eZE9DIT^N+a5+?mG=ALu!=iGBYKYZ>z=bq1fceFDEtN{i90D`)N z^pqgwdxGR7mGS1HtpucEJ)IE%qgQiY(#XUjP~IR(B!WUR06;z%g}mr}iz*niiqzz( zvsPzEXB6{)L|GjcoRX8yI1_vTMtzsBdkJj0rKBKTg3bkSX^CldTcBl?*-tIe58ec; z$ioB6t1c^a(s41yvyXz~ee;ngCWfx&)+Qtg!tOcK_IrB@Z1m0m>d5?(*9pKmW5skN zRPjC>dN>GvqBHOkxg!UZc3fG`LHFM@=we`L!&gGFMR_TnU>7DdU?wjo)~Qz+=2QY@ zBwR}Kf-VRz8^~g{DvSWS+z@|mnxdr9)i3?CX_AfTSO;`5un?2(*(7~e7}a|b4-u!z zHc3K(lj#!1!SVzN1V#E$3y6< z6(&`zW2Iy+*K3d<3uF{u3|%nLo({VVW*s?6Apm#A#%wI`19e8j@IAK=O4NFNs>4y~ z8?}0Xy`mAqD`1tgy{OeQ#rdtCH9p?Wv7|+4@FAAIha6$Dm^kOjp(PNE?xBNA_#cKs z=;ro+=fH}~@T|3DKGox+S&nHuQ3w?66cyCFy7SQ56wfhP<7TcqTET~_Xv4^={p_3> z>hF;qa~}4?4U_QAc5>|z!UzNFfAm#^h6Vl$NiDNbht5W7_Dty#e=I1%ja+|O{9=#K z7Ig@jr;{fBPudvAL~jZvD#CY2>=&_4+=i#FrvPn1w~4YWElV~J@dXU6y!}3XS#y-N zw`Y_ntZeLBX`^h`w`5pF7VUlN`&1+LVNaw3G&3+y>lHiRNKcUk0Et(1yFM?*Te zqDa?Mw?wb+0e7~)m5+Z-7=PZ~oMt88*f%x=z<+(85XmG7mj9>*?$|CS%UlzR*hp2^ z-Z7m1wBfeg_6Cm$JNm@^*7R<2j}OEqsnTp}H*b7)FzE@oBktyF>Z4GP!71zH=Wcf5 zmm&IYl0#n?-5kvqyAd58Vqo?DI*;Z3vcI)*Qc+_)zeA)h!?h~P*O{R^{PE-3t~-4f zye;kq z?=qV=T?tm`+L(+HY+y+{&QfGmoI*6dHTvV$qQ80kUbYXGijw<*o|7OKJaO1 zdNA$+DZ@^@b=JV`)%(>TA`LACpH}f&{HqO9X-(IT+3ocUrAMu8pc=`sA$V-pVo=38 zoN-5ZGGeqTrM7IUCjGyjEw1_&CBPS>89jB(*`IJj6H7N)z}=Fh14!s*jzJNj;3$#gbJ^eDoGbwTra z2yBuF|Jc7TQwlI~8YE;v{kr-o5$bPBo7eW6PSTAjR8tAp@WECgUjybyE)6#1ar_uT zC+e!Bk`4lsvzUdC1mXg2kvl;8wPl`R4K2!KHA(y7>|rIU$|KLJ{T+C^P>qKM%=F^u cf2W;nosC%j&<5`3e|Ka6<%mWy5P_-x0IYzKumAu6 literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png new file mode 100644 index 0000000000000000000000000000000000000000..ade5d82bade07e1410fa117fe494b9aa61c33300 GIT binary patch literal 2067 zcmd5-i8tGc6aPA!qKLBfbFOO%7Q4FcV@sTEqwaZjOh@v|Y0 zmf|fbx(!9LibTcPAkGpA5l8&8|HaOGGoLpzpLz4%%)B=Rw_r|}#1zB;0J!Ah4E5k+ z(LV|jM}E`fo{OBDQ@5~V0xpW~$gA=$?cvSK1LUBx{;J!q@( z_g`qV97gKQ{QO@el4Ol-X3fD9kFf9S&)>~<#poLt+>MV1cf~Zee|pJCRiRKQZCzcW zWLAd?OxNS$Jv3US-UD(`3mXhYg6d`c;)}YcHxD2fJJMC;+Wlb$OTJxy<^E0zip<(u zpU|*MKh&p#8|*n9<*r&S<)2+488X7N9IG84-QK{~EhC8}S%>2&utTZj&@Jj#53TcF!bow`~`*r>|y7A!PpgCwS zIvWm$Pq;#q9ZGF%Y$)D~)Oe+^Glfep%&6Xm!6PGP1|_a-_qa6yfA)mpOToESkCn?N(Ap9m52{xX!&&c z79a6f!NFyDLcpihpCzTFnER{OnZbs^EV(vL5aPf#1D-*qt12tn7So&+L zL7t3=r~Y>WfV=#lg7VJh#nIKD0rt-1(zx%AE#bV&5a$+pN?F(Ok`;JBxS5x$^W5$w z0OXfvhr+*Vi)VlTWy#ps_$x8!Lli)Uui$D5vtgituhEoUQo5~*gvHM6u7|NU%& zQ2AT!`oi2CR9YCARkQ*LDygUl0<^yXuGEopP4aumT++|8# zb~oBx=!>$|skmJI5Pfpk676M2TkXf&95D6j5jEeu4>=sqfD}T~m7<6`IbsF+4kLq? z5WDydUDkd={40~OEEUkoCr7m)`Uip4jm0Uq?ZuR<|a2i zRWs!=!?mK5UmAu6h4;2X&-Vug1fD&6W@KZ7)hR?Q-8Vk1tgH<2_+Pn2N_g5i@2p0u zN&L;OcIP$F(fA=XAya_I72oqL$(xG6o@;{sE(|WhVh1@H`uh58lWvK8l9s-qp|&rz z2R|Bx5dLaL!EgQGyZob}VHi5rxi>K>b*RcT&dA!j_}(jlmR}n1K^4tkaY={!qKlk( z*4jAEdP8LP%`qu9k$>_nW{!PpQ7-3LMi2WzlWolCCA>8;<9Sf~W0*E9xDLTB z=Fo0n;>%8!%RUuHW9bNfjX7(!r}Re;F~6v{FgHK+rur-#Egph{n+%Ux))h}k+QIE!LfBx)2ZQLIE?K0H;QPUQcJf6#IrXtr^^5$o4 zQMZr!OQxs27j)CX#CXztQ6_^_Ew0K6>s#oD3I945*_r9- zF!R?kP+*6xt{s$vYjrsrML=1FfLZo~0a>6ifKbgfnIjl}j-%Z7--;nh9bE+IH6 zNiID-9WGS>3-#NkP<-ywT&Fon2V=_F{{H@z=Z^8*@R4KyOV`Y+9(S}iRV-dPx!eBy z;N$W>vy2P8rJJQiMP2vReHua@ye*orG3qW85OC@QcKLU{sFloQ=PO--U>#%rV+ULu KU{JgrBK!Y4#nrhuOx}d}@AHd_rMRJYw#9*xb~w ziPwkXgoHjz9Ee|&clf9q8qS9%osgULQ(IoKToxL^ zV1w<1rk`ZUWax-!7 zA`}tIWHLb+^ewMhb7l1g1;^TJ^Eg#KI!%|L1#umEMy_7N^C|oi?zP0I^Re9B z6E7_REFvC$3bC-T;4x5J?{O^9x1MSo^~L8L%h)~?_yo#EH&W6=nz3j&QE!K zR@hoQv$kV9qv@bgL*Ijb2!+mrI&b@;pBd`p)Hzl`%O0li$I!(9K{ z)3gFYcbLh&2KyJNT!%!bltigg#Aw4a&vqvD11QP1Qy9I3%)LH+j^hMsY{S}n9T6a5flX-Ws_p$+7D8uypG)N^jr(7mS*{*9um`93hm=}*_AVU%sUs%>UtxlSr>#Bhtwez2rRvKm} z-7w6quBH>Gqp!1%sXeQ2$3xWrD2e7*MX=*o?CaMXwA_{qsrvQkK-}f~eK%?yQpVnm zP6{V>$G+Bz!R~}}#%6+3t+6Ui0zp_w5pO;~7Vq!93XdN9_Ewtl74}2!E4e+~5Z1t) zAvUx&Ixty;;c!1JqKg$RLTaIU#*q~iGE7-U+x#T>U-l&kBC%-IDgpmqgMck((UAeU zhRt~G<4l|4Y#XAU57&Ngua zWAwDO(G8u74kymv**y1W*wLPplZ#VdUY4I{c+7EumF)G*4aM&sykB;aLvh~oy@CZA{(rX@ubY^(qV-L; z#%ZCQh8wOe((s<*%E%B9<*dzm|GQv|;*_A<>9f_2CI1!Ai*-xXy=3AVD8dn~+mgs} z{_}(1uiu}H;(jjEV`vb`w$}94TeB}}dp(0RTCb$8ndbeZvQMMyLe}>4Rd(LNI@7&b z92@*Z7&H1rtQ5a4RPz+dSbE|0_xrtyzb&3mIAS=bxMSDdV?6O8m+w`b_n5@8@7jy_ zn3@Q;(6IlHn7lM~#JkO(Zgx^?owKkxhq&)S;T1-o0fTYcX3+q zJ1Zyiaw&@LG~|e0yZMu!nlQtG1OtoRe>PlgI{Vpw z@9qDrtrK2lpIz%J(f0nC|K$?x6bqSovHlWm2GWXaY+JHkFVW%3`1;TM(}xcmdf5*@ z6v&*FWHpzGVcoiQeRkn%U1uzekXY5*a`QCzzn@=Ur`-Ek(etsQ#-!Fn>fyV0?=pVv z-(R0xP|3+~yW$Yb(Fvt`lTSWb!t)^6uvJh?(QD;fzITfA=Fi_BvTtkI?uRG1rVFeM zoFn13%A&CA_3PJ*GtXJxN{Dk*T*9@b@%>?mVw3pabHAR@EM zz^L_^ChKee^(*s%vuDqqESa48!Q3fgp+vyyg)W+`OHDVK*zSmu?{#B{3xB!!CeMMY zSJgnUsXQfC4O`kn-o$%ZZ2kG; z({uhs8n-#(=eXV4>_FQe= zb43oOCzX6_OCKj4HPlExVYk!p!xX3bVqJ}S=dXV+pX#*y^1}ExwJmNOmMzz$93>7} zx>sKK5-Fvq!r?5DEO7q$foBqCH|Fg9-x3$tZr66DxoY3dS?f>#$aH#9?NRG1bR}=e w9~Vgn<`xF~?H~Caw@z2>5U`?ZX>hDwftkO;`riIFV1dTq>FVdQ&MBb@0L&pYlK=n! literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/test/src/goldens/system_message_dark.png b/packages/stream_chat_flutter/test/src/goldens/system_message_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..9055f705ebc7e3c25c3770057b9ffec701c9ac37 GIT binary patch literal 1812 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 Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: WillPopScope( + onWillPop: () async => false, + child: Scaffold( + body: ImageFooter(), + ), + ), + ), + ), + )); + + expect(find.byType(StreamSvgIcon), findsNWidgets(2)); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/info_tile_test.dart b/packages/stream_chat_flutter/test/src/info_tile_test.dart new file mode 100644 index 00000000..d304d48e --- /dev/null +++ b/packages/stream_chat_flutter/test/src/info_tile_test.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'control test', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: Scaffold( + body: Portal( + child: Container( + child: InfoTile( + showMessage: true, + message: 'message', + child: Text('test'), + ), + ), + ), + ), + ), + )); + + expect(find.text('message'), findsOneWidget); + }, + ); + + testWidgets( + 'it should hide when passing showMessage: false', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: Scaffold( + body: Portal( + child: Container( + child: InfoTile( + showMessage: false, + message: 'message', + child: Text('test'), + ), + ), + ), + ), + ), + )); + + expect(find.text('message'), findsNothing); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart index 3cb916d1..c744a6bb 100644 --- a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart @@ -38,16 +38,17 @@ void main() { ), ), ); - await tester.pump(); + await tester.pumpAndSettle(); - await tester.pump(Duration(milliseconds: 1000)); expect(find.byKey(Key('MessageWidget')), findsOneWidget); expect(find.text('Thread Reply'), findsOneWidget); + expect(find.text('Reply'), findsOneWidget); expect(find.text('Edit Message'), findsOneWidget); expect(find.text('Delete Message'), findsOneWidget); expect(find.text('Copy Message'), findsOneWidget); }, ); + testWidgets( 'it should show some actions', (WidgetTester tester) async { @@ -84,9 +85,8 @@ void main() { ), ), ); - await tester.pump(); + await tester.pumpAndSettle(); - await tester.pump(Duration(milliseconds: 1000)); expect(find.byKey(Key('MessageWidget')), findsOneWidget); expect(find.text('Reply'), findsNothing); expect(find.text('Thread reply'), findsNothing); @@ -95,4 +95,697 @@ void main() { expect(find.text('Copy message'), findsNothing); }, ); + + testWidgets( + 'it should show custom actions', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + var tapped = false; + await tester.pumpWidget( + MaterialApp( + theme: themeData, + home: StreamChat( + streamChatThemeData: streamTheme, + client: client, + child: Container( + child: MessageActionsModal( + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + customActions: [ + MessageAction( + leading: Icon(Icons.check), + title: Text('title'), + onTap: (m) { + tapped = true; + }, + ), + ], + ), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byIcon(Icons.check), findsOneWidget); + expect(find.text('title'), findsOneWidget); + + await tester.tap(find.text('title')); + + expect(tapped, true); + }, + ); + + testWidgets( + 'tapping on reply should call the callback', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + var tapped = false; + + await tester.pumpWidget( + MaterialApp( + theme: themeData, + home: StreamChat( + streamChatThemeData: streamTheme, + client: client, + child: Container( + child: MessageActionsModal( + onReplyTap: (m) { + return tapped = true; + }, + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Reply')); + + expect(tapped, true); + }, + ); + + testWidgets( + 'tapping on thread reply should call the callback', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + var tapped = false; + + await tester.pumpWidget( + MaterialApp( + theme: themeData, + home: StreamChat( + streamChatThemeData: streamTheme, + client: client, + child: Container( + child: MessageActionsModal( + onThreadReplyTap: (m) { + return tapped = true; + }, + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Thread Reply')); + + expect(tapped, true); + }, + ); + + testWidgets( + 'tapping on edit should show the edit bottom sheet', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + streamChatThemeData: streamTheme, + child: child, + ); + }, + theme: themeData, + home: StreamChannel( + showLoading: false, + channel: channel, + child: Container( + child: MessageActionsModal( + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Edit Message')); + + await tester.pumpAndSettle(); + + expect(find.byType(MessageInput), findsOneWidget); + }, + ); + + testWidgets( + 'tapping on edit should show use the custom builder', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + streamChatThemeData: streamTheme, + child: child, + ); + }, + theme: themeData, + home: StreamChannel( + showLoading: false, + channel: channel, + child: Container( + child: MessageActionsModal( + editMessageInputBuilder: (context, m) { + return Text('test'); + }, + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Edit Message')); + + await tester.pumpAndSettle(); + + expect(find.text('test'), findsOneWidget); + }, + ); + + testWidgets( + 'tapping on copy should use the callback', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + var tapped = false; + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + streamChatThemeData: streamTheme, + child: child, + ); + }, + theme: themeData, + home: StreamChannel( + showLoading: false, + channel: channel, + child: Container( + child: MessageActionsModal( + onCopyTap: (m) => tapped = true, + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Copy Message')); + + expect(tapped, true); + }, + ); + + testWidgets( + 'tapping on resend should call send message', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + streamChatThemeData: streamTheme, + child: child, + ); + }, + theme: themeData, + home: StreamChannel( + showLoading: false, + channel: channel, + child: Container( + child: MessageActionsModal( + message: Message( + status: MessageSendingStatus.failed, + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Resend')); + + verify(channel.sendMessage(any)).called(1); + }, + ); + + testWidgets( + 'tapping on resend should call update message if editing the message', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + streamChatThemeData: streamTheme, + child: child, + ); + }, + theme: themeData, + home: StreamChannel( + showLoading: false, + channel: channel, + child: Container( + child: MessageActionsModal( + message: Message( + status: MessageSendingStatus.failed_update, + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Resend Edited Message')); + + verify(channel.updateMessage(any)).called(1); + }, + ); + + testWidgets( + 'tapping on flag message should show the dialog', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + streamChatThemeData: streamTheme, + child: child, + ); + }, + theme: themeData, + home: StreamChannel( + showLoading: false, + channel: channel, + child: Container( + child: MessageActionsModal( + message: Message( + id: 'testid', + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Flag Message')); + await tester.pumpAndSettle(); + + expect(find.text('Flag Message'), findsNWidgets(2)); + + await tester.tap(find.text('FLAG')); + await tester.pumpAndSettle(); + + verify(client.flagMessage('testid')).called(1); + }, + ); + + testWidgets( + 'if flagging a message throws an error the error dialog should appear', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(client.flagMessage(any)).thenThrow(ApiError( + '{}', + 500, + )); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + streamChatThemeData: streamTheme, + child: child, + ); + }, + theme: themeData, + home: StreamChannel( + showLoading: false, + channel: channel, + child: Container( + child: MessageActionsModal( + message: Message( + id: 'testid', + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Flag Message')); + await tester.pumpAndSettle(); + + expect(find.text('Flag Message'), findsNWidgets(2)); + + await tester.tap(find.text('FLAG')); + await tester.pumpAndSettle(); + + expect(find.text('Something went wrong'), findsOneWidget); + }, + ); + + testWidgets( + 'if flagging an already flagged message no error should appear', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(client.flagMessage(any)).thenThrow(ApiError( + '{"code":4}', + 400, + )); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + streamChatThemeData: streamTheme, + child: child, + ); + }, + theme: themeData, + home: StreamChannel( + showLoading: false, + channel: channel, + child: Container( + child: MessageActionsModal( + message: Message( + id: 'testid', + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Flag Message')); + await tester.pumpAndSettle(); + + expect(find.text('Flag Message'), findsNWidgets(2)); + + await tester.tap(find.text('FLAG')); + await tester.pumpAndSettle(); + + expect(find.text('Message flagged'), findsOneWidget); + }, + ); + + testWidgets( + 'tapping on delete message should call client.delete', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + streamChatThemeData: streamTheme, + child: child, + ); + }, + theme: themeData, + home: StreamChannel( + showLoading: false, + channel: channel, + child: Container( + child: MessageActionsModal( + message: Message( + id: 'testid', + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Delete Message')); + await tester.pumpAndSettle(); + + expect(find.text('Delete message'), findsOneWidget); + + await tester.tap(find.text('DELETE')); + await tester.pumpAndSettle(); + + verify(channel.deleteMessage(any)).called(1); + }, + ); + + testWidgets( + 'tapping on delete message should call client.delete', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(channel.deleteMessage(any)).thenThrow(ApiError( + '{}', + 500, + )); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + streamChatThemeData: streamTheme, + child: child, + ); + }, + theme: themeData, + home: StreamChannel( + showLoading: false, + channel: channel, + child: Container( + child: MessageActionsModal( + message: Message( + id: 'testid', + text: 'test', + user: User( + id: 'user-id', + ), + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Delete Message')); + await tester.pumpAndSettle(); + + expect(find.text('Delete message'), findsOneWidget); + + await tester.tap(find.text('DELETE')); + await tester.pumpAndSettle(); + + expect(find.text('Something went wrong'), findsOneWidget); + }, + ); } diff --git a/packages/stream_chat_flutter/test/src/message_input_test.dart b/packages/stream_chat_flutter/test/src/message_input_test.dart new file mode 100644 index 00000000..38e80565 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/message_input_test.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'checks message input features', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + when(channelState.messages).thenReturn([ + Message( + text: 'hello', + user: User(id: 'other-user'), + ) + ]); + when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + Message( + text: 'hello', + user: User(id: 'other-user'), + ) + ])); + + when(channelState.typingEvents).thenAnswer((i) => [ + User(id: 'other-user', extraData: {'name': 'demo'}) + ]); + when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([ + User(id: 'other-user', extraData: {'name': 'demo'}), + User(id: 'other-user', extraData: {'name': 'demo'}), + ])); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: MessageInput(), + ), + ), + ), + )); + + expect(find.byType(TextField), findsOneWidget); + expect(find.byType(StreamSvgIcon), findsNWidgets(8)); + expect(find.byKey(Key('messageInputText')), findsOneWidget); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/message_reaction_modal_test.dart b/packages/stream_chat_flutter/test/src/message_reaction_modal_test.dart deleted file mode 100644 index 4e515a09..00000000 --- a/packages/stream_chat_flutter/test/src/message_reaction_modal_test.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; -import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import 'mocks.dart'; - -void main() { - testWidgets( - 'it should show one thumbs from the picker', - (WidgetTester tester) async { - final client = MockClient(); - final clientState = MockClientState(); - final themeData = ThemeData(); - - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); - - await tester.pumpWidget( - MaterialApp( - theme: themeData, - home: StreamChat( - client: client, - streamChatThemeData: streamTheme, - child: MessageReactionsModal( - message: Message( - id: 'test', - text: 'test message', - user: User( - id: 'test-user', - ), - ), - messageTheme: streamTheme.ownMessageTheme, - ), - ), - ), - ); - - await tester.pump(Duration(milliseconds: 1000)); - - expect(find.byKey(Key('MessageWidget')), findsOneWidget); - expect(find.byKey(Key('StreamSvgIcon-Icon_thumbs_up_reaction.svg')), - findsOneWidget); - }, - ); - - testWidgets( - 'it should show two reactions', - (WidgetTester tester) async { - final client = MockClient(); - final clientState = MockClientState(); - - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - - final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); - final testUserId = 'test user'; - - await tester.pumpWidget( - MaterialApp( - theme: themeData, - home: StreamChat( - streamChatThemeData: streamTheme, - client: client, - child: MessageReactionsModal( - message: Message( - text: 'test message', - user: User( - id: 'test-user', - ), - latestReactions: [ - Reaction( - type: 'like', - user: User(id: testUserId), - ), - Reaction( - type: 'love', - user: User(id: testUserId), - ), - ], - ), - messageTheme: streamTheme.ownMessageTheme, - ), - ), - ), - ); - await tester.pump(); - - await tester.pump(Duration(milliseconds: 1000)); - expect(find.byKey(Key('MessageWidget')), findsOneWidget); - expect(find.byKey(Key('StreamSvgIcon-Icon_thumbs_up_reaction.svg')), - findsNWidgets(2)); - expect(find.byKey(Key('StreamSvgIcon-Icon_love_reaction.svg')), - findsNWidgets(2)); - expect(find.text(testUserId.split(' ')[0]), findsNWidgets(2)); - }, - ); -} diff --git a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart new file mode 100644 index 00000000..044eb693 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; +import 'package:stream_chat_flutter/src/reaction_bubble.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'control test', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final themeData = ThemeData(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + final message = Message( + id: 'test', + text: 'test message', + user: User( + id: 'test-user', + ), + ); + await tester.pumpWidget( + MaterialApp( + theme: themeData, + home: StreamChat( + client: client, + streamChatThemeData: streamTheme, + child: MessageReactionsModal( + message: message, + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ); + + await tester.pump(Duration(milliseconds: 1000)); + + expect(find.byType(MessageWidget), findsOneWidget); + final messageWidget = + tester.widget(find.byType(MessageWidget)); + expect(messageWidget.message, message); + expect(messageWidget.messageTheme, streamTheme.ownMessageTheme); + expect(messageWidget.attachmentBorderRadiusGeometry, null); + expect(messageWidget.showUserAvatar, DisplayWidget.show); + expect(messageWidget.reverse, false); + expect(messageWidget.attachmentShape, null); + expect(messageWidget.shape, null); + expect(messageWidget.onUserAvatarTap, null); + expect(messageWidget.showReactions, false); + expect(messageWidget.showUsername, false); + expect(messageWidget.showThreadReplyIndicator, false); + expect(messageWidget.showTimestamp, false); + expect(messageWidget.translateUserAvatar, false); + expect(messageWidget.showSendingIndicator, false); + + expect(find.byType(ReactionBubble), findsNothing); + + //only one avatar (the message one) + expect(find.byType(UserAvatar), findsOneWidget); + }, + ); + + testWidgets( + 'it should apply passed parameters', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final themeData = ThemeData(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + final message = Message( + id: 'test', + text: 'test message', + user: User( + id: 'test-user', + ), + latestReactions: [ + Reaction( + messageId: 'test', + user: User(id: 'testid'), + ), + ], + ); + + final onUserAvatarTap = (u) => print('ok'); + + await tester.pumpWidget( + MaterialApp( + theme: themeData, + home: StreamChat( + client: client, + streamChatThemeData: streamTheme, + child: MessageReactionsModal( + message: message, + messageTheme: streamTheme.ownMessageTheme, + showUserAvatar: DisplayWidget.gone, + reverse: true, + attachmentBorderRadiusGeometry: BorderRadius.circular(1), + attachmentShape: RoundedRectangleBorder(), + showReactions: false, + messageShape: RoundedRectangleBorder(), + onUserAvatarTap: onUserAvatarTap, + ), + ), + ), + ); + + await tester.pump(Duration(milliseconds: 1000)); + + expect(find.byType(MessageWidget), findsOneWidget); + final messageWidget = + tester.widget(find.byType(MessageWidget)); + expect(messageWidget.message, message); + expect(messageWidget.messageTheme, streamTheme.ownMessageTheme); + expect(messageWidget.attachmentBorderRadiusGeometry, + BorderRadius.circular(1)); + expect(messageWidget.showUserAvatar, DisplayWidget.gone); + expect(messageWidget.reverse, true); + expect(messageWidget.showReactions, false); + expect(messageWidget.attachmentShape, RoundedRectangleBorder()); + expect(messageWidget.shape, RoundedRectangleBorder()); + expect(messageWidget.showReactions, false); + expect(messageWidget.showUsername, false); + expect(messageWidget.showThreadReplyIndicator, false); + expect(messageWidget.showTimestamp, false); + expect(messageWidget.translateUserAvatar, false); + expect(messageWidget.showSendingIndicator, false); + + final userAvatar = tester.widget(find.byType(UserAvatar)); + expect(userAvatar.onTap, onUserAvatarTap); + + expect(find.byType(ReactionBubble), findsOneWidget); + expect(find.byType(UserAvatar), findsOneWidget); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/message_text_test.dart b/packages/stream_chat_flutter/test/src/message_text_test.dart new file mode 100644 index 00000000..1d2ac8be --- /dev/null +++ b/packages/stream_chat_flutter/test/src/message_text_test.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'it should show correct message text', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: MessageText( + message: Message( + text: 'demo', + ), + messageTheme: streamTheme.otherMessageTheme), + ), + ), + ), + )); + + expect(find.byType(MarkdownBody), findsOneWidget); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/mocks.dart b/packages/stream_chat_flutter/test/src/mocks.dart index 0abbf3ca..50f7ee6a 100644 --- a/packages/stream_chat_flutter/test/src/mocks.dart +++ b/packages/stream_chat_flutter/test/src/mocks.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:mockito/mockito.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -8,3 +9,9 @@ class MockClientState extends Mock implements ClientState {} class MockChannel extends Mock implements Channel {} class MockChannelState extends Mock implements ChannelClientState {} + +class MockNavigatorObserver extends Mock implements NavigatorObserver {} + +class MockVoidCallback extends Mock { + void call(); +} diff --git a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart index 0b6c90ef..d4a3b6a6 100644 --- a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart +++ b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart @@ -1,18 +1,20 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:mockito/mockito.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; +import 'simple_frame.dart'; void main() { - testWidgets( + testGoldens( 'it should show no reactions', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: StreamChatTheme( + await tester.pumpWidgetBuilder( + SimpleFrame( + child: StreamChatTheme( data: StreamChatThemeData(), child: Container( child: ReactionBubble( @@ -24,15 +26,173 @@ void main() { ), ), ), + surfaceSize: Size(100, 100), ); - - expect(find.byKey(Key('StreamSvgIcon-Icon_thumbs_up_reaction.svg')), - findsNothing); + await screenMatchesGolden(tester, 'reaction_bubble_0'); }, ); - testWidgets( - 'it should show a like', + testGoldens( + 'it should show a like - light theme', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final themeData = ThemeData.light(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final theme = StreamChatThemeData.getDefaultTheme(themeData); + await tester.pumpWidgetBuilder( + StreamChat( + client: client, + streamChatThemeData: theme, + child: Container( + child: ReactionBubble( + reactions: [ + Reaction( + type: 'like', + user: User(id: 'test'), + ), + ], + borderColor: theme.ownMessageTheme.reactionsBorderColor, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, + maskColor: theme.ownMessageTheme.reactionsMaskColor, + ), + ), + ), + surfaceSize: Size(100, 100), + ); + await screenMatchesGolden(tester, 'reaction_bubble_like_light'); + }, + ); + + testGoldens( + 'it should show a like - dark theme', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final themeData = ThemeData.dark(); + final theme = StreamChatThemeData.getDefaultTheme(themeData); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + await tester.pumpWidgetBuilder( + StreamChat( + client: client, + streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), + child: Container( + color: Colors.black, + child: ReactionBubble( + reactions: [ + Reaction( + type: 'like', + user: User(id: 'test'), + ), + ], + borderColor: theme.ownMessageTheme.reactionsBorderColor, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, + maskColor: theme.ownMessageTheme.reactionsMaskColor, + ), + ), + ), + surfaceSize: Size(100, 100), + ); + await screenMatchesGolden(tester, 'reaction_bubble_like_dark'); + }, + ); + + testGoldens( + 'it should show three reactions - light theme', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final themeData = ThemeData.light(); + final theme = StreamChatThemeData.getDefaultTheme(themeData); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + await tester.pumpWidgetBuilder( + StreamChat( + client: client, + streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), + child: Container( + color: Colors.black, + child: ReactionBubble( + reactions: [ + Reaction( + type: 'like', + user: User(id: 'test'), + ), + Reaction( + type: 'like', + user: User(id: 'user-id'), + ), + Reaction( + type: 'like', + user: User(id: 'test'), + ), + ], + borderColor: theme.ownMessageTheme.reactionsBorderColor, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, + maskColor: theme.ownMessageTheme.reactionsMaskColor, + ), + ), + ), + surfaceSize: Size(140, 140), + ); + await screenMatchesGolden(tester, 'reaction_bubble_3_light'); + }, + ); + + testGoldens( + 'it should show three reactions - dark theme', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final themeData = ThemeData.dark(); + final theme = StreamChatThemeData.getDefaultTheme(themeData); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + await tester.pumpWidgetBuilder( + StreamChat( + client: client, + streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), + child: Container( + color: Colors.black, + child: ReactionBubble( + reactions: [ + Reaction( + type: 'like', + user: User(id: 'test'), + ), + Reaction( + type: 'like', + user: User(id: 'user-id'), + ), + Reaction( + type: 'like', + user: User(id: 'test'), + ), + ], + borderColor: theme.ownMessageTheme.reactionsBorderColor, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, + maskColor: theme.ownMessageTheme.reactionsMaskColor, + ), + ), + ), + surfaceSize: Size(140, 140), + ); + await screenMatchesGolden(tester, 'reaction_bubble_3_dark'); + }, + ); + + testGoldens( + 'it should show two reactions with customized ui', (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); @@ -41,74 +201,40 @@ void main() { when(client.state).thenReturn(clientState); when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - await tester.pumpWidget( - MaterialApp( - theme: themeData, - home: StreamChat( - client: client, - streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), - child: Container( - child: ReactionBubble( - reactions: [ - Reaction( - type: 'like', - user: User(id: 'test'), - ), - ], - borderColor: Colors.black, - backgroundColor: Colors.white, - maskColor: Colors.white, - ), + await tester.pumpWidgetBuilder( + StreamChat( + client: client, + streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), + child: Container( + child: ReactionBubble( + reactions: [ + Reaction( + type: 'like', + user: User(id: 'test'), + ), + Reaction( + type: 'love', + user: User(id: 'user-id'), + ), + Reaction( + type: 'unknown', + user: User(id: 'test'), + ), + ], + borderColor: Colors.red, + backgroundColor: Colors.blue, + maskColor: Colors.green, + highlightOwnReactions: true, + reverse: true, + flipTail: true, + tailCirclesSpacing: 4, ), ), ), + surfaceSize: Size(200, 200), ); - expect(find.byKey(Key('StreamSvgIcon-Icon_thumbs_up_reaction.svg')), - findsOneWidget); - }, - ); - testWidgets( - 'it should show two reactions', - (WidgetTester tester) async { - final client = MockClient(); - final clientState = MockClientState(); - final themeData = ThemeData(); - - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - - await tester.pumpWidget( - MaterialApp( - theme: themeData, - home: StreamChat( - client: client, - streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), - child: Container( - child: ReactionBubble( - reactions: [ - Reaction( - type: 'like', - user: User(id: 'test'), - ), - Reaction( - type: 'love', - user: User(id: 'test'), - ), - ], - borderColor: Colors.black, - backgroundColor: Colors.white, - maskColor: Colors.white, - ), - ), - ), - ), - ); - - expect(find.byKey(Key('StreamSvgIcon-Icon_thumbs_up_reaction.svg')), - findsOneWidget); - expect(find.byKey(Key('StreamSvgIcon-Icon_love_reaction.svg')), - findsOneWidget); + await screenMatchesGolden(tester, 'reaction_bubble_2'); }, ); } diff --git a/packages/stream_chat_flutter/test/src/simple_frame.dart b/packages/stream_chat_flutter/test/src/simple_frame.dart new file mode 100644 index 00000000..c472df85 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/simple_frame.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class SimpleFrame extends StatelessWidget { + final Widget child; + + const SimpleFrame({Key key, @required this.child}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: const Color(0xFFFFFFFF), + border: Border.all(color: const Color(0xFF9E9E9E)), + ), + child: child, + ); + } +} diff --git a/packages/stream_chat_flutter/test/src/system_message_test.dart b/packages/stream_chat_flutter/test/src/system_message_test.dart new file mode 100644 index 00000000..04967c39 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/system_message_test.dart @@ -0,0 +1,164 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:golden_toolkit/golden_toolkit.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'it should show total unread count', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + + when(clientState.totalUnreadCount).thenReturn(10); + when(clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(10)); + + var tapped = false; + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: SystemMessage( + onMessageTap: (m) => tapped = true, + message: Message( + text: 'demo message', + ), + ), + ), + ), + ), + )); + + await tester.tap(find.byType(SystemMessage)); + + expect(find.text('demo message'), findsOneWidget); + expect(tapped, true); + }, + ); + + testGoldens( + 'control golden light', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + + when(clientState.totalUnreadCount).thenReturn(10); + when(clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(10)); + + await tester.pumpWidgetBuilder( + materialAppWrapper( + theme: ThemeData.light(), + )( + StreamChat( + client: client, + child: StreamChannel( + showLoading: false, + channel: channel, + child: Center( + child: SystemMessage( + message: Message( + text: 'demo message', + ), + ), + ), + ), + ), + ), + surfaceSize: Size.square(200), + ); + + await screenMatchesGolden(tester, 'system_message_light'); + }, + ); + + testGoldens( + 'control golden dark', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + + when(clientState.totalUnreadCount).thenReturn(10); + when(clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(10)); + + await tester.pumpWidgetBuilder( + materialAppWrapper( + theme: ThemeData.dark(), + )( + StreamChat( + client: client, + child: StreamChannel( + showLoading: false, + channel: channel, + child: Center( + child: SystemMessage( + message: Message( + text: 'demo message', + ), + ), + ), + ), + ), + ), + surfaceSize: Size.square(200), + ); + + await screenMatchesGolden(tester, 'system_message_dark'); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/thread_header_test.dart b/packages/stream_chat_flutter/test/src/thread_header_test.dart new file mode 100644 index 00000000..25987e31 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/thread_header_test.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'control test', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.unreadCount).thenReturn(1); + when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: ThreadHeader( + parent: Message(), + ), + ), + ), + ), + )); + await tester.pumpAndSettle(); + + expect(find.text('with '), findsOneWidget); + expect(find.byType(ChannelName), findsOneWidget); + expect(find.byType(StreamBackButton), findsOneWidget); + expect(find.text('Thread Reply'), findsOneWidget); + }, + ); + + testWidgets( + 'it should apply passed props', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.unreadCount).thenReturn(1); + when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + + var tapped = false; + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: ThreadHeader( + parent: Message(), + subtitle: Text('subtitle'), + leading: Text('leading'), + title: Text('title'), + onTitleTap: () { + tapped = true; + }, + actions: [ + Text('action'), + ], + ), + ), + ), + ), + )); + await tester.pumpAndSettle(); + + expect(find.text('title'), findsOneWidget); + await tester.tap(find.text('title')); + expect(tapped, true); + expect(find.text('subtitle'), findsOneWidget); + expect(find.text('action'), findsOneWidget); + expect(find.text('leading'), findsOneWidget); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart new file mode 100644 index 00000000..bbdbeb3e --- /dev/null +++ b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'it should show channel typing', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.isMuted).thenReturn(false); + when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + when(channelState.membersStream).thenAnswer((i) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ) + ])); + when(channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + when(channelState.messages).thenReturn([ + Message( + text: 'hello', + user: User(id: 'other-user'), + ) + ]); + when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + Message( + text: 'hello', + user: User(id: 'other-user'), + ) + ])); + + when(channelState.typingEvents).thenAnswer((i) => [ + User(id: 'other-user', extraData: {'name': 'demo'}) + ]); + when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([ + User(id: 'other-user', extraData: {'name': 'demo'}), + User(id: 'other-user', extraData: {'name': 'demo'}), + ])); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: TypingIndicator(), + ), + ), + ), + )); + + expect(find.byKey(Key('typings')), findsOneWidget); + }, + ); +} diff --git a/packages/stream_chat_flutter/test/src/unread_indicator_test.dart b/packages/stream_chat_flutter/test/src/unread_indicator_test.dart new file mode 100644 index 00000000..87018263 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/unread_indicator_test.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'it should show total unread count', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(channel.extraData).thenReturn({ + 'name': 'test', + }); + + when(clientState.totalUnreadCount).thenReturn(10); + when(clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(10)); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: UnreadIndicator(), + ), + ), + ), + )); + + expect(find.text('10'), findsOneWidget); + }, + ); + + testWidgets( + 'it should show nothing if no unread messages', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + when(channel.cid).thenReturn('cid'); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(clientState.channels).thenReturn({ + channel.cid: channel, + }); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channelState.unreadCount).thenReturn(0); + when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(0)); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: UnreadIndicator( + cid: channel.cid, + ), + ), + ), + ), + )); + + expect(find.byType(SizedBox), findsOneWidget); + }, + ); + + testWidgets( + 'it should show 99+ if more than 99 unreads', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + when(channel.cid).thenReturn('cid'); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(clientState.channels).thenReturn({ + channel.cid: channel, + }); + when(channel.lastMessageAt).thenReturn(lastMessageAt); + when(channel.state).thenReturn(channelState); + when(channel.client).thenReturn(client); + when(channelState.unreadCount).thenReturn(100); + when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(100)); + + await tester.pumpWidget(MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: UnreadIndicator( + cid: channel.cid, + ), + ), + ), + ), + )); + + expect(find.text('99+'), findsOneWidget); + }, + ); +} From d5efe48aa491dfbc252645bf3d37a8bfb6b03790 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 9 Apr 2021 11:09:46 +0200 Subject: [PATCH 083/599] add equatable --- packages/stream_chat/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index c3265ea1..f221fc39 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: async: ^2.5.0 collection: ^1.15.0 dio: ">=4.0.0-prev3 <4.0.0" + equatable: ^2.0.0 freezed_annotation: ^0.14.0 http_parser: ^4.0.0 json_annotation: ^4.0.0 From dd88cecedce433610fd19a7eea0adc95dcec63f5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 9 Apr 2021 11:38:25 +0200 Subject: [PATCH 084/599] fix reaction bubble --- .../lib/src/reaction_bubble.dart | 114 +++++++++--------- packages/stream_chat_flutter/pubspec.yaml | 2 +- .../test/src/goldens/reaction_bubble_0.png | Bin 410 -> 636 bytes .../test/src/goldens/reaction_bubble_2.png | Bin 3339 -> 3179 bytes .../src/goldens/reaction_bubble_3_dark.png | Bin 1976 -> 1935 bytes .../src/goldens/reaction_bubble_3_light.png | Bin 2067 -> 2064 bytes .../src/goldens/reaction_bubble_like_dark.png | Bin 1494 -> 1517 bytes .../goldens/reaction_bubble_like_light.png | Bin 1291 -> 1296 bytes 8 files changed, 57 insertions(+), 59 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index 160c0579..51ec7aa5 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -37,70 +37,68 @@ class ReactionBubble extends StatelessWidget { return Transform( transform: Matrix4.rotationY(reverse ? pi : 0), alignment: Alignment.center, - child: Center( - child: Stack( - alignment: Alignment.center, - fit: StackFit.loose, - children: [ - Transform.translate( - offset: Offset(reverse ? offset : -offset, 0), + child: Stack( + alignment: Alignment.center, + fit: StackFit.loose, + children: [ + Transform.translate( + offset: Offset(reverse ? offset : -offset, 0), + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: maskColor, + borderRadius: BorderRadius.all(Radius.circular(16)), + ), child: Container( - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - color: maskColor, - borderRadius: BorderRadius.all(Radius.circular(16)), + padding: EdgeInsets.symmetric( + vertical: 4, + horizontal: totalReactions > 1 ? 4 : 0, ), - child: Container( - padding: EdgeInsets.symmetric( - vertical: 4, - horizontal: totalReactions > 1 ? 4 : 0, - ), - decoration: BoxDecoration( - border: Border.all( - color: borderColor, - ), - color: backgroundColor, - borderRadius: BorderRadius.all(Radius.circular(14)), - ), - child: LayoutBuilder( - builder: (context, constraints) { - return Flex( - direction: Axis.horizontal, - mainAxisSize: MainAxisSize.min, - children: [ - if (constraints.maxWidth < double.infinity) - ...reactions - .take((constraints.maxWidth) ~/ 24) - .map((reaction) { - return _buildReaction( - reactionIcons, - reaction, - context, - ); - }).toList(), - if (constraints.maxWidth == double.infinity) - ...reactions.map((reaction) { - return _buildReaction( - reactionIcons, - reaction, - context, - ); - }).toList(), - ], - ); - }, + decoration: BoxDecoration( + border: Border.all( + color: borderColor, ), + color: backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(14)), + ), + child: LayoutBuilder( + builder: (context, constraints) { + return Flex( + direction: Axis.horizontal, + mainAxisSize: MainAxisSize.min, + children: [ + if (constraints.maxWidth < double.infinity) + ...reactions + .take((constraints.maxWidth) ~/ 24) + .map((reaction) { + return _buildReaction( + reactionIcons, + reaction, + context, + ); + }).toList(), + if (constraints.maxWidth == double.infinity) + ...reactions.map((reaction) { + return _buildReaction( + reactionIcons, + reaction, + context, + ); + }).toList(), + ], + ); + }, ), ), ), - Positioned( - bottom: 2, - left: reverse ? null : 13, - right: !reverse ? null : 13, - child: _buildReactionsTail(context), - ), - ], - ), + ), + Positioned( + bottom: 2, + left: reverse ? null : 13, + right: !reverse ? null : 13, + child: _buildReactionsTail(context), + ), + ], ), ); } diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 711802ca..522a7203 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: rxdart: ^0.26.0 scrollable_positioned_list: ^0.1.8 jiffy: ^3.0.1 - flutter_svg: ">=0.21.0-nullsafety.0 <0.21.0" + flutter_svg: ^0.21.0 flutter_portal: ^0.3.0 cached_network_image: ">=3.0.0-nullsafety <3.0.0" shimmer: ^1.1.2 diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_0.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_0.png index 12a654b9e6433499e1ff4a3a88d9540f98a8e299..18f898adb07837f8c009c64e1ad0d67f67e7b100 100644 GIT binary patch literal 636 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 literal 3339 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 diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png index 2bb180b9394599a9b6533c197f95903b93fbda39..9ee602b39480ddd7d8406ab5b4b0dc1781d4394a 100644 GIT binary patch delta 1638 zcmV-s2ATP|504L!L4P?(L_t(|obBB^j~mw+#_{LeW`?uMF6BfkH!%=Et0Fd#!l`^I zf~1g>50M~1>J$l(Hpn+fn~&f=ac;dErwFWV;s6G06&95s%k_$!%b9ah3`t$ALT-Fq1d|A(KD>8IwQ(8IwQ( z8IwQ(8IwQ(7Jp*5u~BU-v@uZ1Kq&>KccaS~0%DAau|a4WgvMX>kb0-k*Y{Q{1!Eno zO#r2O+2r8G2=6O2z6Rp;_2yB1k`nnsw`2} zRr`Y&QCG#a*yC&*j7?G)n*adPG((=}Fj~Vm4SdreE`MIcU7)pwQVP}>SYyzHfZ1$@ zvMc}qjj!OV@`^{fnT>+6Nd{vb0N~(Y3}-E>x<>7X|4!WUzAcvlXee0ot_|sFz7u^r|FBaZgm(Mk8dJ| zfTAoJ%YO^R7*UiZ5F-u_#sJX1(acsRs9PrwMyd7$7wusvhJd1~`X1M{vwV$~(YPcV zF~5mj9y&vO7zUVqCW5e1`-1(ZVK5*V#J&4%tC-NE7E zEqwpYH^tArjQZ7kX!c)6`JG>EW&4|szxEzRYb37SQxEN2Mi`6%0Hj$4-!z?m;nC3% z0APB0+UfZhsc*jr0MPRXTiJdeA-2YH?Ptv_|Z3_4X$}eh*pN&afU% zCdkqhhlhs%fX_bvT)e&K&A#`0M7IwBFsH|eZXeD5>i_`z-w!w5eqZB{F|KAv3d5Qy zi7wow`8NOnx4v}?XS12;7rpcLTX^T~w|~z4`N#K<@y9=XAll~Bio3r?arCQmf1W&e zANfCj*?IeYkN-xV{n~5amW@itzq0-xb5nhfUu%tKaY*lSXunFrVA`Ws41Yni zC~A3}U+0rg{w*2GGn)N3E_{spqWj^l@9`_8R_kM8Ps@>B7FjT%of*~M!wRN@WwHK) zwvX>09Qzb1i}34f_L9X zAN@lzjIVHC{CVxq(@+1f(f0cuzke|X^&*(i@p{%(F#vvh*$2lMU~PgX1dOr_N~udN zj+#s+m`+cj09a>NQ`5=h5&rVmzim9FHmAq!g*pYmWY0{k^ZUQqcuL*p_?@-TO5yDB zS-XUDU~6N<>tU=*VO)YV&2Zz!4a|#z%Nj08DTPs%;q2@TWm%x9EBJa?>wjXz(`xT4 zAV!pB0q_0liX~kvO%izT+a;Te1>!>^zYeol&1aQne62`l-6jP24~)TL}0gWGeH}QF$OUNJbwB%lL4OOKChP>*|f3GtWix| zf;4NFa6&1V9l2Gu386j5^B!eU@J6=vTWZ&+8tVqRxOppLY!5+;ndNoRUocNu-Hfjd z6n5-pd_=^+oAHSN@c)M)A(JivE0aJ08IwQ(8IwQ(8IwQ(7JqW7V601^6x$-eUi1KC z9rpIe0Dy8nLph%z#@+Z1NBXJ@zhG-`|0Z0LuKpZjU^qMM!e9=*dHaRmN4*TEn4N8F zZ1gp>_Qb@w1p1usYg9@j9qnypQ1tb6ZZIy(paleFy>OOfU3-!{9*?1*fEY0vWr!idHw}z2sOt*z kVvf*wG`@bRgQlzVe}GVwHO8lAEC2ui07*qoM6N<$g8khe_W%F@ delta 1652 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 F{R5y7)d~Or literal 1494 zcmcIk{Xf$Q9RK2uB^AqQX?HWOG7n`!o<=Qhtc_YE6Jr)HhuOx}d}@AHd_rMRJYw#9*xb~w ziPwkXgoHjz9Ee|&clf9q8qS9%osgULQ(IoKToxL^ zV1w<1rk`ZUWax-!7 zA`}tIWHLb+^ewMhb7l1g1;^TJ^Eg#KI!%|L1#umEMy_7N^C|oi?zP0I^Re9B z6E7_REFvC$3bC-T;4x5J?{O^9x1MSo^~L8L%h)~?_yo#EH&W6=nz3j&QE!K zR@hoQv$kV9qv@bgL*Ijb2!+mrI&b@;pBd`p)Hzl`%O0li$I!(9K{ z)3gFYcbLh&2KyJNT!%!bltigg#Aw4a&vqvD11QP1Qy9I3%)LH+j^hMsY{S}n9T6a5flX-Ws_p$+7D8uypG)N^jr(7mS*{*9um`93hm=}*_AVU%sUs%>UtxlSr>#Bhtwez2rRvKm} z-7w6quBH>Gqp!1%sXeQ2$3xWrD2e7*MX=*o?CaMXwA_{qsrvQkK-}f~eK%?yQpVnm zP6{V>$G+Bz!R~}}#%6+3t+6Ui0zp_w5pO;~7Vq!93XdN9_Ewtl74}2!E4e+~5Z1t) zAvUx&Ixty;;c!1JqKg$RLTaIU#*q~iGE7-U+x#T>U-l&kBC%-IDgpmqgMck((UAeU zhRt~GqaevL{aokz`A>uuf6xR6hkfKt%$0sP~m>CiFbKPEsg^EX$ZqCMb#mX_}&c(QNMZ{LETRCKDKAaFnJ1>LZ0t$Z}JYWf`h0`;fPI?~#p0@ZKYBwQAFRQ4M>SYetr3h_y!N`1r#zLR_8WV<@FC8IRw^&kCIo z;H8<0wMM(s`Es10c1)Eht|&g0xBm+YA>nN;oI{c%JMXvFE`)Tu-M!OE zk_7o&2x|?YS~{u95?bwkM{aI^ZWjGPx7&ra7K6b60C0A8w(+?z7h#5CVyNa4hL4Z< z=f{t&_uk#z;qLBkV|cIE!`0Q*#_)fC{=~`YDF7_`6DBB{tbOVC`&(rIAL~;Xp%S8$ zTCCu%^|p?>@;pak%u1SUPzVW_Btbr#Z4BS`_5;Ah#l_a}d^STo7s3ifgBqF=r4*Fb zm|44aw%+UY0KCr9hr=O&m!a?F<>l7eF|!s*>&1wUwT4g(dx6)b@zObr$77tFo*t~d z_ugYT9HNz`&~Z%tU4*88$qEIe;~2&mWTVl+;?71Rm^fbfq{|A85a6|-j?xrmRUyl= zFUOgUMyR}B`IO5Hosdvq?RFc^IgFm4KUIj{dyJl+QC1aNt=7BvS)u7emU*|y!MT@B z4tuTy&#c8{I)#a2+2n92-fjb$T8pZxkR-|WZ9sVrr4){28_?lF*|brvmWOW=P ziXtS&AU0+(v<2!V10WX?_{?sRx`_x-ObmAs3O7jEBqs={qliE)garPC0^bGIuvZWv zbdjz_f9xfM{cQK2#Lm6F%jYnb|2HLk4?Anf@>vV5HO!my7s6rrkdT0g`98LmXjztU z^HWOe&{`inLw`FPg(#(9jKS0VZwc#o{PpWwGW9pJR><Fg6b=iEAxa00000 LNkvXXu0mjfx}PFc delta 1084 zcmV-C1jGA~3X2MmK~%L#L_t(|ob8*va_Tx5MYko}fPoB|dkV^wDJfIt5%PF>jFc%U z^9YnFDb77%umM|^ty_SJAwbB_+_RneR>h2D$)jcI(;uEO#zCM#{p}YJb`g=$A(5>; zfB1j<1wMl3c?W+3fOGEP@21fRA)e>G5pXC^{kLg4(gdvnt+h?05#$){#Ze2!UEz&eaQ4|PUErekR&-38>>+i{03+Eh+F)&#M ztu@;1HuNUFNm3|<)Mc!eOXPWuD2mW(f49$iK1)+9mrDdefKC(vXpR&*A@!jqNfKzK z(Chc#jUoFu=a9rPoO6h}-Np=GRMXyi&q$I4zSihpTzptYRaXDv0!k??7mGLLvqC2X zSPxUa*68*7UoNxiU907Crw&$Vgwz9gp6BTHdf!%duirb^N1k>phPft&0)zNCVQ4|HrvfQbY z4GJL@xiJV^t+Vn@CKCX_U@$nl{IJ!6&GS#a+y4cHkm_sAtwk7y@8(a_bSq>y9G+cI z7=|z#A*?-wYU`vd3TXBIH}dfCf3S@Q!{HEVnqoSg0syYAu8uxetworjSQx5}gxSjr z{`vLmWZv<3jPZDUG<`H0;qLD4X!^gue`9cY2>{!8!UV;TbuN?1+7Rw&N7Iyvhy#vlxWojTc|5K<9@AxxGXO+WSZ1HjGA&B=6=W$-sbSfLnDtD!_G1*J8z zG(EUlAB{!;URUX}*$lvX>U(>8d$MZ*HP2l4LtOt7w{t3VI>c*S<)S z^?3%?=O&)#AqWEGc@C|0<6^}zC}YSzxiRpRLVt5DD4x&P_ji>-r_-Td?0tkr2swn- z8bKJsIY)WW^jR7qJR}H17-JC6=fLI|HrSlK|AbyqhdzeXgL=N%ny<_FX+r3GNPSHb z)NZ8wL>WK>gaALc@ee_@mnDc0I+HL2LXo@+7RG-d0}psR-oJ(b0000 Date: Fri, 9 Apr 2021 12:23:03 +0200 Subject: [PATCH 085/599] sort deps --- packages/stream_chat_flutter_core/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 6077c4cc..4b3d81de 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -14,9 +14,9 @@ environment: dependencies: flutter: sdk: flutter + meta: ^1.2.4 rxdart: ^0.26.0 stream_chat: ^1.5.0 - meta: ^1.2.4 dependency_overrides: stream_chat: From 9ec4c83cfcef68c95ed446a84746028d4da1775f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 12 Apr 2021 18:44:30 +0530 Subject: [PATCH 086/599] fixed tests, null errors --- packages/stream_chat/lib/src/api/channel.dart | 93 +++++++++---------- .../stream_chat/lib/src/api/responses.g.dart | 4 +- .../stream_chat/lib/src/api/retry_policy.dart | 1 - .../stream_chat/lib/src/api/retry_queue.dart | 1 - .../stream_chat/lib/src/api/websocket.dart | 4 +- .../lib/src/attachment_file_uploader.dart | 4 +- packages/stream_chat/lib/src/client.dart | 35 ++++--- .../lib/src/db/chat_persistence_client.dart | 2 +- .../lib/src/extensions/rate_limit.dart | 2 +- .../lib/src/models/attachment_file.dart | 2 +- .../lib/src/models/channel_state.dart | 4 +- .../lib/src/models/serialization.dart | 2 +- packages/stream_chat/pubspec.yaml | 2 +- .../test/src/api/channel_test.dart | 18 ++-- .../test/src/api/requests_test.dart | 4 +- .../test/src/api/responses_test.dart | 42 ++++++--- .../stream_chat/test/src/client_test.dart | 6 +- packages/stream_chat/test/version_test.dart | 4 +- 18 files changed, 124 insertions(+), 106 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 7606bc9a..3782b4b9 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -78,63 +78,63 @@ class Channel { /// Channel configuration as a stream Stream? get configStream => - state?.channelStateStream?.map((cs) => cs!.channel?.config); + state?.channelStateStream.map((cs) => cs!.channel?.config); /// Channel user creator User? get createdBy => state?._channelState?.channel?.createdBy; /// Channel user creator as a stream Stream? get createdByStream => - state?.channelStateStream?.map((cs) => cs!.channel?.createdBy); + state?.channelStateStream.map((cs) => cs!.channel?.createdBy); /// Channel frozen status bool? get frozen => state?._channelState?.channel?.frozen; /// Channel frozen status as a stream Stream? get frozenStream => - state?.channelStateStream?.map((cs) => cs!.channel?.frozen); + state?.channelStateStream.map((cs) => cs!.channel?.frozen); /// Channel creation date DateTime? get createdAt => state?._channelState?.channel?.createdAt; /// Channel creation date as a stream Stream? get createdAtStream => - state?.channelStateStream?.map((cs) => cs!.channel?.createdAt); + state?.channelStateStream.map((cs) => cs!.channel?.createdAt); /// Channel last message date DateTime? get lastMessageAt => state?._channelState?.channel?.lastMessageAt; /// Channel last message date as a stream Stream? get lastMessageAtStream => - state?.channelStateStream?.map((cs) => cs!.channel?.lastMessageAt); + state?.channelStateStream.map((cs) => cs!.channel?.lastMessageAt); /// Channel updated date DateTime? get updatedAt => state?._channelState?.channel?.updatedAt; /// Channel updated date as a stream Stream? get updatedAtStream => - state?.channelStateStream?.map((cs) => cs!.channel?.updatedAt); + state?.channelStateStream.map((cs) => cs!.channel?.updatedAt); /// Channel deletion date DateTime? get deletedAt => state?._channelState?.channel?.deletedAt; /// Channel deletion date as a stream Stream? get deletedAtStream => - state?.channelStateStream?.map((cs) => cs!.channel?.deletedAt); + state?.channelStateStream.map((cs) => cs!.channel?.deletedAt); /// Channel member count int? get memberCount => state?._channelState?.channel?.memberCount; /// Channel member count as a stream Stream? get memberCountStream => - state?.channelStateStream?.map((cs) => cs!.channel?.memberCount); + state?.channelStateStream.map((cs) => cs!.channel?.memberCount); /// Channel id String? get id => state?._channelState?.channel?.id ?? _id; /// Channel id as a stream Stream? get idStream => - state?.channelStateStream?.map((cs) => cs!.channel?.id ?? _id); + state?.channelStateStream.map((cs) => cs!.channel?.id ?? _id); /// Channel cid String? get cid => state?._channelState?.channel?.cid ?? _cid; @@ -144,7 +144,7 @@ class Channel { /// Channel cid as a stream Stream? get cidStream => - state?.channelStateStream?.map((cs) => cs!.channel?.cid ?? _cid); + state?.channelStateStream.map((cs) => cs!.channel?.cid ?? _cid); /// Channel extra data Map? get extraData => @@ -152,7 +152,7 @@ class Channel { /// Channel extra data as a stream Stream?>? get extraDataStream => - state?.channelStateStream?.map((cs) => cs!.channel?.extraData); + state?.channelStateStream.map((cs) => cs!.channel?.extraData); /// The main Stream chat client StreamChatClient get client => _client; @@ -284,7 +284,7 @@ class Channel { it.copyWith(uploadState: UploadState.failed(error: e.toString())), ); }).whenComplete(() { - throttledUpdateAttachment?.cancel(); + throttledUpdateAttachment.cancel(); _cancelableAttachmentUploadRequest.remove(it.id); }); })).whenComplete(() { @@ -297,7 +297,7 @@ class Channel { /// Send a [message] to this channel. /// Waits for a [_messageAttachmentsUploadCompleter] to complete /// before actually sending the message. - Future sendMessage(Message message) async { + Future sendMessage(Message message) async { // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. _messageAttachmentsUploadCompleter @@ -305,7 +305,7 @@ class Channel { ?.completeError('Message Cancelled'); final quotedMessage = state?.messages?.firstWhereOrNull( - (m) => m.id == message?.quotedMessageId, + (m) => m.id == message.quotedMessageId, ); // ignore: parameter_assignments message = message.copyWith( @@ -318,7 +318,7 @@ class Channel { if (it.uploadState.isSuccess) return it; return it.copyWith(uploadState: const UploadState.preparing()); }, - )?.toList(), + ).toList(), ); if (message.parentId != null && message.id == null) { @@ -348,9 +348,8 @@ class Channel { message = await attachmentsUploadCompleter.future; } - final response = await (_client.sendMessage(message, id, type) - as FutureOr); - state?.addMessage(response.message!); + final response = await (_client.sendMessage(message, id, type)); + state?.addMessage(response!.message!); return response; } catch (error) { if (error is DioError && error.type != DioErrorType.response) { @@ -379,7 +378,7 @@ class Channel { if (it.uploadState.isSuccess) return it; return it.copyWith(uploadState: const UploadState.preparing()); }, - )?.toList(), + ).toList(), ); state?.addMessage(message); @@ -596,7 +595,7 @@ class Channel { ..removeWhere((it) => it.userId != user!.id); final newMessage = message.copyWith( - reactionCounts: {...message?.reactionCounts ?? {}} + reactionCounts: {...message.reactionCounts ?? {}} ..update(type, (value) { if (enforceUnique) return value; return value + 1; @@ -660,7 +659,7 @@ class Channel { r.type == reaction.type && r.messageId == reaction.messageId); - final ownReactions = [...latestReactions ?? []] + final ownReactions = [...latestReactions] ..removeWhere((it) => it.userId != user!.id); final newMessage = message.copyWith( @@ -867,7 +866,7 @@ class Channel { '$_channelURL/stop-watching', data: {}, ); - return _client.decode(response?.data, EmptyResponse.fromJson); + return _client.decode(response.data, EmptyResponse.fromJson); } /// List the message replies for a parent message @@ -878,10 +877,10 @@ class Channel { PaginationParams options, { bool preferOffline = false, }) async { - final cachedReplies = (await _client.chatPersistenceClient?.getReplies( + final cachedReplies = await _client.chatPersistenceClient?.getReplies( parentId, options: options, - ))!; + ); if (cachedReplies != null && cachedReplies.isNotEmpty) { state?.updateThreadInfo(parentId, cachedReplies); if (preferOffline) { @@ -1049,7 +1048,7 @@ class Channel { if (id != null) { payload['id'] = id; - } else if (state?.members?.isNotEmpty == true) { + } else if (state?.members.isNotEmpty == true) { payload['members'] = state!.members; } @@ -1222,7 +1221,7 @@ class ChannelClientState { ChannelState channelState, //ignore: unnecessary_parenthesis ) : _debouncedUpdatePersistenceChannelState = ((ChannelState state) => - _channel?._client?.chatPersistenceClient + _channel._client.chatPersistenceClient ?.updateChannelState(state)) .debounced(const Duration(seconds: 1)) { retryQueue = RetryQueue( @@ -1264,12 +1263,12 @@ class ChannelClientState { _channel._client.chatPersistenceClient ?.getChannelThreads(_channel.cid) - ?.then((threads) { + .then((threads) { _threads = threads; - })?.then((_) { + }).then((_) { _channel._client.chatPersistenceClient ?.getChannelStateByCid(_channel.cid) - ?.then((state) { + .then((state) { // Replacing the persistence state members with the latest // `channelState.members` as they may have changes over the time. updateChannelState(state.copyWith(members: channelState.members)); @@ -1309,8 +1308,8 @@ class ChannelClientState { return expiration.isBefore(DateTime.now()); }) == true) - ?.map((e) => e.id) - ?.toList(); + .map((e) => e.id) + .toList(); if (expiredAttachmentMessagesId?.isNotEmpty == true) { _channel.getMessagesById(expiredAttachmentMessagesId!); _updatedMessagesIds.addAll(expiredAttachmentMessagesId); @@ -1560,7 +1559,7 @@ class ChannelClientState { /// Channel members list List get members => _channelState!.members! - .map((e) => e!.copyWith(user: _channel.client.state!.users![e.user!.id!])) + .map((e) => e!.copyWith(user: _channel.client.state!.users[e.user!.id!])) .toList(); /// Channel members list as a stream @@ -1581,7 +1580,7 @@ class ChannelClientState { /// Channel watchers list List get watchers => _channelState!.watchers! - .map((e) => _channel.client.state!.users![e.id!] ?? e) + .map((e) => _channel.client.state!.users[e.id!] ?? e) .toList(); /// Channel watchers list as a stream @@ -1628,7 +1627,7 @@ class ChannelClientState { ...newThreads[parentId] ?.where((newMessage) => !messages!.any((m) => m.id == newMessage.id)) - ?.toList() ?? + .toList() ?? [], ...messages!, ]; @@ -1654,39 +1653,39 @@ class ChannelClientState { /// Update channelState with updated information void updateChannelState(ChannelState updatedState) { final newMessages = [ - ...updatedState?.messages ?? [], + ...updatedState.messages ?? [], ..._channelState?.messages ?.where((m) => updatedState.messages ?.any((newMessage) => newMessage.id == m.id) != true) - ?.toList() ?? + .toList() ?? [], ]..sort(_sortByCreatedAt as int Function(Message, Message)?); final newWatchers = [ - ...updatedState?.watchers ?? [], + ...updatedState.watchers ?? [], ..._channelState?.watchers ?.where((w) => updatedState.watchers ?.any((newWatcher) => newWatcher.id == w.id) != true) - ?.toList() ?? + .toList() ?? [], ]; final newMembers = [ - ...updatedState?.members ?? [], + ...updatedState.members ?? [], ]; final newReads = [ - ...updatedState?.read ?? [], + ...updatedState.read ?? [], ..._channelState?.read ?.where((r) => updatedState.read ?.any((newRead) => newRead.user!.id == r.user!.id) != true) - ?.toList() ?? + .toList() ?? [], ]; @@ -1730,12 +1729,12 @@ class ChannelClientState { set _channelState(ChannelState? v) { _channelStateController.add(v); - _debouncedUpdatePersistenceChannelState?.call([v]); + _debouncedUpdatePersistenceChannelState.call([v]); } /// The channel threads related to this channel - Map>? get threads => - _threadsController.value as Map>?; + Map>? get threads => _threadsController.value + ?.map((key, value) => MapEntry(key ?? '', value ?? [])); /// The channel threads related to this channel as a stream Stream?>> get threadsStream => @@ -1793,7 +1792,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) == true) .listen( (event) { final newMembers = List.from(members); @@ -1841,7 +1840,7 @@ class ChannelClientState { final now = DateTime.now(); var expiredMessages = channelState!.pinnedMessages ?.where((m) => m.pinExpires?.isBefore(now) == true) - ?.toList() ?? + .toList() ?? []; if (expiredMessages.isNotEmpty) { expiredMessages = expiredMessages @@ -1876,7 +1875,7 @@ class ChannelClientState { /// Call this method to dispose this object void dispose() { - _debouncedUpdatePersistenceChannelState?.cancel(); + _debouncedUpdatePersistenceChannelState.cancel(); _unreadCountController.close(); retryQueue!.dispose(); _subscriptions.forEach((s) => s.cancel()); diff --git a/packages/stream_chat/lib/src/api/responses.g.dart b/packages/stream_chat/lib/src/api/responses.g.dart index 141534a5..849b6d1a 100644 --- a/packages/stream_chat/lib/src/api/responses.g.dart +++ b/packages/stream_chat/lib/src/api/responses.g.dart @@ -20,7 +20,7 @@ QueryChannelsResponse _$QueryChannelsResponseFromJson(Map json) { return QueryChannelsResponse() ..duration = json['duration'] as String? ..channels = (json['channels'] as List?) - ?.map((e) => ChannelState.fromJson(e as Map)) + ?.map((e) => ChannelState.fromJson(e as Map)) .toList(); } @@ -169,7 +169,7 @@ SearchMessagesResponse _$SearchMessagesResponseFromJson(Map json) { return SearchMessagesResponse() ..duration = json['duration'] as String? ..results = (json['results'] as List?) - ?.map((e) => GetMessageResponse.fromJson(e as Map)) + ?.map((e) => GetMessageResponse.fromJson(e as Map)) .toList(); } diff --git a/packages/stream_chat/lib/src/api/retry_policy.dart b/packages/stream_chat/lib/src/api/retry_policy.dart index 2eaf6ab5..f0f2c67b 100644 --- a/packages/stream_chat/lib/src/api/retry_policy.dart +++ b/packages/stream_chat/lib/src/api/retry_policy.dart @@ -1,4 +1,3 @@ -import 'package:meta/meta.dart'; import 'package:stream_chat/src/client.dart'; import 'package:stream_chat/src/exceptions.dart'; diff --git a/packages/stream_chat/lib/src/api/retry_queue.dart b/packages/stream_chat/lib/src/api/retry_queue.dart index a63a7d3a..e09c367b 100644 --- a/packages/stream_chat/lib/src/api/retry_queue.dart +++ b/packages/stream_chat/lib/src/api/retry_queue.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:logging/logging.dart'; -import 'package:meta/meta.dart'; import 'package:stream_chat/src/api/channel.dart'; import 'package:stream_chat/src/api/retry_policy.dart'; import 'package:stream_chat/src/event_type.dart'; diff --git a/packages/stream_chat/lib/src/api/websocket.dart b/packages/stream_chat/lib/src/api/websocket.dart index ba4d4271..b2208a13 100644 --- a/packages/stream_chat/lib/src/api/websocket.dart +++ b/packages/stream_chat/lib/src/api/websocket.dart @@ -153,9 +153,7 @@ class WebSocket { onError: (error, stacktrace) { _onConnectionError(error, stacktrace); }, - onDone: () { - _onDone(); - }, + onDone: _onDone, ); return _connectionCompleter.future; } diff --git a/packages/stream_chat/lib/src/attachment_file_uploader.dart b/packages/stream_chat/lib/src/attachment_file_uploader.dart index 42fab498..42df3fb2 100644 --- a/packages/stream_chat/lib/src/attachment_file_uploader.dart +++ b/packages/stream_chat/lib/src/attachment_file_uploader.dart @@ -70,7 +70,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) async { - final filename = file!.path?.split('/')?.last ?? file.name; + final filename = file!.path?.split('/').last ?? file.name; final mimeType = filename.mimeType; MultipartFile? multiPartFile; @@ -107,7 +107,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) async { - final filename = file!.path?.split('/')?.last ?? file.name; + final filename = file!.path?.split('/').last ?? file.name; final mimeType = filename.mimeType; MultipartFile? multiPartFile; diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 5ea3030a..5117f7da 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -34,7 +34,7 @@ import 'package:uuid/uuid.dart'; typedef LogHandlerFunction = void Function(LogRecord record); /// Used for decoding [Map] data to a generic type `T`. -typedef DecoderFunction = T Function(Map?); +typedef DecoderFunction = T Function(Map); /// A function which can be used to request a Stream Chat API token from your /// own backend server. Function requires a single [userId]. @@ -268,9 +268,8 @@ class StreamChatClient { var stringData = options.data.toString(); if (options.data is FormData) { - final multiPart = (options.data as FormData).files[0]?.value; - stringData = - '${multiPart?.filename} - ${multiPart?.contentType}'; + final multiPart = (options.data as FormData).files[0].value; + stringData = '${multiPart.filename} - ${multiPart.contentType}'; } logger.info(''' @@ -408,7 +407,7 @@ class StreamChatClient { /// Connects the current user, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. - Future connectUser(User user, String? token) async { + Future connectUser(User? user, String? token) async { if (_connectCompleter != null && !_connectCompleter!.isCompleted) { logger.warning('Already connecting'); throw Exception('Already connecting'); @@ -417,6 +416,14 @@ class StreamChatClient { _connectCompleter = Completer(); logger.info('connect user'); + + if (user == null) { + final e = Error(); + _connectCompleter! + .completeError(e, StackTrace.fromString('No user provided.')); + throw e; + } + state!.user = OwnUser.fromJson(user.toJson()); this.token = token; _anonymous = false; @@ -638,7 +645,7 @@ class StreamChatClient { bool waitForConnect = true, }) async* { final hash = base64.encode(utf8.encode( - '$filter${_asMap(sort)}$options${paginationParams?.toJson()}' + '$filter${_asMap(sort)}$options${paginationParams.toJson()}' '$messageLimit', )); @@ -731,7 +738,7 @@ class StreamChatClient { QueryChannelsResponse.fromJson, )!; - if ((res.channels ?? []).isEmpty && (paginationParams?.offset ?? 0) == 0) { + if ((res.channels ?? []).isEmpty && (paginationParams.offset) == 0) { logger.warning( ''' We could not find any channel for this query. @@ -758,7 +765,7 @@ class StreamChatClient { filter, channels.map((c) => c.channel!.cid).toList(), clearQueryCache: - paginationParams?.offset == null || paginationParams.offset == 0, + paginationParams.offset == null || paginationParams.offset == 0, ); state!.channels = updateData.key; @@ -976,8 +983,9 @@ class StreamChatClient { .then((res) => decode( res.data, ConnectGuestUserResponse.fromJson)) .whenComplete(() => _anonymous = false); + return connectUser( - (response?.user)!, + response?.user, response?.accessToken, ); } @@ -1008,7 +1016,7 @@ class StreamChatClient { Future _disconnect() async { logger.info('Client disconnecting'); - await _ws?.disconnect(); + await _ws.disconnect(); await _connectionStatusSubscription?.cancel(); } @@ -1427,7 +1435,7 @@ class ClientState { /// Used internally for optimistic update of unread count set totalUnreadCount(int? unreadCount) { - _totalUnreadCountController?.add(unreadCount ?? 0); + _totalUnreadCountController.add(unreadCount ?? 0); } void _listenChannelHidden() { @@ -1473,7 +1481,7 @@ class ClientState { void _updateUsers(List userList) { final newUsers = { - ...users ?? {}, + ...users, for (var user in userList) user!.id: user, }; _usersController.add(newUsers); @@ -1488,7 +1496,8 @@ class ClientState { Stream get userStream => _userController.stream; /// The current user - Map? get users => _usersController.value as Map?; + Map get users => + _usersController.value as Map; /// The current user as a stream Stream> get usersStream => _usersController.stream; diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 6036c649..b3ea7c7e 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -213,7 +213,7 @@ abstract class ChatPersistenceClient { if (m.ownReactions != null) ...m.ownReactions!.map((r) => r.user), ]) - ?.expand((v) => v), + .expand((v) => v), if (cs.read != null) ...cs.read!.map((r) => r.user), if (cs.members != null) ...cs.members!.map((m) => m!.user), ]) diff --git a/packages/stream_chat/lib/src/extensions/rate_limit.dart b/packages/stream_chat/lib/src/extensions/rate_limit.dart index 8ed91481..bf54fe5c 100644 --- a/packages/stream_chat/lib/src/extensions/rate_limit.dart +++ b/packages/stream_chat/lib/src/extensions/rate_limit.dart @@ -124,7 +124,7 @@ class Debounce { Duration? maxWait, }) : _leading = leading, _trailing = trailing, - _wait = wait?.inMilliseconds ?? 0, + _wait = wait.inMilliseconds, _maxing = maxWait != null { if (_maxing) { _maxWait = math.max(maxWait!.inMilliseconds, _wait); diff --git a/packages/stream_chat/lib/src/models/attachment_file.dart b/packages/stream_chat/lib/src/models/attachment_file.dart index 8dac46f6..fa74d801 100644 --- a/packages/stream_chat/lib/src/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/models/attachment_file.dart @@ -87,7 +87,7 @@ class AttachmentFile { final int? size; /// File extension for this file. - String? get extension => name?.split('.')?.last; + String? get extension => name?.split('.').last; /// Serialize to json Map toJson() => _$AttachmentFileToJson(this); diff --git a/packages/stream_chat/lib/src/models/channel_state.dart b/packages/stream_chat/lib/src/models/channel_state.dart index 64c88af7..c90e6298 100644 --- a/packages/stream_chat/lib/src/models/channel_state.dart +++ b/packages/stream_chat/lib/src/models/channel_state.dart @@ -43,8 +43,8 @@ class ChannelState { final List? read; /// Create a new instance from a json - static ChannelState fromJson(Map? json) => - _$ChannelStateFromJson(json!); + static ChannelState fromJson(Map json) => + _$ChannelStateFromJson(json); /// Serialize to json Map toJson() => _$ChannelStateToJson(this); diff --git a/packages/stream_chat/lib/src/models/serialization.dart b/packages/stream_chat/lib/src/models/serialization.dart index 48726d5e..d7cb0ad8 100644 --- a/packages/stream_chat/lib/src/models/serialization.dart +++ b/packages/stream_chat/lib/src/models/serialization.dart @@ -11,7 +11,7 @@ class Serialization { /// List of users to list of userIds static List? userIds(List? users) => - users?.map((u) => u.id)?.toList(); + users?.map((u) => u.id).toList(); /// Takes unknown json keys and puts them in the `extra_data` key static Map? moveToExtraDataFromRoot( diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index defc8db5..c854c327 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: web_socket_channel: ^2.0.0 dev_dependencies: - build_runner: ^1.10.0 + build_runner: ^1.12.2 freezed: ^0.14.1+2 json_serializable: ^4.1.0 mocktail: ^0.1.1 diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 058a8044..94990a7b 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -50,7 +50,7 @@ void main() { ), ); - await channelClient?.sendMessage(message); + await channelClient.sendMessage(message); verify(() => mockDio.post('/channels/messaging/testid/message', data: { @@ -80,7 +80,7 @@ void main() { statusCode: 200, requestOptions: FakeRequestOptions(), )); - await channelClient?.watch(); + await channelClient.watch(); when( () => mockDio.post( @@ -95,7 +95,7 @@ void main() { ), ); - await channelClient?.markRead(); + await channelClient.markRead(); verify(() => mockDio.post('/channels/messaging/testid/read', data: {})).called(1); @@ -124,7 +124,7 @@ void main() { ), ); - await channelClient?.getReplies('messageid', pagination); + await channelClient.getReplies('messageid', pagination); verify(() => mockDio.get('/messages/messageid/replies', queryParameters: pagination.toJson())).called(1); @@ -141,7 +141,7 @@ void main() { httpClient: mockDio, tokenProvider: (_) async => '', ); - Channel channelClient = client.channel('messaging', id: 'testid'); + final channelClient = client.channel('messaging', id: 'testid'); when(() => mockDio.post( any(), @@ -566,9 +566,7 @@ void main() { tokenProvider: (_) async => '', ); - if (client != null) { - client.state?.user = OwnUser(id: 'test-id'); - } + client.state?.user = OwnUser(id: 'test-id'); final channelClient = client.channel('messaging', id: 'testid'); const reactionType = 'test'; @@ -623,9 +621,7 @@ void main() { tokenProvider: (_) async => '', ); - if (client != null) { - client.state?.user = OwnUser(id: 'test-id'); - } + client.state?.user = OwnUser(id: 'test-id'); final channelClient = client.channel('messaging', id: 'testid'); diff --git a/packages/stream_chat/test/src/api/requests_test.dart b/packages/stream_chat/test/src/api/requests_test.dart index 1e46fee0..0e33b3d9 100644 --- a/packages/stream_chat/test/src/api/requests_test.dart +++ b/packages/stream_chat/test/src/api/requests_test.dart @@ -12,7 +12,9 @@ void main() { test('PaginationParams', () { const option = PaginationParams(); final j = option.toJson(); - expect(j, {'limit': 10, 'offset': 0}); + expect(j, containsPair('limit', 10)); + expect(j, containsPair('offset', 0)); + expect(j, contains('hash_code')); }); }); } diff --git a/packages/stream_chat/test/src/api/responses_test.dart b/packages/stream_chat/test/src/api/responses_test.dart index ad81aeff..f91248af 100644 --- a/packages/stream_chat/test/src/api/responses_test.dart +++ b/packages/stream_chat/test/src/api/responses_test.dart @@ -12,7 +12,8 @@ import 'package:stream_chat/stream_chat.dart'; void main() { group('src/api/responses', () { test('QueryChannelsResponse', () { - const jsonExample = r'''{ + const jsonExample = r''' + { "channels": [ { "channel": { @@ -3432,7 +3433,8 @@ void main() { }); test('SendReactionResponse', () { - const jsonExample = r'''{"message": { + const jsonExample = r''' + {"message": { "id": "c6076f11-7768-4a04-bdf2-c43dddd6d666", "text": "What we don't know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.", "html": "\u003cp\u003eWhat we don’t know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.\u003c/p\u003e\n", @@ -3481,7 +3483,8 @@ void main() { }); test('UpdateUsersResponse', () { - const jsonExample = '''{"users": {"bbb19d9a-ee50-45bc-84e5-0584e79d0c9e":{ + const jsonExample = ''' + {"users": {"bbb19d9a-ee50-45bc-84e5-0584e79d0c9e":{ "id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e", "role": "user", "created_at": "2020-01-28T22:17:30.826259Z", @@ -3505,7 +3508,8 @@ void main() { }); test('GetMessagesByIdResponse', () { - const jsonExample = r'''{"messages":[{ + const jsonExample = r''' + {"messages":[{ "id": "c6076f11-7768-4a04-bdf2-c43dddd6d666", "text": "What we don't know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.", "html": "\u003cp\u003eWhat we don’t know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.\u003c/p\u003e\n", @@ -3536,7 +3540,8 @@ void main() { }); test('SendActionResponse', () { - const jsonExample = r'''{"message":{ + const jsonExample = r''' + {"message":{ "id": "c6076f11-7768-4a04-bdf2-c43dddd6d666", "text": "What we don't know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.", "html": "\u003cp\u003eWhat we don’t know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.\u003c/p\u003e\n", @@ -3566,7 +3571,8 @@ void main() { }); test('UpdateMessageResponse', () { - const jsonExample = r'''{"message":{ + const jsonExample = r''' + {"message":{ "id": "c6076f11-7768-4a04-bdf2-c43dddd6d666", "text": "What we don't know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.", "html": "\u003cp\u003eWhat we don’t know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.\u003c/p\u003e\n", @@ -3596,7 +3602,8 @@ void main() { }); test('SendMessageResponse', () { - const jsonExample = r'''{"message":{ + const jsonExample = r''' + {"message":{ "id": "c6076f11-7768-4a04-bdf2-c43dddd6d666", "text": "What we don't know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.", "html": "\u003cp\u003eWhat we don’t know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.\u003c/p\u003e\n", @@ -3626,7 +3633,8 @@ void main() { }); test('GetMessageResponse', () { - const jsonExample = r'''{"message":{ + const jsonExample = r''' + {"message":{ "id": "c6076f11-7768-4a04-bdf2-c43dddd6d666", "text": "What we don't know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.", "html": "\u003cp\u003eWhat we don’t know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.\u003c/p\u003e\n", @@ -3656,7 +3664,8 @@ void main() { }); test('UpdateChannelResponse', () { - const jsonExample = r'''{"message":{ + const jsonExample = r''' + {"message":{ "id": "c6076f11-7768-4a04-bdf2-c43dddd6d666", "text": "What we don't know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.", "html": "\u003cp\u003eWhat we don’t know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.\u003c/p\u003e\n", @@ -3769,7 +3778,8 @@ void main() { }); test('InviteMembersResponse', () { - const jsonExample = r'''{"message":{ + const jsonExample = r''' + {"message":{ "id": "c6076f11-7768-4a04-bdf2-c43dddd6d666", "text": "What we don't know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.", "html": "\u003cp\u003eWhat we don’t know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.\u003c/p\u003e\n", @@ -3882,7 +3892,8 @@ void main() { }); test('RemoveMembersResponse', () { - const jsonExample = r'''{"message":{ + const jsonExample = r''' + {"message":{ "id": "c6076f11-7768-4a04-bdf2-c43dddd6d666", "text": "What we don't know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.", "html": "\u003cp\u003eWhat we don’t know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.\u003c/p\u003e\n", @@ -3995,7 +4006,8 @@ void main() { }); test('AddMembersResponse', () { - const jsonExample = r'''{"message":{ + const jsonExample = r''' + {"message":{ "id": "c6076f11-7768-4a04-bdf2-c43dddd6d666", "text": "What we don't know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.", "html": "\u003cp\u003eWhat we don’t know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.\u003c/p\u003e\n", @@ -4108,7 +4120,8 @@ void main() { }); test('AcceptInviteResponse', () { - const jsonExample = r'''{"message":{ + const jsonExample = r''' + {"message":{ "id": "c6076f11-7768-4a04-bdf2-c43dddd6d666", "text": "What we don't know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.", "html": "\u003cp\u003eWhat we don’t know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.\u003c/p\u003e\n", @@ -4221,7 +4234,8 @@ void main() { }); test('RejectInviteResponse', () { - const jsonExample = r'''{"message":{ + const jsonExample = r''' + {"message":{ "id": "c6076f11-7768-4a04-bdf2-c43dddd6d666", "text": "What we don't know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.", "html": "\u003cp\u003eWhat we don’t know for sure is whether or not a step-daughter of the bear is assumed to be a farci hourglass.\u003c/p\u003e\n", diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 6ae8ee3e..3c0a325c 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -21,7 +21,7 @@ class FakeRequestOptions extends Fake implements RequestOptions {} class MockHttpClientAdapter extends Mock implements HttpClientAdapter {} class Functions { - Future tokenProvider(String userId) => null; + Future tokenProvider(String userId) async => ''; } class MockFunctions extends Mock implements Functions {} @@ -155,7 +155,9 @@ void main() { 'sort': sortOptions, } ..addAll(options) - ..addAll(paginationParams.toJson())), + ..addAll(paginationParams + .toJson() + .map((key, value) => MapEntry(key, value as Object)))), }; when( diff --git a/packages/stream_chat/test/version_test.dart b/packages/stream_chat/test/version_test.dart index 55b1818a..9bbd446f 100644 --- a/packages/stream_chat/test/version_test.dart +++ b/packages/stream_chat/test/version_test.dart @@ -17,8 +17,8 @@ void main() { final String pubspecPath = '${Directory.current.path}/pubspec.yaml'; final String pubspec = File(pubspecPath).readAsStringSync(); final RegExp regex = RegExp('version:\s*(.*)'); - final RegExpMatch match = regex.firstMatch(pubspec); + final RegExpMatch? match = regex.firstMatch(pubspec); expect(match, isNotNull); - expect(PACKAGE_VERSION, match.group(1).trim()); + expect(PACKAGE_VERSION, match?.group(1)?.trim()); }); } From ee617439cd220e3d029d22b65d98a41c1e8521f3 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 12 Apr 2021 19:01:36 +0530 Subject: [PATCH 087/599] merged upper branch --- packages/stream_chat/lib/src/models/attachment.dart | 4 ++-- packages/stream_chat/lib/src/models/message.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/models/attachment.dart index b8244334..0ba66667 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/models/attachment.dart @@ -105,7 +105,7 @@ class Attachment extends Equatable { final AttachmentFile? file; /// The current upload state of the attachment - final UploadState? uploadState; + late final UploadState? uploadState; /// Map of custom channel extraData @JsonKey(includeIfNull: false) @@ -206,7 +206,7 @@ class Attachment extends Equatable { ); @override - List get props => [ + List get props => [ id, type, titleLink, diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index b78501d6..1fb51279 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -349,7 +349,7 @@ class Message extends Equatable { } @override - List get props => [ + List get props => [ id, text, type, From 59b99e1cb20303b1e51592075948f3d85d4361c9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 12 Apr 2021 17:09:39 +0200 Subject: [PATCH 088/599] fix example --- packages/stream_chat/lib/src/client.dart | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 5117f7da..a6ec79d7 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -403,11 +403,11 @@ class StreamChatClient { /// Set the current user, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. @Deprecated('Use `connectUser` instead. Will be removed in Future releases') - Future setUser(User user, String token) => connectUser(user, token); + Future setUser(User user, String token) => connectUser(user, token); /// Connects the current user, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. - Future connectUser(User? user, String? token) async { + Future connectUser(User? user, String? token) async { if (_connectCompleter != null && !_connectCompleter!.isCompleted) { logger.warning('Already connecting'); throw Exception('Already connecting'); @@ -442,11 +442,12 @@ class StreamChatClient { @Deprecated( 'Use `connectUserWithProvider` instead. Will be removed in Future releases', ) - Future setUserWithProvider(User user) => connectUserWithProvider(user); + Future setUserWithProvider(User user) => + connectUserWithProvider(user); /// Connects the current user using the [tokenProvider] to fetch the token. /// It returns a [Future] that resolves when the connection is setup. - Future connectUserWithProvider(User user) async { + Future connectUserWithProvider(User user) async { if (tokenProvider == null) { throw Exception(''' TokenProvider must be provided in the constructor in order to use `connectUserWithProvider` method. @@ -500,7 +501,7 @@ class StreamChatClient { Completer? _connectCompleter; /// Connect the client websocket - Future connect() async { + Future connect() async { logger.info('connecting'); if (wsConnectionStatus == ConnectionStatus.connecting) { logger.warning('Already connecting'); @@ -570,7 +571,7 @@ class StreamChatClient { _connectionStatusSubscription = _ws.connectionStatusStream.listen(_connectionStatusHandler); - var event = (await _chatPersistenceClient?.getConnectionInfo())!; + var event = await _chatPersistenceClient?.getConnectionInfo(); await _ws.connect()!.then((e) async { await _chatPersistenceClient?.updateConnectionInfo(e); @@ -589,7 +590,7 @@ class StreamChatClient { /// Get the events missed while offline to sync the offline storage Future resync([List? cids]) async { - final lastSyncAt = (await _chatPersistenceClient?.getLastSyncAt())!; + final lastSyncAt = await _chatPersistenceClient?.getLastSyncAt(); if (lastSyncAt == null) { _synced = true; @@ -943,12 +944,12 @@ class StreamChatClient { /// the API. It returns a [Future] that resolves when the connection is setup. @Deprecated( 'Use `connectAnonymousUser` instead. Will be removed in Future releases') - Future setAnonymousUser() => connectAnonymousUser(); + Future setAnonymousUser() => connectAnonymousUser(); /// Connects the current user with an anonymous id, this triggers a connection /// to the API. It returns a [Future] that resolves when the connection is /// setup. - Future connectAnonymousUser() async { + Future connectAnonymousUser() async { if (_connectCompleter != null && !_connectCompleter!.isCompleted) { logger.warning('Already connecting'); throw Exception('Already connecting'); @@ -973,11 +974,11 @@ class StreamChatClient { /// It returns a [Future] that resolves when the connection is setup. @Deprecated( 'Use `connectGuestUser` instead. Will be removed in Future releases') - Future setGuestUser(User user) => connectGuestUser(user); + Future setGuestUser(User user) => connectGuestUser(user); /// Connects the current user as guest, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. - Future connectGuestUser(User user) async { + Future connectGuestUser(User user) async { _anonymous = true; final response = await post('/guest', data: {'user': user.toJson()}) .then((res) => decode( From 391eb8c1cefc40331062ad0f36919a1a41741359 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 13 Apr 2021 10:30:06 +0200 Subject: [PATCH 089/599] fix lint --- packages/stream_chat/lib/src/api/channel.dart | 17 +---- packages/stream_chat/lib/src/client.dart | 36 +++++------ .../lib/src/extensions/map_extension.dart | 2 +- .../stream_chat/lib/src/models/message.dart | 63 +++++++++---------- .../stream_chat/lib/src/models/reaction.dart | 21 +++---- 5 files changed, 57 insertions(+), 82 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 3782b4b9..7421daa7 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -321,15 +321,6 @@ class Channel { ).toList(), ); - if (message.parentId != null && message.id == null) { - final parentMessage = - state!.messages!.firstWhere((m) => m.id == message.parentId); - - state?.addMessage(parentMessage.copyWith( - replyCount: parentMessage.replyCount! + 1, - )); - } - state?.addMessage(message); try { @@ -348,7 +339,7 @@ class Channel { message = await attachmentsUploadCompleter.future; } - final response = await (_client.sendMessage(message, id, type)); + final response = await _client.sendMessage(message, id, type); state?.addMessage(response!.message!); return response; } catch (error) { @@ -466,8 +457,7 @@ class Channel { ) { assert(() { if (timeoutOrExpirationDate is! DateTime && - timeoutOrExpirationDate is! num && - timeoutOrExpirationDate != null) { + timeoutOrExpirationDate is! num) { throw ArgumentError('Invalid timeout or Expiration date'); } return true; @@ -997,7 +987,7 @@ class Channel { cid, messagePagination: messagesPagination, ))!; - if (updatedState != null && updatedState.messages!.isNotEmpty) { + if (updatedState.messages!.isNotEmpty) { if (state == null) { _initState(updatedState); } else { @@ -1382,7 +1372,6 @@ class ChannelClientState { [...messages!, ...threads!.values.expand((v) => v)] .where( (message) => - message.status != null && message.status != MessageSendingStatus.sent && message.createdAt!.isBefore( DateTime.now().subtract( diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index a6ec79d7..f6b9da79 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -723,9 +723,7 @@ class StreamChatClient { payload.addAll(options); } - if (paginationParams != null) { - payload.addAll(paginationParams.toJson()); - } + payload.addAll(paginationParams.toJson()); final response = await get( '/channels', @@ -765,8 +763,7 @@ class StreamChatClient { await _chatPersistenceClient?.updateChannelQueries( filter, channels.map((c) => c.channel!.cid).toList(), - clearQueryCache: - paginationParams.offset == null || paginationParams.offset == 0, + clearQueryCache: paginationParams.offset == 0, ); state!.channels = updateData.key; @@ -794,17 +791,15 @@ class StreamChatClient { ) { final channels = {...state!.channels ?? {}}; final newChannels = []; - if (channelStates != null) { - for (final channelState in channelStates) { - final channel = channels[channelState.channel!.cid]; - if (channel != null) { - channel.state?.updateChannelState(channelState); - newChannels.add(channel); - } else { - final newChannel = Channel.fromState(this, channelState); - channels[newChannel.cid] = newChannel; - newChannels.add(newChannel); - } + for (final channelState in channelStates) { + final channel = channels[channelState.channel!.cid]; + if (channel != null) { + channel.state?.updateChannelState(channelState); + newChannels.add(channel); + } else { + final newChannel = Channel.fromState(this, channelState); + channels[newChannel.cid] = newChannel; + newChannels.add(newChannel); } } return MapEntry(channels, newChannels); @@ -1071,7 +1066,7 @@ class StreamChatClient { Map? messageFilters, }) async { assert(() { - if (filters == null || filters.isEmpty) { + if (filters.isEmpty) { throw ArgumentError('`filters` cannot be set as null or empty'); } if (query == null && messageFilters == null) { @@ -1199,9 +1194,7 @@ class StreamChatClient { String? id, Map? extraData, }) { - if (type != null && - id != null && - state!.channels?.containsKey('$type:$id') == true) { + if (id != null && state!.channels?.containsKey('$type:$id') == true) { if (state!.channels!['$type:$id'] != null) { return state!.channels!['$type:$id'] as Channel; } @@ -1369,8 +1362,7 @@ class StreamChatClient { ) { assert(() { if (timeoutOrExpirationDate is! DateTime && - timeoutOrExpirationDate is! num && - timeoutOrExpirationDate != null) { + timeoutOrExpirationDate is! num) { throw ArgumentError('Invalid timeout or Expiration date'); } return true; diff --git a/packages/stream_chat/lib/src/extensions/map_extension.dart b/packages/stream_chat/lib/src/extensions/map_extension.dart index af57a35a..8b1cb51f 100644 --- a/packages/stream_chat/lib/src/extensions/map_extension.dart +++ b/packages/stream_chat/lib/src/extensions/map_extension.dart @@ -1,6 +1,6 @@ /// Useful extension functions for [Map] extension MapX on Map { /// Returns a new map with null keys or values removed - Map get nullProtected => {...this as Map} + Map get nullProtected => {...this as Map} ..removeWhere((key, value) => key == null || value == null); } diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index 1fb51279..1849eb81 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -314,39 +314,36 @@ class Message extends Equatable { /// Returns a new [Message] that is a combination of this message and the /// given [other] message. - Message merge(Message other) { - if (other == null) return this; - return copyWith( - id: other.id, - text: other.text, - type: other.type, - attachments: other.attachments, - mentionedUsers: other.mentionedUsers, - reactionCounts: other.reactionCounts, - reactionScores: other.reactionScores, - latestReactions: other.latestReactions, - ownReactions: other.ownReactions, - parentId: other.parentId, - quotedMessage: other.quotedMessage, - quotedMessageId: other.quotedMessageId, - replyCount: other.replyCount, - threadParticipants: other.threadParticipants, - showInChannel: other.showInChannel, - command: other.command, - createdAt: other.createdAt, - silent: other.silent, - extraData: other.extraData, - user: other.user, - shadowed: other.shadowed, - updatedAt: other.updatedAt, - deletedAt: other.deletedAt, - status: other.status, - pinned: other.pinned, - pinnedAt: other.pinnedAt, - pinExpires: other.pinExpires, - pinnedBy: other.pinnedBy, - ); - } + Message merge(Message other) => copyWith( + id: other.id, + text: other.text, + type: other.type, + attachments: other.attachments, + mentionedUsers: other.mentionedUsers, + reactionCounts: other.reactionCounts, + reactionScores: other.reactionScores, + latestReactions: other.latestReactions, + ownReactions: other.ownReactions, + parentId: other.parentId, + quotedMessage: other.quotedMessage, + quotedMessageId: other.quotedMessageId, + replyCount: other.replyCount, + threadParticipants: other.threadParticipants, + showInChannel: other.showInChannel, + command: other.command, + createdAt: other.createdAt, + silent: other.silent, + extraData: other.extraData, + user: other.user, + shadowed: other.shadowed, + updatedAt: other.updatedAt, + deletedAt: other.deletedAt, + status: other.status, + pinned: other.pinned, + pinnedAt: other.pinnedAt, + pinExpires: other.pinExpires, + pinnedBy: other.pinnedBy, + ); @override List get props => [ diff --git a/packages/stream_chat/lib/src/models/reaction.dart b/packages/stream_chat/lib/src/models/reaction.dart index 42c23841..9f7b5851 100644 --- a/packages/stream_chat/lib/src/models/reaction.dart +++ b/packages/stream_chat/lib/src/models/reaction.dart @@ -83,16 +83,13 @@ class Reaction { /// Returns a new [Reaction] that is a combination of this reaction and the /// given [other] reaction. - Reaction merge(Reaction other) { - if (other == null) return this; - return copyWith( - messageId: other.messageId, - createdAt: other.createdAt, - type: other.type, - user: other.user, - userId: other.userId, - score: other.score, - extraData: other.extraData, - ); - } + Reaction merge(Reaction other) => copyWith( + messageId: other.messageId, + createdAt: other.createdAt, + type: other.type, + user: other.user, + userId: other.userId, + score: other.score, + extraData: other.extraData, + ); } From fe07b8dbb0010bdcb809bf7700cd8759a5a463a1 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 13 Apr 2021 18:20:43 +0530 Subject: [PATCH 090/599] feat: Converted to ios models --- packages/stream_chat/example/lib/main.dart | 2 +- packages/stream_chat/lib/src/api/channel.dart | 102 +++++++++--------- packages/stream_chat/lib/src/client.dart | 6 +- .../lib/src/db/chat_persistence_client.dart | 31 +++--- .../lib/src/models/attachment.dart | 4 +- .../lib/src/models/attachment.g.dart | 7 +- .../lib/src/models/channel_model.dart | 39 ++++--- .../lib/src/models/channel_model.g.dart | 22 ++-- .../lib/src/models/channel_state.dart | 10 +- .../lib/src/models/channel_state.g.dart | 30 +++--- .../stream_chat/lib/src/models/device.dart | 4 +- .../stream_chat/lib/src/models/device.g.dart | 2 +- .../stream_chat/lib/src/models/event.dart | 12 +-- .../stream_chat/lib/src/models/event.g.dart | 22 ++-- .../stream_chat/lib/src/models/member.dart | 24 ++--- .../stream_chat/lib/src/models/member.g.dart | 20 ++-- .../stream_chat/lib/src/models/message.dart | 57 ++++++++-- .../stream_chat/lib/src/models/message.g.dart | 12 +-- .../stream_chat/lib/src/models/own_user.dart | 50 ++++++--- .../lib/src/models/own_user.g.dart | 37 +++---- .../stream_chat/lib/src/models/reaction.dart | 18 ++-- .../lib/src/models/reaction.g.dart | 16 ++- packages/stream_chat/lib/src/models/read.dart | 12 +-- .../stream_chat/lib/src/models/read.g.dart | 18 ++-- packages/stream_chat/lib/src/models/user.dart | 64 ++++++----- .../stream_chat/lib/src/models/user.g.dart | 24 ++--- .../test/src/api/channel_test.dart | 41 ++++--- .../test/src/api/websocket_test.dart | 16 +-- .../stream_chat/test/src/client_test.dart | 24 +++-- .../test/src/models/channel_state_test.dart | 4 +- .../test/src/models/channel_test.dart | 4 +- .../test/src/models/event_test.dart | 4 +- .../test/src/models/message_test.dart | 2 +- .../test/src/models/reaction_test.dart | 6 +- .../test/src/models/read_test.dart | 2 +- .../test/src/models/user_test.dart | 2 +- 36 files changed, 406 insertions(+), 344 deletions(-) diff --git a/packages/stream_chat/example/lib/main.dart b/packages/stream_chat/example/lib/main.dart index d13dda4c..878b5fa1 100644 --- a/packages/stream_chat/example/lib/main.dart +++ b/packages/stream_chat/example/lib/main.dart @@ -11,7 +11,7 @@ Future main() async { /// Please see the following for more information: /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ await client.connectUser( - User( + User.temp( id: 'cool-shadow-7', extraData: { 'image': diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 7421daa7..80f34ccb 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -59,12 +59,12 @@ class Channel { /// Returns true if the channel is muted bool get isMuted => _client.state!.user?.channelMutes - ?.any((element) => element.channel!.cid == cid) == + .any((element) => element.channel!.cid == cid) == true; /// Returns true if the channel is muted as a stream Stream? get isMutedStream => _client.state!.userStream.map((event) => - event!.channelMutes?.any((element) => element.channel!.cid == cid) == + event!.channelMutes.any((element) => element.channel!.cid == cid) == true); /// True if the channel is a group @@ -309,7 +309,7 @@ class Channel { ); // ignore: parameter_assignments message = message.copyWith( - createdAt: message.createdAt ?? DateTime.now(), + createdAt: message.createdAt, user: _client.state!.user, quotedMessage: quotedMessage, status: MessageSendingStatus.sending, @@ -363,7 +363,7 @@ class Channel { // ignore: parameter_assignments message = message.copyWith( status: MessageSendingStatus.updating, - updatedAt: message.updatedAt ?? DateTime.now(), + updatedAt: message.updatedAt, attachments: message.attachments?.map( (it) { if (it.uploadState.isSuccess) return it; @@ -574,7 +574,7 @@ class Channel { messageId: messageId, createdAt: now, type: type, - user: user, + user: user!, score: 1, extraData: extraData, ); @@ -582,7 +582,7 @@ class Channel { // Inserting at the 0th index as it's the latest reaction latestReactions.insert(0, newReaction); final ownReactions = [...latestReactions] - ..removeWhere((it) => it.userId != user!.id); + ..removeWhere((it) => it.userId != user.id); final newMessage = message.copyWith( reactionCounts: {...message.reactionCounts ?? {}} @@ -632,15 +632,11 @@ class Channel { final reactionCounts = {...message.reactionCounts ?? {}}; if (reactionCounts.containsKey(type)) { - if (type != null) { - reactionCounts.update(type, (value) => value - 1); - } + reactionCounts.update(type, (value) => value - 1); } final reactionScores = {...message.reactionScores ?? {}}; if (reactionScores.containsKey(type)) { - if (type != null) { - reactionScores.update(type, (value) => value - 1); - } + reactionScores.update(type, (value) => value - 1); } final latestReactions = [...message.latestReactions ?? []] @@ -917,7 +913,11 @@ class Channel { GetMessagesByIdResponse.fromJson, )!; - state?.updateChannelState(ChannelState(messages: res.messages)); + final messages = res.messages; + + if (messages != null) { + state?.updateChannelState(ChannelState(messages: messages)); + } return res; } @@ -987,7 +987,7 @@ class Channel { cid, messagePagination: messagesPagination, ))!; - if (updatedState.messages!.isNotEmpty) { + if (updatedState.messages.isNotEmpty) { if (state == null) { _initState(updatedState); } else { @@ -1270,17 +1270,17 @@ class ChannelClientState { final _subscriptions = []; void _computeInitialUnread() { - final userRead = channelState?.read?.firstWhereOrNull( - (r) => r.user!.id == _channel._client.state?.user?.id, + final userRead = channelState?.read.firstWhereOrNull( + (r) => r.user.id == _channel._client.state?.user?.id, ); if (userRead != null) { - _unreadCountController.add(userRead.unreadMessages ?? 0); + _unreadCountController.add(userRead.unreadMessages); } } void _checkExpiredAttachmentMessages(ChannelState channelState) { final expiredAttachmentMessagesId = channelState.messages - ?.where((m) => + .where((m) => !_updatedMessagesIds.contains(m.id) && m.attachments?.isNotEmpty == true && m.attachments?.any((e) { @@ -1300,8 +1300,8 @@ class ChannelClientState { true) .map((e) => e.id) .toList(); - if (expiredAttachmentMessagesId?.isNotEmpty == true) { - _channel.getMessagesById(expiredAttachmentMessagesId!); + if (expiredAttachmentMessagesId.isNotEmpty == true) { + _channel.getMessagesById(expiredAttachmentMessagesId); _updatedMessagesIds.addAll(expiredAttachmentMessagesId); } } @@ -1311,7 +1311,7 @@ class ChannelClientState { final member = e.member; updateChannelState(channelState!.copyWith( members: [ - ...channelState!.members!, + ...channelState!.members, member, ], )); @@ -1323,7 +1323,7 @@ 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)), )); })); } @@ -1373,7 +1373,7 @@ class ChannelClientState { .where( (message) => message.status != MessageSendingStatus.sent && - message.createdAt!.isBefore( + message.createdAt.isBefore( DateTime.now().subtract( const Duration( seconds: 1, @@ -1425,7 +1425,7 @@ class ChannelClientState { if (message.pinned == true) { _channelState = _channelState!.copyWith( pinnedMessages: [ - ..._channelState!.pinnedMessages ?? [], + ..._channelState!.pinnedMessages, message, ], ); @@ -1462,7 +1462,7 @@ class ChannelClientState { /// Add a message to this channel void addMessage(Message message) { if (message.parentId == null || message.showInChannel == true) { - final newMessages = List.from(_channelState!.messages!); + final newMessages = List.from(_channelState!.messages); final oldIndex = newMessages.indexWhere((m) => m.id == message.id); if (oldIndex != -1) { Message? m; @@ -1505,16 +1505,17 @@ class ChannelClientState { (event) { final readList = List.from(_channelState?.read ?? []); final userReadIndex = - read?.indexWhere((r) => r.user!.id == event.user!.id); + read?.indexWhere((r) => r.user.id == event.user!.id); if (userReadIndex != null && userReadIndex != -1) { final userRead = readList.removeAt(userReadIndex); - if (userRead.user?.id == _channel._client.state!.user!.id) { + if (userRead.user.id == _channel._client.state!.user!.id) { _unreadCountController.add(0); } readList.add(Read( - user: event.user, - lastRead: event.createdAt, + user: event.user!, + lastRead: event.createdAt!, + unreadMessages: event.totalUnreadCount!, )); _channelState = _channelState!.copyWith(read: readList); } @@ -1531,15 +1532,15 @@ class ChannelClientState { channelStateStream.map((cs) => cs!.messages); /// Channel pinned message list - List? get pinnedMessages => _channelState!.pinnedMessages?.toList(); + List? get pinnedMessages => _channelState!.pinnedMessages.toList(); /// Channel pinned message list as a stream Stream?> get pinnedMessagesStream => - channelStateStream.map((cs) => cs!.pinnedMessages?.toList()); + channelStateStream.map((cs) => cs!.pinnedMessages.toList()); /// Get channel last message - Message? get lastMessage => _channelState!.messages?.isNotEmpty == true - ? _channelState!.messages!.last + Message? get lastMessage => _channelState!.messages.isNotEmpty == true + ? _channelState!.messages.last : null; /// Get channel last message @@ -1547,8 +1548,8 @@ class ChannelClientState { .map((event) => event?.isNotEmpty == true ? event!.last : null); /// Channel members list - List get members => _channelState!.members! - .map((e) => e!.copyWith(user: _channel.client.state!.users[e.user!.id!])) + List get members => _channelState!.members + .map((e) => e!.copyWith(user: _channel.client.state!.users[e.user!.id])) .toList(); /// Channel members list as a stream @@ -1568,8 +1569,8 @@ class ChannelClientState { channelStateStream.map((cs) => cs!.watcherCount); /// Channel watchers list - List get watchers => _channelState!.watchers! - .map((e) => _channel.client.state!.users[e.id!] ?? e) + List get watchers => _channelState!.watchers + .map((e) => _channel.client.state!.users[e.id] ?? e) .toList(); /// Channel watchers list as a stream @@ -1597,7 +1598,7 @@ class ChannelClientState { bool _countMessageAsUnread(Message message) { final userId = _channel.client.state?.user?.id; - final userIsMuted = _channel.client.state?.user?.mutes?.firstWhereOrNull( + final userIsMuted = _channel.client.state?.user?.mutes.firstWhereOrNull( (m) => m.user?.id == message.user!.id, ) != null; @@ -1642,37 +1643,37 @@ class ChannelClientState { /// Update channelState with updated information void updateChannelState(ChannelState updatedState) { final newMessages = [ - ...updatedState.messages ?? [], + ...updatedState.messages, ..._channelState?.messages - ?.where((m) => + .where((m) => updatedState.messages - ?.any((newMessage) => newMessage.id == m.id) != + .any((newMessage) => newMessage.id == m.id) != true) .toList() ?? [], ]..sort(_sortByCreatedAt as int Function(Message, Message)?); final newWatchers = [ - ...updatedState.watchers ?? [], + ...updatedState.watchers, ..._channelState?.watchers - ?.where((w) => + .where((w) => updatedState.watchers - ?.any((newWatcher) => newWatcher.id == w.id) != + .any((newWatcher) => newWatcher.id == w.id) != true) .toList() ?? [], ]; final newMembers = [ - ...updatedState.members ?? [], + ...updatedState.members, ]; final newReads = [ - ...updatedState.read ?? [], + ...updatedState.read, ..._channelState?.read - ?.where((r) => + .where((r) => updatedState.read - ?.any((newRead) => newRead.user!.id == r.user!.id) != + .any((newRead) => newRead.user.id == r.user.id) != true) .toList() ?? [], @@ -1828,9 +1829,8 @@ class ChannelClientState { _pinnedMessagesTimer = Timer.periodic(const Duration(seconds: 30), (_) { final now = DateTime.now(); var expiredMessages = channelState!.pinnedMessages - ?.where((m) => m.pinExpires?.isBefore(now) == true) - .toList() ?? - []; + .where((m) => m.pinExpires?.isBefore(now) == true) + .toList(); if (expiredMessages.isNotEmpty) { expiredMessages = expiredMessages .map((m) => m.copyWith( diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index f6b9da79..4b06788e 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -311,7 +311,7 @@ class StreamChatClient { httpClient.unlock(); - await connectUser(User(id: userId), newToken); + await connectUser(User.temp(id: userId), newToken); try { handler.resolve( @@ -750,7 +750,7 @@ class StreamChatClient { final channels = res.channels!; final users = channels - .expand((it) => it.members!) + .expand((it) => it.members) .map((it) => it!.user) .toList(growable: false); @@ -954,7 +954,7 @@ class StreamChatClient { _anonymous = true; const uuid = Uuid(); - state!.user = OwnUser(id: uuid.v4()); + state!.user = OwnUser.temp(id: uuid.v4()); return connect().then((event) { _connectCompleter!.complete(event); diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index b3ea7c7e..7911a986 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -76,11 +76,11 @@ abstract class ChatPersistenceClient { getPinnedMessagesByCid(cid, messagePagination: pinnedMessagePagination), ]); return ChannelState( - members: data[0] as List?, - read: data[1] as List?, + members: (data[0] as List?)!, + read: (data[1] as List?)!, channel: data[2] as ChannelModel?, - messages: data[3] as List?, - pinnedMessages: data[4] as List?, + messages: (data[3] as List?)!, + pinnedMessages: (data[4] as List?)!, ); } @@ -176,7 +176,7 @@ abstract class ChatPersistenceClient { /// Update list of channel states Future updateChannelStates(List channelStates) async { final deleteReactions = deleteReactionsByMessageId(channelStates - .expand((it) => it.messages!) + .expand((it) => it.messages) .map((m) => m.id) .toList(growable: false)); @@ -193,20 +193,19 @@ abstract class ChatPersistenceClient { channelStates.map((it) => it.channel).where((it) => it != null); final reactions = channelStates - .expand((it) => it.messages!) + .expand((it) => it.messages) .expand((it) => [ if (it.ownReactions != null) ...it.ownReactions!.where((r) => r.userId != null), if (it.latestReactions != null) ...it.latestReactions!.where((r) => r.userId != null) - ]) - .where((it) => it != null); + ]); final users = channelStates .map((cs) => [ cs.channel?.createdBy, - ...?cs.messages - ?.map((m) => [ + ...cs.messages + .map((m) => [ m.user, if (m.latestReactions != null) ...m.latestReactions!.map((r) => r.user), @@ -214,33 +213,33 @@ abstract class ChatPersistenceClient { ...m.ownReactions!.map((r) => r.user), ]) .expand((v) => v), - if (cs.read != null) ...cs.read!.map((r) => r.user), - if (cs.members != null) ...cs.members!.map((m) => m!.user), + ...cs.read.map((r) => r.user), + ...cs.members.map((m) => m!.user), ]) .expand((it) => it) .where((it) => it != null); final updateMessagesFuture = channelStates.map((it) { final cid = it.channel!.cid; - final messages = it.messages!.where((it) => it != null); + final messages = it.messages; return updateMessages(cid, messages.toList(growable: false)); }).toList(growable: false); final updatePinnedMessagesFuture = channelStates.map((it) { final cid = it.channel!.cid; - final messages = it.pinnedMessages!.where((it) => it != null); + final messages = it.pinnedMessages; return updatePinnedMessages(cid, messages.toList(growable: false)); }).toList(growable: false); final updateReadsFuture = channelStates.map((it) { final cid = it.channel!.cid; - final reads = it.read?.where((it) => it != null) ?? []; + final reads = it.read; return updateReads(cid, reads.toList(growable: false)); }).toList(growable: false); final updateMembersFuture = channelStates.map((it) { final cid = it.channel!.cid; - final members = it.members!.where((it) => it != null); + final members = it.members.where((it) => it != null); return updateMembers(cid, members.toList(growable: false)); }).toList(growable: false); diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/models/attachment.dart index 0ba66667..61f7afdf 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/models/attachment.dart @@ -15,7 +15,7 @@ class Attachment extends Equatable { /// Constructor used for json serialization Attachment({ String? id, - this.type, + required this.type, this.titleLink, String? title, this.thumbUrl, @@ -57,7 +57,7 @@ class Attachment extends Equatable { ///The attachment type based on the URL resource. This can be: audio, ///image or video - final String? type; + final String type; ///The link to which the attachment message points to. final String? titleLink; diff --git a/packages/stream_chat/lib/src/models/attachment.g.dart b/packages/stream_chat/lib/src/models/attachment.g.dart index a992096f..207cc02f 100644 --- a/packages/stream_chat/lib/src/models/attachment.g.dart +++ b/packages/stream_chat/lib/src/models/attachment.g.dart @@ -9,7 +9,7 @@ part of 'attachment.dart'; Attachment _$AttachmentFromJson(Map json) { return Attachment( id: json['id'] as String?, - type: json['type'] as String?, + type: json['type'] as String, titleLink: json['title_link'] as String?, title: json['title'] as String?, thumbUrl: json['thumb_url'] as String?, @@ -44,7 +44,9 @@ Attachment _$AttachmentFromJson(Map json) { } Map _$AttachmentToJson(Attachment instance) { - final val = {}; + final val = { + 'type': instance.type, + }; void writeNotNull(String key, dynamic value) { if (value != null) { @@ -52,7 +54,6 @@ Map _$AttachmentToJson(Attachment instance) { } } - writeNotNull('type', instance.type); writeNotNull('title_link', instance.titleLink); writeNotNull('title', instance.title); writeNotNull('thumb_url', instance.thumbUrl); diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index 827f1246..caffc30a 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -12,19 +12,34 @@ class ChannelModel { ChannelModel({ this.id, this.type, - this.cid, - this.config, + required this.cid, + required this.config, this.createdBy, - this.frozen, + this.frozen = false, this.lastMessageAt, - this.createdAt, - this.updatedAt, + required this.createdAt, + required this.updatedAt, this.deletedAt, - this.memberCount, + this.memberCount = 0, this.extraData, this.team, }); + ChannelModel.temp({ + this.id, + this.type, + required this.cid, + this.createdBy, + this.frozen = false, + this.lastMessageAt, + this.deletedAt, + this.memberCount = 0, + this.extraData, + this.team, + }) : createdAt = DateTime.now(), + updatedAt = DateTime.now(), + config = ChannelConfig(); + /// Create a new instance from a json factory ChannelModel.fromJson(Map? json) => _$ChannelModelFromJson( @@ -38,11 +53,11 @@ class ChannelModel { /// The cid of this channel @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final String? cid; + final String cid; /// The channel configuration data @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final ChannelConfig? config; + final ChannelConfig config; /// The user that created this channel @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @@ -50,7 +65,7 @@ class ChannelModel { /// True if this channel is frozen @JsonKey(includeIfNull: false) - final bool? frozen; + final bool frozen; /// The date of the last message @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @@ -58,11 +73,11 @@ class ChannelModel { /// The date of channel creation @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime? createdAt; + final DateTime createdAt; /// The date of the last channel update @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime? updatedAt; + final DateTime updatedAt; /// The date of channel deletion @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @@ -70,7 +85,7 @@ class ChannelModel { /// The count of this channel members @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final int? memberCount; + final int memberCount; /// Map of custom channel extraData @JsonKey(includeIfNull: false) diff --git a/packages/stream_chat/lib/src/models/channel_model.g.dart b/packages/stream_chat/lib/src/models/channel_model.g.dart index 9b1a6aeb..098a22e8 100644 --- a/packages/stream_chat/lib/src/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/models/channel_model.g.dart @@ -10,30 +10,24 @@ ChannelModel _$ChannelModelFromJson(Map json) { return ChannelModel( id: json['id'] as String?, type: json['type'] as String?, - cid: json['cid'] as String?, - config: json['config'] == null - ? null - : ChannelConfig.fromJson( - Map.from(json['config'] as Map)), + cid: json['cid'] as String, + config: ChannelConfig.fromJson( + Map.from(json['config'] as Map)), createdBy: json['created_by'] == null ? null : User.fromJson((json['created_by'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - frozen: json['frozen'] as bool?, + frozen: json['frozen'] as bool, lastMessageAt: json['last_message_at'] == null ? null : DateTime.parse(json['last_message_at'] as String), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), - memberCount: json['member_count'] as int?, + memberCount: json['member_count'] as int, extraData: (json['extra_data'] as Map?)?.map( (k, e) => MapEntry(k as String, e), ), @@ -56,7 +50,7 @@ Map _$ChannelModelToJson(ChannelModel instance) { writeNotNull('cid', readonly(instance.cid)); writeNotNull('config', readonly(instance.config)); writeNotNull('created_by', readonly(instance.createdBy)); - writeNotNull('frozen', instance.frozen); + val['frozen'] = instance.frozen; writeNotNull('last_message_at', readonly(instance.lastMessageAt)); writeNotNull('created_at', readonly(instance.createdAt)); writeNotNull('updated_at', readonly(instance.updatedAt)); diff --git a/packages/stream_chat/lib/src/models/channel_state.dart b/packages/stream_chat/lib/src/models/channel_state.dart index c90e6298..5f9f02bf 100644 --- a/packages/stream_chat/lib/src/models/channel_state.dart +++ b/packages/stream_chat/lib/src/models/channel_state.dart @@ -25,22 +25,22 @@ class ChannelState { final ChannelModel? channel; /// A paginated list of channel messages - final List? messages; + final List messages; /// A paginated list of channel members - final List? members; + final List members; /// A paginated list of pinned messages - final List? pinnedMessages; + final List pinnedMessages; /// The count of users watching the channel final int? watcherCount; /// A paginated list of users watching the channel - final List? watchers; + final List watchers; /// The list of channel reads - final List? read; + final List read; /// Create a new instance from a json static ChannelState fromJson(Map json) => diff --git a/packages/stream_chat/lib/src/models/channel_state.g.dart b/packages/stream_chat/lib/src/models/channel_state.g.dart index 2eaa02d2..c207a2f6 100644 --- a/packages/stream_chat/lib/src/models/channel_state.g.dart +++ b/packages/stream_chat/lib/src/models/channel_state.g.dart @@ -13,29 +13,29 @@ ChannelState _$ChannelStateFromJson(Map json) { : ChannelModel.fromJson((json['channel'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - messages: (json['messages'] as List?) - ?.map((e) => Message.fromJson((e as Map?)?.map( + messages: (json['messages'] as List) + .map((e) => Message.fromJson((e as Map?)?.map( (k, e) => MapEntry(k as String, e), ))) .toList(), - members: (json['members'] as List?) - ?.map((e) => e == null + members: (json['members'] as List) + .map((e) => e == null ? null : Member.fromJson(Map.from(e as Map))) .toList(), - pinnedMessages: (json['pinned_messages'] as List?) - ?.map((e) => Message.fromJson((e as Map?)?.map( + pinnedMessages: (json['pinned_messages'] as List) + .map((e) => Message.fromJson((e as Map?)?.map( (k, e) => MapEntry(k as String, e), ))) .toList(), watcherCount: json['watcher_count'] as int?, - watchers: (json['watchers'] as List?) - ?.map((e) => User.fromJson((e as Map?)?.map( + watchers: (json['watchers'] as List) + .map((e) => User.fromJson((e as Map?)?.map( (k, e) => MapEntry(k as String, e), ))) .toList(), - read: (json['read'] as List?) - ?.map((e) => Read.fromJson(Map.from(e as Map))) + read: (json['read'] as List) + .map((e) => Read.fromJson(Map.from(e as Map))) .toList(), ); } @@ -43,11 +43,11 @@ ChannelState _$ChannelStateFromJson(Map json) { Map _$ChannelStateToJson(ChannelState instance) => { 'channel': instance.channel?.toJson(), - 'messages': instance.messages?.map((e) => e.toJson()).toList(), - 'members': instance.members?.map((e) => e?.toJson()).toList(), + 'messages': instance.messages.map((e) => e.toJson()).toList(), + 'members': instance.members.map((e) => e?.toJson()).toList(), 'pinned_messages': - instance.pinnedMessages?.map((e) => e.toJson()).toList(), + instance.pinnedMessages.map((e) => e.toJson()).toList(), 'watcher_count': instance.watcherCount, - 'watchers': instance.watchers?.map((e) => e.toJson()).toList(), - 'read': instance.read?.map((e) => e.toJson()).toList(), + 'watchers': instance.watchers.map((e) => e.toJson()).toList(), + 'read': instance.read.map((e) => e.toJson()).toList(), }; diff --git a/packages/stream_chat/lib/src/models/device.dart b/packages/stream_chat/lib/src/models/device.dart index 28481282..dc613b3c 100644 --- a/packages/stream_chat/lib/src/models/device.dart +++ b/packages/stream_chat/lib/src/models/device.dart @@ -7,7 +7,7 @@ part 'device.g.dart'; class Device { /// Constructor used for json serialization Device({ - this.id, + required this.id, this.pushProvider, }); @@ -15,7 +15,7 @@ class Device { factory Device.fromJson(Map json) => _$DeviceFromJson(json); /// The id of the device - final String? id; + final String id; /// The notification push provider final String? pushProvider; diff --git a/packages/stream_chat/lib/src/models/device.g.dart b/packages/stream_chat/lib/src/models/device.g.dart index ff4acc89..343ec0e5 100644 --- a/packages/stream_chat/lib/src/models/device.g.dart +++ b/packages/stream_chat/lib/src/models/device.g.dart @@ -8,7 +8,7 @@ part of 'device.dart'; Device _$DeviceFromJson(Map json) { return Device( - id: json['id'] as String?, + id: json['id'] as String, pushProvider: json['push_provider'] as String?, ); } diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index ab97d86f..295d6d8d 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -171,15 +171,15 @@ class EventChannel extends ChannelModel { this.members, String? id, String? type, - String? cid, - ChannelConfig? config, + required String cid, + required ChannelConfig config, User? createdBy, - bool? frozen, + bool frozen = false, DateTime? lastMessageAt, - DateTime? createdAt, - DateTime? updatedAt, + required DateTime createdAt, + required DateTime updatedAt, DateTime? deletedAt, - int? memberCount, + required int memberCount, Map? extraData, }) : super( id: id, diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index 71566caf..5e1e7e58 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -92,30 +92,24 @@ EventChannel _$EventChannelFromJson(Map json) { .toList(), id: json['id'] as String?, type: json['type'] as String?, - cid: json['cid'] as String?, - config: json['config'] == null - ? null - : ChannelConfig.fromJson( - Map.from(json['config'] as Map)), + cid: json['cid'] as String, + config: ChannelConfig.fromJson( + Map.from(json['config'] as Map)), createdBy: json['created_by'] == null ? null : User.fromJson((json['created_by'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), - frozen: json['frozen'] as bool?, + frozen: json['frozen'] as bool, lastMessageAt: json['last_message_at'] == null ? null : DateTime.parse(json['last_message_at'] as String), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), - memberCount: json['member_count'] as int?, + memberCount: json['member_count'] as int, extraData: (json['extra_data'] as Map?)?.map( (k, e) => MapEntry(k as String, e), ), @@ -137,7 +131,7 @@ Map _$EventChannelToJson(EventChannel instance) { writeNotNull('cid', readonly(instance.cid)); writeNotNull('config', readonly(instance.config)); writeNotNull('created_by', readonly(instance.createdBy)); - writeNotNull('frozen', instance.frozen); + val['frozen'] = instance.frozen; writeNotNull('last_message_at', readonly(instance.lastMessageAt)); writeNotNull('created_at', readonly(instance.createdAt)); writeNotNull('updated_at', readonly(instance.updatedAt)); diff --git a/packages/stream_chat/lib/src/models/member.dart b/packages/stream_chat/lib/src/models/member.dart index bd2bed8a..735e4ff3 100644 --- a/packages/stream_chat/lib/src/models/member.dart +++ b/packages/stream_chat/lib/src/models/member.dart @@ -12,14 +12,14 @@ class Member { this.user, this.inviteAcceptedAt, this.inviteRejectedAt, - this.invited, - this.role, + this.invited = false, + required this.role, this.userId, this.isModerator, - this.createdAt, - this.updatedAt, - this.banned, - this.shadowBanned, + required this.createdAt, + required this.updatedAt, + this.banned = false, + this.shadowBanned = false, }); /// Create a new instance from a json @@ -40,10 +40,10 @@ class Member { final DateTime? inviteRejectedAt; /// True if the user has been invited to the channel - final bool? invited; + final bool invited; /// The role of the user in the channel - final String? role; + final String role; /// The id of the interested user final String? userId; @@ -52,16 +52,16 @@ class Member { final bool? isModerator; /// True if the member is banned from the channel - final bool? banned; + final bool banned; /// True if the member is shadow banned from the channel - final bool? shadowBanned; + final bool shadowBanned; /// The date of creation - final DateTime? createdAt; + final DateTime createdAt; /// The last date of update - final DateTime? updatedAt; + final DateTime updatedAt; /// Creates a copy of [Member] with specified attributes overridden. Member copyWith({ diff --git a/packages/stream_chat/lib/src/models/member.g.dart b/packages/stream_chat/lib/src/models/member.g.dart index 52391699..f6716e3d 100644 --- a/packages/stream_chat/lib/src/models/member.g.dart +++ b/packages/stream_chat/lib/src/models/member.g.dart @@ -19,18 +19,14 @@ Member _$MemberFromJson(Map json) { inviteRejectedAt: json['invite_rejected_at'] == null ? null : DateTime.parse(json['invite_rejected_at'] as String), - invited: json['invited'] as bool?, - role: json['role'] as String?, + invited: json['invited'] as bool, + role: json['role'] as String, userId: json['user_id'] as String?, isModerator: json['is_moderator'] as bool?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - banned: json['banned'] as bool?, - shadowBanned: json['shadow_banned'] as bool?, + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + banned: json['banned'] as bool, + shadowBanned: json['shadow_banned'] as bool, ); } @@ -44,6 +40,6 @@ Map _$MemberToJson(Member instance) => { 'is_moderator': instance.isModerator, 'banned': instance.banned, 'shadow_banned': instance.shadowBanned, - 'created_at': instance.createdAt?.toIso8601String(), - 'updated_at': instance.updatedAt?.toIso8601String(), + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), }; diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index 1849eb81..8f260b1b 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -46,8 +46,8 @@ class Message extends Equatable { /// Constructor used for json serialization Message({ String? id, - this.text, - this.type, + required this.text, + required this.type, this.attachments, this.mentionedUsers, this.silent, @@ -63,8 +63,8 @@ class Message extends Equatable { this.threadParticipants, this.showInChannel, this.command, - this.createdAt, - this.updatedAt, + required this.createdAt, + required this.updatedAt, this.user, this.pinned = false, this.pinnedAt, @@ -77,6 +77,40 @@ class Message extends Equatable { }) : id = id ?? const Uuid().v4(), pinExpires = pinExpires?.toUtc(); + /// Constructor for creating temporary/throwaway message with id and text + Message.temp({ + String? id, + required this.text, + this.attachments, + this.mentionedUsers, + this.silent, + this.shadowed, + this.reactionCounts, + this.reactionScores, + this.latestReactions, + this.ownReactions, + this.parentId, + this.quotedMessage, + this.quotedMessageId, + this.replyCount = 0, + this.threadParticipants, + this.showInChannel, + this.command, + this.user, + this.pinned = false, + this.pinnedAt, + DateTime? pinExpires, + this.pinnedBy, + this.extraData, + this.deletedAt, + this.status = MessageSendingStatus.sent, + this.skipPush, + }) : id = id ?? const Uuid().v4(), + pinExpires = pinExpires?.toUtc(), + createdAt = DateTime.now(), + updatedAt = DateTime.now(), + type = ''; + /// Create a new instance from a json factory Message.fromJson(Map? json) => _$MessageFromJson( Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); @@ -86,7 +120,7 @@ class Message extends Equatable { final String id; /// The text of this message - final String? text; + final String text; /// The status of a sending message @JsonKey(ignore: true) @@ -94,7 +128,7 @@ class Message extends Equatable { /// The message type @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final String? type; + final String type; /// The list of attachments, either provided by the user or generated from a /// command or as a result of URL scraping. @@ -158,11 +192,11 @@ class Message extends Equatable { /// Reserved field indicating when the message was created. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime? createdAt; + final DateTime createdAt; /// Reserved field indicating when the message was updated last time. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime? updatedAt; + final DateTime updatedAt; /// User who sent the message @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @@ -384,7 +418,12 @@ class Message extends Equatable { @JsonSerializable() class TranslatedMessage extends Message { /// Constructor used for json serialization - TranslatedMessage(this.i18n); + TranslatedMessage(this.i18n) + : super( + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + text: '', + type: ''); /// Create a new instance from a json factory TranslatedMessage.fromJson(Map? json) => diff --git a/packages/stream_chat/lib/src/models/message.g.dart b/packages/stream_chat/lib/src/models/message.g.dart index 72d2ea1f..cac1b1df 100644 --- a/packages/stream_chat/lib/src/models/message.g.dart +++ b/packages/stream_chat/lib/src/models/message.g.dart @@ -9,8 +9,8 @@ part of 'message.dart'; Message _$MessageFromJson(Map json) { return Message( id: json['id'] as String?, - text: json['text'] as String?, - type: json['type'] as String?, + text: json['text'] as String, + type: json['type'] as String, attachments: (json['attachments'] as List?) ?.map((e) => Attachment.fromJson(Map.from(e as Map))) .toList(), @@ -52,12 +52,8 @@ Message _$MessageFromJson(Map json) { .toList(), showInChannel: json['show_in_channel'] as bool?, command: json['command'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), user: json['user'] == null ? null : User.fromJson((json['user'] as Map?)?.map( diff --git a/packages/stream_chat/lib/src/models/own_user.dart b/packages/stream_chat/lib/src/models/own_user.dart index 041c4e95..9ebc633b 100644 --- a/packages/stream_chat/lib/src/models/own_user.dart +++ b/packages/stream_chat/lib/src/models/own_user.dart @@ -12,19 +12,19 @@ part 'own_user.g.dart'; class OwnUser extends User { /// Constructor used for json serialization OwnUser({ - this.devices, - this.mutes, - this.totalUnreadCount, + this.devices = const [], + this.mutes = const [], + this.totalUnreadCount = 0, this.unreadChannels, - this.channelMutes, - String? id, - String? role, - DateTime? createdAt, - DateTime? updatedAt, + this.channelMutes = const [], + required String id, + required String role, + required DateTime createdAt, + required DateTime updatedAt, DateTime? lastActive, - bool? online, - Map? extraData, - bool? banned, + bool online = false, + Map extraData = const {}, + bool banned = false, }) : super( id: id, role: role, @@ -36,25 +36,45 @@ class OwnUser extends User { banned: banned, ); + /// Create a temporary/throwaway user with an ID + OwnUser.temp({ + required String id, + this.devices = const [], + this.mutes = const [], + this.totalUnreadCount = 0, + this.unreadChannels, + this.channelMutes = const [], + DateTime? lastActive, + bool online = false, + Map extraData = const {}, + bool banned = false, + }) : super.temp( + id: id, + lastActive: lastActive, + online: online, + extraData: extraData, + banned: banned, + ); + /// Create a new instance from a json factory OwnUser.fromJson(Map? json) => _$OwnUserFromJson( Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); /// List of user devices @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final List? devices; + final List devices; /// List of users muted by the user @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final List? mutes; + final List mutes; /// List of users muted by the user @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final List? channelMutes; + final List channelMutes; /// Total unread messages by the user @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final int? totalUnreadCount; + final int totalUnreadCount; /// Total unread channels by the user @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) diff --git a/packages/stream_chat/lib/src/models/own_user.g.dart b/packages/stream_chat/lib/src/models/own_user.g.dart index ce265308..5cc0bd53 100644 --- a/packages/stream_chat/lib/src/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/models/own_user.g.dart @@ -7,34 +7,29 @@ part of 'own_user.dart'; // ************************************************************************** OwnUser _$OwnUserFromJson(Map json) { + print(json); return OwnUser( - devices: (json['devices'] as List?) - ?.map((e) => Device.fromJson(Map.from(e as Map))) + devices: (json['devices'] as List) + .map((e) => Device.fromJson(Map.from(e as Map))) .toList(), - mutes: (json['mutes'] as List?) - ?.map((e) => Mute.fromJson(Map.from(e as Map))) + mutes: (json['mutes'] as List) + .map((e) => Mute.fromJson(Map.from(e as Map))) .toList(), - totalUnreadCount: json['total_unread_count'] as int?, + totalUnreadCount: json['total_unread_count'] as int, unreadChannels: json['unread_channels'] as int?, - channelMutes: (json['channel_mutes'] as List?) - ?.map((e) => Mute.fromJson(Map.from(e as Map))) + channelMutes: (json['channel_mutes'] as List) + .map((e) => Mute.fromJson(Map.from(e as Map))) .toList(), - id: json['id'] as String?, - role: json['role'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), + id: json['id'] as String, + role: json['role'] as String, + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), lastActive: json['last_active'] == null ? null : DateTime.parse(json['last_active'] as String), - online: json['online'] as bool?, - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ), - banned: json['banned'] as bool?, + online: json['online'] as bool, + extraData: Map.from(json['extra_data'] as Map), + banned: json['banned'] as bool, ); } @@ -55,7 +50,7 @@ Map _$OwnUserToJson(OwnUser instance) { writeNotNull('last_active', readonly(instance.lastActive)); writeNotNull('online', readonly(instance.online)); writeNotNull('banned', readonly(instance.banned)); - writeNotNull('extra_data', instance.extraData); + val['extra_data'] = instance.extraData; writeNotNull('devices', readonly(instance.devices)); writeNotNull('mutes', readonly(instance.mutes)); writeNotNull('channel_mutes', readonly(instance.channelMutes)); diff --git a/packages/stream_chat/lib/src/models/reaction.dart b/packages/stream_chat/lib/src/models/reaction.dart index 9f7b5851..cea2eaef 100644 --- a/packages/stream_chat/lib/src/models/reaction.dart +++ b/packages/stream_chat/lib/src/models/reaction.dart @@ -10,13 +10,13 @@ class Reaction { /// Constructor used for json serialization Reaction({ this.messageId, - this.createdAt, - this.type, - this.user, + required this.createdAt, + required this.type, + required this.user, String? userId, - this.score, + required this.score, this.extraData, - }) : userId = userId ?? user?.id; + }) : userId = userId ?? user.id; /// Create a new instance from a json factory Reaction.fromJson(Map? json) => _$ReactionFromJson( @@ -26,18 +26,18 @@ class Reaction { final String? messageId; /// The type of the reaction - final String? type; + final String type; /// The date of the reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime? createdAt; + final DateTime createdAt; /// The user that sent the reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final User? user; + final User user; /// The score of the reaction (ie. number of reactions sent) - final int? score; + final int score; /// The userId that sent the reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) diff --git a/packages/stream_chat/lib/src/models/reaction.g.dart b/packages/stream_chat/lib/src/models/reaction.g.dart index 644c643a..2f1d1eb8 100644 --- a/packages/stream_chat/lib/src/models/reaction.g.dart +++ b/packages/stream_chat/lib/src/models/reaction.g.dart @@ -9,17 +9,13 @@ part of 'reaction.dart'; Reaction _$ReactionFromJson(Map json) { return Reaction( messageId: json['message_id'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - type: json['type'] as String?, - user: json['user'] == null - ? null - : User.fromJson((json['user'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + createdAt: DateTime.parse(json['created_at'] as String), + type: json['type'] as String, + user: User.fromJson((json['user'] as Map?)?.map( + (k, e) => MapEntry(k as String, e), + )), userId: json['user_id'] as String?, - score: json['score'] as int?, + score: json['score'] as int, extraData: (json['extra_data'] as Map?)?.map( (k, e) => MapEntry(k as String, e), ), diff --git a/packages/stream_chat/lib/src/models/read.dart b/packages/stream_chat/lib/src/models/read.dart index 87b3dc89..3fccc20b 100644 --- a/packages/stream_chat/lib/src/models/read.dart +++ b/packages/stream_chat/lib/src/models/read.dart @@ -8,22 +8,22 @@ part 'read.g.dart'; class Read { /// Constructor used for json serialization Read({ - this.lastRead, - this.user, - this.unreadMessages, + required this.lastRead, + required this.user, + required this.unreadMessages, }); /// Create a new instance from a json factory Read.fromJson(Map json) => _$ReadFromJson(json); /// Date of the read event - final DateTime? lastRead; + final DateTime lastRead; /// User who sent the event - final User? user; + final User user; /// Number of unread messages - final int? unreadMessages; + final int unreadMessages; /// Serialize to json Map toJson() => _$ReadToJson(this); diff --git a/packages/stream_chat/lib/src/models/read.g.dart b/packages/stream_chat/lib/src/models/read.g.dart index 83c822e5..7b2a6f48 100644 --- a/packages/stream_chat/lib/src/models/read.g.dart +++ b/packages/stream_chat/lib/src/models/read.g.dart @@ -8,20 +8,16 @@ part of 'read.dart'; Read _$ReadFromJson(Map json) { return Read( - lastRead: json['last_read'] == null - ? null - : DateTime.parse(json['last_read'] as String), - user: json['user'] == null - ? null - : User.fromJson((json['user'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), - unreadMessages: json['unread_messages'] as int?, + lastRead: DateTime.parse(json['last_read'] as String), + user: User.fromJson((json['user'] as Map?)?.map( + (k, e) => MapEntry(k as String, e), + )), + unreadMessages: json['unread_messages'] as int, ); } Map _$ReadToJson(Read instance) => { - 'last_read': instance.lastRead?.toIso8601String(), - 'user': instance.user?.toJson(), + 'last_read': instance.lastRead.toIso8601String(), + 'user': instance.user.toJson(), 'unread_messages': instance.unreadMessages, }; diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/models/user.dart index 97411a3e..1488696a 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/models/user.dart @@ -8,17 +8,29 @@ part 'user.g.dart'; class User { /// Constructor used for json serialization User({ - this.id, - this.role, - this.createdAt, - this.updatedAt, + required this.id, + required this.role, + required this.createdAt, + required this.updatedAt, this.lastActive, - this.online, - this.extraData, - this.banned, - this.teams, + this.online = false, + this.extraData = const {}, + this.banned = false, + this.teams = const [], }); + /// Use constructor for a temporary/throwaway user with an ID + User.temp({ + required this.id, + this.role = '', + this.lastActive, + this.online = false, + this.extraData = const {}, + this.banned = false, + this.teams = const [], + }) : createdAt = DateTime.now(), + updatedAt = DateTime.now(); + /// Create a new instance from a json factory User.fromJson(Map? json) => _$UserFromJson( Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); @@ -26,14 +38,14 @@ class User { /// Use this named constructor to create a new user instance User.init( this.id, { - this.online, - this.extraData, - }) : createdAt = null, - updatedAt = null, - lastActive = null, - banned = null, - teams = null, - role = null; + this.online = false, + this.extraData = const {}, + required this.createdAt, + required this.updatedAt, + this.teams = const [], + required this.role, + }) : lastActive = null, + banned = false; /// Known top level fields. /// Useful for [Serialization] methods. @@ -49,23 +61,23 @@ class User { ]; /// User id - final String? id; + final String id; /// User role @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final String? role; + final String role; /// User role @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final List? teams; + final List teams; /// Date of user creation @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime? createdAt; + final DateTime createdAt; /// Date of last user update @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime? updatedAt; + final DateTime updatedAt; /// Date of last user connection @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @@ -73,23 +85,23 @@ class User { /// True if user is online @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final bool? online; + final bool online; /// True if user is banned from the chat @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final bool? banned; + final bool banned; /// Map of custom user extraData @JsonKey(includeIfNull: false) - final Map? extraData; + final Map extraData; @override int get hashCode => id.hashCode; /// Shortcut for user name String? get name => - (extraData?.containsKey('name') == true && extraData!['name'] != '') - ? extraData!['name'] + (extraData.containsKey('name') == true && extraData['name'] != '') + ? extraData['name'] : id; @override diff --git a/packages/stream_chat/lib/src/models/user.g.dart b/packages/stream_chat/lib/src/models/user.g.dart index 06989395..f1e4088f 100644 --- a/packages/stream_chat/lib/src/models/user.g.dart +++ b/packages/stream_chat/lib/src/models/user.g.dart @@ -8,23 +8,17 @@ part of 'user.dart'; User _$UserFromJson(Map json) { return User( - id: json['id'] as String?, - role: json['role'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), + id: json['id'] as String, + role: json['role'] as String, + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), lastActive: json['last_active'] == null ? null : DateTime.parse(json['last_active'] as String), - online: json['online'] as bool?, - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ), - banned: json['banned'] as bool?, - teams: (json['teams'] as List?)?.map((e) => e as String).toList(), + online: json['online'] as bool, + extraData: Map.from(json['extra_data'] as Map), + banned: json['banned'] as bool, + teams: (json['teams'] as List).map((e) => e as String).toList(), ); } @@ -46,6 +40,6 @@ Map _$UserToJson(User instance) { writeNotNull('last_active', readonly(instance.lastActive)); writeNotNull('online', readonly(instance.online)); writeNotNull('banned', readonly(instance.banned)); - writeNotNull('extra_data', instance.extraData); + val['extra_data'] = instance.extraData; return val; } diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 94990a7b..5d7c61f0 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -35,7 +35,7 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message(text: 'hey', id: 'test'); + final message = Message.temp(text: 'hey', id: 'test'); when( () => mockDio.post( @@ -168,7 +168,7 @@ void main() { requestOptions: FakeRequestOptions(), )); - await channelClient.sendAction(Message(id: 'messageid'), data); + await channelClient.sendAction(Message.temp(id: 'messageid', text: ''), data); verify(() => mockDio.post('/messages/messageid/action', data: { 'id': 'testid', @@ -334,7 +334,7 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); - final message = Message(text: 'Hello'); + final message = Message.temp(text: 'Hello'); expect( () => channelClient.pinMessage(message, 'InvalidType'), @@ -354,7 +354,7 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message(text: 'Hello', id: 'test'); + final message = Message.temp(text: 'Hello', id: 'test'); when( () => mockDio.post( @@ -388,7 +388,7 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message(text: 'Hello', id: 'test'); + final message = Message.temp(text: 'Hello', id: 'test'); when( () => mockDio.post( @@ -566,7 +566,7 @@ void main() { tokenProvider: (_) async => '', ); - client.state?.user = OwnUser(id: 'test-id'); + client.state?.user = OwnUser.temp(id: 'test-id'); final channelClient = client.channel('messaging', id: 'testid'); const reactionType = 'test'; @@ -590,8 +590,9 @@ void main() { ); await channelClient.sendReaction( - Message( + Message.temp( id: 'messageid', + text: '', reactionCounts: const {}, reactionScores: const {}, latestReactions: const [], @@ -621,7 +622,7 @@ void main() { tokenProvider: (_) async => '', ); - client.state?.user = OwnUser(id: 'test-id'); + client.state?.user = OwnUser.temp(id: 'test-id'); final channelClient = client.channel('messaging', id: 'testid'); @@ -636,14 +637,22 @@ void main() { ); await channelClient.deleteReaction( - Message( + Message.temp( id: 'messageid', + text: '', reactionCounts: const {}, reactionScores: const {}, latestReactions: const [], ownReactions: const [], ), - Reaction(type: 'test'), + Reaction( + type: 'test', + createdAt: DateTime.now(), + score: 0, + user: User.temp( + id: client.state?.user?.id ?? '', + ), + ), ); verify(() => @@ -699,7 +708,7 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); final members = ['vishal']; - final message = Message(text: 'test'); + final message = Message.temp(text: 'test'); when( () => mockDio.post( @@ -733,7 +742,7 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message(text: 'test'); + final message = Message.temp(text: 'test'); when( () => mockDio.post( @@ -2081,7 +2090,7 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message(text: 'test'); + final message = Message.temp(text: 'test'); when( () => mockDio.post( @@ -2180,7 +2189,7 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message(text: 'test'); + final message = Message.temp(text: 'test'); when( () => mockDio.post( @@ -2215,7 +2224,7 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); final members = ['vishal']; - final message = Message(text: 'test'); + final message = Message.temp(text: 'test'); when( () => mockDio.post( @@ -2249,7 +2258,7 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); final members = ['vishal']; - final message = Message(text: 'test'); + final message = Message.temp(text: 'test'); when( () => mockDio.post( diff --git a/packages/stream_chat/test/src/api/websocket_test.dart b/packages/stream_chat/test/src/api/websocket_test.dart index 0f3bd5d6..f6616beb 100644 --- a/packages/stream_chat/test/src/api/websocket_test.dart +++ b/packages/stream_chat/test/src/api/websocket_test.dart @@ -38,7 +38,7 @@ void main() { final ConnectWebSocket connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', - user: User(id: 'testid'), + user: User.temp(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -76,7 +76,7 @@ void main() { final ConnectWebSocket connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', - user: User(id: 'testid'), + user: User.temp(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -112,7 +112,7 @@ void main() { final ConnectWebSocket connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', - user: User(id: 'testid'), + user: User.temp(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -150,7 +150,7 @@ void main() { final ws = WebSocket( baseUrl: 'baseurl', - user: User(id: 'testid'), + user: User.temp(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -184,7 +184,7 @@ void main() { final ConnectWebSocket connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', - user: User(id: 'testid'), + user: User.temp(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -229,7 +229,7 @@ void main() { Logger.root.level = Level.ALL; final ws = WebSocket( baseUrl: 'baseurl', - user: User(id: 'testid'), + user: User.temp(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -273,7 +273,7 @@ void main() { final ConnectWebSocket connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', - user: User(id: 'testid'), + user: User.temp(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -310,7 +310,7 @@ void main() { final ConnectWebSocket connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', - user: User(id: 'testid'), + user: User.temp(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 3c0a325c..775d68b0 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -479,7 +479,7 @@ void main() { final client = StreamChatClient('api-key', httpClient: mockDio); - expect(() => client.connectUserWithProvider(User(id: 'test-id')), + expect(() => client.connectUserWithProvider(User.temp(id: 'test-id')), throwsA(isA())); }); @@ -517,7 +517,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final user = User(id: 'test-id'); + final user = User.temp(id: 'test-id'); final data = { 'users': {user.id: user.toJson()}, }; @@ -542,8 +542,8 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final user = User(id: 'test-id'); - final user2 = User(id: 'test-id2'); + final user = User.temp(id: 'test-id'); + final user2 = User.temp(id: 'test-id2'); final data = { 'users': { @@ -737,7 +737,13 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final message = Message(id: 'test', updatedAt: DateTime.now()); + final message = Message( + id: 'test', + updatedAt: DateTime.now(), + createdAt: DateTime.now(), + text: '', + type: '', + ); when( () => mockDio.post( @@ -775,7 +781,7 @@ void main() { ), ); - await client.deleteMessage(Message(id: messageId)); + await client.deleteMessage(Message.temp(id: messageId, text: '')); verify(() => mockDio.delete('/messages/$messageId')).called(1); }); @@ -1095,7 +1101,7 @@ void main() { ); test('should throw argument error', () { - final message = Message(text: 'Hello'); + final message = Message.temp(text: 'Hello'); expect( () => client.pinMessage(message, 'InvalidType'), throwsArgumentError, @@ -1104,7 +1110,7 @@ void main() { test('should complete successfully', () async { const timeout = 30; - final message = Message(text: 'Hello'); + final message = Message.temp(text: 'Hello'); when( () => mockDio.post( @@ -1126,7 +1132,7 @@ void main() { }); test('should unpin message successfully', () async { - final message = Message(text: 'Hello'); + final message = Message.temp(text: 'Hello'); when( () => mockDio.post( diff --git a/packages/stream_chat/test/src/models/channel_state_test.dart b/packages/stream_chat/test/src/models/channel_state_test.dart index 9aa2b2d6..79b8b0fa 100644 --- a/packages/stream_chat/test/src/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/models/channel_state_test.dart @@ -1330,10 +1330,10 @@ void main() { members: [], messages: (j['messages'] as List).map((m) => Message.fromJson(m)).toList(), - read: null, + read: [], watcherCount: 5, pinnedMessages: [], - watchers: null, + watchers: [], ); expect( diff --git a/packages/stream_chat/test/src/models/channel_test.dart b/packages/stream_chat/test/src/models/channel_test.dart index 4a221a8a..6d0475e0 100644 --- a/packages/stream_chat/test/src/models/channel_test.dart +++ b/packages/stream_chat/test/src/models/channel_test.dart @@ -25,7 +25,7 @@ void main() { }); test('should serialize to json correctly', () { - final channel = ChannelModel( + final channel = ChannelModel.temp( type: 'type', id: 'id', cid: 'a:a', @@ -39,7 +39,7 @@ void main() { }); test('should serialize to json correctly when frozen is provided', () { - final channel = ChannelModel( + final channel = ChannelModel.temp( type: 'type', id: 'id', cid: 'a:a', diff --git a/packages/stream_chat/test/src/models/event_test.dart b/packages/stream_chat/test/src/models/event_test.dart index a7c669df..4e657bdb 100644 --- a/packages/stream_chat/test/src/models/event_test.dart +++ b/packages/stream_chat/test/src/models/event_test.dart @@ -51,12 +51,12 @@ void main() { test('should serialize to json correctly', () { final event = Event( - user: User(id: 'id'), + user: User.temp(id: 'id'), type: 'type', cid: 'cid', connectionId: 'connectionId', createdAt: DateTime.parse('2020-01-29T03:22:47.63613Z'), - me: OwnUser(id: 'id2'), + me: OwnUser.temp(id: 'id2'), totalUnreadCount: 1, unreadChannels: 1, online: true, diff --git a/packages/stream_chat/test/src/models/message_test.dart b/packages/stream_chat/test/src/models/message_test.dart index 0898e902..72b9ba09 100644 --- a/packages/stream_chat/test/src/models/message_test.dart +++ b/packages/stream_chat/test/src/models/message_test.dart @@ -97,7 +97,7 @@ void main() { }); test('should serialize to json correctly', () { - final message = Message( + final message = Message.temp( id: '4637f7e4-a06b-42db-ba5a-8d8270dd926f', text: 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA', diff --git a/packages/stream_chat/test/src/models/reaction_test.dart b/packages/stream_chat/test/src/models/reaction_test.dart index 80d2964b..e82dcc79 100644 --- a/packages/stream_chat/test/src/models/reaction_test.dart +++ b/packages/stream_chat/test/src/models/reaction_test.dart @@ -33,8 +33,8 @@ void main() { expect(reaction.createdAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); expect(reaction.type, 'wow'); expect( - reaction.user?.toJson(), - User.init('2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { + reaction.user.toJson(), + User.temp(id:'2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { 'image': 'https://randomuser.me/api/portraits/women/45.jpg', 'name': 'Daisy Morgan' }).toJson(), @@ -49,7 +49,7 @@ void main() { messageId: '76cd8c82-b557-4e48-9d12-87995d3a0e04', createdAt: DateTime.parse('2020-01-28T22:17:31.108742Z'), type: 'wow', - user: User.init('2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { + user: User.temp(id:'2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { 'image': 'https://randomuser.me/api/portraits/women/45.jpg', 'name': 'Daisy Morgan' }), diff --git a/packages/stream_chat/test/src/models/read_test.dart b/packages/stream_chat/test/src/models/read_test.dart index 7575d35f..24b56cd8 100644 --- a/packages/stream_chat/test/src/models/read_test.dart +++ b/packages/stream_chat/test/src/models/read_test.dart @@ -26,7 +26,7 @@ void main() { test('should serialize to json correctly', () { final read = Read( lastRead: DateTime.parse('2020-01-28T22:17:30.966485504Z'), - user: User.init('bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'), + user: User.temp(id: 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'), unreadMessages: 10, ); diff --git a/packages/stream_chat/test/src/models/user_test.dart b/packages/stream_chat/test/src/models/user_test.dart index 0a307126..8a30ad4d 100644 --- a/packages/stream_chat/test/src/models/user_test.dart +++ b/packages/stream_chat/test/src/models/user_test.dart @@ -17,7 +17,7 @@ void main() { }); test('should serialize to json correctly', () { - final user = User( + final user = User.temp( id: 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e', role: 'abc', ); From 97c35371af22afd266343fb3a880ff4f7d760824 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 13 Apr 2021 15:48:05 +0200 Subject: [PATCH 091/599] fix models --- packages/stream_chat/build.yaml | 3 +- packages/stream_chat/lib/src/api/channel.dart | 2 +- .../stream_chat/lib/src/api/requests.dart | 1 + .../stream_chat/lib/src/api/requests.g.dart | 1 - .../stream_chat/lib/src/api/responses.g.dart | 213 +++++++----------- .../stream_chat/lib/src/api/websocket.dart | 4 +- packages/stream_chat/lib/src/client.dart | 10 +- .../lib/src/db/chat_persistence_client.dart | 14 +- .../stream_chat/lib/src/models/action.g.dart | 2 +- .../lib/src/models/attachment.g.dart | 14 +- .../lib/src/models/attachment_file.g.dart | 10 +- .../lib/src/models/channel_config.g.dart | 4 +- .../lib/src/models/channel_model.g.dart | 13 +- .../lib/src/models/channel_state.g.dart | 25 +- .../stream_chat/lib/src/models/command.g.dart | 2 +- .../stream_chat/lib/src/models/device.g.dart | 2 +- .../stream_chat/lib/src/models/event.g.dart | 43 ++-- .../stream_chat/lib/src/models/member.g.dart | 6 +- .../stream_chat/lib/src/models/message.g.dart | 50 ++-- .../stream_chat/lib/src/models/mute.g.dart | 10 +- .../lib/src/models/own_user.g.dart | 11 +- .../lib/src/models/reaction.g.dart | 10 +- .../stream_chat/lib/src/models/read.g.dart | 6 +- .../stream_chat/lib/src/models/user.g.dart | 4 +- .../test/src/api/channel_test.dart | 3 +- .../test/src/api/requests_test.dart | 3 +- .../test/src/api/websocket_test.dart | 26 +-- .../test/src/models/reaction_test.dart | 4 +- .../test/src/models/user_test.dart | 5 +- 29 files changed, 193 insertions(+), 308 deletions(-) diff --git a/packages/stream_chat/build.yaml b/packages/stream_chat/build.yaml index ddbd70dd..d439bddb 100644 --- a/packages/stream_chat/build.yaml +++ b/packages/stream_chat/build.yaml @@ -4,5 +4,4 @@ targets: json_serializable: options: explicit_to_json: true - field_rename: snake - any_map: true + field_rename: snake \ No newline at end of file diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 80f34ccb..dd25976c 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1830,7 +1830,7 @@ class ChannelClientState { final now = DateTime.now(); var expiredMessages = channelState!.pinnedMessages .where((m) => m.pinExpires?.isBefore(now) == true) - .toList(); + .toList(); if (expiredMessages.isNotEmpty) { expiredMessages = expiredMessages .map((m) => m.copyWith( diff --git a/packages/stream_chat/lib/src/api/requests.dart b/packages/stream_chat/lib/src/api/requests.dart index 5e3c0741..e78a80ec 100644 --- a/packages/stream_chat/lib/src/api/requests.dart +++ b/packages/stream_chat/lib/src/api/requests.dart @@ -106,6 +106,7 @@ class PaginationParams { ); @override + @JsonKey(ignore: true) int get hashCode => runtimeType.hashCode ^ limit.hashCode ^ diff --git a/packages/stream_chat/lib/src/api/requests.g.dart b/packages/stream_chat/lib/src/api/requests.g.dart index fd9a5a35..1ec7b5fc 100644 --- a/packages/stream_chat/lib/src/api/requests.g.dart +++ b/packages/stream_chat/lib/src/api/requests.g.dart @@ -28,6 +28,5 @@ Map _$PaginationParamsToJson(PaginationParams instance) { writeNotNull('id_gte', instance.greaterThanOrEqual); writeNotNull('id_lt', instance.lessThan); writeNotNull('id_lte', instance.lessThanOrEqual); - val['hash_code'] = instance.hashCode; return val; } diff --git a/packages/stream_chat/lib/src/api/responses.g.dart b/packages/stream_chat/lib/src/api/responses.g.dart index 849b6d1a..8be1e62b 100644 --- a/packages/stream_chat/lib/src/api/responses.g.dart +++ b/packages/stream_chat/lib/src/api/responses.g.dart @@ -6,17 +6,16 @@ part of 'responses.dart'; // JsonSerializableGenerator // ************************************************************************** -SyncResponse _$SyncResponseFromJson(Map json) { +SyncResponse _$SyncResponseFromJson(Map json) { return SyncResponse() ..duration = json['duration'] as String? ..events = (json['events'] as List?) - ?.map((e) => Event.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + ?.map((e) => Event.fromJson(e as Map?)) .toList(); } -QueryChannelsResponse _$QueryChannelsResponseFromJson(Map json) { +QueryChannelsResponse _$QueryChannelsResponseFromJson( + Map json) { return QueryChannelsResponse() ..duration = json['duration'] as String? ..channels = (json['channels'] as List?) @@ -24,148 +23,127 @@ QueryChannelsResponse _$QueryChannelsResponseFromJson(Map json) { .toList(); } -TranslateMessageResponse _$TranslateMessageResponseFromJson(Map json) { +TranslateMessageResponse _$TranslateMessageResponseFromJson( + Map json) { return TranslateMessageResponse() ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : TranslatedMessage.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : TranslatedMessage.fromJson(json['message'] as Map?); } -QueryMembersResponse _$QueryMembersResponseFromJson(Map json) { +QueryMembersResponse _$QueryMembersResponseFromJson(Map json) { return QueryMembersResponse() ..duration = json['duration'] as String? ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(Map.from(e as Map))) + ?.map((e) => Member.fromJson(e as Map)) .toList(); } -QueryUsersResponse _$QueryUsersResponseFromJson(Map json) { +QueryUsersResponse _$QueryUsersResponseFromJson(Map json) { return QueryUsersResponse() ..duration = json['duration'] as String? ..users = (json['users'] as List?) - ?.map((e) => User.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + ?.map((e) => User.fromJson(e as Map?)) .toList(); } -QueryReactionsResponse _$QueryReactionsResponseFromJson(Map json) { +QueryReactionsResponse _$QueryReactionsResponseFromJson( + Map json) { return QueryReactionsResponse() ..duration = json['duration'] as String? ..reactions = (json['reactions'] as List?) - ?.map((e) => Reaction.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + ?.map((e) => Reaction.fromJson(e as Map?)) .toList(); } -QueryRepliesResponse _$QueryRepliesResponseFromJson(Map json) { +QueryRepliesResponse _$QueryRepliesResponseFromJson(Map json) { return QueryRepliesResponse() ..duration = json['duration'] as String? ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + ?.map((e) => Message.fromJson(e as Map?)) .toList(); } -ListDevicesResponse _$ListDevicesResponseFromJson(Map json) { +ListDevicesResponse _$ListDevicesResponseFromJson(Map json) { return ListDevicesResponse() ..duration = json['duration'] as String? ..devices = (json['devices'] as List?) - ?.map((e) => Device.fromJson(Map.from(e as Map))) + ?.map((e) => Device.fromJson(e as Map)) .toList(); } -SendFileResponse _$SendFileResponseFromJson(Map json) { +SendFileResponse _$SendFileResponseFromJson(Map json) { return SendFileResponse() ..duration = json['duration'] as String? ..file = json['file'] as String?; } -SendImageResponse _$SendImageResponseFromJson(Map json) { +SendImageResponse _$SendImageResponseFromJson(Map json) { return SendImageResponse() ..duration = json['duration'] as String? ..file = json['file'] as String?; } -SendReactionResponse _$SendReactionResponseFromJson(Map json) { +SendReactionResponse _$SendReactionResponseFromJson(Map json) { return SendReactionResponse() ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )) + : Message.fromJson(json['message'] as Map?) ..reaction = json['reaction'] == null ? null - : Reaction.fromJson((json['reaction'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : Reaction.fromJson(json['reaction'] as Map?); } -ConnectGuestUserResponse _$ConnectGuestUserResponseFromJson(Map json) { +ConnectGuestUserResponse _$ConnectGuestUserResponseFromJson( + Map json) { return ConnectGuestUserResponse() ..duration = json['duration'] as String? ..accessToken = json['access_token'] as String? ..user = json['user'] == null ? null - : User.fromJson((json['user'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : User.fromJson(json['user'] as Map?); } -UpdateUsersResponse _$UpdateUsersResponseFromJson(Map json) { +UpdateUsersResponse _$UpdateUsersResponseFromJson(Map json) { return UpdateUsersResponse() ..duration = json['duration'] as String? - ..users = (json['users'] as Map?)?.map( - (k, e) => MapEntry( - k as String, - User.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))), + ..users = (json['users'] as Map?)?.map( + (k, e) => MapEntry(k, User.fromJson(e as Map?)), ); } -UpdateMessageResponse _$UpdateMessageResponseFromJson(Map json) { +UpdateMessageResponse _$UpdateMessageResponseFromJson( + Map json) { return UpdateMessageResponse() ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : Message.fromJson(json['message'] as Map?); } -SendMessageResponse _$SendMessageResponseFromJson(Map json) { +SendMessageResponse _$SendMessageResponseFromJson(Map json) { return SendMessageResponse() ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : Message.fromJson(json['message'] as Map?); } -GetMessageResponse _$GetMessageResponseFromJson(Map json) { +GetMessageResponse _$GetMessageResponseFromJson(Map json) { return GetMessageResponse() ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )) + : Message.fromJson(json['message'] as Map?) ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : ChannelModel.fromJson(json['channel'] as Map?); } -SearchMessagesResponse _$SearchMessagesResponseFromJson(Map json) { +SearchMessagesResponse _$SearchMessagesResponseFromJson( + Map json) { return SearchMessagesResponse() ..duration = json['duration'] as String? ..results = (json['results'] as List?) @@ -173,169 +151,140 @@ SearchMessagesResponse _$SearchMessagesResponseFromJson(Map json) { .toList(); } -GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson(Map json) { +GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson( + Map json) { return GetMessagesByIdResponse() ..duration = json['duration'] as String? ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + ?.map((e) => Message.fromJson(e as Map?)) .toList(); } -UpdateChannelResponse _$UpdateChannelResponseFromJson(Map json) { +UpdateChannelResponse _$UpdateChannelResponseFromJson( + Map json) { return UpdateChannelResponse() ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )) + : ChannelModel.fromJson(json['channel'] as Map?) ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(Map.from(e as Map))) + ?.map((e) => Member.fromJson(e as Map)) .toList() ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : Message.fromJson(json['message'] as Map?); } -PartialUpdateChannelResponse _$PartialUpdateChannelResponseFromJson(Map json) { +PartialUpdateChannelResponse _$PartialUpdateChannelResponseFromJson( + Map json) { return PartialUpdateChannelResponse() ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )) + : ChannelModel.fromJson(json['channel'] as Map?) ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(Map.from(e as Map))) + ?.map((e) => Member.fromJson(e as Map)) .toList(); } -InviteMembersResponse _$InviteMembersResponseFromJson(Map json) { +InviteMembersResponse _$InviteMembersResponseFromJson( + Map json) { return InviteMembersResponse() ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )) + : ChannelModel.fromJson(json['channel'] as Map?) ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(Map.from(e as Map))) + ?.map((e) => Member.fromJson(e as Map)) .toList() ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : Message.fromJson(json['message'] as Map?); } -RemoveMembersResponse _$RemoveMembersResponseFromJson(Map json) { +RemoveMembersResponse _$RemoveMembersResponseFromJson( + Map json) { return RemoveMembersResponse() ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )) + : ChannelModel.fromJson(json['channel'] as Map?) ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(Map.from(e as Map))) + ?.map((e) => Member.fromJson(e as Map)) .toList() ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : Message.fromJson(json['message'] as Map?); } -SendActionResponse _$SendActionResponseFromJson(Map json) { +SendActionResponse _$SendActionResponseFromJson(Map json) { return SendActionResponse() ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : Message.fromJson(json['message'] as Map?); } -AddMembersResponse _$AddMembersResponseFromJson(Map json) { +AddMembersResponse _$AddMembersResponseFromJson(Map json) { return AddMembersResponse() ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )) + : ChannelModel.fromJson(json['channel'] as Map?) ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(Map.from(e as Map))) + ?.map((e) => Member.fromJson(e as Map)) .toList() ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : Message.fromJson(json['message'] as Map?); } -AcceptInviteResponse _$AcceptInviteResponseFromJson(Map json) { +AcceptInviteResponse _$AcceptInviteResponseFromJson(Map json) { return AcceptInviteResponse() ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )) + : ChannelModel.fromJson(json['channel'] as Map?) ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(Map.from(e as Map))) + ?.map((e) => Member.fromJson(e as Map)) .toList() ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : Message.fromJson(json['message'] as Map?); } -RejectInviteResponse _$RejectInviteResponseFromJson(Map json) { +RejectInviteResponse _$RejectInviteResponseFromJson(Map json) { return RejectInviteResponse() ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )) + : ChannelModel.fromJson(json['channel'] as Map?) ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(Map.from(e as Map))) + ?.map((e) => Member.fromJson(e as Map)) .toList() ..message = json['message'] == null ? null - : Message.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )); + : Message.fromJson(json['message'] as Map?); } -EmptyResponse _$EmptyResponseFromJson(Map json) { +EmptyResponse _$EmptyResponseFromJson(Map json) { return EmptyResponse()..duration = json['duration'] as String?; } -ChannelStateResponse _$ChannelStateResponseFromJson(Map json) { +ChannelStateResponse _$ChannelStateResponseFromJson(Map json) { return ChannelStateResponse() ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )) + : ChannelModel.fromJson(json['channel'] as Map?) ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + ?.map((e) => Message.fromJson(e as Map?)) .toList() ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(Map.from(e as Map))) + ?.map((e) => Member.fromJson(e as Map)) .toList() ..watcherCount = json['watcher_count'] as int? ..read = (json['read'] as List?) - ?.map((e) => Read.fromJson(Map.from(e as Map))) + ?.map((e) => Read.fromJson(e as Map)) .toList(); } diff --git a/packages/stream_chat/lib/src/api/websocket.dart b/packages/stream_chat/lib/src/api/websocket.dart index b2208a13..1e808795 100644 --- a/packages/stream_chat/lib/src/api/websocket.dart +++ b/packages/stream_chat/lib/src/api/websocket.dart @@ -124,10 +124,10 @@ class WebSocket { Event _decodeEvent(String source) => Event.fromJson(json.decode(source)); - Completer _connectionCompleter = Completer(); + Completer _connectionCompleter = Completer(); /// Connect the WS using the parameters passed in the constructor - Future? connect() { + Future connect() async { _manuallyDisconnected = false; if (_connecting) { diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 4b06788e..147a08d0 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -573,9 +573,11 @@ class StreamChatClient { var event = await _chatPersistenceClient?.getConnectionInfo(); - await _ws.connect()!.then((e) async { - await _chatPersistenceClient?.updateConnectionInfo(e); - event = e; + await _ws.connect().then((e) async { + if (e != null) { + await _chatPersistenceClient?.updateConnectionInfo(e); + event = e; + } await resync(); }).catchError((err, stacktrace) { logger.severe('error connecting ws', err, stacktrace); @@ -737,7 +739,7 @@ class StreamChatClient { QueryChannelsResponse.fromJson, )!; - if ((res.channels ?? []).isEmpty && (paginationParams.offset) == 0) { + if ((res.channels ?? []).isEmpty && paginationParams.offset == 0) { logger.warning( ''' We could not find any channel for this query. diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 7911a986..daabc26a 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -192,14 +192,12 @@ abstract class ChatPersistenceClient { final channels = channelStates.map((it) => it.channel).where((it) => it != null); - final reactions = channelStates - .expand((it) => it.messages) - .expand((it) => [ - if (it.ownReactions != null) - ...it.ownReactions!.where((r) => r.userId != null), - if (it.latestReactions != null) - ...it.latestReactions!.where((r) => r.userId != null) - ]); + final reactions = channelStates.expand((it) => it.messages).expand((it) => [ + if (it.ownReactions != null) + ...it.ownReactions!.where((r) => r.userId != null), + if (it.latestReactions != null) + ...it.latestReactions!.where((r) => r.userId != null) + ]); final users = channelStates .map((cs) => [ diff --git a/packages/stream_chat/lib/src/models/action.g.dart b/packages/stream_chat/lib/src/models/action.g.dart index 64a5195b..770298c7 100644 --- a/packages/stream_chat/lib/src/models/action.g.dart +++ b/packages/stream_chat/lib/src/models/action.g.dart @@ -6,7 +6,7 @@ part of 'action.dart'; // JsonSerializableGenerator // ************************************************************************** -Action _$ActionFromJson(Map json) { +Action _$ActionFromJson(Map json) { return Action( name: json['name'] as String?, style: json['style'] as String?, diff --git a/packages/stream_chat/lib/src/models/attachment.g.dart b/packages/stream_chat/lib/src/models/attachment.g.dart index 207cc02f..ff371a5f 100644 --- a/packages/stream_chat/lib/src/models/attachment.g.dart +++ b/packages/stream_chat/lib/src/models/attachment.g.dart @@ -6,7 +6,7 @@ part of 'attachment.dart'; // JsonSerializableGenerator // ************************************************************************** -Attachment _$AttachmentFromJson(Map json) { +Attachment _$AttachmentFromJson(Map json) { return Attachment( id: json['id'] as String?, type: json['type'] as String, @@ -27,19 +27,15 @@ Attachment _$AttachmentFromJson(Map json) { authorIcon: json['author_icon'] as String?, assetUrl: json['asset_url'] as String?, actions: (json['actions'] as List?) - ?.map((e) => Action.fromJson(Map.from(e as Map))) + ?.map((e) => Action.fromJson(e as Map)) .toList(), - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ), + extraData: json['extra_data'] as Map?, file: json['file'] == null ? null - : AttachmentFile.fromJson( - Map.from(json['file'] as Map)), + : AttachmentFile.fromJson(json['file'] as Map), uploadState: json['upload_state'] == null ? null - : UploadState.fromJson( - Map.from(json['upload_state'] as Map)), + : UploadState.fromJson(json['upload_state'] as Map), ); } diff --git a/packages/stream_chat/lib/src/models/attachment_file.g.dart b/packages/stream_chat/lib/src/models/attachment_file.g.dart index 8dc70ac3..55fcea6c 100644 --- a/packages/stream_chat/lib/src/models/attachment_file.g.dart +++ b/packages/stream_chat/lib/src/models/attachment_file.g.dart @@ -6,7 +6,7 @@ part of 'attachment_file.dart'; // JsonSerializableGenerator // ************************************************************************** -AttachmentFile _$AttachmentFileFromJson(Map json) { +AttachmentFile _$AttachmentFileFromJson(Map json) { return AttachmentFile( path: json['path'] as String?, name: json['name'] as String?, @@ -23,14 +23,14 @@ Map _$AttachmentFileToJson(AttachmentFile instance) => 'size': instance.size, }; -_$Preparing _$_$PreparingFromJson(Map json) { +_$Preparing _$_$PreparingFromJson(Map json) { return _$Preparing(); } Map _$_$PreparingToJson(_$Preparing instance) => {}; -_$InProgress _$_$InProgressFromJson(Map json) { +_$InProgress _$_$InProgressFromJson(Map json) { return _$InProgress( uploaded: json['uploaded'] as int, total: json['total'] as int, @@ -43,14 +43,14 @@ Map _$_$InProgressToJson(_$InProgress instance) => 'total': instance.total, }; -_$Success _$_$SuccessFromJson(Map json) { +_$Success _$_$SuccessFromJson(Map json) { return _$Success(); } Map _$_$SuccessToJson(_$Success instance) => {}; -_$Failed _$_$FailedFromJson(Map json) { +_$Failed _$_$FailedFromJson(Map json) { return _$Failed( error: json['error'] as String, ); diff --git a/packages/stream_chat/lib/src/models/channel_config.g.dart b/packages/stream_chat/lib/src/models/channel_config.g.dart index a1104888..41cd0cd2 100644 --- a/packages/stream_chat/lib/src/models/channel_config.g.dart +++ b/packages/stream_chat/lib/src/models/channel_config.g.dart @@ -6,11 +6,11 @@ part of 'channel_config.dart'; // JsonSerializableGenerator // ************************************************************************** -ChannelConfig _$ChannelConfigFromJson(Map json) { +ChannelConfig _$ChannelConfigFromJson(Map json) { return ChannelConfig( automod: json['automod'] as String?, commands: (json['commands'] as List?) - ?.map((e) => Command.fromJson(Map.from(e as Map))) + ?.map((e) => Command.fromJson(e as Map)) .toList(), connectEvents: json['connect_events'] as bool?, createdAt: json['created_at'] == null diff --git a/packages/stream_chat/lib/src/models/channel_model.g.dart b/packages/stream_chat/lib/src/models/channel_model.g.dart index 098a22e8..eb292e8d 100644 --- a/packages/stream_chat/lib/src/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/models/channel_model.g.dart @@ -6,18 +6,15 @@ part of 'channel_model.dart'; // JsonSerializableGenerator // ************************************************************************** -ChannelModel _$ChannelModelFromJson(Map json) { +ChannelModel _$ChannelModelFromJson(Map json) { return ChannelModel( id: json['id'] as String?, type: json['type'] as String?, cid: json['cid'] as String, - config: ChannelConfig.fromJson( - Map.from(json['config'] as Map)), + config: ChannelConfig.fromJson(json['config'] as Map), createdBy: json['created_by'] == null ? null - : User.fromJson((json['created_by'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : User.fromJson(json['created_by'] as Map?), frozen: json['frozen'] as bool, lastMessageAt: json['last_message_at'] == null ? null @@ -28,9 +25,7 @@ ChannelModel _$ChannelModelFromJson(Map json) { ? null : DateTime.parse(json['deleted_at'] as String), memberCount: json['member_count'] as int, - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ), + extraData: json['extra_data'] as Map?, team: json['team'] as String?, ); } diff --git a/packages/stream_chat/lib/src/models/channel_state.g.dart b/packages/stream_chat/lib/src/models/channel_state.g.dart index c207a2f6..713d324c 100644 --- a/packages/stream_chat/lib/src/models/channel_state.g.dart +++ b/packages/stream_chat/lib/src/models/channel_state.g.dart @@ -6,36 +6,27 @@ part of 'channel_state.dart'; // JsonSerializableGenerator // ************************************************************************** -ChannelState _$ChannelStateFromJson(Map json) { +ChannelState _$ChannelStateFromJson(Map json) { return ChannelState( channel: json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : ChannelModel.fromJson(json['channel'] as Map?), messages: (json['messages'] as List) - .map((e) => Message.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + .map((e) => Message.fromJson(e as Map?)) .toList(), members: (json['members'] as List) - .map((e) => e == null - ? null - : Member.fromJson(Map.from(e as Map))) + .map((e) => + e == null ? null : Member.fromJson(e as Map)) .toList(), pinnedMessages: (json['pinned_messages'] as List) - .map((e) => Message.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + .map((e) => Message.fromJson(e as Map?)) .toList(), watcherCount: json['watcher_count'] as int?, watchers: (json['watchers'] as List) - .map((e) => User.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + .map((e) => User.fromJson(e as Map?)) .toList(), read: (json['read'] as List) - .map((e) => Read.fromJson(Map.from(e as Map))) + .map((e) => Read.fromJson(e as Map)) .toList(), ); } diff --git a/packages/stream_chat/lib/src/models/command.g.dart b/packages/stream_chat/lib/src/models/command.g.dart index b6c08c32..1ce00415 100644 --- a/packages/stream_chat/lib/src/models/command.g.dart +++ b/packages/stream_chat/lib/src/models/command.g.dart @@ -6,7 +6,7 @@ part of 'command.dart'; // JsonSerializableGenerator // ************************************************************************** -Command _$CommandFromJson(Map json) { +Command _$CommandFromJson(Map json) { return Command( name: json['name'] as String?, description: json['description'] as String?, diff --git a/packages/stream_chat/lib/src/models/device.g.dart b/packages/stream_chat/lib/src/models/device.g.dart index 343ec0e5..fe65053b 100644 --- a/packages/stream_chat/lib/src/models/device.g.dart +++ b/packages/stream_chat/lib/src/models/device.g.dart @@ -6,7 +6,7 @@ part of 'device.dart'; // JsonSerializableGenerator // ************************************************************************** -Device _$DeviceFromJson(Map json) { +Device _$DeviceFromJson(Map json) { return Device( id: json['id'] as String, pushProvider: json['push_provider'] as String?, diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index 5e1e7e58..aa38a032 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -6,7 +6,7 @@ part of 'event.dart'; // JsonSerializableGenerator // ************************************************************************** -Event _$EventFromJson(Map json) { +Event _$EventFromJson(Map json) { return Event( type: json['type'] as String?, cid: json['cid'] as String?, @@ -16,41 +16,29 @@ Event _$EventFromJson(Map json) { : DateTime.parse(json['created_at'] as String), me: json['me'] == null ? null - : OwnUser.fromJson((json['me'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : OwnUser.fromJson(json['me'] as Map?), user: json['user'] == null ? null - : User.fromJson((json['user'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : User.fromJson(json['user'] as Map?), message: json['message'] == null ? null - : Message.fromJson((json['message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : Message.fromJson(json['message'] as Map?), totalUnreadCount: json['total_unread_count'] as int?, unreadChannels: json['unread_channels'] as int?, reaction: json['reaction'] == null ? null - : Reaction.fromJson((json['reaction'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : Reaction.fromJson(json['reaction'] as Map?), online: json['online'] as bool?, channel: json['channel'] == null ? null - : EventChannel.fromJson((json['channel'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : EventChannel.fromJson(json['channel'] as Map?), member: json['member'] == null ? null - : Member.fromJson(Map.from(json['member'] as Map)), + : Member.fromJson(json['member'] as Map), channelId: json['channel_id'] as String?, channelType: json['channel_type'] as String?, parentId: json['parent_id'] as String?, - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ), + extraData: json['extra_data'] as Map?, )..isLocal = json['is_local'] as bool?; } @@ -85,21 +73,18 @@ Map _$EventToJson(Event instance) { return val; } -EventChannel _$EventChannelFromJson(Map json) { +EventChannel _$EventChannelFromJson(Map json) { return EventChannel( members: (json['members'] as List?) - ?.map((e) => Member.fromJson(Map.from(e as Map))) + ?.map((e) => Member.fromJson(e as Map)) .toList(), id: json['id'] as String?, type: json['type'] as String?, cid: json['cid'] as String, - config: ChannelConfig.fromJson( - Map.from(json['config'] as Map)), + config: ChannelConfig.fromJson(json['config'] as Map), createdBy: json['created_by'] == null ? null - : User.fromJson((json['created_by'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : User.fromJson(json['created_by'] as Map?), frozen: json['frozen'] as bool, lastMessageAt: json['last_message_at'] == null ? null @@ -110,9 +95,7 @@ EventChannel _$EventChannelFromJson(Map json) { ? null : DateTime.parse(json['deleted_at'] as String), memberCount: json['member_count'] as int, - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ), + extraData: json['extra_data'] as Map?, ); } diff --git a/packages/stream_chat/lib/src/models/member.g.dart b/packages/stream_chat/lib/src/models/member.g.dart index f6716e3d..6c014f9f 100644 --- a/packages/stream_chat/lib/src/models/member.g.dart +++ b/packages/stream_chat/lib/src/models/member.g.dart @@ -6,13 +6,11 @@ part of 'member.dart'; // JsonSerializableGenerator // ************************************************************************** -Member _$MemberFromJson(Map json) { +Member _$MemberFromJson(Map json) { return Member( user: json['user'] == null ? null - : User.fromJson((json['user'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : User.fromJson(json['user'] as Map?), inviteAcceptedAt: json['invite_accepted_at'] == null ? null : DateTime.parse(json['invite_accepted_at'] as String), diff --git a/packages/stream_chat/lib/src/models/message.g.dart b/packages/stream_chat/lib/src/models/message.g.dart index cac1b1df..54e485a3 100644 --- a/packages/stream_chat/lib/src/models/message.g.dart +++ b/packages/stream_chat/lib/src/models/message.g.dart @@ -6,49 +6,39 @@ part of 'message.dart'; // JsonSerializableGenerator // ************************************************************************** -Message _$MessageFromJson(Map json) { +Message _$MessageFromJson(Map json) { return Message( id: json['id'] as String?, text: json['text'] as String, type: json['type'] as String, attachments: (json['attachments'] as List?) - ?.map((e) => Attachment.fromJson(Map.from(e as Map))) + ?.map((e) => Attachment.fromJson(e as Map)) .toList(), mentionedUsers: (json['mentioned_users'] as List?) - ?.map((e) => User.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + ?.map((e) => User.fromJson(e as Map?)) .toList(), silent: json['silent'] as bool?, shadowed: json['shadowed'] as bool?, - reactionCounts: (json['reaction_counts'] as Map?)?.map( - (k, e) => MapEntry(k as String, e as int), + reactionCounts: (json['reaction_counts'] as Map?)?.map( + (k, e) => MapEntry(k, e as int), ), - reactionScores: (json['reaction_scores'] as Map?)?.map( - (k, e) => MapEntry(k as String, e as int), + reactionScores: (json['reaction_scores'] as Map?)?.map( + (k, e) => MapEntry(k, e as int), ), latestReactions: (json['latest_reactions'] as List?) - ?.map((e) => Reaction.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + ?.map((e) => Reaction.fromJson(e as Map?)) .toList(), ownReactions: (json['own_reactions'] as List?) - ?.map((e) => Reaction.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + ?.map((e) => Reaction.fromJson(e as Map?)) .toList(), parentId: json['parent_id'] as String?, quotedMessage: json['quoted_message'] == null ? null - : Message.fromJson((json['quoted_message'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : Message.fromJson(json['quoted_message'] as Map?), quotedMessageId: json['quoted_message_id'] as String?, replyCount: json['reply_count'] as int?, threadParticipants: (json['thread_participants'] as List?) - ?.map((e) => User.fromJson((e as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ))) + ?.map((e) => User.fromJson(e as Map?)) .toList(), showInChannel: json['show_in_channel'] as bool?, command: json['command'] as String?, @@ -56,9 +46,7 @@ Message _$MessageFromJson(Map json) { updatedAt: DateTime.parse(json['updated_at'] as String), user: json['user'] == null ? null - : User.fromJson((json['user'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : User.fromJson(json['user'] as Map?), pinned: json['pinned'] as bool?, pinnedAt: json['pinned_at'] == null ? null @@ -68,12 +56,8 @@ Message _$MessageFromJson(Map json) { : DateTime.parse(json['pin_expires'] as String), pinnedBy: json['pinned_by'] == null ? null - : User.fromJson((json['pinned_by'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ), + : User.fromJson(json['pinned_by'] as Map?), + extraData: json['extra_data'] as Map?, deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), @@ -123,10 +107,10 @@ Map _$MessageToJson(Message instance) { return val; } -TranslatedMessage _$TranslatedMessageFromJson(Map json) { +TranslatedMessage _$TranslatedMessageFromJson(Map json) { return TranslatedMessage( - (json['i18n'] as Map?)?.map( - (k, e) => MapEntry(k as String, e as String), + (json['i18n'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), ), ); } diff --git a/packages/stream_chat/lib/src/models/mute.g.dart b/packages/stream_chat/lib/src/models/mute.g.dart index c8fe5444..15e3b810 100644 --- a/packages/stream_chat/lib/src/models/mute.g.dart +++ b/packages/stream_chat/lib/src/models/mute.g.dart @@ -6,18 +6,14 @@ part of 'mute.dart'; // JsonSerializableGenerator // ************************************************************************** -Mute _$MuteFromJson(Map json) { +Mute _$MuteFromJson(Map json) { return Mute( user: json['user'] == null ? null - : User.fromJson((json['user'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : User.fromJson(json['user'] as Map?), channel: json['channel'] == null ? null - : ChannelModel.fromJson((json['channel'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + : ChannelModel.fromJson(json['channel'] as Map?), createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), diff --git a/packages/stream_chat/lib/src/models/own_user.g.dart b/packages/stream_chat/lib/src/models/own_user.g.dart index 5cc0bd53..19629e42 100644 --- a/packages/stream_chat/lib/src/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/models/own_user.g.dart @@ -6,19 +6,18 @@ part of 'own_user.dart'; // JsonSerializableGenerator // ************************************************************************** -OwnUser _$OwnUserFromJson(Map json) { - print(json); +OwnUser _$OwnUserFromJson(Map json) { return OwnUser( devices: (json['devices'] as List) - .map((e) => Device.fromJson(Map.from(e as Map))) + .map((e) => Device.fromJson(e as Map)) .toList(), mutes: (json['mutes'] as List) - .map((e) => Mute.fromJson(Map.from(e as Map))) + .map((e) => Mute.fromJson(e as Map)) .toList(), totalUnreadCount: json['total_unread_count'] as int, unreadChannels: json['unread_channels'] as int?, channelMutes: (json['channel_mutes'] as List) - .map((e) => Mute.fromJson(Map.from(e as Map))) + .map((e) => Mute.fromJson(e as Map)) .toList(), id: json['id'] as String, role: json['role'] as String, @@ -28,7 +27,7 @@ OwnUser _$OwnUserFromJson(Map json) { ? null : DateTime.parse(json['last_active'] as String), online: json['online'] as bool, - extraData: Map.from(json['extra_data'] as Map), + extraData: json['extra_data'] as Map, banned: json['banned'] as bool, ); } diff --git a/packages/stream_chat/lib/src/models/reaction.g.dart b/packages/stream_chat/lib/src/models/reaction.g.dart index 2f1d1eb8..a0aad565 100644 --- a/packages/stream_chat/lib/src/models/reaction.g.dart +++ b/packages/stream_chat/lib/src/models/reaction.g.dart @@ -6,19 +6,15 @@ part of 'reaction.dart'; // JsonSerializableGenerator // ************************************************************************** -Reaction _$ReactionFromJson(Map json) { +Reaction _$ReactionFromJson(Map json) { return Reaction( messageId: json['message_id'] as String?, createdAt: DateTime.parse(json['created_at'] as String), type: json['type'] as String, - user: User.fromJson((json['user'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + user: User.fromJson(json['user'] as Map?), userId: json['user_id'] as String?, score: json['score'] as int, - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ), + extraData: json['extra_data'] as Map?, ); } diff --git a/packages/stream_chat/lib/src/models/read.g.dart b/packages/stream_chat/lib/src/models/read.g.dart index 7b2a6f48..158f6f8f 100644 --- a/packages/stream_chat/lib/src/models/read.g.dart +++ b/packages/stream_chat/lib/src/models/read.g.dart @@ -6,12 +6,10 @@ part of 'read.dart'; // JsonSerializableGenerator // ************************************************************************** -Read _$ReadFromJson(Map json) { +Read _$ReadFromJson(Map json) { return Read( lastRead: DateTime.parse(json['last_read'] as String), - user: User.fromJson((json['user'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), + user: User.fromJson(json['user'] as Map?), unreadMessages: json['unread_messages'] as int, ); } diff --git a/packages/stream_chat/lib/src/models/user.g.dart b/packages/stream_chat/lib/src/models/user.g.dart index f1e4088f..457999a6 100644 --- a/packages/stream_chat/lib/src/models/user.g.dart +++ b/packages/stream_chat/lib/src/models/user.g.dart @@ -6,7 +6,7 @@ part of 'user.dart'; // JsonSerializableGenerator // ************************************************************************** -User _$UserFromJson(Map json) { +User _$UserFromJson(Map json) { return User( id: json['id'] as String, role: json['role'] as String, @@ -16,7 +16,7 @@ User _$UserFromJson(Map json) { ? null : DateTime.parse(json['last_active'] as String), online: json['online'] as bool, - extraData: Map.from(json['extra_data'] as Map), + extraData: json['extra_data'] as Map, banned: json['banned'] as bool, teams: (json['teams'] as List).map((e) => e as String).toList(), ); diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 5d7c61f0..97d4fa6f 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -168,7 +168,8 @@ void main() { requestOptions: FakeRequestOptions(), )); - await channelClient.sendAction(Message.temp(id: 'messageid', text: ''), data); + await channelClient.sendAction( + Message.temp(id: 'messageid', text: ''), data); verify(() => mockDio.post('/messages/messageid/action', data: { 'id': 'testid', diff --git a/packages/stream_chat/test/src/api/requests_test.dart b/packages/stream_chat/test/src/api/requests_test.dart index 0e33b3d9..76253403 100644 --- a/packages/stream_chat/test/src/api/requests_test.dart +++ b/packages/stream_chat/test/src/api/requests_test.dart @@ -1,5 +1,5 @@ -import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; void main() { group('src/api/requests', () { @@ -14,7 +14,6 @@ void main() { final j = option.toJson(); expect(j, containsPair('limit', 10)); expect(j, containsPair('offset', 0)); - expect(j, contains('hash_code')); }); }); } diff --git a/packages/stream_chat/test/src/api/websocket_test.dart b/packages/stream_chat/test/src/api/websocket_test.dart index f6616beb..8f16a25d 100644 --- a/packages/stream_chat/test/src/api/websocket_test.dart +++ b/packages/stream_chat/test/src/api/websocket_test.dart @@ -35,7 +35,7 @@ void main() { }); test('should connect with correct parameters', () async { - final ConnectWebSocket connectFunc = MockFunctions().connectFunc; + final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', user: User.temp(id: 'testid'), @@ -73,7 +73,7 @@ void main() { test('should connect with correct parameters and handle events', () async { final handleFunc = MockFunctions().handleFunc; - final ConnectWebSocket connectFunc = MockFunctions().connectFunc; + final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', user: User.temp(id: 'testid'), @@ -92,7 +92,7 @@ void main() { when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - final connect = ws.connect()?.then((_) { + final connect = ws.connect().then((_) { streamController.sink.add('{}'); return Future.delayed(const Duration(milliseconds: 200)); }).then((value) { @@ -109,7 +109,7 @@ void main() { test('should close correctly the controller', () async { final handleFunc = MockFunctions().handleFunc; - final ConnectWebSocket connectFunc = MockFunctions().connectFunc; + final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', user: User.temp(id: 'testid'), @@ -128,7 +128,7 @@ void main() { when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - final connect = ws.connect()?.then((_) { + final connect = ws.connect().then((_) { streamController.sink.add('{}'); return Future.delayed(const Duration(milliseconds: 200)); }).then((value) { @@ -146,7 +146,7 @@ void main() { test('should close correctly the controller while connecting', () async { final handleFunc = MockFunctions().handleFunc; - final ConnectWebSocket connectFunc = MockFunctions().connectFunc; + final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', @@ -181,7 +181,7 @@ void main() { test('should run correctly health check', () async { final handleFunc = MockFunctions().handleFunc; - final ConnectWebSocket connectFunc = MockFunctions().connectFunc; + final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', user: User.temp(id: 'testid'), @@ -206,7 +206,7 @@ void main() { (_) => streamController.sink.add('{}'), ); - final connect = ws.connect()?.then((_) { + final connect = ws.connect().then((_) { streamController.sink.add('{}'); return Future.delayed(const Duration(milliseconds: 200)); }).then((value) async { @@ -225,7 +225,7 @@ void main() { test('should run correctly reconnection check', () async { final handleFunc = MockFunctions().handleFunc; - final ConnectWebSocket connectFunc = MockFunctions().connectFunc; + final connectFunc = MockFunctions().connectFunc; Logger.root.level = Level.ALL; final ws = WebSocket( baseUrl: 'baseurl', @@ -247,7 +247,7 @@ void main() { when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); when(() => mockWSChannel.sink).thenReturn(mockWSSink); - final connect = ws.connect()?.then((_) { + final connect = ws.connect().then((_) { streamController.sink.add('{}'); streamController.close(); streamController = StreamController.broadcast(); @@ -270,7 +270,7 @@ void main() { test('should close correctly the controller', () async { final handleFunc = MockFunctions().handleFunc; - final ConnectWebSocket connectFunc = MockFunctions().connectFunc; + final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', user: User.temp(id: 'testid'), @@ -290,7 +290,7 @@ void main() { when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); when(() => mockWSChannel.sink).thenReturn(mockWSSink); - final connect = ws.connect()?.then((_) { + final connect = ws.connect().then((_) { streamController.sink.add('{}'); return Future.delayed(const Duration(milliseconds: 200)); }).then((value) async { @@ -307,7 +307,7 @@ void main() { }); test('should throw an error', () async { - final ConnectWebSocket connectFunc = MockFunctions().connectFunc; + final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', user: User.temp(id: 'testid'), diff --git a/packages/stream_chat/test/src/models/reaction_test.dart b/packages/stream_chat/test/src/models/reaction_test.dart index e82dcc79..2a884962 100644 --- a/packages/stream_chat/test/src/models/reaction_test.dart +++ b/packages/stream_chat/test/src/models/reaction_test.dart @@ -34,7 +34,7 @@ void main() { expect(reaction.type, 'wow'); expect( reaction.user.toJson(), - User.temp(id:'2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { + User.temp(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { 'image': 'https://randomuser.me/api/portraits/women/45.jpg', 'name': 'Daisy Morgan' }).toJson(), @@ -49,7 +49,7 @@ void main() { messageId: '76cd8c82-b557-4e48-9d12-87995d3a0e04', createdAt: DateTime.parse('2020-01-28T22:17:31.108742Z'), type: 'wow', - user: User.temp(id:'2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { + user: User.temp(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { 'image': 'https://randomuser.me/api/portraits/women/45.jpg', 'name': 'Daisy Morgan' }), diff --git a/packages/stream_chat/test/src/models/user_test.dart b/packages/stream_chat/test/src/models/user_test.dart index 8a30ad4d..b28888c4 100644 --- a/packages/stream_chat/test/src/models/user_test.dart +++ b/packages/stream_chat/test/src/models/user_test.dart @@ -1,13 +1,14 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:stream_chat/src/models/user.dart'; +import 'package:test/test.dart'; void main() { group('src/models/user', () { const jsonExample = ''' { - "id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e" + "id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e", + "role": "test-role" } '''; From 88f9bcc80c39c28242b1afffe12c70b24bfe2ada Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 14 Apr 2021 14:56:04 +0530 Subject: [PATCH 092/599] feat: Converted to ios models part II --- packages/stream_chat/example/lib/main.dart | 2 +- packages/stream_chat/lib/src/api/channel.dart | 8 +-- packages/stream_chat/lib/src/client.dart | 6 +-- .../lib/src/db/chat_persistence_client.dart | 4 +- .../lib/src/models/attachment.dart | 3 +- .../lib/src/models/attachment.g.dart | 2 +- .../lib/src/models/channel_model.dart | 32 ++++-------- .../lib/src/models/channel_model.g.dart | 16 ++++-- .../lib/src/models/channel_state.dart | 9 +++- .../lib/src/models/channel_state.g.dart | 38 +++++++------- .../stream_chat/lib/src/models/device.dart | 2 +- .../stream_chat/lib/src/models/event.g.dart | 4 +- .../stream_chat/lib/src/models/member.dart | 13 +++-- .../stream_chat/lib/src/models/member.g.dart | 16 +++--- .../stream_chat/lib/src/models/message.dart | 51 +++---------------- .../stream_chat/lib/src/models/message.g.dart | 8 ++- .../stream_chat/lib/src/models/own_user.dart | 46 +++++++---------- .../lib/src/models/own_user.g.dart | 37 ++++++++------ packages/stream_chat/lib/src/models/read.dart | 1 + .../stream_chat/lib/src/models/read.g.dart | 2 +- packages/stream_chat/lib/src/models/user.dart | 35 ++++++------- .../stream_chat/lib/src/models/user.g.dart | 18 ++++--- .../test/src/api/channel_test.dart | 43 ++++++---------- .../test/src/api/websocket_test.dart | 16 +++--- .../stream_chat/test/src/client_test.dart | 20 +++----- .../test/src/models/channel_state_test.dart | 16 +++--- .../test/src/models/channel_test.dart | 6 +-- .../test/src/models/event_test.dart | 4 +- .../test/src/models/message_test.dart | 2 +- .../test/src/models/reaction_test.dart | 4 +- .../test/src/models/read_test.dart | 4 +- .../test/src/models/user_test.dart | 2 +- 32 files changed, 217 insertions(+), 253 deletions(-) diff --git a/packages/stream_chat/example/lib/main.dart b/packages/stream_chat/example/lib/main.dart index 878b5fa1..d13dda4c 100644 --- a/packages/stream_chat/example/lib/main.dart +++ b/packages/stream_chat/example/lib/main.dart @@ -11,7 +11,7 @@ Future main() async { /// Please see the following for more information: /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ await client.connectUser( - User.temp( + User( id: 'cool-shadow-7', extraData: { 'image': diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index dd25976c..01d8399e 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1312,7 +1312,7 @@ class ChannelClientState { updateChannelState(channelState!.copyWith( members: [ ...channelState!.members, - member, + member!, ], )); })); @@ -1323,7 +1323,7 @@ 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)), )); })); } @@ -1549,7 +1549,7 @@ class ChannelClientState { /// Channel members list List get members => _channelState!.members - .map((e) => e!.copyWith(user: _channel.client.state!.users[e.user!.id])) + .map((e) => e.copyWith(user: _channel.client.state!.users[e.user!.id])) .toList(); /// Channel members list as a stream @@ -1664,7 +1664,7 @@ class ChannelClientState { [], ]; - final newMembers = [ + final newMembers = [ ...updatedState.members, ]; diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 147a08d0..816d780c 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -311,7 +311,7 @@ class StreamChatClient { httpClient.unlock(); - await connectUser(User.temp(id: userId), newToken); + await connectUser(User(id: userId), newToken); try { handler.resolve( @@ -753,7 +753,7 @@ class StreamChatClient { final users = channels .expand((it) => it.members) - .map((it) => it!.user) + .map((it) => it.user) .toList(growable: false); state!._updateUsers(users); @@ -956,7 +956,7 @@ class StreamChatClient { _anonymous = true; const uuid = Uuid(); - state!.user = OwnUser.temp(id: uuid.v4()); + state!.user = OwnUser(id: uuid.v4()); return connect().then((event) { _connectCompleter!.complete(event); diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index daabc26a..2646665a 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -76,7 +76,7 @@ abstract class ChatPersistenceClient { getPinnedMessagesByCid(cid, messagePagination: pinnedMessagePagination), ]); return ChannelState( - members: (data[0] as List?)!, + members: (data[0] as List?)!, read: (data[1] as List?)!, channel: data[2] as ChannelModel?, messages: (data[3] as List?)!, @@ -212,7 +212,7 @@ abstract class ChatPersistenceClient { ]) .expand((v) => v), ...cs.read.map((r) => r.user), - ...cs.members.map((m) => m!.user), + ...cs.members.map((m) => m.user), ]) .expand((it) => it) .where((it) => it != null); diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/models/attachment.dart index 61f7afdf..7a302447 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/models/attachment.dart @@ -15,7 +15,7 @@ class Attachment extends Equatable { /// Constructor used for json serialization Attachment({ String? id, - required this.type, + String? type, this.titleLink, String? title, this.thumbUrl, @@ -38,6 +38,7 @@ class Attachment extends Equatable { UploadState? uploadState, }) : id = id ?? const Uuid().v4(), title = title ?? file?.name, + type = type ?? '', localUri = file?.path != null ? Uri.parse(file!.path!) : null { this.uploadState = uploadState ?? ((assetUrl != null || imageUrl != null) diff --git a/packages/stream_chat/lib/src/models/attachment.g.dart b/packages/stream_chat/lib/src/models/attachment.g.dart index ff371a5f..acee04fa 100644 --- a/packages/stream_chat/lib/src/models/attachment.g.dart +++ b/packages/stream_chat/lib/src/models/attachment.g.dart @@ -9,7 +9,7 @@ part of 'attachment.dart'; Attachment _$AttachmentFromJson(Map json) { return Attachment( id: json['id'] as String?, - type: json['type'] as String, + type: json['type'] as String?, titleLink: json['title_link'] as String?, title: json['title'] as String?, thumbUrl: json['thumb_url'] as String?, diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index caffc30a..5a45f48d 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -12,33 +12,20 @@ class ChannelModel { ChannelModel({ this.id, this.type, - required this.cid, - required this.config, + this.cid = '', + ChannelConfig? config, this.createdBy, this.frozen = false, this.lastMessageAt, - required this.createdAt, - required this.updatedAt, + DateTime? createdAt, + DateTime? updatedAt, this.deletedAt, this.memberCount = 0, this.extraData, this.team, - }); - - ChannelModel.temp({ - this.id, - this.type, - required this.cid, - this.createdBy, - this.frozen = false, - this.lastMessageAt, - this.deletedAt, - this.memberCount = 0, - this.extraData, - this.team, - }) : createdAt = DateTime.now(), - updatedAt = DateTime.now(), - config = ChannelConfig(); + }) : config = config ?? ChannelConfig(), + createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json factory ChannelModel.fromJson(Map? json) => @@ -64,7 +51,7 @@ class ChannelModel { final User? createdBy; /// True if this channel is frozen - @JsonKey(includeIfNull: false) + @JsonKey(includeIfNull: false, defaultValue: false) final bool frozen; /// The date of the last message @@ -84,7 +71,8 @@ class ChannelModel { final DateTime? deletedAt; /// The count of this channel members - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey( + includeIfNull: false, toJson: Serialization.readOnly, defaultValue: 0) final int memberCount; /// Map of custom channel extraData diff --git a/packages/stream_chat/lib/src/models/channel_model.g.dart b/packages/stream_chat/lib/src/models/channel_model.g.dart index eb292e8d..19c4c085 100644 --- a/packages/stream_chat/lib/src/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/models/channel_model.g.dart @@ -11,20 +11,26 @@ ChannelModel _$ChannelModelFromJson(Map json) { id: json['id'] as String?, type: json['type'] as String?, cid: json['cid'] as String, - config: ChannelConfig.fromJson(json['config'] as Map), + config: json['config'] == null + ? null + : ChannelConfig.fromJson(json['config'] as Map), createdBy: json['created_by'] == null ? null : User.fromJson(json['created_by'] as Map?), - frozen: json['frozen'] as bool, + frozen: json['frozen'] as bool? ?? false, lastMessageAt: json['last_message_at'] == null ? null : DateTime.parse(json['last_message_at'] as String), - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), - memberCount: json['member_count'] as int, + memberCount: json['member_count'] as int? ?? 0, extraData: json['extra_data'] as Map?, team: json['team'] as String?, ); diff --git a/packages/stream_chat/lib/src/models/channel_state.dart b/packages/stream_chat/lib/src/models/channel_state.dart index 5f9f02bf..9d18f434 100644 --- a/packages/stream_chat/lib/src/models/channel_state.dart +++ b/packages/stream_chat/lib/src/models/channel_state.dart @@ -25,21 +25,26 @@ class ChannelState { final ChannelModel? channel; /// A paginated list of channel messages + @JsonKey(defaultValue: []) final List messages; /// A paginated list of channel members - final List members; + @JsonKey(defaultValue: []) + final List members; /// A paginated list of pinned messages + @JsonKey(defaultValue: []) final List pinnedMessages; /// The count of users watching the channel final int? watcherCount; /// A paginated list of users watching the channel + @JsonKey(defaultValue: []) final List watchers; /// The list of channel reads + @JsonKey(defaultValue: []) final List read; /// Create a new instance from a json @@ -53,7 +58,7 @@ class ChannelState { ChannelState copyWith({ ChannelModel? channel, List? messages, - List? members, + List? members, List? pinnedMessages, int? watcherCount, List? watchers, diff --git a/packages/stream_chat/lib/src/models/channel_state.g.dart b/packages/stream_chat/lib/src/models/channel_state.g.dart index 713d324c..67e98782 100644 --- a/packages/stream_chat/lib/src/models/channel_state.g.dart +++ b/packages/stream_chat/lib/src/models/channel_state.g.dart @@ -11,23 +11,27 @@ ChannelState _$ChannelStateFromJson(Map json) { channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map?), - messages: (json['messages'] as List) - .map((e) => Message.fromJson(e as Map?)) - .toList(), - members: (json['members'] as List) - .map((e) => - e == null ? null : Member.fromJson(e as Map)) - .toList(), - pinnedMessages: (json['pinned_messages'] as List) - .map((e) => Message.fromJson(e as Map?)) - .toList(), + messages: (json['messages'] as List?) + ?.map((e) => Message.fromJson(e as Map?)) + .toList() ?? + [], + members: (json['members'] as List?) + ?.map((e) => Member.fromJson(e as Map)) + .toList() ?? + [], + pinnedMessages: (json['pinned_messages'] as List?) + ?.map((e) => Message.fromJson(e as Map?)) + .toList() ?? + [], watcherCount: json['watcher_count'] as int?, - watchers: (json['watchers'] as List) - .map((e) => User.fromJson(e as Map?)) - .toList(), - read: (json['read'] as List) - .map((e) => Read.fromJson(e as Map)) - .toList(), + watchers: (json['watchers'] as List?) + ?.map((e) => User.fromJson(e as Map?)) + .toList() ?? + [], + read: (json['read'] as List?) + ?.map((e) => Read.fromJson(e as Map)) + .toList() ?? + [], ); } @@ -35,7 +39,7 @@ Map _$ChannelStateToJson(ChannelState instance) => { 'channel': instance.channel?.toJson(), 'messages': instance.messages.map((e) => e.toJson()).toList(), - 'members': instance.members.map((e) => e?.toJson()).toList(), + 'members': instance.members.map((e) => e.toJson()).toList(), 'pinned_messages': instance.pinnedMessages.map((e) => e.toJson()).toList(), 'watcher_count': instance.watcherCount, diff --git a/packages/stream_chat/lib/src/models/device.dart b/packages/stream_chat/lib/src/models/device.dart index dc613b3c..24dcb678 100644 --- a/packages/stream_chat/lib/src/models/device.dart +++ b/packages/stream_chat/lib/src/models/device.dart @@ -7,7 +7,7 @@ part 'device.g.dart'; class Device { /// Constructor used for json serialization Device({ - required this.id, + this.id = '', this.pushProvider, }); diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index aa38a032..651c5837 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -85,7 +85,7 @@ EventChannel _$EventChannelFromJson(Map json) { createdBy: json['created_by'] == null ? null : User.fromJson(json['created_by'] as Map?), - frozen: json['frozen'] as bool, + frozen: json['frozen'] as bool? ?? false, lastMessageAt: json['last_message_at'] == null ? null : DateTime.parse(json['last_message_at'] as String), @@ -94,7 +94,7 @@ EventChannel _$EventChannelFromJson(Map json) { deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), - memberCount: json['member_count'] as int, + memberCount: json['member_count'] as int? ?? 0, extraData: json['extra_data'] as Map?, ); } diff --git a/packages/stream_chat/lib/src/models/member.dart b/packages/stream_chat/lib/src/models/member.dart index 735e4ff3..ad5f64c8 100644 --- a/packages/stream_chat/lib/src/models/member.dart +++ b/packages/stream_chat/lib/src/models/member.dart @@ -13,14 +13,15 @@ class Member { this.inviteAcceptedAt, this.inviteRejectedAt, this.invited = false, - required this.role, + this.role = '', this.userId, this.isModerator, - required this.createdAt, - required this.updatedAt, + DateTime? createdAt, + DateTime? updatedAt, this.banned = false, this.shadowBanned = false, - }); + }) : createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json factory Member.fromJson(Map json) { @@ -40,9 +41,11 @@ class Member { final DateTime? inviteRejectedAt; /// True if the user has been invited to the channel + @JsonKey(defaultValue: false) final bool invited; /// The role of the user in the channel + @JsonKey(defaultValue: '') final String role; /// The id of the interested user @@ -52,9 +55,11 @@ class Member { final bool? isModerator; /// True if the member is banned from the channel + @JsonKey(defaultValue: false) final bool banned; /// True if the member is shadow banned from the channel + @JsonKey(defaultValue: false) final bool shadowBanned; /// The date of creation diff --git a/packages/stream_chat/lib/src/models/member.g.dart b/packages/stream_chat/lib/src/models/member.g.dart index 6c014f9f..df9b6641 100644 --- a/packages/stream_chat/lib/src/models/member.g.dart +++ b/packages/stream_chat/lib/src/models/member.g.dart @@ -17,14 +17,18 @@ Member _$MemberFromJson(Map json) { inviteRejectedAt: json['invite_rejected_at'] == null ? null : DateTime.parse(json['invite_rejected_at'] as String), - invited: json['invited'] as bool, - role: json['role'] as String, + invited: json['invited'] as bool? ?? false, + role: json['role'] as String? ?? '', userId: json['user_id'] as String?, isModerator: json['is_moderator'] as bool?, - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), - banned: json['banned'] as bool, - shadowBanned: json['shadow_banned'] as bool, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + banned: json['banned'] as bool? ?? false, + shadowBanned: json['shadow_banned'] as bool? ?? false, ); } diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index 8f260b1b..f7fcfbf1 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -46,41 +46,8 @@ class Message extends Equatable { /// Constructor used for json serialization Message({ String? id, - required this.text, - required this.type, - this.attachments, - this.mentionedUsers, - this.silent, - this.shadowed, - this.reactionCounts, - this.reactionScores, - this.latestReactions, - this.ownReactions, - this.parentId, - this.quotedMessage, - this.quotedMessageId, - this.replyCount = 0, - this.threadParticipants, - this.showInChannel, - this.command, - required this.createdAt, - required this.updatedAt, - this.user, - this.pinned = false, - this.pinnedAt, - DateTime? pinExpires, - this.pinnedBy, - this.extraData, - this.deletedAt, - this.status = MessageSendingStatus.sent, - this.skipPush, - }) : id = id ?? const Uuid().v4(), - pinExpires = pinExpires?.toUtc(); - - /// Constructor for creating temporary/throwaway message with id and text - Message.temp({ - String? id, - required this.text, + this.text = '', + this.type = '', this.attachments, this.mentionedUsers, this.silent, @@ -96,6 +63,8 @@ class Message extends Equatable { this.threadParticipants, this.showInChannel, this.command, + DateTime? createdAt, + DateTime? updatedAt, this.user, this.pinned = false, this.pinnedAt, @@ -107,9 +76,8 @@ class Message extends Equatable { this.skipPush, }) : id = id ?? const Uuid().v4(), pinExpires = pinExpires?.toUtc(), - createdAt = DateTime.now(), - updatedAt = DateTime.now(), - type = ''; + createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json factory Message.fromJson(Map? json) => _$MessageFromJson( @@ -418,12 +386,7 @@ class Message extends Equatable { @JsonSerializable() class TranslatedMessage extends Message { /// Constructor used for json serialization - TranslatedMessage(this.i18n) - : super( - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - text: '', - type: ''); + TranslatedMessage(this.i18n) : super(); /// Create a new instance from a json factory TranslatedMessage.fromJson(Map? json) => diff --git a/packages/stream_chat/lib/src/models/message.g.dart b/packages/stream_chat/lib/src/models/message.g.dart index 54e485a3..fddbdd61 100644 --- a/packages/stream_chat/lib/src/models/message.g.dart +++ b/packages/stream_chat/lib/src/models/message.g.dart @@ -42,8 +42,12 @@ Message _$MessageFromJson(Map json) { .toList(), showInChannel: json['show_in_channel'] as bool?, command: json['command'] as String?, - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), user: json['user'] == null ? null : User.fromJson(json['user'] as Map?), diff --git a/packages/stream_chat/lib/src/models/own_user.dart b/packages/stream_chat/lib/src/models/own_user.dart index 9ebc633b..2ba29aa0 100644 --- a/packages/stream_chat/lib/src/models/own_user.dart +++ b/packages/stream_chat/lib/src/models/own_user.dart @@ -17,10 +17,10 @@ class OwnUser extends User { this.totalUnreadCount = 0, this.unreadChannels, this.channelMutes = const [], - required String id, - required String role, - required DateTime createdAt, - required DateTime updatedAt, + String id = '', + String role = '', + DateTime? createdAt, + DateTime? updatedAt, DateTime? lastActive, bool online = false, Map extraData = const {}, @@ -36,44 +36,34 @@ class OwnUser extends User { banned: banned, ); - /// Create a temporary/throwaway user with an ID - OwnUser.temp({ - required String id, - this.devices = const [], - this.mutes = const [], - this.totalUnreadCount = 0, - this.unreadChannels, - this.channelMutes = const [], - DateTime? lastActive, - bool online = false, - Map extraData = const {}, - bool banned = false, - }) : super.temp( - id: id, - lastActive: lastActive, - online: online, - extraData: extraData, - banned: banned, - ); - /// Create a new instance from a json factory OwnUser.fromJson(Map? json) => _$OwnUserFromJson( Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); /// List of user devices - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey( + includeIfNull: false, + toJson: Serialization.readOnly, + defaultValue: []) final List devices; /// List of users muted by the user - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey( + includeIfNull: false, + toJson: Serialization.readOnly, + defaultValue: []) final List mutes; /// List of users muted by the user - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey( + includeIfNull: false, + toJson: Serialization.readOnly, + defaultValue: []) final List channelMutes; /// Total unread messages by the user - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey( + includeIfNull: false, toJson: Serialization.readOnly, defaultValue: 0) final int totalUnreadCount; /// Total unread channels by the user diff --git a/packages/stream_chat/lib/src/models/own_user.g.dart b/packages/stream_chat/lib/src/models/own_user.g.dart index 19629e42..5802060c 100644 --- a/packages/stream_chat/lib/src/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/models/own_user.g.dart @@ -8,27 +8,34 @@ part of 'own_user.dart'; OwnUser _$OwnUserFromJson(Map json) { return OwnUser( - devices: (json['devices'] as List) - .map((e) => Device.fromJson(e as Map)) - .toList(), - mutes: (json['mutes'] as List) - .map((e) => Mute.fromJson(e as Map)) - .toList(), - totalUnreadCount: json['total_unread_count'] as int, + devices: (json['devices'] as List?) + ?.map((e) => Device.fromJson(e as Map)) + .toList() ?? + [], + mutes: (json['mutes'] as List?) + ?.map((e) => Mute.fromJson(e as Map)) + .toList() ?? + [], + totalUnreadCount: json['total_unread_count'] as int? ?? 0, unreadChannels: json['unread_channels'] as int?, - channelMutes: (json['channel_mutes'] as List) - .map((e) => Mute.fromJson(e as Map)) - .toList(), + channelMutes: (json['channel_mutes'] as List?) + ?.map((e) => Mute.fromJson(e as Map)) + .toList() ?? + [], id: json['id'] as String, - role: json['role'] as String, - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), + role: json['role'] as String? ?? '', + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), lastActive: json['last_active'] == null ? null : DateTime.parse(json['last_active'] as String), - online: json['online'] as bool, + online: json['online'] as bool? ?? false, extraData: json['extra_data'] as Map, - banned: json['banned'] as bool, + banned: json['banned'] as bool? ?? false, ); } diff --git a/packages/stream_chat/lib/src/models/read.dart b/packages/stream_chat/lib/src/models/read.dart index 3fccc20b..ef20eb6e 100644 --- a/packages/stream_chat/lib/src/models/read.dart +++ b/packages/stream_chat/lib/src/models/read.dart @@ -23,6 +23,7 @@ class Read { final User user; /// Number of unread messages + @JsonKey(defaultValue: 0) final int unreadMessages; /// Serialize to json diff --git a/packages/stream_chat/lib/src/models/read.g.dart b/packages/stream_chat/lib/src/models/read.g.dart index 158f6f8f..82aeba22 100644 --- a/packages/stream_chat/lib/src/models/read.g.dart +++ b/packages/stream_chat/lib/src/models/read.g.dart @@ -10,7 +10,7 @@ Read _$ReadFromJson(Map json) { return Read( lastRead: DateTime.parse(json['last_read'] as String), user: User.fromJson(json['user'] as Map?), - unreadMessages: json['unread_messages'] as int, + unreadMessages: json['unread_messages'] as int? ?? 0, ); } diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/models/user.dart index 1488696a..ab690e8f 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/models/user.dart @@ -8,28 +8,17 @@ part 'user.g.dart'; class User { /// Constructor used for json serialization User({ - required this.id, - required this.role, - required this.createdAt, - required this.updatedAt, - this.lastActive, - this.online = false, - this.extraData = const {}, - this.banned = false, - this.teams = const [], - }); - - /// Use constructor for a temporary/throwaway user with an ID - User.temp({ - required this.id, + this.id = '', this.role = '', + DateTime? createdAt, + DateTime? updatedAt, this.lastActive, this.online = false, this.extraData = const {}, this.banned = false, this.teams = const [], - }) : createdAt = DateTime.now(), - updatedAt = DateTime.now(); + }) : createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json factory User.fromJson(Map? json) => _$UserFromJson( @@ -64,11 +53,15 @@ class User { final String id; /// User role - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey( + includeIfNull: false, toJson: Serialization.readOnly, defaultValue: '') final String role; /// User role - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey( + includeIfNull: false, + toJson: Serialization.readOnly, + defaultValue: []) final List teams; /// Date of user creation @@ -84,11 +77,13 @@ class User { final DateTime? lastActive; /// True if user is online - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey( + includeIfNull: false, toJson: Serialization.readOnly, defaultValue: false) final bool online; /// True if user is banned from the chat - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey( + includeIfNull: false, toJson: Serialization.readOnly, defaultValue: false) final bool banned; /// Map of custom user extraData diff --git a/packages/stream_chat/lib/src/models/user.g.dart b/packages/stream_chat/lib/src/models/user.g.dart index 457999a6..18408d76 100644 --- a/packages/stream_chat/lib/src/models/user.g.dart +++ b/packages/stream_chat/lib/src/models/user.g.dart @@ -9,16 +9,22 @@ part of 'user.dart'; User _$UserFromJson(Map json) { return User( id: json['id'] as String, - role: json['role'] as String, - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), + role: json['role'] as String? ?? '', + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), lastActive: json['last_active'] == null ? null : DateTime.parse(json['last_active'] as String), - online: json['online'] as bool, + online: json['online'] as bool? ?? false, extraData: json['extra_data'] as Map, - banned: json['banned'] as bool, - teams: (json['teams'] as List).map((e) => e as String).toList(), + banned: json['banned'] as bool? ?? false, + teams: + (json['teams'] as List?)?.map((e) => e as String).toList() ?? + [], ); } diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 97d4fa6f..5eee577e 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -35,7 +35,7 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message.temp(text: 'hey', id: 'test'); + final message = Message(text: 'hey', id: 'test'); when( () => mockDio.post( @@ -168,8 +168,7 @@ void main() { requestOptions: FakeRequestOptions(), )); - await channelClient.sendAction( - Message.temp(id: 'messageid', text: ''), data); + await channelClient.sendAction(Message(id: 'messageid'), data); verify(() => mockDio.post('/messages/messageid/action', data: { 'id': 'testid', @@ -335,7 +334,7 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); - final message = Message.temp(text: 'Hello'); + final message = Message(text: 'Hello'); expect( () => channelClient.pinMessage(message, 'InvalidType'), @@ -355,7 +354,7 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message.temp(text: 'Hello', id: 'test'); + final message = Message(text: 'Hello', id: 'test'); when( () => mockDio.post( @@ -389,7 +388,7 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message.temp(text: 'Hello', id: 'test'); + final message = Message(text: 'Hello', id: 'test'); when( () => mockDio.post( @@ -567,7 +566,7 @@ void main() { tokenProvider: (_) async => '', ); - client.state?.user = OwnUser.temp(id: 'test-id'); + client.state?.user = OwnUser(id: 'test-id'); final channelClient = client.channel('messaging', id: 'testid'); const reactionType = 'test'; @@ -591,13 +590,8 @@ void main() { ); await channelClient.sendReaction( - Message.temp( + Message( id: 'messageid', - text: '', - reactionCounts: const {}, - reactionScores: const {}, - latestReactions: const [], - ownReactions: const [], ), reactionType, ); @@ -623,7 +617,7 @@ void main() { tokenProvider: (_) async => '', ); - client.state?.user = OwnUser.temp(id: 'test-id'); + client.state?.user = OwnUser(id: 'test-id'); final channelClient = client.channel('messaging', id: 'testid'); @@ -638,19 +632,14 @@ void main() { ); await channelClient.deleteReaction( - Message.temp( + Message( id: 'messageid', - text: '', - reactionCounts: const {}, - reactionScores: const {}, - latestReactions: const [], - ownReactions: const [], ), Reaction( type: 'test', createdAt: DateTime.now(), score: 0, - user: User.temp( + user: User( id: client.state?.user?.id ?? '', ), ), @@ -709,7 +698,7 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); final members = ['vishal']; - final message = Message.temp(text: 'test'); + final message = Message(text: 'test'); when( () => mockDio.post( @@ -743,7 +732,7 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message.temp(text: 'test'); + final message = Message(text: 'test'); when( () => mockDio.post( @@ -2091,7 +2080,7 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message.temp(text: 'test'); + final message = Message(text: 'test'); when( () => mockDio.post( @@ -2190,7 +2179,7 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final message = Message.temp(text: 'test'); + final message = Message(text: 'test'); when( () => mockDio.post( @@ -2225,7 +2214,7 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); final members = ['vishal']; - final message = Message.temp(text: 'test'); + final message = Message(text: 'test'); when( () => mockDio.post( @@ -2259,7 +2248,7 @@ void main() { ); final channelClient = client.channel('messaging', id: 'testid'); final members = ['vishal']; - final message = Message.temp(text: 'test'); + final message = Message(text: 'test'); when( () => mockDio.post( diff --git a/packages/stream_chat/test/src/api/websocket_test.dart b/packages/stream_chat/test/src/api/websocket_test.dart index 8f16a25d..156b203e 100644 --- a/packages/stream_chat/test/src/api/websocket_test.dart +++ b/packages/stream_chat/test/src/api/websocket_test.dart @@ -38,7 +38,7 @@ void main() { final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', - user: User.temp(id: 'testid'), + user: User(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -76,7 +76,7 @@ void main() { final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', - user: User.temp(id: 'testid'), + user: User(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -112,7 +112,7 @@ void main() { final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', - user: User.temp(id: 'testid'), + user: User(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -150,7 +150,7 @@ void main() { final ws = WebSocket( baseUrl: 'baseurl', - user: User.temp(id: 'testid'), + user: User(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -184,7 +184,7 @@ void main() { final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', - user: User.temp(id: 'testid'), + user: User(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -229,7 +229,7 @@ void main() { Logger.root.level = Level.ALL; final ws = WebSocket( baseUrl: 'baseurl', - user: User.temp(id: 'testid'), + user: User(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -273,7 +273,7 @@ void main() { final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', - user: User.temp(id: 'testid'), + user: User(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, @@ -310,7 +310,7 @@ void main() { final connectFunc = MockFunctions().connectFunc; final ws = WebSocket( baseUrl: 'baseurl', - user: User.temp(id: 'testid'), + user: User(id: 'testid'), logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 775d68b0..8f95b3a4 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -479,7 +479,7 @@ void main() { final client = StreamChatClient('api-key', httpClient: mockDio); - expect(() => client.connectUserWithProvider(User.temp(id: 'test-id')), + expect(() => client.connectUserWithProvider(User(id: 'test-id')), throwsA(isA())); }); @@ -517,7 +517,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final user = User.temp(id: 'test-id'); + final user = User(id: 'test-id'); final data = { 'users': {user.id: user.toJson()}, }; @@ -542,8 +542,8 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final user = User.temp(id: 'test-id'); - final user2 = User.temp(id: 'test-id2'); + final user = User(id: 'test-id'); + final user2 = User(id: 'test-id2'); final data = { 'users': { @@ -739,10 +739,6 @@ void main() { final client = StreamChatClient('api-key', httpClient: mockDio); final message = Message( id: 'test', - updatedAt: DateTime.now(), - createdAt: DateTime.now(), - text: '', - type: '', ); when( @@ -781,7 +777,7 @@ void main() { ), ); - await client.deleteMessage(Message.temp(id: messageId, text: '')); + await client.deleteMessage(Message(id: messageId, text: '')); verify(() => mockDio.delete('/messages/$messageId')).called(1); }); @@ -1101,7 +1097,7 @@ void main() { ); test('should throw argument error', () { - final message = Message.temp(text: 'Hello'); + final message = Message(text: 'Hello'); expect( () => client.pinMessage(message, 'InvalidType'), throwsArgumentError, @@ -1110,7 +1106,7 @@ void main() { test('should complete successfully', () async { const timeout = 30; - final message = Message.temp(text: 'Hello'); + final message = Message(text: 'Hello'); when( () => mockDio.post( @@ -1132,7 +1128,7 @@ void main() { }); test('should unpin message successfully', () async { - final message = Message.temp(text: 'Hello'); + final message = Message(text: 'Hello'); when( () => mockDio.post( diff --git a/packages/stream_chat/test/src/models/channel_state_test.dart b/packages/stream_chat/test/src/models/channel_state_test.dart index 79b8b0fa..98d08596 100644 --- a/packages/stream_chat/test/src/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/models/channel_state_test.dart @@ -851,8 +851,8 @@ void main() { expect(channelState.channel?.type, 'team'); expect(channelState.channel?.config, isA()); expect(channelState.channel?.config, isNotNull); - expect(channelState.channel?.config?.commands, hasLength(1)); - expect(channelState.channel?.config?.commands![0], isA()); + expect(channelState.channel?.config.commands, hasLength(1)); + expect(channelState.channel?.config.commands![0], isA()); expect(channelState.channel?.lastMessageAt, DateTime.parse('2020-01-30T13:43:41.062362Z')); expect(channelState.channel?.createdAt, @@ -868,13 +868,13 @@ void main() { 'https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png', ); expect(channelState.messages, hasLength(25)); - expect(channelState.messages![0], isA()); - expect(channelState.messages![0], isNotNull); + expect(channelState.messages[0], isA()); + expect(channelState.messages[0], isNotNull); expect( - channelState.messages![0].createdAt, + channelState.messages[0].createdAt, DateTime.parse('2020-01-29T03:23:02.843948Z'), ); - expect(channelState.messages![0].user, isA()); + expect(channelState.messages[0].user, isA()); expect(channelState.watcherCount, 5); }); @@ -889,8 +889,8 @@ void main() { "image": "https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png", "example": 1 }, - "watchers": null, - "read": null, + "watchers": [], + "read": [], "messages": [ { "id": "dry-meadow-0-2b73cc8b-cd86-4a01-8d40-bd82ad07a030", diff --git a/packages/stream_chat/test/src/models/channel_test.dart b/packages/stream_chat/test/src/models/channel_test.dart index 6d0475e0..d2d78fd5 100644 --- a/packages/stream_chat/test/src/models/channel_test.dart +++ b/packages/stream_chat/test/src/models/channel_test.dart @@ -25,7 +25,7 @@ void main() { }); test('should serialize to json correctly', () { - final channel = ChannelModel.temp( + final channel = ChannelModel( type: 'type', id: 'id', cid: 'a:a', @@ -34,12 +34,12 @@ void main() { expect( channel.toJson(), - {'id': 'id', 'type': 'type', 'name': 'cool'}, + {'id': 'id', 'type': 'type', 'frozen': false, 'name': 'cool'}, ); }); test('should serialize to json correctly when frozen is provided', () { - final channel = ChannelModel.temp( + final channel = ChannelModel( type: 'type', id: 'id', cid: 'a:a', diff --git a/packages/stream_chat/test/src/models/event_test.dart b/packages/stream_chat/test/src/models/event_test.dart index 4e657bdb..a7c669df 100644 --- a/packages/stream_chat/test/src/models/event_test.dart +++ b/packages/stream_chat/test/src/models/event_test.dart @@ -51,12 +51,12 @@ void main() { test('should serialize to json correctly', () { final event = Event( - user: User.temp(id: 'id'), + user: User(id: 'id'), type: 'type', cid: 'cid', connectionId: 'connectionId', createdAt: DateTime.parse('2020-01-29T03:22:47.63613Z'), - me: OwnUser.temp(id: 'id2'), + me: OwnUser(id: 'id2'), totalUnreadCount: 1, unreadChannels: 1, online: true, diff --git a/packages/stream_chat/test/src/models/message_test.dart b/packages/stream_chat/test/src/models/message_test.dart index 72b9ba09..0898e902 100644 --- a/packages/stream_chat/test/src/models/message_test.dart +++ b/packages/stream_chat/test/src/models/message_test.dart @@ -97,7 +97,7 @@ void main() { }); test('should serialize to json correctly', () { - final message = Message.temp( + final message = Message( id: '4637f7e4-a06b-42db-ba5a-8d8270dd926f', text: 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA', diff --git a/packages/stream_chat/test/src/models/reaction_test.dart b/packages/stream_chat/test/src/models/reaction_test.dart index 2a884962..49d226ea 100644 --- a/packages/stream_chat/test/src/models/reaction_test.dart +++ b/packages/stream_chat/test/src/models/reaction_test.dart @@ -34,7 +34,7 @@ void main() { expect(reaction.type, 'wow'); expect( reaction.user.toJson(), - User.temp(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { + User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { 'image': 'https://randomuser.me/api/portraits/women/45.jpg', 'name': 'Daisy Morgan' }).toJson(), @@ -49,7 +49,7 @@ void main() { messageId: '76cd8c82-b557-4e48-9d12-87995d3a0e04', createdAt: DateTime.parse('2020-01-28T22:17:31.108742Z'), type: 'wow', - user: User.temp(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { + user: User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { 'image': 'https://randomuser.me/api/portraits/women/45.jpg', 'name': 'Daisy Morgan' }), diff --git a/packages/stream_chat/test/src/models/read_test.dart b/packages/stream_chat/test/src/models/read_test.dart index 24b56cd8..66efe809 100644 --- a/packages/stream_chat/test/src/models/read_test.dart +++ b/packages/stream_chat/test/src/models/read_test.dart @@ -19,14 +19,14 @@ void main() { test('should parse json correctly', () { final read = Read.fromJson(json.decode(jsonExample)); expect(read.lastRead, DateTime.parse('2020-01-28T22:17:30.966485504Z')); - expect(read.user?.id, 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'); + expect(read.user.id, 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'); expect(read.unreadMessages, 10); }); test('should serialize to json correctly', () { final read = Read( lastRead: DateTime.parse('2020-01-28T22:17:30.966485504Z'), - user: User.temp(id: 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'), + user: User(id: 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'), unreadMessages: 10, ); diff --git a/packages/stream_chat/test/src/models/user_test.dart b/packages/stream_chat/test/src/models/user_test.dart index b28888c4..8f56f7e1 100644 --- a/packages/stream_chat/test/src/models/user_test.dart +++ b/packages/stream_chat/test/src/models/user_test.dart @@ -18,7 +18,7 @@ void main() { }); test('should serialize to json correctly', () { - final user = User.temp( + final user = User( id: 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e', role: 'abc', ); From 1bff129debbbcb69c50fb40f27dc6a01e890a984 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 14 Apr 2021 14:01:34 +0200 Subject: [PATCH 093/599] hotfix tests --- packages/stream_chat/lib/src/api/websocket.dart | 2 +- .../test/src/api/websocket_test.dart | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat/lib/src/api/websocket.dart b/packages/stream_chat/lib/src/api/websocket.dart index 1e808795..bf27a990 100644 --- a/packages/stream_chat/lib/src/api/websocket.dart +++ b/packages/stream_chat/lib/src/api/websocket.dart @@ -316,6 +316,6 @@ class WebSocket { _manuallyDisconnected = true; _connectionStatus = ConnectionStatus.disconnected; await _connectionStatusController.close(); - return _channel.sink.close(); + await _channel.sink.close(); } } diff --git a/packages/stream_chat/test/src/api/websocket_test.dart b/packages/stream_chat/test/src/api/websocket_test.dart index 156b203e..b9c897a6 100644 --- a/packages/stream_chat/test/src/api/websocket_test.dart +++ b/packages/stream_chat/test/src/api/websocket_test.dart @@ -124,8 +124,10 @@ void main() { const computedUrl = 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; + final mockWSSink = MockWSSink(); + when(() => mockWSChannel.sink).thenAnswer((_) => mockWSSink); + when(() => mockWSSink.close(any(), any())).thenAnswer((_) async => null); when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); final connect = ws.connect().then((_) { @@ -165,8 +167,10 @@ void main() { const computedUrl = 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; + final mockWSSink = MockWSSink(); + when(() => mockWSChannel.sink).thenAnswer((_) => mockWSSink); + when(() => mockWSSink.close(any(), any())).thenAnswer((_) async => null); when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); ws.connect(); @@ -199,7 +203,8 @@ void main() { when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - when(() => mockWSChannel.sink).thenReturn(mockWSSink); + when(() => mockWSChannel.sink).thenAnswer((_) => mockWSSink); + when(() => mockWSSink.close(any(), any())).thenAnswer((_) async => null); final timer = Timer.periodic( const Duration(milliseconds: 1000), @@ -245,7 +250,8 @@ void main() { when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - when(() => mockWSChannel.sink).thenReturn(mockWSSink); + when(() => mockWSChannel.sink).thenAnswer((_) => mockWSSink); + when(() => mockWSSink.close(any(), any())).thenAnswer((_) async => null); final connect = ws.connect().then((_) { streamController.sink.add('{}'); @@ -288,7 +294,8 @@ void main() { when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - when(() => mockWSChannel.sink).thenReturn(mockWSSink); + when(() => mockWSChannel.sink).thenAnswer((_) => mockWSSink); + when(() => mockWSSink.close(any(), any())).thenAnswer((_) async => null); final connect = ws.connect().then((_) { streamController.sink.add('{}'); From 45c1e31509da2200ce8499247b155f82cdc9deca Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 14 Apr 2021 14:29:47 +0200 Subject: [PATCH 094/599] fix warning --- packages/stream_chat/lib/src/client.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 816d780c..e951ed78 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1399,12 +1399,12 @@ class ClientState { .map((e) => e.me) .listen((user) { _userController.add(user); - if (user!.totalUnreadCount != null) { - _totalUnreadCountController.add(user.totalUnreadCount); + if (user?.totalUnreadCount != null) { + _totalUnreadCountController.add(user?.totalUnreadCount); } - if (user.unreadChannels != null) { - _unreadChannelsController.add(user.unreadChannels); + if (user?.unreadChannels != null) { + _unreadChannelsController.add(user?.unreadChannels); } }), _client From 2d11e32700272627261b5a8a17d9f73f46cc3382 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 14 Apr 2021 15:34:52 +0200 Subject: [PATCH 095/599] fix json serialization nnbd --- .../stream_chat/lib/src/db/chat_persistence_client.dart | 2 +- packages/stream_chat/lib/src/models/attachment.dart | 4 ++-- packages/stream_chat/lib/src/models/channel_model.dart | 4 ++-- packages/stream_chat/lib/src/models/event.dart | 8 ++++---- packages/stream_chat/lib/src/models/message.dart | 8 ++++---- packages/stream_chat/lib/src/models/own_user.dart | 4 ++-- packages/stream_chat/lib/src/models/reaction.dart | 7 +++++-- packages/stream_chat/lib/src/models/serialization.dart | 6 ++---- packages/stream_chat/lib/src/models/user.dart | 4 ++-- 9 files changed, 24 insertions(+), 23 deletions(-) diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 2646665a..3f999ed8 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -237,7 +237,7 @@ abstract class ChatPersistenceClient { final updateMembersFuture = channelStates.map((it) { final cid = it.channel!.cid; - final members = it.members.where((it) => it != null); + final members = it.members; return updateMembers(cid, members.toList(growable: false)); }).toList(growable: false); diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/models/attachment.dart index 7a302447..877c292d 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/models/attachment.dart @@ -49,12 +49,12 @@ class Attachment extends Equatable { /// Create a new instance from a json factory Attachment.fromJson(Map json) => _$AttachmentFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); + Serialization.moveToExtraDataFromRoot(json, topLevelFields)); /// Create a new instance from a db data factory Attachment.fromData(Map json) => _$AttachmentFromJson(Serialization.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/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index 5a45f48d..edd373ae 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -28,9 +28,9 @@ class ChannelModel { updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json - factory ChannelModel.fromJson(Map? json) => + factory ChannelModel.fromJson(Map json) => _$ChannelModelFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); + Serialization.moveToExtraDataFromRoot(json, topLevelFields)); /// The id of this channel final String? id; diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index 295d6d8d..621464b6 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -31,11 +31,11 @@ class Event { }) : isLocal = true; /// Create a new instance from a json - factory Event.fromJson(Map? json) => + factory Event.fromJson(Map json) => _$EventFromJson(Serialization.moveToExtraDataFromRoot( json, topLevelFields, - )!) + )) ..isLocal = false; /// The type of the event @@ -197,11 +197,11 @@ class EventChannel extends ChannelModel { ); /// Create a new instance from a json - factory EventChannel.fromJson(Map? json) => + factory EventChannel.fromJson(Map json) => _$EventChannelFromJson(Serialization.moveToExtraDataFromRoot( json, topLevelFields, - )!); + )); /// A paginated list of channel members final List? members; diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index f7fcfbf1..b25662c0 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -80,8 +80,8 @@ class Message extends Equatable { updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json - factory Message.fromJson(Map? json) => _$MessageFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); + factory Message.fromJson(Map json) => _$MessageFromJson( + Serialization.moveToExtraDataFromRoot(json, topLevelFields)); /// The message ID. This is either created by Stream or set client side when /// the message is added. @@ -389,9 +389,9 @@ class TranslatedMessage extends Message { TranslatedMessage(this.i18n) : super(); /// Create a new instance from a json - factory TranslatedMessage.fromJson(Map? json) => + factory TranslatedMessage.fromJson(Map json) => _$TranslatedMessageFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)!, + Serialization.moveToExtraDataFromRoot(json, topLevelFields), ); /// A Map of diff --git a/packages/stream_chat/lib/src/models/own_user.dart b/packages/stream_chat/lib/src/models/own_user.dart index 2ba29aa0..d22f706e 100644 --- a/packages/stream_chat/lib/src/models/own_user.dart +++ b/packages/stream_chat/lib/src/models/own_user.dart @@ -37,8 +37,8 @@ class OwnUser extends User { ); /// Create a new instance from a json - factory OwnUser.fromJson(Map? json) => _$OwnUserFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); + factory OwnUser.fromJson(Map json) => _$OwnUserFromJson( + Serialization.moveToExtraDataFromRoot(json, topLevelFields)); /// List of user devices @JsonKey( diff --git a/packages/stream_chat/lib/src/models/reaction.dart b/packages/stream_chat/lib/src/models/reaction.dart index cea2eaef..5bbd8944 100644 --- a/packages/stream_chat/lib/src/models/reaction.dart +++ b/packages/stream_chat/lib/src/models/reaction.dart @@ -19,8 +19,11 @@ class Reaction { }) : userId = userId ?? user.id; /// Create a new instance from a json - factory Reaction.fromJson(Map? json) => _$ReactionFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); + factory Reaction.fromJson(Map json) => + _$ReactionFromJson(Serialization.moveToExtraDataFromRoot( + json, + topLevelFields, + )); /// The messageId to which the reaction belongs final String? messageId; diff --git a/packages/stream_chat/lib/src/models/serialization.dart b/packages/stream_chat/lib/src/models/serialization.dart index d7cb0ad8..6cd4cfbd 100644 --- a/packages/stream_chat/lib/src/models/serialization.dart +++ b/packages/stream_chat/lib/src/models/serialization.dart @@ -14,12 +14,10 @@ class Serialization { users?.map((u) => u.id).toList(); /// Takes unknown json keys and puts them in the `extra_data` key - static Map? moveToExtraDataFromRoot( - Map? json, + static Map moveToExtraDataFromRoot( + Map json, List topLevelFields, ) { - if (json == null) return null; - final jsonClone = Map.from(json); final extraDataMap = Map.from(json) diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/models/user.dart index ab690e8f..224e0f20 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/models/user.dart @@ -21,8 +21,8 @@ class User { updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json - factory User.fromJson(Map? json) => _$UserFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)!); + factory User.fromJson(Map json) => _$UserFromJson( + Serialization.moveToExtraDataFromRoot(json, topLevelFields)); /// Use this named constructor to create a new user instance User.init( From 834e29c3cc14f928e10203e2776ecb55f24c044c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 14 Apr 2021 19:31:59 +0530 Subject: [PATCH 096/599] fix: generated new models, fixed reactions --- .../stream_chat/lib/src/api/responses.g.dart | 60 +++++++++---------- .../lib/src/models/channel_model.g.dart | 2 +- .../lib/src/models/channel_state.g.dart | 8 +-- .../stream_chat/lib/src/models/event.g.dart | 12 ++-- .../stream_chat/lib/src/models/member.g.dart | 2 +- .../stream_chat/lib/src/models/message.g.dart | 14 ++--- .../stream_chat/lib/src/models/mute.g.dart | 4 +- .../stream_chat/lib/src/models/reaction.dart | 10 ++-- .../lib/src/models/reaction.g.dart | 8 ++- .../stream_chat/lib/src/models/read.g.dart | 2 +- .../test/src/models/serialization_test.dart | 4 +- 11 files changed, 65 insertions(+), 61 deletions(-) diff --git a/packages/stream_chat/lib/src/api/responses.g.dart b/packages/stream_chat/lib/src/api/responses.g.dart index 8be1e62b..e1363d0c 100644 --- a/packages/stream_chat/lib/src/api/responses.g.dart +++ b/packages/stream_chat/lib/src/api/responses.g.dart @@ -10,7 +10,7 @@ SyncResponse _$SyncResponseFromJson(Map json) { return SyncResponse() ..duration = json['duration'] as String? ..events = (json['events'] as List?) - ?.map((e) => Event.fromJson(e as Map?)) + ?.map((e) => Event.fromJson(e as Map)) .toList(); } @@ -29,7 +29,7 @@ TranslateMessageResponse _$TranslateMessageResponseFromJson( ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : TranslatedMessage.fromJson(json['message'] as Map?); + : TranslatedMessage.fromJson(json['message'] as Map); } QueryMembersResponse _$QueryMembersResponseFromJson(Map json) { @@ -44,7 +44,7 @@ QueryUsersResponse _$QueryUsersResponseFromJson(Map json) { return QueryUsersResponse() ..duration = json['duration'] as String? ..users = (json['users'] as List?) - ?.map((e) => User.fromJson(e as Map?)) + ?.map((e) => User.fromJson(e as Map)) .toList(); } @@ -53,7 +53,7 @@ QueryReactionsResponse _$QueryReactionsResponseFromJson( return QueryReactionsResponse() ..duration = json['duration'] as String? ..reactions = (json['reactions'] as List?) - ?.map((e) => Reaction.fromJson(e as Map?)) + ?.map((e) => Reaction.fromJson(e as Map)) .toList(); } @@ -61,7 +61,7 @@ QueryRepliesResponse _$QueryRepliesResponseFromJson(Map json) { return QueryRepliesResponse() ..duration = json['duration'] as String? ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map?)) + ?.map((e) => Message.fromJson(e as Map)) .toList(); } @@ -90,10 +90,10 @@ SendReactionResponse _$SendReactionResponseFromJson(Map json) { ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson(json['message'] as Map?) + : Message.fromJson(json['message'] as Map) ..reaction = json['reaction'] == null ? null - : Reaction.fromJson(json['reaction'] as Map?); + : Reaction.fromJson(json['reaction'] as Map); } ConnectGuestUserResponse _$ConnectGuestUserResponseFromJson( @@ -103,14 +103,14 @@ ConnectGuestUserResponse _$ConnectGuestUserResponseFromJson( ..accessToken = json['access_token'] as String? ..user = json['user'] == null ? null - : User.fromJson(json['user'] as Map?); + : User.fromJson(json['user'] as Map); } UpdateUsersResponse _$UpdateUsersResponseFromJson(Map json) { return UpdateUsersResponse() ..duration = json['duration'] as String? ..users = (json['users'] as Map?)?.map( - (k, e) => MapEntry(k, User.fromJson(e as Map?)), + (k, e) => MapEntry(k, User.fromJson(e as Map)), ); } @@ -120,7 +120,7 @@ UpdateMessageResponse _$UpdateMessageResponseFromJson( ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson(json['message'] as Map?); + : Message.fromJson(json['message'] as Map); } SendMessageResponse _$SendMessageResponseFromJson(Map json) { @@ -128,7 +128,7 @@ SendMessageResponse _$SendMessageResponseFromJson(Map json) { ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson(json['message'] as Map?); + : Message.fromJson(json['message'] as Map); } GetMessageResponse _$GetMessageResponseFromJson(Map json) { @@ -136,10 +136,10 @@ GetMessageResponse _$GetMessageResponseFromJson(Map json) { ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson(json['message'] as Map?) + : Message.fromJson(json['message'] as Map) ..channel = json['channel'] == null ? null - : ChannelModel.fromJson(json['channel'] as Map?); + : ChannelModel.fromJson(json['channel'] as Map); } SearchMessagesResponse _$SearchMessagesResponseFromJson( @@ -156,7 +156,7 @@ GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson( return GetMessagesByIdResponse() ..duration = json['duration'] as String? ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map?)) + ?.map((e) => Message.fromJson(e as Map)) .toList(); } @@ -166,13 +166,13 @@ UpdateChannelResponse _$UpdateChannelResponseFromJson( ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson(json['channel'] as Map?) + : ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) ?.map((e) => Member.fromJson(e as Map)) .toList() ..message = json['message'] == null ? null - : Message.fromJson(json['message'] as Map?); + : Message.fromJson(json['message'] as Map); } PartialUpdateChannelResponse _$PartialUpdateChannelResponseFromJson( @@ -181,7 +181,7 @@ PartialUpdateChannelResponse _$PartialUpdateChannelResponseFromJson( ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson(json['channel'] as Map?) + : ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) ?.map((e) => Member.fromJson(e as Map)) .toList(); @@ -193,13 +193,13 @@ InviteMembersResponse _$InviteMembersResponseFromJson( ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson(json['channel'] as Map?) + : ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) ?.map((e) => Member.fromJson(e as Map)) .toList() ..message = json['message'] == null ? null - : Message.fromJson(json['message'] as Map?); + : Message.fromJson(json['message'] as Map); } RemoveMembersResponse _$RemoveMembersResponseFromJson( @@ -208,13 +208,13 @@ RemoveMembersResponse _$RemoveMembersResponseFromJson( ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson(json['channel'] as Map?) + : ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) ?.map((e) => Member.fromJson(e as Map)) .toList() ..message = json['message'] == null ? null - : Message.fromJson(json['message'] as Map?); + : Message.fromJson(json['message'] as Map); } SendActionResponse _$SendActionResponseFromJson(Map json) { @@ -222,7 +222,7 @@ SendActionResponse _$SendActionResponseFromJson(Map json) { ..duration = json['duration'] as String? ..message = json['message'] == null ? null - : Message.fromJson(json['message'] as Map?); + : Message.fromJson(json['message'] as Map); } AddMembersResponse _$AddMembersResponseFromJson(Map json) { @@ -230,13 +230,13 @@ AddMembersResponse _$AddMembersResponseFromJson(Map json) { ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson(json['channel'] as Map?) + : ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) ?.map((e) => Member.fromJson(e as Map)) .toList() ..message = json['message'] == null ? null - : Message.fromJson(json['message'] as Map?); + : Message.fromJson(json['message'] as Map); } AcceptInviteResponse _$AcceptInviteResponseFromJson(Map json) { @@ -244,13 +244,13 @@ AcceptInviteResponse _$AcceptInviteResponseFromJson(Map json) { ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson(json['channel'] as Map?) + : ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) ?.map((e) => Member.fromJson(e as Map)) .toList() ..message = json['message'] == null ? null - : Message.fromJson(json['message'] as Map?); + : Message.fromJson(json['message'] as Map); } RejectInviteResponse _$RejectInviteResponseFromJson(Map json) { @@ -258,13 +258,13 @@ RejectInviteResponse _$RejectInviteResponseFromJson(Map json) { ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson(json['channel'] as Map?) + : ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) ?.map((e) => Member.fromJson(e as Map)) .toList() ..message = json['message'] == null ? null - : Message.fromJson(json['message'] as Map?); + : Message.fromJson(json['message'] as Map); } EmptyResponse _$EmptyResponseFromJson(Map json) { @@ -276,9 +276,9 @@ ChannelStateResponse _$ChannelStateResponseFromJson(Map json) { ..duration = json['duration'] as String? ..channel = json['channel'] == null ? null - : ChannelModel.fromJson(json['channel'] as Map?) + : ChannelModel.fromJson(json['channel'] as Map) ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map?)) + ?.map((e) => Message.fromJson(e as Map)) .toList() ..members = (json['members'] as List?) ?.map((e) => Member.fromJson(e as Map)) diff --git a/packages/stream_chat/lib/src/models/channel_model.g.dart b/packages/stream_chat/lib/src/models/channel_model.g.dart index 19c4c085..58a9b61e 100644 --- a/packages/stream_chat/lib/src/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/models/channel_model.g.dart @@ -16,7 +16,7 @@ ChannelModel _$ChannelModelFromJson(Map json) { : ChannelConfig.fromJson(json['config'] as Map), createdBy: json['created_by'] == null ? null - : User.fromJson(json['created_by'] as Map?), + : User.fromJson(json['created_by'] as Map), frozen: json['frozen'] as bool? ?? false, lastMessageAt: json['last_message_at'] == null ? null diff --git a/packages/stream_chat/lib/src/models/channel_state.g.dart b/packages/stream_chat/lib/src/models/channel_state.g.dart index 67e98782..39c6373f 100644 --- a/packages/stream_chat/lib/src/models/channel_state.g.dart +++ b/packages/stream_chat/lib/src/models/channel_state.g.dart @@ -10,9 +10,9 @@ ChannelState _$ChannelStateFromJson(Map json) { return ChannelState( channel: json['channel'] == null ? null - : ChannelModel.fromJson(json['channel'] as Map?), + : ChannelModel.fromJson(json['channel'] as Map), messages: (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map?)) + ?.map((e) => Message.fromJson(e as Map)) .toList() ?? [], members: (json['members'] as List?) @@ -20,12 +20,12 @@ ChannelState _$ChannelStateFromJson(Map json) { .toList() ?? [], pinnedMessages: (json['pinned_messages'] as List?) - ?.map((e) => Message.fromJson(e as Map?)) + ?.map((e) => Message.fromJson(e as Map)) .toList() ?? [], watcherCount: json['watcher_count'] as int?, watchers: (json['watchers'] as List?) - ?.map((e) => User.fromJson(e as Map?)) + ?.map((e) => User.fromJson(e as Map)) .toList() ?? [], read: (json['read'] as List?) diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index 651c5837..c4df9904 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -16,22 +16,22 @@ Event _$EventFromJson(Map json) { : DateTime.parse(json['created_at'] as String), me: json['me'] == null ? null - : OwnUser.fromJson(json['me'] as Map?), + : OwnUser.fromJson(json['me'] as Map), user: json['user'] == null ? null - : User.fromJson(json['user'] as Map?), + : User.fromJson(json['user'] as Map), message: json['message'] == null ? null - : Message.fromJson(json['message'] as Map?), + : Message.fromJson(json['message'] as Map), totalUnreadCount: json['total_unread_count'] as int?, unreadChannels: json['unread_channels'] as int?, reaction: json['reaction'] == null ? null - : Reaction.fromJson(json['reaction'] as Map?), + : Reaction.fromJson(json['reaction'] as Map), online: json['online'] as bool?, channel: json['channel'] == null ? null - : EventChannel.fromJson(json['channel'] as Map?), + : EventChannel.fromJson(json['channel'] as Map), member: json['member'] == null ? null : Member.fromJson(json['member'] as Map), @@ -84,7 +84,7 @@ EventChannel _$EventChannelFromJson(Map json) { config: ChannelConfig.fromJson(json['config'] as Map), createdBy: json['created_by'] == null ? null - : User.fromJson(json['created_by'] as Map?), + : User.fromJson(json['created_by'] as Map), frozen: json['frozen'] as bool? ?? false, lastMessageAt: json['last_message_at'] == null ? null diff --git a/packages/stream_chat/lib/src/models/member.g.dart b/packages/stream_chat/lib/src/models/member.g.dart index df9b6641..091cb109 100644 --- a/packages/stream_chat/lib/src/models/member.g.dart +++ b/packages/stream_chat/lib/src/models/member.g.dart @@ -10,7 +10,7 @@ Member _$MemberFromJson(Map json) { return Member( user: json['user'] == null ? null - : User.fromJson(json['user'] as Map?), + : User.fromJson(json['user'] as Map), inviteAcceptedAt: json['invite_accepted_at'] == null ? null : DateTime.parse(json['invite_accepted_at'] as String), diff --git a/packages/stream_chat/lib/src/models/message.g.dart b/packages/stream_chat/lib/src/models/message.g.dart index fddbdd61..9dfea3c4 100644 --- a/packages/stream_chat/lib/src/models/message.g.dart +++ b/packages/stream_chat/lib/src/models/message.g.dart @@ -15,7 +15,7 @@ Message _$MessageFromJson(Map json) { ?.map((e) => Attachment.fromJson(e as Map)) .toList(), mentionedUsers: (json['mentioned_users'] as List?) - ?.map((e) => User.fromJson(e as Map?)) + ?.map((e) => User.fromJson(e as Map)) .toList(), silent: json['silent'] as bool?, shadowed: json['shadowed'] as bool?, @@ -26,19 +26,19 @@ Message _$MessageFromJson(Map json) { (k, e) => MapEntry(k, e as int), ), latestReactions: (json['latest_reactions'] as List?) - ?.map((e) => Reaction.fromJson(e as Map?)) + ?.map((e) => Reaction.fromJson(e as Map)) .toList(), ownReactions: (json['own_reactions'] as List?) - ?.map((e) => Reaction.fromJson(e as Map?)) + ?.map((e) => Reaction.fromJson(e as Map)) .toList(), parentId: json['parent_id'] as String?, quotedMessage: json['quoted_message'] == null ? null - : Message.fromJson(json['quoted_message'] as Map?), + : Message.fromJson(json['quoted_message'] as Map), quotedMessageId: json['quoted_message_id'] as String?, replyCount: json['reply_count'] as int?, threadParticipants: (json['thread_participants'] as List?) - ?.map((e) => User.fromJson(e as Map?)) + ?.map((e) => User.fromJson(e as Map)) .toList(), showInChannel: json['show_in_channel'] as bool?, command: json['command'] as String?, @@ -50,7 +50,7 @@ Message _$MessageFromJson(Map json) { : DateTime.parse(json['updated_at'] as String), user: json['user'] == null ? null - : User.fromJson(json['user'] as Map?), + : User.fromJson(json['user'] as Map), pinned: json['pinned'] as bool?, pinnedAt: json['pinned_at'] == null ? null @@ -60,7 +60,7 @@ Message _$MessageFromJson(Map json) { : DateTime.parse(json['pin_expires'] as String), pinnedBy: json['pinned_by'] == null ? null - : User.fromJson(json['pinned_by'] as Map?), + : User.fromJson(json['pinned_by'] as Map), extraData: json['extra_data'] as Map?, deletedAt: json['deleted_at'] == null ? null diff --git a/packages/stream_chat/lib/src/models/mute.g.dart b/packages/stream_chat/lib/src/models/mute.g.dart index 15e3b810..652a1bc3 100644 --- a/packages/stream_chat/lib/src/models/mute.g.dart +++ b/packages/stream_chat/lib/src/models/mute.g.dart @@ -10,10 +10,10 @@ Mute _$MuteFromJson(Map json) { return Mute( user: json['user'] == null ? null - : User.fromJson(json['user'] as Map?), + : User.fromJson(json['user'] as Map), channel: json['channel'] == null ? null - : ChannelModel.fromJson(json['channel'] as Map?), + : ChannelModel.fromJson(json['channel'] as Map), createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), diff --git a/packages/stream_chat/lib/src/models/reaction.dart b/packages/stream_chat/lib/src/models/reaction.dart index 5bbd8944..767f1057 100644 --- a/packages/stream_chat/lib/src/models/reaction.dart +++ b/packages/stream_chat/lib/src/models/reaction.dart @@ -10,13 +10,14 @@ class Reaction { /// Constructor used for json serialization Reaction({ this.messageId, - required this.createdAt, - required this.type, + DateTime? createdAt, + this.type = '', required this.user, String? userId, - required this.score, + this.score = 0, this.extraData, - }) : userId = userId ?? user.id; + }) : userId = userId ?? user.id, + createdAt = createdAt ?? DateTime.now(); /// Create a new instance from a json factory Reaction.fromJson(Map json) => @@ -40,6 +41,7 @@ class Reaction { final User user; /// The score of the reaction (ie. number of reactions sent) + @JsonKey(defaultValue: 0) final int score; /// The userId that sent the reaction diff --git a/packages/stream_chat/lib/src/models/reaction.g.dart b/packages/stream_chat/lib/src/models/reaction.g.dart index a0aad565..203be6eb 100644 --- a/packages/stream_chat/lib/src/models/reaction.g.dart +++ b/packages/stream_chat/lib/src/models/reaction.g.dart @@ -9,11 +9,13 @@ part of 'reaction.dart'; Reaction _$ReactionFromJson(Map json) { return Reaction( messageId: json['message_id'] as String?, - createdAt: DateTime.parse(json['created_at'] as String), + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), type: json['type'] as String, - user: User.fromJson(json['user'] as Map?), + user: User.fromJson(json['user'] as Map), userId: json['user_id'] as String?, - score: json['score'] as int, + score: json['score'] as int? ?? 0, extraData: json['extra_data'] as Map?, ); } diff --git a/packages/stream_chat/lib/src/models/read.g.dart b/packages/stream_chat/lib/src/models/read.g.dart index 82aeba22..93c832d8 100644 --- a/packages/stream_chat/lib/src/models/read.g.dart +++ b/packages/stream_chat/lib/src/models/read.g.dart @@ -9,7 +9,7 @@ part of 'read.dart'; Read _$ReadFromJson(Map json) { return Read( lastRead: DateTime.parse(json['last_read'] as String), - user: User.fromJson(json['user'] as Map?), + user: User.fromJson(json['user'] as Map), unreadMessages: json['unread_messages'] as int? ?? 0, ); } diff --git a/packages/stream_chat/test/src/models/serialization_test.dart b/packages/stream_chat/test/src/models/serialization_test.dart index e8a2de3f..9e64caad 100644 --- a/packages/stream_chat/test/src/models/serialization_test.dart +++ b/packages/stream_chat/test/src/models/serialization_test.dart @@ -49,12 +49,12 @@ void main() { }); test('should return null', () { - final result = Serialization.moveToExtraDataFromRoot(null, [ + final result = Serialization.moveToExtraDataFromRoot({}, [ 'prop1', 'prop2', ]); - expect(result, null); + expect(result, {'extra_data': {}}); }); }); } From be9c841c8aea91946e46ca0ba23dec08faa08bc4 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Apr 2021 13:41:05 +0530 Subject: [PATCH 097/599] fix: Review changes --- packages/stream_chat/lib/src/api/channel.dart | 26 +++--- .../stream_chat/lib/src/api/retry_queue.dart | 27 ++++--- .../lib/src/attachment_file_uploader.dart | 28 +++---- packages/stream_chat/lib/src/client.dart | 81 +++++++++---------- .../lib/src/db/chat_persistence_client.dart | 4 +- .../test/src/api/channel_test.dart | 1 - .../stream_chat/test/src/client_test.dart | 2 +- .../test/src/models/action_test.dart | 3 +- .../test/src/models/attachment_test.dart | 3 +- .../test/src/models/channel_test.dart | 1 - .../test/src/models/device_test.dart | 3 +- .../test/src/models/message_test.dart | 7 +- 12 files changed, 98 insertions(+), 88 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 01d8399e..c4bcef33 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -249,13 +249,13 @@ class Channel { Future future; if (isImage) { future = sendImage( - it.file, + it.file!, onSendProgress: onSendProgress, cancelToken: cancelToken, ).then((it) => it!.file!); } else { future = sendFile( - it.file, + it.file!, onSendProgress: onSendProgress, cancelToken: cancelToken, ).then((it) => it!.file!); @@ -340,7 +340,7 @@ class Channel { } final response = await _client.sendMessage(message, id, type); - state?.addMessage(response!.message!); + state?.addMessage(response.message!); return response; } catch (error) { if (error is DioError && error.type != DioErrorType.response) { @@ -392,7 +392,7 @@ class Channel { final response = await _client.updateMessage(message); - final m = response?.message?.copyWith( + final m = response.message?.copyWith( ownReactions: message.ownReactions, ); @@ -453,10 +453,11 @@ class Channel { /// Pins provided message Future pinMessage( Message message, - Object timeoutOrExpirationDate, + Object? timeoutOrExpirationDate, ) { assert(() { if (timeoutOrExpirationDate is! DateTime && + timeoutOrExpirationDate != null && timeoutOrExpirationDate is! num) { throw ArgumentError('Invalid timeout or Expiration date'); } @@ -485,7 +486,7 @@ class Channel { /// Send a file to this channel Future sendFile( - AttachmentFile? file, { + AttachmentFile file, { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) => @@ -499,7 +500,7 @@ class Channel { /// Send an image to this channel Future sendImage( - AttachmentFile? file, { + AttachmentFile file, { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) => @@ -765,7 +766,7 @@ class Channel { 'message_id': messageId, }); - final res = _client.decode(response.data, SendActionResponse.fromJson)!; + final res = _client.decode(response.data, SendActionResponse.fromJson); if (res.message != null) { state!.addMessage(res.message!); @@ -880,7 +881,7 @@ class Channel { final repliesResponse = _client.decode( response.data, QueryRepliesResponse.fromJson, - )!; + ); state?.updateThreadInfo(parentId, repliesResponse.messages); @@ -911,7 +912,7 @@ class Channel { final res = _client.decode( response.data, GetMessagesByIdResponse.fromJson, - )!; + ); final messages = res.messages; @@ -999,8 +1000,7 @@ class Channel { try { final response = await _client.post(path, data: payload); - final updatedState = - _client.decode(response.data, ChannelState.fromJson)!; + final updatedState = _client.decode(response.data, ChannelState.fromJson); if (_id == null) { _id = updatedState.channel!.id; @@ -1191,7 +1191,7 @@ class Channel { /// Call this method to dispose the channel client void dispose() { - state!.dispose(); + state?.dispose(); } void _checkInitialized() { diff --git a/packages/stream_chat/lib/src/api/retry_queue.dart b/packages/stream_chat/lib/src/api/retry_queue.dart index e09c367b..c13db9c6 100644 --- a/packages/stream_chat/lib/src/api/retry_queue.dart +++ b/packages/stream_chat/lib/src/api/retry_queue.dart @@ -40,16 +40,17 @@ class RetryQueue { })); } - final HeapPriorityQueue _messageQueue = HeapPriorityQueue(_byDate); + final HeapPriorityQueue _messageQueue = HeapPriorityQueue(_byDate); bool _isRetrying = false; RetryPolicy? _retryPolicy; /// Add a list of messages - void add(List messages) { + void add(List messages) { logger?.info('added ${messages.length} messages'); final messageList = _messageQueue.toList(); + _messageQueue.addAll(messages - .where((element) => !messageList.any((m) => m!.id == element!.id))); + .where((element) => !messageList.any((m) => m.id == element.id))); if (_messageQueue.isNotEmpty && !_isRetrying) { _startRetrying(); @@ -62,7 +63,7 @@ class RetryQueue { final retryPolicy = _retryPolicy!.copyWith(attempt: 0); while (_messageQueue.isNotEmpty) { - final message = _messageQueue.first!; + final message = _messageQueue.first; try { logger?.info('retry attempt ${retryPolicy.attempt}'); await _sendMessage(message); @@ -141,7 +142,7 @@ class RetryQueue { final messageList = _messageQueue.toList(); if (event.message != null) { final messageIndex = - messageList.indexWhere((m) => m!.id == event.message!.id); + messageList.indexWhere((m) => m.id == event.message!.id); if (messageIndex == -1 && [ MessageSendingStatus.failed_update, @@ -149,7 +150,11 @@ class RetryQueue { MessageSendingStatus.failed_delete, ].contains(event.message!.status)) { logger?.info('add message from events'); - add([event.message]); + final m = event.message; + + if (m != null) { + add([m]); + } } else if (messageIndex != -1 && [ MessageSendingStatus.sent, @@ -167,9 +172,13 @@ class RetryQueue { _subscriptions.forEach((s) => s.cancel()); } - static int _byDate(Message? m1, Message? m2) { - final date1 = _getMessageDate(m1!)!; - final date2 = _getMessageDate(m2!)!; + static int _byDate(Message m1, Message m2) { + final date1 = _getMessageDate(m1); + final date2 = _getMessageDate(m2); + + if (date1 == null || date2 == null) { + return 0; + } return date1.compareTo(date2); } diff --git a/packages/stream_chat/lib/src/attachment_file_uploader.dart b/packages/stream_chat/lib/src/attachment_file_uploader.dart index 42df3fb2..70976d40 100644 --- a/packages/stream_chat/lib/src/attachment_file_uploader.dart +++ b/packages/stream_chat/lib/src/attachment_file_uploader.dart @@ -11,8 +11,8 @@ abstract class AttachmentFileUploader { /// /// Optionally, access upload progress using [onSendProgress] /// and cancel the request using [cancelToken] - Future sendImage( - AttachmentFile? image, + Future sendImage( + AttachmentFile image, String? channelId, String? channelType, { ProgressCallback? onSendProgress, @@ -24,8 +24,8 @@ abstract class AttachmentFileUploader { /// /// Optionally, access upload progress using [onSendProgress] /// and cancel the request using [cancelToken] - Future sendFile( - AttachmentFile? file, + Future sendFile( + AttachmentFile file, String? channelId, String? channelType, { ProgressCallback? onSendProgress, @@ -36,7 +36,7 @@ abstract class AttachmentFileUploader { /// Returns [EmptyResponse] once deleted successfully. /// /// Optionally, cancel the request using [cancelToken] - Future deleteImage( + Future deleteImage( String url, String? channelId, String? channelType, { @@ -47,7 +47,7 @@ abstract class AttachmentFileUploader { /// Returns [EmptyResponse] once deleted successfully. /// /// Optionally, cancel the request using [cancelToken] - Future deleteFile( + Future deleteFile( String url, String? channelId, String? channelType, { @@ -63,14 +63,14 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { final StreamChatClient _client; @override - Future sendImage( - AttachmentFile? file, + Future sendImage( + AttachmentFile file, String? channelId, String? channelType, { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) async { - final filename = file!.path?.split('/').last ?? file.name; + final filename = file.path?.split('/').last ?? file.name; final mimeType = filename.mimeType; MultipartFile? multiPartFile; @@ -100,14 +100,14 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { } @override - Future sendFile( - AttachmentFile? file, + Future sendFile( + AttachmentFile file, String? channelId, String? channelType, { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) async { - final filename = file!.path?.split('/').last ?? file.name; + final filename = file.path?.split('/').last ?? file.name; final mimeType = filename.mimeType; MultipartFile? multiPartFile; @@ -137,7 +137,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { } @override - Future deleteImage( + Future deleteImage( String url, String? channelId, String? channelType, { @@ -152,7 +152,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { } @override - Future deleteFile( + Future deleteFile( String url, String? channelId, String? channelType, { diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index e951ed78..1066c670 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -482,9 +482,10 @@ class StreamChatClient { } if (!event.isLocal!) { - if (_synced && event.createdAt != null) { + final createdAt = event.createdAt; + if (_synced && createdAt != null) { await _chatPersistenceClient?.updateConnectionInfo(event); - await _chatPersistenceClient?.updateLastSyncAt(event.createdAt); + await _chatPersistenceClient?.updateLastSyncAt(createdAt); } } @@ -615,7 +616,7 @@ class StreamChatClient { final res = decode( rawRes.data, SyncResponse.fromJson, - )!; + ); res.events!.sort((a, b) => a.createdAt!.compareTo(b.createdAt!)); @@ -737,7 +738,7 @@ class StreamChatClient { final res = decode( response.data, QueryChannelsResponse.fromJson, - )!; + ); if ((res.channels ?? []).isEmpty && paginationParams.offset == 0) { logger.warning( @@ -914,12 +915,10 @@ class StreamChatClient { } /// Used to log errors and stacktrace in case of bad json deserialization - T? decode(String? j, DecoderFunction decoderFunction) { + T decode(String? j, DecoderFunction decoderFunction) { try { - if (j == null) { - return null; - } - return decoderFunction(json.decode(j)); + final data = j ?? '{}'; + return decoderFunction(json.decode(data)); } catch (error, stacktrace) { logger.severe('Error decoding response', error, stacktrace); rethrow; @@ -983,8 +982,8 @@ class StreamChatClient { .whenComplete(() => _anonymous = false); return connectUser( - response?.user, - response?.accessToken, + response.user, + response.accessToken, ); } @@ -1052,7 +1051,7 @@ class StreamChatClient { final response = decode( rawRes.data, QueryUsersResponse.fromJson, - )!; + ); state?._updateUsers(response.users!); @@ -1060,7 +1059,7 @@ class StreamChatClient { } /// A message search. - Future search( + Future search( Map filters, { String? query, List? sort, @@ -1099,8 +1098,8 @@ class StreamChatClient { } /// Send a [file] to the [channelId] of type [channelType] - Future sendFile( - AttachmentFile? file, + Future sendFile( + AttachmentFile file, String? channelId, String? channelType, { ProgressCallback? onSendProgress, @@ -1115,8 +1114,8 @@ class StreamChatClient { ); /// Send a [image] to the [channelId] of type [channelType] - Future sendImage( - AttachmentFile? image, + Future sendImage( + AttachmentFile image, String? channelId, String? channelType, { ProgressCallback? onSendProgress, @@ -1131,7 +1130,7 @@ class StreamChatClient { ); /// Delete a file from this channel - Future deleteFile( + Future deleteFile( String url, String? channelId, String? channelType, { @@ -1145,7 +1144,7 @@ class StreamChatClient { ); /// Delete an image from this channel - Future deleteImage( + Future deleteImage( String url, String? channelId, String? channelType, { @@ -1159,7 +1158,7 @@ class StreamChatClient { ); /// Add a device for Push Notifications. - Future addDevice(String id, PushProvider pushProvider) async { + Future addDevice(String id, PushProvider pushProvider) async { final response = await post('/devices', data: { 'id': id, 'push_provider': pushProvider.name, @@ -1168,14 +1167,14 @@ class StreamChatClient { } /// Gets a list of user devices. - Future getDevices() async { + Future getDevices() async { final response = await get('/devices'); return decode( response.data, ListDevicesResponse.fromJson); } /// Remove a user's device. - Future removeDevice(String id) async { + Future removeDevice(String id) async { final response = await delete('/devices', queryParameters: { 'id': id, }); @@ -1206,11 +1205,11 @@ class StreamChatClient { } /// Update or Create the given user object. - Future updateUser(User user) async => + Future updateUser(User user) async => updateUsers([user]); /// Batch update a list of users - Future updateUsers(List users) async { + Future updateUsers(List users) async { final response = await post('/users', data: { 'users': users.asMap().map((_, u) => MapEntry(u.id, u.toJson())), }); @@ -1221,7 +1220,7 @@ class StreamChatClient { } /// Bans a user from all channels - Future banUser( + Future banUser( String targetUserID, [ Map options = const {}, ]) async { @@ -1237,7 +1236,7 @@ class StreamChatClient { } /// Remove global ban for a user - Future unbanUser( + Future unbanUser( String targetUserID, [ Map options = const {}, ]) async { @@ -1253,7 +1252,7 @@ class StreamChatClient { } /// Shadow bans a user - Future shadowBan( + Future shadowBan( String targetID, [ Map options = const {}, ]) async => @@ -1263,7 +1262,7 @@ class StreamChatClient { }); /// Removes shadow ban from a user - Future removeShadowBan( + Future removeShadowBan( String targetID, [ Map options = const {}, ]) async => @@ -1273,7 +1272,7 @@ class StreamChatClient { }); /// Mutes a user - Future muteUser(String targetID) async { + Future muteUser(String targetID) async { final response = await post('/moderation/mute', data: { 'target_id': targetID, }); @@ -1281,7 +1280,7 @@ class StreamChatClient { } /// Unmutes a user - Future unmuteUser(String targetID) async { + Future unmuteUser(String targetID) async { final response = await post('/moderation/unmute', data: { 'target_id': targetID, }); @@ -1289,7 +1288,7 @@ class StreamChatClient { } /// Flag a message - Future flagMessage(String messageID) async { + Future flagMessage(String messageID) async { final response = await post('/moderation/flag', data: { 'target_message_id': messageID, }); @@ -1297,7 +1296,7 @@ class StreamChatClient { } /// Unflag a message - Future unflagMessage(String messageId) async { + Future unflagMessage(String messageId) async { final response = await post('/moderation/unflag', data: { 'target_message_id': messageId, }); @@ -1305,7 +1304,7 @@ class StreamChatClient { } /// Flag a user - Future flagUser(String userId) async { + Future flagUser(String userId) async { final response = await post('/moderation/flag', data: { 'target_user_id': userId, }); @@ -1313,7 +1312,7 @@ class StreamChatClient { } /// Unflag a message - Future unflagUser(String userId) async { + Future unflagUser(String userId) async { final response = await post('/moderation/unflag', data: { 'target_user_id': userId, }); @@ -1321,13 +1320,13 @@ class StreamChatClient { } /// Mark all channels for this user as read - Future markAllRead() async { + Future markAllRead() async { final response = await post('/channels/read'); return decode(response.data, EmptyResponse.fromJson); } /// Sends the message to the given channel - Future sendMessage( + Future sendMessage( Message message, String? channelId, String? channelType) async { final response = await post( '/channels/$channelType/$channelId/message', @@ -1337,7 +1336,7 @@ class StreamChatClient { } /// Update the given message - Future updateMessage(Message message) async { + Future updateMessage(Message message) async { final response = await post( '/messages/${message.id}', data: {'message': message.toJson()}, @@ -1346,19 +1345,19 @@ class StreamChatClient { } /// Deletes the given message - Future deleteMessage(Message message) async { + Future deleteMessage(Message message) async { final response = await delete('/messages/${message.id}'); return decode(response.data, EmptyResponse.fromJson); } /// Get a message by id - Future getMessage(String messageId) async { + Future getMessage(String messageId) async { final response = await get('/messages/$messageId'); return decode(response.data, GetMessageResponse.fromJson); } /// Pins provided message - Future pinMessage( + Future pinMessage( Message message, Object timeoutOrExpirationDate, ) { @@ -1384,7 +1383,7 @@ class StreamChatClient { } /// Unpins provided message - Future unpinMessage(Message message) => + Future unpinMessage(Message message) => updateMessage(message.copyWith(pinned: false)); } diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 3f999ed8..2dc2521c 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -11,7 +11,7 @@ import 'package:stream_chat/src/models/user.dart'; /// A simple client used for persisting chat data locally. abstract class ChatPersistenceClient { /// Creates a new connection to the client - Future connect(String? userId); + Future connect(String userId); /// Closes the client connection /// If [flush] is true, the data will also be deleted @@ -33,7 +33,7 @@ abstract class ChatPersistenceClient { Future updateConnectionInfo(Event event); /// Update stored lastSyncAt - Future updateLastSyncAt(DateTime? lastSyncAt); + Future updateLastSyncAt(DateTime lastSyncAt); /// Get the channel cids saved in the offline storage Future> getChannelCids(); diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 5eee577e..cdeae39d 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -638,7 +638,6 @@ void main() { Reaction( type: 'test', createdAt: DateTime.now(), - score: 0, user: User( id: client.state?.user?.id ?? '', ), diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 8f95b3a4..4e880bed 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -777,7 +777,7 @@ void main() { ), ); - await client.deleteMessage(Message(id: messageId, text: '')); + await client.deleteMessage(Message(id: messageId)); verify(() => mockDio.delete('/messages/$messageId')).called(1); }); diff --git a/packages/stream_chat/test/src/models/action_test.dart b/packages/stream_chat/test/src/models/action_test.dart index 5d142953..22e66078 100644 --- a/packages/stream_chat/test/src/models/action_test.dart +++ b/packages/stream_chat/test/src/models/action_test.dart @@ -5,7 +5,8 @@ import 'package:stream_chat/src/models/action.dart'; void main() { group('src/models/action', () { - const jsonExample = '''{ + const jsonExample = ''' + { "name": "name", "style": "style", "text": "text", diff --git a/packages/stream_chat/test/src/models/attachment_test.dart b/packages/stream_chat/test/src/models/attachment_test.dart index 899aec73..1ca45132 100644 --- a/packages/stream_chat/test/src/models/attachment_test.dart +++ b/packages/stream_chat/test/src/models/attachment_test.dart @@ -6,7 +6,8 @@ import 'package:test/test.dart'; void main() { group('src/models/attachment', () { - const jsonExample = '''{ + const jsonExample = ''' + { "type": "giphy", "title": "awesome", "title_link": "https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti", diff --git a/packages/stream_chat/test/src/models/channel_test.dart b/packages/stream_chat/test/src/models/channel_test.dart index d2d78fd5..a48c5a23 100644 --- a/packages/stream_chat/test/src/models/channel_test.dart +++ b/packages/stream_chat/test/src/models/channel_test.dart @@ -44,7 +44,6 @@ void main() { id: 'id', cid: 'a:a', extraData: {'name': 'cool'}, - frozen: false, ); expect( diff --git a/packages/stream_chat/test/src/models/device_test.dart b/packages/stream_chat/test/src/models/device_test.dart index 27a94982..5cbf015d 100644 --- a/packages/stream_chat/test/src/models/device_test.dart +++ b/packages/stream_chat/test/src/models/device_test.dart @@ -5,7 +5,8 @@ import 'package:stream_chat/src/models/device.dart'; void main() { group('src/models/device', () { - const jsonExample = '''{ + const jsonExample = ''' + { "id": "device-id", "push_provider": "push-provider" }'''; diff --git a/packages/stream_chat/test/src/models/message_test.dart b/packages/stream_chat/test/src/models/message_test.dart index 0898e902..a34911cf 100644 --- a/packages/stream_chat/test/src/models/message_test.dart +++ b/packages/stream_chat/test/src/models/message_test.dart @@ -8,7 +8,8 @@ import 'package:stream_chat/src/models/user.dart'; void main() { group('src/models/message', () { - const jsonExample = r'''{ + const jsonExample = r''' + { "id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", "text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", "type": "regular", @@ -103,7 +104,7 @@ void main() { 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA', silent: false, attachments: [ - Attachment.fromJson({ + Attachment.fromJson(const { 'type': 'video', 'author_name': 'GIPHY', 'title': 'The Lion King Disney GIF - Find \u0026 Share on GIPHY', @@ -123,7 +124,7 @@ void main() { ], showInChannel: true, parentId: 'parentId', - extraData: {'hey': 'test'}, + extraData: const {'hey': 'test'}, ); expect( From 72a0ca3110c2e3a6d6b7597c5c681f6b3858668b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Apr 2021 14:11:46 +0530 Subject: [PATCH 098/599] fix: Review changes --- packages/stream_chat/lib/src/api/channel.dart | 15 +++--- packages/stream_chat/lib/src/client.dart | 8 +++- .../lib/src/db/chat_persistence_client.dart | 47 ++++++++++--------- 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index c4bcef33..4ed2c939 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -985,7 +985,7 @@ class Channel { if (preferOffline && cid != null) { final updatedState = (await _client.chatPersistenceClient?.getChannelStateByCid( - cid, + cid!, messagePagination: messagesPagination, ))!; if (updatedState.messages.isNotEmpty) { @@ -1014,7 +1014,7 @@ class Channel { rethrow; } return _client.chatPersistenceClient!.getChannelStateByCid( - cid, + cid!, messagePagination: messagesPagination, ); } @@ -1122,7 +1122,10 @@ class Channel { if (clearHistory == true) { state!.truncate(); - await _client.chatPersistenceClient?.deleteMessageByCid(_cid); + final cid = _cid; + if (cid != null) { + await _client.chatPersistenceClient?.deleteMessageByCid(cid); + } } return _client.decode(response.data, EmptyResponse.fromJson); @@ -1252,12 +1255,12 @@ class ChannelClientState { _startCleaningPinnedMessages(); _channel._client.chatPersistenceClient - ?.getChannelThreads(_channel.cid) + ?.getChannelThreads(_channel.cid!) .then((threads) { _threads = threads; }).then((_) { _channel._client.chatPersistenceClient - ?.getChannelStateByCid(_channel.cid) + ?.getChannelStateByCid(_channel.cid!) .then((state) { // Replacing the persistence state members with the latest // `channelState.members` as they may have changes over the time. @@ -1734,7 +1737,7 @@ class ChannelClientState { set _threads(Map?> v) { _channel._client.chatPersistenceClient?.updateMessages( - _channel.cid, + _channel.cid!, v.values.expand((v) => v!).toList(), ); _threadsController.add(v); diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 1066c670..69b6321d 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -764,7 +764,7 @@ class StreamChatClient { final updateData = _mapChannelStateToChannel(channels); await _chatPersistenceClient?.updateChannelQueries( - filter, + filter ?? {}, channels.map((c) => c.channel!.cid).toList(), clearQueryCache: paginationParams.offset == 0, ); @@ -1434,7 +1434,11 @@ class ClientState { void _listenChannelHidden() { _subscriptions.add(_client.on(EventType.channelHidden).listen((event) { - _client.chatPersistenceClient?.deleteChannels([event.cid]); + final cid = event.cid; + + if (cid != null) { + _client.chatPersistenceClient?.deleteChannels([cid]); + } if (channels != null) { channels = channels?..removeWhere((cid, ch) => cid == event.cid); } diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 2dc2521c..07424299 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -39,32 +39,32 @@ abstract class ChatPersistenceClient { Future> getChannelCids(); /// Get stored [ChannelModel]s by providing channel [cid] - Future getChannelByCid(String? cid); + Future getChannelByCid(String cid); /// Get stored channel [Member]s by providing channel [cid] - Future> getMembersByCid(String? cid); + Future> getMembersByCid(String cid); /// Get stored channel [Read]s by providing channel [cid] - Future> getReadsByCid(String? cid); + Future> getReadsByCid(String cid); /// Get stored [Message]s by providing channel [cid] /// /// Optionally, you can [messagePagination] /// for filtering out messages Future> getMessagesByCid( - String? cid, { + String cid, { PaginationParams? messagePagination, }); /// Get stored pinned [Message]s by providing channel [cid] Future> getPinnedMessagesByCid( - String? cid, { + String cid, { PaginationParams? messagePagination, }); /// Get [ChannelState] data by providing channel [cid] Future getChannelStateByCid( - String? cid, { + String cid, { PaginationParams? messagePagination, PaginationParams? pinnedMessagePagination, }) async { @@ -99,8 +99,8 @@ abstract class ChatPersistenceClient { /// If [clearQueryCache] is true before the insert /// the list of matching rows will be deleted Future updateChannelQueries( - Map? filter, - List cids, { + Map filter, + List cids, { bool clearQueryCache = false, }); @@ -119,46 +119,46 @@ abstract class ChatPersistenceClient { Future deletePinnedMessageByIds(List messageIds); /// Remove a message by channel [cid] - Future deleteMessageByCid(String? cid) => deleteMessageByCids([cid]); + Future deleteMessageByCid(String cid) => deleteMessageByCids([cid]); /// Remove a pinned message by channel [cid] Future deletePinnedMessageByCid(String cid) async => deletePinnedMessageByCids([cid]); /// Remove a message by message [cids] - Future deleteMessageByCids(List cids); + Future deleteMessageByCids(List cids); /// Remove a pinned message by message [cids] Future deletePinnedMessageByCids(List cids); /// Remove a channel by [cid] - Future deleteChannels(List cids); + Future deleteChannels(List cids); /// Updates the message data of a particular channel [cid] with /// the new [messages] data - Future updateMessages(String? cid, List messages); + Future updateMessages(String cid, List messages); /// Updates the pinned message data of a particular channel [cid] with /// the new [messages] data - Future updatePinnedMessages(String? cid, List messages); + Future updatePinnedMessages(String cid, List messages); /// Returns all the threads by parent message of a particular channel by /// providing channel [cid] - Future>> getChannelThreads(String? cid); + Future>> getChannelThreads(String cid); /// Updates all the channels using the new [channels] data. - Future updateChannels(List channels); + Future updateChannels(List channels); /// Updates all the members of a particular channle [cid] /// with the new [members] data - Future updateMembers(String? cid, List members); + Future updateMembers(String cid, List members); /// Updates the read data of a particular channel [cid] with /// the new [reads] data - Future updateReads(String? cid, List reads); + Future updateReads(String cid, List reads); /// Updates the users data with the new [users] data - Future updateUsers(List users); + Future updateUsers(List users); /// Updates the reactions data with the new [reactions] data Future updateReactions(List reactions); @@ -167,7 +167,7 @@ abstract class ChatPersistenceClient { Future deleteReactionsByMessageId(List messageIds); /// Deletes all the members by channel [cids] - Future deleteMembersByCids(List cids); + Future deleteMembersByCids(List cids); /// Update the channel state data using [channelState] Future updateChannelState(ChannelState channelState) => @@ -189,8 +189,9 @@ abstract class ChatPersistenceClient { deleteMembers, ]); - final channels = - channelStates.map((it) => it.channel).where((it) => it != null); + final channels = channelStates + .map((it) => it.channel) + .where((it) => it != null) as Iterable; final reactions = channelStates.expand((it) => it.messages).expand((it) => [ if (it.ownReactions != null) @@ -199,7 +200,7 @@ abstract class ChatPersistenceClient { ...it.latestReactions!.where((r) => r.userId != null) ]); - final users = channelStates + var users = channelStates .map((cs) => [ cs.channel?.createdBy, ...cs.messages @@ -215,7 +216,7 @@ abstract class ChatPersistenceClient { ...cs.members.map((m) => m.user), ]) .expand((it) => it) - .where((it) => it != null); + .where((it) => it != null) as Iterable; final updateMessagesFuture = channelStates.map((it) { final cid = it.channel!.cid; From 1aea44e568614dc5dd447ff9b077c1af1b9972f5 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Apr 2021 15:27:43 +0530 Subject: [PATCH 099/599] fix: analysis --- packages/stream_chat/lib/src/db/chat_persistence_client.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 07424299..50222499 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -200,7 +200,7 @@ abstract class ChatPersistenceClient { ...it.latestReactions!.where((r) => r.userId != null) ]); - var users = channelStates + final users = channelStates .map((cs) => [ cs.channel?.createdBy, ...cs.messages From e1ab168d331c2549d870a2c5bb2d0f58cd4cfb69 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Apr 2021 17:52:08 +0530 Subject: [PATCH 100/599] fix: client state migrated to null --- packages/stream_chat/lib/src/api/channel.dart | 42 ++++++++--------- packages/stream_chat/lib/src/client.dart | 46 +++++++++---------- .../test/src/api/channel_test.dart | 6 +-- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 4ed2c939..ce1d25ae 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -58,12 +58,12 @@ class Channel { /// Returns true if the channel is muted bool get isMuted => - _client.state!.user?.channelMutes + _client.state.user?.channelMutes .any((element) => element.channel!.cid == cid) == true; /// Returns true if the channel is muted as a stream - Stream? get isMutedStream => _client.state!.userStream.map((event) => + Stream? get isMutedStream => _client.state.userStream.map((event) => event!.channelMutes.any((element) => element.channel!.cid == cid) == true); @@ -310,7 +310,7 @@ class Channel { // ignore: parameter_assignments message = message.copyWith( createdAt: message.createdAt, - user: _client.state!.user, + user: _client.state.user, quotedMessage: quotedMessage, status: MessageSendingStatus.sending, attachments: message.attachments?.map( @@ -564,7 +564,7 @@ class Channel { }) async { final messageId = message.id; final now = DateTime.now(); - final user = _client.state!.user; + final user = _client.state.user; final latestReactions = [...message.latestReactions ?? []]; if (enforceUnique) { @@ -629,7 +629,7 @@ class Channel { Future deleteReaction( Message message, Reaction reaction) async { final type = reaction.type; - final user = _client.state!.user; + final user = _client.state.user; final reactionCounts = {...message.reactionCounts ?? {}}; if (reactionCounts.containsKey(type)) { @@ -805,8 +805,8 @@ class Channel { /// Mark all channel messages as read Future markRead() async { _checkInitialized(); - client.state!.totalUnreadCount = max( - 0, (client.state!.totalUnreadCount ?? 0) - (state!.unreadCount ?? 0)); + client.state.totalUnreadCount = max( + 0, (client.state.totalUnreadCount ?? 0) - (state!.unreadCount ?? 0)); state!._unreadCountController.add(0); final response = await _client.post('$_channelURL/read', data: {}); return _client.decode(response.data, EmptyResponse.fromJson); @@ -841,7 +841,7 @@ class Channel { void _initState(ChannelState channelState) { state = ChannelClientState(this, channelState); - client.state!.channels![cid] = this; + client.state.channels![cid] = this; if (!_initializedCompleter.isCompleted) { _initializedCompleter.complete(true); } @@ -1274,7 +1274,7 @@ class ChannelClientState { void _computeInitialUnread() { final userRead = channelState?.read.firstWhereOrNull( - (r) => r.user.id == _channel._client.state?.user?.id, + (r) => r.user.id == _channel._client.state.user?.id, ); if (userRead != null) { _unreadCountController.add(userRead.unreadMessages); @@ -1391,7 +1391,7 @@ class ChannelClientState { void _listenReactionDeleted() { _subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) { - final userId = _channel.client.state!.user!.id; + final userId = _channel.client.state.user!.id; final message = event.message!.copyWith( ownReactions: [...event.message!.latestReactions!] ..removeWhere((it) => it.userId != userId), @@ -1402,7 +1402,7 @@ class ChannelClientState { void _listenReactions() { _subscriptions.add(_channel.on(EventType.reactionNew).listen((event) { - final userId = _channel.client.state!.user!.id; + final userId = _channel.client.state.user!.id; final message = event.message!.copyWith( ownReactions: [...event.message!.latestReactions!] ..removeWhere((it) => it.userId != userId), @@ -1418,7 +1418,7 @@ class ChannelClientState { EventType.reactionUpdated, ) .listen((event) { - final userId = _channel.client.state!.user!.id; + final userId = _channel.client.state.user!.id; final message = event.message!.copyWith( ownReactions: [...event.message!.latestReactions!] ..removeWhere((it) => it.userId != userId), @@ -1512,7 +1512,7 @@ class ChannelClientState { if (userReadIndex != null && userReadIndex != -1) { final userRead = readList.removeAt(userReadIndex); - if (userRead.user.id == _channel._client.state!.user!.id) { + if (userRead.user.id == _channel._client.state.user!.id) { _unreadCountController.add(0); } readList.add(Read( @@ -1552,14 +1552,14 @@ class ChannelClientState { /// Channel members list List get members => _channelState!.members - .map((e) => e.copyWith(user: _channel.client.state!.users[e.user!.id])) + .map((e) => e.copyWith(user: _channel.client.state.users[e.user!.id])) .toList(); /// Channel members list as a stream Stream> get membersStream => CombineLatestStream.combine2< List?, Map, List>( channelStateStream.map((cs) => cs!.members), - _channel.client.state!.usersStream, + _channel.client.state.usersStream, (members, users) => members!.map((e) => e!.copyWith(user: users[e.user!.id])).toList(), ); @@ -1573,14 +1573,14 @@ class ChannelClientState { /// Channel watchers list List get watchers => _channelState!.watchers - .map((e) => _channel.client.state!.users[e.id] ?? e) + .map((e) => _channel.client.state.users[e.id] ?? e) .toList(); /// Channel watchers list as a stream Stream> get watchersStream => CombineLatestStream.combine2< List?, Map, List>( channelStateStream.map((cs) => cs!.watchers), - _channel.client.state!.usersStream, + _channel.client.state.usersStream, (watchers, users) => watchers!.map((e) => users[e.id] ?? e).toList(), ); @@ -1600,8 +1600,8 @@ class ChannelClientState { int? get unreadCount => _unreadCountController.value; bool _countMessageAsUnread(Message message) { - final userId = _channel.client.state?.user?.id; - final userIsMuted = _channel.client.state?.user?.mutes.firstWhereOrNull( + final userId = _channel.client.state.user?.id; + final userIsMuted = _channel.client.state.user?.mutes.firstWhereOrNull( (m) => m.user?.id == message.user!.id, ) != null; @@ -1763,7 +1763,7 @@ class ChannelClientState { ..add( _channel.on(EventType.typingStart).listen( (event) { - if (event.user!.id != _channel.client.state!.user!.id) { + if (event.user!.id != _channel.client.state.user!.id) { _typings[event.user] = DateTime.now(); _typingEventsController.add(_typings.keys.toList()); } @@ -1773,7 +1773,7 @@ class ChannelClientState { ..add( _channel.on(EventType.typingStop).listen( (event) { - if (event.user!.id != _channel.client.state!.user!.id) { + if (event.user!.id != _channel.client.state.user!.id) { _typings.remove(event.user); _typingEventsController.add(_typings.keys.toList()); } diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 69b6321d..46eb21af 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -134,7 +134,7 @@ class StreamChatClient { RetryPolicy? get retryPolicy => _retryPolicy; /// This client state - ClientState? state; + late final ClientState state; /// By default the Chat client will write all messages with level Warn or /// Error to stdout. @@ -301,7 +301,7 @@ class StreamChatClient { if (tokenProvider != null) { httpClient.lock(); - final userId = state!.user!.id; + final userId = state.user!.id; await _disconnect(); @@ -389,7 +389,7 @@ class StreamChatClient { await _disconnect(); httpClient.close(); await _controller.close(); - state!.dispose(); + state.dispose(); await _wsConnectionStatusController.close(); } @@ -424,7 +424,7 @@ class StreamChatClient { throw e; } - state!.user = OwnUser.fromJson(user.toJson()); + state.user = OwnUser.fromJson(user.toJson()); this.token = token; _anonymous = false; @@ -490,11 +490,11 @@ class StreamChatClient { } if (event.user != null) { - state!._updateUser(event.user); + state._updateUser(event.user); } if (event.me != null) { - state!.user = event.me; + state.user = event.me; } _controller.add(event); } @@ -518,12 +518,12 @@ class StreamChatClient { if (_originalChatPersistenceClient != null) { _chatPersistenceClient = _originalChatPersistenceClient; - await _chatPersistenceClient!.connect(state!.user!.id); + await _chatPersistenceClient!.connect(state.user!.id); } _ws = WebSocket( baseUrl: baseURL, - user: state!.user, + user: state.user, connectParams: { 'api_key': apiKey, 'authorization': token, @@ -531,7 +531,7 @@ class StreamChatClient { 'X-Stream-Client': _userAgent, }, connectPayload: { - 'user_id': state!.user!.id, + 'user_id': state.user!.id, 'server_determines_connection_id': true, }, handler: handleEvent, @@ -552,11 +552,11 @@ class StreamChatClient { type: EventType.connectionRecovered, online: true, )); - if (state!.channels?.isNotEmpty == true) { + if (state.channels?.isNotEmpty == true) { // ignore: unawaited_futures queryChannelsOnline(filter: { 'cid': { - '\$in': state!.channels!.keys.toList(), + '\$in': state.channels!.keys.toList(), }, }).then( (_) async { @@ -757,7 +757,7 @@ class StreamChatClient { .map((it) => it.user) .toList(growable: false); - state!._updateUsers(users); + state._updateUsers(users); logger.info('Got ${res.channels?.length} channels from api'); @@ -769,7 +769,7 @@ class StreamChatClient { clearQueryCache: paginationParams.offset == 0, ); - state!.channels = updateData.key; + state.channels = updateData.key; return updateData.value; } @@ -785,19 +785,19 @@ class StreamChatClient { paginationParams: paginationParams, ))!; final updatedData = _mapChannelStateToChannel(offlineChannels); - state!.channels = updatedData.key; + state.channels = updatedData.key; return updatedData.value; } MapEntry, List> _mapChannelStateToChannel( List channelStates, ) { - final channels = {...state!.channels ?? {}}; + final channels = {...state.channels ?? {}}; final newChannels = []; for (final channelState in channelStates) { final channel = channels[channelState.channel!.cid]; if (channel != null) { - channel.state?.updateChannelState(channelState); + channel.state!.updateChannelState(channelState); newChannels.add(channel); } else { final newChannel = Channel.fromState(this, channelState); @@ -931,7 +931,7 @@ class StreamChatClient { '${PACKAGE_VERSION.split('+')[0]}'; Map get _commonQueryParams => { - 'user_id': state!.user?.id, + 'user_id': state.user?.id, 'api_key': apiKey, 'connection_id': _connectionId, }; @@ -955,7 +955,7 @@ class StreamChatClient { _anonymous = true; const uuid = Uuid(); - state!.user = OwnUser(id: uuid.v4()); + state.user = OwnUser(id: uuid.v4()); return connect().then((event) { _connectCompleter!.complete(event); @@ -1003,7 +1003,7 @@ class StreamChatClient { _connectCompleter = null; if (clearUser == true) { - state!.dispose(); + state.dispose(); state = ClientState(this); } @@ -1053,7 +1053,7 @@ class StreamChatClient { QueryUsersResponse.fromJson, ); - state?._updateUsers(response.users!); + state._updateUsers(response.users!); return response; } @@ -1195,9 +1195,9 @@ class StreamChatClient { String? id, Map? extraData, }) { - if (id != null && state!.channels?.containsKey('$type:$id') == true) { - if (state!.channels!['$type:$id'] != null) { - return state!.channels!['$type:$id'] as Channel; + if (id != null && state.channels?.containsKey('$type:$id') == true) { + if (state.channels!['$type:$id'] != null) { + return state.channels!['$type:$id'] as Channel; } } diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index cdeae39d..4717f524 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -566,7 +566,7 @@ void main() { tokenProvider: (_) async => '', ); - client.state?.user = OwnUser(id: 'test-id'); + client.state.user = OwnUser(id: 'test-id'); final channelClient = client.channel('messaging', id: 'testid'); const reactionType = 'test'; @@ -617,7 +617,7 @@ void main() { tokenProvider: (_) async => '', ); - client.state?.user = OwnUser(id: 'test-id'); + client.state.user = OwnUser(id: 'test-id'); final channelClient = client.channel('messaging', id: 'testid'); @@ -639,7 +639,7 @@ void main() { type: 'test', createdAt: DateTime.now(), user: User( - id: client.state?.user?.id ?? '', + id: client.state.user?.id ?? '', ), ), ); From 8a923929142e868f445c55167c9d6ad44c7d7d16 Mon Sep 17 00:00:00 2001 From: Neevash Ramdial Date: Fri, 16 Apr 2021 09:34:50 -0400 Subject: [PATCH 101/599] retryAttachmentUpload should not be null --- packages/stream_chat/lib/src/api/channel.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index ce1d25ae..f868e6e0 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -197,7 +197,7 @@ class Channel { String messageId, Iterable attachmentIds, ) { - final message = state!.messages!.firstWhereOrNull( + final message = state!.messages.firstWhereOrNull( (it) => it.id == messageId, ); @@ -304,7 +304,7 @@ class Channel { .remove(message.id) ?.completeError('Message Cancelled'); - final quotedMessage = state?.messages?.firstWhereOrNull( + final quotedMessage = state?.messages.firstWhereOrNull( (m) => m.id == message.quotedMessageId, ); // ignore: parameter_assignments @@ -771,11 +771,11 @@ class Channel { if (res.message != null) { state!.addMessage(res.message!); } else { - final oldIndex = state!.messages?.indexWhere((m) => m.id == messageId); + final oldIndex = state!.messages.indexWhere((m) => m.id == messageId); Message? oldMessage; - if (oldIndex != null && oldIndex != -1) { - oldMessage = state!.messages![oldIndex]; + if (oldIndex != -1) { + oldMessage = state!.messages[oldIndex]; state!.updateChannelState(state!._channelState!.copyWith( messages: state?.messages?..remove(oldMessage), )); @@ -784,7 +784,7 @@ class Channel { .expand((messages) => messages) .firstWhereOrNull((m) => m.id == messageId); if (oldMessage?.parentId != null) { - final parentMessage = state!.messages!.firstWhereOrNull( + final parentMessage = state!.messages.firstWhereOrNull( (element) => element.id == oldMessage!.parentId, ); if (parentMessage != null) { @@ -1372,7 +1372,7 @@ class ChannelClientState { /// Retry failed message Future retryFailedMessages() async { final failedMessages = - [...messages!, ...threads!.values.expand((v) => v)] + [...messages, ...threads!.values.expand((v) => v)] .where( (message) => message.status != MessageSendingStatus.sent && @@ -1528,7 +1528,7 @@ class ChannelClientState { } /// Channel message list - List? get messages => _channelState!.messages; + List get messages => _channelState!.messages; /// Channel message list as a stream Stream?> get messagesStream => From af135ecb9cd20b2f9ca741b04ad287554db61100 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Apr 2021 12:40:00 +0200 Subject: [PATCH 102/599] move on with the migration --- packages/stream_chat/lib/src/api/channel.dart | 235 +++++++++++------- .../stream_chat/lib/src/api/responses.dart | 205 ++++++++------- .../stream_chat/lib/src/api/responses.g.dart | 159 ++++++------ .../lib/src/attachment_file_uploader.dart | 34 +-- packages/stream_chat/lib/src/client.dart | 51 ++-- .../stream_chat/lib/src/models/action.dart | 17 +- .../stream_chat/lib/src/models/action.g.dart | 8 +- .../lib/src/models/attachment.dart | 25 +- .../lib/src/models/attachment.g.dart | 14 +- .../lib/src/models/attachment_file.dart | 12 +- .../lib/src/models/channel_config.dart | 81 +++--- .../lib/src/models/channel_config.g.dart | 37 ++- .../lib/src/models/channel_model.dart | 20 +- .../lib/src/models/channel_model.g.dart | 2 +- .../stream_chat/lib/src/models/command.dart | 12 +- .../stream_chat/lib/src/models/command.g.dart | 6 +- .../stream_chat/lib/src/models/device.dart | 6 +- .../stream_chat/lib/src/models/device.g.dart | 2 +- .../stream_chat/lib/src/models/event.dart | 2 - .../stream_chat/lib/src/models/member.dart | 10 +- .../stream_chat/lib/src/models/member.g.dart | 4 +- .../stream_chat/lib/src/models/message.dart | 66 +++-- .../stream_chat/lib/src/models/message.g.dart | 29 +-- packages/stream_chat/lib/src/models/mute.dart | 15 +- .../stream_chat/lib/src/models/mute.g.dart | 16 +- .../stream_chat/lib/src/models/own_user.dart | 3 +- .../stream_chat/lib/src/models/reaction.dart | 5 +- packages/stream_chat/lib/src/models/read.dart | 2 +- .../lib/src/models/serialization.dart | 3 +- packages/stream_chat/lib/src/models/user.dart | 5 +- .../test/src/api/channel_test.dart | 9 +- .../stream_chat/test/src/client_test.dart | 12 +- .../test/src/models/attachment_test.dart | 9 +- .../test/src/models/channel_state_test.dart | 56 ++--- .../test/src/models/channel_test.dart | 6 +- .../test/src/models/message_test.dart | 9 +- .../src/message_reactions_modal_test.dart | 1 + 37 files changed, 653 insertions(+), 535 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index f868e6e0..90bd6025 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -59,13 +59,12 @@ class Channel { /// Returns true if the channel is muted bool get isMuted => _client.state.user?.channelMutes - .any((element) => element.channel!.cid == cid) == + .any((element) => element.channel.cid == cid) == true; /// Returns true if the channel is muted as a stream Stream? get isMutedStream => _client.state.userStream.map((event) => - event!.channelMutes.any((element) => element.channel!.cid == cid) == - true); + event!.channelMutes.any((element) => element.channel.cid == cid) == true); /// True if the channel is a group bool get isGroup => memberCount != 2; @@ -74,85 +73,130 @@ class Channel { bool get isDistinct => id?.startsWith('!members') == true; /// Channel configuration - ChannelConfig? get config => state?._channelState?.channel?.config; + ChannelConfig? get config { + _checkInitialized(); + return state?._channelState?.channel?.config; + } /// Channel configuration as a stream - Stream? get configStream => - state?.channelStateStream.map((cs) => cs!.channel?.config); + Stream? get configStream { + _checkInitialized(); + return state?.channelStateStream.map((cs) => cs!.channel?.config); + } /// Channel user creator - User? get createdBy => state?._channelState?.channel?.createdBy; + User? get createdBy { + _checkInitialized(); + return state?._channelState?.channel?.createdBy; + } /// Channel user creator as a stream - Stream? get createdByStream => - state?.channelStateStream.map((cs) => cs!.channel?.createdBy); + Stream? get createdByStream { + _checkInitialized(); + return state?.channelStateStream.map((cs) => cs!.channel?.createdBy); + } /// Channel frozen status - bool? get frozen => state?._channelState?.channel?.frozen; + bool? get frozen { + _checkInitialized(); + return state?._channelState?.channel?.frozen; + } /// Channel frozen status as a stream - Stream? get frozenStream => - state?.channelStateStream.map((cs) => cs!.channel?.frozen); + Stream? get frozenStream { + _checkInitialized(); + return state?.channelStateStream.map((cs) => cs!.channel?.frozen); + } /// Channel creation date - DateTime? get createdAt => state?._channelState?.channel?.createdAt; + DateTime? get createdAt { + _checkInitialized(); + return state?._channelState?.channel?.createdAt; + } /// Channel creation date as a stream - Stream? get createdAtStream => - state?.channelStateStream.map((cs) => cs!.channel?.createdAt); + Stream? get createdAtStream { + _checkInitialized(); + return state?.channelStateStream.map((cs) => cs!.channel?.createdAt); + } /// Channel last message date - DateTime? get lastMessageAt => state?._channelState?.channel?.lastMessageAt; + DateTime? get lastMessageAt { + _checkInitialized(); + + return state?._channelState?.channel?.lastMessageAt; + } /// Channel last message date as a stream - Stream? get lastMessageAtStream => - state?.channelStateStream.map((cs) => cs!.channel?.lastMessageAt); + Stream? get lastMessageAtStream { + _checkInitialized(); + + return state?.channelStateStream.map((cs) => cs!.channel?.lastMessageAt); + } /// Channel updated date - DateTime? get updatedAt => state?._channelState?.channel?.updatedAt; + DateTime? get updatedAt { + _checkInitialized(); + + return state?._channelState?.channel?.updatedAt; + } /// Channel updated date as a stream - Stream? get updatedAtStream => - state?.channelStateStream.map((cs) => cs!.channel?.updatedAt); + Stream? get updatedAtStream { + _checkInitialized(); + + return state?.channelStateStream.map((cs) => cs!.channel?.updatedAt); + } /// Channel deletion date - DateTime? get deletedAt => state?._channelState?.channel?.deletedAt; + DateTime? get deletedAt { + _checkInitialized(); + + return state?._channelState?.channel?.deletedAt; + } /// Channel deletion date as a stream - Stream? get deletedAtStream => - state?.channelStateStream.map((cs) => cs!.channel?.deletedAt); + Stream? get deletedAtStream { + _checkInitialized(); + + return state?.channelStateStream.map((cs) => cs!.channel?.deletedAt); + } /// Channel member count - int? get memberCount => state?._channelState?.channel?.memberCount; + int? get memberCount { + _checkInitialized(); + + return state?._channelState?.channel?.memberCount; + } /// Channel member count as a stream - Stream? get memberCountStream => - state?.channelStateStream.map((cs) => cs!.channel?.memberCount); + Stream? get memberCountStream { + _checkInitialized(); + + return state?.channelStateStream.map((cs) => cs!.channel?.memberCount); + } /// Channel id String? get id => state?._channelState?.channel?.id ?? _id; - /// Channel id as a stream - Stream? get idStream => - state?.channelStateStream.map((cs) => cs!.channel?.id ?? _id); - /// Channel cid String? get cid => state?._channelState?.channel?.cid ?? _cid; /// Channel team - String? get team => state?._channelState?.channel?.team; - - /// Channel cid as a stream - Stream? get cidStream => - state?.channelStateStream.map((cs) => cs!.channel?.cid ?? _cid); + String? get team { + _checkInitialized(); + return state?._channelState?.channel?.team; + } /// Channel extra data Map? get extraData => state?._channelState?.channel?.extraData ?? _extraData; /// Channel extra data as a stream - Stream?>? get extraDataStream => - state?.channelStateStream.map((cs) => cs!.channel?.extraData); + Stream?>? get extraDataStream { + _checkInitialized(); + return state?.channelStateStream.map((cs) => cs!.channel?.extraData); + } /// The main Stream chat client StreamChatClient get client => _client; @@ -205,14 +249,14 @@ class Channel { throw Exception('Error, Message not found'); } - final attachments = message.attachments!.where((it) { + final attachments = message.attachments.where((it) { if (it.uploadState.isSuccess) return false; return attachmentIds.contains(it.id); }); if (attachments.isEmpty) { client.logger.info('No attachments available to upload'); - if (message.attachments!.every((it) => it.uploadState.isSuccess)) { + if (message.attachments.every((it) => it.uploadState.isSuccess)) { _messageAttachmentsUploadCompleter.remove(messageId)?.complete(message); } return Future.value(); @@ -222,9 +266,9 @@ class Channel { void updateAttachment(Attachment attachment) { final index = - message.attachments!.indexWhere((it) => it.id == attachment.id); + message.attachments.indexWhere((it) => it.id == attachment.id); if (index != -1) { - message.attachments![index] = attachment; + message.attachments[index] = attachment; state?.addMessage(message); } } @@ -252,13 +296,13 @@ class Channel { it.file!, onSendProgress: onSendProgress, cancelToken: cancelToken, - ).then((it) => it!.file!); + ).then((it) => it!.file); } else { future = sendFile( it.file!, onSendProgress: onSendProgress, cancelToken: cancelToken, - ).then((it) => it!.file!); + ).then((it) => it!.file); } _cancelableAttachmentUploadRequest[it.id] = cancelToken; return future.then((url) { @@ -288,7 +332,7 @@ class Channel { _cancelableAttachmentUploadRequest.remove(it.id); }); })).whenComplete(() { - if (message.attachments!.every((it) => it.uploadState.isSuccess)) { + if (message.attachments.every((it) => it.uploadState.isSuccess)) { _messageAttachmentsUploadCompleter.remove(messageId)?.complete(message); } }); @@ -313,7 +357,7 @@ class Channel { user: _client.state.user, quotedMessage: quotedMessage, status: MessageSendingStatus.sending, - attachments: message.attachments?.map( + attachments: message.attachments.map( (it) { if (it.uploadState.isSuccess) return it; return it.copyWith(uploadState: const UploadState.preparing()); @@ -324,7 +368,7 @@ class Channel { state?.addMessage(message); try { - if (message.attachments?.any((it) => !it.uploadState.isSuccess) == true) { + if (message.attachments.any((it) => !it.uploadState.isSuccess) == true) { final attachmentsUploadCompleter = Completer(); _messageAttachmentsUploadCompleter[message.id] = attachmentsUploadCompleter; @@ -332,15 +376,15 @@ class Channel { // ignore: unawaited_futures _uploadAttachments( message.id, - message.attachments!.map((it) => it.id), + message.attachments.map((it) => it.id), ); // ignore: parameter_assignments message = await attachmentsUploadCompleter.future; } - final response = await _client.sendMessage(message, id, type); - state?.addMessage(response.message!); + final response = await _client.sendMessage(message, id!, type!); + state?.addMessage(response.message); return response; } catch (error) { if (error is DioError && error.type != DioErrorType.response) { @@ -364,7 +408,7 @@ class Channel { message = message.copyWith( status: MessageSendingStatus.updating, updatedAt: message.updatedAt, - attachments: message.attachments?.map( + attachments: message.attachments.map( (it) { if (it.uploadState.isSuccess) return it; return it.copyWith(uploadState: const UploadState.preparing()); @@ -375,7 +419,7 @@ class Channel { state?.addMessage(message); try { - if (message.attachments?.any((it) => !it.uploadState.isSuccess) == true) { + if (message.attachments.any((it) => !it.uploadState.isSuccess) == true) { final attachmentsUploadCompleter = Completer(); _messageAttachmentsUploadCompleter[message.id] = attachmentsUploadCompleter; @@ -383,7 +427,7 @@ class Channel { // ignore: unawaited_futures _uploadAttachments( message.id, - message.attachments!.map((it) => it.id), + message.attachments.map((it) => it.id), ); // ignore: parameter_assignments @@ -392,13 +436,11 @@ class Channel { final response = await _client.updateMessage(message); - final m = response.message?.copyWith( + final m = response.message.copyWith( ownReactions: message.ownReactions, ); - if (m != null) { - state?.addMessage(m); - } + state?.addMessage(m); return response; } catch (error) { @@ -489,28 +531,32 @@ class Channel { AttachmentFile file, { ProgressCallback? onSendProgress, CancelToken? cancelToken, - }) => - _client.sendFile( - file, - id, - type, - onSendProgress: onSendProgress, - cancelToken: cancelToken, - ); + }) { + _checkInitialized(); + return _client.sendFile( + file, + id!, + type!, + onSendProgress: onSendProgress, + cancelToken: cancelToken, + ); + } /// Send an image to this channel Future sendImage( AttachmentFile file, { ProgressCallback? onSendProgress, CancelToken? cancelToken, - }) => - _client.sendImage( - file, - id, - type, - onSendProgress: onSendProgress, - cancelToken: cancelToken, - ); + }) { + _checkInitialized(); + return _client.sendImage( + file, + id!, + type!, + onSendProgress: onSendProgress, + cancelToken: cancelToken, + ); + } /// A message search. Future search({ @@ -535,15 +581,29 @@ class Channel { Future deleteFile( String url, { CancelToken? cancelToken, - }) => - _client.deleteFile(url, id, type, cancelToken: cancelToken); + }) { + _checkInitialized(); + return _client.deleteFile( + url, + id!, + type!, + cancelToken: cancelToken, + ); + } /// Delete an image from this channel Future deleteImage( String url, { CancelToken? cancelToken, - }) => - _client.deleteImage(url, id, type, cancelToken: cancelToken); + }) { + _checkInitialized(); + return _client.deleteImage( + url, + id!, + type!, + cancelToken: cancelToken, + ); + } /// Send an event on this channel Future sendEvent(Event event) { @@ -916,9 +976,7 @@ class Channel { final messages = res.messages; - if (messages != null) { - state?.updateChannelState(ChannelState(messages: messages)); - } + state?.updateChannelState(ChannelState(messages: messages)); return res; } @@ -1198,11 +1256,10 @@ class Channel { } void _checkInitialized() { - if (!_initializedCompleter.isCompleted) { - throw Exception( - "Channel $cid hasn't been initialized yet. Make sure to call .watch()" - ' or to instantiate the client using [Channel.fromState]'); - } + assert( + !_initializedCompleter.isCompleted, + "Channel $cid hasn't been initialized yet. Make sure to call .watch()" + ' or to instantiate the client using [Channel.fromState]'); } } @@ -1285,8 +1342,8 @@ class ChannelClientState { final expiredAttachmentMessagesId = channelState.messages .where((m) => !_updatedMessagesIds.contains(m.id) && - m.attachments?.isNotEmpty == true && - m.attachments?.any((e) { + m.attachments.isNotEmpty == true && + m.attachments.any((e) { final url = e.imageUrl ?? e.assetUrl; if (url == null || !url.contains('')) { return false; @@ -1602,12 +1659,12 @@ class ChannelClientState { bool _countMessageAsUnread(Message message) { final userId = _channel.client.state.user?.id; final userIsMuted = _channel.client.state.user?.mutes.firstWhereOrNull( - (m) => m.user?.id == message.user!.id, + (m) => m.user.id == message.user?.id, ) != null; return message.silent != true && message.shadowed != true && - message.user!.id != userId && + message.user?.id != userId && !userIsMuted; } diff --git a/packages/stream_chat/lib/src/api/responses.dart b/packages/stream_chat/lib/src/api/responses.dart index a26fc626..a64cd64e 100644 --- a/packages/stream_chat/lib/src/api/responses.dart +++ b/packages/stream_chat/lib/src/api/responses.dart @@ -20,186 +20,194 @@ class _BaseResponse { @JsonSerializable(createToJson: false) class SyncResponse extends _BaseResponse { /// The list of events - List? events; + @JsonKey(defaultValue: []) + late List events; /// Create a new instance from a json - static SyncResponse fromJson(Map? json) => - _$SyncResponseFromJson(json!); + static SyncResponse fromJson(Map json) => + _$SyncResponseFromJson(json); } /// Model response for [StreamChatClient.queryChannels] api call @JsonSerializable(createToJson: false) class QueryChannelsResponse extends _BaseResponse { /// List of channels state returned by the query - List? channels; + @JsonKey(defaultValue: []) + late List channels; /// Create a new instance from a json - static QueryChannelsResponse fromJson(Map? json) => - _$QueryChannelsResponseFromJson(json!); + static QueryChannelsResponse fromJson(Map json) => + _$QueryChannelsResponseFromJson(json); } /// Model response for [StreamChatClient.queryChannels] api call @JsonSerializable(createToJson: false) class TranslateMessageResponse extends _BaseResponse { - /// List of channels state returned by the query - TranslatedMessage? message; + /// Translated message + late TranslatedMessage message; /// Create a new instance from a json - static TranslateMessageResponse fromJson(Map? json) => - _$TranslateMessageResponseFromJson(json!); + static TranslateMessageResponse fromJson(Map json) => + _$TranslateMessageResponseFromJson(json); } /// Model response for [StreamChatClient.queryChannels] api call @JsonSerializable(createToJson: false) class QueryMembersResponse extends _BaseResponse { /// List of channels state returned by the query - List? members; + @JsonKey(defaultValue: []) + late List members; /// Create a new instance from a json - static QueryMembersResponse fromJson(Map? json) => - _$QueryMembersResponseFromJson(json!); + static QueryMembersResponse fromJson(Map json) => + _$QueryMembersResponseFromJson(json); } /// Model response for [StreamChatClient.queryUsers] api call @JsonSerializable(createToJson: false) class QueryUsersResponse extends _BaseResponse { /// List of users returned by the query - List? users; + @JsonKey(defaultValue: []) + late List users; /// Create a new instance from a json - static QueryUsersResponse fromJson(Map? json) => - _$QueryUsersResponseFromJson(json!); + static QueryUsersResponse fromJson(Map json) => + _$QueryUsersResponseFromJson(json); } /// Model response for [channel.getReactions] api call @JsonSerializable(createToJson: false) class QueryReactionsResponse extends _BaseResponse { /// List of reactions returned by the query - List? reactions; + @JsonKey(defaultValue: []) + late List reactions; /// Create a new instance from a json - static QueryReactionsResponse fromJson(Map? json) => - _$QueryReactionsResponseFromJson(json!); + static QueryReactionsResponse fromJson(Map json) => + _$QueryReactionsResponseFromJson(json); } /// Model response for [Channel.getReplies] api call @JsonSerializable(createToJson: false) class QueryRepliesResponse extends _BaseResponse { /// List of messages returned by the api call - List? messages; + @JsonKey(defaultValue: []) + late List messages; /// Create a new instance from a json - static QueryRepliesResponse fromJson(Map? json) => - _$QueryRepliesResponseFromJson(json!); + static QueryRepliesResponse fromJson(Map json) => + _$QueryRepliesResponseFromJson(json); } /// Model response for [StreamChatClient.getDevices] api call @JsonSerializable(createToJson: false) class ListDevicesResponse extends _BaseResponse { /// List of user devices - List? devices; + @JsonKey(defaultValue: []) + late List devices; /// Create a new instance from a json - static ListDevicesResponse fromJson(Map? json) => - _$ListDevicesResponseFromJson(json!); + static ListDevicesResponse fromJson(Map json) => + _$ListDevicesResponseFromJson(json); } /// Model response for [Channel.sendFile] api call @JsonSerializable(createToJson: false) class SendFileResponse extends _BaseResponse { /// The url of the uploaded file - String? file; + late String file; /// Create a new instance from a json - static SendFileResponse fromJson(Map? json) => - _$SendFileResponseFromJson(json!); + static SendFileResponse fromJson(Map json) => + _$SendFileResponseFromJson(json); } /// Model response for [Channel.sendImage] api call @JsonSerializable(createToJson: false) class SendImageResponse extends _BaseResponse { /// The url of the uploaded file - String? file; + late String file; /// Create a new instance from a json - static SendImageResponse fromJson(Map? json) => - _$SendImageResponseFromJson(json!); + static SendImageResponse fromJson(Map json) => + _$SendImageResponseFromJson(json); } /// Model response for [Channel.sendReaction] api call @JsonSerializable(createToJson: false) class SendReactionResponse extends _BaseResponse { /// Message returned by the api call - Message? message; + late Message message; /// The reaction created by the api call - Reaction? reaction; + late Reaction reaction; /// Create a new instance from a json - static SendReactionResponse fromJson(Map? json) => - _$SendReactionResponseFromJson(json!); + static SendReactionResponse fromJson(Map json) => + _$SendReactionResponseFromJson(json); } /// Model response for [StreamChatClient.connectGuestUser] api call @JsonSerializable(createToJson: false) class ConnectGuestUserResponse extends _BaseResponse { /// Guest user access token - String? accessToken; + late String accessToken; /// Guest user - User? user; + late User user; /// Create a new instance from a json - static ConnectGuestUserResponse fromJson(Map? json) => - _$ConnectGuestUserResponseFromJson(json!); + static ConnectGuestUserResponse fromJson(Map json) => + _$ConnectGuestUserResponseFromJson(json); } /// Model response for [StreamChatClient.updateUser] api call @JsonSerializable(createToJson: false) class UpdateUsersResponse extends _BaseResponse { /// Updated users - Map? users; + @JsonKey(defaultValue: {}) + late Map users; /// Create a new instance from a json - static UpdateUsersResponse fromJson(Map? json) => - _$UpdateUsersResponseFromJson(json!); + static UpdateUsersResponse fromJson(Map json) => + _$UpdateUsersResponseFromJson(json); } /// Model response for [StreamChatClient.updateMessage] api call @JsonSerializable(createToJson: false) class UpdateMessageResponse extends _BaseResponse { /// Message returned by the api call - Message? message; + late Message message; /// Create a new instance from a json - static UpdateMessageResponse fromJson(Map? json) => - _$UpdateMessageResponseFromJson(json!); + static UpdateMessageResponse fromJson(Map json) => + _$UpdateMessageResponseFromJson(json); } /// Model response for [Channel.sendMessage] api call @JsonSerializable(createToJson: false) class SendMessageResponse extends _BaseResponse { /// Message returned by the api call - Message? message; + late Message message; /// Create a new instance from a json - static SendMessageResponse fromJson(Map? json) => - _$SendMessageResponseFromJson(json!); + static SendMessageResponse fromJson(Map json) => + _$SendMessageResponseFromJson(json); } /// Model response for [StreamChatClient.getMessage] api call @JsonSerializable(createToJson: false) class GetMessageResponse extends _BaseResponse { /// Message returned by the api call - Message? message; + late Message message; /// Channel of the message ChannelModel? channel; /// Create a new instance from a json - static GetMessageResponse fromJson(Map? json) { - final res = _$GetMessageResponseFromJson(json!); - final jsonChannel = res.message?.extraData?.remove('channel'); + static GetMessageResponse fromJson(Map json) { + final res = _$GetMessageResponseFromJson(json); + final jsonChannel = res.message.extraData.remove('channel'); if (jsonChannel != null) { res.channel = ChannelModel.fromJson(jsonChannel); } @@ -211,29 +219,31 @@ class GetMessageResponse extends _BaseResponse { @JsonSerializable(createToJson: false) class SearchMessagesResponse extends _BaseResponse { /// List of messages returned by the api call - List? results; + @JsonKey(defaultValue: []) + late List results; /// Create a new instance from a json - static SearchMessagesResponse fromJson(Map? json) => - _$SearchMessagesResponseFromJson(json!); + static SearchMessagesResponse fromJson(Map json) => + _$SearchMessagesResponseFromJson(json); } /// Model response for [Channel.getMessagesById] api call @JsonSerializable(createToJson: false) class GetMessagesByIdResponse extends _BaseResponse { /// Message returned by the api call - List? messages; + @JsonKey(defaultValue: []) + late List messages; /// Create a new instance from a json - static GetMessagesByIdResponse fromJson(Map? json) => - _$GetMessagesByIdResponseFromJson(json!); + static GetMessagesByIdResponse fromJson(Map json) => + _$GetMessagesByIdResponseFromJson(json); } /// Model response for [Channel.update] api call @JsonSerializable(createToJson: false) class UpdateChannelResponse extends _BaseResponse { /// Updated channel - ChannelModel? channel; + late ChannelModel channel; /// Channel members List? members; @@ -242,56 +252,58 @@ class UpdateChannelResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static UpdateChannelResponse fromJson(Map? json) => - _$UpdateChannelResponseFromJson(json!); + static UpdateChannelResponse fromJson(Map json) => + _$UpdateChannelResponseFromJson(json); } /// Model response for [Channel.updatePartial] api call @JsonSerializable(createToJson: false) class PartialUpdateChannelResponse extends _BaseResponse { /// Updated channel - ChannelModel? channel; + late ChannelModel channel; /// Channel members List? members; /// Create a new instance from a json - static PartialUpdateChannelResponse fromJson(Map? json) => - _$PartialUpdateChannelResponseFromJson(json!); + static PartialUpdateChannelResponse fromJson(Map json) => + _$PartialUpdateChannelResponseFromJson(json); } /// Model response for [Channel.inviteMembers] api call @JsonSerializable(createToJson: false) class InviteMembersResponse extends _BaseResponse { /// Updated channel - ChannelModel? channel; + late ChannelModel channel; /// Channel members - List? members; + @JsonKey(defaultValue: []) + late List members; /// Message returned by the api call Message? message; /// Create a new instance from a json - static InviteMembersResponse fromJson(Map? json) => - _$InviteMembersResponseFromJson(json!); + static InviteMembersResponse fromJson(Map json) => + _$InviteMembersResponseFromJson(json); } /// Model response for [Channel.removeMembers] api call @JsonSerializable(createToJson: false) class RemoveMembersResponse extends _BaseResponse { /// Updated channel - ChannelModel? channel; + late ChannelModel channel; /// Channel members - List? members; + @JsonKey(defaultValue: []) + late List members; /// Message returned by the api call Message? message; /// Create a new instance from a json - static RemoveMembersResponse fromJson(Map? json) => - _$RemoveMembersResponseFromJson(json!); + static RemoveMembersResponse fromJson(Map json) => + _$RemoveMembersResponseFromJson(json); } /// Model response for [Channel.sendAction] api call @@ -301,86 +313,93 @@ class SendActionResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static SendActionResponse fromJson(Map? json) => - _$SendActionResponseFromJson(json!); + static SendActionResponse fromJson(Map json) => + _$SendActionResponseFromJson(json); } /// Model response for [Channel.addMembers] api call @JsonSerializable(createToJson: false) class AddMembersResponse extends _BaseResponse { /// Updated channel - ChannelModel? channel; + late ChannelModel channel; /// Channel members - List? members; + @JsonKey(defaultValue: []) + late List members; /// Message returned by the api call Message? message; /// Create a new instance from a json - static AddMembersResponse fromJson(Map? json) => - _$AddMembersResponseFromJson(json!); + static AddMembersResponse fromJson(Map json) => + _$AddMembersResponseFromJson(json); } /// Model response for [Channel.acceptInvite] api call @JsonSerializable(createToJson: false) class AcceptInviteResponse extends _BaseResponse { /// Updated channel - ChannelModel? channel; + late ChannelModel channel; /// Channel members - List? members; + @JsonKey(defaultValue: []) + late List members; /// Message returned by the api call Message? message; /// Create a new instance from a json - static AcceptInviteResponse fromJson(Map? json) => - _$AcceptInviteResponseFromJson(json!); + static AcceptInviteResponse fromJson(Map json) => + _$AcceptInviteResponseFromJson(json); } /// Model response for [Channel.rejectInvite] api call @JsonSerializable(createToJson: false) class RejectInviteResponse extends _BaseResponse { /// Updated channel - ChannelModel? channel; + late ChannelModel channel; /// Channel members - List? members; + @JsonKey(defaultValue: []) + late List members; /// Message returned by the api call Message? message; /// Create a new instance from a json - static RejectInviteResponse fromJson(Map? json) => - _$RejectInviteResponseFromJson(json!); + static RejectInviteResponse fromJson(Map json) => + _$RejectInviteResponseFromJson(json); } /// Model response for empty responses @JsonSerializable(createToJson: false) class EmptyResponse extends _BaseResponse { /// Create a new instance from a json - static EmptyResponse fromJson(Map? json) => - _$EmptyResponseFromJson(json!); + static EmptyResponse fromJson(Map json) => + _$EmptyResponseFromJson(json); } /// Model response for [Channel.query] api call @JsonSerializable(createToJson: false) class ChannelStateResponse extends _BaseResponse { /// Updated channel - ChannelModel? channel; + late ChannelModel channel; /// List of messages returned by the api call - List? messages; + @JsonKey(defaultValue: []) + late List messages; /// Channel members - List? members; + @JsonKey(defaultValue: []) + late List members; /// Number of users watching the channel - int? watcherCount; + @JsonKey(defaultValue: 0) + late int watcherCount; /// List of read states - List? read; + @JsonKey(defaultValue: []) + late List read; /// Create a new instance from a json static ChannelStateResponse fromJson(Map json) => diff --git a/packages/stream_chat/lib/src/api/responses.g.dart b/packages/stream_chat/lib/src/api/responses.g.dart index e1363d0c..2358593d 100644 --- a/packages/stream_chat/lib/src/api/responses.g.dart +++ b/packages/stream_chat/lib/src/api/responses.g.dart @@ -10,8 +10,9 @@ SyncResponse _$SyncResponseFromJson(Map json) { return SyncResponse() ..duration = json['duration'] as String? ..events = (json['events'] as List?) - ?.map((e) => Event.fromJson(e as Map)) - .toList(); + ?.map((e) => Event.fromJson(e as Map)) + .toList() ?? + []; } QueryChannelsResponse _$QueryChannelsResponseFromJson( @@ -19,33 +20,35 @@ QueryChannelsResponse _$QueryChannelsResponseFromJson( return QueryChannelsResponse() ..duration = json['duration'] as String? ..channels = (json['channels'] as List?) - ?.map((e) => ChannelState.fromJson(e as Map)) - .toList(); + ?.map((e) => ChannelState.fromJson(e as Map)) + .toList() ?? + []; } TranslateMessageResponse _$TranslateMessageResponseFromJson( Map json) { return TranslateMessageResponse() ..duration = json['duration'] as String? - ..message = json['message'] == null - ? null - : TranslatedMessage.fromJson(json['message'] as Map); + ..message = + TranslatedMessage.fromJson(json['message'] as Map); } QueryMembersResponse _$QueryMembersResponseFromJson(Map json) { return QueryMembersResponse() ..duration = json['duration'] as String? ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList(); + ?.map((e) => Member.fromJson(e as Map)) + .toList() ?? + []; } QueryUsersResponse _$QueryUsersResponseFromJson(Map json) { return QueryUsersResponse() ..duration = json['duration'] as String? ..users = (json['users'] as List?) - ?.map((e) => User.fromJson(e as Map)) - .toList(); + ?.map((e) => User.fromJson(e as Map)) + .toList() ?? + []; } QueryReactionsResponse _$QueryReactionsResponseFromJson( @@ -53,90 +56,82 @@ QueryReactionsResponse _$QueryReactionsResponseFromJson( return QueryReactionsResponse() ..duration = json['duration'] as String? ..reactions = (json['reactions'] as List?) - ?.map((e) => Reaction.fromJson(e as Map)) - .toList(); + ?.map((e) => Reaction.fromJson(e as Map)) + .toList() ?? + []; } QueryRepliesResponse _$QueryRepliesResponseFromJson(Map json) { return QueryRepliesResponse() ..duration = json['duration'] as String? ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList(); + ?.map((e) => Message.fromJson(e as Map)) + .toList() ?? + []; } ListDevicesResponse _$ListDevicesResponseFromJson(Map json) { return ListDevicesResponse() ..duration = json['duration'] as String? ..devices = (json['devices'] as List?) - ?.map((e) => Device.fromJson(e as Map)) - .toList(); + ?.map((e) => Device.fromJson(e as Map)) + .toList() ?? + []; } SendFileResponse _$SendFileResponseFromJson(Map json) { return SendFileResponse() ..duration = json['duration'] as String? - ..file = json['file'] as String?; + ..file = json['file'] as String; } SendImageResponse _$SendImageResponseFromJson(Map json) { return SendImageResponse() ..duration = json['duration'] as String? - ..file = json['file'] as String?; + ..file = json['file'] as String; } SendReactionResponse _$SendReactionResponseFromJson(Map json) { return SendReactionResponse() ..duration = json['duration'] as String? - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map) - ..reaction = json['reaction'] == null - ? null - : Reaction.fromJson(json['reaction'] as Map); + ..message = Message.fromJson(json['message'] as Map) + ..reaction = Reaction.fromJson(json['reaction'] as Map); } ConnectGuestUserResponse _$ConnectGuestUserResponseFromJson( Map json) { return ConnectGuestUserResponse() ..duration = json['duration'] as String? - ..accessToken = json['access_token'] as String? - ..user = json['user'] == null - ? null - : User.fromJson(json['user'] as Map); + ..accessToken = json['access_token'] as String + ..user = User.fromJson(json['user'] as Map); } UpdateUsersResponse _$UpdateUsersResponseFromJson(Map json) { return UpdateUsersResponse() ..duration = json['duration'] as String? ..users = (json['users'] as Map?)?.map( - (k, e) => MapEntry(k, User.fromJson(e as Map)), - ); + (k, e) => MapEntry(k, User.fromJson(e as Map)), + ) ?? + {}; } UpdateMessageResponse _$UpdateMessageResponseFromJson( Map json) { return UpdateMessageResponse() ..duration = json['duration'] as String? - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); + ..message = Message.fromJson(json['message'] as Map); } SendMessageResponse _$SendMessageResponseFromJson(Map json) { return SendMessageResponse() ..duration = json['duration'] as String? - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); + ..message = Message.fromJson(json['message'] as Map); } GetMessageResponse _$GetMessageResponseFromJson(Map json) { return GetMessageResponse() ..duration = json['duration'] as String? - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map) + ..message = Message.fromJson(json['message'] as Map) ..channel = json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map); @@ -147,8 +142,9 @@ SearchMessagesResponse _$SearchMessagesResponseFromJson( return SearchMessagesResponse() ..duration = json['duration'] as String? ..results = (json['results'] as List?) - ?.map((e) => GetMessageResponse.fromJson(e as Map)) - .toList(); + ?.map((e) => GetMessageResponse.fromJson(e as Map)) + .toList() ?? + []; } GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson( @@ -156,17 +152,16 @@ GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson( return GetMessagesByIdResponse() ..duration = json['duration'] as String? ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList(); + ?.map((e) => Message.fromJson(e as Map)) + .toList() ?? + []; } UpdateChannelResponse _$UpdateChannelResponseFromJson( Map json) { return UpdateChannelResponse() ..duration = json['duration'] as String? - ..channel = json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map) + ..channel = ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) ?.map((e) => Member.fromJson(e as Map)) .toList() @@ -179,9 +174,7 @@ PartialUpdateChannelResponse _$PartialUpdateChannelResponseFromJson( Map json) { return PartialUpdateChannelResponse() ..duration = json['duration'] as String? - ..channel = json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map) + ..channel = ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) ?.map((e) => Member.fromJson(e as Map)) .toList(); @@ -191,12 +184,11 @@ InviteMembersResponse _$InviteMembersResponseFromJson( Map json) { return InviteMembersResponse() ..duration = json['duration'] as String? - ..channel = json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map) + ..channel = ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() + ?.map((e) => Member.fromJson(e as Map)) + .toList() ?? + [] ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); @@ -206,12 +198,11 @@ RemoveMembersResponse _$RemoveMembersResponseFromJson( Map json) { return RemoveMembersResponse() ..duration = json['duration'] as String? - ..channel = json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map) + ..channel = ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() + ?.map((e) => Member.fromJson(e as Map)) + .toList() ?? + [] ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); @@ -228,12 +219,11 @@ SendActionResponse _$SendActionResponseFromJson(Map json) { AddMembersResponse _$AddMembersResponseFromJson(Map json) { return AddMembersResponse() ..duration = json['duration'] as String? - ..channel = json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map) + ..channel = ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() + ?.map((e) => Member.fromJson(e as Map)) + .toList() ?? + [] ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); @@ -242,12 +232,11 @@ AddMembersResponse _$AddMembersResponseFromJson(Map json) { AcceptInviteResponse _$AcceptInviteResponseFromJson(Map json) { return AcceptInviteResponse() ..duration = json['duration'] as String? - ..channel = json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map) + ..channel = ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() + ?.map((e) => Member.fromJson(e as Map)) + .toList() ?? + [] ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); @@ -256,12 +245,11 @@ AcceptInviteResponse _$AcceptInviteResponseFromJson(Map json) { RejectInviteResponse _$RejectInviteResponseFromJson(Map json) { return RejectInviteResponse() ..duration = json['duration'] as String? - ..channel = json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map) + ..channel = ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() + ?.map((e) => Member.fromJson(e as Map)) + .toList() ?? + [] ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); @@ -274,17 +262,18 @@ EmptyResponse _$EmptyResponseFromJson(Map json) { ChannelStateResponse _$ChannelStateResponseFromJson(Map json) { return ChannelStateResponse() ..duration = json['duration'] as String? - ..channel = json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map) + ..channel = ChannelModel.fromJson(json['channel'] as Map) ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList() + ?.map((e) => Message.fromJson(e as Map)) + .toList() ?? + [] ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() - ..watcherCount = json['watcher_count'] as int? + ?.map((e) => Member.fromJson(e as Map)) + .toList() ?? + [] + ..watcherCount = json['watcher_count'] as int? ?? 0 ..read = (json['read'] as List?) - ?.map((e) => Read.fromJson(e as Map)) - .toList(); + ?.map((e) => Read.fromJson(e as Map)) + .toList() ?? + []; } diff --git a/packages/stream_chat/lib/src/attachment_file_uploader.dart b/packages/stream_chat/lib/src/attachment_file_uploader.dart index 70976d40..7b90f0c9 100644 --- a/packages/stream_chat/lib/src/attachment_file_uploader.dart +++ b/packages/stream_chat/lib/src/attachment_file_uploader.dart @@ -1,8 +1,8 @@ import 'package:dio/dio.dart'; import 'package:stream_chat/src/api/responses.dart'; import 'package:stream_chat/src/client.dart'; -import 'package:stream_chat/src/models/attachment_file.dart'; import 'package:stream_chat/src/extensions/string_extension.dart'; +import 'package:stream_chat/src/models/attachment_file.dart'; /// Class responsible for uploading images and files to a given channel abstract class AttachmentFileUploader { @@ -13,8 +13,8 @@ abstract class AttachmentFileUploader { /// and cancel the request using [cancelToken] Future sendImage( AttachmentFile image, - String? channelId, - String? channelType, { + String channelId, + String channelType, { ProgressCallback? onSendProgress, CancelToken? cancelToken, }); @@ -26,8 +26,8 @@ abstract class AttachmentFileUploader { /// and cancel the request using [cancelToken] Future sendFile( AttachmentFile file, - String? channelId, - String? channelType, { + String channelId, + String channelType, { ProgressCallback? onSendProgress, CancelToken? cancelToken, }); @@ -38,8 +38,8 @@ abstract class AttachmentFileUploader { /// Optionally, cancel the request using [cancelToken] Future deleteImage( String url, - String? channelId, - String? channelType, { + String channelId, + String channelType, { CancelToken? cancelToken, }); @@ -49,8 +49,8 @@ abstract class AttachmentFileUploader { /// Optionally, cancel the request using [cancelToken] Future deleteFile( String url, - String? channelId, - String? channelType, { + String channelId, + String channelType, { CancelToken? cancelToken, }); } @@ -65,8 +65,8 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { @override Future sendImage( AttachmentFile file, - String? channelId, - String? channelType, { + String channelId, + String channelType, { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) async { @@ -102,8 +102,8 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { @override Future sendFile( AttachmentFile file, - String? channelId, - String? channelType, { + String channelId, + String channelType, { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) async { @@ -139,8 +139,8 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { @override Future deleteImage( String url, - String? channelId, - String? channelType, { + String channelId, + String channelType, { CancelToken? cancelToken, }) async { final response = await _client.delete( @@ -154,8 +154,8 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { @override Future deleteFile( String url, - String? channelId, - String? channelType, { + String channelId, + String channelType, { CancelToken? cancelToken, }) async { final response = await _client.delete( diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 46eb21af..fa007456 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -82,7 +82,7 @@ class StreamChatClient { this.tokenProvider, this.baseURL = _defaultBaseURL, this.logLevel = Level.WARNING, - this.logHandlerFunction, + LogHandlerFunction? logHandlerFunction, Duration connectTimeout = const Duration(seconds: 6), Duration receiveTimeout = const Duration(seconds: 6), Dio? httpClient, @@ -103,7 +103,7 @@ class StreamChatClient { state = ClientState(this); - _setupLogger(); + _setupLogger(logHandlerFunction); _setupDio(httpClient, receiveTimeout, connectTimeout); logger.info('instantiating new client'); @@ -134,7 +134,7 @@ class StreamChatClient { RetryPolicy? get retryPolicy => _retryPolicy; /// This client state - late final ClientState state; + late ClientState state; /// By default the Chat client will write all messages with level Warn or /// Error to stdout. @@ -170,7 +170,7 @@ class StreamChatClient { /// final client = StreamChatClient("stream-chat-api-key", /// logHandlerFunction: myLogHandlerFunction); ///``` - LogHandlerFunction? logHandlerFunction; + late LogHandlerFunction logHandlerFunction; /// Your project Stream Chat api key. /// Find your API keys here https://getstream.io/dashboard/ @@ -371,14 +371,14 @@ class StreamChatClient { ) => Logger.detached(name) ..level = logLevel - ..onRecord.listen(logHandlerFunction ?? _getDefaultLogHandler()); + ..onRecord.listen(logHandlerFunction); - void _setupLogger() { + void _setupLogger(LogHandlerFunction? logHandlerFunction) { logger.level = logLevel; - logHandlerFunction ??= _getDefaultLogHandler(); + this.logHandlerFunction = logHandlerFunction ?? _getDefaultLogHandler(); - logger.onRecord.listen(logHandlerFunction); + logger.onRecord.listen(this.logHandlerFunction); logger.info('logger setup'); } @@ -618,15 +618,15 @@ class StreamChatClient { SyncResponse.fromJson, ); - res.events!.sort((a, b) => a.createdAt!.compareTo(b.createdAt!)); + res.events.sort((a, b) => a.createdAt!.compareTo(b.createdAt!)); - res.events!.forEach((element) { + res.events.forEach((element) { logger ..fine('element.type: ${element.type}') ..fine('element.message.text: ${element.message?.text}'); }); - res.events!.forEach(handleEvent); + res.events.forEach(handleEvent); await _chatPersistenceClient?.updateLastSyncAt(DateTime.now()); _synced = true; @@ -740,7 +740,7 @@ class StreamChatClient { QueryChannelsResponse.fromJson, ); - if ((res.channels ?? []).isEmpty && paginationParams.offset == 0) { + if (res.channels.isEmpty && paginationParams.offset == 0) { logger.warning( ''' We could not find any channel for this query. @@ -750,7 +750,7 @@ class StreamChatClient { return []; } - final channels = res.channels!; + final channels = res.channels; final users = channels .expand((it) => it.members) @@ -759,7 +759,7 @@ class StreamChatClient { state._updateUsers(users); - logger.info('Got ${res.channels?.length} channels from api'); + logger.info('Got ${res.channels.length} channels from api'); final updateData = _mapChannelStateToChannel(channels); @@ -1053,7 +1053,7 @@ class StreamChatClient { QueryUsersResponse.fromJson, ); - state._updateUsers(response.users!); + state._updateUsers(response.users); return response; } @@ -1100,8 +1100,8 @@ class StreamChatClient { /// Send a [file] to the [channelId] of type [channelType] Future sendFile( AttachmentFile file, - String? channelId, - String? channelType, { + String channelId, + String channelType, { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) => @@ -1116,8 +1116,8 @@ class StreamChatClient { /// Send a [image] to the [channelId] of type [channelType] Future sendImage( AttachmentFile image, - String? channelId, - String? channelType, { + String channelId, + String channelType, { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) => @@ -1132,8 +1132,8 @@ class StreamChatClient { /// Delete a file from this channel Future deleteFile( String url, - String? channelId, - String? channelType, { + String channelId, + String channelType, { CancelToken? cancelToken, }) => attachmentFileUploader!.deleteFile( @@ -1146,8 +1146,8 @@ class StreamChatClient { /// Delete an image from this channel Future deleteImage( String url, - String? channelId, - String? channelType, { + String channelId, + String channelType, { CancelToken? cancelToken, }) => attachmentFileUploader!.deleteImage( @@ -1327,7 +1327,10 @@ class StreamChatClient { /// Sends the message to the given channel Future sendMessage( - Message message, String? channelId, String? channelType) async { + Message message, + String channelId, + String channelType, + ) async { final response = await post( '/channels/$channelType/$channelId/message', data: {'message': message.toJson()}, diff --git a/packages/stream_chat/lib/src/models/action.dart b/packages/stream_chat/lib/src/models/action.dart index b91f1e13..16a307e7 100644 --- a/packages/stream_chat/lib/src/models/action.dart +++ b/packages/stream_chat/lib/src/models/action.dart @@ -6,22 +6,29 @@ part 'action.g.dart'; @JsonSerializable() class Action { /// Constructor used for json serialization - Action({this.name, this.style, this.text, this.type, this.value}); + Action({ + required this.name, + this.style = 'default', + required this.text, + required this.type, + this.value, + }); /// Create a new instance from a json factory Action.fromJson(Map json) => _$ActionFromJson(json); /// The name of the action - final String? name; + final String name; /// The style of the action - final String? style; + @JsonKey(defaultValue: 'default') + final String style; /// The test of the action - final String? text; + final String text; /// The type of the action - final String? type; + final String type; /// The value of the action final String? value; diff --git a/packages/stream_chat/lib/src/models/action.g.dart b/packages/stream_chat/lib/src/models/action.g.dart index 770298c7..9ae999ed 100644 --- a/packages/stream_chat/lib/src/models/action.g.dart +++ b/packages/stream_chat/lib/src/models/action.g.dart @@ -8,10 +8,10 @@ part of 'action.dart'; Action _$ActionFromJson(Map json) { return Action( - name: json['name'] as String?, - style: json['style'] as String?, - text: json['text'] as String?, - type: json['type'] as String?, + name: json['name'] as String, + style: json['style'] as String? ?? 'default', + text: json['text'] as String, + type: json['type'] as String, value: json['value'] as String?, ); } diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/models/attachment.dart index 877c292d..fd91454a 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/models/attachment.dart @@ -15,7 +15,7 @@ class Attachment extends Equatable { /// Constructor used for json serialization Attachment({ String? id, - String? type, + this.type, this.titleLink, String? title, this.thumbUrl, @@ -32,14 +32,14 @@ class Attachment extends Equatable { this.authorLink, this.authorIcon, this.assetUrl, - this.actions, + List? actions, this.extraData, this.file, UploadState? uploadState, }) : id = id ?? const Uuid().v4(), title = title ?? file?.name, - type = type ?? '', - localUri = file?.path != null ? Uri.parse(file!.path!) : null { + localUri = file?.path != null ? Uri.parse(file!.path!) : null, + actions = actions ?? [] { this.uploadState = uploadState ?? ((assetUrl != null || imageUrl != null) ? const UploadState.success() @@ -58,7 +58,7 @@ class Attachment extends Equatable { ///The attachment type based on the URL resource. This can be: audio, ///image or video - final String type; + final String? type; ///The link to which the attachment message points to. final String? titleLink; @@ -98,7 +98,8 @@ class Attachment extends Equatable { final String? assetUrl; /// Actions from a command - final List? actions; + @JsonKey(defaultValue: []) + final List actions; final Uri? localUri; @@ -106,7 +107,7 @@ class Attachment extends Equatable { final AttachmentFile? file; /// The current upload state of the attachment - late final UploadState? uploadState; + late final UploadState uploadState; /// Map of custom channel extraData @JsonKey(includeIfNull: false) @@ -149,13 +150,13 @@ class Attachment extends Equatable { ]; /// Serialize to json - Map toJson() => Serialization.moveFromExtraDataToRoot( - _$AttachmentToJson(this), topLevelFields) - ..removeWhere((key, value) => dbSpecificTopLevelFields.contains(key)); + Map toJson() => + Serialization.moveFromExtraDataToRoot(_$AttachmentToJson(this)) + ..removeWhere((key, value) => dbSpecificTopLevelFields.contains(key)); /// Serialize to db data - Map toData() => Serialization.moveFromExtraDataToRoot( - _$AttachmentToJson(this), topLevelFields + dbSpecificTopLevelFields); + Map toData() => + Serialization.moveFromExtraDataToRoot(_$AttachmentToJson(this)); Attachment copyWith({ String? id, diff --git a/packages/stream_chat/lib/src/models/attachment.g.dart b/packages/stream_chat/lib/src/models/attachment.g.dart index acee04fa..b7a7304d 100644 --- a/packages/stream_chat/lib/src/models/attachment.g.dart +++ b/packages/stream_chat/lib/src/models/attachment.g.dart @@ -27,8 +27,9 @@ Attachment _$AttachmentFromJson(Map json) { authorIcon: json['author_icon'] as String?, assetUrl: json['asset_url'] as String?, actions: (json['actions'] as List?) - ?.map((e) => Action.fromJson(e as Map)) - .toList(), + ?.map((e) => Action.fromJson(e as Map)) + .toList() ?? + [], extraData: json['extra_data'] as Map?, file: json['file'] == null ? null @@ -40,9 +41,7 @@ Attachment _$AttachmentFromJson(Map json) { } Map _$AttachmentToJson(Attachment instance) { - final val = { - 'type': instance.type, - }; + final val = {}; void writeNotNull(String key, dynamic value) { if (value != null) { @@ -50,6 +49,7 @@ Map _$AttachmentToJson(Attachment instance) { } } + writeNotNull('type', instance.type); writeNotNull('title_link', instance.titleLink); writeNotNull('title', instance.title); writeNotNull('thumb_url', instance.thumbUrl); @@ -66,9 +66,9 @@ Map _$AttachmentToJson(Attachment instance) { writeNotNull('author_link', instance.authorLink); writeNotNull('author_icon', instance.authorIcon); writeNotNull('asset_url', instance.assetUrl); - writeNotNull('actions', instance.actions?.map((e) => e.toJson()).toList()); + val['actions'] = instance.actions.map((e) => e.toJson()).toList(); writeNotNull('file', instance.file?.toJson()); - writeNotNull('upload_state', instance.uploadState?.toJson()); + val['upload_state'] = instance.uploadState.toJson(); writeNotNull('extra_data', instance.extraData); val['id'] = instance.id; return val; diff --git a/packages/stream_chat/lib/src/models/attachment_file.dart b/packages/stream_chat/lib/src/models/attachment_file.dart index fa74d801..d72cecbd 100644 --- a/packages/stream_chat/lib/src/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/models/attachment_file.dart @@ -4,7 +4,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:meta/meta.dart'; part 'attachment_file.freezed.dart'; - part 'attachment_file.g.dart'; /// Union class to hold various [UploadState] of a attachment. @@ -14,8 +13,10 @@ class UploadState with _$UploadState { const factory UploadState.preparing() = Preparing; /// InProgress state of the union - const factory UploadState.inProgress( - {required int uploaded, required int total}) = InProgress; + const factory UploadState.inProgress({ + required int uploaded, + required int total, + }) = InProgress; /// Success state of the union const factory UploadState.success() = Success; @@ -62,7 +63,10 @@ class AttachmentFile { this.name, this.bytes, this.size, - }); + }) : assert( + path != null || bytes != null, + 'Either path or bytes should be != null', + ); /// Create a new instance from a json factory AttachmentFile.fromJson(Map json) => diff --git a/packages/stream_chat/lib/src/models/channel_config.dart b/packages/stream_chat/lib/src/models/channel_config.dart index 1500aaf2..9ba36e4f 100644 --- a/packages/stream_chat/lib/src/models/channel_config.dart +++ b/packages/stream_chat/lib/src/models/channel_config.dart @@ -1,5 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/models/command.dart'; + part 'channel_config.g.dart'; /// The class that contains the information about the configuration of a channel @@ -7,75 +8,85 @@ part 'channel_config.g.dart'; class ChannelConfig { /// Constructor used for json serialization ChannelConfig({ - this.automod, - this.commands, - this.connectEvents, - this.createdAt, - this.updatedAt, - this.maxMessageLength, - this.messageRetention, - this.mutes, - this.name, - this.reactions, - this.readEvents, - this.replies, - this.search, - this.typingEvents, - this.uploads, - this.urlEnrichment, - }); + this.automod = 'flag', + this.commands = const [], + this.connectEvents = false, + DateTime? createdAt, + DateTime? updatedAt, + this.maxMessageLength = 0, + this.messageRetention = '', + this.mutes = false, + this.reactions = false, + this.readEvents = false, + this.replies = false, + this.search = false, + this.typingEvents = false, + this.uploads = false, + this.urlEnrichment = false, + }) : createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json factory ChannelConfig.fromJson(Map json) => _$ChannelConfigFromJson(json); /// Moderation configuration - final String? automod; + @JsonKey(defaultValue: 'flag') + final String automod; /// List of available commands - final List? commands; + @JsonKey(defaultValue: []) + final List commands; /// True if the channel should send connect events - final bool? connectEvents; + @JsonKey(defaultValue: false) + final bool connectEvents; /// Date of channel creation - final DateTime? createdAt; + final DateTime createdAt; /// Date of last channel update - final DateTime? updatedAt; + final DateTime updatedAt; /// Max channel message length - final int? maxMessageLength; + @JsonKey(defaultValue: 0) + final int maxMessageLength; /// Duration of message retention - final String? messageRetention; + @JsonKey(defaultValue: '') + final String messageRetention; /// True if users can be muted - final bool? mutes; - - /// Name of the channel - final String? name; + @JsonKey(defaultValue: false) + final bool mutes; /// True if reaction are active for this channel - final bool? reactions; + @JsonKey(defaultValue: false) + final bool reactions; /// True if readEvents are active for this channel - final bool? readEvents; + @JsonKey(defaultValue: false) + final bool readEvents; /// True if reply message are active for this channel - final bool? replies; + @JsonKey(defaultValue: false) + final bool replies; /// True if it's possible to perform a search in this channel - final bool? search; + @JsonKey(defaultValue: false) + final bool search; /// True if typing events should be sent for this channel - final bool? typingEvents; + @JsonKey(defaultValue: false) + final bool typingEvents; /// True if it's possible to upload files to this channel - final bool? uploads; + @JsonKey(defaultValue: false) + final bool uploads; /// True if urls appears as attachments - final bool? urlEnrichment; + @JsonKey(defaultValue: false) + final bool urlEnrichment; /// Serialize to json Map toJson() => _$ChannelConfigToJson(this); diff --git a/packages/stream_chat/lib/src/models/channel_config.g.dart b/packages/stream_chat/lib/src/models/channel_config.g.dart index 41cd0cd2..723281c0 100644 --- a/packages/stream_chat/lib/src/models/channel_config.g.dart +++ b/packages/stream_chat/lib/src/models/channel_config.g.dart @@ -8,42 +8,41 @@ part of 'channel_config.dart'; ChannelConfig _$ChannelConfigFromJson(Map json) { return ChannelConfig( - automod: json['automod'] as String?, + automod: json['automod'] as String? ?? 'flag', commands: (json['commands'] as List?) - ?.map((e) => Command.fromJson(e as Map)) - .toList(), - connectEvents: json['connect_events'] as bool?, + ?.map((e) => Command.fromJson(e as Map)) + .toList() ?? + [], + connectEvents: json['connect_events'] as bool? ?? false, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), - maxMessageLength: json['max_message_length'] as int?, - messageRetention: json['message_retention'] as String?, - mutes: json['mutes'] as bool?, - name: json['name'] as String?, - reactions: json['reactions'] as bool?, - readEvents: json['read_events'] as bool?, - replies: json['replies'] as bool?, - search: json['search'] as bool?, - typingEvents: json['typing_events'] as bool?, - uploads: json['uploads'] as bool?, - urlEnrichment: json['url_enrichment'] as bool?, + maxMessageLength: json['max_message_length'] as int? ?? 0, + messageRetention: json['message_retention'] as String? ?? '', + mutes: json['mutes'] as bool? ?? false, + reactions: json['reactions'] as bool? ?? false, + readEvents: json['read_events'] as bool? ?? false, + replies: json['replies'] as bool? ?? false, + search: json['search'] as bool? ?? false, + typingEvents: json['typing_events'] as bool? ?? false, + uploads: json['uploads'] as bool? ?? false, + urlEnrichment: json['url_enrichment'] as bool? ?? false, ); } Map _$ChannelConfigToJson(ChannelConfig instance) => { 'automod': instance.automod, - 'commands': instance.commands?.map((e) => e.toJson()).toList(), + 'commands': instance.commands.map((e) => e.toJson()).toList(), 'connect_events': instance.connectEvents, - 'created_at': instance.createdAt?.toIso8601String(), - 'updated_at': instance.updatedAt?.toIso8601String(), + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), 'max_message_length': instance.maxMessageLength, 'message_retention': instance.messageRetention, 'mutes': instance.mutes, - 'name': instance.name, 'reactions': instance.reactions, 'read_events': instance.readEvents, 'replies': instance.replies, diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index edd373ae..d68edbde 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -10,9 +10,9 @@ part 'channel_model.g.dart'; class ChannelModel { /// Constructor used for json serialization ChannelModel({ - this.id, - this.type, - this.cid = '', + String? id, + String? type, + String? cid, ChannelConfig? config, this.createdBy, this.frozen = false, @@ -25,7 +25,14 @@ class ChannelModel { this.team, }) : config = config ?? ChannelConfig(), createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + updatedAt = updatedAt ?? DateTime.now(), + assert( + cid != null || (id != null && type != null), + 'provide either a cid or an id and type', + ), + id = id ?? cid!.split(':')[1], + type = type ?? cid!.split(':')[0], + cid = cid ?? '$type:$id'; /// Create a new instance from a json factory ChannelModel.fromJson(Map json) => @@ -33,10 +40,10 @@ class ChannelModel { Serialization.moveToExtraDataFromRoot(json, topLevelFields)); /// The id of this channel - final String? id; + final String id; /// The type of this channel - final String? type; + final String type; /// The cid of this channel @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @@ -107,7 +114,6 @@ class ChannelModel { /// Serialize to json Map toJson() => Serialization.moveFromExtraDataToRoot( _$ChannelModelToJson(this), - topLevelFields, ); /// Creates a copy of [ChannelModel] with specified attributes overridden. diff --git a/packages/stream_chat/lib/src/models/channel_model.g.dart b/packages/stream_chat/lib/src/models/channel_model.g.dart index 58a9b61e..e71112b6 100644 --- a/packages/stream_chat/lib/src/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/models/channel_model.g.dart @@ -10,7 +10,7 @@ ChannelModel _$ChannelModelFromJson(Map json) { return ChannelModel( id: json['id'] as String?, type: json['type'] as String?, - cid: json['cid'] as String, + cid: json['cid'] as String?, config: json['config'] == null ? null : ChannelConfig.fromJson(json['config'] as Map), diff --git a/packages/stream_chat/lib/src/models/command.dart b/packages/stream_chat/lib/src/models/command.dart index c8df72fd..5ba0043c 100644 --- a/packages/stream_chat/lib/src/models/command.dart +++ b/packages/stream_chat/lib/src/models/command.dart @@ -7,9 +7,9 @@ part 'command.g.dart'; class Command { /// Constructor used for json serialization Command({ - this.name, - this.description, - this.args, + required this.name, + required this.description, + required this.args, }); /// Create a new instance from a json @@ -17,13 +17,13 @@ class Command { _$CommandFromJson(json); /// The name of the command - final String? name; + final String name; /// The description explaining the command - final String? description; + final String description; /// The arguments of the command - final String? args; + final String args; /// Serialize to json Map toJson() => _$CommandToJson(this); diff --git a/packages/stream_chat/lib/src/models/command.g.dart b/packages/stream_chat/lib/src/models/command.g.dart index 1ce00415..cf8be971 100644 --- a/packages/stream_chat/lib/src/models/command.g.dart +++ b/packages/stream_chat/lib/src/models/command.g.dart @@ -8,9 +8,9 @@ part of 'command.dart'; Command _$CommandFromJson(Map json) { return Command( - name: json['name'] as String?, - description: json['description'] as String?, - args: json['args'] as String?, + name: json['name'] as String, + description: json['description'] as String, + args: json['args'] as String, ); } diff --git a/packages/stream_chat/lib/src/models/device.dart b/packages/stream_chat/lib/src/models/device.dart index 24dcb678..5dc98d25 100644 --- a/packages/stream_chat/lib/src/models/device.dart +++ b/packages/stream_chat/lib/src/models/device.dart @@ -7,8 +7,8 @@ part 'device.g.dart'; class Device { /// Constructor used for json serialization Device({ - this.id = '', - this.pushProvider, + required this.id, + required this.pushProvider, }); /// Create a new instance from a json @@ -18,7 +18,7 @@ class Device { final String id; /// The notification push provider - final String? pushProvider; + final String pushProvider; /// Serialize to json Map toJson() => _$DeviceToJson(this); diff --git a/packages/stream_chat/lib/src/models/device.g.dart b/packages/stream_chat/lib/src/models/device.g.dart index fe65053b..5fcd9435 100644 --- a/packages/stream_chat/lib/src/models/device.g.dart +++ b/packages/stream_chat/lib/src/models/device.g.dart @@ -9,7 +9,7 @@ part of 'device.dart'; Device _$DeviceFromJson(Map json) { return Device( id: json['id'] as String, - pushProvider: json['push_provider'] as String?, + pushProvider: json['push_provider'] as String, ); } diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index 621464b6..52092656 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -119,7 +119,6 @@ class Event { /// Serialize to json Map toJson() => Serialization.moveFromExtraDataToRoot( _$EventToJson(this), - topLevelFields, ); /// Creates a copy of [Event] with specified attributes overridden. @@ -217,6 +216,5 @@ class EventChannel extends ChannelModel { @override Map toJson() => Serialization.moveFromExtraDataToRoot( _$EventChannelToJson(this), - topLevelFields, ); } diff --git a/packages/stream_chat/lib/src/models/member.dart b/packages/stream_chat/lib/src/models/member.dart index ad5f64c8..49df170c 100644 --- a/packages/stream_chat/lib/src/models/member.dart +++ b/packages/stream_chat/lib/src/models/member.dart @@ -13,9 +13,9 @@ class Member { this.inviteAcceptedAt, this.inviteRejectedAt, this.invited = false, - this.role = '', + this.role, this.userId, - this.isModerator, + this.isModerator = false, DateTime? createdAt, DateTime? updatedAt, this.banned = false, @@ -45,14 +45,14 @@ class Member { final bool invited; /// The role of the user in the channel - @JsonKey(defaultValue: '') - final String role; + final String? role; /// The id of the interested user final String? userId; /// True if the user is a moderator of the channel - final bool? isModerator; + @JsonKey(defaultValue: false) + final bool isModerator; /// True if the member is banned from the channel @JsonKey(defaultValue: false) diff --git a/packages/stream_chat/lib/src/models/member.g.dart b/packages/stream_chat/lib/src/models/member.g.dart index 091cb109..a75b458d 100644 --- a/packages/stream_chat/lib/src/models/member.g.dart +++ b/packages/stream_chat/lib/src/models/member.g.dart @@ -18,9 +18,9 @@ Member _$MemberFromJson(Map json) { ? null : DateTime.parse(json['invite_rejected_at'] as String), invited: json['invited'] as bool? ?? false, - role: json['role'] as String? ?? '', + role: json['role'] as String?, userId: json['user_id'] as String?, - isModerator: json['is_moderator'] as bool?, + isModerator: json['is_moderator'] as bool? ?? false, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index b25662c0..03a162c5 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -46,12 +46,12 @@ class Message extends Equatable { /// Constructor used for json serialization Message({ String? id, - this.text = '', - this.type = '', - this.attachments, - this.mentionedUsers, - this.silent, - this.shadowed, + this.text, + this.type = 'regular', + this.attachments = const [], + this.mentionedUsers = const [], + this.silent = false, + this.shadowed = false, this.reactionCounts, this.reactionScores, this.latestReactions, @@ -70,10 +70,10 @@ class Message extends Equatable { this.pinnedAt, DateTime? pinExpires, this.pinnedBy, - this.extraData, + this.extraData = const {}, this.deletedAt, this.status = MessageSendingStatus.sent, - this.skipPush, + this.skipPush = false, }) : id = id ?? const Uuid().v4(), pinExpires = pinExpires?.toUtc(), createdAt = createdAt ?? DateTime.now(), @@ -88,24 +88,34 @@ class Message extends Equatable { final String id; /// The text of this message - final String text; + final String? text; /// The status of a sending message @JsonKey(ignore: true) final MessageSendingStatus status; /// The message type - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey( + includeIfNull: false, + toJson: Serialization.readOnly, + defaultValue: 'regular', + ) final String type; /// The list of attachments, either provided by the user or generated from a /// command or as a result of URL scraping. - @JsonKey(includeIfNull: false) - final List? attachments; + @JsonKey( + includeIfNull: false, + defaultValue: [], + ) + final List attachments; /// The list of user mentioned in the message - @JsonKey(toJson: Serialization.userIds) - final List? mentionedUsers; + @JsonKey( + toJson: Serialization.userIds, + defaultValue: [], + ) + final List mentionedUsers; /// A map describing the count of number of every reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @@ -145,14 +155,20 @@ class Message extends Equatable { final bool? showInChannel; /// If true the message is silent - final bool? silent; + @JsonKey(defaultValue: false) + final bool silent; /// If true the message will not send a push notification - final bool? skipPush; + @JsonKey(defaultValue: false) + final bool skipPush; /// If true the message is shadowed - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final bool? shadowed; + @JsonKey( + includeIfNull: false, + toJson: Serialization.readOnly, + defaultValue: false, + ) + final bool shadowed; /// A used command name. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @@ -171,7 +187,8 @@ class Message extends Equatable { final User? user; /// If true the message is pinned - final bool? pinned; + @JsonKey(defaultValue: false) + final bool pinned; /// Reserved field indicating when the message was pinned @JsonKey(toJson: Serialization.readOnly) @@ -187,8 +204,11 @@ class Message extends Equatable { final User? pinnedBy; /// Message custom extraData - @JsonKey(includeIfNull: false) - final Map? extraData; + @JsonKey( + includeIfNull: false, + defaultValue: {}, + ) + final Map extraData; /// True if the message is a system info bool get isSystem => type == 'system'; @@ -238,7 +258,8 @@ class Message extends Equatable { /// Serialize to json Map toJson() => Serialization.moveFromExtraDataToRoot( - _$MessageToJson(this), topLevelFields); + _$MessageToJson(this), + ); /// Creates a copy of [Message] with specified attributes overridden. Message copyWith({ @@ -408,6 +429,5 @@ class TranslatedMessage extends Message { @override Map toJson() => Serialization.moveFromExtraDataToRoot( _$TranslatedMessageToJson(this), - topLevelFields, ); } diff --git a/packages/stream_chat/lib/src/models/message.g.dart b/packages/stream_chat/lib/src/models/message.g.dart index 9dfea3c4..ca094a83 100644 --- a/packages/stream_chat/lib/src/models/message.g.dart +++ b/packages/stream_chat/lib/src/models/message.g.dart @@ -9,16 +9,18 @@ part of 'message.dart'; Message _$MessageFromJson(Map json) { return Message( id: json['id'] as String?, - text: json['text'] as String, - type: json['type'] as String, + text: json['text'] as String?, + type: json['type'] as String? ?? 'regular', attachments: (json['attachments'] as List?) - ?.map((e) => Attachment.fromJson(e as Map)) - .toList(), + ?.map((e) => Attachment.fromJson(e as Map)) + .toList() ?? + [], mentionedUsers: (json['mentioned_users'] as List?) - ?.map((e) => User.fromJson(e as Map)) - .toList(), - silent: json['silent'] as bool?, - shadowed: json['shadowed'] as bool?, + ?.map((e) => User.fromJson(e as Map)) + .toList() ?? + [], + silent: json['silent'] as bool? ?? false, + shadowed: json['shadowed'] as bool? ?? false, reactionCounts: (json['reaction_counts'] as Map?)?.map( (k, e) => MapEntry(k, e as int), ), @@ -51,7 +53,7 @@ Message _$MessageFromJson(Map json) { user: json['user'] == null ? null : User.fromJson(json['user'] as Map), - pinned: json['pinned'] as bool?, + pinned: json['pinned'] as bool? ?? false, pinnedAt: json['pinned_at'] == null ? null : DateTime.parse(json['pinned_at'] as String), @@ -61,11 +63,11 @@ Message _$MessageFromJson(Map json) { pinnedBy: json['pinned_by'] == null ? null : User.fromJson(json['pinned_by'] as Map), - extraData: json['extra_data'] as Map?, + extraData: json['extra_data'] as Map? ?? {}, deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), - skipPush: json['skip_push'] as bool?, + skipPush: json['skip_push'] as bool? ?? false, ); } @@ -82,8 +84,7 @@ Map _$MessageToJson(Message instance) { } writeNotNull('type', readonly(instance.type)); - writeNotNull( - 'attachments', instance.attachments?.map((e) => e.toJson()).toList()); + val['attachments'] = instance.attachments.map((e) => e.toJson()).toList(); val['mentioned_users'] = Serialization.userIds(instance.mentionedUsers); writeNotNull('reaction_counts', readonly(instance.reactionCounts)); writeNotNull('reaction_scores', readonly(instance.reactionScores)); @@ -106,7 +107,7 @@ Map _$MessageToJson(Message instance) { val['pinned_at'] = readonly(instance.pinnedAt); val['pin_expires'] = instance.pinExpires?.toIso8601String(); val['pinned_by'] = readonly(instance.pinnedBy); - writeNotNull('extra_data', instance.extraData); + val['extra_data'] = instance.extraData; writeNotNull('deleted_at', readonly(instance.deletedAt)); return val; } diff --git a/packages/stream_chat/lib/src/models/mute.dart b/packages/stream_chat/lib/src/models/mute.dart index 65300d4a..3ba26230 100644 --- a/packages/stream_chat/lib/src/models/mute.dart +++ b/packages/stream_chat/lib/src/models/mute.dart @@ -9,26 +9,31 @@ part 'mute.g.dart'; @JsonSerializable() class Mute { /// Constructor used for json serialization - Mute({this.user, this.channel, this.createdAt, this.updatedAt}); + Mute({ + required this.user, + required this.channel, + required this.createdAt, + required this.updatedAt, + }); /// Create a new instance from a json factory Mute.fromJson(Map json) => _$MuteFromJson(json); /// The user that performed the muting action @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final User? user; + final User user; /// The target user @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final ChannelModel? channel; + final ChannelModel channel; /// The date in which the use was muted @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime? createdAt; + final DateTime createdAt; /// The date of the last update @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final DateTime? updatedAt; + final DateTime updatedAt; /// Serialize to json Map toJson() => _$MuteToJson(this); diff --git a/packages/stream_chat/lib/src/models/mute.g.dart b/packages/stream_chat/lib/src/models/mute.g.dart index 652a1bc3..e77b8707 100644 --- a/packages/stream_chat/lib/src/models/mute.g.dart +++ b/packages/stream_chat/lib/src/models/mute.g.dart @@ -8,18 +8,10 @@ part of 'mute.dart'; Mute _$MuteFromJson(Map json) { return Mute( - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), + user: User.fromJson(json['user'] as Map), + channel: ChannelModel.fromJson(json['channel'] as Map), + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), ); } diff --git a/packages/stream_chat/lib/src/models/own_user.dart b/packages/stream_chat/lib/src/models/own_user.dart index d22f706e..3ce951cf 100644 --- a/packages/stream_chat/lib/src/models/own_user.dart +++ b/packages/stream_chat/lib/src/models/own_user.dart @@ -84,5 +84,6 @@ class OwnUser extends User { /// Serialize to json @override Map toJson() => Serialization.moveFromExtraDataToRoot( - _$OwnUserToJson(this), topLevelFields); + _$OwnUserToJson(this), + ); } diff --git a/packages/stream_chat/lib/src/models/reaction.dart b/packages/stream_chat/lib/src/models/reaction.dart index 767f1057..eefeaebe 100644 --- a/packages/stream_chat/lib/src/models/reaction.dart +++ b/packages/stream_chat/lib/src/models/reaction.dart @@ -11,7 +11,7 @@ class Reaction { Reaction({ this.messageId, DateTime? createdAt, - this.type = '', + required this.type, required this.user, String? userId, this.score = 0, @@ -64,7 +64,8 @@ class Reaction { /// Serialize to json Map toJson() => Serialization.moveFromExtraDataToRoot( - _$ReactionToJson(this), topLevelFields); + _$ReactionToJson(this), + ); /// Creates a copy of [Reaction] with specified attributes overridden. Reaction copyWith({ diff --git a/packages/stream_chat/lib/src/models/read.dart b/packages/stream_chat/lib/src/models/read.dart index ef20eb6e..cbd47dd1 100644 --- a/packages/stream_chat/lib/src/models/read.dart +++ b/packages/stream_chat/lib/src/models/read.dart @@ -10,7 +10,7 @@ class Read { Read({ required this.lastRead, required this.user, - required this.unreadMessages, + this.unreadMessages = 0, }); /// Create a new instance from a json diff --git a/packages/stream_chat/lib/src/models/serialization.dart b/packages/stream_chat/lib/src/models/serialization.dart index 6cd4cfbd..d584912e 100644 --- a/packages/stream_chat/lib/src/models/serialization.dart +++ b/packages/stream_chat/lib/src/models/serialization.dart @@ -10,7 +10,7 @@ class Serialization { static const Function readOnly = readonly; /// List of users to list of userIds - static List? userIds(List? users) => + static List? userIds(List? users) => users?.map((u) => u.id).toList(); /// Takes unknown json keys and puts them in the `extra_data` key @@ -36,7 +36,6 @@ class Serialization { /// the json map static Map moveFromExtraDataToRoot( Map json, - List topLevelFields, ) { final jsonClone = Map.from(json); return jsonClone diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/models/user.dart index 224e0f20..67fc99f0 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/models/user.dart @@ -105,8 +105,9 @@ class User { other is User && runtimeType == other.runtimeType && id == other.id; /// Serialize to json - Map toJson() => - Serialization.moveFromExtraDataToRoot(_$UserToJson(this), topLevelFields); + Map toJson() => Serialization.moveFromExtraDataToRoot( + _$UserToJson(this), + ); /// Creates a copy of [User] with specified attributes overridden. User copyWith({ diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 4717f524..e0a29cc8 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:dio/dio.dart'; import 'package:dio/native_imp.dart'; import 'package:mocktail/mocktail.dart'; @@ -6,11 +8,10 @@ import 'package:stream_chat/src/client.dart'; import 'package:stream_chat/src/event_type.dart'; import 'package:stream_chat/src/models/event.dart'; import 'package:stream_chat/src/models/message.dart'; -import 'package:stream_chat/src/models/reaction.dart'; import 'package:stream_chat/src/models/own_user.dart'; -import 'package:test/test.dart'; - +import 'package:stream_chat/src/models/reaction.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; class MockDio extends Mock implements DioForNative {} @@ -76,7 +77,7 @@ void main() { any(), data: any(named: 'data'), )).thenAnswer((_) async => Response( - data: '{}', + data: jsonEncode(ChannelState()), statusCode: 200, requestOptions: FakeRequestOptions(), )); diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 4e880bed..c93059d5 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -9,9 +9,9 @@ import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/src/api/requests.dart'; import 'package:stream_chat/src/client.dart'; import 'package:stream_chat/src/exceptions.dart'; +import 'package:stream_chat/src/models/channel_model.dart'; import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/user.dart'; -import 'package:stream_chat/src/models/channel_model.dart'; import 'package:test/test.dart'; class MockDio extends Mock implements DioForNative {} @@ -748,7 +748,7 @@ void main() { ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({'message': message}), statusCode: 200, requestOptions: FakeRequestOptions(), ), @@ -793,7 +793,7 @@ void main() { when(() => mockDio.get('/messages/$messageId')).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({'message': Message(id: messageId)}), statusCode: 200, requestOptions: FakeRequestOptions(), ), @@ -1115,7 +1115,7 @@ void main() { ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({'message': message}), statusCode: 200, requestOptions: FakeRequestOptions(), ), @@ -1137,7 +1137,7 @@ void main() { ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({'message': message}), statusCode: 200, requestOptions: FakeRequestOptions(), ), @@ -1177,7 +1177,7 @@ void main() { ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({'channel': ChannelModel(cid: 'messaging:test')}), statusCode: 200, requestOptions: FakeRequestOptions(), ), diff --git a/packages/stream_chat/test/src/models/attachment_test.dart b/packages/stream_chat/test/src/models/attachment_test.dart index 1ca45132..1342e72e 100644 --- a/packages/stream_chat/test/src/models/attachment_test.dart +++ b/packages/stream_chat/test/src/models/attachment_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:stream_chat/src/models/attachment.dart'; -import 'package:stream_chat/src/models/action.dart'; +import 'package:stream_chat/src/models/action.dart'; +import 'package:stream_chat/src/models/attachment.dart'; import 'package:test/test.dart'; void main() { @@ -50,7 +50,7 @@ void main() { 'https://media0.giphy.com/media/3o7TKnCdBx5cMg0qti/giphy.gif', ); expect(attachment.actions, hasLength(3)); - expect(attachment.actions![0], isA()); + expect(attachment.actions[0], isA()); }); test('should serialize to json correctly', () { @@ -67,7 +67,8 @@ void main() { 'type': 'image', 'title': 'soo', 'title_link': - 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti' + 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', + 'actions': [], }, ); }); diff --git a/packages/stream_chat/test/src/models/channel_state_test.dart b/packages/stream_chat/test/src/models/channel_state_test.dart index 98d08596..afe384f5 100644 --- a/packages/stream_chat/test/src/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/models/channel_state_test.dart @@ -1,12 +1,12 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:stream_chat/src/models/channel_config.dart'; import 'package:stream_chat/src/models/channel_state.dart'; import 'package:stream_chat/src/models/command.dart'; import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/user.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; void main() { group('src/models/channel_state', () { @@ -852,7 +852,7 @@ void main() { expect(channelState.channel?.config, isA()); expect(channelState.channel?.config, isNotNull); expect(channelState.channel?.config.commands, hasLength(1)); - expect(channelState.channel?.config.commands![0], isA()); + expect(channelState.channel?.config.commands[0], isA()); expect(channelState.channel?.lastMessageAt, DateTime.parse('2020-01-30T13:43:41.062362Z')); expect(channelState.channel?.createdAt, @@ -902,7 +902,7 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "skip_push": null, + "skip_push": false, "silent": false, "pinned": false, "pinned_at": null, @@ -919,7 +919,7 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "skip_push": null, + "skip_push": false, "silent": false, "pinned": false, "pinned_at": null, @@ -929,7 +929,7 @@ void main() { { "id": "dry-meadow-0-53e6299f-9b97-4a9c-a27e-7e2dde49b7e0", "text": "test message", - "skip_push": null, + "skip_push": false, "attachments": [], "parent_id": null, "quoted_message": null, @@ -952,7 +952,7 @@ void main() { "quoted_message_id": null, "show_in_channel": null, "mentioned_users": [], - "skip_push": null, + "skip_push": false, "status": "SENT", "silent": false, "pinned": false, @@ -964,7 +964,7 @@ void main() { "id": "dry-meadow-0-64d7970f-ede8-4b31-9738-1bc1756d2bfe", "text": "test", "attachments": [], - "skip_push": null, + "skip_push": false, "parent_id": null, "quoted_message": null, "quoted_message_id": null, @@ -982,7 +982,7 @@ void main() { "text": "hi", "attachments": [], "parent_id": null, - "skip_push": null, + "skip_push": false, "quoted_message": null, "quoted_message_id": null, "show_in_channel": null, @@ -1000,7 +1000,7 @@ void main() { "attachments": [], "parent_id": null, "quoted_message": null, - "skip_push": null, + "skip_push": false, "quoted_message_id": null, "show_in_channel": null, "mentioned_users": [], @@ -1023,7 +1023,7 @@ void main() { "status": "SENT", "silent": false, "pinned": false, - "skip_push": null, + "skip_push": false, "pinned_at": null, "pin_expires": null, "pinned_by": null @@ -1041,7 +1041,7 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": null, + "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1053,7 +1053,7 @@ void main() { "quoted_message": null, "quoted_message_id": null, "show_in_channel": null, - "skip_push": null, + "skip_push": false, "mentioned_users": [], "status": "SENT", "silent": false, @@ -1071,7 +1071,7 @@ void main() { "quoted_message_id": null, "show_in_channel": null, "mentioned_users": [], - "skip_push": null, + "skip_push": false, "status": "SENT", "silent": false, "pinned": false, @@ -1090,7 +1090,7 @@ void main() { "mentioned_users": [], "status": "SENT", "silent": false, - "skip_push": null, + "skip_push": false, "pinned": false, "pinned_at": null, "pin_expires": null, @@ -1100,7 +1100,7 @@ void main() { "id": "icy-recipe-7-935c396e-ddf8-4a9a-951c-0a12fa5bf055", "text": "what are you doing?", "attachments": [], - "skip_push": null, + "skip_push": false, "parent_id": null, "quoted_message": null, "quoted_message_id": null, @@ -1118,7 +1118,7 @@ void main() { "text": "👍", "attachments": [], "parent_id": null, - "skip_push": null, + "skip_push": false, "quoted_message": null, "quoted_message_id": null, "show_in_channel": null, @@ -1134,7 +1134,7 @@ void main() { "id": "snowy-credit-3-3e0c1a0d-d22f-42ee-b2a1-f9f49477bf21", "text": "sdasas", "attachments": [], - "skip_push": null, + "skip_push": false, "parent_id": null, "quoted_message": null, "quoted_message_id": null, @@ -1155,7 +1155,7 @@ void main() { "quoted_message": null, "quoted_message_id": null, "show_in_channel": null, - "skip_push": null, + "skip_push": false, "mentioned_users": [], "status": "SENT", "silent": false, @@ -1168,7 +1168,7 @@ void main() { "id": "snowy-credit-3-cfaf0b46-1daa-49c5-947c-b16d6697487d", "text": "nhisagdhsadz", "attachments": [], - "skip_push": null, + "skip_push": false, "parent_id": null, "quoted_message": null, "quoted_message_id": null, @@ -1187,7 +1187,7 @@ void main() { "attachments": [], "parent_id": null, "quoted_message": null, - "skip_push": null, + "skip_push": false, "quoted_message_id": null, "show_in_channel": null, "mentioned_users": [], @@ -1204,7 +1204,7 @@ void main() { "attachments": [], "parent_id": null, "quoted_message": null, - "skip_push": null, + "skip_push": false, "quoted_message_id": null, "show_in_channel": null, "mentioned_users": [], @@ -1212,7 +1212,7 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": null, + "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1229,7 +1229,7 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": null, + "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1246,7 +1246,7 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": null, + "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1263,7 +1263,7 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": null, + "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1280,7 +1280,7 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": null, + "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1297,7 +1297,7 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": null, + "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1314,7 +1314,7 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": null, + "skip_push": false, "pin_expires": null, "pinned_by": null } diff --git a/packages/stream_chat/test/src/models/channel_test.dart b/packages/stream_chat/test/src/models/channel_test.dart index a48c5a23..01c06fee 100644 --- a/packages/stream_chat/test/src/models/channel_test.dart +++ b/packages/stream_chat/test/src/models/channel_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:stream_chat/src/models/channel_model.dart'; +import 'package:test/test.dart'; void main() { group('src/models/channel', () { @@ -9,7 +9,7 @@ void main() { { "id": "test", "type": "livestream", - "cid": "test:livestream", + "cid": "livestream:test", "cats": true, "fruit": ["bananas", "apples"] } @@ -19,7 +19,7 @@ void main() { final channel = ChannelModel.fromJson(json.decode(jsonExample)); expect(channel.id, equals('test')); expect(channel.type, equals('livestream')); - expect(channel.cid, equals('test:livestream')); + expect(channel.cid, equals('livestream:test')); expect(channel.extraData!['cats'], equals(true)); expect(channel.extraData!['fruit'], equals(['bananas', 'apples'])); }); diff --git a/packages/stream_chat/test/src/models/message_test.dart b/packages/stream_chat/test/src/models/message_test.dart index a34911cf..ddaed93f 100644 --- a/packages/stream_chat/test/src/models/message_test.dart +++ b/packages/stream_chat/test/src/models/message_test.dart @@ -1,10 +1,10 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:stream_chat/src/models/attachment.dart'; import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/reaction.dart'; import 'package:stream_chat/src/models/user.dart'; +import 'package:test/test.dart'; void main() { group('src/models/message', () { @@ -134,7 +134,7 @@ void main() { "id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", "text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", "silent": false, - "skip_push": null, + "skip_push": false, "attachments": [ { "type": "video", @@ -145,10 +145,11 @@ void main() { "og_scrape_url": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", "image_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", "author_name": "GIPHY", - "asset_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4" + "asset_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4", + "actions": [] } ], - "mentioned_users": null, + "mentioned_users": [], "parent_id": "parentId", "quoted_message": null, "quoted_message_id": null, diff --git a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart index 044eb693..8745cb45 100644 --- a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart @@ -90,6 +90,7 @@ void main() { Reaction( messageId: 'test', user: User(id: 'testid'), + type: 'test', ), ], ); From 07196eab9694aa59058397c769042b12574ddb3b Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 19 Apr 2021 17:43:11 +0530 Subject: [PATCH 103/599] fix tests Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/api/channel.dart | 210 +++++++------- .../stream_chat/lib/src/models/reaction.dart | 6 +- .../lib/src/models/reaction.g.dart | 4 +- .../test/src/api/channel_test.dart | 258 ++++++++++++++++-- 4 files changed, 339 insertions(+), 139 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 90bd6025..6fad7868 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -75,7 +75,7 @@ class Channel { /// Channel configuration ChannelConfig? get config { _checkInitialized(); - return state?._channelState?.channel?.config; + return state?._channelState.channel?.config; } /// Channel configuration as a stream @@ -87,7 +87,7 @@ class Channel { /// Channel user creator User? get createdBy { _checkInitialized(); - return state?._channelState?.channel?.createdBy; + return state?._channelState.channel?.createdBy; } /// Channel user creator as a stream @@ -99,7 +99,7 @@ class Channel { /// Channel frozen status bool? get frozen { _checkInitialized(); - return state?._channelState?.channel?.frozen; + return state?._channelState.channel?.frozen; } /// Channel frozen status as a stream @@ -111,7 +111,7 @@ class Channel { /// Channel creation date DateTime? get createdAt { _checkInitialized(); - return state?._channelState?.channel?.createdAt; + return state?._channelState.channel?.createdAt; } /// Channel creation date as a stream @@ -124,7 +124,7 @@ class Channel { DateTime? get lastMessageAt { _checkInitialized(); - return state?._channelState?.channel?.lastMessageAt; + return state?._channelState.channel?.lastMessageAt; } /// Channel last message date as a stream @@ -138,7 +138,7 @@ class Channel { DateTime? get updatedAt { _checkInitialized(); - return state?._channelState?.channel?.updatedAt; + return state?._channelState.channel?.updatedAt; } /// Channel updated date as a stream @@ -152,7 +152,7 @@ class Channel { DateTime? get deletedAt { _checkInitialized(); - return state?._channelState?.channel?.deletedAt; + return state?._channelState.channel?.deletedAt; } /// Channel deletion date as a stream @@ -166,7 +166,7 @@ class Channel { int? get memberCount { _checkInitialized(); - return state?._channelState?.channel?.memberCount; + return state?._channelState.channel?.memberCount; } /// Channel member count as a stream @@ -177,20 +177,20 @@ class Channel { } /// Channel id - String? get id => state?._channelState?.channel?.id ?? _id; + String? get id => state?._channelState.channel?.id ?? _id; /// Channel cid - String? get cid => state?._channelState?.channel?.cid ?? _cid; + String? get cid => state?._channelState.channel?.cid ?? _cid; /// Channel team String? get team { _checkInitialized(); - return state?._channelState?.channel?.team; + return state?._channelState.channel?.team; } /// Channel extra data Map? get extraData => - state?._channelState?.channel?.extraData ?? _extraData; + state?._channelState.channel?.extraData ?? _extraData; /// Channel extra data as a stream Stream?>? get extraDataStream { @@ -341,14 +341,15 @@ class Channel { /// Send a [message] to this channel. /// Waits for a [_messageAttachmentsUploadCompleter] to complete /// before actually sending the message. - Future sendMessage(Message message) async { + Future sendMessage(Message message) async { + _checkInitialized(); // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. _messageAttachmentsUploadCompleter .remove(message.id) ?.completeError('Message Cancelled'); - final quotedMessage = state?.messages.firstWhereOrNull( + final quotedMessage = state!.messages.firstWhereOrNull( (m) => m.id == message.quotedMessageId, ); // ignore: parameter_assignments @@ -365,7 +366,7 @@ class Channel { ).toList(), ); - state?.addMessage(message); + state!.addMessage(message); try { if (message.attachments.any((it) => !it.uploadState.isSuccess) == true) { @@ -384,11 +385,11 @@ class Channel { } final response = await _client.sendMessage(message, id!, type!); - state?.addMessage(response.message); + state!.addMessage(response.message); return response; } catch (error) { if (error is DioError && error.type != DioErrorType.response) { - state?.retryQueue?.add([message]); + state!.retryQueue?.add([message]); } rethrow; } @@ -397,7 +398,7 @@ class Channel { /// Updates the [message] in this channel. /// Waits for a [_messageAttachmentsUploadCompleter] to complete /// before actually updating the message. - Future updateMessage(Message message) async { + Future updateMessage(Message message) async { // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. _messageAttachmentsUploadCompleter @@ -452,7 +453,7 @@ class Channel { } /// Deletes the [message] from the channel. - Future deleteMessage(Message message) async { + Future deleteMessage(Message message) async { // Directly deleting the local messages which are not yet sent to server if (message.status == MessageSendingStatus.sending || message.status == MessageSendingStatus.failed) { @@ -493,7 +494,7 @@ class Channel { } /// Pins provided message - Future pinMessage( + Future pinMessage( Message message, Object? timeoutOrExpirationDate, ) { @@ -523,11 +524,11 @@ class Channel { } /// Unpins provided message - Future unpinMessage(Message message) => + Future unpinMessage(Message message) => updateMessage(message.copyWith(pinned: false)); /// Send a file to this channel - Future sendFile( + Future sendFile( AttachmentFile file, { ProgressCallback? onSendProgress, CancelToken? cancelToken, @@ -543,7 +544,7 @@ class Channel { } /// Send an image to this channel - Future sendImage( + Future sendImage( AttachmentFile file, { ProgressCallback? onSendProgress, CancelToken? cancelToken, @@ -559,7 +560,7 @@ class Channel { } /// A message search. - Future search({ + Future search({ String? query, Map? messageFilters, List? sort, @@ -578,7 +579,7 @@ class Channel { ); /// Delete a file from this channel - Future deleteFile( + Future deleteFile( String url, { CancelToken? cancelToken, }) { @@ -592,7 +593,7 @@ class Channel { } /// Delete an image from this channel - Future deleteImage( + Future deleteImage( String url, { CancelToken? cancelToken, }) { @@ -616,12 +617,13 @@ class Channel { /// Send a reaction to this channel /// Set [enforceUnique] to true to remove the existing user reaction - Future sendReaction( + Future sendReaction( Message message, String type, { Map extraData = const {}, bool enforceUnique = false, }) async { + _checkInitialized(); final messageId = message.id; final now = DateTime.now(); final user = _client.state.user; @@ -686,7 +688,7 @@ class Channel { } /// Delete a reaction from this channel - Future deleteReaction( + Future deleteReaction( Message message, Reaction reaction) async { final type = reaction.type; final user = _client.state.user; @@ -730,7 +732,7 @@ class Channel { } /// Edit the channel custom data - Future update( + Future update( Map channelData, [ Message? updateMessage, ]) async { @@ -743,40 +745,40 @@ class Channel { } /// Edit the channel custom data - Future updatePartial( + Future updatePartial( Map channelData) async { final response = await _client.patch(_channelURL, data: channelData); return _client.decode(response.data, PartialUpdateChannelResponse.fromJson); } /// Delete this channel. Messages are permanently removed. - Future delete() async { + Future delete() async { final response = await _client.delete(_channelURL); return _client.decode(response.data, EmptyResponse.fromJson); } /// Removes all messages from the channel - Future truncate() async { + Future truncate() async { final response = await _client.post('$_channelURL/truncate'); return _client.decode(response.data, EmptyResponse.fromJson); } /// Accept invitation to the channel - Future acceptInvite([Message? message]) async { + Future acceptInvite([Message? message]) async { final res = await _client.post(_channelURL, data: {'accept_invite': true, 'message': message?.toJson()}); return _client.decode(res.data, AcceptInviteResponse.fromJson); } /// Reject invitation to the channel - Future rejectInvite([Message? message]) async { + Future rejectInvite([Message? message]) async { final res = await _client.post(_channelURL, data: {'reject_invite': true, 'message': message?.toJson()}); return _client.decode(res.data, RejectInviteResponse.fromJson); } /// Add members to the channel - Future addMembers( + Future addMembers( List memberIds, [ Message? message, ]) async { @@ -788,7 +790,7 @@ class Channel { } /// Invite members to the channel - Future inviteMembers( + Future inviteMembers( List memberIds, [ Message? message, ]) async { @@ -800,7 +802,7 @@ class Channel { } /// Remove members from the channel - Future removeMembers( + Future removeMembers( List memberIds, [ Message? message, ]) async { @@ -836,7 +838,7 @@ class Channel { Message? oldMessage; if (oldIndex != -1) { oldMessage = state!.messages[oldIndex]; - state!.updateChannelState(state!._channelState!.copyWith( + state!.updateChannelState(state!._channelState.copyWith( messages: state?.messages?..remove(oldMessage), )); } else { @@ -863,7 +865,7 @@ class Channel { } /// Mark all channel messages as read - Future markRead() async { + Future markRead() async { _checkInitialized(); client.state.totalUnreadCount = max( 0, (client.state.totalUnreadCount ?? 0) - (state!.unreadCount ?? 0)); @@ -908,7 +910,7 @@ class Channel { } /// Stop watching the channel - Future stopWatching() async { + Future stopWatching() async { final response = await _client.post( '$_channelURL/stop-watching', data: {}, @@ -949,7 +951,7 @@ class Channel { } /// List the reactions for a message in the channel - Future getReactions( + Future getReactions( String messageID, PaginationParams options, ) async { @@ -982,7 +984,7 @@ class Channel { } /// Retrieves a list of messages by ID - Future translateMessage( + Future translateMessage( String messageId, String language, ) async { @@ -999,16 +1001,16 @@ class Channel { } /// Creates a new channel - Future? create() async => query(options: { + Future create() async => query(options: { 'watch': false, 'state': false, 'presence': false, - })!; + }); /// Query the API, get messages, members or other channel fields /// Set [preferOffline] to true to avoid the api call if the data is already /// in the offline storage - Future? query({ + Future query({ Map options = const {}, PaginationParams? messagesPagination, PaginationParams? membersPagination, @@ -1079,7 +1081,7 @@ class Channel { } /// Query channel members - Future queryMembers({ + Future queryMembers({ Map? filter, List? sort, PaginationParams? pagination, @@ -1108,7 +1110,7 @@ class Channel { } /// Mutes the channel - Future mute({Duration? expiration}) async { + Future mute({Duration? expiration}) async { final response = await _client.post('/moderation/mute/channel', data: { 'channel_cid': cid, if (expiration != null) 'expiration': expiration.inMilliseconds, @@ -1117,7 +1119,7 @@ class Channel { } /// Unmutes the channel - Future unmute() async { + Future unmute() async { final response = await _client.post('/moderation/unmute/channel', data: { 'channel_cid': cid, }); @@ -1125,7 +1127,7 @@ class Channel { } /// Bans a user from the channel - Future banUser( + Future banUser( String userID, Map options, ) async { @@ -1139,7 +1141,7 @@ class Channel { } /// Remove the ban for a user in the channel - Future unbanUser(String userID) async { + Future unbanUser(String userID) async { _checkInitialized(); return _client.unbanUser(userID, { 'type': type, @@ -1148,7 +1150,7 @@ class Channel { } /// Shadow bans a user from the channel - Future shadowBan( + Future shadowBan( String userID, Map options, ) async { @@ -1162,7 +1164,7 @@ class Channel { } /// Remove the shadow ban for a user in the channel - Future removeShadowBan(String userID) async { + Future removeShadowBan(String userID) async { _checkInitialized(); return _client.removeShadowBan(userID, { 'type': type, @@ -1173,7 +1175,7 @@ class Channel { /// Hides the channel from [StreamChatClient.queryChannels] for the user /// until a message is added If [clearHistory] is set to true - all messages /// will be removed for the user - Future hide({bool clearHistory = false}) async { + Future hide({bool clearHistory = false}) async { _checkInitialized(); final response = await _client .post('$_channelURL/hide', data: {'clear_history': clearHistory}); @@ -1190,7 +1192,7 @@ class Channel { } /// Removes the hidden status for the channel - Future show() async { + Future show() async { _checkInitialized(); final response = await _client.post('$_channelURL/show'); return _client.decode(response.data, EmptyResponse.fromJson); @@ -1257,9 +1259,10 @@ class Channel { void _checkInitialized() { assert( - !_initializedCompleter.isCompleted, - "Channel $cid hasn't been initialized yet. Make sure to call .watch()" - ' or to instantiate the client using [Channel.fromState]'); + _initializedCompleter.isCompleted, + "Channel $cid hasn't been initialized yet. Make sure to call .watch()" + ' or to instantiate the client using [Channel.fromState]', + ); } } @@ -1413,9 +1416,9 @@ class ChannelClientState { /// This flag should be managed by UI sdks. /// When false, any new message (received by WebSocket event /// - [EventType.messageNew]) will not be pushed on to message list. - bool? get isUpToDate => _isUpToDateController.value; + bool get isUpToDate => _isUpToDateController.value!; - set isUpToDate(bool? isUpToDate) => _isUpToDateController.add(isUpToDate); + set isUpToDate(bool isUpToDate) => _isUpToDateController.add(isUpToDate); /// [isUpToDate] flag count as a stream Stream get isUpToDateStream => _isUpToDateController.stream; @@ -1483,9 +1486,9 @@ class ChannelClientState { addMessage(message); if (message.pinned == true) { - _channelState = _channelState!.copyWith( + _channelState = _channelState.copyWith( pinnedMessages: [ - ..._channelState!.pinnedMessages, + ..._channelState.pinnedMessages, message, ], ); @@ -1522,7 +1525,7 @@ class ChannelClientState { /// Add a message to this channel void addMessage(Message message) { if (message.parentId == null || message.showInChannel == true) { - final newMessages = List.from(_channelState!.messages); + final newMessages = List.from(_channelState.messages); final oldIndex = newMessages.indexWhere((m) => m.id == message.id); if (oldIndex != -1) { Message? m; @@ -1537,9 +1540,9 @@ class ChannelClientState { newMessages.add(message); } - _channelState = _channelState!.copyWith( + _channelState = _channelState.copyWith( messages: newMessages, - channel: _channelState!.channel!.copyWith( + channel: _channelState.channel?.copyWith( lastMessageAt: message.createdAt, ), ); @@ -1551,7 +1554,7 @@ class ChannelClientState { } void _listenReadEvents() { - if (_channel.config?.readEvents == false) { + if (_channelState.channel?.config.readEvents == false) { return; } @@ -1563,7 +1566,7 @@ class ChannelClientState { ) .listen( (event) { - final readList = List.from(_channelState?.read ?? []); + final readList = List.from(_channelState.read); final userReadIndex = read?.indexWhere((r) => r.user.id == event.user!.id); @@ -1577,7 +1580,7 @@ class ChannelClientState { lastRead: event.createdAt!, unreadMessages: event.totalUnreadCount!, )); - _channelState = _channelState!.copyWith(read: readList); + _channelState = _channelState.copyWith(read: readList); } }, ), @@ -1585,22 +1588,22 @@ class ChannelClientState { } /// Channel message list - List get messages => _channelState!.messages; + List get messages => _channelState.messages; /// Channel message list as a stream Stream?> get messagesStream => channelStateStream.map((cs) => cs!.messages); /// Channel pinned message list - List? get pinnedMessages => _channelState!.pinnedMessages.toList(); + List? get pinnedMessages => _channelState.pinnedMessages.toList(); /// Channel pinned message list as a stream Stream?> get pinnedMessagesStream => channelStateStream.map((cs) => cs!.pinnedMessages.toList()); /// Get channel last message - Message? get lastMessage => _channelState!.messages.isNotEmpty == true - ? _channelState!.messages.last + Message? get lastMessage => _channelState.messages.isNotEmpty == true + ? _channelState.messages.last : null; /// Get channel last message @@ -1608,7 +1611,7 @@ class ChannelClientState { .map((event) => event?.isNotEmpty == true ? event!.last : null); /// Channel members list - List get members => _channelState!.members + List get members => _channelState.members .map((e) => e.copyWith(user: _channel.client.state.users[e.user!.id])) .toList(); @@ -1622,14 +1625,14 @@ class ChannelClientState { ); /// Channel watcher count - int? get watcherCount => _channelState!.watcherCount; + int? get watcherCount => _channelState.watcherCount; /// Channel watcher count as a stream Stream get watcherCountStream => channelStateStream.map((cs) => cs!.watcherCount); /// Channel watchers list - List get watchers => _channelState!.watchers + List get watchers => _channelState.watchers .map((e) => _channel.client.state.users[e.id] ?? e) .toList(); @@ -1642,7 +1645,7 @@ class ChannelClientState { ); /// Channel read list - List? get read => _channelState!.read; + List? get read => _channelState.read; /// Channel read list as a stream Stream?> get readStream => @@ -1693,7 +1696,7 @@ class ChannelClientState { /// Delete all channel messages void truncate() { - _channelState = _channelState!.copyWith( + _channelState = _channelState.copyWith( messages: [], ); } @@ -1704,24 +1707,22 @@ class ChannelClientState { void updateChannelState(ChannelState updatedState) { final newMessages = [ ...updatedState.messages, - ..._channelState?.messages - .where((m) => - updatedState.messages - .any((newMessage) => newMessage.id == m.id) != - true) - .toList() ?? - [], + ..._channelState.messages + .where((m) => + updatedState.messages + .any((newMessage) => newMessage.id == m.id) != + true) + .toList(), ]..sort(_sortByCreatedAt as int Function(Message, Message)?); final newWatchers = [ ...updatedState.watchers, - ..._channelState?.watchers - .where((w) => - updatedState.watchers - .any((newWatcher) => newWatcher.id == w.id) != - true) - .toList() ?? - [], + ..._channelState.watchers + .where((w) => + updatedState.watchers + .any((newWatcher) => newWatcher.id == w.id) != + true) + .toList(), ]; final newMembers = [ @@ -1730,20 +1731,19 @@ class ChannelClientState { final newReads = [ ...updatedState.read, - ..._channelState?.read - .where((r) => - updatedState.read - .any((newRead) => newRead.user.id == r.user.id) != - true) - .toList() ?? - [], + ..._channelState.read + .where((r) => + updatedState.read + .any((newRead) => newRead.user.id == r.user.id) != + true) + .toList(), ]; _checkExpiredAttachmentMessages(updatedState); - _channelState = _channelState!.copyWith( + _channelState = _channelState.copyWith( messages: newMessages, - channel: _channelState!.channel?.merge(updatedState.channel), + channel: _channelState.channel?.merge(updatedState.channel), watchers: newWatchers, watcherCount: updatedState.watcherCount, members: newMembers, @@ -1765,7 +1765,7 @@ class ChannelClientState { } /// The channel state related to this client - ChannelState? get _channelState => _channelStateController.value; + ChannelState get _channelState => _channelStateController.value!; /// The channel state related to this client as a stream Stream get channelStateStream => @@ -1773,11 +1773,11 @@ class ChannelClientState { /// The channel state related to this client ChannelState? get channelState => _channelStateController.value; - late BehaviorSubject _channelStateController; + late BehaviorSubject _channelStateController; final Debounce _debouncedUpdatePersistenceChannelState; - set _channelState(ChannelState? v) { + set _channelState(ChannelState v) { _channelStateController.add(v); _debouncedUpdatePersistenceChannelState.call([v]); } @@ -1812,7 +1812,7 @@ class ChannelClientState { final Map _typings = {}; void _listenTypingEvents() { - if (_channel.config?.typingEvents == false) { + if (_channelState.channel?.config.typingEvents == false) { return; } @@ -1867,7 +1867,7 @@ class ChannelClientState { late Timer _cleaningTimer; void _startCleaning() { - if (_channel.config?.typingEvents == false) { + if (_channelState.channel?.config.typingEvents == false) { return; } @@ -1899,7 +1899,7 @@ class ChannelClientState { )) .toList(); - updateChannelState(_channelState!.copyWith( + updateChannelState(_channelState.copyWith( pinnedMessages: pinnedMessages!.where(_pinIsValid()).toList(), messages: expiredMessages, )); diff --git a/packages/stream_chat/lib/src/models/reaction.dart b/packages/stream_chat/lib/src/models/reaction.dart index eefeaebe..82913218 100644 --- a/packages/stream_chat/lib/src/models/reaction.dart +++ b/packages/stream_chat/lib/src/models/reaction.dart @@ -12,11 +12,11 @@ class Reaction { this.messageId, DateTime? createdAt, required this.type, - required this.user, + this.user, String? userId, this.score = 0, this.extraData, - }) : userId = userId ?? user.id, + }) : userId = userId ?? user?.id, createdAt = createdAt ?? DateTime.now(); /// Create a new instance from a json @@ -38,7 +38,7 @@ class Reaction { /// The user that sent the reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) - final User user; + final User? user; /// The score of the reaction (ie. number of reactions sent) @JsonKey(defaultValue: 0) diff --git a/packages/stream_chat/lib/src/models/reaction.g.dart b/packages/stream_chat/lib/src/models/reaction.g.dart index 203be6eb..963b4d21 100644 --- a/packages/stream_chat/lib/src/models/reaction.g.dart +++ b/packages/stream_chat/lib/src/models/reaction.g.dart @@ -13,7 +13,9 @@ Reaction _$ReactionFromJson(Map json) { ? null : DateTime.parse(json['created_at'] as String), type: json['type'] as String, - user: User.fromJson(json['user'] as Map), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), userId: json['user_id'] as String?, score: json['score'] as int? ?? 0, extraData: json['extra_data'] as Map?, diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index e0a29cc8..b41214d2 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -38,6 +38,17 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); final message = Message(text: 'hey', id: 'test'); + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState()), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + when( () => mockDio.post( '/channels/messaging/testid/message', @@ -45,7 +56,7 @@ void main() { ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({'message': message}), statusCode: 200, requestOptions: FakeRequestOptions(), ), @@ -227,6 +238,17 @@ void main() { ); final channelClient = client.channel(channelType, id: channelId); + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState()), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + when(() => mockUploader.sendFile(file, channelId, channelType)) .thenAnswer((_) async => SendFileResponse()); @@ -255,6 +277,17 @@ void main() { ); final channelClient = client.channel(channelType, id: channelId); + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState()), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + when(() => mockUploader.sendImage(image, channelId, channelType)) .thenAnswer((_) async => SendImageResponse()); @@ -278,6 +311,17 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); const url = 'url'; + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState()), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + when( () => mockDio.delete( '/channels/messaging/testid/file', @@ -311,6 +355,17 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); const url = 'url'; + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState()), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + when( () => mockDio.delete( '/channels/messaging/testid/image', @@ -357,6 +412,17 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); final message = Message(text: 'Hello', id: 'test'); + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState()), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + when( () => mockDio.post( '/messages/${message.id}', @@ -364,7 +430,7 @@ void main() { ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({'message': message}), statusCode: 200, requestOptions: FakeRequestOptions(), ), @@ -391,6 +457,17 @@ void main() { final channelClient = client.channel('messaging', id: 'testid'); final message = Message(text: 'Hello', id: 'test'); + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState()), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + when( () => mockDio.post( '/messages/${message.id}', @@ -398,7 +475,7 @@ void main() { ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({'message': message}), statusCode: 200, requestOptions: FakeRequestOptions(), ), @@ -567,14 +644,30 @@ void main() { tokenProvider: (_) async => '', ); - client.state.user = OwnUser(id: 'test-id'); + final user = OwnUser(id: 'test-id'); - final channelClient = client.channel('messaging', id: 'testid'); + client.state.user = user; + + final message = Message(id: 'messageid'); const reactionType = 'test'; + final reaction = Reaction(type: reactionType); + final channelClient = client.channel('messaging', id: 'testid'); + + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); + await channelClient.watch(); when( () => mockDio.post( - '/messages/messageid/reaction', + '/messages/${message.id}/reaction', data: { 'reaction': { 'type': reactionType, @@ -584,16 +677,17 @@ void main() { ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({ + 'message': message, + 'reaction': reaction, + }), statusCode: 200, requestOptions: FakeRequestOptions(), ), ); await channelClient.sendReaction( - Message( - id: 'messageid', - ), + message, reactionType, ); @@ -697,26 +791,44 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final members = ['vishal']; + final channelModel = ChannelModel(cid: 'messaging:testid'); + + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState(channel: channelModel)), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + + final members = [Member(userId: 'vishal')]; + final memberIds = members.map((e) => e.userId!).toList(); final message = Message(text: 'test'); when( () => mockDio.post( '/channels/messaging/testid', - data: {'add_members': members, 'message': message.toJson()}, + data: {'add_members': memberIds, 'message': message.toJson()}, ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({ + 'members': members, + 'message': message, + 'channel': channelModel, + }), statusCode: 200, requestOptions: FakeRequestOptions(), ), ); - await channelClient.addMembers(members, message); + await channelClient.addMembers(memberIds, message); verify(() => mockDio.post('/channels/messaging/testid', - data: {'add_members': members, 'message': message.toJson()})) + data: {'add_members': memberIds, 'message': message.toJson()})) .called(1); }); @@ -732,6 +844,19 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); + final channelModel = ChannelModel(cid: 'messaging:testid'); + + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState(channel: channelModel)), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + final message = Message(text: 'test'); when( @@ -741,7 +866,10 @@ void main() { ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({ + 'message': message, + 'channel': channelModel, + }), statusCode: 200, requestOptions: FakeRequestOptions(), ), @@ -2080,6 +2208,19 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); + final channelModel = ChannelModel(cid: 'messaging:testid'); + + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState(channel: channelModel)), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + final message = Message(text: 'test'); when( @@ -2092,7 +2233,10 @@ void main() { ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({ + 'channel': channelModel, + 'message': message, + }), statusCode: 200, requestOptions: FakeRequestOptions(), ), @@ -2179,6 +2323,19 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); + final channelModel = ChannelModel(cid: 'messaging:testid'); + + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState(channel: channelModel)), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + final message = Message(text: 'test'); when( @@ -2188,7 +2345,10 @@ void main() { ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({ + 'message': message, + 'channel': channelModel, + }), statusCode: 200, requestOptions: FakeRequestOptions(), ), @@ -2213,26 +2373,45 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final members = ['vishal']; + final channelModel = ChannelModel(cid: 'messaging:testid'); + + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState(channel: channelModel)), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + + final members = [Member(userId: 'vishal')]; + final memberIds = members.map((e) => e.userId!).toList(); final message = Message(text: 'test'); when( () => mockDio.post( '/channels/messaging/testid', - data: {'invites': members, 'message': message.toJson()}, + data: {'invites': memberIds, 'message': message.toJson()}, ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({ + 'members': members, + 'message': message, + 'channel': channelModel, + }), statusCode: 200, requestOptions: FakeRequestOptions(), ), ); - await channelClient.inviteMembers(members, message); + await channelClient.inviteMembers(memberIds, message); verify(() => mockDio.post('/channels/messaging/testid', - data: {'invites': members, 'message': message.toJson()})).called(1); + data: {'invites': memberIds, 'message': message.toJson()})) + .called(1); }); test('removeMembers', () async { @@ -2247,27 +2426,46 @@ void main() { tokenProvider: (_) async => '', ); final channelClient = client.channel('messaging', id: 'testid'); - final members = ['vishal']; + final channelModel = ChannelModel(cid: 'messaging:testid'); + + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState(channel: channelModel)), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + + final members = [Member(userId: 'vishal')]; + final memberIds = members.map((e) => e.userId!).toList(); final message = Message(text: 'test'); when( () => mockDio.post( '/channels/messaging/testid', - data: {'remove_members': members, 'message': message.toJson()}, + data: {'remove_members': memberIds, 'message': message.toJson()}, ), ).thenAnswer( (_) async => Response( - data: '{}', + data: jsonEncode({ + 'members': members, + 'message': message, + 'channel': channelModel, + }), statusCode: 200, requestOptions: FakeRequestOptions(), ), ); - await channelClient.removeMembers(members, message); + await channelClient.removeMembers(memberIds, message); - verify(() => mockDio.post('/channels/messaging/testid', - data: {'remove_members': members, 'message': message.toJson()})) - .called(1); + verify(() => mockDio.post('/channels/messaging/testid', data: { + 'remove_members': memberIds, + 'message': message.toJson() + })).called(1); }); test('hide', () async { From 5c5c4bbc3fb1e3f1baf64e6992d03e3caf9c3011 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Apr 2021 15:11:40 +0200 Subject: [PATCH 104/599] fix tests --- packages/stream_chat/lib/src/api/channel.dart | 8 ++++---- packages/stream_chat/test/src/api/channel_test.dart | 8 ++++---- packages/stream_chat/test/src/models/message_test.dart | 1 - packages/stream_chat/test/src/models/reaction_test.dart | 4 ++-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 6fad7868..63fc5595 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -296,13 +296,13 @@ class Channel { it.file!, onSendProgress: onSendProgress, cancelToken: cancelToken, - ).then((it) => it!.file); + ).then((it) => it.file); } else { future = sendFile( it.file!, onSendProgress: onSendProgress, cancelToken: cancelToken, - ).then((it) => it!.file); + ).then((it) => it.file); } _cancelableAttachmentUploadRequest[it.id] = cancelToken; return future.then((url) { @@ -886,7 +886,7 @@ class Channel { ChannelState response; try { - response = await query(options: watchOptions)!; + response = await query(options: watchOptions); } catch (error, stackTrace) { if (!_initializedCompleter.isCompleted) { _initializedCompleter.completeError(error, stackTrace); @@ -1511,7 +1511,7 @@ class ChannelClientState { ) .listen((event) { final message = event.message!; - if (isUpToDate! || + if (isUpToDate || (message.parentId != null && message.showInChannel != true)) { addMessage(message); } diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index b41214d2..041a3e49 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -1200,8 +1200,8 @@ void main() { verify(() => mockDio.post('/channels/messaging/query', data: options)).called(1); - expect(channelClient.id, response?.channel?.id); - expect(channelClient.cid, response?.channel?.cid); + expect(channelClient.id, response.channel?.id); + expect(channelClient.cid, response.channel?.cid); }); test('with id', () async { @@ -1837,8 +1837,8 @@ void main() { verify(() => mockDio.post('/channels/messaging/query', data: options)).called(1); - expect(channelClient.id, response?.channel?.id); - expect(channelClient.cid, response?.channel?.cid); + expect(channelClient.id, response.channel?.id); + expect(channelClient.cid, response.channel?.cid); }); test('watch', () async { diff --git a/packages/stream_chat/test/src/models/message_test.dart b/packages/stream_chat/test/src/models/message_test.dart index ddaed93f..1219e137 100644 --- a/packages/stream_chat/test/src/models/message_test.dart +++ b/packages/stream_chat/test/src/models/message_test.dart @@ -102,7 +102,6 @@ void main() { id: '4637f7e4-a06b-42db-ba5a-8d8270dd926f', text: 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA', - silent: false, attachments: [ Attachment.fromJson(const { 'type': 'video', diff --git a/packages/stream_chat/test/src/models/reaction_test.dart b/packages/stream_chat/test/src/models/reaction_test.dart index 49d226ea..795e2967 100644 --- a/packages/stream_chat/test/src/models/reaction_test.dart +++ b/packages/stream_chat/test/src/models/reaction_test.dart @@ -1,8 +1,8 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:stream_chat/src/models/reaction.dart'; import 'package:stream_chat/src/models/user.dart'; +import 'package:test/test.dart'; void main() { group('src/models/reaction', () { @@ -33,7 +33,7 @@ void main() { expect(reaction.createdAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); expect(reaction.type, 'wow'); expect( - reaction.user.toJson(), + reaction.user?.toJson(), User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { 'image': 'https://randomuser.me/api/portraits/women/45.jpg', 'name': 'Daisy Morgan' From 098935834fa1edd5aefa01256dbed88977bce113 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Apr 2021 15:23:00 +0200 Subject: [PATCH 105/599] migrate example --- packages/stream_chat/example/lib/main.dart | 37 ++++++++++++---------- packages/stream_chat/example/pubspec.yaml | 2 +- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/stream_chat/example/lib/main.dart b/packages/stream_chat/example/lib/main.dart index d13dda4c..11e9779f 100644 --- a/packages/stream_chat/example/lib/main.dart +++ b/packages/stream_chat/example/lib/main.dart @@ -44,9 +44,9 @@ class StreamExample extends StatelessWidget { /// To initialize this example, an instance of /// [client] and [channel] is required. const StreamExample({ - Key key, - @required this.client, - @required this.channel, + Key? key, + required this.client, + required this.channel, }) : super(key: key); /// Instance of [StreamChatClient] we created earlier. @@ -69,28 +69,31 @@ class StreamExample extends StatelessWidget { /// containing the channel name and a [MessageView] displaying recent messages. class HomeScreen extends StatelessWidget { /// [HomeScreen] is constructed using the [Channel] we defined earlier. - const HomeScreen({Key key, @required this.channel}) : super(key: key); + const HomeScreen({ + Key? key, + required this.channel, + }) : super(key: key); /// Channel object containing the [Channel.id] we'd like to observe. final Channel channel; @override Widget build(BuildContext context) { - final messages = channel.state.channelStateStream; + final messages = channel.state!.channelStateStream; return Scaffold( appBar: AppBar( title: Text('Channel: ${channel.id}'), ), body: SafeArea( - child: StreamBuilder( + child: StreamBuilder( stream: messages, builder: ( BuildContext context, - AsyncSnapshot snapshot, + AsyncSnapshot snapshot, ) { if (snapshot.hasData && snapshot.data != null) { return MessageView( - messages: snapshot.data.messages.reversed.toList(), + messages: snapshot.data!.messages.reversed.toList(), channel: channel, ); } else if (snapshot.hasError) { @@ -119,9 +122,9 @@ class HomeScreen extends StatelessWidget { class MessageView extends StatefulWidget { /// Message takes the latest list of messages and the current channel. const MessageView({ - Key key, - @required this.messages, - @required this.channel, + Key? key, + required this.messages, + required this.channel, }) : super(key: key); /// List of messages sent in the given channel. @@ -135,8 +138,8 @@ class MessageView extends StatefulWidget { } class _MessageViewState extends State { - TextEditingController _controller; - ScrollController _scrollController; + late final TextEditingController _controller; + late final ScrollController _scrollController; List get _messages => widget.messages; @@ -174,12 +177,12 @@ class _MessageViewState extends State { reverse: true, itemBuilder: (BuildContext context, int index) { final item = _messages[index]; - if (item.user.id == widget.channel.client.uid) { + if (item.user?.id == widget.channel.client.uid) { return Align( alignment: Alignment.centerRight, child: Padding( padding: const EdgeInsets.all(8), - child: Text(item.text), + child: Text(item.text ?? ''), ), ); } else { @@ -187,7 +190,7 @@ class _MessageViewState extends State { alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.all(8), - child: Text(item.text), + child: Text(item.text ?? ''), ), ); } @@ -246,5 +249,5 @@ class _MessageViewState extends State { /// Helper extension for quickly retrieving /// the current user id from a [StreamChatClient]. extension on StreamChatClient { - String get uid => state.user.id; + String get uid => state.user!.id; } diff --git a/packages/stream_chat/example/pubspec.yaml b/packages/stream_chat/example/pubspec.yaml index 2d010ab6..1b092b3e 100644 --- a/packages/stream_chat/example/pubspec.yaml +++ b/packages/stream_chat/example/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: "none" version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: cupertino_icons: ^1.0.0 From 1c7c1dd81c9b748fcc1f1f1bbe6429805397fe14 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Apr 2021 15:38:13 +0200 Subject: [PATCH 106/599] update dio --- 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 ace20c3b..8e0d5f12 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: async: ^2.5.0 collection: ^1.15.0 - dio: ">=4.0.0-prev3 <4.0.0" + dio: ^4.0.0 equatable: ^2.0.0 freezed_annotation: ^0.14.0 http_parser: ^4.0.0 From 7f29ca52e1b841d41ee9b58b77a41349656fcb69 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Apr 2021 15:44:46 +0200 Subject: [PATCH 107/599] fix persistence abstract class --- .../lib/src/db/chat_persistence_client.dart | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 50222499..d93d95da 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -76,11 +76,11 @@ abstract class ChatPersistenceClient { getPinnedMessagesByCid(cid, messagePagination: pinnedMessagePagination), ]); return ChannelState( - members: (data[0] as List?)!, - read: (data[1] as List?)!, - channel: data[2] as ChannelModel?, - messages: (data[3] as List?)!, - pinnedMessages: (data[4] as List?)!, + members: data[0] as List, + read: data[1] as List, + channel: data[2] as ChannelModel, + messages: data[3] as List, + pinnedMessages: data[4] as List, ); } @@ -195,9 +195,10 @@ abstract class ChatPersistenceClient { final reactions = channelStates.expand((it) => it.messages).expand((it) => [ if (it.ownReactions != null) - ...it.ownReactions!.where((r) => r.userId != null), + ...it.ownReactions?.where((r) => r.userId != null) ?? [], if (it.latestReactions != null) - ...it.latestReactions!.where((r) => r.userId != null) + ...it.latestReactions?.where((r) => r.userId != null) ?? + [], ]); final users = channelStates @@ -207,9 +208,9 @@ abstract class ChatPersistenceClient { .map((m) => [ m.user, if (m.latestReactions != null) - ...m.latestReactions!.map((r) => r.user), + ...m.latestReactions?.map((r) => r.user) ?? [], if (m.ownReactions != null) - ...m.ownReactions!.map((r) => r.user), + ...m.ownReactions?.map((r) => r.user) ?? [], ]) .expand((v) => v), ...cs.read.map((r) => r.user), From 9f01e71ed33224fe034c87c1d52a639ff7e8c82b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Apr 2021 15:51:09 +0200 Subject: [PATCH 108/599] fix persistence abstract class --- .../lib/src/db/chat_persistence_client.dart | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index d93d95da..3fae32af 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -180,8 +180,11 @@ abstract class ChatPersistenceClient { .map((m) => m.id) .toList(growable: false)); + final cleanedChannelStates = + channelStates.where((it) => it.channel != null); + final deleteMembers = deleteMembersByCids( - channelStates.map((it) => it.channel!.cid).toList(growable: false), + cleanedChannelStates.map((it) => it.channel!.cid).toList(growable: false), ); await Future.wait([ @@ -189,19 +192,21 @@ abstract class ChatPersistenceClient { deleteMembers, ]); - final channels = channelStates + final channels = cleanedChannelStates .map((it) => it.channel) .where((it) => it != null) as Iterable; - final reactions = channelStates.expand((it) => it.messages).expand((it) => [ - if (it.ownReactions != null) - ...it.ownReactions?.where((r) => r.userId != null) ?? [], - if (it.latestReactions != null) - ...it.latestReactions?.where((r) => r.userId != null) ?? - [], - ]); + final reactions = + cleanedChannelStates.expand((it) => it.messages).expand((it) => [ + if (it.ownReactions != null) + ...it.ownReactions?.where((r) => r.userId != null) ?? + [], + if (it.latestReactions != null) + ...it.latestReactions?.where((r) => r.userId != null) ?? + [], + ]); - final users = channelStates + final users = cleanedChannelStates .map((cs) => [ cs.channel?.createdBy, ...cs.messages @@ -219,25 +224,25 @@ abstract class ChatPersistenceClient { .expand((it) => it) .where((it) => it != null) as Iterable; - final updateMessagesFuture = channelStates.map((it) { + final updateMessagesFuture = cleanedChannelStates.map((it) { final cid = it.channel!.cid; final messages = it.messages; return updateMessages(cid, messages.toList(growable: false)); }).toList(growable: false); - final updatePinnedMessagesFuture = channelStates.map((it) { + final updatePinnedMessagesFuture = cleanedChannelStates.map((it) { final cid = it.channel!.cid; final messages = it.pinnedMessages; return updatePinnedMessages(cid, messages.toList(growable: false)); }).toList(growable: false); - final updateReadsFuture = channelStates.map((it) { + final updateReadsFuture = cleanedChannelStates.map((it) { final cid = it.channel!.cid; final reads = it.read; return updateReads(cid, reads.toList(growable: false)); }).toList(growable: false); - final updateMembersFuture = channelStates.map((it) { + final updateMembersFuture = cleanedChannelStates.map((it) { final cid = it.channel!.cid; final members = it.members; return updateMembers(cid, members.toList(growable: false)); From 3083839714f5e6f279d75ebe29523ec95ab1edea Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Apr 2021 15:53:12 +0200 Subject: [PATCH 109/599] update extensions --- .../lib/src/extensions/string_extension.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat/lib/src/extensions/string_extension.dart b/packages/stream_chat/lib/src/extensions/string_extension.dart index e87eea7e..440e2cb6 100644 --- a/packages/stream_chat/lib/src/extensions/string_extension.dart +++ b/packages/stream_chat/lib/src/extensions/string_extension.dart @@ -2,14 +2,17 @@ import 'package:http_parser/http_parser.dart' as http_parser; import 'package:mime/mime.dart'; /// Useful extension functions for [String] -extension StringX on String? { +extension StringX on String { /// Returns the mime type from the passed file name. http_parser.MediaType? get mimeType { - if (this == null) return null; - if (this!.toLowerCase().endsWith('heic')) { + if (toLowerCase().endsWith('heic')) { return http_parser.MediaType.parse('image/heic'); } else { - return http_parser.MediaType.parse(lookupMimeType(this!)!); + final mimeType = lookupMimeType(this); + if (mimeType == null) { + return null; + } + return http_parser.MediaType.parse(mimeType); } } } From 9eb76732c1ba272c24b65114b47fa39097353526 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Apr 2021 15:53:41 +0200 Subject: [PATCH 110/599] update extensions --- packages/stream_chat/lib/src/attachment_file_uploader.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/lib/src/attachment_file_uploader.dart b/packages/stream_chat/lib/src/attachment_file_uploader.dart index 7b90f0c9..d00dd4e7 100644 --- a/packages/stream_chat/lib/src/attachment_file_uploader.dart +++ b/packages/stream_chat/lib/src/attachment_file_uploader.dart @@ -71,7 +71,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { CancelToken? cancelToken, }) async { final filename = file.path?.split('/').last ?? file.name; - final mimeType = filename.mimeType; + final mimeType = filename?.mimeType; MultipartFile? multiPartFile; if (file.path != null) { @@ -108,7 +108,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { CancelToken? cancelToken, }) async { final filename = file.path?.split('/').last ?? file.name; - final mimeType = filename.mimeType; + final mimeType = filename?.mimeType; MultipartFile? multiPartFile; if (file.path != null) { From 673368a1b21bfcde778a3b7c0674180d17e28243 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Apr 2021 16:05:10 +0200 Subject: [PATCH 111/599] update map extensions --- packages/stream_chat/lib/src/extensions/map_extension.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/extensions/map_extension.dart b/packages/stream_chat/lib/src/extensions/map_extension.dart index 8b1cb51f..d99ab96a 100644 --- a/packages/stream_chat/lib/src/extensions/map_extension.dart +++ b/packages/stream_chat/lib/src/extensions/map_extension.dart @@ -1,6 +1,6 @@ /// Useful extension functions for [Map] -extension MapX on Map { +extension MapX on Map { /// Returns a new map with null keys or values removed - Map get nullProtected => {...this as Map} - ..removeWhere((key, value) => key == null || value == null); + Map get nullProtected => + Map.from(this)..removeWhere((key, value) => key == null || value == null); } From b69be11e4d5e12a28e80161ac0e6351435dec3d6 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Apr 2021 16:06:53 +0200 Subject: [PATCH 112/599] update dio --- packages/stream_chat_flutter/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 522a7203..ffee617b 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -43,7 +43,7 @@ dependencies: ezanimation: ^0.4.1 synchronized: ^3.0.0 characters: ^1.0.0 - dio: ">=4.0.0-prev3 <4.0.0" + dio: ^4.0.0 path_provider: ^2.0.0 video_thumbnail: ^0.2.5+1 From b250bdb98ec46573c0d4affc8fc16945010a34f1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Apr 2021 16:21:32 +0200 Subject: [PATCH 113/599] fix persistence abstract class --- .../stream_chat/lib/src/api/websocket.dart | 60 +++++++++---------- packages/stream_chat/lib/src/client.dart | 4 +- .../lib/src/db/chat_persistence_client.dart | 10 ++-- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/packages/stream_chat/lib/src/api/websocket.dart b/packages/stream_chat/lib/src/api/websocket.dart index bf27a990..8b670f7f 100644 --- a/packages/stream_chat/lib/src/api/websocket.dart +++ b/packages/stream_chat/lib/src/api/websocket.dart @@ -28,32 +28,32 @@ class WebSocket { /// To connect the WS call [connect] WebSocket({ required this.baseUrl, - this.user, - this.connectParams, - this.connectPayload, - this.handler, + required this.user, + required this.handler, + this.connectParams = const {}, + this.connectPayload = const {}, this.logger, this.connectFunc, this.reconnectionMonitorInterval = 1, this.healthCheckInterval = 20, this.reconnectionMonitorTimeout = 40, }) { - final qs = Map.from(connectParams!); + final qs = Map.from(connectParams); - final data = Map.from(connectPayload!); + final data = Map.from(connectPayload); - data['user_details'] = user!.toJson(); + data['user_details'] = user.toJson(); qs['json'] = json.encode(data); if (baseUrl.startsWith('https')) { _path = baseUrl.replaceFirst('https://', ''); - _path = Uri.https(_path!, 'connect', qs) + _path = Uri.https(_path, 'connect', qs) .toString() .replaceFirst('https', 'wss'); } else if (baseUrl.startsWith('http')) { _path = baseUrl.replaceFirst('http://', ''); _path = - Uri.http(_path!, 'connect', qs).toString().replaceFirst('http', 'ws'); + Uri.http(_path, 'connect', qs).toString().replaceFirst('http', 'ws'); } else { _path = Uri.https(baseUrl, 'connect', qs) .toString() @@ -65,17 +65,17 @@ class WebSocket { final String baseUrl; /// User performing the WS connection - final User? user; + final User user; /// Querystring connection parameters - final Map? connectParams; + final Map connectParams; /// WS connection payload - final Map? connectPayload; + final Map connectPayload; /// Functions that will be called every time a new event is received from the /// connection - final EventHandler? handler; + final EventHandler handler; /// A WS specific logger instance final Logger? logger; @@ -113,7 +113,7 @@ class WebSocket { Stream get connectionStatusStream => _connectionStatusController.stream; - String? _path; + late final String _path; int _retryAttempt = 1; late WebSocketChannel _channel; Timer? _healthCheck, _reconnectionMonitor; @@ -131,17 +131,17 @@ class WebSocket { _manuallyDisconnected = false; if (_connecting) { - logger!.severe('already connecting'); + logger?.severe('already connecting'); return null; } _connecting = true; _connectionStatus = ConnectionStatus.connecting; - logger!.info('connecting to $_path'); + logger?.info('connecting to $_path'); _channel = - connectFunc?.call(_path) ?? WebSocketChannel.connect(Uri.parse(_path!)); + connectFunc?.call(_path) ?? WebSocketChannel.connect(Uri.parse(_path)); _channel.stream.listen( (data) async { final jsonData = json.decode(data); @@ -164,7 +164,7 @@ class WebSocket { return; } - logger!.info('connection closed | closeCode: ${_channel.closeCode} | ' + logger?.info('connection closed | closeCode: ${_channel.closeCode} | ' 'closedReason: ${_channel.closeReason}'); if (!_reconnecting) { @@ -178,10 +178,10 @@ class WebSocket { } final event = _decodeEvent(data); - logger!.info('received new event: $data'); + logger?.info('received new event: $data'); if (_lastEventAt == null) { - logger!.info('connection estabilished'); + logger?.info('connection estabilished'); _connecting = false; _reconnecting = false; _lastEventAt = DateTime.now(); @@ -197,14 +197,14 @@ class WebSocket { _startHealthCheck(); } - handler!(event); + handler(event); _lastEventAt = DateTime.now(); } Future _onConnectionError(error, [stacktrace]) async { - logger!..severe('error connecting')..severe(error); + logger?..severe('error connecting')..severe(error); if (stacktrace != null) { - logger!.severe(stacktrace); + logger?.severe(stacktrace); } _connecting = false; @@ -242,18 +242,18 @@ class WebSocket { return; } if (_connecting) { - logger!.info('already connecting'); + logger?.info('already connecting'); return; } - logger!.info('reconnecting..'); + logger?.info('reconnecting..'); _cancelTimers(); try { await connect(); } catch (e) { - logger!.log(Level.SEVERE, e.toString()); + logger?.log(Level.SEVERE, e.toString()); } await Future.delayed( Duration(seconds: min(_retryAttempt * 5, 25)), @@ -265,7 +265,7 @@ class WebSocket { } Future _reconnect() async { - logger!.info('reconnect'); + logger?.info('reconnect'); if (!_reconnecting) { _reconnecting = true; _connectionStatus = ConnectionStatus.connecting; @@ -285,12 +285,12 @@ class WebSocket { } void _healthCheckTimer(_) { - logger!.info('sending health.check'); + logger?.info('sending health.check'); _channel.sink.add("{'type': 'health.check'}"); } void _startHealthCheck() { - logger!.info('start health check monitor'); + logger?.info('start health check monitor'); _healthCheck = Timer.periodic( Duration(seconds: healthCheckInterval), @@ -309,7 +309,7 @@ class WebSocket { if (_manuallyDisconnected) { return; } - logger!.info('disconnecting'); + logger?.info('disconnecting'); _connectionCompleter = Completer(); _cancelTimers(); _reconnecting = false; diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index fa007456..aecbce3b 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -523,10 +523,10 @@ class StreamChatClient { _ws = WebSocket( baseUrl: baseURL, - user: state.user, + user: state.user!, connectParams: { 'api_key': apiKey, - 'authorization': token, + 'authorization': token!, 'stream-auth-type': _authType, 'X-Stream-Client': _userAgent, }, diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 3fae32af..c0f85f94 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -199,11 +199,9 @@ abstract class ChatPersistenceClient { final reactions = cleanedChannelStates.expand((it) => it.messages).expand((it) => [ if (it.ownReactions != null) - ...it.ownReactions?.where((r) => r.userId != null) ?? - [], + ...it.ownReactions!.where((r) => r.userId != null), if (it.latestReactions != null) - ...it.latestReactions?.where((r) => r.userId != null) ?? - [], + ...it.latestReactions!.where((r) => r.userId != null), ]); final users = cleanedChannelStates @@ -213,9 +211,9 @@ abstract class ChatPersistenceClient { .map((m) => [ m.user, if (m.latestReactions != null) - ...m.latestReactions?.map((r) => r.user) ?? [], + ...m.latestReactions!.map((r) => r.user), if (m.ownReactions != null) - ...m.ownReactions?.map((r) => r.user) ?? [], + ...m.ownReactions!.map((r) => r.user), ]) .expand((v) => v), ...cs.read.map((r) => r.user), From d9cd6ea55f37663d4449763950298d2de1440034 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Apr 2021 16:42:34 +0200 Subject: [PATCH 114/599] polish llc --- packages/stream_chat/lib/src/client.dart | 2 +- packages/stream_chat/lib/src/models/event.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index aecbce3b..f3464121 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -481,7 +481,7 @@ class StreamChatClient { _connectionId = event.connectionId; } - if (!event.isLocal!) { + if (!event.isLocal) { final createdAt = event.createdAt; if (_synced && createdAt != null) { await _chatPersistenceClient?.updateConnectionInfo(event); diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index 52092656..a65801e7 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -88,7 +88,7 @@ class Event { final String? parentId; /// True if the event is generated by this client - bool? isLocal; + late final bool isLocal; /// Map of custom channel extraData @JsonKey(includeIfNull: false) From 7226d8c4d3691da153e550a45a0dd510dd7ccb90 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 09:35:11 +0200 Subject: [PATCH 115/599] fix event --- packages/stream_chat/lib/src/models/event.dart | 3 ++- packages/stream_chat/lib/src/models/event.g.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index a65801e7..1ec087a5 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -88,7 +88,8 @@ class Event { final String? parentId; /// True if the event is generated by this client - late final bool isLocal; + @JsonKey(defaultValue: false) + bool isLocal; /// Map of custom channel extraData @JsonKey(includeIfNull: false) diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index c4df9904..66933d30 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -39,7 +39,7 @@ Event _$EventFromJson(Map json) { channelType: json['channel_type'] as String?, parentId: json['parent_id'] as String?, extraData: json['extra_data'] as Map?, - )..isLocal = json['is_local'] as bool?; + )..isLocal = json['is_local'] as bool? ?? false; } Map _$EventToJson(Event instance) { From 79769acef061b010c35acbc19dd5188b23b4f698 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 09:41:41 +0200 Subject: [PATCH 116/599] ignore islocal --- packages/stream_chat/lib/src/models/event.dart | 2 +- packages/stream_chat/lib/src/models/event.g.dart | 3 +-- packages/stream_chat/test/src/models/event_test.dart | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index 1ec087a5..a70b2b3e 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -88,7 +88,7 @@ class Event { final String? parentId; /// True if the event is generated by this client - @JsonKey(defaultValue: false) + @JsonKey(ignore: true) bool isLocal; /// Map of custom channel extraData diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index 66933d30..afe1f7c5 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -39,7 +39,7 @@ Event _$EventFromJson(Map json) { channelType: json['channel_type'] as String?, parentId: json['parent_id'] as String?, extraData: json['extra_data'] as Map?, - )..isLocal = json['is_local'] as bool? ?? false; + ); } Map _$EventToJson(Event instance) { @@ -60,7 +60,6 @@ Map _$EventToJson(Event instance) { 'unread_channels': instance.unreadChannels, 'online': instance.online, 'parent_id': instance.parentId, - 'is_local': instance.isLocal, }; void writeNotNull(String key, dynamic value) { diff --git a/packages/stream_chat/test/src/models/event_test.dart b/packages/stream_chat/test/src/models/event_test.dart index a7c669df..265337be 100644 --- a/packages/stream_chat/test/src/models/event_test.dart +++ b/packages/stream_chat/test/src/models/event_test.dart @@ -1,9 +1,9 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:stream_chat/src/models/event.dart'; import 'package:stream_chat/src/models/own_user.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; void main() { group('src/models/event', () { @@ -77,7 +77,6 @@ void main() { 'total_unread_count': 1, 'unread_channels': 1, 'online': true, - 'is_local': true, 'member': null, 'channel_id': null, 'channel_type': null, From 4a094629d11ca98454869e8afa66b0e6b3b2a223 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 09:50:09 +0200 Subject: [PATCH 117/599] make event model const --- .../stream_chat/lib/src/models/event.dart | 27 +++++----- .../stream_chat/lib/src/models/event.g.dart | 50 ++++++++----------- 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index a70b2b3e..65633814 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -10,7 +10,7 @@ part 'event.g.dart'; @JsonSerializable() class Event { /// Constructor used for json serialization - Event({ + const Event({ this.type, this.cid, this.connectionId, @@ -27,16 +27,18 @@ class Event { this.channelId, this.channelType, this.parentId, - this.extraData, - }) : isLocal = true; + this.extraData = const {}, + this.isLocal = true, + }); /// Create a new instance from a json - factory Event.fromJson(Map json) => - _$EventFromJson(Serialization.moveToExtraDataFromRoot( - json, - topLevelFields, - )) - ..isLocal = false; + factory Event.fromJson(Map json) => _$EventFromJson({ + ...Serialization.moveToExtraDataFromRoot( + json, + topLevelFields, + ), + 'is_local': false, + }); /// The type of the event /// [EventType] contains some predefined constant types @@ -89,11 +91,11 @@ class Event { /// True if the event is generated by this client @JsonKey(ignore: true) - bool isLocal; + final bool isLocal; /// Map of custom channel extraData - @JsonKey(includeIfNull: false) - final Map? extraData; + @JsonKey(defaultValue: {}) + final Map extraData; /// Known top level fields. /// Useful for [Serialization] methods. @@ -114,7 +116,6 @@ class Event { 'channel_id', 'channel_type', 'parent_id', - 'is_local', ]; /// Serialize to json diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index afe1f7c5..e4889978 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -38,39 +38,29 @@ Event _$EventFromJson(Map json) { channelId: json['channel_id'] as String?, channelType: json['channel_type'] as String?, parentId: json['parent_id'] as String?, - extraData: json['extra_data'] as Map?, + extraData: json['extra_data'] as Map? ?? {}, ); } -Map _$EventToJson(Event instance) { - final val = { - 'type': instance.type, - 'cid': instance.cid, - 'channel_id': instance.channelId, - 'channel_type': instance.channelType, - 'connection_id': instance.connectionId, - 'created_at': instance.createdAt?.toIso8601String(), - 'me': instance.me?.toJson(), - 'user': instance.user?.toJson(), - 'message': instance.message?.toJson(), - 'channel': instance.channel?.toJson(), - 'member': instance.member?.toJson(), - 'reaction': instance.reaction?.toJson(), - 'total_unread_count': instance.totalUnreadCount, - 'unread_channels': instance.unreadChannels, - 'online': instance.online, - 'parent_id': instance.parentId, - }; - - void writeNotNull(String key, dynamic value) { - if (value != null) { - val[key] = value; - } - } - - writeNotNull('extra_data', instance.extraData); - return val; -} +Map _$EventToJson(Event instance) => { + 'type': instance.type, + 'cid': instance.cid, + 'channel_id': instance.channelId, + 'channel_type': instance.channelType, + 'connection_id': instance.connectionId, + 'created_at': instance.createdAt?.toIso8601String(), + 'me': instance.me?.toJson(), + 'user': instance.user?.toJson(), + 'message': instance.message?.toJson(), + 'channel': instance.channel?.toJson(), + 'member': instance.member?.toJson(), + 'reaction': instance.reaction?.toJson(), + 'total_unread_count': instance.totalUnreadCount, + 'unread_channels': instance.unreadChannels, + 'online': instance.online, + 'parent_id': instance.parentId, + 'extra_data': instance.extraData, + }; EventChannel _$EventChannelFromJson(Map json) { return EventChannel( From a49d99210ed97ec7cceb052cf90c8b9b3dfb333d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 09:53:39 +0200 Subject: [PATCH 118/599] fix event.islocal --- packages/stream_chat/lib/src/models/event.dart | 14 ++++++-------- packages/stream_chat/lib/src/models/event.g.dart | 2 ++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index 65633814..ba95678d 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -32,13 +32,11 @@ class Event { }); /// Create a new instance from a json - factory Event.fromJson(Map json) => _$EventFromJson({ - ...Serialization.moveToExtraDataFromRoot( - json, - topLevelFields, - ), - 'is_local': false, - }); + factory Event.fromJson(Map json) => + _$EventFromJson(Serialization.moveToExtraDataFromRoot( + json, + topLevelFields, + )); /// The type of the event /// [EventType] contains some predefined constant types @@ -90,7 +88,7 @@ class Event { final String? parentId; /// True if the event is generated by this client - @JsonKey(ignore: true) + @JsonKey(defaultValue: false) final bool isLocal; /// Map of custom channel extraData diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index e4889978..d0bb0d87 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -39,6 +39,7 @@ Event _$EventFromJson(Map json) { channelType: json['channel_type'] as String?, parentId: json['parent_id'] as String?, extraData: json['extra_data'] as Map? ?? {}, + isLocal: json['is_local'] as bool? ?? false, ); } @@ -59,6 +60,7 @@ Map _$EventToJson(Event instance) => { 'unread_channels': instance.unreadChannels, 'online': instance.online, 'parent_id': instance.parentId, + 'is_local': instance.isLocal, 'extra_data': instance.extraData, }; From 253715a246cab25444b34c5621704f86c224573b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 09:54:41 +0200 Subject: [PATCH 119/599] add is_local to top level fields --- packages/stream_chat/lib/src/models/event.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index ba95678d..7e57de60 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -114,6 +114,7 @@ class Event { 'channel_id', 'channel_type', 'parent_id', + 'is_local', ]; /// Serialize to json From a273730b09dcc243626da6f20a3591073aafb575 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 20 Apr 2021 13:54:29 +0530 Subject: [PATCH 120/599] fix event tests Signed-off-by: Sahil Kumar --- packages/stream_chat/test/src/models/event_test.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/test/src/models/event_test.dart b/packages/stream_chat/test/src/models/event_test.dart index 265337be..16e56cc3 100644 --- a/packages/stream_chat/test/src/models/event_test.dart +++ b/packages/stream_chat/test/src/models/event_test.dart @@ -35,7 +35,8 @@ void main() { "online": false, "image": "https://getstream.io/random_svg/?name=Dry+meadow", "name": "Dry meadow" - } + }, + "is_local": false } '''; @@ -47,6 +48,7 @@ void main() { expect(event.createdAt, isA()); expect(event.me, isA()); expect(event.user, isA()); + expect(event.isLocal, false); }); test('should serialize to json correctly', () { @@ -81,6 +83,7 @@ void main() { 'channel_id': null, 'channel_type': null, 'parent_id': null, + 'is_local': true, }, ); }); From 8136b50c78d43b6d22b3dd7b5dc7740e49254e83 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 20 Apr 2021 13:56:01 +0530 Subject: [PATCH 121/599] fix analyzer warnings Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/client.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index f3464121..403e5a2b 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -548,7 +548,7 @@ class StreamChatClient { ); if (status == ConnectionStatus.connected) { - handleEvent(Event( + handleEvent(const Event( type: EventType.connectionRecovered, online: true, )); From 9440bd4793e124c262803c0e291e676d0a0dee5f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 20 Apr 2021 13:57:49 +0530 Subject: [PATCH 122/599] modify event tests Signed-off-by: Sahil Kumar --- packages/stream_chat/test/src/models/event_test.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/stream_chat/test/src/models/event_test.dart b/packages/stream_chat/test/src/models/event_test.dart index 16e56cc3..96efa844 100644 --- a/packages/stream_chat/test/src/models/event_test.dart +++ b/packages/stream_chat/test/src/models/event_test.dart @@ -35,8 +35,7 @@ void main() { "online": false, "image": "https://getstream.io/random_svg/?name=Dry+meadow", "name": "Dry meadow" - }, - "is_local": false + } } '''; From 74fb9d8dd83441d27a715f1c7f3529b3090f3824 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 8 Apr 2021 13:22:21 +0530 Subject: [PATCH 123/599] added non-nullability for core --- .../lib/src/channel_list_core.dart | 36 +++++++-------- .../lib/src/channels_bloc.dart | 39 ++++++++-------- .../lib/src/lazy_load_scroll_view.dart | 24 +++++----- .../lib/src/message_list_core.dart | 34 +++++++------- .../lib/src/message_search_bloc.dart | 33 +++++++------- .../lib/src/message_search_list_core.dart | 36 +++++++-------- .../lib/src/stream_channel.dart | 40 ++++++++--------- .../lib/src/stream_chat_core.dart | 28 ++++++------ .../lib/src/typedef.dart | 2 +- .../lib/src/user_list_core.dart | 45 ++++++++++--------- .../lib/src/users_bloc.dart | 26 +++++------ .../stream_chat_flutter_core/pubspec.yaml | 7 +-- .../test/matchers/channel_matcher.dart | 4 +- .../get_message_response_matcher.dart | 4 +- .../test/matchers/message_matcher.dart | 4 +- .../test/matchers/users_matcher.dart | 4 +- 16 files changed, 185 insertions(+), 181 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 3adea312..70d97df7 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -57,11 +57,11 @@ import 'package:stream_chat_flutter_core/src/typedef.dart'; class ChannelListCore extends StatefulWidget { /// Instantiate a new ChannelListView const ChannelListCore({ - Key key, - @required this.errorBuilder, - @required this.emptyBuilder, - @required this.loadingBuilder, - @required this.listBuilder, + Key? key, + required this.errorBuilder, + required this.emptyBuilder, + required this.loadingBuilder, + required this.listBuilder, this.filter, this.options, this.sort, @@ -91,7 +91,7 @@ class ChannelListCore extends StatefulWidget { /// Use [ChannelListController.loadData] and /// [ChannelListController.paginateData] respectively for reloading and /// pagination. - final ChannelListController channelListController; + final ChannelListController? channelListController; /// The builder that will be used in case of error final ErrorBuilder errorBuilder; @@ -100,7 +100,7 @@ class ChannelListCore extends StatefulWidget { final WidgetBuilder loadingBuilder; /// The builder which is used when list of channels loads - final Function(BuildContext, List) listBuilder; + final Function(BuildContext, List) listBuilder; /// The builder used when the channel list is empty. final WidgetBuilder emptyBuilder; @@ -108,20 +108,20 @@ class ChannelListCore extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map filter; + final Map? filter; /// Query channels options. /// /// state: if true returns the Channel state /// watch: if true listen to changes to this Channel in real time. - final Map options; + final Map? options; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be /// provided. /// You can sort based on last_updated, last_message_at, updated_at, created /// _at or member_count. Direction can be ascending or descending. - final List> sort; + final List>? sort; /// Pagination parameters /// limit: the number of channels to return (max is 30) @@ -142,10 +142,10 @@ class ChannelListCoreState extends State { return _buildListView(channelsBloc); } - StreamBuilder> _buildListView( + StreamBuilder> _buildListView( ChannelsBlocState channelsBlocState, ) => - StreamBuilder>( + StreamBuilder>( stream: channelsBlocState.channelsStream, builder: (context, snapshot) { if (snapshot.hasError) { @@ -154,7 +154,7 @@ class ChannelListCoreState extends State { if (!snapshot.hasData) { return widget.loadingBuilder(context); } - final channels = snapshot.data; + final channels = snapshot.data!; if (channels.isEmpty) { return widget.emptyBuilder(context); } @@ -186,7 +186,7 @@ class ChannelListCoreState extends State { ); } - StreamSubscription _subscription; + late StreamSubscription _subscription; @override void initState() { @@ -203,8 +203,8 @@ class ChannelListCoreState extends State { .listen((event) => loadData()); if (widget.channelListController != null) { - widget.channelListController.loadData = loadData; - widget.channelListController.paginateData = paginateData; + widget.channelListController!.loadData = loadData; + widget.channelListController!.paginateData = paginateData; } } @@ -233,10 +233,10 @@ class ChannelListCoreState extends State { class ChannelListController { /// This function calls Stream's servers to load a list of channels. /// If there is existing data, calling this function causes a reload. - AsyncCallback loadData; + AsyncCallback? loadData; /// This function is used to load another page of data. Note, [loadData] /// should be used to populate the initial page of data. Calling /// [paginateData] performs a query to load subsequent pages. - AsyncCallback paginateData; + AsyncCallback? paginateData; } 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 2b4968eb..2e42c0af 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -20,8 +20,8 @@ class ChannelsBloc extends StatefulWidget { /// Creates a new [ChannelsBloc]. The parameter [child] must be supplied and /// not null. const ChannelsBloc({ - Key key, - @required this.child, + Key? key, + required this.child, this.lockChannelsOrder = false, this.channelsComparator, this.shouldAddChannel, @@ -36,18 +36,18 @@ class ChannelsBloc extends StatefulWidget { final bool lockChannelsOrder; /// Comparator used to sort the channels when a message.new event is received - final Comparator channelsComparator; + final Comparator? channelsComparator; /// Function used to evaluate if a channel should be added to the list when a /// message.new event is received - final bool Function(Event) shouldAddChannel; + final bool Function(Event)? shouldAddChannel; @override ChannelsBlocState createState() => ChannelsBlocState(); /// Use this method to get the current [ChannelsBlocState] instance static ChannelsBlocState of(BuildContext context) { - ChannelsBlocState streamChatState; + ChannelsBlocState? streamChatState; streamChatState = context.findAncestorStateOfType(); @@ -69,14 +69,15 @@ class ChannelsBlocState extends State } /// The current channel list - List get channels => _channelsController.value; + List? get channels => _channelsController.value as List?; /// The current channel list as a stream - Stream> get channelsStream => _channelsController.stream; + Stream> get channelsStream => _channelsController.stream; final _queryChannelsLoadingController = BehaviorSubject.seeded(false); - final _channelsController = BehaviorSubject>(); + final BehaviorSubject> _channelsController = + BehaviorSubject>(); /// The stream notifying the state of queryChannel call Stream get queryChannelsLoading => @@ -88,10 +89,10 @@ class ChannelsBlocState extends State /// Calls [client.queryChannels] updating [queryChannelsLoading] stream Future queryChannels({ - Map filter, - List> sortOptions, - PaginationParams paginationParams, - Map options, + Map? filter, + List>? sortOptions, + PaginationParams? paginationParams, + Map? options, }) async { final client = StreamChatCore.of(context).client; @@ -110,10 +111,10 @@ class ChannelsBlocState extends State final oldChannels = List.from(channels ?? []); var newChannels = []; await for (final channels in client.queryChannels( - filter: filter, - sort: sortOptions, - options: options, - paginationParams: paginationParams, + filter: filter!, + sort: sortOptions!, + options: options!, + paginationParams: paginationParams!, )) { newChannels = channels; if (clear) { @@ -123,7 +124,7 @@ class ChannelsBlocState extends State _channelsController.add(temp); } if (_channelsController.hasValue && - _queryChannelsLoadingController.value) { + _queryChannelsLoadingController.value!) { _queryChannelsLoadingController.sink.add(false); } } @@ -149,8 +150,8 @@ class ChannelsBlocState extends State if (!widget.lockChannelsOrder) { _subscriptions.add(client.on(EventType.messageNew).listen((e) { - final newChannels = List.from(channels ?? []); - final index = newChannels.indexWhere((c) => c.cid == e.cid); + final newChannels = List.from(channels ?? []); + final index = newChannels.indexWhere((c) => c!.cid == e.cid); if (index != -1) { if (index > 0) { final channel = newChannels.removeAt(index); diff --git a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart index 808c1dae..1f07c8ce 100644 --- a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart @@ -9,8 +9,8 @@ class LazyLoadScrollView extends StatefulWidget { /// Creates a new instance of [LazyLoadScrollView]. The parameter [child] /// must be supplied and not null. const LazyLoadScrollView({ - Key key, - @required this.child, + Key? key, + required this.child, this.onStartOfPage, this.onEndOfPage, this.onPageScrollStart, @@ -24,19 +24,19 @@ class LazyLoadScrollView extends StatefulWidget { final Widget child; /// Called when the [child] reaches the start of the list - final AsyncCallback onStartOfPage; + final AsyncCallback? onStartOfPage; /// Called when the [child] reaches the end of the list - final AsyncCallback onEndOfPage; + final AsyncCallback? onEndOfPage; /// Called when the list scrolling starts - final VoidCallback onPageScrollStart; + final VoidCallback? onPageScrollStart; /// Called when the list scrolling ends - final VoidCallback onPageScrollEnd; + final VoidCallback? onPageScrollEnd; /// Called every time the [child] is in-between the list - final VoidCallback onInBetweenOfPage; + final VoidCallback? onInBetweenOfPage; /// The offset to take into account when triggering [onEndOfPage]/[onStartOfPage] in pixels final double scrollOffset; @@ -59,13 +59,13 @@ class _LazyLoadScrollViewState extends State { bool _onNotification(ScrollNotification notification) { if (notification is ScrollStartNotification) { if (widget.onPageScrollStart != null) { - widget.onPageScrollStart(); + widget.onPageScrollStart!(); return true; } } if (notification is ScrollEndNotification) { if (widget.onPageScrollEnd != null) { - widget.onPageScrollEnd(); + widget.onPageScrollEnd!(); return true; } } @@ -78,7 +78,7 @@ class _LazyLoadScrollViewState extends State { if (pixels > (minScrollExtent + scrollOffset) && pixels < (maxScrollExtent - scrollOffset)) { if (widget.onInBetweenOfPage != null) { - widget.onInBetweenOfPage(); + widget.onInBetweenOfPage!(); return true; } } @@ -117,7 +117,7 @@ class _LazyLoadScrollViewState extends State { if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) { if (widget.onEndOfPage != null) { _loadMoreStatus = _LoadingStatus.loading; - widget.onEndOfPage().whenComplete(() { + widget.onEndOfPage!().whenComplete(() { _loadMoreStatus = _LoadingStatus.stable; }); } @@ -128,7 +128,7 @@ class _LazyLoadScrollViewState extends State { if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) { if (widget.onStartOfPage != null) { _loadMoreStatus = _LoadingStatus.loading; - widget.onStartOfPage().whenComplete(() { + widget.onStartOfPage!().whenComplete(() { _loadMoreStatus = _LoadingStatus.stable; }); } diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 53844447..65209297 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -61,11 +61,11 @@ import 'package:stream_chat_flutter_core/src/typedef.dart'; class MessageListCore extends StatefulWidget { /// Instantiate a new [MessageListView]. const MessageListCore({ - Key key, - @required this.loadingBuilder, - @required this.emptyBuilder, - @required this.messageListBuilder, - @required this.errorWidgetBuilder, + Key? key, + required this.loadingBuilder, + required this.emptyBuilder, + required this.messageListBuilder, + required this.errorWidgetBuilder, this.showScrollToBottom = true, this.parentMessage, this.messageListController, @@ -84,7 +84,7 @@ class MessageListCore extends StatefulWidget { /// A [MessageListController] allows pagination. /// Use [ChannelListController.paginateData] pagination. - final MessageListController messageListController; + final MessageListController? messageListController; /// Function called when messages are fetched final Widget Function(BuildContext, List) messageListBuilder; @@ -108,10 +108,10 @@ class MessageListCore extends StatefulWidget { /// If the current message belongs to a `thread`, this property represents the /// first message or the parent of the conversation. - final Message parentMessage; + final Message? parentMessage; /// Predicate used to filter messages - final bool Function(Message) messageFilter; + final bool Function(Message)? messageFilter; @override MessageListCoreState createState() => MessageListCoreState(); @@ -119,7 +119,7 @@ class MessageListCore extends StatefulWidget { /// The current state of the [MessageListCore]. class MessageListCoreState extends State { - StreamChannelState _streamChannel; + late StreamChannelState _streamChannel; bool get _upToDate => _streamChannel.channel.state.isUpToDate; @@ -133,8 +133,8 @@ class MessageListCoreState extends State { Widget build(BuildContext context) { final messagesStream = _isThreadConversation ? _streamChannel.channel.state.threadsStream - .where((threads) => threads.containsKey(widget.parentMessage.id)) - .map((threads) => threads[widget.parentMessage.id]) + .where((threads) => threads.containsKey(widget.parentMessage!.id)) + .map((threads) => threads[widget.parentMessage!.id]) : _streamChannel.channel.state?.messagesStream; bool defaultFilter(Message m) { @@ -144,7 +144,7 @@ class MessageListCoreState extends State { return true; } - return StreamBuilder>( + return StreamBuilder?>( stream: messagesStream?.map((messages) => messages?.where(widget.messageFilter ?? defaultFilter)?.toList()), builder: (context, snapshot) { @@ -171,11 +171,11 @@ class MessageListCoreState extends State { /// /// Optionally pass the fetch direction, defaults to [QueryDirection.bottom] Future paginateData( - {QueryDirection direction = QueryDirection.bottom}) { + {QueryDirection? direction = QueryDirection.bottom}) { if (!_isThreadConversation) { return _streamChannel.queryMessages(direction: direction); } else { - return _streamChannel.getReplies(widget.parentMessage.id); + return _streamChannel.getReplies(widget.parentMessage!.id); } } @@ -184,11 +184,11 @@ class MessageListCoreState extends State { _streamChannel = StreamChannel.of(context); if (_isThreadConversation) { - _streamChannel.getReplies(widget.parentMessage.id); + _streamChannel.getReplies(widget.parentMessage!.id); } if (widget.messageListController != null) { - widget.messageListController.paginateData = paginateData; + widget.messageListController!.paginateData = paginateData; } super.initState(); @@ -206,5 +206,5 @@ class MessageListCoreState extends State { /// Controller used for paginating data in [ChannelListView] class MessageListController { /// Call this function to load further data - Future Function({QueryDirection direction}) paginateData; + Future Function({QueryDirection? direction})? paginateData; } 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 1c5c78de..d41086cd 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 @@ -13,9 +13,9 @@ import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; class MessageSearchBloc extends StatefulWidget { /// Instantiate a new MessageSearchBloc const MessageSearchBloc({ - Key key, - @required this.child, - }) : assert(child != null, 'Parameter child should not be null.'), + Key? key, + required this.child, + }) : assert(child != null, 'Parameter child should not be null.'), super(key: key); /// The widget child @@ -26,7 +26,7 @@ class MessageSearchBloc extends StatefulWidget { /// Use this method to get the current [MessageSearchBlocState] instance static MessageSearchBlocState of(BuildContext context) { - MessageSearchBlocState state; + MessageSearchBlocState? state; state = context.findAncestorStateOfType(); @@ -42,7 +42,7 @@ class MessageSearchBloc extends StatefulWidget { class MessageSearchBlocState extends State with AutomaticKeepAliveClientMixin { /// The current messages list - List get messageResponses => _messageResponses.value; + List? get messageResponses => _messageResponses.value; /// The current messages list as a stream Stream> get messagesStream => @@ -59,11 +59,11 @@ class MessageSearchBlocState extends State /// Calls [StreamChatClient.search] updating /// [messagesStream] and [queryMessagesLoading] stream Future search({ - Map filter, - Map messageFilter, - List sort, - String query, - PaginationParams pagination, + Map? filter, + Map? messageFilter, + List? sort, + String? query, + PaginationParams? pagination, }) async { final client = StreamChatCore.of(context).client; @@ -80,11 +80,11 @@ class MessageSearchBlocState extends State final oldMessages = List.from(messageResponses ?? []); final messages = await client.search( - filter, - sort: sort, - query: query, - paginationParams: pagination, - messageFilters: messageFilter, + filter!, + sort: sort!, + query: query!, + paginationParams: pagination!, + messageFilters: messageFilter!, ); if (clear) { @@ -93,7 +93,8 @@ class MessageSearchBlocState extends State final temp = oldMessages + messages.results; _messageResponses.add(temp); } - if (_messageResponses.hasValue && _queryMessagesLoadingController.value) { + if (_messageResponses.hasValue && + _queryMessagesLoadingController.value!) { _queryMessagesLoadingController.add(false); } } catch (e, stk) { diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index 72c5ea86..67019677 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -42,11 +42,11 @@ class MessageSearchListCore extends StatefulWidget { /// * [loadingBuilder] /// * [childBuilder] const MessageSearchListCore({ - Key key, - @required this.emptyBuilder, - @required this.errorBuilder, - @required this.loadingBuilder, - @required this.childBuilder, + Key? key, + required this.emptyBuilder, + required this.errorBuilder, + required this.loadingBuilder, + required this.childBuilder, this.messageQuery, this.filters, this.sortOptions, @@ -63,36 +63,36 @@ class MessageSearchListCore extends StatefulWidget { /// Use [MessageSearchListController.loadData] and /// [MessageSearchListController.paginateData] respectively for reloading and /// pagination. - final MessageSearchListController messageSearchListController; + final MessageSearchListController? messageSearchListController; /// Message String to search on - final String messageQuery; + final String? messageQuery; /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map filters; + final Map? filters; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be /// provided. /// You can sort based on last_updated, last_message_at, updated_at, created_ /// at or member_count. Direction can be ascending or descending. - final List sortOptions; + final List? sortOptions; /// Pagination parameters /// limit: the number of users to return (max is 30) /// offset: the offset (max is 1000) /// message_limit: how many messages should be included to each channel - final PaginationParams paginationParams; + final PaginationParams? paginationParams; /// The message query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map messageFilters; + final Map? messageFilters; /// The builder that is used when the search messages are fetched - final Widget Function(List) childBuilder; + final Widget Function(List?) childBuilder; /// The builder used when the channel list is empty. final WidgetBuilder emptyBuilder; @@ -114,8 +114,8 @@ class MessageSearchListCoreState extends State { super.didChangeDependencies(); loadData(); if (widget.messageSearchListController != null) { - widget.messageSearchListController.loadData = loadData; - widget.messageSearchListController.paginateData = paginateData; + widget.messageSearchListController!.loadData = loadData; + widget.messageSearchListController!.paginateData = paginateData; } } @@ -135,7 +135,7 @@ class MessageSearchListCoreState extends State { if (!snapshot.hasData) { return widget.loadingBuilder(context); } - final items = snapshot.data; + final items = snapshot.data!; if (items.isEmpty) { return widget.emptyBuilder(context); } @@ -161,7 +161,7 @@ class MessageSearchListCoreState extends State { return messageSearchBloc.search( filter: widget.filters, sort: widget.sortOptions, - pagination: widget.paginationParams.copyWith( + pagination: widget.paginationParams!.copyWith( offset: messageSearchBloc.messageResponses?.length ?? 0, ), query: widget.messageQuery, @@ -187,8 +187,8 @@ class MessageSearchListCoreState extends State { /// Controller used for paginating data in [ChannelListView] class MessageSearchListController { /// Call this function to reload data - AsyncCallback loadData; + AsyncCallback? loadData; /// Call this function to load further data - AsyncCallback paginateData; + AsyncCallback? paginateData; } 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 58a47d0a..f4b6e6c7 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; @@ -21,9 +22,9 @@ class StreamChannel extends StatefulWidget { /// Creates a new instance of [StreamChannel]. Both [child] and [client] must /// be supplied and not null. const StreamChannel({ - Key key, - @required this.child, - @required this.channel, + Key? key, + required this.child, + required this.channel, this.showLoading = true, this.initialMessageId, }) : assert(child != null, 'Child should not be null'), @@ -40,11 +41,11 @@ class StreamChannel extends StatefulWidget { final bool showLoading; /// If passed the channel will load from this particular message. - final String initialMessageId; + final String? initialMessageId; /// Use this method to get the current [StreamChannelState] instance static StreamChannelState of(BuildContext context) { - StreamChannelState streamChannelState; + StreamChannelState? streamChannelState; streamChannelState = context.findAncestorStateOfType(); @@ -67,7 +68,7 @@ class StreamChannelState extends State { Channel get channel => widget.channel; /// InitialMessageId - String get initialMessageId => widget.initialMessageId; + String? get initialMessageId => widget.initialMessageId; /// Current channel state stream Stream get channelStateStream => @@ -146,7 +147,7 @@ class StreamChannelState extends State { } /// Calls [channel.query] updating [queryMessage] stream - Future queryMessages({QueryDirection direction = QueryDirection.top}) { + Future queryMessages({QueryDirection? direction = QueryDirection.top}) { if (direction == QueryDirection.top) return _queryTopMessages(); return _queryBottomMessages(); } @@ -157,12 +158,12 @@ class StreamChannelState extends State { int limit = 50, bool preferOffline = false, }) async { - if (_topPaginationEnded || _queryTopMessagesController.value) return; + if (_topPaginationEnded || _queryTopMessagesController.value!) return; _queryTopMessagesController.add(true); - Message message; + late Message message; if (channel.state.threads.containsKey(parentId)) { - final thread = channel.state.threads[parentId]; + final thread = channel.state.threads[parentId]!; if (thread.isNotEmpty) { message = thread.first; } @@ -202,7 +203,7 @@ class StreamChannelState extends State { /// Loads channel at specific message Future loadChannelAtMessage( - String messageId, { + String? messageId, { int before = 20, int after = 20, bool preferOffline = false, @@ -214,13 +215,13 @@ class StreamChannelState extends State { preferOffline: preferOffline, ); - Future _queryAtMessage({ - String messageId, + Future> _queryAtMessage({ + String? messageId, int before = 20, int after = 20, bool preferOffline = false, }) async { - if (channel.state == null) return; + if (channel.state == null) return []; channel.state.isUpToDate = false; channel.state.truncate(); @@ -232,7 +233,7 @@ class StreamChannelState extends State { preferOffline: preferOffline, ); channel.state.isUpToDate = true; - return; + return []; } return Future.wait([ @@ -284,9 +285,8 @@ class StreamChannelState extends State { /// Future getMessage(String messageId) async { - var message = channel.state.messages.firstWhere( + var message = channel.state.messages.firstWhereOrNull( (it) => it.id == messageId, - orElse: () => null, ); if (message == null) { final response = await channel.getMessagesById([messageId]); @@ -298,7 +298,7 @@ class StreamChannelState extends State { /// Reloads the channel with latest message Future reloadChannel() => _queryAtMessage(before: 30); - List> _futures; + late List> _futures; Future get _loadChannelAtMessage async { try { @@ -358,9 +358,9 @@ class StreamChannelState extends State { } return Center(child: Text(message)); } - final initialized = snapshot.data[0]; + final initialized = snapshot.data![0]; // ignore: avoid_bool_literals_in_conditional_expressions - final dataLoaded = initialMessageId == null ? true : snapshot.data[1]; + final dataLoaded = initialMessageId == null ? true : snapshot.data![1]; if (widget.showLoading && (!initialized || !dataLoaded)) { return const Center( child: CircularProgressIndicator(), diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index 15c2ddf3..42ec8a21 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -38,9 +38,9 @@ class StreamChatCore extends StatefulWidget { /// [StreamChatCore] is a stateful widget which reacts to system events and /// updates Stream's connection status accordingly. const StreamChatCore({ - Key key, - @required this.client, - @required this.child, + Key? key, + required this.client, + required this.child, this.onBackgroundEventReceived, this.backgroundKeepAlive = const Duration(minutes: 1), }) : assert(client != null, 'Stream Chat Client should not be null'), @@ -61,14 +61,14 @@ class StreamChatCore extends StatefulWidget { /// Handler called whenever the [client] receives a new [Event] while the app /// is in background. Can be used to display various notifications depending /// upon the [Event.type] - final EventHandler onBackgroundEventReceived; + final EventHandler? onBackgroundEventReceived; @override StreamChatCoreState createState() => StreamChatCoreState(); /// Use this method to get the current [StreamChatCoreState] instance static StreamChatCoreState of(BuildContext context) { - StreamChatCoreState streamChatState; + StreamChatCoreState? streamChatState; streamChatState = context.findAncestorStateOfType(); @@ -87,24 +87,24 @@ class StreamChatCoreState extends State /// Initialized client used throughout the application. StreamChatClient get client => widget.client; - Timer _disconnectTimer; + Timer? _disconnectTimer; @override Widget build(BuildContext context) => widget.child; /// The current user - User get user => client.state?.user; + User? get user => client.state?.user; /// The current user as a stream - Stream get userStream => client.state?.userStream; + Stream? get userStream => client.state?.userStream; @override void initState() { super.initState(); - WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance!.addObserver(this); } - StreamSubscription _eventSubscription; + StreamSubscription? _eventSubscription; @override void didChangeAppLifecycleState(AppLifecycleState state) { @@ -119,15 +119,15 @@ class StreamChatCoreState extends State ); void onTimerComplete() { - _eventSubscription.cancel(); + _eventSubscription!.cancel(); client.disconnect(); } _disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete); } else if (state == AppLifecycleState.resumed) { if (_disconnectTimer?.isActive == true) { - _eventSubscription.cancel(); - _disconnectTimer.cancel(); + _eventSubscription!.cancel(); + _disconnectTimer!.cancel(); } else { if (client.wsConnectionStatus == ConnectionStatus.disconnected) { client.connect(); @@ -139,7 +139,7 @@ class StreamChatCoreState extends State @override void dispose() { - WidgetsBinding.instance.removeObserver(this); + WidgetsBinding.instance!.removeObserver(this); _eventSubscription?.cancel(); _disconnectTimer?.cancel(); super.dispose(); diff --git a/packages/stream_chat_flutter_core/lib/src/typedef.dart b/packages/stream_chat_flutter_core/lib/src/typedef.dart index ace8d7e6..d5e7af2c 100644 --- a/packages/stream_chat_flutter_core/lib/src/typedef.dart +++ b/packages/stream_chat_flutter_core/lib/src/typedef.dart @@ -4,7 +4,7 @@ import 'package:stream_chat/stream_chat.dart'; /// A signature for a callback which exposes an error and returns a function. /// This Callback can be used in cases where an API failure occurs and the /// widget is unable to render data. -typedef ErrorBuilder = Widget Function(BuildContext context, Object error); +typedef ErrorBuilder = Widget Function(BuildContext context, Object? error); /// A Signature for a handler function which will expose a [event]. typedef EventHandler = void Function(Event event); diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index f1bd68ee..d2412b97 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -57,11 +57,11 @@ import 'package:stream_chat_flutter_core/src/users_bloc.dart'; class UserListCore extends StatefulWidget { /// Instantiate a new [UserListCore] const UserListCore({ - @required this.errorBuilder, - @required this.emptyBuilder, - @required this.loadingBuilder, - @required this.listBuilder, - Key key, + required this.errorBuilder, + required this.emptyBuilder, + required this.loadingBuilder, + required this.listBuilder, + Key? key, this.filter, this.options, this.sort, @@ -77,10 +77,10 @@ class UserListCore extends StatefulWidget { /// A [UserListController] allows reloading and pagination. /// Use [UserListController.loadData] and [UserListController.paginateData] /// respectively for reloading and pagination. - final UserListController userListController; + final UserListController? userListController; /// The builder that will be used in case of error - final Widget Function(Object error) errorBuilder; + final Widget Function(Object? error) errorBuilder; /// The builder that will be used to build the list final Widget Function(BuildContext context, List users) listBuilder; @@ -94,25 +94,25 @@ class UserListCore extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map filter; + final Map? filter; /// Query channels options. /// /// state: if true returns the Channel state /// watch: if true listen to changes to this Channel in real time. - final Map options; + final Map? options; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be /// provided. You can sort based on last_updated, last_message_at, updated_at, /// created_at or member_count. Direction can be ascending or descending. - final List sort; + final List? sort; /// Pagination parameters /// limit: the number of users to return (max is 30) /// offset: the offset (max is 1000) /// message_limit: how many messages should be included to each channel - final PaginationParams pagination; + final PaginationParams? pagination; /// Set it to true to group users by their first character /// @@ -131,8 +131,8 @@ class UserListCoreState extends State super.didChangeDependencies(); loadData(); if (widget.userListController != null) { - widget.userListController.loadData = loadData; - widget.userListController.paginateData = paginateData; + widget.userListController!.loadData = loadData; + widget.userListController!.paginateData = paginateData; } } @@ -158,14 +158,14 @@ class UserListCoreState extends State } final groupedUsers = >{}; for (final e in temp) { - final alphabet = e.name[0]?.toUpperCase(); + final alphabet = e.name[0].toUpperCase(); groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e]; } final items = []; for (final key in groupedUsers.keys) { items ..add(ListHeaderItem(key)) - ..addAll(groupedUsers[key].map((e) => ListUserItem(e))); + ..addAll(groupedUsers[key]!.map((e) => ListUserItem(e))); } return items; } @@ -185,7 +185,7 @@ class UserListCoreState extends State if (!snapshot.hasData) { return widget.loadingBuilder(context); } - final items = snapshot.data; + final items = snapshot.data!; if (items.isEmpty) { return widget.emptyBuilder(context); } @@ -210,7 +210,7 @@ class UserListCoreState extends State return _usersBloc.queryUsers( filter: widget.filter, sort: widget.sort, - pagination: widget.pagination.copyWith( + pagination: widget.pagination!.copyWith( offset: _usersBloc.users?.length ?? 0, ), options: widget.options, @@ -235,7 +235,7 @@ class UserListCoreState extends State /// with `USER`. abstract class ListItem { /// Unique key per list item - String get key { + String? get key { if (this is ListHeaderItem) { final header = (this as ListHeaderItem).heading; return 'HEADER-${header.toLowerCase()}'; @@ -250,8 +250,8 @@ abstract class ListItem { /// Helper function to build widget based on ListItem type // ignore: missing_return Widget when({ - @required Widget Function(String heading) headerItem, - @required Widget Function(User user) userItem, + required Widget Function(String heading) headerItem, + required Widget Function(User user) userItem, }) { if (this is ListHeaderItem) { return headerItem((this as ListHeaderItem).heading); @@ -259,6 +259,7 @@ abstract class ListItem { if (this is ListUserItem) { return userItem((this as ListUserItem).user); } + return Container(); } } @@ -283,8 +284,8 @@ class ListUserItem extends ListItem { /// Controller used for paginating data in [ChannelListView] class UserListController { /// Call this function to reload data - AsyncCallback loadData; + AsyncCallback? loadData; /// Call this function to load further data - AsyncCallback paginateData; + AsyncCallback? paginateData; } 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 92881619..2470fba7 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -14,8 +14,8 @@ class UsersBloc extends StatefulWidget { /// Instantiate a new [UsersBloc]. The parameter [child] must be supplied and /// not null. const UsersBloc({ - @required this.child, - Key key, + required this.child, + Key? key, }) : assert( child != null, 'When constructing a UsersBloc, the parameter ' @@ -30,7 +30,7 @@ class UsersBloc extends StatefulWidget { /// Use this method to get the current [UsersBlocState] instance static UsersBlocState of(BuildContext context) { - UsersBlocState state; + UsersBlocState? state; state = context.findAncestorStateOfType(); @@ -46,7 +46,7 @@ class UsersBloc extends StatefulWidget { class UsersBlocState extends State with AutomaticKeepAliveClientMixin { /// The current users list - List get users => _usersController.value; + List? get users => _usersController.value; /// The current users list as a stream Stream> get usersStream => _usersController.stream; @@ -62,10 +62,10 @@ class UsersBlocState extends State /// online/offline. /// [API Reference](https://getstream.io/chat/docs/flutter-dart/query_users/?language=dart) Future queryUsers({ - Map filter, - List sort, - Map options, - PaginationParams pagination, + Map? filter, + List? sort, + Map? options, + PaginationParams? pagination, }) async { final client = StreamChatCore.of(context).client; @@ -83,10 +83,10 @@ class UsersBlocState extends State final oldUsers = List.from(users ?? []); final usersResponse = await client.queryUsers( - filter: filter, - sort: sort, - options: options, - pagination: pagination, + filter: filter!, + sort: sort!, + options: options!, + pagination: pagination!, ); if (clear) { @@ -95,7 +95,7 @@ class UsersBlocState extends State final temp = oldUsers + usersResponse.users; _usersController.add(temp); } - if (_usersController.hasValue && _queryUsersLoadingController.value) { + if (_usersController.hasValue && _queryUsersLoadingController.value!) { _queryUsersLoadingController.add(false); } } catch (e, stk) { diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 4b3d81de..195c4ee3 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -8,7 +8,7 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues publish_to: none environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' flutter: ">=1.17.0" dependencies: @@ -17,14 +17,15 @@ dependencies: meta: ^1.2.4 rxdart: ^0.26.0 stream_chat: ^1.5.0 + collection: ^1.15.0-nullsafety.4 dependency_overrides: stream_chat: path: ../stream_chat dev_dependencies: - fake_async: ^1.1.0 + fake_async: ^1.2.0 flutter_test: sdk: flutter - mockito: ^4.1.3 + mockito: ^5.0.3 diff --git a/packages/stream_chat_flutter_core/test/matchers/channel_matcher.dart b/packages/stream_chat_flutter_core/test/matchers/channel_matcher.dart index f371acff..f6d7fb50 100644 --- a/packages/stream_chat_flutter_core/test/matchers/channel_matcher.dart +++ b/packages/stream_chat_flutter_core/test/matchers/channel_matcher.dart @@ -7,7 +7,7 @@ Matcher isSameChannelAs(Channel targetChannel) => class _IsSameChannelAs extends Matcher { const _IsSameChannelAs({ - @required this.targetChannel, + required this.targetChannel, }) : assert(targetChannel != null, ''); final Channel targetChannel; @@ -26,7 +26,7 @@ Matcher isSameChannelListAs(List targetChannelList) => class _IsSameChannelListAs extends Matcher { const _IsSameChannelListAs({ - @required this.targetChannelList, + required this.targetChannelList, }) : assert(targetChannelList != null, ''); final List targetChannelList; diff --git a/packages/stream_chat_flutter_core/test/matchers/get_message_response_matcher.dart b/packages/stream_chat_flutter_core/test/matchers/get_message_response_matcher.dart index bd89420b..42ea0d34 100644 --- a/packages/stream_chat_flutter_core/test/matchers/get_message_response_matcher.dart +++ b/packages/stream_chat_flutter_core/test/matchers/get_message_response_matcher.dart @@ -7,7 +7,7 @@ Matcher isSameMessageResponseAs(GetMessageResponse targetResponse) => class _IsSameMessageResponseAs extends Matcher { const _IsSameMessageResponseAs({ - @required this.targetResponse, + required this.targetResponse, }) : assert(targetResponse != null, ''); final GetMessageResponse targetResponse; @@ -28,7 +28,7 @@ Matcher isSameMessageResponseListAs( class _IsSameMessageResponseListAs extends Matcher { const _IsSameMessageResponseListAs({ - @required this.targetResponseList, + required this.targetResponseList, }) : assert(targetResponseList != null, ''); final List targetResponseList; diff --git a/packages/stream_chat_flutter_core/test/matchers/message_matcher.dart b/packages/stream_chat_flutter_core/test/matchers/message_matcher.dart index 12a3e19d..69a24f49 100644 --- a/packages/stream_chat_flutter_core/test/matchers/message_matcher.dart +++ b/packages/stream_chat_flutter_core/test/matchers/message_matcher.dart @@ -7,7 +7,7 @@ Matcher isSameMessageAs(Message targetMessage) => class _IsSameMessageAs extends Matcher { const _IsSameMessageAs({ - @required this.targetMessage, + required this.targetMessage, }) : assert(targetMessage != null, ''); final Message targetMessage; @@ -26,7 +26,7 @@ Matcher isSameMessageListAs(List targetMessageList) => class _IsSameMessageListAs extends Matcher { const _IsSameMessageListAs({ - @required this.targetMessageList, + required this.targetMessageList, }) : assert(targetMessageList != null, ''); final List targetMessageList; diff --git a/packages/stream_chat_flutter_core/test/matchers/users_matcher.dart b/packages/stream_chat_flutter_core/test/matchers/users_matcher.dart index 3816fa24..38e25a64 100644 --- a/packages/stream_chat_flutter_core/test/matchers/users_matcher.dart +++ b/packages/stream_chat_flutter_core/test/matchers/users_matcher.dart @@ -6,7 +6,7 @@ Matcher isSameUserAs(User targetUser) => _IsSameUserAs(targetUser: targetUser); class _IsSameUserAs extends Matcher { const _IsSameUserAs({ - @required this.targetUser, + required this.targetUser, }) : assert(targetUser != null, ''); final User targetUser; @@ -24,7 +24,7 @@ Matcher isSameUserListAs(List targetUserList) => class _IsSameUserListAs extends Matcher { const _IsSameUserListAs({ - @required this.targetUserList, + required this.targetUserList, }) : assert(targetUserList != null, ''); final List targetUserList; From e2fed839462170fd13eb348c37b92292c3e9e3a8 Mon Sep 17 00:00:00 2001 From: Neevash Ramdial Date: Fri, 16 Apr 2021 09:30:16 -0400 Subject: [PATCH 124/599] remove assert for nullable constructor params --- .../lib/src/channels_bloc.dart | 3 +-- .../lib/src/message_list_core.dart | 12 +----------- .../lib/src/message_search_bloc.dart | 3 +-- 3 files changed, 3 insertions(+), 15 deletions(-) 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 2e42c0af..b7ff3d85 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -25,8 +25,7 @@ class ChannelsBloc extends StatefulWidget { this.lockChannelsOrder = false, this.channelsComparator, this.shouldAddChannel, - }) : assert(child != null, 'Parameter child should not be null.'), - super(key: key); + }) : super(key: key); /// The widget child final Widget child; diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 65209297..219b4ab1 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -70,17 +70,7 @@ class MessageListCore extends StatefulWidget { this.parentMessage, this.messageListController, this.messageFilter, - }) : assert(loadingBuilder != null, 'loadingBuilder should not be null'), - assert(emptyBuilder != null, 'emptyBuilder should not be null'), - assert( - messageListBuilder != null, - 'messageListBuilder should not be null', - ), - assert( - errorWidgetBuilder != null, - 'errorWidgetBuilder should not be null', - ), - super(key: key); + }) : super(key: key); /// A [MessageListController] allows pagination. /// Use [ChannelListController.paginateData] pagination. 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 d41086cd..9f55b7e8 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 @@ -15,8 +15,7 @@ class MessageSearchBloc extends StatefulWidget { const MessageSearchBloc({ Key? key, required this.child, - }) : assert(child != null, 'Parameter child should not be null.'), - super(key: key); + }) : super(key: key); /// The widget child final Widget child; From 2f4595c379beb0b061bf07ee67609d4cff24a588 Mon Sep 17 00:00:00 2001 From: Neevash Ramdial Date: Fri, 16 Apr 2021 09:30:54 -0400 Subject: [PATCH 125/599] migrate message_search_bloc.dart --- .../lib/src/message_search_bloc.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 9f55b7e8..fc858e5b 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 @@ -72,9 +72,7 @@ class MessageSearchBlocState extends State _queryMessagesLoadingController.add(true); } try { - final clear = pagination == null || - pagination.offset == null || - pagination.offset == 0; + final clear = pagination == null || pagination.offset == 0; final oldMessages = List.from(messageResponses ?? []); @@ -86,11 +84,13 @@ class MessageSearchBlocState extends State messageFilters: messageFilter!, ); - if (clear) { - _messageResponses.add(messages.results); - } else { - final temp = oldMessages + messages.results; - _messageResponses.add(temp); + if (messages.results != null) { + if (clear) { + _messageResponses.add(messages.results!); + } else { + final temp = oldMessages + messages.results!; + _messageResponses.add(temp); + } } if (_messageResponses.hasValue && _queryMessagesLoadingController.value!) { From e52e79bafa6d84afa0e66d8de4affa7f5397dd86 Mon Sep 17 00:00:00 2001 From: Neevash Ramdial Date: Fri, 16 Apr 2021 09:31:50 -0400 Subject: [PATCH 126/599] wip message_list_core --- .../lib/src/message_list_core.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 219b4ab1..c376dddc 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -128,7 +128,7 @@ class MessageListCoreState extends State { : _streamChannel.channel.state?.messagesStream; bool defaultFilter(Message m) { - final isMyMessage = m.user.id == _currentUser.id; + final isMyMessage = m.user?.id == _currentUser?.id; final isDeletedOrShadowed = m.isDeleted == true || m.shadowed == true; if (isDeletedOrShadowed && !isMyMessage) return false; return true; @@ -136,14 +136,17 @@ class MessageListCoreState extends State { return StreamBuilder?>( stream: messagesStream?.map((messages) => - messages?.where(widget.messageFilter ?? defaultFilter)?.toList()), + messages?.where(widget.messageFilter ?? defaultFilter).toList( + growable: false, + )), builder: (context, snapshot) { if (snapshot.hasError) { return widget.errorWidgetBuilder(context, snapshot.error); } else if (!snapshot.hasData) { return widget.loadingBuilder(context); } else { - final messageList = snapshot.data?.reversed?.toList() ?? []; + final messageList = + snapshot.data?.reversed.toList(growable: false) ?? []; if (messageList.isEmpty && !_isThreadConversation) { if (_upToDate) { return widget.emptyBuilder(context); From 8f7471fec23d1e49f5b0640da788fa1473906a79 Mon Sep 17 00:00:00 2001 From: Neevash Ramdial Date: Fri, 16 Apr 2021 09:32:28 -0400 Subject: [PATCH 127/599] migrate channels_bloc --- .../lib/src/channels_bloc.dart | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) 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 b7ff3d85..ad39809f 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -104,9 +104,7 @@ class ChannelsBlocState extends State } try { - final clear = paginationParams == null || - paginationParams.offset == null || - paginationParams.offset == 0; + final clear = paginationParams == null || paginationParams.offset == 0; final oldChannels = List.from(channels ?? []); var newChannels = []; await for (final channels in client.queryChannels( @@ -162,9 +160,9 @@ class ChannelsBlocState extends State newChannels.insert(0, _hiddenChannels[hiddenIndex]); _hiddenChannels.removeAt(hiddenIndex); } else { - if (client.state?.channels != null && - client.state?.channels[e.cid] != null) { - newChannels.insert(0, client.state.channels[e.cid]); + if (client.state.channels != null && + client.state.channels?[e.cid] != null) { + newChannels.insert(0, client.state.channels?[e.cid]); } } } @@ -195,7 +193,7 @@ class ChannelsBlocState extends State // ignore: cascade_invocations final channel = e.channel; _channelsController.add(List.from( - (channels ?? [])..removeWhere((c) => c.cid == channel.cid))); + (channels ?? [])..removeWhere((c) => c.cid == channel?.cid))); })); } From 34f2539595559c2773294b66ab9039e61bd8b676 Mon Sep 17 00:00:00 2001 From: Neevash Ramdial Date: Fri, 16 Apr 2021 09:33:24 -0400 Subject: [PATCH 128/599] wip stream_chat.dart --- .../lib/src/stream_channel.dart | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) 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 f4b6e6c7..c119f207 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; @@ -27,9 +26,7 @@ class StreamChannel extends StatefulWidget { required this.channel, this.showLoading = true, this.initialMessageId, - }) : assert(child != null, 'Child should not be null'), - assert(channel != null, 'Channel should not be null'), - super(key: key); + }) : super(key: key); // ignore: public_member_api_docs final Widget child; @@ -189,16 +186,21 @@ class StreamChannelState extends State { /// Query the channel members and watchers Future queryMembersAndWatchers() async { - await widget.channel.query( - membersPagination: PaginationParams( - offset: channel.state.members?.length, - limit: 100, - ), - watchersPagination: PaginationParams( - offset: channel.state.watchers?.length, - limit: 100, - ), - ); + final _members = channel.state?.members; + if (_members != null) { + await widget.channel.query( + membersPagination: PaginationParams( + offset: _members.length, + limit: 100, + ), + watchersPagination: PaginationParams( + offset: _members.length, + limit: 100, + ), + ); + } else { + return; + } } /// Loads channel at specific message From 629691ad5b8684ba1498ad94298991032ad344d5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 11:07:55 +0200 Subject: [PATCH 129/599] update user model --- .../lib/src/models/own_user.g.dart | 2 +- packages/stream_chat/lib/src/models/user.dart | 21 ++++--------------- .../stream_chat/lib/src/models/user.g.dart | 2 +- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/packages/stream_chat/lib/src/models/own_user.g.dart b/packages/stream_chat/lib/src/models/own_user.g.dart index 5802060c..785efb04 100644 --- a/packages/stream_chat/lib/src/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/models/own_user.g.dart @@ -23,7 +23,7 @@ OwnUser _$OwnUserFromJson(Map json) { .toList() ?? [], id: json['id'] as String, - role: json['role'] as String? ?? '', + role: json['role'] as String, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/models/user.dart index 67fc99f0..8b965589 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/models/user.dart @@ -8,8 +8,8 @@ part 'user.g.dart'; class User { /// Constructor used for json serialization User({ - this.id = '', - this.role = '', + required this.id, + this.role, DateTime? createdAt, DateTime? updatedAt, this.lastActive, @@ -24,18 +24,6 @@ class User { factory User.fromJson(Map json) => _$UserFromJson( Serialization.moveToExtraDataFromRoot(json, topLevelFields)); - /// Use this named constructor to create a new user instance - User.init( - this.id, { - this.online = false, - this.extraData = const {}, - required this.createdAt, - required this.updatedAt, - this.teams = const [], - required this.role, - }) : lastActive = null, - banned = false; - /// Known top level fields. /// Useful for [Serialization] methods. static const topLevelFields = [ @@ -53,9 +41,8 @@ class User { final String id; /// User role - @JsonKey( - includeIfNull: false, toJson: Serialization.readOnly, defaultValue: '') - final String role; + @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + final String? role; /// User role @JsonKey( diff --git a/packages/stream_chat/lib/src/models/user.g.dart b/packages/stream_chat/lib/src/models/user.g.dart index 18408d76..ef1ccf77 100644 --- a/packages/stream_chat/lib/src/models/user.g.dart +++ b/packages/stream_chat/lib/src/models/user.g.dart @@ -9,7 +9,7 @@ part of 'user.dart'; User _$UserFromJson(Map json) { return User( id: json['id'] as String, - role: json['role'] as String? ?? '', + role: json['role'] as String?, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), From 271dad7f0c13ac69e09838491339f4725788ae6e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 20 Apr 2021 14:18:30 +0530 Subject: [PATCH 130/599] bump deps, migrate converters Signed-off-by: Sahil Kumar --- .../lib/src/converter/list_converter.dart | 4 ++-- .../lib/src/converter/map_converter.dart | 4 ++-- .../message_sending_status_converter.dart | 4 ++-- packages/stream_chat_persistence/pubspec.yaml | 18 +++++++++--------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/stream_chat_persistence/lib/src/converter/list_converter.dart b/packages/stream_chat_persistence/lib/src/converter/list_converter.dart index cbb8ec05..d813afc3 100644 --- a/packages/stream_chat_persistence/lib/src/converter/list_converter.dart +++ b/packages/stream_chat_persistence/lib/src/converter/list_converter.dart @@ -6,7 +6,7 @@ import 'package:moor/moor.dart'; /// by the sqlite backend. class ListConverter extends TypeConverter, String> { @override - List mapToDart(String fromDb) { + List? mapToDart(String? fromDb) { if (fromDb == null) { return null; } @@ -14,7 +14,7 @@ class ListConverter extends TypeConverter, String> { } @override - String mapToSql(List value) { + String? mapToSql(List? value) { if (value == null) { return null; } diff --git a/packages/stream_chat_persistence/lib/src/converter/map_converter.dart b/packages/stream_chat_persistence/lib/src/converter/map_converter.dart index b11eb5b8..64284a9d 100644 --- a/packages/stream_chat_persistence/lib/src/converter/map_converter.dart +++ b/packages/stream_chat_persistence/lib/src/converter/map_converter.dart @@ -6,7 +6,7 @@ import 'package:moor/moor.dart'; /// by the sqlite backend. class MapConverter extends TypeConverter, String> { @override - Map mapToDart(String fromDb) { + Map? mapToDart(String? fromDb) { if (fromDb == null) { return null; } @@ -14,7 +14,7 @@ class MapConverter extends TypeConverter, String> { } @override - String mapToSql(Map value) { + String? mapToSql(Map? value) { if (value == null) { return null; } diff --git a/packages/stream_chat_persistence/lib/src/converter/message_sending_status_converter.dart b/packages/stream_chat_persistence/lib/src/converter/message_sending_status_converter.dart index 75b006bb..9d6ea11f 100644 --- a/packages/stream_chat_persistence/lib/src/converter/message_sending_status_converter.dart +++ b/packages/stream_chat_persistence/lib/src/converter/message_sending_status_converter.dart @@ -6,7 +6,7 @@ import 'package:stream_chat/stream_chat.dart'; class MessageSendingStatusConverter extends TypeConverter { @override - MessageSendingStatus mapToDart(int fromDb) { + MessageSendingStatus? mapToDart(int? fromDb) { switch (fromDb) { case 0: return MessageSendingStatus.sending; @@ -28,7 +28,7 @@ class MessageSendingStatusConverter } @override - int mapToSql(MessageSendingStatus value) { + int? mapToSql(MessageSendingStatus? value) { switch (value) { case MessageSendingStatus.sending: return 0; diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index f1d8c631..f23bc79c 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -8,18 +8,18 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues publish_to: none environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter - logging: ^1.0.0 + logging: ^1.0.1 meta: ^1.3.0 - moor: ^4.2.0 + moor: ^4.2.1 mutex: ^3.0.0 path: ^1.8.0 - path_provider: ^2.0.0 - sqlite3_flutter_libs: ^0.4.0+1 + path_provider: ^2.0.1 + sqlite3_flutter_libs: ^0.4.1 stream_chat: ^1.5.1 dependency_overrides: @@ -27,8 +27,8 @@ dependency_overrides: path: ../stream_chat dev_dependencies: - build_runner: ^1.11.0 - mocktail: ^0.1.0 - moor_generator: ^4.2.0 + build_runner: ^1.12.2 + mocktail: ^0.1.1 + moor_generator: ^4.2.1 pedantic: ^1.11.0 - test: ^1.16.0 + test: ^1.16.8 From e3589a303058104ee2922c849576ef7d9bf6544c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 20 Apr 2021 15:37:12 +0530 Subject: [PATCH 131/599] modify entities according to NNBD models Signed-off-by: Sahil Kumar --- .../lib/src/db/moor_chat_database.g.dart | 2713 ++++++++--------- .../lib/src/entity/channels.dart | 6 +- .../lib/src/entity/members.dart | 12 +- .../lib/src/entity/messages.dart | 19 +- .../lib/src/entity/reactions.dart | 4 +- .../lib/src/entity/reads.dart | 2 +- .../lib/src/entity/users.dart | 8 +- .../src/stream_chat_persistence_client.dart | 18 +- 8 files changed, 1323 insertions(+), 1459 deletions(-) diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index b83b16da..1453e3d4 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -8,57 +8,80 @@ part of 'moor_chat_database.dart'; // ignore_for_file: unnecessary_brace_in_string_interps, unnecessary_this class ChannelEntity extends DataClass implements Insertable { + /// The id of this channel final String id; + + /// The type of this channel final String type; + + /// The cid of this channel final String cid; + + /// The channel configuration data final Map config; + + /// True if this channel entity is frozen final bool frozen; - final DateTime lastMessageAt; + + /// The date of the last message + final DateTime? lastMessageAt; + + /// The date of channel creation final DateTime createdAt; + + /// The date of the last channel update final DateTime updatedAt; - final DateTime deletedAt; + + /// The date of channel deletion + final DateTime? deletedAt; + + /// The count of this channel members final int memberCount; - final String createdById; - final Map extraData; + + /// The id of the user that created this channel + final String? createdById; + + /// Map of custom channel extraData + final Map? extraData; ChannelEntity( - {@required this.id, - @required this.type, - @required this.cid, - @required this.config, - @required this.frozen, + {required this.id, + required this.type, + required this.cid, + required this.config, + required this.frozen, this.lastMessageAt, - this.createdAt, - this.updatedAt, + required this.createdAt, + required this.updatedAt, this.deletedAt, - this.memberCount, + required this.memberCount, this.createdById, this.extraData}); factory ChannelEntity.fromData( Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; final stringType = db.typeSystem.forDartType(); final boolType = db.typeSystem.forDartType(); final dateTimeType = db.typeSystem.forDartType(); final intType = db.typeSystem.forDartType(); return ChannelEntity( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id']), - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type']), - cid: stringType.mapFromDatabaseResponse(data['${effectivePrefix}cid']), - config: $ChannelsTable.$converter0.mapToDart( - stringType.mapFromDatabaseResponse(data['${effectivePrefix}config'])), + id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type'])!, + cid: stringType.mapFromDatabaseResponse(data['${effectivePrefix}cid'])!, + config: $ChannelsTable.$converter0.mapToDart(stringType + .mapFromDatabaseResponse(data['${effectivePrefix}config']))!, frozen: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}frozen']), + boolType.mapFromDatabaseResponse(data['${effectivePrefix}frozen'])!, lastMessageAt: dateTimeType .mapFromDatabaseResponse(data['${effectivePrefix}last_message_at']), createdAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}created_at']), + .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, updatedAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}updated_at']), + .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, deletedAt: dateTimeType .mapFromDatabaseResponse(data['${effectivePrefix}deleted_at']), memberCount: intType - .mapFromDatabaseResponse(data['${effectivePrefix}member_count']), + .mapFromDatabaseResponse(data['${effectivePrefix}member_count'])!, createdById: stringType .mapFromDatabaseResponse(data['${effectivePrefix}created_by_id']), extraData: $ChannelsTable.$converter1.mapToDart(stringType @@ -68,49 +91,35 @@ class ChannelEntity extends DataClass implements Insertable { @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } - if (!nullToAbsent || type != null) { - map['type'] = Variable(type); - } - if (!nullToAbsent || cid != null) { - map['cid'] = Variable(cid); - } - if (!nullToAbsent || config != null) { + map['id'] = Variable(id); + map['type'] = Variable(type); + map['cid'] = Variable(cid); + { final converter = $ChannelsTable.$converter0; - map['config'] = Variable(converter.mapToSql(config)); - } - if (!nullToAbsent || frozen != null) { - map['frozen'] = Variable(frozen); + map['config'] = Variable(converter.mapToSql(config)!); } + map['frozen'] = Variable(frozen); if (!nullToAbsent || lastMessageAt != null) { - map['last_message_at'] = Variable(lastMessageAt); - } - if (!nullToAbsent || createdAt != null) { - map['created_at'] = Variable(createdAt); - } - if (!nullToAbsent || updatedAt != null) { - map['updated_at'] = Variable(updatedAt); + map['last_message_at'] = Variable(lastMessageAt); } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); if (!nullToAbsent || deletedAt != null) { - map['deleted_at'] = Variable(deletedAt); - } - if (!nullToAbsent || memberCount != null) { - map['member_count'] = Variable(memberCount); + map['deleted_at'] = Variable(deletedAt); } + map['member_count'] = Variable(memberCount); if (!nullToAbsent || createdById != null) { - map['created_by_id'] = Variable(createdById); + map['created_by_id'] = Variable(createdById); } if (!nullToAbsent || extraData != null) { final converter = $ChannelsTable.$converter1; - map['extra_data'] = Variable(converter.mapToSql(extraData)); + map['extra_data'] = Variable(converter.mapToSql(extraData)); } return map; } factory ChannelEntity.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return ChannelEntity( id: serializer.fromJson(json['id']), @@ -118,17 +127,17 @@ class ChannelEntity extends DataClass implements Insertable { cid: serializer.fromJson(json['cid']), config: serializer.fromJson>(json['config']), frozen: serializer.fromJson(json['frozen']), - lastMessageAt: serializer.fromJson(json['lastMessageAt']), + lastMessageAt: serializer.fromJson(json['lastMessageAt']), createdAt: serializer.fromJson(json['createdAt']), updatedAt: serializer.fromJson(json['updatedAt']), - deletedAt: serializer.fromJson(json['deletedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), memberCount: serializer.fromJson(json['memberCount']), - createdById: serializer.fromJson(json['createdById']), - extraData: serializer.fromJson>(json['extraData']), + createdById: serializer.fromJson(json['createdById']), + extraData: serializer.fromJson?>(json['extraData']), ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), @@ -136,29 +145,29 @@ class ChannelEntity extends DataClass implements Insertable { 'cid': serializer.toJson(cid), 'config': serializer.toJson>(config), 'frozen': serializer.toJson(frozen), - 'lastMessageAt': serializer.toJson(lastMessageAt), + 'lastMessageAt': serializer.toJson(lastMessageAt), 'createdAt': serializer.toJson(createdAt), 'updatedAt': serializer.toJson(updatedAt), - 'deletedAt': serializer.toJson(deletedAt), + 'deletedAt': serializer.toJson(deletedAt), 'memberCount': serializer.toJson(memberCount), - 'createdById': serializer.toJson(createdById), - 'extraData': serializer.toJson>(extraData), + 'createdById': serializer.toJson(createdById), + 'extraData': serializer.toJson?>(extraData), }; } ChannelEntity copyWith( - {String id, - String type, - String cid, - Map config, - bool frozen, - Value lastMessageAt = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value deletedAt = const Value.absent(), - Value memberCount = const Value.absent(), - Value createdById = const Value.absent(), - Value> extraData = const Value.absent()}) => + {String? id, + String? type, + String? cid, + Map? config, + bool? frozen, + Value lastMessageAt = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + int? memberCount, + Value createdById = const Value.absent(), + Value?> extraData = const Value.absent()}) => ChannelEntity( id: id ?? this.id, type: type ?? this.type, @@ -167,10 +176,10 @@ class ChannelEntity extends DataClass implements Insertable { frozen: frozen ?? this.frozen, lastMessageAt: lastMessageAt.present ? lastMessageAt.value : this.lastMessageAt, - createdAt: createdAt.present ? createdAt.value : this.createdAt, - updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, - memberCount: memberCount.present ? memberCount.value : this.memberCount, + memberCount: memberCount ?? this.memberCount, createdById: createdById.present ? createdById.value : this.createdById, extraData: extraData.present ? extraData.value : this.extraData, ); @@ -240,13 +249,13 @@ class ChannelsCompanion extends UpdateCompanion { final Value cid; final Value> config; final Value frozen; - final Value lastMessageAt; + final Value lastMessageAt; final Value createdAt; final Value updatedAt; - final Value deletedAt; + final Value deletedAt; final Value memberCount; - final Value createdById; - final Value> extraData; + final Value createdById; + final Value?> extraData; const ChannelsCompanion({ this.id = const Value.absent(), this.type = const Value.absent(), @@ -262,10 +271,10 @@ class ChannelsCompanion extends UpdateCompanion { this.extraData = const Value.absent(), }); ChannelsCompanion.insert({ - @required String id, - @required String type, - @required String cid, - @required Map config, + required String id, + required String type, + required String cid, + required Map config, this.frozen = const Value.absent(), this.lastMessageAt = const Value.absent(), this.createdAt = const Value.absent(), @@ -279,18 +288,18 @@ class ChannelsCompanion extends UpdateCompanion { cid = Value(cid), config = Value(config); static Insertable custom({ - Expression id, - Expression type, - Expression cid, - Expression config, - Expression frozen, - Expression lastMessageAt, - Expression createdAt, - Expression updatedAt, - Expression deletedAt, - Expression memberCount, - Expression createdById, - Expression extraData, + Expression? id, + Expression? type, + Expression? cid, + Expression>? config, + Expression? frozen, + Expression? lastMessageAt, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? memberCount, + Expression? createdById, + Expression?>? extraData, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -309,18 +318,18 @@ class ChannelsCompanion extends UpdateCompanion { } ChannelsCompanion copyWith( - {Value id, - Value type, - Value cid, - Value> config, - Value frozen, - Value lastMessageAt, - Value createdAt, - Value updatedAt, - Value deletedAt, - Value memberCount, - Value createdById, - Value> extraData}) { + {Value? id, + Value? type, + Value? cid, + Value>? config, + Value? frozen, + Value? lastMessageAt, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? memberCount, + Value? createdById, + Value?>? extraData}) { return ChannelsCompanion( id: id ?? this.id, type: type ?? this.type, @@ -351,13 +360,13 @@ class ChannelsCompanion extends UpdateCompanion { } if (config.present) { final converter = $ChannelsTable.$converter0; - map['config'] = Variable(converter.mapToSql(config.value)); + map['config'] = Variable(converter.mapToSql(config.value)!); } if (frozen.present) { map['frozen'] = Variable(frozen.value); } if (lastMessageAt.present) { - map['last_message_at'] = Variable(lastMessageAt.value); + map['last_message_at'] = Variable(lastMessageAt.value); } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); @@ -366,17 +375,18 @@ class ChannelsCompanion extends UpdateCompanion { map['updated_at'] = Variable(updatedAt.value); } if (deletedAt.present) { - map['deleted_at'] = Variable(deletedAt.value); + map['deleted_at'] = Variable(deletedAt.value); } if (memberCount.present) { map['member_count'] = Variable(memberCount.value); } if (createdById.present) { - map['created_by_id'] = Variable(createdById.value); + map['created_by_id'] = Variable(createdById.value); } if (extraData.present) { final converter = $ChannelsTable.$converter1; - map['extra_data'] = Variable(converter.mapToSql(extraData.value)); + map['extra_data'] = + Variable(converter.mapToSql(extraData.value)); } return map; } @@ -404,12 +414,11 @@ class ChannelsCompanion extends UpdateCompanion { class $ChannelsTable extends Channels with TableInfo<$ChannelsTable, ChannelEntity> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $ChannelsTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - GeneratedTextColumn _id; @override - GeneratedTextColumn get id => _id ??= _constructId(); + late final GeneratedTextColumn id = _constructId(); GeneratedTextColumn _constructId() { return GeneratedTextColumn( 'id', @@ -419,9 +428,8 @@ class $ChannelsTable extends Channels } final VerificationMeta _typeMeta = const VerificationMeta('type'); - GeneratedTextColumn _type; @override - GeneratedTextColumn get type => _type ??= _constructType(); + late final GeneratedTextColumn type = _constructType(); GeneratedTextColumn _constructType() { return GeneratedTextColumn( 'type', @@ -431,9 +439,8 @@ class $ChannelsTable extends Channels } final VerificationMeta _cidMeta = const VerificationMeta('cid'); - GeneratedTextColumn _cid; @override - GeneratedTextColumn get cid => _cid ??= _constructCid(); + late final GeneratedTextColumn cid = _constructCid(); GeneratedTextColumn _constructCid() { return GeneratedTextColumn( 'cid', @@ -443,9 +450,8 @@ class $ChannelsTable extends Channels } final VerificationMeta _configMeta = const VerificationMeta('config'); - GeneratedTextColumn _config; @override - GeneratedTextColumn get config => _config ??= _constructConfig(); + late final GeneratedTextColumn config = _constructConfig(); GeneratedTextColumn _constructConfig() { return GeneratedTextColumn( 'config', @@ -455,20 +461,17 @@ class $ChannelsTable extends Channels } final VerificationMeta _frozenMeta = const VerificationMeta('frozen'); - GeneratedBoolColumn _frozen; @override - GeneratedBoolColumn get frozen => _frozen ??= _constructFrozen(); + late final GeneratedBoolColumn frozen = _constructFrozen(); GeneratedBoolColumn _constructFrozen() { return GeneratedBoolColumn('frozen', $tableName, false, - defaultValue: Constant(false)); + defaultValue: const Constant(false)); } final VerificationMeta _lastMessageAtMeta = const VerificationMeta('lastMessageAt'); - GeneratedDateTimeColumn _lastMessageAt; @override - GeneratedDateTimeColumn get lastMessageAt => - _lastMessageAt ??= _constructLastMessageAt(); + late final GeneratedDateTimeColumn lastMessageAt = _constructLastMessageAt(); GeneratedDateTimeColumn _constructLastMessageAt() { return GeneratedDateTimeColumn( 'last_message_at', @@ -478,33 +481,24 @@ class $ChannelsTable extends Channels } final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - GeneratedDateTimeColumn _createdAt; @override - GeneratedDateTimeColumn get createdAt => _createdAt ??= _constructCreatedAt(); + late final GeneratedDateTimeColumn createdAt = _constructCreatedAt(); GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn( - 'created_at', - $tableName, - true, - ); + return GeneratedDateTimeColumn('created_at', $tableName, false, + defaultValue: currentDateAndTime); } final VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); - GeneratedDateTimeColumn _updatedAt; @override - GeneratedDateTimeColumn get updatedAt => _updatedAt ??= _constructUpdatedAt(); + late final GeneratedDateTimeColumn updatedAt = _constructUpdatedAt(); GeneratedDateTimeColumn _constructUpdatedAt() { - return GeneratedDateTimeColumn( - 'updated_at', - $tableName, - true, - ); + return GeneratedDateTimeColumn('updated_at', $tableName, false, + defaultValue: currentDateAndTime); } final VerificationMeta _deletedAtMeta = const VerificationMeta('deletedAt'); - GeneratedDateTimeColumn _deletedAt; @override - GeneratedDateTimeColumn get deletedAt => _deletedAt ??= _constructDeletedAt(); + late final GeneratedDateTimeColumn deletedAt = _constructDeletedAt(); GeneratedDateTimeColumn _constructDeletedAt() { return GeneratedDateTimeColumn( 'deleted_at', @@ -515,24 +509,17 @@ class $ChannelsTable extends Channels final VerificationMeta _memberCountMeta = const VerificationMeta('memberCount'); - GeneratedIntColumn _memberCount; @override - GeneratedIntColumn get memberCount => - _memberCount ??= _constructMemberCount(); + late final GeneratedIntColumn memberCount = _constructMemberCount(); GeneratedIntColumn _constructMemberCount() { - return GeneratedIntColumn( - 'member_count', - $tableName, - true, - ); + return GeneratedIntColumn('member_count', $tableName, false, + defaultValue: const Constant(0)); } final VerificationMeta _createdByIdMeta = const VerificationMeta('createdById'); - GeneratedTextColumn _createdById; @override - GeneratedTextColumn get createdById => - _createdById ??= _constructCreatedById(); + late final GeneratedTextColumn createdById = _constructCreatedById(); GeneratedTextColumn _constructCreatedById() { return GeneratedTextColumn( 'created_by_id', @@ -542,9 +529,8 @@ class $ChannelsTable extends Channels } final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); - GeneratedTextColumn _extraData; @override - GeneratedTextColumn get extraData => _extraData ??= _constructExtraData(); + late final GeneratedTextColumn extraData = _constructExtraData(); GeneratedTextColumn _constructExtraData() { return GeneratedTextColumn( 'extra_data', @@ -580,56 +566,56 @@ class $ChannelsTable extends Channels final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } else if (isInserting) { context.missing(_idMeta); } if (data.containsKey('type')) { context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type'], _typeMeta)); + _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } else if (isInserting) { context.missing(_typeMeta); } if (data.containsKey('cid')) { context.handle( - _cidMeta, cid.isAcceptableOrUnknown(data['cid'], _cidMeta)); + _cidMeta, cid.isAcceptableOrUnknown(data['cid']!, _cidMeta)); } else if (isInserting) { context.missing(_cidMeta); } context.handle(_configMeta, const VerificationResult.success()); if (data.containsKey('frozen')) { context.handle(_frozenMeta, - frozen.isAcceptableOrUnknown(data['frozen'], _frozenMeta)); + frozen.isAcceptableOrUnknown(data['frozen']!, _frozenMeta)); } if (data.containsKey('last_message_at')) { context.handle( _lastMessageAtMeta, lastMessageAt.isAcceptableOrUnknown( - data['last_message_at'], _lastMessageAtMeta)); + data['last_message_at']!, _lastMessageAtMeta)); } if (data.containsKey('created_at')) { context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at'], _createdAtMeta)); + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at'], _updatedAtMeta)); + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('deleted_at')) { context.handle(_deletedAtMeta, - deletedAt.isAcceptableOrUnknown(data['deleted_at'], _deletedAtMeta)); + deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta)); } if (data.containsKey('member_count')) { context.handle( _memberCountMeta, memberCount.isAcceptableOrUnknown( - data['member_count'], _memberCountMeta)); + data['member_count']!, _memberCountMeta)); } if (data.containsKey('created_by_id')) { context.handle( _createdByIdMeta, createdById.isAcceptableOrUnknown( - data['created_by_id'], _createdByIdMeta)); + data['created_by_id']!, _createdByIdMeta)); } context.handle(_extraDataMeta, const VerificationResult.success()); return context; @@ -638,7 +624,7 @@ class $ChannelsTable extends Channels @override Set get $primaryKey => {cid}; @override - ChannelEntity map(Map data, {String tablePrefix}) { + ChannelEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; return ChannelEntity.fromData(data, _db, prefix: effectivePrefix); } @@ -655,50 +641,98 @@ class $ChannelsTable extends Channels } class MessageEntity extends DataClass implements Insertable { + /// The message id final String id; - final String messageText; + + /// The text of this message + final String? messageText; + + /// The list of attachments, either provided by the user + /// or generated from a command or as a result of URL scraping. final List attachments; + + /// The status of a sending message final MessageSendingStatus status; + + /// The message type final String type; + + /// The list of user mentioned in the message final List mentionedUsers; - final Map reactionCounts; - final Map reactionScores; - final String parentId; - final String quotedMessageId; - final int replyCount; - final bool showInChannel; + + /// A map describing the count of number of every reaction + final Map? reactionCounts; + + /// A map describing the count of score of every reaction + final Map? reactionScores; + + /// The ID of the parent message, if the message is a thread reply. + final String? parentId; + + /// The ID of the quoted message, if the message is a quoted reply. + final String? quotedMessageId; + + /// Number of replies for this message. + final int? replyCount; + + /// Check if this message needs to show in the channel. + final bool? showInChannel; + + /// If true the message is shadowed final bool shadowed; - final String command; + + /// A used command name. + final String? command; + + /// The DateTime when the message was created. final DateTime createdAt; + + /// The DateTime when the message was updated last time. final DateTime updatedAt; - final DateTime deletedAt; - final String userId; + + /// The DateTime when the message was deleted. + final DateTime? deletedAt; + + /// Id of the User who sent the message + final String? userId; + + /// Whether the message is pinned or not final bool pinned; - final DateTime pinnedAt; - final DateTime pinExpires; - final String pinnedByUserId; - final String channelCid; - final Map extraData; + + /// The DateTime at which the message was pinned + final DateTime? pinnedAt; + + /// The DateTime on which the message pin expires + final DateTime? pinExpires; + + /// Id of the User who pinned the message + final String? pinnedByUserId; + + /// The channel cid of which this message is part of + final String? channelCid; + + /// Message custom extraData + final Map? extraData; MessageEntity( - {@required this.id, + {required this.id, this.messageText, - this.attachments, - this.status, - this.type, - this.mentionedUsers, + required this.attachments, + required this.status, + required this.type, + required this.mentionedUsers, this.reactionCounts, this.reactionScores, this.parentId, this.quotedMessageId, this.replyCount, this.showInChannel, - this.shadowed, + required this.shadowed, this.command, - @required this.createdAt, - this.updatedAt, + required this.createdAt, + required this.updatedAt, this.deletedAt, this.userId, - @required this.pinned, + required this.pinned, this.pinnedAt, this.pinExpires, this.pinnedByUserId, @@ -706,23 +740,23 @@ class MessageEntity extends DataClass implements Insertable { this.extraData}); factory MessageEntity.fromData( Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; final stringType = db.typeSystem.forDartType(); final intType = db.typeSystem.forDartType(); final boolType = db.typeSystem.forDartType(); final dateTimeType = db.typeSystem.forDartType(); return MessageEntity( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id']), + id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, messageText: stringType .mapFromDatabaseResponse(data['${effectivePrefix}message_text']), attachments: $MessagesTable.$converter0.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}attachments'])), + .mapFromDatabaseResponse(data['${effectivePrefix}attachments']))!, status: $MessagesTable.$converter1.mapToDart( - intType.mapFromDatabaseResponse(data['${effectivePrefix}status'])), - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type']), + intType.mapFromDatabaseResponse(data['${effectivePrefix}status']))!, + type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type'])!, mentionedUsers: $MessagesTable.$converter2.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}mentioned_users'])), + .mapFromDatabaseResponse(data['${effectivePrefix}mentioned_users']))!, reactionCounts: $MessagesTable.$converter3.mapToDart(stringType .mapFromDatabaseResponse(data['${effectivePrefix}reaction_counts'])), reactionScores: $MessagesTable.$converter4.mapToDart(stringType @@ -736,19 +770,19 @@ class MessageEntity extends DataClass implements Insertable { showInChannel: boolType .mapFromDatabaseResponse(data['${effectivePrefix}show_in_channel']), shadowed: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}shadowed']), + boolType.mapFromDatabaseResponse(data['${effectivePrefix}shadowed'])!, command: stringType.mapFromDatabaseResponse(data['${effectivePrefix}command']), createdAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}created_at']), + .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, updatedAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}updated_at']), + .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, deletedAt: dateTimeType .mapFromDatabaseResponse(data['${effectivePrefix}deleted_at']), userId: stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), pinned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}pinned']), + boolType.mapFromDatabaseResponse(data['${effectivePrefix}pinned'])!, pinnedAt: dateTimeType .mapFromDatabaseResponse(data['${effectivePrefix}pinned_at']), pinExpires: dateTimeType @@ -764,186 +798,173 @@ class MessageEntity extends DataClass implements Insertable { @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } + map['id'] = Variable(id); if (!nullToAbsent || messageText != null) { - map['message_text'] = Variable(messageText); + map['message_text'] = Variable(messageText); } - if (!nullToAbsent || attachments != null) { + { final converter = $MessagesTable.$converter0; - map['attachments'] = Variable(converter.mapToSql(attachments)); + map['attachments'] = Variable(converter.mapToSql(attachments)!); } - if (!nullToAbsent || status != null) { + { final converter = $MessagesTable.$converter1; - map['status'] = Variable(converter.mapToSql(status)); + map['status'] = Variable(converter.mapToSql(status)!); } - if (!nullToAbsent || type != null) { - map['type'] = Variable(type); - } - if (!nullToAbsent || mentionedUsers != null) { + map['type'] = Variable(type); + { final converter = $MessagesTable.$converter2; map['mentioned_users'] = - Variable(converter.mapToSql(mentionedUsers)); + Variable(converter.mapToSql(mentionedUsers)!); } if (!nullToAbsent || reactionCounts != null) { final converter = $MessagesTable.$converter3; map['reaction_counts'] = - Variable(converter.mapToSql(reactionCounts)); + Variable(converter.mapToSql(reactionCounts)); } if (!nullToAbsent || reactionScores != null) { final converter = $MessagesTable.$converter4; map['reaction_scores'] = - Variable(converter.mapToSql(reactionScores)); + Variable(converter.mapToSql(reactionScores)); } if (!nullToAbsent || parentId != null) { - map['parent_id'] = Variable(parentId); + map['parent_id'] = Variable(parentId); } if (!nullToAbsent || quotedMessageId != null) { - map['quoted_message_id'] = Variable(quotedMessageId); + map['quoted_message_id'] = Variable(quotedMessageId); } if (!nullToAbsent || replyCount != null) { - map['reply_count'] = Variable(replyCount); + map['reply_count'] = Variable(replyCount); } if (!nullToAbsent || showInChannel != null) { - map['show_in_channel'] = Variable(showInChannel); - } - if (!nullToAbsent || shadowed != null) { - map['shadowed'] = Variable(shadowed); + map['show_in_channel'] = Variable(showInChannel); } + map['shadowed'] = Variable(shadowed); if (!nullToAbsent || command != null) { - map['command'] = Variable(command); - } - if (!nullToAbsent || createdAt != null) { - map['created_at'] = Variable(createdAt); - } - if (!nullToAbsent || updatedAt != null) { - map['updated_at'] = Variable(updatedAt); + map['command'] = Variable(command); } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); if (!nullToAbsent || deletedAt != null) { - map['deleted_at'] = Variable(deletedAt); + map['deleted_at'] = Variable(deletedAt); } if (!nullToAbsent || userId != null) { - map['user_id'] = Variable(userId); - } - if (!nullToAbsent || pinned != null) { - map['pinned'] = Variable(pinned); + map['user_id'] = Variable(userId); } + map['pinned'] = Variable(pinned); if (!nullToAbsent || pinnedAt != null) { - map['pinned_at'] = Variable(pinnedAt); + map['pinned_at'] = Variable(pinnedAt); } if (!nullToAbsent || pinExpires != null) { - map['pin_expires'] = Variable(pinExpires); + map['pin_expires'] = Variable(pinExpires); } if (!nullToAbsent || pinnedByUserId != null) { - map['pinned_by_user_id'] = Variable(pinnedByUserId); + map['pinned_by_user_id'] = Variable(pinnedByUserId); } if (!nullToAbsent || channelCid != null) { - map['channel_cid'] = Variable(channelCid); + map['channel_cid'] = Variable(channelCid); } if (!nullToAbsent || extraData != null) { final converter = $MessagesTable.$converter5; - map['extra_data'] = Variable(converter.mapToSql(extraData)); + map['extra_data'] = Variable(converter.mapToSql(extraData)); } return map; } factory MessageEntity.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return MessageEntity( id: serializer.fromJson(json['id']), - messageText: serializer.fromJson(json['messageText']), + messageText: serializer.fromJson(json['messageText']), attachments: serializer.fromJson>(json['attachments']), status: serializer.fromJson(json['status']), type: serializer.fromJson(json['type']), mentionedUsers: serializer.fromJson>(json['mentionedUsers']), reactionCounts: - serializer.fromJson>(json['reactionCounts']), + serializer.fromJson?>(json['reactionCounts']), reactionScores: - serializer.fromJson>(json['reactionScores']), - parentId: serializer.fromJson(json['parentId']), - quotedMessageId: serializer.fromJson(json['quotedMessageId']), - replyCount: serializer.fromJson(json['replyCount']), - showInChannel: serializer.fromJson(json['showInChannel']), + serializer.fromJson?>(json['reactionScores']), + parentId: serializer.fromJson(json['parentId']), + quotedMessageId: serializer.fromJson(json['quotedMessageId']), + replyCount: serializer.fromJson(json['replyCount']), + showInChannel: serializer.fromJson(json['showInChannel']), shadowed: serializer.fromJson(json['shadowed']), - command: serializer.fromJson(json['command']), + command: serializer.fromJson(json['command']), createdAt: serializer.fromJson(json['createdAt']), updatedAt: serializer.fromJson(json['updatedAt']), - deletedAt: serializer.fromJson(json['deletedAt']), - userId: serializer.fromJson(json['userId']), + deletedAt: serializer.fromJson(json['deletedAt']), + userId: serializer.fromJson(json['userId']), pinned: serializer.fromJson(json['pinned']), - pinnedAt: serializer.fromJson(json['pinnedAt']), - pinExpires: serializer.fromJson(json['pinExpires']), - pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), - channelCid: serializer.fromJson(json['channelCid']), - extraData: serializer.fromJson>(json['extraData']), + pinnedAt: serializer.fromJson(json['pinnedAt']), + pinExpires: serializer.fromJson(json['pinExpires']), + pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), + channelCid: serializer.fromJson(json['channelCid']), + extraData: serializer.fromJson?>(json['extraData']), ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), - 'messageText': serializer.toJson(messageText), + 'messageText': serializer.toJson(messageText), 'attachments': serializer.toJson>(attachments), 'status': serializer.toJson(status), 'type': serializer.toJson(type), 'mentionedUsers': serializer.toJson>(mentionedUsers), - 'reactionCounts': serializer.toJson>(reactionCounts), - 'reactionScores': serializer.toJson>(reactionScores), - 'parentId': serializer.toJson(parentId), - 'quotedMessageId': serializer.toJson(quotedMessageId), - 'replyCount': serializer.toJson(replyCount), - 'showInChannel': serializer.toJson(showInChannel), + 'reactionCounts': serializer.toJson?>(reactionCounts), + 'reactionScores': serializer.toJson?>(reactionScores), + 'parentId': serializer.toJson(parentId), + 'quotedMessageId': serializer.toJson(quotedMessageId), + 'replyCount': serializer.toJson(replyCount), + 'showInChannel': serializer.toJson(showInChannel), 'shadowed': serializer.toJson(shadowed), - 'command': serializer.toJson(command), + 'command': serializer.toJson(command), 'createdAt': serializer.toJson(createdAt), 'updatedAt': serializer.toJson(updatedAt), - 'deletedAt': serializer.toJson(deletedAt), - 'userId': serializer.toJson(userId), + 'deletedAt': serializer.toJson(deletedAt), + 'userId': serializer.toJson(userId), 'pinned': serializer.toJson(pinned), - 'pinnedAt': serializer.toJson(pinnedAt), - 'pinExpires': serializer.toJson(pinExpires), - 'pinnedByUserId': serializer.toJson(pinnedByUserId), - 'channelCid': serializer.toJson(channelCid), - 'extraData': serializer.toJson>(extraData), + 'pinnedAt': serializer.toJson(pinnedAt), + 'pinExpires': serializer.toJson(pinExpires), + 'pinnedByUserId': serializer.toJson(pinnedByUserId), + 'channelCid': serializer.toJson(channelCid), + 'extraData': serializer.toJson?>(extraData), }; } MessageEntity copyWith( - {String id, - Value messageText = const Value.absent(), - Value> attachments = const Value.absent(), - Value status = const Value.absent(), - Value type = const Value.absent(), - Value> mentionedUsers = const Value.absent(), - Value> reactionCounts = const Value.absent(), - Value> reactionScores = const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - Value shadowed = const Value.absent(), - Value command = const Value.absent(), - DateTime createdAt, - Value updatedAt = const Value.absent(), - Value deletedAt = const Value.absent(), - Value userId = const Value.absent(), - bool pinned, - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - Value channelCid = const Value.absent(), - Value> extraData = const Value.absent()}) => + {String? id, + Value messageText = const Value.absent(), + List? attachments, + MessageSendingStatus? status, + String? type, + List? mentionedUsers, + Value?> reactionCounts = const Value.absent(), + Value?> reactionScores = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + bool? shadowed, + Value command = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + Value userId = const Value.absent(), + bool? pinned, + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + Value channelCid = const Value.absent(), + Value?> extraData = const Value.absent()}) => MessageEntity( id: id ?? this.id, messageText: messageText.present ? messageText.value : this.messageText, - attachments: attachments.present ? attachments.value : this.attachments, - status: status.present ? status.value : this.status, - type: type.present ? type.value : this.type, - mentionedUsers: - mentionedUsers.present ? mentionedUsers.value : this.mentionedUsers, + attachments: attachments ?? this.attachments, + status: status ?? this.status, + type: type ?? this.type, + mentionedUsers: mentionedUsers ?? this.mentionedUsers, reactionCounts: reactionCounts.present ? reactionCounts.value : this.reactionCounts, reactionScores: @@ -955,10 +976,10 @@ class MessageEntity extends DataClass implements Insertable { replyCount: replyCount.present ? replyCount.value : this.replyCount, showInChannel: showInChannel.present ? showInChannel.value : this.showInChannel, - shadowed: shadowed.present ? shadowed.value : this.shadowed, + shadowed: shadowed ?? this.shadowed, command: command.present ? command.value : this.command, createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, + updatedAt: updatedAt ?? this.updatedAt, deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, userId: userId.present ? userId.value : this.userId, pinned: pinned ?? this.pinned, @@ -1076,29 +1097,29 @@ class MessageEntity extends DataClass implements Insertable { class MessagesCompanion extends UpdateCompanion { final Value id; - final Value messageText; + final Value messageText; final Value> attachments; final Value status; final Value type; final Value> mentionedUsers; - final Value> reactionCounts; - final Value> reactionScores; - final Value parentId; - final Value quotedMessageId; - final Value replyCount; - final Value showInChannel; + final Value?> reactionCounts; + final Value?> reactionScores; + final Value parentId; + final Value quotedMessageId; + final Value replyCount; + final Value showInChannel; final Value shadowed; - final Value command; + final Value command; final Value createdAt; final Value updatedAt; - final Value deletedAt; - final Value userId; + final Value deletedAt; + final Value userId; final Value pinned; - final Value pinnedAt; - final Value pinExpires; - final Value pinnedByUserId; - final Value channelCid; - final Value> extraData; + final Value pinnedAt; + final Value pinExpires; + final Value pinnedByUserId; + final Value channelCid; + final Value?> extraData; const MessagesCompanion({ this.id = const Value.absent(), this.messageText = const Value.absent(), @@ -1126,12 +1147,12 @@ class MessagesCompanion extends UpdateCompanion { this.extraData = const Value.absent(), }); MessagesCompanion.insert({ - @required String id, + required String id, this.messageText = const Value.absent(), - this.attachments = const Value.absent(), + required List attachments, this.status = const Value.absent(), this.type = const Value.absent(), - this.mentionedUsers = const Value.absent(), + required List mentionedUsers, this.reactionCounts = const Value.absent(), this.reactionScores = const Value.absent(), this.parentId = const Value.absent(), @@ -1140,7 +1161,7 @@ class MessagesCompanion extends UpdateCompanion { this.showInChannel = const Value.absent(), this.shadowed = const Value.absent(), this.command = const Value.absent(), - @required DateTime createdAt, + this.createdAt = const Value.absent(), this.updatedAt = const Value.absent(), this.deletedAt = const Value.absent(), this.userId = const Value.absent(), @@ -1151,32 +1172,33 @@ class MessagesCompanion extends UpdateCompanion { this.channelCid = const Value.absent(), this.extraData = const Value.absent(), }) : id = Value(id), - createdAt = Value(createdAt); + attachments = Value(attachments), + mentionedUsers = Value(mentionedUsers); static Insertable custom({ - Expression id, - Expression messageText, - Expression attachments, - Expression status, - Expression type, - Expression mentionedUsers, - Expression reactionCounts, - Expression reactionScores, - Expression parentId, - Expression quotedMessageId, - Expression replyCount, - Expression showInChannel, - Expression shadowed, - Expression command, - Expression createdAt, - Expression updatedAt, - Expression deletedAt, - Expression userId, - Expression pinned, - Expression pinnedAt, - Expression pinExpires, - Expression pinnedByUserId, - Expression channelCid, - Expression extraData, + Expression? id, + Expression? messageText, + Expression>? attachments, + Expression? status, + Expression? type, + Expression>? mentionedUsers, + Expression?>? reactionCounts, + Expression?>? reactionScores, + Expression? parentId, + Expression? quotedMessageId, + Expression? replyCount, + Expression? showInChannel, + Expression? shadowed, + Expression? command, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? userId, + Expression? pinned, + Expression? pinnedAt, + Expression? pinExpires, + Expression? pinnedByUserId, + Expression? channelCid, + Expression?>? extraData, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -1207,30 +1229,30 @@ class MessagesCompanion extends UpdateCompanion { } MessagesCompanion copyWith( - {Value id, - Value messageText, - Value> attachments, - Value status, - Value type, - Value> mentionedUsers, - Value> reactionCounts, - Value> reactionScores, - Value parentId, - Value quotedMessageId, - Value replyCount, - Value showInChannel, - Value shadowed, - Value command, - Value createdAt, - Value updatedAt, - Value deletedAt, - Value userId, - Value pinned, - Value pinnedAt, - Value pinExpires, - Value pinnedByUserId, - Value channelCid, - Value> extraData}) { + {Value? id, + Value? messageText, + Value>? attachments, + Value? status, + Value? type, + Value>? mentionedUsers, + Value?>? reactionCounts, + Value?>? reactionScores, + Value? parentId, + Value? quotedMessageId, + Value? replyCount, + Value? showInChannel, + Value? shadowed, + Value? command, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? userId, + Value? pinned, + Value? pinnedAt, + Value? pinExpires, + Value? pinnedByUserId, + Value? channelCid, + Value?>? extraData}) { return MessagesCompanion( id: id ?? this.id, messageText: messageText ?? this.messageText, @@ -1266,16 +1288,16 @@ class MessagesCompanion extends UpdateCompanion { map['id'] = Variable(id.value); } if (messageText.present) { - map['message_text'] = Variable(messageText.value); + map['message_text'] = Variable(messageText.value); } if (attachments.present) { final converter = $MessagesTable.$converter0; map['attachments'] = - Variable(converter.mapToSql(attachments.value)); + Variable(converter.mapToSql(attachments.value)!); } if (status.present) { final converter = $MessagesTable.$converter1; - map['status'] = Variable(converter.mapToSql(status.value)); + map['status'] = Variable(converter.mapToSql(status.value)!); } if (type.present) { map['type'] = Variable(type.value); @@ -1283,35 +1305,35 @@ class MessagesCompanion extends UpdateCompanion { if (mentionedUsers.present) { final converter = $MessagesTable.$converter2; map['mentioned_users'] = - Variable(converter.mapToSql(mentionedUsers.value)); + Variable(converter.mapToSql(mentionedUsers.value)!); } if (reactionCounts.present) { final converter = $MessagesTable.$converter3; map['reaction_counts'] = - Variable(converter.mapToSql(reactionCounts.value)); + Variable(converter.mapToSql(reactionCounts.value)); } if (reactionScores.present) { final converter = $MessagesTable.$converter4; map['reaction_scores'] = - Variable(converter.mapToSql(reactionScores.value)); + Variable(converter.mapToSql(reactionScores.value)); } if (parentId.present) { - map['parent_id'] = Variable(parentId.value); + map['parent_id'] = Variable(parentId.value); } if (quotedMessageId.present) { - map['quoted_message_id'] = Variable(quotedMessageId.value); + map['quoted_message_id'] = Variable(quotedMessageId.value); } if (replyCount.present) { - map['reply_count'] = Variable(replyCount.value); + map['reply_count'] = Variable(replyCount.value); } if (showInChannel.present) { - map['show_in_channel'] = Variable(showInChannel.value); + map['show_in_channel'] = Variable(showInChannel.value); } if (shadowed.present) { map['shadowed'] = Variable(shadowed.value); } if (command.present) { - map['command'] = Variable(command.value); + map['command'] = Variable(command.value); } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); @@ -1320,29 +1342,30 @@ class MessagesCompanion extends UpdateCompanion { map['updated_at'] = Variable(updatedAt.value); } if (deletedAt.present) { - map['deleted_at'] = Variable(deletedAt.value); + map['deleted_at'] = Variable(deletedAt.value); } if (userId.present) { - map['user_id'] = Variable(userId.value); + map['user_id'] = Variable(userId.value); } if (pinned.present) { map['pinned'] = Variable(pinned.value); } if (pinnedAt.present) { - map['pinned_at'] = Variable(pinnedAt.value); + map['pinned_at'] = Variable(pinnedAt.value); } if (pinExpires.present) { - map['pin_expires'] = Variable(pinExpires.value); + map['pin_expires'] = Variable(pinExpires.value); } if (pinnedByUserId.present) { - map['pinned_by_user_id'] = Variable(pinnedByUserId.value); + map['pinned_by_user_id'] = Variable(pinnedByUserId.value); } if (channelCid.present) { - map['channel_cid'] = Variable(channelCid.value); + map['channel_cid'] = Variable(channelCid.value); } if (extraData.present) { final converter = $MessagesTable.$converter5; - map['extra_data'] = Variable(converter.mapToSql(extraData.value)); + map['extra_data'] = + Variable(converter.mapToSql(extraData.value)); } return map; } @@ -1382,12 +1405,11 @@ class MessagesCompanion extends UpdateCompanion { class $MessagesTable extends Messages with TableInfo<$MessagesTable, MessageEntity> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $MessagesTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - GeneratedTextColumn _id; @override - GeneratedTextColumn get id => _id ??= _constructId(); + late final GeneratedTextColumn id = _constructId(); GeneratedTextColumn _constructId() { return GeneratedTextColumn( 'id', @@ -1398,10 +1420,8 @@ class $MessagesTable extends Messages final VerificationMeta _messageTextMeta = const VerificationMeta('messageText'); - GeneratedTextColumn _messageText; @override - GeneratedTextColumn get messageText => - _messageText ??= _constructMessageText(); + late final GeneratedTextColumn messageText = _constructMessageText(); GeneratedTextColumn _constructMessageText() { return GeneratedTextColumn( 'message_text', @@ -1412,62 +1432,48 @@ class $MessagesTable extends Messages final VerificationMeta _attachmentsMeta = const VerificationMeta('attachments'); - GeneratedTextColumn _attachments; @override - GeneratedTextColumn get attachments => - _attachments ??= _constructAttachments(); + late final GeneratedTextColumn attachments = _constructAttachments(); GeneratedTextColumn _constructAttachments() { return GeneratedTextColumn( 'attachments', $tableName, - true, + false, ); } final VerificationMeta _statusMeta = const VerificationMeta('status'); - GeneratedIntColumn _status; @override - GeneratedIntColumn get status => _status ??= _constructStatus(); + late final GeneratedIntColumn status = _constructStatus(); GeneratedIntColumn _constructStatus() { - return GeneratedIntColumn( - 'status', - $tableName, - true, - ); + return GeneratedIntColumn('status', $tableName, false, + defaultValue: const Constant(1)); } final VerificationMeta _typeMeta = const VerificationMeta('type'); - GeneratedTextColumn _type; @override - GeneratedTextColumn get type => _type ??= _constructType(); + late final GeneratedTextColumn type = _constructType(); GeneratedTextColumn _constructType() { - return GeneratedTextColumn( - 'type', - $tableName, - true, - ); + return GeneratedTextColumn('type', $tableName, false, + defaultValue: const Constant('regular')); } final VerificationMeta _mentionedUsersMeta = const VerificationMeta('mentionedUsers'); - GeneratedTextColumn _mentionedUsers; @override - GeneratedTextColumn get mentionedUsers => - _mentionedUsers ??= _constructMentionedUsers(); + late final GeneratedTextColumn mentionedUsers = _constructMentionedUsers(); GeneratedTextColumn _constructMentionedUsers() { return GeneratedTextColumn( 'mentioned_users', $tableName, - true, + false, ); } final VerificationMeta _reactionCountsMeta = const VerificationMeta('reactionCounts'); - GeneratedTextColumn _reactionCounts; @override - GeneratedTextColumn get reactionCounts => - _reactionCounts ??= _constructReactionCounts(); + late final GeneratedTextColumn reactionCounts = _constructReactionCounts(); GeneratedTextColumn _constructReactionCounts() { return GeneratedTextColumn( 'reaction_counts', @@ -1478,10 +1484,8 @@ class $MessagesTable extends Messages final VerificationMeta _reactionScoresMeta = const VerificationMeta('reactionScores'); - GeneratedTextColumn _reactionScores; @override - GeneratedTextColumn get reactionScores => - _reactionScores ??= _constructReactionScores(); + late final GeneratedTextColumn reactionScores = _constructReactionScores(); GeneratedTextColumn _constructReactionScores() { return GeneratedTextColumn( 'reaction_scores', @@ -1491,9 +1495,8 @@ class $MessagesTable extends Messages } final VerificationMeta _parentIdMeta = const VerificationMeta('parentId'); - GeneratedTextColumn _parentId; @override - GeneratedTextColumn get parentId => _parentId ??= _constructParentId(); + late final GeneratedTextColumn parentId = _constructParentId(); GeneratedTextColumn _constructParentId() { return GeneratedTextColumn( 'parent_id', @@ -1504,10 +1507,8 @@ class $MessagesTable extends Messages final VerificationMeta _quotedMessageIdMeta = const VerificationMeta('quotedMessageId'); - GeneratedTextColumn _quotedMessageId; @override - GeneratedTextColumn get quotedMessageId => - _quotedMessageId ??= _constructQuotedMessageId(); + late final GeneratedTextColumn quotedMessageId = _constructQuotedMessageId(); GeneratedTextColumn _constructQuotedMessageId() { return GeneratedTextColumn( 'quoted_message_id', @@ -1517,9 +1518,8 @@ class $MessagesTable extends Messages } final VerificationMeta _replyCountMeta = const VerificationMeta('replyCount'); - GeneratedIntColumn _replyCount; @override - GeneratedIntColumn get replyCount => _replyCount ??= _constructReplyCount(); + late final GeneratedIntColumn replyCount = _constructReplyCount(); GeneratedIntColumn _constructReplyCount() { return GeneratedIntColumn( 'reply_count', @@ -1530,10 +1530,8 @@ class $MessagesTable extends Messages final VerificationMeta _showInChannelMeta = const VerificationMeta('showInChannel'); - GeneratedBoolColumn _showInChannel; @override - GeneratedBoolColumn get showInChannel => - _showInChannel ??= _constructShowInChannel(); + late final GeneratedBoolColumn showInChannel = _constructShowInChannel(); GeneratedBoolColumn _constructShowInChannel() { return GeneratedBoolColumn( 'show_in_channel', @@ -1543,21 +1541,16 @@ class $MessagesTable extends Messages } final VerificationMeta _shadowedMeta = const VerificationMeta('shadowed'); - GeneratedBoolColumn _shadowed; @override - GeneratedBoolColumn get shadowed => _shadowed ??= _constructShadowed(); + late final GeneratedBoolColumn shadowed = _constructShadowed(); GeneratedBoolColumn _constructShadowed() { - return GeneratedBoolColumn( - 'shadowed', - $tableName, - true, - ); + return GeneratedBoolColumn('shadowed', $tableName, false, + defaultValue: const Constant(false)); } final VerificationMeta _commandMeta = const VerificationMeta('command'); - GeneratedTextColumn _command; @override - GeneratedTextColumn get command => _command ??= _constructCommand(); + late final GeneratedTextColumn command = _constructCommand(); GeneratedTextColumn _constructCommand() { return GeneratedTextColumn( 'command', @@ -1567,33 +1560,24 @@ class $MessagesTable extends Messages } final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - GeneratedDateTimeColumn _createdAt; @override - GeneratedDateTimeColumn get createdAt => _createdAt ??= _constructCreatedAt(); + late final GeneratedDateTimeColumn createdAt = _constructCreatedAt(); GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn( - 'created_at', - $tableName, - false, - ); + return GeneratedDateTimeColumn('created_at', $tableName, false, + defaultValue: currentDateAndTime); } final VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); - GeneratedDateTimeColumn _updatedAt; @override - GeneratedDateTimeColumn get updatedAt => _updatedAt ??= _constructUpdatedAt(); + late final GeneratedDateTimeColumn updatedAt = _constructUpdatedAt(); GeneratedDateTimeColumn _constructUpdatedAt() { - return GeneratedDateTimeColumn( - 'updated_at', - $tableName, - true, - ); + return GeneratedDateTimeColumn('updated_at', $tableName, false, + defaultValue: currentDateAndTime); } final VerificationMeta _deletedAtMeta = const VerificationMeta('deletedAt'); - GeneratedDateTimeColumn _deletedAt; @override - GeneratedDateTimeColumn get deletedAt => _deletedAt ??= _constructDeletedAt(); + late final GeneratedDateTimeColumn deletedAt = _constructDeletedAt(); GeneratedDateTimeColumn _constructDeletedAt() { return GeneratedDateTimeColumn( 'deleted_at', @@ -1603,9 +1587,8 @@ class $MessagesTable extends Messages } final VerificationMeta _userIdMeta = const VerificationMeta('userId'); - GeneratedTextColumn _userId; @override - GeneratedTextColumn get userId => _userId ??= _constructUserId(); + late final GeneratedTextColumn userId = _constructUserId(); GeneratedTextColumn _constructUserId() { return GeneratedTextColumn( 'user_id', @@ -1615,18 +1598,16 @@ class $MessagesTable extends Messages } final VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); - GeneratedBoolColumn _pinned; @override - GeneratedBoolColumn get pinned => _pinned ??= _constructPinned(); + late final GeneratedBoolColumn pinned = _constructPinned(); GeneratedBoolColumn _constructPinned() { return GeneratedBoolColumn('pinned', $tableName, false, defaultValue: const Constant(false)); } final VerificationMeta _pinnedAtMeta = const VerificationMeta('pinnedAt'); - GeneratedDateTimeColumn _pinnedAt; @override - GeneratedDateTimeColumn get pinnedAt => _pinnedAt ??= _constructPinnedAt(); + late final GeneratedDateTimeColumn pinnedAt = _constructPinnedAt(); GeneratedDateTimeColumn _constructPinnedAt() { return GeneratedDateTimeColumn( 'pinned_at', @@ -1636,10 +1617,8 @@ class $MessagesTable extends Messages } final VerificationMeta _pinExpiresMeta = const VerificationMeta('pinExpires'); - GeneratedDateTimeColumn _pinExpires; @override - GeneratedDateTimeColumn get pinExpires => - _pinExpires ??= _constructPinExpires(); + late final GeneratedDateTimeColumn pinExpires = _constructPinExpires(); GeneratedDateTimeColumn _constructPinExpires() { return GeneratedDateTimeColumn( 'pin_expires', @@ -1650,10 +1629,8 @@ class $MessagesTable extends Messages final VerificationMeta _pinnedByUserIdMeta = const VerificationMeta('pinnedByUserId'); - GeneratedTextColumn _pinnedByUserId; @override - GeneratedTextColumn get pinnedByUserId => - _pinnedByUserId ??= _constructPinnedByUserId(); + late final GeneratedTextColumn pinnedByUserId = _constructPinnedByUserId(); GeneratedTextColumn _constructPinnedByUserId() { return GeneratedTextColumn( 'pinned_by_user_id', @@ -1663,9 +1640,8 @@ class $MessagesTable extends Messages } final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); - GeneratedTextColumn _channelCid; @override - GeneratedTextColumn get channelCid => _channelCid ??= _constructChannelCid(); + late final GeneratedTextColumn channelCid = _constructChannelCid(); GeneratedTextColumn _constructChannelCid() { return GeneratedTextColumn('channel_cid', $tableName, true, $customConstraints: @@ -1673,9 +1649,8 @@ class $MessagesTable extends Messages } final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); - GeneratedTextColumn _extraData; @override - GeneratedTextColumn get extraData => _extraData ??= _constructExtraData(); + late final GeneratedTextColumn extraData = _constructExtraData(); GeneratedTextColumn _constructExtraData() { return GeneratedTextColumn( 'extra_data', @@ -1723,7 +1698,7 @@ class $MessagesTable extends Messages final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } else if (isInserting) { context.missing(_idMeta); } @@ -1731,90 +1706,88 @@ class $MessagesTable extends Messages context.handle( _messageTextMeta, messageText.isAcceptableOrUnknown( - data['message_text'], _messageTextMeta)); + data['message_text']!, _messageTextMeta)); } context.handle(_attachmentsMeta, const VerificationResult.success()); context.handle(_statusMeta, const VerificationResult.success()); if (data.containsKey('type')) { context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type'], _typeMeta)); + _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } context.handle(_mentionedUsersMeta, const VerificationResult.success()); context.handle(_reactionCountsMeta, const VerificationResult.success()); context.handle(_reactionScoresMeta, const VerificationResult.success()); if (data.containsKey('parent_id')) { context.handle(_parentIdMeta, - parentId.isAcceptableOrUnknown(data['parent_id'], _parentIdMeta)); + parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); } if (data.containsKey('quoted_message_id')) { context.handle( _quotedMessageIdMeta, quotedMessageId.isAcceptableOrUnknown( - data['quoted_message_id'], _quotedMessageIdMeta)); + data['quoted_message_id']!, _quotedMessageIdMeta)); } if (data.containsKey('reply_count')) { context.handle( _replyCountMeta, replyCount.isAcceptableOrUnknown( - data['reply_count'], _replyCountMeta)); + data['reply_count']!, _replyCountMeta)); } if (data.containsKey('show_in_channel')) { context.handle( _showInChannelMeta, showInChannel.isAcceptableOrUnknown( - data['show_in_channel'], _showInChannelMeta)); + data['show_in_channel']!, _showInChannelMeta)); } if (data.containsKey('shadowed')) { context.handle(_shadowedMeta, - shadowed.isAcceptableOrUnknown(data['shadowed'], _shadowedMeta)); + shadowed.isAcceptableOrUnknown(data['shadowed']!, _shadowedMeta)); } if (data.containsKey('command')) { context.handle(_commandMeta, - command.isAcceptableOrUnknown(data['command'], _commandMeta)); + command.isAcceptableOrUnknown(data['command']!, _commandMeta)); } if (data.containsKey('created_at')) { context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at'], _createdAtMeta)); - } else if (isInserting) { - context.missing(_createdAtMeta); + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at'], _updatedAtMeta)); + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('deleted_at')) { context.handle(_deletedAtMeta, - deletedAt.isAcceptableOrUnknown(data['deleted_at'], _deletedAtMeta)); + deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta)); } if (data.containsKey('user_id')) { context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id'], _userIdMeta)); + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('pinned')) { context.handle(_pinnedMeta, - pinned.isAcceptableOrUnknown(data['pinned'], _pinnedMeta)); + pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); } if (data.containsKey('pinned_at')) { context.handle(_pinnedAtMeta, - pinnedAt.isAcceptableOrUnknown(data['pinned_at'], _pinnedAtMeta)); + pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); } if (data.containsKey('pin_expires')) { context.handle( _pinExpiresMeta, pinExpires.isAcceptableOrUnknown( - data['pin_expires'], _pinExpiresMeta)); + data['pin_expires']!, _pinExpiresMeta)); } if (data.containsKey('pinned_by_user_id')) { context.handle( _pinnedByUserIdMeta, pinnedByUserId.isAcceptableOrUnknown( - data['pinned_by_user_id'], _pinnedByUserIdMeta)); + data['pinned_by_user_id']!, _pinnedByUserIdMeta)); } if (data.containsKey('channel_cid')) { context.handle( _channelCidMeta, channelCid.isAcceptableOrUnknown( - data['channel_cid'], _channelCidMeta)); + data['channel_cid']!, _channelCidMeta)); } context.handle(_extraDataMeta, const VerificationResult.success()); return context; @@ -1823,7 +1796,7 @@ class $MessagesTable extends Messages @override Set get $primaryKey => {id}; @override - MessageEntity map(Map data, {String tablePrefix}) { + MessageEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; return MessageEntity.fromData(data, _db, prefix: effectivePrefix); } @@ -1849,50 +1822,98 @@ class $MessagesTable extends Messages class PinnedMessageEntity extends DataClass implements Insertable { + /// The message id final String id; - final String messageText; + + /// The text of this message + final String? messageText; + + /// The list of attachments, either provided by the user + /// or generated from a command or as a result of URL scraping. final List attachments; + + /// The status of a sending message final MessageSendingStatus status; + + /// The message type final String type; + + /// The list of user mentioned in the message final List mentionedUsers; - final Map reactionCounts; - final Map reactionScores; - final String parentId; - final String quotedMessageId; - final int replyCount; - final bool showInChannel; + + /// A map describing the count of number of every reaction + final Map? reactionCounts; + + /// A map describing the count of score of every reaction + final Map? reactionScores; + + /// The ID of the parent message, if the message is a thread reply. + final String? parentId; + + /// The ID of the quoted message, if the message is a quoted reply. + final String? quotedMessageId; + + /// Number of replies for this message. + final int? replyCount; + + /// Check if this message needs to show in the channel. + final bool? showInChannel; + + /// If true the message is shadowed final bool shadowed; - final String command; + + /// A used command name. + final String? command; + + /// The DateTime when the message was created. final DateTime createdAt; + + /// The DateTime when the message was updated last time. final DateTime updatedAt; - final DateTime deletedAt; - final String userId; + + /// The DateTime when the message was deleted. + final DateTime? deletedAt; + + /// Id of the User who sent the message + final String? userId; + + /// Whether the message is pinned or not final bool pinned; - final DateTime pinnedAt; - final DateTime pinExpires; - final String pinnedByUserId; - final String channelCid; - final Map extraData; + + /// The DateTime at which the message was pinned + final DateTime? pinnedAt; + + /// The DateTime on which the message pin expires + final DateTime? pinExpires; + + /// Id of the User who pinned the message + final String? pinnedByUserId; + + /// The channel cid of which this message is part of + final String? channelCid; + + /// Message custom extraData + final Map? extraData; PinnedMessageEntity( - {@required this.id, + {required this.id, this.messageText, - this.attachments, - this.status, - this.type, - this.mentionedUsers, + required this.attachments, + required this.status, + required this.type, + required this.mentionedUsers, this.reactionCounts, this.reactionScores, this.parentId, this.quotedMessageId, this.replyCount, this.showInChannel, - this.shadowed, + required this.shadowed, this.command, - @required this.createdAt, - this.updatedAt, + required this.createdAt, + required this.updatedAt, this.deletedAt, this.userId, - @required this.pinned, + required this.pinned, this.pinnedAt, this.pinExpires, this.pinnedByUserId, @@ -1900,23 +1921,23 @@ class PinnedMessageEntity extends DataClass this.extraData}); factory PinnedMessageEntity.fromData( Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; final stringType = db.typeSystem.forDartType(); final intType = db.typeSystem.forDartType(); final boolType = db.typeSystem.forDartType(); final dateTimeType = db.typeSystem.forDartType(); return PinnedMessageEntity( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id']), + id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, messageText: stringType .mapFromDatabaseResponse(data['${effectivePrefix}message_text']), attachments: $PinnedMessagesTable.$converter0.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}attachments'])), + .mapFromDatabaseResponse(data['${effectivePrefix}attachments']))!, status: $PinnedMessagesTable.$converter1.mapToDart( - intType.mapFromDatabaseResponse(data['${effectivePrefix}status'])), - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type']), + intType.mapFromDatabaseResponse(data['${effectivePrefix}status']))!, + type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type'])!, mentionedUsers: $PinnedMessagesTable.$converter2.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}mentioned_users'])), + .mapFromDatabaseResponse(data['${effectivePrefix}mentioned_users']))!, reactionCounts: $PinnedMessagesTable.$converter3.mapToDart(stringType .mapFromDatabaseResponse(data['${effectivePrefix}reaction_counts'])), reactionScores: $PinnedMessagesTable.$converter4.mapToDart(stringType @@ -1930,19 +1951,19 @@ class PinnedMessageEntity extends DataClass showInChannel: boolType .mapFromDatabaseResponse(data['${effectivePrefix}show_in_channel']), shadowed: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}shadowed']), + boolType.mapFromDatabaseResponse(data['${effectivePrefix}shadowed'])!, command: stringType.mapFromDatabaseResponse(data['${effectivePrefix}command']), createdAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}created_at']), + .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, updatedAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}updated_at']), + .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, deletedAt: dateTimeType .mapFromDatabaseResponse(data['${effectivePrefix}deleted_at']), userId: stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), pinned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}pinned']), + boolType.mapFromDatabaseResponse(data['${effectivePrefix}pinned'])!, pinnedAt: dateTimeType .mapFromDatabaseResponse(data['${effectivePrefix}pinned_at']), pinExpires: dateTimeType @@ -1958,186 +1979,173 @@ class PinnedMessageEntity extends DataClass @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } + map['id'] = Variable(id); if (!nullToAbsent || messageText != null) { - map['message_text'] = Variable(messageText); + map['message_text'] = Variable(messageText); } - if (!nullToAbsent || attachments != null) { + { final converter = $PinnedMessagesTable.$converter0; - map['attachments'] = Variable(converter.mapToSql(attachments)); + map['attachments'] = Variable(converter.mapToSql(attachments)!); } - if (!nullToAbsent || status != null) { + { final converter = $PinnedMessagesTable.$converter1; - map['status'] = Variable(converter.mapToSql(status)); + map['status'] = Variable(converter.mapToSql(status)!); } - if (!nullToAbsent || type != null) { - map['type'] = Variable(type); - } - if (!nullToAbsent || mentionedUsers != null) { + map['type'] = Variable(type); + { final converter = $PinnedMessagesTable.$converter2; map['mentioned_users'] = - Variable(converter.mapToSql(mentionedUsers)); + Variable(converter.mapToSql(mentionedUsers)!); } if (!nullToAbsent || reactionCounts != null) { final converter = $PinnedMessagesTable.$converter3; map['reaction_counts'] = - Variable(converter.mapToSql(reactionCounts)); + Variable(converter.mapToSql(reactionCounts)); } if (!nullToAbsent || reactionScores != null) { final converter = $PinnedMessagesTable.$converter4; map['reaction_scores'] = - Variable(converter.mapToSql(reactionScores)); + Variable(converter.mapToSql(reactionScores)); } if (!nullToAbsent || parentId != null) { - map['parent_id'] = Variable(parentId); + map['parent_id'] = Variable(parentId); } if (!nullToAbsent || quotedMessageId != null) { - map['quoted_message_id'] = Variable(quotedMessageId); + map['quoted_message_id'] = Variable(quotedMessageId); } if (!nullToAbsent || replyCount != null) { - map['reply_count'] = Variable(replyCount); + map['reply_count'] = Variable(replyCount); } if (!nullToAbsent || showInChannel != null) { - map['show_in_channel'] = Variable(showInChannel); - } - if (!nullToAbsent || shadowed != null) { - map['shadowed'] = Variable(shadowed); + map['show_in_channel'] = Variable(showInChannel); } + map['shadowed'] = Variable(shadowed); if (!nullToAbsent || command != null) { - map['command'] = Variable(command); - } - if (!nullToAbsent || createdAt != null) { - map['created_at'] = Variable(createdAt); - } - if (!nullToAbsent || updatedAt != null) { - map['updated_at'] = Variable(updatedAt); + map['command'] = Variable(command); } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); if (!nullToAbsent || deletedAt != null) { - map['deleted_at'] = Variable(deletedAt); + map['deleted_at'] = Variable(deletedAt); } if (!nullToAbsent || userId != null) { - map['user_id'] = Variable(userId); - } - if (!nullToAbsent || pinned != null) { - map['pinned'] = Variable(pinned); + map['user_id'] = Variable(userId); } + map['pinned'] = Variable(pinned); if (!nullToAbsent || pinnedAt != null) { - map['pinned_at'] = Variable(pinnedAt); + map['pinned_at'] = Variable(pinnedAt); } if (!nullToAbsent || pinExpires != null) { - map['pin_expires'] = Variable(pinExpires); + map['pin_expires'] = Variable(pinExpires); } if (!nullToAbsent || pinnedByUserId != null) { - map['pinned_by_user_id'] = Variable(pinnedByUserId); + map['pinned_by_user_id'] = Variable(pinnedByUserId); } if (!nullToAbsent || channelCid != null) { - map['channel_cid'] = Variable(channelCid); + map['channel_cid'] = Variable(channelCid); } if (!nullToAbsent || extraData != null) { final converter = $PinnedMessagesTable.$converter5; - map['extra_data'] = Variable(converter.mapToSql(extraData)); + map['extra_data'] = Variable(converter.mapToSql(extraData)); } return map; } factory PinnedMessageEntity.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return PinnedMessageEntity( id: serializer.fromJson(json['id']), - messageText: serializer.fromJson(json['messageText']), + messageText: serializer.fromJson(json['messageText']), attachments: serializer.fromJson>(json['attachments']), status: serializer.fromJson(json['status']), type: serializer.fromJson(json['type']), mentionedUsers: serializer.fromJson>(json['mentionedUsers']), reactionCounts: - serializer.fromJson>(json['reactionCounts']), + serializer.fromJson?>(json['reactionCounts']), reactionScores: - serializer.fromJson>(json['reactionScores']), - parentId: serializer.fromJson(json['parentId']), - quotedMessageId: serializer.fromJson(json['quotedMessageId']), - replyCount: serializer.fromJson(json['replyCount']), - showInChannel: serializer.fromJson(json['showInChannel']), + serializer.fromJson?>(json['reactionScores']), + parentId: serializer.fromJson(json['parentId']), + quotedMessageId: serializer.fromJson(json['quotedMessageId']), + replyCount: serializer.fromJson(json['replyCount']), + showInChannel: serializer.fromJson(json['showInChannel']), shadowed: serializer.fromJson(json['shadowed']), - command: serializer.fromJson(json['command']), + command: serializer.fromJson(json['command']), createdAt: serializer.fromJson(json['createdAt']), updatedAt: serializer.fromJson(json['updatedAt']), - deletedAt: serializer.fromJson(json['deletedAt']), - userId: serializer.fromJson(json['userId']), + deletedAt: serializer.fromJson(json['deletedAt']), + userId: serializer.fromJson(json['userId']), pinned: serializer.fromJson(json['pinned']), - pinnedAt: serializer.fromJson(json['pinnedAt']), - pinExpires: serializer.fromJson(json['pinExpires']), - pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), - channelCid: serializer.fromJson(json['channelCid']), - extraData: serializer.fromJson>(json['extraData']), + pinnedAt: serializer.fromJson(json['pinnedAt']), + pinExpires: serializer.fromJson(json['pinExpires']), + pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), + channelCid: serializer.fromJson(json['channelCid']), + extraData: serializer.fromJson?>(json['extraData']), ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), - 'messageText': serializer.toJson(messageText), + 'messageText': serializer.toJson(messageText), 'attachments': serializer.toJson>(attachments), 'status': serializer.toJson(status), 'type': serializer.toJson(type), 'mentionedUsers': serializer.toJson>(mentionedUsers), - 'reactionCounts': serializer.toJson>(reactionCounts), - 'reactionScores': serializer.toJson>(reactionScores), - 'parentId': serializer.toJson(parentId), - 'quotedMessageId': serializer.toJson(quotedMessageId), - 'replyCount': serializer.toJson(replyCount), - 'showInChannel': serializer.toJson(showInChannel), + 'reactionCounts': serializer.toJson?>(reactionCounts), + 'reactionScores': serializer.toJson?>(reactionScores), + 'parentId': serializer.toJson(parentId), + 'quotedMessageId': serializer.toJson(quotedMessageId), + 'replyCount': serializer.toJson(replyCount), + 'showInChannel': serializer.toJson(showInChannel), 'shadowed': serializer.toJson(shadowed), - 'command': serializer.toJson(command), + 'command': serializer.toJson(command), 'createdAt': serializer.toJson(createdAt), 'updatedAt': serializer.toJson(updatedAt), - 'deletedAt': serializer.toJson(deletedAt), - 'userId': serializer.toJson(userId), + 'deletedAt': serializer.toJson(deletedAt), + 'userId': serializer.toJson(userId), 'pinned': serializer.toJson(pinned), - 'pinnedAt': serializer.toJson(pinnedAt), - 'pinExpires': serializer.toJson(pinExpires), - 'pinnedByUserId': serializer.toJson(pinnedByUserId), - 'channelCid': serializer.toJson(channelCid), - 'extraData': serializer.toJson>(extraData), + 'pinnedAt': serializer.toJson(pinnedAt), + 'pinExpires': serializer.toJson(pinExpires), + 'pinnedByUserId': serializer.toJson(pinnedByUserId), + 'channelCid': serializer.toJson(channelCid), + 'extraData': serializer.toJson?>(extraData), }; } PinnedMessageEntity copyWith( - {String id, - Value messageText = const Value.absent(), - Value> attachments = const Value.absent(), - Value status = const Value.absent(), - Value type = const Value.absent(), - Value> mentionedUsers = const Value.absent(), - Value> reactionCounts = const Value.absent(), - Value> reactionScores = const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - Value shadowed = const Value.absent(), - Value command = const Value.absent(), - DateTime createdAt, - Value updatedAt = const Value.absent(), - Value deletedAt = const Value.absent(), - Value userId = const Value.absent(), - bool pinned, - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - Value channelCid = const Value.absent(), - Value> extraData = const Value.absent()}) => + {String? id, + Value messageText = const Value.absent(), + List? attachments, + MessageSendingStatus? status, + String? type, + List? mentionedUsers, + Value?> reactionCounts = const Value.absent(), + Value?> reactionScores = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + bool? shadowed, + Value command = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + Value userId = const Value.absent(), + bool? pinned, + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + Value channelCid = const Value.absent(), + Value?> extraData = const Value.absent()}) => PinnedMessageEntity( id: id ?? this.id, messageText: messageText.present ? messageText.value : this.messageText, - attachments: attachments.present ? attachments.value : this.attachments, - status: status.present ? status.value : this.status, - type: type.present ? type.value : this.type, - mentionedUsers: - mentionedUsers.present ? mentionedUsers.value : this.mentionedUsers, + attachments: attachments ?? this.attachments, + status: status ?? this.status, + type: type ?? this.type, + mentionedUsers: mentionedUsers ?? this.mentionedUsers, reactionCounts: reactionCounts.present ? reactionCounts.value : this.reactionCounts, reactionScores: @@ -2149,10 +2157,10 @@ class PinnedMessageEntity extends DataClass replyCount: replyCount.present ? replyCount.value : this.replyCount, showInChannel: showInChannel.present ? showInChannel.value : this.showInChannel, - shadowed: shadowed.present ? shadowed.value : this.shadowed, + shadowed: shadowed ?? this.shadowed, command: command.present ? command.value : this.command, createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, + updatedAt: updatedAt ?? this.updatedAt, deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, userId: userId.present ? userId.value : this.userId, pinned: pinned ?? this.pinned, @@ -2270,29 +2278,29 @@ class PinnedMessageEntity extends DataClass class PinnedMessagesCompanion extends UpdateCompanion { final Value id; - final Value messageText; + final Value messageText; final Value> attachments; final Value status; final Value type; final Value> mentionedUsers; - final Value> reactionCounts; - final Value> reactionScores; - final Value parentId; - final Value quotedMessageId; - final Value replyCount; - final Value showInChannel; + final Value?> reactionCounts; + final Value?> reactionScores; + final Value parentId; + final Value quotedMessageId; + final Value replyCount; + final Value showInChannel; final Value shadowed; - final Value command; + final Value command; final Value createdAt; final Value updatedAt; - final Value deletedAt; - final Value userId; + final Value deletedAt; + final Value userId; final Value pinned; - final Value pinnedAt; - final Value pinExpires; - final Value pinnedByUserId; - final Value channelCid; - final Value> extraData; + final Value pinnedAt; + final Value pinExpires; + final Value pinnedByUserId; + final Value channelCid; + final Value?> extraData; const PinnedMessagesCompanion({ this.id = const Value.absent(), this.messageText = const Value.absent(), @@ -2320,12 +2328,12 @@ class PinnedMessagesCompanion extends UpdateCompanion { this.extraData = const Value.absent(), }); PinnedMessagesCompanion.insert({ - @required String id, + required String id, this.messageText = const Value.absent(), - this.attachments = const Value.absent(), + required List attachments, this.status = const Value.absent(), this.type = const Value.absent(), - this.mentionedUsers = const Value.absent(), + required List mentionedUsers, this.reactionCounts = const Value.absent(), this.reactionScores = const Value.absent(), this.parentId = const Value.absent(), @@ -2334,7 +2342,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { this.showInChannel = const Value.absent(), this.shadowed = const Value.absent(), this.command = const Value.absent(), - @required DateTime createdAt, + this.createdAt = const Value.absent(), this.updatedAt = const Value.absent(), this.deletedAt = const Value.absent(), this.userId = const Value.absent(), @@ -2345,32 +2353,33 @@ class PinnedMessagesCompanion extends UpdateCompanion { this.channelCid = const Value.absent(), this.extraData = const Value.absent(), }) : id = Value(id), - createdAt = Value(createdAt); + attachments = Value(attachments), + mentionedUsers = Value(mentionedUsers); static Insertable custom({ - Expression id, - Expression messageText, - Expression attachments, - Expression status, - Expression type, - Expression mentionedUsers, - Expression reactionCounts, - Expression reactionScores, - Expression parentId, - Expression quotedMessageId, - Expression replyCount, - Expression showInChannel, - Expression shadowed, - Expression command, - Expression createdAt, - Expression updatedAt, - Expression deletedAt, - Expression userId, - Expression pinned, - Expression pinnedAt, - Expression pinExpires, - Expression pinnedByUserId, - Expression channelCid, - Expression extraData, + Expression? id, + Expression? messageText, + Expression>? attachments, + Expression? status, + Expression? type, + Expression>? mentionedUsers, + Expression?>? reactionCounts, + Expression?>? reactionScores, + Expression? parentId, + Expression? quotedMessageId, + Expression? replyCount, + Expression? showInChannel, + Expression? shadowed, + Expression? command, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? userId, + Expression? pinned, + Expression? pinnedAt, + Expression? pinExpires, + Expression? pinnedByUserId, + Expression? channelCid, + Expression?>? extraData, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -2401,30 +2410,30 @@ class PinnedMessagesCompanion extends UpdateCompanion { } PinnedMessagesCompanion copyWith( - {Value id, - Value messageText, - Value> attachments, - Value status, - Value type, - Value> mentionedUsers, - Value> reactionCounts, - Value> reactionScores, - Value parentId, - Value quotedMessageId, - Value replyCount, - Value showInChannel, - Value shadowed, - Value command, - Value createdAt, - Value updatedAt, - Value deletedAt, - Value userId, - Value pinned, - Value pinnedAt, - Value pinExpires, - Value pinnedByUserId, - Value channelCid, - Value> extraData}) { + {Value? id, + Value? messageText, + Value>? attachments, + Value? status, + Value? type, + Value>? mentionedUsers, + Value?>? reactionCounts, + Value?>? reactionScores, + Value? parentId, + Value? quotedMessageId, + Value? replyCount, + Value? showInChannel, + Value? shadowed, + Value? command, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? userId, + Value? pinned, + Value? pinnedAt, + Value? pinExpires, + Value? pinnedByUserId, + Value? channelCid, + Value?>? extraData}) { return PinnedMessagesCompanion( id: id ?? this.id, messageText: messageText ?? this.messageText, @@ -2460,16 +2469,16 @@ class PinnedMessagesCompanion extends UpdateCompanion { map['id'] = Variable(id.value); } if (messageText.present) { - map['message_text'] = Variable(messageText.value); + map['message_text'] = Variable(messageText.value); } if (attachments.present) { final converter = $PinnedMessagesTable.$converter0; map['attachments'] = - Variable(converter.mapToSql(attachments.value)); + Variable(converter.mapToSql(attachments.value)!); } if (status.present) { final converter = $PinnedMessagesTable.$converter1; - map['status'] = Variable(converter.mapToSql(status.value)); + map['status'] = Variable(converter.mapToSql(status.value)!); } if (type.present) { map['type'] = Variable(type.value); @@ -2477,35 +2486,35 @@ class PinnedMessagesCompanion extends UpdateCompanion { if (mentionedUsers.present) { final converter = $PinnedMessagesTable.$converter2; map['mentioned_users'] = - Variable(converter.mapToSql(mentionedUsers.value)); + Variable(converter.mapToSql(mentionedUsers.value)!); } if (reactionCounts.present) { final converter = $PinnedMessagesTable.$converter3; map['reaction_counts'] = - Variable(converter.mapToSql(reactionCounts.value)); + Variable(converter.mapToSql(reactionCounts.value)); } if (reactionScores.present) { final converter = $PinnedMessagesTable.$converter4; map['reaction_scores'] = - Variable(converter.mapToSql(reactionScores.value)); + Variable(converter.mapToSql(reactionScores.value)); } if (parentId.present) { - map['parent_id'] = Variable(parentId.value); + map['parent_id'] = Variable(parentId.value); } if (quotedMessageId.present) { - map['quoted_message_id'] = Variable(quotedMessageId.value); + map['quoted_message_id'] = Variable(quotedMessageId.value); } if (replyCount.present) { - map['reply_count'] = Variable(replyCount.value); + map['reply_count'] = Variable(replyCount.value); } if (showInChannel.present) { - map['show_in_channel'] = Variable(showInChannel.value); + map['show_in_channel'] = Variable(showInChannel.value); } if (shadowed.present) { map['shadowed'] = Variable(shadowed.value); } if (command.present) { - map['command'] = Variable(command.value); + map['command'] = Variable(command.value); } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); @@ -2514,29 +2523,30 @@ class PinnedMessagesCompanion extends UpdateCompanion { map['updated_at'] = Variable(updatedAt.value); } if (deletedAt.present) { - map['deleted_at'] = Variable(deletedAt.value); + map['deleted_at'] = Variable(deletedAt.value); } if (userId.present) { - map['user_id'] = Variable(userId.value); + map['user_id'] = Variable(userId.value); } if (pinned.present) { map['pinned'] = Variable(pinned.value); } if (pinnedAt.present) { - map['pinned_at'] = Variable(pinnedAt.value); + map['pinned_at'] = Variable(pinnedAt.value); } if (pinExpires.present) { - map['pin_expires'] = Variable(pinExpires.value); + map['pin_expires'] = Variable(pinExpires.value); } if (pinnedByUserId.present) { - map['pinned_by_user_id'] = Variable(pinnedByUserId.value); + map['pinned_by_user_id'] = Variable(pinnedByUserId.value); } if (channelCid.present) { - map['channel_cid'] = Variable(channelCid.value); + map['channel_cid'] = Variable(channelCid.value); } if (extraData.present) { final converter = $PinnedMessagesTable.$converter5; - map['extra_data'] = Variable(converter.mapToSql(extraData.value)); + map['extra_data'] = + Variable(converter.mapToSql(extraData.value)); } return map; } @@ -2576,12 +2586,11 @@ class PinnedMessagesCompanion extends UpdateCompanion { class $PinnedMessagesTable extends PinnedMessages with TableInfo<$PinnedMessagesTable, PinnedMessageEntity> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $PinnedMessagesTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - GeneratedTextColumn _id; @override - GeneratedTextColumn get id => _id ??= _constructId(); + late final GeneratedTextColumn id = _constructId(); GeneratedTextColumn _constructId() { return GeneratedTextColumn( 'id', @@ -2592,10 +2601,8 @@ class $PinnedMessagesTable extends PinnedMessages final VerificationMeta _messageTextMeta = const VerificationMeta('messageText'); - GeneratedTextColumn _messageText; @override - GeneratedTextColumn get messageText => - _messageText ??= _constructMessageText(); + late final GeneratedTextColumn messageText = _constructMessageText(); GeneratedTextColumn _constructMessageText() { return GeneratedTextColumn( 'message_text', @@ -2606,62 +2613,48 @@ class $PinnedMessagesTable extends PinnedMessages final VerificationMeta _attachmentsMeta = const VerificationMeta('attachments'); - GeneratedTextColumn _attachments; @override - GeneratedTextColumn get attachments => - _attachments ??= _constructAttachments(); + late final GeneratedTextColumn attachments = _constructAttachments(); GeneratedTextColumn _constructAttachments() { return GeneratedTextColumn( 'attachments', $tableName, - true, + false, ); } final VerificationMeta _statusMeta = const VerificationMeta('status'); - GeneratedIntColumn _status; @override - GeneratedIntColumn get status => _status ??= _constructStatus(); + late final GeneratedIntColumn status = _constructStatus(); GeneratedIntColumn _constructStatus() { - return GeneratedIntColumn( - 'status', - $tableName, - true, - ); + return GeneratedIntColumn('status', $tableName, false, + defaultValue: const Constant(1)); } final VerificationMeta _typeMeta = const VerificationMeta('type'); - GeneratedTextColumn _type; @override - GeneratedTextColumn get type => _type ??= _constructType(); + late final GeneratedTextColumn type = _constructType(); GeneratedTextColumn _constructType() { - return GeneratedTextColumn( - 'type', - $tableName, - true, - ); + return GeneratedTextColumn('type', $tableName, false, + defaultValue: const Constant('regular')); } final VerificationMeta _mentionedUsersMeta = const VerificationMeta('mentionedUsers'); - GeneratedTextColumn _mentionedUsers; @override - GeneratedTextColumn get mentionedUsers => - _mentionedUsers ??= _constructMentionedUsers(); + late final GeneratedTextColumn mentionedUsers = _constructMentionedUsers(); GeneratedTextColumn _constructMentionedUsers() { return GeneratedTextColumn( 'mentioned_users', $tableName, - true, + false, ); } final VerificationMeta _reactionCountsMeta = const VerificationMeta('reactionCounts'); - GeneratedTextColumn _reactionCounts; @override - GeneratedTextColumn get reactionCounts => - _reactionCounts ??= _constructReactionCounts(); + late final GeneratedTextColumn reactionCounts = _constructReactionCounts(); GeneratedTextColumn _constructReactionCounts() { return GeneratedTextColumn( 'reaction_counts', @@ -2672,10 +2665,8 @@ class $PinnedMessagesTable extends PinnedMessages final VerificationMeta _reactionScoresMeta = const VerificationMeta('reactionScores'); - GeneratedTextColumn _reactionScores; @override - GeneratedTextColumn get reactionScores => - _reactionScores ??= _constructReactionScores(); + late final GeneratedTextColumn reactionScores = _constructReactionScores(); GeneratedTextColumn _constructReactionScores() { return GeneratedTextColumn( 'reaction_scores', @@ -2685,9 +2676,8 @@ class $PinnedMessagesTable extends PinnedMessages } final VerificationMeta _parentIdMeta = const VerificationMeta('parentId'); - GeneratedTextColumn _parentId; @override - GeneratedTextColumn get parentId => _parentId ??= _constructParentId(); + late final GeneratedTextColumn parentId = _constructParentId(); GeneratedTextColumn _constructParentId() { return GeneratedTextColumn( 'parent_id', @@ -2698,10 +2688,8 @@ class $PinnedMessagesTable extends PinnedMessages final VerificationMeta _quotedMessageIdMeta = const VerificationMeta('quotedMessageId'); - GeneratedTextColumn _quotedMessageId; @override - GeneratedTextColumn get quotedMessageId => - _quotedMessageId ??= _constructQuotedMessageId(); + late final GeneratedTextColumn quotedMessageId = _constructQuotedMessageId(); GeneratedTextColumn _constructQuotedMessageId() { return GeneratedTextColumn( 'quoted_message_id', @@ -2711,9 +2699,8 @@ class $PinnedMessagesTable extends PinnedMessages } final VerificationMeta _replyCountMeta = const VerificationMeta('replyCount'); - GeneratedIntColumn _replyCount; @override - GeneratedIntColumn get replyCount => _replyCount ??= _constructReplyCount(); + late final GeneratedIntColumn replyCount = _constructReplyCount(); GeneratedIntColumn _constructReplyCount() { return GeneratedIntColumn( 'reply_count', @@ -2724,10 +2711,8 @@ class $PinnedMessagesTable extends PinnedMessages final VerificationMeta _showInChannelMeta = const VerificationMeta('showInChannel'); - GeneratedBoolColumn _showInChannel; @override - GeneratedBoolColumn get showInChannel => - _showInChannel ??= _constructShowInChannel(); + late final GeneratedBoolColumn showInChannel = _constructShowInChannel(); GeneratedBoolColumn _constructShowInChannel() { return GeneratedBoolColumn( 'show_in_channel', @@ -2737,21 +2722,16 @@ class $PinnedMessagesTable extends PinnedMessages } final VerificationMeta _shadowedMeta = const VerificationMeta('shadowed'); - GeneratedBoolColumn _shadowed; @override - GeneratedBoolColumn get shadowed => _shadowed ??= _constructShadowed(); + late final GeneratedBoolColumn shadowed = _constructShadowed(); GeneratedBoolColumn _constructShadowed() { - return GeneratedBoolColumn( - 'shadowed', - $tableName, - true, - ); + return GeneratedBoolColumn('shadowed', $tableName, false, + defaultValue: const Constant(false)); } final VerificationMeta _commandMeta = const VerificationMeta('command'); - GeneratedTextColumn _command; @override - GeneratedTextColumn get command => _command ??= _constructCommand(); + late final GeneratedTextColumn command = _constructCommand(); GeneratedTextColumn _constructCommand() { return GeneratedTextColumn( 'command', @@ -2761,33 +2741,24 @@ class $PinnedMessagesTable extends PinnedMessages } final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - GeneratedDateTimeColumn _createdAt; @override - GeneratedDateTimeColumn get createdAt => _createdAt ??= _constructCreatedAt(); + late final GeneratedDateTimeColumn createdAt = _constructCreatedAt(); GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn( - 'created_at', - $tableName, - false, - ); + return GeneratedDateTimeColumn('created_at', $tableName, false, + defaultValue: currentDateAndTime); } final VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); - GeneratedDateTimeColumn _updatedAt; @override - GeneratedDateTimeColumn get updatedAt => _updatedAt ??= _constructUpdatedAt(); + late final GeneratedDateTimeColumn updatedAt = _constructUpdatedAt(); GeneratedDateTimeColumn _constructUpdatedAt() { - return GeneratedDateTimeColumn( - 'updated_at', - $tableName, - true, - ); + return GeneratedDateTimeColumn('updated_at', $tableName, false, + defaultValue: currentDateAndTime); } final VerificationMeta _deletedAtMeta = const VerificationMeta('deletedAt'); - GeneratedDateTimeColumn _deletedAt; @override - GeneratedDateTimeColumn get deletedAt => _deletedAt ??= _constructDeletedAt(); + late final GeneratedDateTimeColumn deletedAt = _constructDeletedAt(); GeneratedDateTimeColumn _constructDeletedAt() { return GeneratedDateTimeColumn( 'deleted_at', @@ -2797,9 +2768,8 @@ class $PinnedMessagesTable extends PinnedMessages } final VerificationMeta _userIdMeta = const VerificationMeta('userId'); - GeneratedTextColumn _userId; @override - GeneratedTextColumn get userId => _userId ??= _constructUserId(); + late final GeneratedTextColumn userId = _constructUserId(); GeneratedTextColumn _constructUserId() { return GeneratedTextColumn( 'user_id', @@ -2809,18 +2779,16 @@ class $PinnedMessagesTable extends PinnedMessages } final VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); - GeneratedBoolColumn _pinned; @override - GeneratedBoolColumn get pinned => _pinned ??= _constructPinned(); + late final GeneratedBoolColumn pinned = _constructPinned(); GeneratedBoolColumn _constructPinned() { return GeneratedBoolColumn('pinned', $tableName, false, defaultValue: const Constant(false)); } final VerificationMeta _pinnedAtMeta = const VerificationMeta('pinnedAt'); - GeneratedDateTimeColumn _pinnedAt; @override - GeneratedDateTimeColumn get pinnedAt => _pinnedAt ??= _constructPinnedAt(); + late final GeneratedDateTimeColumn pinnedAt = _constructPinnedAt(); GeneratedDateTimeColumn _constructPinnedAt() { return GeneratedDateTimeColumn( 'pinned_at', @@ -2830,10 +2798,8 @@ class $PinnedMessagesTable extends PinnedMessages } final VerificationMeta _pinExpiresMeta = const VerificationMeta('pinExpires'); - GeneratedDateTimeColumn _pinExpires; @override - GeneratedDateTimeColumn get pinExpires => - _pinExpires ??= _constructPinExpires(); + late final GeneratedDateTimeColumn pinExpires = _constructPinExpires(); GeneratedDateTimeColumn _constructPinExpires() { return GeneratedDateTimeColumn( 'pin_expires', @@ -2844,10 +2810,8 @@ class $PinnedMessagesTable extends PinnedMessages final VerificationMeta _pinnedByUserIdMeta = const VerificationMeta('pinnedByUserId'); - GeneratedTextColumn _pinnedByUserId; @override - GeneratedTextColumn get pinnedByUserId => - _pinnedByUserId ??= _constructPinnedByUserId(); + late final GeneratedTextColumn pinnedByUserId = _constructPinnedByUserId(); GeneratedTextColumn _constructPinnedByUserId() { return GeneratedTextColumn( 'pinned_by_user_id', @@ -2857,9 +2821,8 @@ class $PinnedMessagesTable extends PinnedMessages } final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); - GeneratedTextColumn _channelCid; @override - GeneratedTextColumn get channelCid => _channelCid ??= _constructChannelCid(); + late final GeneratedTextColumn channelCid = _constructChannelCid(); GeneratedTextColumn _constructChannelCid() { return GeneratedTextColumn('channel_cid', $tableName, true, $customConstraints: @@ -2867,9 +2830,8 @@ class $PinnedMessagesTable extends PinnedMessages } final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); - GeneratedTextColumn _extraData; @override - GeneratedTextColumn get extraData => _extraData ??= _constructExtraData(); + late final GeneratedTextColumn extraData = _constructExtraData(); GeneratedTextColumn _constructExtraData() { return GeneratedTextColumn( 'extra_data', @@ -2918,7 +2880,7 @@ class $PinnedMessagesTable extends PinnedMessages final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } else if (isInserting) { context.missing(_idMeta); } @@ -2926,90 +2888,88 @@ class $PinnedMessagesTable extends PinnedMessages context.handle( _messageTextMeta, messageText.isAcceptableOrUnknown( - data['message_text'], _messageTextMeta)); + data['message_text']!, _messageTextMeta)); } context.handle(_attachmentsMeta, const VerificationResult.success()); context.handle(_statusMeta, const VerificationResult.success()); if (data.containsKey('type')) { context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type'], _typeMeta)); + _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } context.handle(_mentionedUsersMeta, const VerificationResult.success()); context.handle(_reactionCountsMeta, const VerificationResult.success()); context.handle(_reactionScoresMeta, const VerificationResult.success()); if (data.containsKey('parent_id')) { context.handle(_parentIdMeta, - parentId.isAcceptableOrUnknown(data['parent_id'], _parentIdMeta)); + parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); } if (data.containsKey('quoted_message_id')) { context.handle( _quotedMessageIdMeta, quotedMessageId.isAcceptableOrUnknown( - data['quoted_message_id'], _quotedMessageIdMeta)); + data['quoted_message_id']!, _quotedMessageIdMeta)); } if (data.containsKey('reply_count')) { context.handle( _replyCountMeta, replyCount.isAcceptableOrUnknown( - data['reply_count'], _replyCountMeta)); + data['reply_count']!, _replyCountMeta)); } if (data.containsKey('show_in_channel')) { context.handle( _showInChannelMeta, showInChannel.isAcceptableOrUnknown( - data['show_in_channel'], _showInChannelMeta)); + data['show_in_channel']!, _showInChannelMeta)); } if (data.containsKey('shadowed')) { context.handle(_shadowedMeta, - shadowed.isAcceptableOrUnknown(data['shadowed'], _shadowedMeta)); + shadowed.isAcceptableOrUnknown(data['shadowed']!, _shadowedMeta)); } if (data.containsKey('command')) { context.handle(_commandMeta, - command.isAcceptableOrUnknown(data['command'], _commandMeta)); + command.isAcceptableOrUnknown(data['command']!, _commandMeta)); } if (data.containsKey('created_at')) { context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at'], _createdAtMeta)); - } else if (isInserting) { - context.missing(_createdAtMeta); + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at'], _updatedAtMeta)); + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('deleted_at')) { context.handle(_deletedAtMeta, - deletedAt.isAcceptableOrUnknown(data['deleted_at'], _deletedAtMeta)); + deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta)); } if (data.containsKey('user_id')) { context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id'], _userIdMeta)); + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('pinned')) { context.handle(_pinnedMeta, - pinned.isAcceptableOrUnknown(data['pinned'], _pinnedMeta)); + pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); } if (data.containsKey('pinned_at')) { context.handle(_pinnedAtMeta, - pinnedAt.isAcceptableOrUnknown(data['pinned_at'], _pinnedAtMeta)); + pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); } if (data.containsKey('pin_expires')) { context.handle( _pinExpiresMeta, pinExpires.isAcceptableOrUnknown( - data['pin_expires'], _pinExpiresMeta)); + data['pin_expires']!, _pinExpiresMeta)); } if (data.containsKey('pinned_by_user_id')) { context.handle( _pinnedByUserIdMeta, pinnedByUserId.isAcceptableOrUnknown( - data['pinned_by_user_id'], _pinnedByUserIdMeta)); + data['pinned_by_user_id']!, _pinnedByUserIdMeta)); } if (data.containsKey('channel_cid')) { context.handle( _channelCidMeta, channelCid.isAcceptableOrUnknown( - data['channel_cid'], _channelCidMeta)); + data['channel_cid']!, _channelCidMeta)); } context.handle(_extraDataMeta, const VerificationResult.success()); return context; @@ -3018,7 +2978,7 @@ class $PinnedMessagesTable extends PinnedMessages @override Set get $primaryKey => {id}; @override - PinnedMessageEntity map(Map data, {String tablePrefix}) { + PinnedMessageEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; return PinnedMessageEntity.fromData(data, _db, prefix: effectivePrefix); } @@ -3043,35 +3003,46 @@ class $PinnedMessagesTable extends PinnedMessages } class ReactionEntity extends DataClass implements Insertable { + /// The id of the user that sent the reaction final String userId; + + /// The messageId to which the reaction belongs final String messageId; + + /// The type of the reaction final String type; + + /// The DateTime on which the reaction is created final DateTime createdAt; + + /// The score of the reaction (ie. number of reactions sent) final int score; - final Map extraData; + + /// Reaction custom extraData + final Map? extraData; ReactionEntity( - {@required this.userId, - @required this.messageId, - @required this.type, - @required this.createdAt, - this.score, + {required this.userId, + required this.messageId, + required this.type, + required this.createdAt, + required this.score, this.extraData}); factory ReactionEntity.fromData( Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; final stringType = db.typeSystem.forDartType(); final dateTimeType = db.typeSystem.forDartType(); final intType = db.typeSystem.forDartType(); return ReactionEntity( - userId: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), + userId: stringType + .mapFromDatabaseResponse(data['${effectivePrefix}user_id'])!, messageId: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}message_id']), - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type']), + .mapFromDatabaseResponse(data['${effectivePrefix}message_id'])!, + type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type'])!, createdAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}created_at']), - score: intType.mapFromDatabaseResponse(data['${effectivePrefix}score']), + .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, + score: intType.mapFromDatabaseResponse(data['${effectivePrefix}score'])!, extraData: $ReactionsTable.$converter0.mapToDart(stringType .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); @@ -3079,30 +3050,20 @@ class ReactionEntity extends DataClass implements Insertable { @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || userId != null) { - map['user_id'] = Variable(userId); - } - if (!nullToAbsent || messageId != null) { - map['message_id'] = Variable(messageId); - } - if (!nullToAbsent || type != null) { - map['type'] = Variable(type); - } - if (!nullToAbsent || createdAt != null) { - map['created_at'] = Variable(createdAt); - } - if (!nullToAbsent || score != null) { - map['score'] = Variable(score); - } + map['user_id'] = Variable(userId); + map['message_id'] = Variable(messageId); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['score'] = Variable(score); if (!nullToAbsent || extraData != null) { final converter = $ReactionsTable.$converter0; - map['extra_data'] = Variable(converter.mapToSql(extraData)); + map['extra_data'] = Variable(converter.mapToSql(extraData)); } return map; } factory ReactionEntity.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return ReactionEntity( userId: serializer.fromJson(json['userId']), @@ -3110,11 +3071,11 @@ class ReactionEntity extends DataClass implements Insertable { type: serializer.fromJson(json['type']), createdAt: serializer.fromJson(json['createdAt']), score: serializer.fromJson(json['score']), - extraData: serializer.fromJson>(json['extraData']), + extraData: serializer.fromJson?>(json['extraData']), ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'userId': serializer.toJson(userId), @@ -3122,23 +3083,23 @@ class ReactionEntity extends DataClass implements Insertable { 'type': serializer.toJson(type), 'createdAt': serializer.toJson(createdAt), 'score': serializer.toJson(score), - 'extraData': serializer.toJson>(extraData), + 'extraData': serializer.toJson?>(extraData), }; } ReactionEntity copyWith( - {String userId, - String messageId, - String type, - DateTime createdAt, - Value score = const Value.absent(), - Value> extraData = const Value.absent()}) => + {String? userId, + String? messageId, + String? type, + DateTime? createdAt, + int? score, + Value?> extraData = const Value.absent()}) => ReactionEntity( userId: userId ?? this.userId, messageId: messageId ?? this.messageId, type: type ?? this.type, createdAt: createdAt ?? this.createdAt, - score: score.present ? score.value : this.score, + score: score ?? this.score, extraData: extraData.present ? extraData.value : this.extraData, ); @override @@ -3181,7 +3142,7 @@ class ReactionsCompanion extends UpdateCompanion { final Value type; final Value createdAt; final Value score; - final Value> extraData; + final Value?> extraData; const ReactionsCompanion({ this.userId = const Value.absent(), this.messageId = const Value.absent(), @@ -3191,23 +3152,22 @@ class ReactionsCompanion extends UpdateCompanion { this.extraData = const Value.absent(), }); ReactionsCompanion.insert({ - @required String userId, - @required String messageId, - @required String type, - @required DateTime createdAt, + required String userId, + required String messageId, + required String type, + this.createdAt = const Value.absent(), this.score = const Value.absent(), this.extraData = const Value.absent(), }) : userId = Value(userId), messageId = Value(messageId), - type = Value(type), - createdAt = Value(createdAt); + type = Value(type); static Insertable custom({ - Expression userId, - Expression messageId, - Expression type, - Expression createdAt, - Expression score, - Expression extraData, + Expression? userId, + Expression? messageId, + Expression? type, + Expression? createdAt, + Expression? score, + Expression?>? extraData, }) { return RawValuesInsertable({ if (userId != null) 'user_id': userId, @@ -3220,12 +3180,12 @@ class ReactionsCompanion extends UpdateCompanion { } ReactionsCompanion copyWith( - {Value userId, - Value messageId, - Value type, - Value createdAt, - Value score, - Value> extraData}) { + {Value? userId, + Value? messageId, + Value? type, + Value? createdAt, + Value? score, + Value?>? extraData}) { return ReactionsCompanion( userId: userId ?? this.userId, messageId: messageId ?? this.messageId, @@ -3256,7 +3216,8 @@ class ReactionsCompanion extends UpdateCompanion { } if (extraData.present) { final converter = $ReactionsTable.$converter0; - map['extra_data'] = Variable(converter.mapToSql(extraData.value)); + map['extra_data'] = + Variable(converter.mapToSql(extraData.value)); } return map; } @@ -3278,12 +3239,11 @@ class ReactionsCompanion extends UpdateCompanion { class $ReactionsTable extends Reactions with TableInfo<$ReactionsTable, ReactionEntity> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $ReactionsTable(this._db, [this._alias]); final VerificationMeta _userIdMeta = const VerificationMeta('userId'); - GeneratedTextColumn _userId; @override - GeneratedTextColumn get userId => _userId ??= _constructUserId(); + late final GeneratedTextColumn userId = _constructUserId(); GeneratedTextColumn _constructUserId() { return GeneratedTextColumn( 'user_id', @@ -3293,18 +3253,16 @@ class $ReactionsTable extends Reactions } final VerificationMeta _messageIdMeta = const VerificationMeta('messageId'); - GeneratedTextColumn _messageId; @override - GeneratedTextColumn get messageId => _messageId ??= _constructMessageId(); + late final GeneratedTextColumn messageId = _constructMessageId(); GeneratedTextColumn _constructMessageId() { return GeneratedTextColumn('message_id', $tableName, false, $customConstraints: 'REFERENCES messages(id) ON DELETE CASCADE'); } final VerificationMeta _typeMeta = const VerificationMeta('type'); - GeneratedTextColumn _type; @override - GeneratedTextColumn get type => _type ??= _constructType(); + late final GeneratedTextColumn type = _constructType(); GeneratedTextColumn _constructType() { return GeneratedTextColumn( 'type', @@ -3314,33 +3272,24 @@ class $ReactionsTable extends Reactions } final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - GeneratedDateTimeColumn _createdAt; @override - GeneratedDateTimeColumn get createdAt => _createdAt ??= _constructCreatedAt(); + late final GeneratedDateTimeColumn createdAt = _constructCreatedAt(); GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn( - 'created_at', - $tableName, - false, - ); + return GeneratedDateTimeColumn('created_at', $tableName, false, + defaultValue: currentDateAndTime); } final VerificationMeta _scoreMeta = const VerificationMeta('score'); - GeneratedIntColumn _score; @override - GeneratedIntColumn get score => _score ??= _constructScore(); + late final GeneratedIntColumn score = _constructScore(); GeneratedIntColumn _constructScore() { - return GeneratedIntColumn( - 'score', - $tableName, - true, - ); + return GeneratedIntColumn('score', $tableName, false, + defaultValue: const Constant(0)); } final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); - GeneratedTextColumn _extraData; @override - GeneratedTextColumn get extraData => _extraData ??= _constructExtraData(); + late final GeneratedTextColumn extraData = _constructExtraData(); GeneratedTextColumn _constructExtraData() { return GeneratedTextColumn( 'extra_data', @@ -3365,31 +3314,29 @@ class $ReactionsTable extends Reactions final data = instance.toColumns(true); if (data.containsKey('user_id')) { context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id'], _userIdMeta)); + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } else if (isInserting) { context.missing(_userIdMeta); } if (data.containsKey('message_id')) { context.handle(_messageIdMeta, - messageId.isAcceptableOrUnknown(data['message_id'], _messageIdMeta)); + messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); } else if (isInserting) { context.missing(_messageIdMeta); } if (data.containsKey('type')) { context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type'], _typeMeta)); + _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } else if (isInserting) { context.missing(_typeMeta); } if (data.containsKey('created_at')) { context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at'], _createdAtMeta)); - } else if (isInserting) { - context.missing(_createdAtMeta); + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('score')) { context.handle( - _scoreMeta, score.isAcceptableOrUnknown(data['score'], _scoreMeta)); + _scoreMeta, score.isAcceptableOrUnknown(data['score']!, _scoreMeta)); } context.handle(_extraDataMeta, const VerificationResult.success()); return context; @@ -3398,7 +3345,7 @@ class $ReactionsTable extends Reactions @override Set get $primaryKey => {messageId, type, userId}; @override - ReactionEntity map(Map data, {String tablePrefix}) { + ReactionEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; return ReactionEntity.fromData(data, _db, prefix: effectivePrefix); } @@ -3413,42 +3360,57 @@ class $ReactionsTable extends Reactions } class UserEntity extends DataClass implements Insertable { + /// User id final String id; - final String role; + + /// User role + final String? role; + + /// Date of user creation final DateTime createdAt; + + /// Date of last user update final DateTime updatedAt; - final DateTime lastActive; + + /// Date of last user connection + final DateTime? lastActive; + + /// True if user is online final bool online; + + /// True if user is banned from the chat final bool banned; - final Map extraData; + + /// Map of custom user extraData + final Map? extraData; UserEntity( - {@required this.id, + {required this.id, this.role, - this.createdAt, - this.updatedAt, + required this.createdAt, + required this.updatedAt, this.lastActive, - this.online, - this.banned, + required this.online, + required this.banned, this.extraData}); factory UserEntity.fromData(Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; final stringType = db.typeSystem.forDartType(); final dateTimeType = db.typeSystem.forDartType(); final boolType = db.typeSystem.forDartType(); return UserEntity( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id']), + id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, role: stringType.mapFromDatabaseResponse(data['${effectivePrefix}role']), createdAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}created_at']), + .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, updatedAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}updated_at']), + .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, lastActive: dateTimeType .mapFromDatabaseResponse(data['${effectivePrefix}last_active']), online: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}online']), + boolType.mapFromDatabaseResponse(data['${effectivePrefix}online'])!, banned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}banned']), + boolType.mapFromDatabaseResponse(data['${effectivePrefix}banned'])!, extraData: $UsersTable.$converter0.mapToDart(stringType .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); @@ -3456,80 +3418,70 @@ class UserEntity extends DataClass implements Insertable { @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } + map['id'] = Variable(id); if (!nullToAbsent || role != null) { - map['role'] = Variable(role); - } - if (!nullToAbsent || createdAt != null) { - map['created_at'] = Variable(createdAt); - } - if (!nullToAbsent || updatedAt != null) { - map['updated_at'] = Variable(updatedAt); + map['role'] = Variable(role); } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); if (!nullToAbsent || lastActive != null) { - map['last_active'] = Variable(lastActive); - } - if (!nullToAbsent || online != null) { - map['online'] = Variable(online); - } - if (!nullToAbsent || banned != null) { - map['banned'] = Variable(banned); + map['last_active'] = Variable(lastActive); } + map['online'] = Variable(online); + map['banned'] = Variable(banned); if (!nullToAbsent || extraData != null) { final converter = $UsersTable.$converter0; - map['extra_data'] = Variable(converter.mapToSql(extraData)); + map['extra_data'] = Variable(converter.mapToSql(extraData)); } return map; } factory UserEntity.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return UserEntity( id: serializer.fromJson(json['id']), - role: serializer.fromJson(json['role']), + role: serializer.fromJson(json['role']), createdAt: serializer.fromJson(json['createdAt']), updatedAt: serializer.fromJson(json['updatedAt']), - lastActive: serializer.fromJson(json['lastActive']), + lastActive: serializer.fromJson(json['lastActive']), online: serializer.fromJson(json['online']), banned: serializer.fromJson(json['banned']), - extraData: serializer.fromJson>(json['extraData']), + extraData: serializer.fromJson?>(json['extraData']), ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), - 'role': serializer.toJson(role), + 'role': serializer.toJson(role), 'createdAt': serializer.toJson(createdAt), 'updatedAt': serializer.toJson(updatedAt), - 'lastActive': serializer.toJson(lastActive), + 'lastActive': serializer.toJson(lastActive), 'online': serializer.toJson(online), 'banned': serializer.toJson(banned), - 'extraData': serializer.toJson>(extraData), + 'extraData': serializer.toJson?>(extraData), }; } UserEntity copyWith( - {String id, - Value role = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value lastActive = const Value.absent(), - Value online = const Value.absent(), - Value banned = const Value.absent(), - Value> extraData = const Value.absent()}) => + {String? id, + Value role = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + Value lastActive = const Value.absent(), + bool? online, + bool? banned, + Value?> extraData = const Value.absent()}) => UserEntity( id: id ?? this.id, role: role.present ? role.value : this.role, - createdAt: createdAt.present ? createdAt.value : this.createdAt, - updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, lastActive: lastActive.present ? lastActive.value : this.lastActive, - online: online.present ? online.value : this.online, - banned: banned.present ? banned.value : this.banned, + online: online ?? this.online, + banned: banned ?? this.banned, extraData: extraData.present ? extraData.value : this.extraData, ); @override @@ -3576,13 +3528,13 @@ class UserEntity extends DataClass implements Insertable { class UsersCompanion extends UpdateCompanion { final Value id; - final Value role; + final Value role; final Value createdAt; final Value updatedAt; - final Value lastActive; + final Value lastActive; final Value online; final Value banned; - final Value> extraData; + final Value?> extraData; const UsersCompanion({ this.id = const Value.absent(), this.role = const Value.absent(), @@ -3594,7 +3546,7 @@ class UsersCompanion extends UpdateCompanion { this.extraData = const Value.absent(), }); UsersCompanion.insert({ - @required String id, + required String id, this.role = const Value.absent(), this.createdAt = const Value.absent(), this.updatedAt = const Value.absent(), @@ -3604,14 +3556,14 @@ class UsersCompanion extends UpdateCompanion { this.extraData = const Value.absent(), }) : id = Value(id); static Insertable custom({ - Expression id, - Expression role, - Expression createdAt, - Expression updatedAt, - Expression lastActive, - Expression online, - Expression banned, - Expression extraData, + Expression? id, + Expression? role, + Expression? createdAt, + Expression? updatedAt, + Expression? lastActive, + Expression? online, + Expression? banned, + Expression?>? extraData, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -3626,14 +3578,14 @@ class UsersCompanion extends UpdateCompanion { } UsersCompanion copyWith( - {Value id, - Value role, - Value createdAt, - Value updatedAt, - Value lastActive, - Value online, - Value banned, - Value> extraData}) { + {Value? id, + Value? role, + Value? createdAt, + Value? updatedAt, + Value? lastActive, + Value? online, + Value? banned, + Value?>? extraData}) { return UsersCompanion( id: id ?? this.id, role: role ?? this.role, @@ -3653,7 +3605,7 @@ class UsersCompanion extends UpdateCompanion { map['id'] = Variable(id.value); } if (role.present) { - map['role'] = Variable(role.value); + map['role'] = Variable(role.value); } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); @@ -3662,7 +3614,7 @@ class UsersCompanion extends UpdateCompanion { map['updated_at'] = Variable(updatedAt.value); } if (lastActive.present) { - map['last_active'] = Variable(lastActive.value); + map['last_active'] = Variable(lastActive.value); } if (online.present) { map['online'] = Variable(online.value); @@ -3672,7 +3624,8 @@ class UsersCompanion extends UpdateCompanion { } if (extraData.present) { final converter = $UsersTable.$converter0; - map['extra_data'] = Variable(converter.mapToSql(extraData.value)); + map['extra_data'] = + Variable(converter.mapToSql(extraData.value)); } return map; } @@ -3695,12 +3648,11 @@ class UsersCompanion extends UpdateCompanion { class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $UsersTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - GeneratedTextColumn _id; @override - GeneratedTextColumn get id => _id ??= _constructId(); + late final GeneratedTextColumn id = _constructId(); GeneratedTextColumn _constructId() { return GeneratedTextColumn( 'id', @@ -3710,9 +3662,8 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { } final VerificationMeta _roleMeta = const VerificationMeta('role'); - GeneratedTextColumn _role; @override - GeneratedTextColumn get role => _role ??= _constructRole(); + late final GeneratedTextColumn role = _constructRole(); GeneratedTextColumn _constructRole() { return GeneratedTextColumn( 'role', @@ -3722,34 +3673,24 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { } final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - GeneratedDateTimeColumn _createdAt; @override - GeneratedDateTimeColumn get createdAt => _createdAt ??= _constructCreatedAt(); + late final GeneratedDateTimeColumn createdAt = _constructCreatedAt(); GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn( - 'created_at', - $tableName, - true, - ); + return GeneratedDateTimeColumn('created_at', $tableName, false, + defaultValue: currentDateAndTime); } final VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); - GeneratedDateTimeColumn _updatedAt; @override - GeneratedDateTimeColumn get updatedAt => _updatedAt ??= _constructUpdatedAt(); + late final GeneratedDateTimeColumn updatedAt = _constructUpdatedAt(); GeneratedDateTimeColumn _constructUpdatedAt() { - return GeneratedDateTimeColumn( - 'updated_at', - $tableName, - true, - ); + return GeneratedDateTimeColumn('updated_at', $tableName, false, + defaultValue: currentDateAndTime); } final VerificationMeta _lastActiveMeta = const VerificationMeta('lastActive'); - GeneratedDateTimeColumn _lastActive; @override - GeneratedDateTimeColumn get lastActive => - _lastActive ??= _constructLastActive(); + late final GeneratedDateTimeColumn lastActive = _constructLastActive(); GeneratedDateTimeColumn _constructLastActive() { return GeneratedDateTimeColumn( 'last_active', @@ -3759,33 +3700,24 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { } final VerificationMeta _onlineMeta = const VerificationMeta('online'); - GeneratedBoolColumn _online; @override - GeneratedBoolColumn get online => _online ??= _constructOnline(); + late final GeneratedBoolColumn online = _constructOnline(); GeneratedBoolColumn _constructOnline() { - return GeneratedBoolColumn( - 'online', - $tableName, - true, - ); + return GeneratedBoolColumn('online', $tableName, false, + defaultValue: const Constant(false)); } final VerificationMeta _bannedMeta = const VerificationMeta('banned'); - GeneratedBoolColumn _banned; @override - GeneratedBoolColumn get banned => _banned ??= _constructBanned(); + late final GeneratedBoolColumn banned = _constructBanned(); GeneratedBoolColumn _constructBanned() { - return GeneratedBoolColumn( - 'banned', - $tableName, - true, - ); + return GeneratedBoolColumn('banned', $tableName, false, + defaultValue: const Constant(false)); } final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); - GeneratedTextColumn _extraData; @override - GeneratedTextColumn get extraData => _extraData ??= _constructExtraData(); + late final GeneratedTextColumn extraData = _constructExtraData(); GeneratedTextColumn _constructExtraData() { return GeneratedTextColumn( 'extra_data', @@ -3809,35 +3741,35 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } else if (isInserting) { context.missing(_idMeta); } if (data.containsKey('role')) { context.handle( - _roleMeta, role.isAcceptableOrUnknown(data['role'], _roleMeta)); + _roleMeta, role.isAcceptableOrUnknown(data['role']!, _roleMeta)); } if (data.containsKey('created_at')) { context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at'], _createdAtMeta)); + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at'], _updatedAtMeta)); + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('last_active')) { context.handle( _lastActiveMeta, lastActive.isAcceptableOrUnknown( - data['last_active'], _lastActiveMeta)); + data['last_active']!, _lastActiveMeta)); } if (data.containsKey('online')) { context.handle(_onlineMeta, - online.isAcceptableOrUnknown(data['online'], _onlineMeta)); + online.isAcceptableOrUnknown(data['online']!, _onlineMeta)); } if (data.containsKey('banned')) { context.handle(_bannedMeta, - banned.isAcceptableOrUnknown(data['banned'], _bannedMeta)); + banned.isAcceptableOrUnknown(data['banned']!, _bannedMeta)); } context.handle(_extraDataMeta, const VerificationResult.success()); return context; @@ -3846,7 +3778,7 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { @override Set get $primaryKey => {id}; @override - UserEntity map(Map data, {String tablePrefix}) { + UserEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; return UserEntity.fromData(data, _db, prefix: effectivePrefix); } @@ -3861,107 +3793,114 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { } class MemberEntity extends DataClass implements Insertable { + /// The interested user id final String userId; + + /// The channel cid of which this user is part of final String channelCid; - final String role; - final DateTime inviteAcceptedAt; - final DateTime inviteRejectedAt; + + /// The role of the user in the channel + final String? role; + + /// The date on which the user accepted the invite to the channel + final DateTime? inviteAcceptedAt; + + /// The date on which the user rejected the invite to the channel + final DateTime? inviteRejectedAt; + + /// True if the user has been invited to the channel final bool invited; + + /// True if the member is banned from the channel final bool banned; + + /// True if the member is shadow banned from the channel final bool shadowBanned; + + /// True if the user is a moderator of the channel final bool isModerator; + + /// The date of creation final DateTime createdAt; + + /// The last date of update final DateTime updatedAt; MemberEntity( - {@required this.userId, - @required this.channelCid, + {required this.userId, + required this.channelCid, this.role, this.inviteAcceptedAt, this.inviteRejectedAt, - this.invited, - this.banned, - this.shadowBanned, - this.isModerator, - @required this.createdAt, - this.updatedAt}); + required this.invited, + required this.banned, + required this.shadowBanned, + required this.isModerator, + required this.createdAt, + required this.updatedAt}); factory MemberEntity.fromData(Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; final stringType = db.typeSystem.forDartType(); final dateTimeType = db.typeSystem.forDartType(); final boolType = db.typeSystem.forDartType(); return MemberEntity( - userId: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), + userId: stringType + .mapFromDatabaseResponse(data['${effectivePrefix}user_id'])!, channelCid: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), + .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!, role: stringType.mapFromDatabaseResponse(data['${effectivePrefix}role']), inviteAcceptedAt: dateTimeType.mapFromDatabaseResponse( data['${effectivePrefix}invite_accepted_at']), inviteRejectedAt: dateTimeType.mapFromDatabaseResponse( data['${effectivePrefix}invite_rejected_at']), invited: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}invited']), + boolType.mapFromDatabaseResponse(data['${effectivePrefix}invited'])!, banned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}banned']), + boolType.mapFromDatabaseResponse(data['${effectivePrefix}banned'])!, shadowBanned: boolType - .mapFromDatabaseResponse(data['${effectivePrefix}shadow_banned']), + .mapFromDatabaseResponse(data['${effectivePrefix}shadow_banned'])!, isModerator: boolType - .mapFromDatabaseResponse(data['${effectivePrefix}is_moderator']), + .mapFromDatabaseResponse(data['${effectivePrefix}is_moderator'])!, createdAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}created_at']), + .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, updatedAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}updated_at']), + .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || userId != null) { - map['user_id'] = Variable(userId); - } - if (!nullToAbsent || channelCid != null) { - map['channel_cid'] = Variable(channelCid); - } + map['user_id'] = Variable(userId); + map['channel_cid'] = Variable(channelCid); if (!nullToAbsent || role != null) { - map['role'] = Variable(role); + map['role'] = Variable(role); } if (!nullToAbsent || inviteAcceptedAt != null) { - map['invite_accepted_at'] = Variable(inviteAcceptedAt); + map['invite_accepted_at'] = Variable(inviteAcceptedAt); } if (!nullToAbsent || inviteRejectedAt != null) { - map['invite_rejected_at'] = Variable(inviteRejectedAt); - } - if (!nullToAbsent || invited != null) { - map['invited'] = Variable(invited); - } - if (!nullToAbsent || banned != null) { - map['banned'] = Variable(banned); - } - if (!nullToAbsent || shadowBanned != null) { - map['shadow_banned'] = Variable(shadowBanned); - } - if (!nullToAbsent || isModerator != null) { - map['is_moderator'] = Variable(isModerator); - } - if (!nullToAbsent || createdAt != null) { - map['created_at'] = Variable(createdAt); - } - if (!nullToAbsent || updatedAt != null) { - map['updated_at'] = Variable(updatedAt); + map['invite_rejected_at'] = Variable(inviteRejectedAt); } + map['invited'] = Variable(invited); + map['banned'] = Variable(banned); + map['shadow_banned'] = Variable(shadowBanned); + map['is_moderator'] = Variable(isModerator); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); return map; } factory MemberEntity.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return MemberEntity( userId: serializer.fromJson(json['userId']), channelCid: serializer.fromJson(json['channelCid']), - role: serializer.fromJson(json['role']), - inviteAcceptedAt: serializer.fromJson(json['inviteAcceptedAt']), - inviteRejectedAt: serializer.fromJson(json['inviteRejectedAt']), + role: serializer.fromJson(json['role']), + inviteAcceptedAt: + serializer.fromJson(json['inviteAcceptedAt']), + inviteRejectedAt: + serializer.fromJson(json['inviteRejectedAt']), invited: serializer.fromJson(json['invited']), banned: serializer.fromJson(json['banned']), shadowBanned: serializer.fromJson(json['shadowBanned']), @@ -3971,14 +3910,14 @@ class MemberEntity extends DataClass implements Insertable { ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'userId': serializer.toJson(userId), 'channelCid': serializer.toJson(channelCid), - 'role': serializer.toJson(role), - 'inviteAcceptedAt': serializer.toJson(inviteAcceptedAt), - 'inviteRejectedAt': serializer.toJson(inviteRejectedAt), + 'role': serializer.toJson(role), + 'inviteAcceptedAt': serializer.toJson(inviteAcceptedAt), + 'inviteRejectedAt': serializer.toJson(inviteRejectedAt), 'invited': serializer.toJson(invited), 'banned': serializer.toJson(banned), 'shadowBanned': serializer.toJson(shadowBanned), @@ -3989,17 +3928,17 @@ class MemberEntity extends DataClass implements Insertable { } MemberEntity copyWith( - {String userId, - String channelCid, - Value role = const Value.absent(), - Value inviteAcceptedAt = const Value.absent(), - Value inviteRejectedAt = const Value.absent(), - Value invited = const Value.absent(), - Value banned = const Value.absent(), - Value shadowBanned = const Value.absent(), - Value isModerator = const Value.absent(), - DateTime createdAt, - Value updatedAt = const Value.absent()}) => + {String? userId, + String? channelCid, + Value role = const Value.absent(), + Value inviteAcceptedAt = const Value.absent(), + Value inviteRejectedAt = const Value.absent(), + bool? invited, + bool? banned, + bool? shadowBanned, + bool? isModerator, + DateTime? createdAt, + DateTime? updatedAt}) => MemberEntity( userId: userId ?? this.userId, channelCid: channelCid ?? this.channelCid, @@ -4010,13 +3949,12 @@ class MemberEntity extends DataClass implements Insertable { inviteRejectedAt: inviteRejectedAt.present ? inviteRejectedAt.value : this.inviteRejectedAt, - invited: invited.present ? invited.value : this.invited, - banned: banned.present ? banned.value : this.banned, - shadowBanned: - shadowBanned.present ? shadowBanned.value : this.shadowBanned, - isModerator: isModerator.present ? isModerator.value : this.isModerator, + invited: invited ?? this.invited, + banned: banned ?? this.banned, + shadowBanned: shadowBanned ?? this.shadowBanned, + isModerator: isModerator ?? this.isModerator, createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, + updatedAt: updatedAt ?? this.updatedAt, ); @override String toString() { @@ -4077,9 +4015,9 @@ class MemberEntity extends DataClass implements Insertable { class MembersCompanion extends UpdateCompanion { final Value userId; final Value channelCid; - final Value role; - final Value inviteAcceptedAt; - final Value inviteRejectedAt; + final Value role; + final Value inviteAcceptedAt; + final Value inviteRejectedAt; final Value invited; final Value banned; final Value shadowBanned; @@ -4100,8 +4038,8 @@ class MembersCompanion extends UpdateCompanion { this.updatedAt = const Value.absent(), }); MembersCompanion.insert({ - @required String userId, - @required String channelCid, + required String userId, + required String channelCid, this.role = const Value.absent(), this.inviteAcceptedAt = const Value.absent(), this.inviteRejectedAt = const Value.absent(), @@ -4109,23 +4047,22 @@ class MembersCompanion extends UpdateCompanion { this.banned = const Value.absent(), this.shadowBanned = const Value.absent(), this.isModerator = const Value.absent(), - @required DateTime createdAt, + this.createdAt = const Value.absent(), this.updatedAt = const Value.absent(), }) : userId = Value(userId), - channelCid = Value(channelCid), - createdAt = Value(createdAt); + channelCid = Value(channelCid); static Insertable custom({ - Expression userId, - Expression channelCid, - Expression role, - Expression inviteAcceptedAt, - Expression inviteRejectedAt, - Expression invited, - Expression banned, - Expression shadowBanned, - Expression isModerator, - Expression createdAt, - Expression updatedAt, + Expression? userId, + Expression? channelCid, + Expression? role, + Expression? inviteAcceptedAt, + Expression? inviteRejectedAt, + Expression? invited, + Expression? banned, + Expression? shadowBanned, + Expression? isModerator, + Expression? createdAt, + Expression? updatedAt, }) { return RawValuesInsertable({ if (userId != null) 'user_id': userId, @@ -4143,17 +4080,17 @@ class MembersCompanion extends UpdateCompanion { } MembersCompanion copyWith( - {Value userId, - Value channelCid, - Value role, - Value inviteAcceptedAt, - Value inviteRejectedAt, - Value invited, - Value banned, - Value shadowBanned, - Value isModerator, - Value createdAt, - Value updatedAt}) { + {Value? userId, + Value? channelCid, + Value? role, + Value? inviteAcceptedAt, + Value? inviteRejectedAt, + Value? invited, + Value? banned, + Value? shadowBanned, + Value? isModerator, + Value? createdAt, + Value? updatedAt}) { return MembersCompanion( userId: userId ?? this.userId, channelCid: channelCid ?? this.channelCid, @@ -4179,13 +4116,13 @@ class MembersCompanion extends UpdateCompanion { map['channel_cid'] = Variable(channelCid.value); } if (role.present) { - map['role'] = Variable(role.value); + map['role'] = Variable(role.value); } if (inviteAcceptedAt.present) { - map['invite_accepted_at'] = Variable(inviteAcceptedAt.value); + map['invite_accepted_at'] = Variable(inviteAcceptedAt.value); } if (inviteRejectedAt.present) { - map['invite_rejected_at'] = Variable(inviteRejectedAt.value); + map['invite_rejected_at'] = Variable(inviteRejectedAt.value); } if (invited.present) { map['invited'] = Variable(invited.value); @@ -4230,12 +4167,11 @@ class MembersCompanion extends UpdateCompanion { class $MembersTable extends Members with TableInfo<$MembersTable, MemberEntity> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $MembersTable(this._db, [this._alias]); final VerificationMeta _userIdMeta = const VerificationMeta('userId'); - GeneratedTextColumn _userId; @override - GeneratedTextColumn get userId => _userId ??= _constructUserId(); + late final GeneratedTextColumn userId = _constructUserId(); GeneratedTextColumn _constructUserId() { return GeneratedTextColumn( 'user_id', @@ -4245,18 +4181,16 @@ class $MembersTable extends Members } final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); - GeneratedTextColumn _channelCid; @override - GeneratedTextColumn get channelCid => _channelCid ??= _constructChannelCid(); + late final GeneratedTextColumn channelCid = _constructChannelCid(); GeneratedTextColumn _constructChannelCid() { return GeneratedTextColumn('channel_cid', $tableName, false, $customConstraints: 'REFERENCES channels(cid) ON DELETE CASCADE'); } final VerificationMeta _roleMeta = const VerificationMeta('role'); - GeneratedTextColumn _role; @override - GeneratedTextColumn get role => _role ??= _constructRole(); + late final GeneratedTextColumn role = _constructRole(); GeneratedTextColumn _constructRole() { return GeneratedTextColumn( 'role', @@ -4267,10 +4201,9 @@ class $MembersTable extends Members final VerificationMeta _inviteAcceptedAtMeta = const VerificationMeta('inviteAcceptedAt'); - GeneratedDateTimeColumn _inviteAcceptedAt; @override - GeneratedDateTimeColumn get inviteAcceptedAt => - _inviteAcceptedAt ??= _constructInviteAcceptedAt(); + late final GeneratedDateTimeColumn inviteAcceptedAt = + _constructInviteAcceptedAt(); GeneratedDateTimeColumn _constructInviteAcceptedAt() { return GeneratedDateTimeColumn( 'invite_accepted_at', @@ -4281,10 +4214,9 @@ class $MembersTable extends Members final VerificationMeta _inviteRejectedAtMeta = const VerificationMeta('inviteRejectedAt'); - GeneratedDateTimeColumn _inviteRejectedAt; @override - GeneratedDateTimeColumn get inviteRejectedAt => - _inviteRejectedAt ??= _constructInviteRejectedAt(); + late final GeneratedDateTimeColumn inviteRejectedAt = + _constructInviteRejectedAt(); GeneratedDateTimeColumn _constructInviteRejectedAt() { return GeneratedDateTimeColumn( 'invite_rejected_at', @@ -4294,79 +4226,53 @@ class $MembersTable extends Members } final VerificationMeta _invitedMeta = const VerificationMeta('invited'); - GeneratedBoolColumn _invited; @override - GeneratedBoolColumn get invited => _invited ??= _constructInvited(); + late final GeneratedBoolColumn invited = _constructInvited(); GeneratedBoolColumn _constructInvited() { - return GeneratedBoolColumn( - 'invited', - $tableName, - true, - ); + return GeneratedBoolColumn('invited', $tableName, false, + defaultValue: const Constant(false)); } final VerificationMeta _bannedMeta = const VerificationMeta('banned'); - GeneratedBoolColumn _banned; @override - GeneratedBoolColumn get banned => _banned ??= _constructBanned(); + late final GeneratedBoolColumn banned = _constructBanned(); GeneratedBoolColumn _constructBanned() { - return GeneratedBoolColumn( - 'banned', - $tableName, - true, - ); + return GeneratedBoolColumn('banned', $tableName, false, + defaultValue: const Constant(false)); } final VerificationMeta _shadowBannedMeta = const VerificationMeta('shadowBanned'); - GeneratedBoolColumn _shadowBanned; @override - GeneratedBoolColumn get shadowBanned => - _shadowBanned ??= _constructShadowBanned(); + late final GeneratedBoolColumn shadowBanned = _constructShadowBanned(); GeneratedBoolColumn _constructShadowBanned() { - return GeneratedBoolColumn( - 'shadow_banned', - $tableName, - true, - ); + return GeneratedBoolColumn('shadow_banned', $tableName, false, + defaultValue: const Constant(false)); } final VerificationMeta _isModeratorMeta = const VerificationMeta('isModerator'); - GeneratedBoolColumn _isModerator; @override - GeneratedBoolColumn get isModerator => - _isModerator ??= _constructIsModerator(); + late final GeneratedBoolColumn isModerator = _constructIsModerator(); GeneratedBoolColumn _constructIsModerator() { - return GeneratedBoolColumn( - 'is_moderator', - $tableName, - true, - ); + return GeneratedBoolColumn('is_moderator', $tableName, false, + defaultValue: const Constant(false)); } final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - GeneratedDateTimeColumn _createdAt; @override - GeneratedDateTimeColumn get createdAt => _createdAt ??= _constructCreatedAt(); + late final GeneratedDateTimeColumn createdAt = _constructCreatedAt(); GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn( - 'created_at', - $tableName, - false, - ); + return GeneratedDateTimeColumn('created_at', $tableName, false, + defaultValue: currentDateAndTime); } final VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); - GeneratedDateTimeColumn _updatedAt; @override - GeneratedDateTimeColumn get updatedAt => _updatedAt ??= _constructUpdatedAt(); + late final GeneratedDateTimeColumn updatedAt = _constructUpdatedAt(); GeneratedDateTimeColumn _constructUpdatedAt() { - return GeneratedDateTimeColumn( - 'updated_at', - $tableName, - true, - ); + return GeneratedDateTimeColumn('updated_at', $tableName, false, + defaultValue: currentDateAndTime); } @override @@ -4396,7 +4302,7 @@ class $MembersTable extends Members final data = instance.toColumns(true); if (data.containsKey('user_id')) { context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id'], _userIdMeta)); + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } else if (isInserting) { context.missing(_userIdMeta); } @@ -4404,55 +4310,53 @@ class $MembersTable extends Members context.handle( _channelCidMeta, channelCid.isAcceptableOrUnknown( - data['channel_cid'], _channelCidMeta)); + data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } if (data.containsKey('role')) { context.handle( - _roleMeta, role.isAcceptableOrUnknown(data['role'], _roleMeta)); + _roleMeta, role.isAcceptableOrUnknown(data['role']!, _roleMeta)); } if (data.containsKey('invite_accepted_at')) { context.handle( _inviteAcceptedAtMeta, inviteAcceptedAt.isAcceptableOrUnknown( - data['invite_accepted_at'], _inviteAcceptedAtMeta)); + data['invite_accepted_at']!, _inviteAcceptedAtMeta)); } if (data.containsKey('invite_rejected_at')) { context.handle( _inviteRejectedAtMeta, inviteRejectedAt.isAcceptableOrUnknown( - data['invite_rejected_at'], _inviteRejectedAtMeta)); + data['invite_rejected_at']!, _inviteRejectedAtMeta)); } if (data.containsKey('invited')) { context.handle(_invitedMeta, - invited.isAcceptableOrUnknown(data['invited'], _invitedMeta)); + invited.isAcceptableOrUnknown(data['invited']!, _invitedMeta)); } if (data.containsKey('banned')) { context.handle(_bannedMeta, - banned.isAcceptableOrUnknown(data['banned'], _bannedMeta)); + banned.isAcceptableOrUnknown(data['banned']!, _bannedMeta)); } if (data.containsKey('shadow_banned')) { context.handle( _shadowBannedMeta, shadowBanned.isAcceptableOrUnknown( - data['shadow_banned'], _shadowBannedMeta)); + data['shadow_banned']!, _shadowBannedMeta)); } if (data.containsKey('is_moderator')) { context.handle( _isModeratorMeta, isModerator.isAcceptableOrUnknown( - data['is_moderator'], _isModeratorMeta)); + data['is_moderator']!, _isModeratorMeta)); } if (data.containsKey('created_at')) { context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at'], _createdAtMeta)); - } else if (isInserting) { - context.missing(_createdAtMeta); + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at'], _updatedAtMeta)); + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } return context; } @@ -4460,7 +4364,7 @@ class $MembersTable extends Members @override Set get $primaryKey => {userId, channelCid}; @override - MemberEntity map(Map data, {String tablePrefix}) { + MemberEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; return MemberEntity.fromData(data, _db, prefix: effectivePrefix); } @@ -4472,52 +4376,51 @@ class $MembersTable extends Members } class ReadEntity extends DataClass implements Insertable { + /// Date of the read event final DateTime lastRead; + + /// Id of the User who sent the event final String userId; + + /// The channel cid of which this read belongs final String channelCid; + + /// Number of unread messages final int unreadMessages; ReadEntity( - {@required this.lastRead, - @required this.userId, - @required this.channelCid, - this.unreadMessages}); + {required this.lastRead, + required this.userId, + required this.channelCid, + required this.unreadMessages}); factory ReadEntity.fromData(Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; final dateTimeType = db.typeSystem.forDartType(); final stringType = db.typeSystem.forDartType(); final intType = db.typeSystem.forDartType(); return ReadEntity( lastRead: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}last_read']), - userId: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), + .mapFromDatabaseResponse(data['${effectivePrefix}last_read'])!, + userId: stringType + .mapFromDatabaseResponse(data['${effectivePrefix}user_id'])!, channelCid: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), + .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!, unreadMessages: intType - .mapFromDatabaseResponse(data['${effectivePrefix}unread_messages']), + .mapFromDatabaseResponse(data['${effectivePrefix}unread_messages'])!, ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || lastRead != null) { - map['last_read'] = Variable(lastRead); - } - if (!nullToAbsent || userId != null) { - map['user_id'] = Variable(userId); - } - if (!nullToAbsent || channelCid != null) { - map['channel_cid'] = Variable(channelCid); - } - if (!nullToAbsent || unreadMessages != null) { - map['unread_messages'] = Variable(unreadMessages); - } + map['last_read'] = Variable(lastRead); + map['user_id'] = Variable(userId); + map['channel_cid'] = Variable(channelCid); + map['unread_messages'] = Variable(unreadMessages); return map; } factory ReadEntity.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return ReadEntity( lastRead: serializer.fromJson(json['lastRead']), @@ -4527,7 +4430,7 @@ class ReadEntity extends DataClass implements Insertable { ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'lastRead': serializer.toJson(lastRead), @@ -4538,16 +4441,15 @@ class ReadEntity extends DataClass implements Insertable { } ReadEntity copyWith( - {DateTime lastRead, - String userId, - String channelCid, - Value unreadMessages = const Value.absent()}) => + {DateTime? lastRead, + String? userId, + String? channelCid, + int? unreadMessages}) => ReadEntity( lastRead: lastRead ?? this.lastRead, userId: userId ?? this.userId, channelCid: channelCid ?? this.channelCid, - unreadMessages: - unreadMessages.present ? unreadMessages.value : this.unreadMessages, + unreadMessages: unreadMessages ?? this.unreadMessages, ); @override String toString() { @@ -4587,18 +4489,18 @@ class ReadsCompanion extends UpdateCompanion { this.unreadMessages = const Value.absent(), }); ReadsCompanion.insert({ - @required DateTime lastRead, - @required String userId, - @required String channelCid, + required DateTime lastRead, + required String userId, + required String channelCid, this.unreadMessages = const Value.absent(), }) : lastRead = Value(lastRead), userId = Value(userId), channelCid = Value(channelCid); static Insertable custom({ - Expression lastRead, - Expression userId, - Expression channelCid, - Expression unreadMessages, + Expression? lastRead, + Expression? userId, + Expression? channelCid, + Expression? unreadMessages, }) { return RawValuesInsertable({ if (lastRead != null) 'last_read': lastRead, @@ -4609,10 +4511,10 @@ class ReadsCompanion extends UpdateCompanion { } ReadsCompanion copyWith( - {Value lastRead, - Value userId, - Value channelCid, - Value unreadMessages}) { + {Value? lastRead, + Value? userId, + Value? channelCid, + Value? unreadMessages}) { return ReadsCompanion( lastRead: lastRead ?? this.lastRead, userId: userId ?? this.userId, @@ -4653,12 +4555,11 @@ class ReadsCompanion extends UpdateCompanion { class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $ReadsTable(this._db, [this._alias]); final VerificationMeta _lastReadMeta = const VerificationMeta('lastRead'); - GeneratedDateTimeColumn _lastRead; @override - GeneratedDateTimeColumn get lastRead => _lastRead ??= _constructLastRead(); + late final GeneratedDateTimeColumn lastRead = _constructLastRead(); GeneratedDateTimeColumn _constructLastRead() { return GeneratedDateTimeColumn( 'last_read', @@ -4668,9 +4569,8 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { } final VerificationMeta _userIdMeta = const VerificationMeta('userId'); - GeneratedTextColumn _userId; @override - GeneratedTextColumn get userId => _userId ??= _constructUserId(); + late final GeneratedTextColumn userId = _constructUserId(); GeneratedTextColumn _constructUserId() { return GeneratedTextColumn( 'user_id', @@ -4680,9 +4580,8 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { } final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); - GeneratedTextColumn _channelCid; @override - GeneratedTextColumn get channelCid => _channelCid ??= _constructChannelCid(); + late final GeneratedTextColumn channelCid = _constructChannelCid(); GeneratedTextColumn _constructChannelCid() { return GeneratedTextColumn('channel_cid', $tableName, false, $customConstraints: 'REFERENCES channels(cid) ON DELETE CASCADE'); @@ -4690,16 +4589,11 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { final VerificationMeta _unreadMessagesMeta = const VerificationMeta('unreadMessages'); - GeneratedIntColumn _unreadMessages; @override - GeneratedIntColumn get unreadMessages => - _unreadMessages ??= _constructUnreadMessages(); + late final GeneratedIntColumn unreadMessages = _constructUnreadMessages(); GeneratedIntColumn _constructUnreadMessages() { - return GeneratedIntColumn( - 'unread_messages', - $tableName, - true, - ); + return GeneratedIntColumn('unread_messages', $tableName, false, + defaultValue: const Constant(0)); } @override @@ -4718,13 +4612,13 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { final data = instance.toColumns(true); if (data.containsKey('last_read')) { context.handle(_lastReadMeta, - lastRead.isAcceptableOrUnknown(data['last_read'], _lastReadMeta)); + lastRead.isAcceptableOrUnknown(data['last_read']!, _lastReadMeta)); } else if (isInserting) { context.missing(_lastReadMeta); } if (data.containsKey('user_id')) { context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id'], _userIdMeta)); + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } else if (isInserting) { context.missing(_userIdMeta); } @@ -4732,7 +4626,7 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { context.handle( _channelCidMeta, channelCid.isAcceptableOrUnknown( - data['channel_cid'], _channelCidMeta)); + data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } @@ -4740,7 +4634,7 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { context.handle( _unreadMessagesMeta, unreadMessages.isAcceptableOrUnknown( - data['unread_messages'], _unreadMessagesMeta)); + data['unread_messages']!, _unreadMessagesMeta)); } return context; } @@ -4748,7 +4642,7 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { @override Set get $primaryKey => {userId, channelCid}; @override - ReadEntity map(Map data, {String tablePrefix}) { + ReadEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; return ReadEntity.fromData(data, _db, prefix: effectivePrefix); } @@ -4761,35 +4655,34 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { class ChannelQueryEntity extends DataClass implements Insertable { + /// The unique hash of this query final String queryHash; + + /// The channel cid of this query final String channelCid; - ChannelQueryEntity({@required this.queryHash, @required this.channelCid}); + ChannelQueryEntity({required this.queryHash, required this.channelCid}); factory ChannelQueryEntity.fromData( Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; final stringType = db.typeSystem.forDartType(); return ChannelQueryEntity( queryHash: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}query_hash']), + .mapFromDatabaseResponse(data['${effectivePrefix}query_hash'])!, channelCid: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), + .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!, ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || queryHash != null) { - map['query_hash'] = Variable(queryHash); - } - if (!nullToAbsent || channelCid != null) { - map['channel_cid'] = Variable(channelCid); - } + map['query_hash'] = Variable(queryHash); + map['channel_cid'] = Variable(channelCid); return map; } factory ChannelQueryEntity.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return ChannelQueryEntity( queryHash: serializer.fromJson(json['queryHash']), @@ -4797,7 +4690,7 @@ class ChannelQueryEntity extends DataClass ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'queryHash': serializer.toJson(queryHash), @@ -4805,7 +4698,7 @@ class ChannelQueryEntity extends DataClass }; } - ChannelQueryEntity copyWith({String queryHash, String channelCid}) => + ChannelQueryEntity copyWith({String? queryHash, String? channelCid}) => ChannelQueryEntity( queryHash: queryHash ?? this.queryHash, channelCid: channelCid ?? this.channelCid, @@ -4837,13 +4730,13 @@ class ChannelQueriesCompanion extends UpdateCompanion { this.channelCid = const Value.absent(), }); ChannelQueriesCompanion.insert({ - @required String queryHash, - @required String channelCid, - }) : queryHash = Value(queryHash), + required String queryHash, + required String channelCid, + }) : queryHash = Value(queryHash), channelCid = Value(channelCid); static Insertable custom({ - Expression queryHash, - Expression channelCid, + Expression? queryHash, + Expression? channelCid, }) { return RawValuesInsertable({ if (queryHash != null) 'query_hash': queryHash, @@ -4852,7 +4745,7 @@ class ChannelQueriesCompanion extends UpdateCompanion { } ChannelQueriesCompanion copyWith( - {Value queryHash, Value channelCid}) { + {Value? queryHash, Value? channelCid}) { return ChannelQueriesCompanion( queryHash: queryHash ?? this.queryHash, channelCid: channelCid ?? this.channelCid, @@ -4884,12 +4777,11 @@ class ChannelQueriesCompanion extends UpdateCompanion { class $ChannelQueriesTable extends ChannelQueries with TableInfo<$ChannelQueriesTable, ChannelQueryEntity> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $ChannelQueriesTable(this._db, [this._alias]); final VerificationMeta _queryHashMeta = const VerificationMeta('queryHash'); - GeneratedTextColumn _queryHash; @override - GeneratedTextColumn get queryHash => _queryHash ??= _constructQueryHash(); + late final GeneratedTextColumn queryHash = _constructQueryHash(); GeneratedTextColumn _constructQueryHash() { return GeneratedTextColumn( 'query_hash', @@ -4899,9 +4791,8 @@ class $ChannelQueriesTable extends ChannelQueries } final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); - GeneratedTextColumn _channelCid; @override - GeneratedTextColumn get channelCid => _channelCid ??= _constructChannelCid(); + late final GeneratedTextColumn channelCid = _constructChannelCid(); GeneratedTextColumn _constructChannelCid() { return GeneratedTextColumn( 'channel_cid', @@ -4925,7 +4816,7 @@ class $ChannelQueriesTable extends ChannelQueries final data = instance.toColumns(true); if (data.containsKey('query_hash')) { context.handle(_queryHashMeta, - queryHash.isAcceptableOrUnknown(data['query_hash'], _queryHashMeta)); + queryHash.isAcceptableOrUnknown(data['query_hash']!, _queryHashMeta)); } else if (isInserting) { context.missing(_queryHashMeta); } @@ -4933,7 +4824,7 @@ class $ChannelQueriesTable extends ChannelQueries context.handle( _channelCidMeta, channelCid.isAcceptableOrUnknown( - data['channel_cid'], _channelCidMeta)); + data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } @@ -4943,7 +4834,7 @@ class $ChannelQueriesTable extends ChannelQueries @override Set get $primaryKey => {queryHash, channelCid}; @override - ChannelQueryEntity map(Map data, {String tablePrefix}) { + ChannelQueryEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; return ChannelQueryEntity.fromData(data, _db, prefix: effectivePrefix); } @@ -4956,14 +4847,25 @@ class $ChannelQueriesTable extends ChannelQueries class ConnectionEventEntity extends DataClass implements Insertable { + /// event id final int id; - final Map ownUser; - final int totalUnreadCount; - final int unreadChannels; - final DateTime lastEventAt; - final DateTime lastSyncAt; + + /// User object of the current user + final Map? ownUser; + + /// The number of unread messages for current user + final int? totalUnreadCount; + + /// User total unread channels for current user + final int? unreadChannels; + + /// DateTime of the last event + final DateTime? lastEventAt; + + /// DateTime of the last sync + final DateTime? lastSyncAt; ConnectionEventEntity( - {@required this.id, + {required this.id, this.ownUser, this.totalUnreadCount, this.unreadChannels, @@ -4971,13 +4873,13 @@ class ConnectionEventEntity extends DataClass this.lastSyncAt}); factory ConnectionEventEntity.fromData( Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; final intType = db.typeSystem.forDartType(); final stringType = db.typeSystem.forDartType(); final dateTimeType = db.typeSystem.forDartType(); return ConnectionEventEntity( - id: intType.mapFromDatabaseResponse(data['${effectivePrefix}id']), + id: intType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, ownUser: $ConnectionEventsTable.$converter0.mapToDart(stringType .mapFromDatabaseResponse(data['${effectivePrefix}own_user'])), totalUnreadCount: intType.mapFromDatabaseResponse( @@ -4993,60 +4895,58 @@ class ConnectionEventEntity extends DataClass @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } + map['id'] = Variable(id); if (!nullToAbsent || ownUser != null) { final converter = $ConnectionEventsTable.$converter0; - map['own_user'] = Variable(converter.mapToSql(ownUser)); + map['own_user'] = Variable(converter.mapToSql(ownUser)); } if (!nullToAbsent || totalUnreadCount != null) { - map['total_unread_count'] = Variable(totalUnreadCount); + map['total_unread_count'] = Variable(totalUnreadCount); } if (!nullToAbsent || unreadChannels != null) { - map['unread_channels'] = Variable(unreadChannels); + map['unread_channels'] = Variable(unreadChannels); } if (!nullToAbsent || lastEventAt != null) { - map['last_event_at'] = Variable(lastEventAt); + map['last_event_at'] = Variable(lastEventAt); } if (!nullToAbsent || lastSyncAt != null) { - map['last_sync_at'] = Variable(lastSyncAt); + map['last_sync_at'] = Variable(lastSyncAt); } return map; } factory ConnectionEventEntity.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return ConnectionEventEntity( id: serializer.fromJson(json['id']), - ownUser: serializer.fromJson>(json['ownUser']), - totalUnreadCount: serializer.fromJson(json['totalUnreadCount']), - unreadChannels: serializer.fromJson(json['unreadChannels']), - lastEventAt: serializer.fromJson(json['lastEventAt']), - lastSyncAt: serializer.fromJson(json['lastSyncAt']), + ownUser: serializer.fromJson?>(json['ownUser']), + totalUnreadCount: serializer.fromJson(json['totalUnreadCount']), + unreadChannels: serializer.fromJson(json['unreadChannels']), + lastEventAt: serializer.fromJson(json['lastEventAt']), + lastSyncAt: serializer.fromJson(json['lastSyncAt']), ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), - 'ownUser': serializer.toJson>(ownUser), - 'totalUnreadCount': serializer.toJson(totalUnreadCount), - 'unreadChannels': serializer.toJson(unreadChannels), - 'lastEventAt': serializer.toJson(lastEventAt), - 'lastSyncAt': serializer.toJson(lastSyncAt), + 'ownUser': serializer.toJson?>(ownUser), + 'totalUnreadCount': serializer.toJson(totalUnreadCount), + 'unreadChannels': serializer.toJson(unreadChannels), + 'lastEventAt': serializer.toJson(lastEventAt), + 'lastSyncAt': serializer.toJson(lastSyncAt), }; } ConnectionEventEntity copyWith( - {int id, - Value> ownUser = const Value.absent(), - Value totalUnreadCount = const Value.absent(), - Value unreadChannels = const Value.absent(), - Value lastEventAt = const Value.absent(), - Value lastSyncAt = const Value.absent()}) => + {int? id, + Value?> ownUser = const Value.absent(), + Value totalUnreadCount = const Value.absent(), + Value unreadChannels = const Value.absent(), + Value lastEventAt = const Value.absent(), + Value lastSyncAt = const Value.absent()}) => ConnectionEventEntity( id: id ?? this.id, ownUser: ownUser.present ? ownUser.value : this.ownUser, @@ -5094,11 +4994,11 @@ class ConnectionEventEntity extends DataClass class ConnectionEventsCompanion extends UpdateCompanion { final Value id; - final Value> ownUser; - final Value totalUnreadCount; - final Value unreadChannels; - final Value lastEventAt; - final Value lastSyncAt; + final Value?> ownUser; + final Value totalUnreadCount; + final Value unreadChannels; + final Value lastEventAt; + final Value lastSyncAt; const ConnectionEventsCompanion({ this.id = const Value.absent(), this.ownUser = const Value.absent(), @@ -5116,12 +5016,12 @@ class ConnectionEventsCompanion extends UpdateCompanion { this.lastSyncAt = const Value.absent(), }); static Insertable custom({ - Expression id, - Expression ownUser, - Expression totalUnreadCount, - Expression unreadChannels, - Expression lastEventAt, - Expression lastSyncAt, + Expression? id, + Expression?>? ownUser, + Expression? totalUnreadCount, + Expression? unreadChannels, + Expression? lastEventAt, + Expression? lastSyncAt, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -5134,12 +5034,12 @@ class ConnectionEventsCompanion extends UpdateCompanion { } ConnectionEventsCompanion copyWith( - {Value id, - Value> ownUser, - Value totalUnreadCount, - Value unreadChannels, - Value lastEventAt, - Value lastSyncAt}) { + {Value? id, + Value?>? ownUser, + Value? totalUnreadCount, + Value? unreadChannels, + Value? lastEventAt, + Value? lastSyncAt}) { return ConnectionEventsCompanion( id: id ?? this.id, ownUser: ownUser ?? this.ownUser, @@ -5158,19 +5058,19 @@ class ConnectionEventsCompanion extends UpdateCompanion { } if (ownUser.present) { final converter = $ConnectionEventsTable.$converter0; - map['own_user'] = Variable(converter.mapToSql(ownUser.value)); + map['own_user'] = Variable(converter.mapToSql(ownUser.value)); } if (totalUnreadCount.present) { - map['total_unread_count'] = Variable(totalUnreadCount.value); + map['total_unread_count'] = Variable(totalUnreadCount.value); } if (unreadChannels.present) { - map['unread_channels'] = Variable(unreadChannels.value); + map['unread_channels'] = Variable(unreadChannels.value); } if (lastEventAt.present) { - map['last_event_at'] = Variable(lastEventAt.value); + map['last_event_at'] = Variable(lastEventAt.value); } if (lastSyncAt.present) { - map['last_sync_at'] = Variable(lastSyncAt.value); + map['last_sync_at'] = Variable(lastSyncAt.value); } return map; } @@ -5192,12 +5092,11 @@ class ConnectionEventsCompanion extends UpdateCompanion { class $ConnectionEventsTable extends ConnectionEvents with TableInfo<$ConnectionEventsTable, ConnectionEventEntity> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $ConnectionEventsTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - GeneratedIntColumn _id; @override - GeneratedIntColumn get id => _id ??= _constructId(); + late final GeneratedIntColumn id = _constructId(); GeneratedIntColumn _constructId() { return GeneratedIntColumn( 'id', @@ -5207,9 +5106,8 @@ class $ConnectionEventsTable extends ConnectionEvents } final VerificationMeta _ownUserMeta = const VerificationMeta('ownUser'); - GeneratedTextColumn _ownUser; @override - GeneratedTextColumn get ownUser => _ownUser ??= _constructOwnUser(); + late final GeneratedTextColumn ownUser = _constructOwnUser(); GeneratedTextColumn _constructOwnUser() { return GeneratedTextColumn( 'own_user', @@ -5220,10 +5118,8 @@ class $ConnectionEventsTable extends ConnectionEvents final VerificationMeta _totalUnreadCountMeta = const VerificationMeta('totalUnreadCount'); - GeneratedIntColumn _totalUnreadCount; @override - GeneratedIntColumn get totalUnreadCount => - _totalUnreadCount ??= _constructTotalUnreadCount(); + late final GeneratedIntColumn totalUnreadCount = _constructTotalUnreadCount(); GeneratedIntColumn _constructTotalUnreadCount() { return GeneratedIntColumn( 'total_unread_count', @@ -5234,10 +5130,8 @@ class $ConnectionEventsTable extends ConnectionEvents final VerificationMeta _unreadChannelsMeta = const VerificationMeta('unreadChannels'); - GeneratedIntColumn _unreadChannels; @override - GeneratedIntColumn get unreadChannels => - _unreadChannels ??= _constructUnreadChannels(); + late final GeneratedIntColumn unreadChannels = _constructUnreadChannels(); GeneratedIntColumn _constructUnreadChannels() { return GeneratedIntColumn( 'unread_channels', @@ -5248,10 +5142,8 @@ class $ConnectionEventsTable extends ConnectionEvents final VerificationMeta _lastEventAtMeta = const VerificationMeta('lastEventAt'); - GeneratedDateTimeColumn _lastEventAt; @override - GeneratedDateTimeColumn get lastEventAt => - _lastEventAt ??= _constructLastEventAt(); + late final GeneratedDateTimeColumn lastEventAt = _constructLastEventAt(); GeneratedDateTimeColumn _constructLastEventAt() { return GeneratedDateTimeColumn( 'last_event_at', @@ -5261,10 +5153,8 @@ class $ConnectionEventsTable extends ConnectionEvents } final VerificationMeta _lastSyncAtMeta = const VerificationMeta('lastSyncAt'); - GeneratedDateTimeColumn _lastSyncAt; @override - GeneratedDateTimeColumn get lastSyncAt => - _lastSyncAt ??= _constructLastSyncAt(); + late final GeneratedDateTimeColumn lastSyncAt = _constructLastSyncAt(); GeneratedDateTimeColumn _constructLastSyncAt() { return GeneratedDateTimeColumn( 'last_sync_at', @@ -5289,32 +5179,32 @@ class $ConnectionEventsTable extends ConnectionEvents final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } context.handle(_ownUserMeta, const VerificationResult.success()); if (data.containsKey('total_unread_count')) { context.handle( _totalUnreadCountMeta, totalUnreadCount.isAcceptableOrUnknown( - data['total_unread_count'], _totalUnreadCountMeta)); + data['total_unread_count']!, _totalUnreadCountMeta)); } if (data.containsKey('unread_channels')) { context.handle( _unreadChannelsMeta, unreadChannels.isAcceptableOrUnknown( - data['unread_channels'], _unreadChannelsMeta)); + data['unread_channels']!, _unreadChannelsMeta)); } if (data.containsKey('last_event_at')) { context.handle( _lastEventAtMeta, lastEventAt.isAcceptableOrUnknown( - data['last_event_at'], _lastEventAtMeta)); + data['last_event_at']!, _lastEventAtMeta)); } if (data.containsKey('last_sync_at')) { context.handle( _lastSyncAtMeta, lastSyncAt.isAcceptableOrUnknown( - data['last_sync_at'], _lastSyncAtMeta)); + data['last_sync_at']!, _lastSyncAtMeta)); } return context; } @@ -5322,7 +5212,7 @@ class $ConnectionEventsTable extends ConnectionEvents @override Set get $primaryKey => {id}; @override - ConnectionEventEntity map(Map data, {String tablePrefix}) { + ConnectionEventEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; return ConnectionEventEntity.fromData(data, _db, prefix: effectivePrefix); } @@ -5339,51 +5229,28 @@ class $ConnectionEventsTable extends ConnectionEvents abstract class _$MoorChatDatabase extends GeneratedDatabase { _$MoorChatDatabase(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e); _$MoorChatDatabase.connect(DatabaseConnection c) : super.connect(c); - $ChannelsTable _channels; - $ChannelsTable get channels => _channels ??= $ChannelsTable(this); - $MessagesTable _messages; - $MessagesTable get messages => _messages ??= $MessagesTable(this); - $PinnedMessagesTable _pinnedMessages; - $PinnedMessagesTable get pinnedMessages => - _pinnedMessages ??= $PinnedMessagesTable(this); - $ReactionsTable _reactions; - $ReactionsTable get reactions => _reactions ??= $ReactionsTable(this); - $UsersTable _users; - $UsersTable get users => _users ??= $UsersTable(this); - $MembersTable _members; - $MembersTable get members => _members ??= $MembersTable(this); - $ReadsTable _reads; - $ReadsTable get reads => _reads ??= $ReadsTable(this); - $ChannelQueriesTable _channelQueries; - $ChannelQueriesTable get channelQueries => - _channelQueries ??= $ChannelQueriesTable(this); - $ConnectionEventsTable _connectionEvents; - $ConnectionEventsTable get connectionEvents => - _connectionEvents ??= $ConnectionEventsTable(this); - UserDao _userDao; - UserDao get userDao => _userDao ??= UserDao(this as MoorChatDatabase); - ChannelDao _channelDao; - ChannelDao get channelDao => - _channelDao ??= ChannelDao(this as MoorChatDatabase); - MessageDao _messageDao; - MessageDao get messageDao => - _messageDao ??= MessageDao(this as MoorChatDatabase); - PinnedMessageDao _pinnedMessageDao; - PinnedMessageDao get pinnedMessageDao => - _pinnedMessageDao ??= PinnedMessageDao(this as MoorChatDatabase); - MemberDao _memberDao; - MemberDao get memberDao => _memberDao ??= MemberDao(this as MoorChatDatabase); - ReactionDao _reactionDao; - ReactionDao get reactionDao => - _reactionDao ??= ReactionDao(this as MoorChatDatabase); - ReadDao _readDao; - ReadDao get readDao => _readDao ??= ReadDao(this as MoorChatDatabase); - ChannelQueryDao _channelQueryDao; - ChannelQueryDao get channelQueryDao => - _channelQueryDao ??= ChannelQueryDao(this as MoorChatDatabase); - ConnectionEventDao _connectionEventDao; - ConnectionEventDao get connectionEventDao => - _connectionEventDao ??= ConnectionEventDao(this as MoorChatDatabase); + late final $ChannelsTable channels = $ChannelsTable(this); + late final $MessagesTable messages = $MessagesTable(this); + late final $PinnedMessagesTable pinnedMessages = $PinnedMessagesTable(this); + late final $ReactionsTable reactions = $ReactionsTable(this); + late final $UsersTable users = $UsersTable(this); + late final $MembersTable members = $MembersTable(this); + late final $ReadsTable reads = $ReadsTable(this); + late final $ChannelQueriesTable channelQueries = $ChannelQueriesTable(this); + late final $ConnectionEventsTable connectionEvents = + $ConnectionEventsTable(this); + late final UserDao userDao = UserDao(this as MoorChatDatabase); + late final ChannelDao channelDao = ChannelDao(this as MoorChatDatabase); + late final MessageDao messageDao = MessageDao(this as MoorChatDatabase); + late final PinnedMessageDao pinnedMessageDao = + PinnedMessageDao(this as MoorChatDatabase); + late final MemberDao memberDao = MemberDao(this as MoorChatDatabase); + late final ReactionDao reactionDao = ReactionDao(this as MoorChatDatabase); + late final ReadDao readDao = ReadDao(this as MoorChatDatabase); + late final ChannelQueryDao channelQueryDao = + ChannelQueryDao(this as MoorChatDatabase); + late final ConnectionEventDao connectionEventDao = + ConnectionEventDao(this as MoorChatDatabase); @override Iterable get allTables => allSchemaEntities.whereType(); @override diff --git a/packages/stream_chat_persistence/lib/src/entity/channels.dart b/packages/stream_chat_persistence/lib/src/entity/channels.dart index 6f60fac3..06a1045e 100644 --- a/packages/stream_chat_persistence/lib/src/entity/channels.dart +++ b/packages/stream_chat_persistence/lib/src/entity/channels.dart @@ -24,16 +24,16 @@ class Channels extends Table { DateTimeColumn get lastMessageAt => dateTime().nullable()(); /// The date of channel creation - DateTimeColumn get createdAt => dateTime().nullable()(); + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); /// The date of the last channel update - DateTimeColumn get updatedAt => dateTime().nullable()(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); /// The date of channel deletion DateTimeColumn get deletedAt => dateTime().nullable()(); /// The count of this channel members - IntColumn get memberCount => integer().nullable()(); + IntColumn get memberCount => integer().withDefault(const Constant(0))(); /// The id of the user that created this channel TextColumn get createdById => text().nullable()(); diff --git a/packages/stream_chat_persistence/lib/src/entity/members.dart b/packages/stream_chat_persistence/lib/src/entity/members.dart index c7316640..8d3d4a57 100644 --- a/packages/stream_chat_persistence/lib/src/entity/members.dart +++ b/packages/stream_chat_persistence/lib/src/entity/members.dart @@ -21,22 +21,22 @@ class Members extends Table { DateTimeColumn get inviteRejectedAt => dateTime().nullable()(); /// True if the user has been invited to the channel - BoolColumn get invited => boolean().nullable()(); + BoolColumn get invited => boolean().withDefault(const Constant(false))(); /// True if the member is banned from the channel - BoolColumn get banned => boolean().nullable()(); + BoolColumn get banned => boolean().withDefault(const Constant(false))(); /// True if the member is shadow banned from the channel - BoolColumn get shadowBanned => boolean().nullable()(); + BoolColumn get shadowBanned => boolean().withDefault(const Constant(false))(); /// True if the user is a moderator of the channel - BoolColumn get isModerator => boolean().nullable()(); + BoolColumn get isModerator => boolean().withDefault(const Constant(false))(); /// The date of creation - DateTimeColumn get createdAt => dateTime()(); + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); /// The last date of update - DateTimeColumn get updatedAt => dateTime().nullable()(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); @override Set get primaryKey => { diff --git a/packages/stream_chat_persistence/lib/src/entity/messages.dart b/packages/stream_chat_persistence/lib/src/entity/messages.dart index 8840d85f..7cb1326d 100644 --- a/packages/stream_chat_persistence/lib/src/entity/messages.dart +++ b/packages/stream_chat_persistence/lib/src/entity/messages.dart @@ -15,19 +15,18 @@ class Messages extends Table { /// The list of attachments, either provided by the user /// or generated from a command or as a result of URL scraping. - TextColumn get attachments => - text().nullable().map(ListConverter())(); + TextColumn get attachments => text().map(ListConverter())(); /// The status of a sending message - IntColumn get status => - integer().nullable().map(MessageSendingStatusConverter())(); + IntColumn get status => integer() + .withDefault(const Constant(1)) + .map(MessageSendingStatusConverter())(); /// The message type - TextColumn get type => text().nullable()(); + TextColumn get type => text().withDefault(const Constant('regular'))(); /// The list of user mentioned in the message - TextColumn get mentionedUsers => - text().nullable().map(ListConverter())(); + TextColumn get mentionedUsers => text().map(ListConverter())(); /// A map describing the count of number of every reaction TextColumn get reactionCounts => text().nullable().map(MapConverter())(); @@ -48,16 +47,16 @@ class Messages extends Table { BoolColumn get showInChannel => boolean().nullable()(); /// If true the message is shadowed - BoolColumn get shadowed => boolean().nullable()(); + BoolColumn get shadowed => boolean().withDefault(const Constant(false))(); /// A used command name. TextColumn get command => text().nullable()(); /// The DateTime when the message was created. - DateTimeColumn get createdAt => dateTime()(); + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); /// The DateTime when the message was updated last time. - DateTimeColumn get updatedAt => dateTime().nullable()(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); /// The DateTime when the message was deleted. DateTimeColumn get deletedAt => dateTime().nullable()(); diff --git a/packages/stream_chat_persistence/lib/src/entity/reactions.dart b/packages/stream_chat_persistence/lib/src/entity/reactions.dart index 53cbdd11..2b7d12b5 100644 --- a/packages/stream_chat_persistence/lib/src/entity/reactions.dart +++ b/packages/stream_chat_persistence/lib/src/entity/reactions.dart @@ -16,10 +16,10 @@ class Reactions extends Table { TextColumn get type => text()(); /// The DateTime on which the reaction is created - DateTimeColumn get createdAt => dateTime()(); + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); /// The score of the reaction (ie. number of reactions sent) - IntColumn get score => integer().nullable()(); + IntColumn get score => integer().withDefault(const Constant(0))(); /// Reaction custom extraData TextColumn get extraData => text().nullable().map(MapConverter())(); diff --git a/packages/stream_chat_persistence/lib/src/entity/reads.dart b/packages/stream_chat_persistence/lib/src/entity/reads.dart index 7943ed9e..1f59b3c8 100644 --- a/packages/stream_chat_persistence/lib/src/entity/reads.dart +++ b/packages/stream_chat_persistence/lib/src/entity/reads.dart @@ -15,7 +15,7 @@ class Reads extends Table { text().customConstraint('REFERENCES channels(cid) ON DELETE CASCADE')(); /// Number of unread messages - IntColumn get unreadMessages => integer().nullable()(); + IntColumn get unreadMessages => integer().withDefault(const Constant(0))(); @override Set get primaryKey => { diff --git a/packages/stream_chat_persistence/lib/src/entity/users.dart b/packages/stream_chat_persistence/lib/src/entity/users.dart index 7ece9600..d496841b 100644 --- a/packages/stream_chat_persistence/lib/src/entity/users.dart +++ b/packages/stream_chat_persistence/lib/src/entity/users.dart @@ -12,19 +12,19 @@ class Users extends Table { TextColumn get role => text().nullable()(); /// Date of user creation - DateTimeColumn get createdAt => dateTime().nullable()(); + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); /// Date of last user update - DateTimeColumn get updatedAt => dateTime().nullable()(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); /// Date of last user connection DateTimeColumn get lastActive => dateTime().nullable()(); /// True if user is online - BoolColumn get online => boolean().nullable()(); + BoolColumn get online => boolean().withDefault(const Constant(false))(); /// True if user is banned from the chat - BoolColumn get banned => boolean().nullable()(); + BoolColumn get banned => boolean().withDefault(const Constant(false))(); /// Map of custom user extraData TextColumn get extraData => text().nullable().map(MapConverter())(); 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 2c9d28fb..3a8a5b5b 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 @@ -30,17 +30,15 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { /// Connection mode on which the client will work ConnectionMode connectionMode = ConnectionMode.regular, Level logLevel = Level.WARNING, - LogHandlerFunction logHandlerFunction, - }) : assert(connectionMode != null, 'ConnectionMode cannot be null'), - assert(logLevel != null, 'LogLevel cannot be null'), - _connectionMode = connectionMode, + LogHandlerFunction? logHandlerFunction, + }) : _connectionMode = connectionMode, _logger = Logger.detached('đŸ’Ŋ')..level = logLevel { _logger.onRecord.listen(logHandlerFunction ?? _defaultLogHandler); } /// [MoorChatDatabase] instance used by this client. @visibleForTesting - MoorChatDatabase db; + MoorChatDatabase? db; final Logger _logger; final ConnectionMode _connectionMode; @@ -75,7 +73,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { @override Future connect( String userId, { - DatabaseProvider databaseProvider, // Used only for testing + DatabaseProvider? databaseProvider, // Used only for testing }) async { if (db != null) { throw Exception( @@ -330,13 +328,13 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { _logger.info('Disconnecting'); if (flush) { _logger.info('Flushing'); - await db.batch((batch) { - db.allTables.forEach((table) { - db.delete(table).go(); + await db!.batch((batch) { + db!.allTables.forEach((table) { + db!.delete(table).go(); }); }); } - await db.disconnect(); + await db!.disconnect(); db = null; } }); From 987da10cba09a68c68deac146cb6bf393734f15b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 12:44:11 +0200 Subject: [PATCH 132/599] migrate code --- .../analysis_options.yaml | 1 - .../lib/src/channel_list_core.dart | 30 +- .../lib/src/channels_bloc.dart | 26 +- .../lib/src/lazy_load_scroll_view.dart | 11 +- .../lib/src/message_list_core.dart | 15 +- .../lib/src/message_search_bloc.dart | 24 +- .../lib/src/message_search_list_core.dart | 22 +- .../lib/src/stream_channel.dart | 44 +- .../lib/src/stream_chat_core.dart | 18 +- .../lib/src/typedef.dart | 2 +- .../lib/src/user_list_core.dart | 14 +- .../lib/src/users_bloc.dart | 18 +- .../stream_chat_flutter_core/pubspec.yaml | 8 +- .../test/channel_list_core_test.dart | 251 +++++----- .../test/channels_bloc_test.dart | 437 +++++++++--------- .../test/lazy_load_scroll_view_test.dart | 12 - .../get_message_response_matcher.dart | 9 +- .../test/message_list_core_test.dart | 106 ++--- .../test/message_search_bloc_test.dart | 198 ++++---- .../test/message_search_list_core_test.dart | 319 ++++++------- .../stream_chat_flutter_core/test/mocks.dart | 11 +- .../test/stream_channel_test.dart | 195 ++++---- .../test/stream_chat_core_test.dart | 60 +-- .../test/user_list_core_test.dart | 259 +++++------ .../test/users_bloc_test.dart | 160 +++---- 25 files changed, 986 insertions(+), 1264 deletions(-) diff --git a/packages/stream_chat_flutter_core/analysis_options.yaml b/packages/stream_chat_flutter_core/analysis_options.yaml index 545d5492..039d0230 100644 --- a/packages/stream_chat_flutter_core/analysis_options.yaml +++ b/packages/stream_chat_flutter_core/analysis_options.yaml @@ -3,7 +3,6 @@ analyzer: - lib/**/*.g.dart - lib/**/*.freezed.dart - example/* - - test/* linter: rules: - always_use_package_imports diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 70d97df7..322a2780 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -69,23 +69,7 @@ class ChannelListCore extends StatefulWidget { limit: 25, ), this.channelListController, - }) : assert( - errorBuilder != null, - 'Parameter errorBuilder should not be null', - ), - assert( - emptyBuilder != null, - 'Parameter emptyBuilder should not be null', - ), - assert( - loadingBuilder != null, - 'Parameter loadingBuilder should not be null', - ), - assert( - listBuilder != null, - 'Parameter listBuilder should not be null', - ), - super(key: key); + }) : super(key: key); /// A [ChannelListController] allows reloading and pagination. /// Use [ChannelListController.loadData] and @@ -100,7 +84,7 @@ class ChannelListCore extends StatefulWidget { final WidgetBuilder loadingBuilder; /// The builder which is used when list of channels loads - final Function(BuildContext, List) listBuilder; + final Function(BuildContext, List) listBuilder; /// The builder used when the channel list is empty. final WidgetBuilder emptyBuilder; @@ -142,14 +126,14 @@ class ChannelListCoreState extends State { return _buildListView(channelsBloc); } - StreamBuilder> _buildListView( + StreamBuilder> _buildListView( ChannelsBlocState channelsBlocState, ) => - StreamBuilder>( + StreamBuilder>( stream: channelsBlocState.channelsStream, builder: (context, snapshot) { if (snapshot.hasError) { - return widget.errorBuilder(context, snapshot.error); + return widget.errorBuilder(context, snapshot.error!); } if (!snapshot.hasData) { return widget.loadingBuilder(context); @@ -215,8 +199,8 @@ class ChannelListCoreState extends State { if (widget.filter?.toString() != oldWidget.filter?.toString() || jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) || widget.options?.toString() != oldWidget.options?.toString() || - widget.pagination?.toJson()?.toString() != - oldWidget.pagination?.toJson()?.toString()) { + widget.pagination.toJson().toString() != + oldWidget.pagination.toJson().toString()) { loadData(); } } 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 ad39809f..7910ab7e 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -35,7 +35,7 @@ class ChannelsBloc extends StatefulWidget { final bool lockChannelsOrder; /// Comparator used to sort the channels when a message.new event is received - final Comparator? channelsComparator; + final Comparator? channelsComparator; /// Function used to evaluate if a channel should be added to the list when a /// message.new event is received @@ -68,14 +68,14 @@ class ChannelsBlocState extends State } /// The current channel list - List? get channels => _channelsController.value as List?; + List? get channels => _channelsController.value; /// The current channel list as a stream - Stream> get channelsStream => _channelsController.stream; + Stream> get channelsStream => _channelsController.stream; final _queryChannelsLoadingController = BehaviorSubject.seeded(false); - final BehaviorSubject> _channelsController = + final BehaviorSubject> _channelsController = BehaviorSubject>(); /// The stream notifying the state of queryChannel call @@ -90,7 +90,7 @@ class ChannelsBlocState extends State Future queryChannels({ Map? filter, List>? sortOptions, - PaginationParams? paginationParams, + PaginationParams paginationParams = const PaginationParams(limit: 30), Map? options, }) async { final client = StreamChatCore.of(context).client; @@ -104,14 +104,14 @@ class ChannelsBlocState extends State } try { - final clear = paginationParams == null || paginationParams.offset == 0; + final clear = paginationParams.offset == 0; final oldChannels = List.from(channels ?? []); var newChannels = []; await for (final channels in client.queryChannels( - filter: filter!, - sort: sortOptions!, - options: options!, - paginationParams: paginationParams!, + filter: filter, + sort: sortOptions, + options: options, + paginationParams: paginationParams, )) { newChannels = channels; if (clear) { @@ -147,8 +147,8 @@ class ChannelsBlocState extends State if (!widget.lockChannelsOrder) { _subscriptions.add(client.on(EventType.messageNew).listen((e) { - final newChannels = List.from(channels ?? []); - final index = newChannels.indexWhere((c) => c!.cid == e.cid); + final newChannels = List.from(channels ?? []); + final index = newChannels.indexWhere((c) => c.cid == e.cid); if (index != -1) { if (index > 0) { final channel = newChannels.removeAt(index); @@ -162,7 +162,7 @@ class ChannelsBlocState extends State } else { if (client.state.channels != null && client.state.channels?[e.cid] != null) { - newChannels.insert(0, client.state.channels?[e.cid]); + newChannels.insert(0, client.state.channels![e.cid]!); } } } diff --git a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart index 1f07c8ce..834d00b0 100644 --- a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart @@ -17,8 +17,7 @@ class LazyLoadScrollView extends StatefulWidget { this.onPageScrollEnd, this.onInBetweenOfPage, this.scrollOffset = 100, - }) : assert(child != null, 'Parameter child should not be null'), - super(key: key); + }) : super(key: key); /// The [Widget] that this widget watches for changes on final Widget child; @@ -46,7 +45,7 @@ class LazyLoadScrollView extends StatefulWidget { } class _LazyLoadScrollViewState extends State { - _LoadingStatus _loadMoreStatus = _LoadingStatus.stable; + var _loadMoreStatus = _LoadingStatus.stable; double _scrollPosition = 0; @override @@ -73,7 +72,7 @@ class _LazyLoadScrollViewState extends State { final pixels = notification.metrics.pixels; final maxScrollExtent = notification.metrics.maxScrollExtent; final minScrollExtent = notification.metrics.minScrollExtent; - final scrollOffset = widget.scrollOffset ?? 0; + final scrollOffset = widget.scrollOffset; if (pixels > (minScrollExtent + scrollOffset) && pixels < (maxScrollExtent - scrollOffset)) { @@ -114,7 +113,7 @@ class _LazyLoadScrollViewState extends State { } void _onEndOfPage() { - if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) { + if (_loadMoreStatus == _LoadingStatus.stable) { if (widget.onEndOfPage != null) { _loadMoreStatus = _LoadingStatus.loading; widget.onEndOfPage!().whenComplete(() { @@ -125,7 +124,7 @@ class _LazyLoadScrollViewState extends State { } void _onStartOfPage() { - if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) { + if (_loadMoreStatus == _LoadingStatus.stable) { if (widget.onStartOfPage != null) { _loadMoreStatus = _LoadingStatus.loading; widget.onStartOfPage!().whenComplete(() { diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index c376dddc..f7bce6cd 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -111,18 +111,18 @@ class MessageListCore extends StatefulWidget { class MessageListCoreState extends State { late StreamChannelState _streamChannel; - bool get _upToDate => _streamChannel.channel.state.isUpToDate; + bool get _upToDate => _streamChannel.channel.state?.isUpToDate ?? true; bool get _isThreadConversation => widget.parentMessage != null; - OwnUser get _currentUser => _streamChannel.channel.client.state.user; + OwnUser? get _currentUser => _streamChannel.channel.client.state.user; var _messages = []; @override Widget build(BuildContext context) { final messagesStream = _isThreadConversation - ? _streamChannel.channel.state.threadsStream + ? _streamChannel.channel.state?.threadsStream .where((threads) => threads.containsKey(widget.parentMessage!.id)) .map((threads) => threads[widget.parentMessage!.id]) : _streamChannel.channel.state?.messagesStream; @@ -141,7 +141,7 @@ class MessageListCoreState extends State { )), builder: (context, snapshot) { if (snapshot.hasError) { - return widget.errorWidgetBuilder(context, snapshot.error); + return widget.errorWidgetBuilder(context, snapshot.error!); } else if (!snapshot.hasData) { return widget.loadingBuilder(context); } else { @@ -163,8 +163,9 @@ class MessageListCoreState extends State { /// Fetches more messages with updated pagination and updates the widget. /// /// Optionally pass the fetch direction, defaults to [QueryDirection.bottom] - Future paginateData( - {QueryDirection? direction = QueryDirection.bottom}) { + Future paginateData({ + QueryDirection direction = QueryDirection.bottom, + }) { if (!_isThreadConversation) { return _streamChannel.queryMessages(direction: direction); } else { @@ -199,5 +200,5 @@ class MessageListCoreState extends State { /// Controller used for paginating data in [ChannelListView] class MessageListController { /// Call this function to load further data - Future Function({QueryDirection? direction})? paginateData; + Future Function({QueryDirection direction})? paginateData; } 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 fc858e5b..8586e1ff 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 @@ -58,7 +58,7 @@ class MessageSearchBlocState extends State /// Calls [StreamChatClient.search] updating /// [messagesStream] and [queryMessagesLoading] stream Future search({ - Map? filter, + required Map filter, Map? messageFilter, List? sort, String? query, @@ -77,20 +77,18 @@ class MessageSearchBlocState extends State final oldMessages = List.from(messageResponses ?? []); final messages = await client.search( - filter!, - sort: sort!, - query: query!, - paginationParams: pagination!, - messageFilters: messageFilter!, + filter, + sort: sort, + query: query, + paginationParams: pagination, + messageFilters: messageFilter, ); - if (messages.results != null) { - if (clear) { - _messageResponses.add(messages.results!); - } else { - final temp = oldMessages + messages.results!; - _messageResponses.add(temp); - } + if (clear) { + _messageResponses.add(messages.results); + } else { + final temp = oldMessages + messages.results; + _messageResponses.add(temp); } if (_messageResponses.hasValue && _queryMessagesLoadingController.value!) { diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index 67019677..ca661dc6 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -47,17 +47,13 @@ class MessageSearchListCore extends StatefulWidget { required this.errorBuilder, required this.loadingBuilder, required this.childBuilder, + required this.filters, this.messageQuery, - this.filters, this.sortOptions, this.paginationParams, this.messageFilters, this.messageSearchListController, - }) : assert(emptyBuilder != null, 'emptyBuilder should not be null'), - assert(errorBuilder != null, 'errorBuilder should not be null'), - assert(loadingBuilder != null, 'loadingBuilder should not be null'), - assert(childBuilder != null, 'childBuilder should not be null'), - super(key: key); + }) : super(key: key); /// A [MessageSearchListController] allows reloading and pagination. /// Use [MessageSearchListController.loadData] and @@ -71,7 +67,7 @@ class MessageSearchListCore extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map? filters; + final Map filters; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be @@ -92,7 +88,7 @@ class MessageSearchListCore extends StatefulWidget { final Map? messageFilters; /// The builder that is used when the search messages are fetched - final Widget Function(List?) childBuilder; + final Widget Function(List) childBuilder; /// The builder used when the channel list is empty. final WidgetBuilder emptyBuilder; @@ -130,7 +126,7 @@ class MessageSearchListCoreState extends State { stream: messageSearchBloc.messagesStream, builder: (context, snapshot) { if (snapshot.hasError) { - return widget.errorBuilder(context, snapshot.error); + return widget.errorBuilder(context, snapshot.error!); } if (!snapshot.hasData) { return widget.loadingBuilder(context); @@ -139,7 +135,7 @@ class MessageSearchListCoreState extends State { if (items.isEmpty) { return widget.emptyBuilder(context); } - return widget.childBuilder(snapshot.data); + return widget.childBuilder(items); }, ); @@ -172,13 +168,13 @@ class MessageSearchListCoreState extends State { @override void didUpdateWidget(MessageSearchListCore oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.filters?.toString() != oldWidget.filters?.toString() || + if (widget.filters.toString() != oldWidget.filters.toString() || jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) || widget.messageQuery?.toString() != oldWidget.messageQuery?.toString() || widget.messageFilters?.toString() != oldWidget.messageFilters?.toString() || - widget.paginationParams?.toJson()?.toString() != - oldWidget.paginationParams?.toJson()?.toString()) { + widget.paginationParams?.toJson().toString() != + oldWidget.paginationParams?.toJson().toString()) { loadData(); } } 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 c119f207..34f58faa 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; @@ -28,7 +29,7 @@ class StreamChannel extends StatefulWidget { this.initialMessageId, }) : super(key: key); - // ignore: public_member_api_docs + /// The child of the widget final Widget child; /// [channel] specifies the channel with which child should be wrapped @@ -68,8 +69,8 @@ class StreamChannelState extends State { String? get initialMessageId => widget.initialMessageId; /// Current channel state stream - Stream get channelStateStream => - widget.channel.state.channelStateStream; + Stream? get channelStateStream => + widget.channel.state?.channelStateStream; final _queryTopMessagesController = BehaviorSubject.seeded(false); final _queryBottomMessagesController = BehaviorSubject.seeded(false); @@ -87,16 +88,18 @@ class StreamChannelState extends State { int limit = 20, bool preferOffline = false, }) async { - if (_topPaginationEnded || _queryTopMessagesController?.value == true) { + if (_topPaginationEnded || + _queryTopMessagesController.value == true || + channel.state == null) { return; } _queryTopMessagesController.add(true); - if (channel.state.messages.isEmpty) { + if (channel.state!.messages.isEmpty) { return _queryTopMessagesController.add(false); } - final oldestMessage = channel.state.messages.first; + final oldestMessage = channel.state!.messages.first; try { final state = await queryBeforeMessage( @@ -118,15 +121,16 @@ class StreamChannelState extends State { bool preferOffline = false, }) async { if (_bottomPaginationEnded || - _queryBottomMessagesController?.value == true || - channel?.state?.isUpToDate == true) return; + _queryBottomMessagesController.value == true || + channel.state == null || + channel.state!.isUpToDate == true) return; _queryBottomMessagesController.add(true); - if (channel.state.messages.isEmpty) { + if (channel.state!.messages.isEmpty) { return _queryBottomMessagesController.add(false); } - final recentMessage = channel.state.messages.last; + final recentMessage = channel.state!.messages.last; try { final state = await queryAfterMessage( @@ -155,12 +159,14 @@ class StreamChannelState extends State { int limit = 50, bool preferOffline = false, }) async { - if (_topPaginationEnded || _queryTopMessagesController.value!) return; + if (_topPaginationEnded || + _queryTopMessagesController.value! || + channel.state == null) return; _queryTopMessagesController.add(true); late Message message; - if (channel.state.threads.containsKey(parentId)) { - final thread = channel.state.threads[parentId]!; + if (channel.state!.threads.containsKey(parentId)) { + final thread = channel.state!.threads[parentId]!; if (thread.isNotEmpty) { message = thread.first; } @@ -170,7 +176,7 @@ class StreamChannelState extends State { final response = await channel.getReplies( parentId, PaginationParams( - lessThan: message?.id, + lessThan: message.id, limit: limit, ), preferOffline: preferOffline, @@ -224,8 +230,8 @@ class StreamChannelState extends State { bool preferOffline = false, }) async { if (channel.state == null) return []; - channel.state.isUpToDate = false; - channel.state.truncate(); + channel.state!.isUpToDate = false; + channel.state!.truncate(); if (messageId == null) { await channel.query( @@ -234,7 +240,7 @@ class StreamChannelState extends State { ), preferOffline: preferOffline, ); - channel.state.isUpToDate = true; + channel.state!.isUpToDate = true; return []; } @@ -280,14 +286,14 @@ class StreamChannelState extends State { preferOffline: preferOffline, ); if (state.messages.isEmpty || state.messages.length < limit) { - channel.state.isUpToDate = true; + channel.state?.isUpToDate = true; } return state; } /// Future getMessage(String messageId) async { - var message = channel.state.messages.firstWhereOrNull( + var message = channel.state?.messages.firstWhereOrNull( (it) => it.id == messageId, ); if (message == null) { diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index 42ec8a21..a72425ed 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -43,9 +43,7 @@ class StreamChatCore extends StatefulWidget { required this.child, this.onBackgroundEventReceived, this.backgroundKeepAlive = const Duration(minutes: 1), - }) : assert(client != null, 'Stream Chat Client should not be null'), - assert(child != null, 'Child should not be null'), - super(key: key); + }) : super(key: key); /// Instance of Stream Chat Client containing information about the current /// application. @@ -93,15 +91,15 @@ class StreamChatCoreState extends State Widget build(BuildContext context) => widget.child; /// The current user - User? get user => client.state?.user; + User? get user => client.state.user; /// The current user as a stream - Stream? get userStream => client.state?.userStream; + Stream get userStream => client.state.userStream; @override void initState() { super.initState(); - WidgetsBinding.instance!.addObserver(this); + WidgetsBinding.instance?.addObserver(this); } StreamSubscription? _eventSubscription; @@ -119,15 +117,15 @@ class StreamChatCoreState extends State ); void onTimerComplete() { - _eventSubscription!.cancel(); + _eventSubscription?.cancel(); client.disconnect(); } _disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete); } else if (state == AppLifecycleState.resumed) { if (_disconnectTimer?.isActive == true) { - _eventSubscription!.cancel(); - _disconnectTimer!.cancel(); + _eventSubscription?.cancel(); + _disconnectTimer?.cancel(); } else { if (client.wsConnectionStatus == ConnectionStatus.disconnected) { client.connect(); @@ -139,7 +137,7 @@ class StreamChatCoreState extends State @override void dispose() { - WidgetsBinding.instance!.removeObserver(this); + WidgetsBinding.instance?.removeObserver(this); _eventSubscription?.cancel(); _disconnectTimer?.cancel(); super.dispose(); diff --git a/packages/stream_chat_flutter_core/lib/src/typedef.dart b/packages/stream_chat_flutter_core/lib/src/typedef.dart index d5e7af2c..ace8d7e6 100644 --- a/packages/stream_chat_flutter_core/lib/src/typedef.dart +++ b/packages/stream_chat_flutter_core/lib/src/typedef.dart @@ -4,7 +4,7 @@ import 'package:stream_chat/stream_chat.dart'; /// A signature for a callback which exposes an error and returns a function. /// This Callback can be used in cases where an API failure occurs and the /// widget is unable to render data. -typedef ErrorBuilder = Widget Function(BuildContext context, Object? error); +typedef ErrorBuilder = Widget Function(BuildContext context, Object error); /// A Signature for a handler function which will expose a [event]. typedef EventHandler = void Function(Event event); diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index d2412b97..1443ef17 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -68,11 +68,7 @@ class UserListCore extends StatefulWidget { this.pagination, this.groupAlphabetically = false, this.userListController, - }) : assert(errorBuilder != null, ''), - assert(emptyBuilder != null, ''), - assert(loadingBuilder != null, ''), - assert(listBuilder != null, ''), - super(key: key); + }) : super(key: key); /// A [UserListController] allows reloading and pagination. /// Use [UserListController.loadData] and [UserListController.paginateData] @@ -80,7 +76,7 @@ class UserListCore extends StatefulWidget { final UserListController? userListController; /// The builder that will be used in case of error - final Widget Function(Object? error) errorBuilder; + final Widget Function(Object error) errorBuilder; /// The builder that will be used to build the list final Widget Function(BuildContext context, List users) listBuilder; @@ -180,7 +176,7 @@ class UserListCoreState extends State stream: _buildUserStream(usersBlocState), builder: (context, snapshot) { if (snapshot.hasError) { - return widget.errorBuilder(snapshot.error); + return widget.errorBuilder(snapshot.error!); } if (!snapshot.hasData) { return widget.loadingBuilder(context); @@ -223,8 +219,8 @@ class UserListCoreState extends State if (widget.filter?.toString() != oldWidget.filter?.toString() || jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) || widget.options?.toString() != oldWidget.options?.toString() || - widget.pagination?.toJson()?.toString() != - oldWidget.pagination?.toJson()?.toString()) { + widget.pagination?.toJson().toString() != + oldWidget.pagination?.toJson().toString()) { loadData(); } } 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 2470fba7..dcc3252f 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -16,11 +16,7 @@ class UsersBloc extends StatefulWidget { const UsersBloc({ required this.child, Key? key, - }) : assert( - child != null, - 'When constructing a UsersBloc, the parameter ' - 'child should not be null.'), - super(key: key); + }) : super(key: key); /// The widget child final Widget child; @@ -76,17 +72,15 @@ class UsersBlocState extends State } try { - final clear = pagination == null || - pagination.offset == null || - pagination.offset == 0; + final clear = pagination == null || pagination.offset == 0; final oldUsers = List.from(users ?? []); final usersResponse = await client.queryUsers( - filter: filter!, - sort: sort!, - options: options!, - pagination: pagination!, + filter: filter, + sort: sort, + options: options, + pagination: pagination, ); if (clear) { diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 195c4ee3..750aef95 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -12,12 +12,12 @@ environment: flutter: ">=1.17.0" dependencies: + collection: ^1.15.0-nullsafety.4 flutter: sdk: flutter - meta: ^1.2.4 + meta: ^1.3.0 rxdart: ^0.26.0 - stream_chat: ^1.5.0 - collection: ^1.15.0-nullsafety.4 + stream_chat: ^1.5.2 dependency_overrides: stream_chat: @@ -27,5 +27,5 @@ dev_dependencies: fake_async: ^1.2.0 flutter_test: sdk: flutter - mockito: ^5.0.3 + mocktail: ^0.1.1 diff --git a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart index b0fb9ded..5bbdaf39 100644 --- a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart @@ -1,8 +1,8 @@ import 'dart:async'; -import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; -import 'package:mockito/mockito.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter_core/src/channel_list_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -30,58 +30,6 @@ void main() { ); } - test( - 'should throw assertion error in case listBuilder is null', - () { - final channelListCore = () => ChannelListCore( - listBuilder: null, - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), - ); - expect(channelListCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case loadingBuilder is null', - () { - final channelListCore = () => ChannelListCore( - listBuilder: (_, __) => Offstage(), - loadingBuilder: null, - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), - ); - expect(channelListCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case emptyBuilder is null', - () { - final channelListCore = () => ChannelListCore( - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: null, - errorBuilder: (BuildContext context, Object error) => Offstage(), - ); - expect(channelListCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case errorBuilder is null', - () { - final channelListCore = () => ChannelListCore( - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: null, - ); - expect(channelListCore, throwsA(isA())); - }, - ); - testWidgets( 'should throw if ChannelListCore is used where ChannelsBloc is not present ' 'in the widget tree', @@ -116,7 +64,8 @@ void main() { final mockClient = MockClient(); - when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + when(() => mockClient.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -151,7 +100,8 @@ void main() { final mockClient = MockClient(); - when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + when(() => mockClient.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -185,15 +135,16 @@ void main() { final mockClient = MockClient(); - when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + when(() => mockClient.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.empty()); const error = 'Error! Error! Error!'; - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: pagination, - )).thenThrow(error); + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: pagination, + )).thenThrow(error); await tester.pumpWidget( StreamChatCore( @@ -208,12 +159,12 @@ void main() { expect(find.byKey(errorWidgetKey), findsOneWidget); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: pagination, - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: pagination, + )).called(1); }, ); @@ -233,15 +184,16 @@ void main() { final mockClient = MockClient(); - when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + when(() => mockClient.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.empty()); const channels = []; - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: pagination, - )).thenAnswer((_) => Stream.value(channels)); + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: pagination, + )).thenAnswer((_) => Stream.value(channels)); await tester.pumpWidget( StreamChatCore( @@ -256,12 +208,12 @@ void main() { expect(find.byKey(emptyWidgetKey), findsOneWidget); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: pagination, - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: pagination, + )).called(1); }, ); @@ -281,15 +233,16 @@ void main() { final mockClient = MockClient(); - when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + when(() => mockClient.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.empty()); final channels = _generateChannels(mockClient); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: pagination, - )).thenAnswer((_) => Stream.value(channels)); + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: pagination, + )).thenAnswer((_) => Stream.value(channels)); await tester.pumpWidget( StreamChatCore( @@ -304,12 +257,12 @@ void main() { expect(find.byKey(listWidgetKey), findsOneWidget); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: pagination, - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: pagination, + )).called(1); }, ); @@ -337,15 +290,16 @@ void main() { final mockClient = MockClient(); - when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + when(() => mockClient.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.empty()); final channels = _generateChannels(mockClient); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: pagination, - )).thenAnswer((_) => Stream.value(channels)); + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: pagination, + )).thenAnswer((_) => Stream.value(channels)); await tester.pumpWidget( Directionality( @@ -364,12 +318,12 @@ void main() { expect(find.byKey(listWidgetKey), findsOneWidget); expect(find.text(channels.map((e) => e.cid).join(',')), findsOneWidget); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: pagination, - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: pagination, + )).called(1); final channelListCoreState = tester.state( find.byKey(channelListCoreKey), @@ -378,12 +332,12 @@ void main() { final offset = channels.length; final paginatedChannels = _generateChannels(mockClient, offset: offset); final updatedPagination = pagination.copyWith(offset: offset); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: updatedPagination, - )).thenAnswer((_) => Stream.value(paginatedChannels)); + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: updatedPagination, + )).thenAnswer((_) => Stream.value(paginatedChannels)); await channelListCoreState.paginateData(); @@ -398,12 +352,12 @@ void main() { findsOneWidget, ); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: updatedPagination, - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: updatedPagination, + )).called(1); }, ); @@ -411,8 +365,8 @@ void main() { 'should rebuild ChannelListCore with updated widget data ' 'on calling setState()', (tester) async { - StateSetter _stateSetter; - int limit = pagination.limit; + StateSetter? _stateSetter; + var limit = pagination.limit; const channelListCoreKey = Key('channelListCore'); const listWidgetKey = Key('listWidget'); @@ -435,15 +389,16 @@ void main() { final mockClient = MockClient(); - when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + when(() => mockClient.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.empty()); final channels = _generateChannels(mockClient); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: pagination, - )).thenAnswer((_) => Stream.value(channels)); + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: pagination, + )).thenAnswer((_) => Stream.value(channels)); await tester.pumpWidget( Directionality( @@ -466,24 +421,24 @@ void main() { expect(find.byKey(listWidgetKey), findsOneWidget); expect(find.text(channels.map((e) => e.cid).join(',')), findsOneWidget); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: pagination, - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: pagination, + )).called(1); // Rebuilding ChannelListCore with new pagination limit - _stateSetter(() => limit = 6); + _stateSetter?.call(() => limit = 6); final updatedChannels = _generateChannels(mockClient, count: limit); final updatedPagination = pagination.copyWith(limit: limit); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: updatedPagination, - )).thenAnswer((_) => Stream.value(updatedChannels)); + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: updatedPagination, + )).thenAnswer((_) => Stream.value(updatedChannels)); await tester.pumpAndSettle(); @@ -493,12 +448,12 @@ void main() { findsOneWidget, ); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: updatedPagination, - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: updatedPagination, + )).called(1); }, ); } diff --git a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart index e021e9b7..de8b0af1 100644 --- a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart @@ -2,13 +2,17 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'matchers/channel_matcher.dart'; import 'mocks.dart'; void main() { + setUpAll(() { + registerFallbackValue(const PaginationParams()); + }); + List _generateChannels( StreamChatClient client, { int count = 3, @@ -28,18 +32,6 @@ void main() { ); } - test( - 'should throw assertion error if child is null', - () async { - const channelsBlocKey = Key('channelsBloc'); - final channelsBloc = () => ChannelsBloc( - key: channelsBlocKey, - child: null, - ); - expect(channelsBloc, throwsA(isA())); - }, - ); - testWidgets( 'should throw if ChannelsBloc is used where StreamChat is not present in the widget tree', (tester) async { @@ -70,7 +62,8 @@ void main() { final mockClient = MockClient(); - when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + when(() => mockClient.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -101,7 +94,8 @@ void main() { final mockClient = MockClient(); - when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + when(() => mockClient.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -117,12 +111,12 @@ void main() { final offlineChannels = _generateChannels(mockClient); final onlineChannels = _generateChannels(mockClient, offset: 3); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer( + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( (_) => Stream.fromIterable([offlineChannels, onlineChannels]), ); @@ -136,12 +130,12 @@ void main() { ]), ); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).called(1); }, ); @@ -163,7 +157,8 @@ void main() { final mockClient = MockClient(); - when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + when(() => mockClient.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -176,14 +171,14 @@ void main() { find.byKey(channelsBlocKey), ); - final error = 'Error! Error! Error!'; + const error = 'Error! Error! Error!'; - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).thenThrow(error); + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).thenThrow(error); channelsBlocState.queryChannels(); @@ -192,12 +187,12 @@ void main() { emitsError(error), ); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).called(1); }, ); @@ -214,7 +209,8 @@ void main() { final mockClient = MockClient(); - when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + when(() => mockClient.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -229,38 +225,41 @@ void main() { final channels = _generateChannels(mockClient); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer((_) => Stream.value(channels)); + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer((_) => Stream.value(channels)); - channelsBlocState.queryChannels(); + const pagination = PaginationParams(limit: 3); + channelsBlocState.queryChannels( + paginationParams: pagination, + ); await expectLater( channelsBlocState.channelsStream, emits(isSameChannelListAs(channels)), ); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).called(1); final offset = channels.length; - final paginationParams = PaginationParams(offset: offset); + final paginationParams = pagination.copyWith(offset: offset); final newChannels = _generateChannels(mockClient, offset: offset); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: paginationParams, - )).thenAnswer( + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: paginationParams, + )).thenAnswer( (_) => Stream.value(newChannels), ); @@ -277,12 +276,12 @@ void main() { ), ]); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: paginationParams, - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: paginationParams, + )).called(1); }, ); @@ -299,7 +298,8 @@ void main() { final mockClient = MockClient(); - when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); + when(() => mockClient.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -313,39 +313,41 @@ void main() { ); final channels = _generateChannels(mockClient); + final paginationParams = const PaginationParams( + limit: 3, + ); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer((_) => Stream.value(channels)); + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: paginationParams, + )).thenAnswer((_) => Stream.value(channels)); - channelsBlocState.queryChannels(); + channelsBlocState.queryChannels( + paginationParams: paginationParams, + ); await expectLater( channelsBlocState.channelsStream, emits(isSameChannelListAs(channels)), ); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).called(1); - - final offset = channels.length; - final paginationParams = PaginationParams(offset: offset); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: paginationParams, + )).called(1); final error = 'Error! Error! Error!'; - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: paginationParams, - )).thenThrow(error); + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: paginationParams, + )).thenThrow(error); channelsBlocState.queryChannels(paginationParams: paginationParams); @@ -354,17 +356,17 @@ void main() { emitsError(error), ); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: paginationParams, - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: paginationParams, + )).called(1); }, ); group('event controller test', () { - StreamController eventController; + late StreamController eventController; setUp(() { eventController = StreamController.broadcast(); }); @@ -379,12 +381,12 @@ void main() { child: Offstage(), ); - when(mockClient.on(any, any, any, any)) + when(() => mockClient.on(any(), any(), any(), any())) .thenAnswer((_) => Stream.empty()); - when(mockClient.on( - EventType.channelHidden, - )).thenAnswer((_) => eventController.stream); + when(() => mockClient.on( + EventType.channelHidden, + )).thenAnswer((_) => eventController.stream); await tester.pumpWidget( StreamChatCore( @@ -399,23 +401,23 @@ void main() { final channels = _generateChannels(mockClient); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer( + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( (_) => Stream.value(channels), ); await channelsBlocState.queryChannels(); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).called(1); final channelHiddenEvent = Event( type: EventType.channelHidden, @@ -435,7 +437,7 @@ void main() { ]), ); - verify(mockClient.on(EventType.channelHidden)).called(1); + verify(() => mockClient.on(EventType.channelHidden)).called(1); }, ); @@ -450,13 +452,13 @@ void main() { child: Offstage(), ); - when(mockClient.on(any, any, any, any)) + when(() => mockClient.on(any(), any(), any(), any())) .thenAnswer((_) => Stream.empty()); - when(mockClient.on( - EventType.channelDeleted, - EventType.notificationRemovedFromChannel, - )).thenAnswer((_) => eventController.stream); + when(() => mockClient.on( + EventType.channelDeleted, + EventType.notificationRemovedFromChannel, + )).thenAnswer((_) => eventController.stream); await tester.pumpWidget( StreamChatCore( @@ -471,31 +473,38 @@ void main() { final channels = _generateChannels(mockClient); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer( + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( (_) => Stream.value(channels), ); await channelsBlocState.queryChannels(); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).called(1); final channelDeletedOrNotificationRemovedEvent = Event( - channel: EventChannel(cid: channels.first.cid), + channel: EventChannel( + cid: channels.first.cid!, + updatedAt: DateTime.now(), + config: ChannelConfig(), + createdAt: DateTime.now(), + memberCount: 1, + ), ); eventController.add(channelDeletedOrNotificationRemovedEvent); - final channelCid = channelDeletedOrNotificationRemovedEvent.channel.cid; + final channelCid = + channelDeletedOrNotificationRemovedEvent.channel?.cid; final newChannels = [...channels] ..removeWhere((it) => it.cid == channelCid); @@ -507,10 +516,10 @@ void main() { ]), ); - verify(mockClient.on( - EventType.channelDeleted, - EventType.notificationRemovedFromChannel, - )).called(1); + verify(() => mockClient.on( + EventType.channelDeleted, + EventType.notificationRemovedFromChannel, + )).called(1); }, ); @@ -525,12 +534,12 @@ void main() { child: Offstage(), ); - when(mockClient.on(any, any, any, any)) + when(() => mockClient.on(any(), any(), any(), any())) .thenAnswer((_) => Stream.empty()); - when(mockClient.on( - EventType.messageNew, - )).thenAnswer((_) => eventController.stream); + when(() => mockClient.on( + EventType.messageNew, + )).thenAnswer((_) => eventController.stream); await tester.pumpWidget( StreamChatCore( @@ -545,23 +554,23 @@ void main() { final channels = _generateChannels(mockClient); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer( + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( (_) => Stream.value(channels), ); await channelsBlocState.queryChannels(); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).called(1); final messageNewEvent = Event( type: EventType.messageNew, @@ -585,7 +594,7 @@ void main() { ]), ); - verify(mockClient.on(EventType.messageNew)).called(1); + verify(() => mockClient.on(EventType.messageNew)).called(1); }, ); @@ -609,16 +618,16 @@ void main() { shouldAddChannel: (e) => channels.map((it) => it.cid).contains(e.cid), ); - when(mockClient.on(any, any, any, any)) + when(() => mockClient.on(any(), any(), any(), any())) .thenAnswer((_) => Stream.empty()); - when(mockClient.on( - EventType.channelHidden, - )).thenAnswer((_) => hiddenChannelEventController.stream); + when(() => mockClient.on( + EventType.channelHidden, + )).thenAnswer((_) => hiddenChannelEventController.stream); - when(mockClient.on( - EventType.messageNew, - )).thenAnswer((_) => eventController.stream); + when(() => mockClient.on( + EventType.messageNew, + )).thenAnswer((_) => eventController.stream); final messageNewEvent = Event( type: EventType.messageNew, @@ -636,23 +645,23 @@ void main() { find.byKey(channelsBlocKey), ); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer( + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( (_) => Stream.value(channels), ); await channelsBlocState.queryChannels(); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).called(1); final channelHiddenEvent = Event( type: EventType.channelHidden, @@ -681,8 +690,8 @@ void main() { ]), ); - verify(mockClient.on(EventType.channelHidden)).called(1); - verify(mockClient.on(EventType.messageNew)).called(1); + verify(() => mockClient.on(EventType.channelHidden)).called(1); + verify(() => mockClient.on(EventType.messageNew)).called(1); }, ); @@ -703,14 +712,14 @@ void main() { shouldAddChannel: (_) => true, ); - when(mockClient.state.channels).thenReturn(stateChannels); + when(() => mockClient.state.channels).thenReturn(stateChannels); - when(mockClient.on(any, any, any, any)) + when(() => mockClient.on(any(), any(), any(), any())) .thenAnswer((_) => Stream.empty()); - when(mockClient.on( - EventType.messageNew, - )).thenAnswer((_) => eventController.stream); + when(() => mockClient.on( + EventType.messageNew, + )).thenAnswer((_) => eventController.stream); await tester.pumpWidget( StreamChatCore( @@ -723,23 +732,23 @@ void main() { find.byKey(channelsBlocKey), ); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer( + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( (_) => Stream.value(channels), ); await channelsBlocState.queryChannels(); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).called(1); final messageNewEvent = Event( type: EventType.messageNew, @@ -749,7 +758,7 @@ void main() { eventController.add(messageNewEvent); final newChannels = [...channels] - ..insert(0, stateChannels[stateChannels.keys.first]); + ..insert(0, stateChannels[stateChannels.keys.first]!); await expectLater( channelsBlocState.channelsStream, @@ -759,7 +768,7 @@ void main() { ]), ); - verify(mockClient.on(EventType.messageNew)).called(1); + verify(() => mockClient.on(EventType.messageNew)).called(1); }, ); @@ -770,8 +779,8 @@ void main() { final mockClient = MockClient(); final channels = _generateChannels(mockClient); int channelComparator(Channel a, Channel b) { - final aData = a.extraData['extra_data_key'] as String; - final bData = b.extraData['extra_data_key'] as String; + final aData = a.extraData!['extra_data_key'] as String; + final bData = b.extraData!['extra_data_key'] as String; return bData.compareTo(aData); } @@ -783,12 +792,12 @@ void main() { channelsComparator: channelComparator, ); - when(mockClient.on(any, any, any, any)) + when(() => mockClient.on(any(), any(), any(), any())) .thenAnswer((_) => Stream.empty()); - when(mockClient.on( - EventType.messageNew, - )).thenAnswer((_) => eventController.stream); + when(() => mockClient.on( + EventType.messageNew, + )).thenAnswer((_) => eventController.stream); await tester.pumpWidget( StreamChatCore( @@ -801,23 +810,23 @@ void main() { find.byKey(channelsBlocKey), ); - when(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer( + when(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( (_) => Stream.value(channels), ); await channelsBlocState.queryChannels(); - verify(mockClient.queryChannels( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + paginationParams: any(named: 'paginationParams'), + )).called(1); final messageNewEvent = Event( type: EventType.messageNew, @@ -836,7 +845,7 @@ void main() { ]), ); - verify(mockClient.on(EventType.messageNew)).called(1); + verify(() => mockClient.on(EventType.messageNew)).called(1); }, ); diff --git a/packages/stream_chat_flutter_core/test/lazy_load_scroll_view_test.dart b/packages/stream_chat_flutter_core/test/lazy_load_scroll_view_test.dart index 19c1f2a2..af59088f 100644 --- a/packages/stream_chat_flutter_core/test/lazy_load_scroll_view_test.dart +++ b/packages/stream_chat_flutter_core/test/lazy_load_scroll_view_test.dart @@ -3,18 +3,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter_core/src/lazy_load_scroll_view.dart'; void main() { - test( - 'should throw assertion error if child is null', - () async { - const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); - final lazyLoadScrollView = () => LazyLoadScrollView( - key: lazyLoadScrollViewKey, - child: null, - ); - expect(lazyLoadScrollView, throwsA(isA())); - }, - ); - testWidgets( 'should render LazyLoadScrollView if child is provided', (tester) async { diff --git a/packages/stream_chat_flutter_core/test/matchers/get_message_response_matcher.dart b/packages/stream_chat_flutter_core/test/matchers/get_message_response_matcher.dart index 42ea0d34..c3a6629d 100644 --- a/packages/stream_chat_flutter_core/test/matchers/get_message_response_matcher.dart +++ b/packages/stream_chat_flutter_core/test/matchers/get_message_response_matcher.dart @@ -1,4 +1,3 @@ -import 'package:meta/meta.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -8,14 +7,14 @@ Matcher isSameMessageResponseAs(GetMessageResponse targetResponse) => class _IsSameMessageResponseAs extends Matcher { const _IsSameMessageResponseAs({ required this.targetResponse, - }) : assert(targetResponse != null, ''); + }); final GetMessageResponse targetResponse; @override bool matches(covariant GetMessageResponse response, Map matchState) => response.message.id == targetResponse.message.id && - response.channel.cid == targetResponse.channel.cid; + response.channel?.cid == targetResponse.channel?.cid; @override Description describe(Description description) => @@ -29,14 +28,14 @@ Matcher isSameMessageResponseListAs( class _IsSameMessageResponseListAs extends Matcher { const _IsSameMessageResponseListAs({ required this.targetResponseList, - }) : assert(targetResponseList != null, ''); + }); final List targetResponseList; @override bool matches( covariant List responseList, Map matchState) { - bool matches = true; + var matches = true; for (var i = 0; i < responseList.length; i++) { final response = responseList[i]; final targetResponse = targetResponseList[i]; diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index 95446f95..9aea9489 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter_core/src/message_list_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -62,61 +62,6 @@ void main() { return threads ? threadMessages : messages; } - test( - 'should throw assertion error in case messageListBuilder is null', - () { - final messageListCore = () => MessageListCore( - messageListBuilder: null, - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorWidgetBuilder: (BuildContext context, Object error) => - Offstage(), - ); - expect(messageListCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case loadingBuilder is null', - () { - final messageListCore = () => MessageListCore( - messageListBuilder: (_, __) => Offstage(), - loadingBuilder: null, - emptyBuilder: (BuildContext context) => Offstage(), - errorWidgetBuilder: (BuildContext context, Object error) => - Offstage(), - ); - expect(messageListCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case emptyBuilder is null', - () { - final messageListCore = () => MessageListCore( - messageListBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: null, - errorWidgetBuilder: (BuildContext context, Object error) => - Offstage(), - ); - expect(messageListCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case errorWidgetBuilder is null', - () { - final messageListCore = () => MessageListCore( - messageListBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorWidgetBuilder: null, - ); - expect(messageListCore, throwsA(isA())); - }, - ); - testWidgets( 'should throw if MessageListCore is used where StreamChannel is not present ' 'in the widget tree', @@ -150,8 +95,11 @@ void main() { ); final mockChannel = MockChannel(); + when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true)); - when(mockChannel.state.isUpToDate).thenReturn(true); + when(() => mockChannel.state.isUpToDate).thenReturn(true); + when(() => mockChannel.state.messagesStream) + .thenAnswer((_) => Stream.value([])); await tester.pumpWidget( StreamChannel( @@ -182,7 +130,10 @@ void main() { final mockChannel = MockChannel(); - when(mockChannel.state.isUpToDate).thenReturn(true); + when(() => mockChannel.state.isUpToDate).thenReturn(true); + when(() => mockChannel.state.messagesStream) + .thenAnswer((_) => Stream.value([])); + when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true)); await tester.pumpWidget( StreamChannel( @@ -213,11 +164,11 @@ void main() { final mockChannel = MockChannel(); - when(mockChannel.state.isUpToDate).thenReturn(true); - when(mockChannel.initialized).thenAnswer((_) async => true); + when(() => mockChannel.state.isUpToDate).thenReturn(true); + when(() => mockChannel.initialized).thenAnswer((_) async => true); const error = 'Error! Error! Error!'; - when(mockChannel.state.messagesStream) + when(() => mockChannel.state.messagesStream) .thenAnswer((_) => Stream.error(error)); await tester.pumpWidget( @@ -252,11 +203,11 @@ void main() { final mockChannel = MockChannel(); - when(mockChannel.state.isUpToDate).thenReturn(true); - when(mockChannel.initialized).thenAnswer((_) async => true); + when(() => mockChannel.state.isUpToDate).thenReturn(true); + when(() => mockChannel.initialized).thenAnswer((_) async => true); const messages = []; - when(mockChannel.state.messagesStream) + when(() => mockChannel.state.messagesStream) .thenAnswer((_) => Stream.value(messages)); await tester.pumpWidget( @@ -291,11 +242,18 @@ void main() { final mockChannel = MockChannel(); - when(mockChannel.state.isUpToDate).thenReturn(false); - when(mockChannel.initialized).thenAnswer((_) async => true); + when(() => mockChannel.state.isUpToDate).thenReturn(false); + when(() => mockChannel.initialized).thenAnswer((_) async => true); + when(() => mockChannel.query( + options: any(named: 'options'), + membersPagination: any(named: 'membersPagination'), + messagesPagination: any(named: 'messagesPagination'), + preferOffline: any(named: 'preferOffline'), + watchersPagination: any(named: 'watchersPagination'), + )).thenAnswer((_) async => ChannelState()); const messages = []; - when(mockChannel.state.messagesStream) + when(() => mockChannel.state.messagesStream) .thenAnswer((_) => Stream.value(messages)); await tester.pumpWidget( @@ -335,11 +293,11 @@ void main() { final mockChannel = MockChannel(); - when(mockChannel.state.isUpToDate).thenReturn(true); - when(mockChannel.initialized).thenAnswer((_) async => true); + when(() => mockChannel.state.isUpToDate).thenReturn(true); + when(() => mockChannel.initialized).thenAnswer((_) async => true); final messages = _generateMessages(); - when(mockChannel.state.messagesStream) + when(() => mockChannel.state.messagesStream) .thenAnswer((_) => Stream.value(messages)); await tester.pumpWidget( @@ -382,13 +340,13 @@ void main() { final mockChannel = MockChannel(); - when(mockChannel.state.isUpToDate).thenReturn(true); - when(mockChannel.initialized).thenAnswer((_) async => true); + when(() => mockChannel.state.isUpToDate).thenReturn(true); + when(() => mockChannel.initialized).thenAnswer((_) async => true); final threads = {parentMessage.id: messages}; - when(mockChannel.state.threads).thenReturn(threads); - when(mockChannel.state.threadsStream) + when(() => mockChannel.state.threads).thenReturn(threads); + when(() => mockChannel.state.threadsStream) .thenAnswer((_) => Stream.value(threads)); await tester.pumpWidget( diff --git a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart index e639c03b..5b427c02 100644 --- a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter_core/src/message_search_bloc.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -23,24 +23,12 @@ void main() { text: 'testTextData$index', ) ..channel = ChannelModel( - cid: 'testCid', + cid: 'testCid:id', ); }, ); } - test( - 'should throw assertion error if child is null', - () async { - const messageSearchBlocKey = Key('messageSearchBloc'); - final messageSearchBloc = () => MessageSearchBloc( - key: messageSearchBlocKey, - child: null, - ); - expect(messageSearchBloc, throwsA(isA())); - }, - ); - testWidgets( 'messageSearchBlocState.search() should throw if used where ' 'StreamChat is not present in the widget tree', @@ -62,7 +50,7 @@ void main() { ); try { - await usersBlocState.search(); + await usersBlocState.search(filter: {}); } catch (e) { expect(e, isInstanceOf()); } @@ -93,30 +81,30 @@ void main() { final messageResponseList = _generateMessages(); - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer( + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( (_) async => SearchMessagesResponse()..results = messageResponseList, ); - messageSearchBlocState.search(); + messageSearchBlocState.search(filter: {}); await expectLater( messageSearchBlocState.messagesStream, emits(isSameMessageResponseListAs(messageResponseList)), ); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).called(1); }, ); @@ -144,28 +132,28 @@ void main() { ); const error = 'Error! Error! Error!'; - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).thenThrow(error); + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).thenThrow(error); - messageSearchBlocState.search(); + messageSearchBlocState.search(filter: {}); await expectLater( messageSearchBlocState.messagesStream, emitsError(error), ); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).called(1); }, ); @@ -196,47 +184,47 @@ void main() { final messageResponseList = _generateMessages(); - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer( + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( (_) async => SearchMessagesResponse()..results = messageResponseList, ); - messageSearchBlocState.search(); + messageSearchBlocState.search(filter: {}); await expectLater( messageSearchBlocState.messagesStream, emits(isSameMessageResponseListAs(messageResponseList)), ); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).called(1); final offset = messageResponseList.length; final paginatedMessageResponseList = _generateMessages(offset: offset); final pagination = PaginationParams(offset: offset); - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: pagination, - )).thenAnswer( + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: pagination, + )).thenAnswer( (_) async => SearchMessagesResponse()..results = paginatedMessageResponseList, ); - messageSearchBlocState.search(pagination: pagination); + messageSearchBlocState.search(pagination: pagination, filter: {}); await Future.wait([ expectLater( @@ -251,13 +239,13 @@ void main() { ), ]); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: pagination, - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: pagination, + )).called(1); }, ); @@ -288,57 +276,57 @@ void main() { final messageResponseList = _generateMessages(); - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer( + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( (_) async => SearchMessagesResponse()..results = messageResponseList, ); - messageSearchBlocState.search(); + messageSearchBlocState.search(filter: {}); await expectLater( messageSearchBlocState.messagesStream, emits(isSameMessageResponseListAs(messageResponseList)), ); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).called(1); final offset = messageResponseList.length; final pagination = PaginationParams(offset: offset); const error = 'Error! Error! Error!'; - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: pagination, - )).thenThrow(error); + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: pagination, + )).thenThrow(error); - messageSearchBlocState.search(pagination: pagination); + messageSearchBlocState.search(pagination: pagination, filter: {}); await expectLater( messageSearchBlocState.queryMessagesLoading, emitsError(error), ); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: pagination, - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: pagination, + )).called(1); }, ); } diff --git a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart index aa4a47f9..00aee32a 100644 --- a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart @@ -1,8 +1,8 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter_core/src/message_search_list_core.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'mocks.dart'; @@ -10,74 +10,21 @@ void main() { List _generateMessages({ int count = 3, int offset = 0, - }) { - return List.generate( - count, - (index) { - index = index + offset; - return GetMessageResponse() - ..message = Message( - id: 'testId$index', - text: 'testTextData$index', - ) - ..channel = ChannelModel( - cid: 'testCid', - ); - }, - ); - } - - test( - 'should throw assertion error in case childBuilder is null', - () { - final messageSearchListCore = () => MessageSearchListCore( - childBuilder: null, - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), - ); - expect(messageSearchListCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case loadingBuilder is null', - () { - final messageSearchListCore = () => MessageSearchListCore( - childBuilder: (List messages) => Offstage(), - loadingBuilder: null, - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), - ); - expect(messageSearchListCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case emptyBuilder is null', - () { - final messageSearchListCore = () => MessageSearchListCore( - childBuilder: (List messages) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: null, - errorBuilder: (BuildContext context, Object error) => Offstage(), - ); - expect(messageSearchListCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case errorBuilder is null', - () { - final messageSearchListCore = () => MessageSearchListCore( - childBuilder: (List messages) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: null, - ); - expect(messageSearchListCore, throwsA(isA())); - }, - ); + }) => + List.generate( + count, + (index) { + index = index + offset; + return GetMessageResponse() + ..message = Message( + id: 'testId$index', + text: 'testTextData$index', + ) + ..channel = ChannelModel( + cid: 'test:Cid', + ); + }, + ); testWidgets( 'should throw if MessageSearchListCore is used where MessageSearchBloc ' @@ -86,10 +33,11 @@ void main() { const messageSearchListCoreKey = Key('messageSearchListCore'); final messageSearchListCore = MessageSearchListCore( key: messageSearchListCoreKey, - childBuilder: (List messages) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + childBuilder: (List? messages) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object? error) => const Offstage(), + filters: const {}, ); await tester.pumpWidget(messageSearchListCore); @@ -109,7 +57,8 @@ void main() { childBuilder: (List messages) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + errorBuilder: (BuildContext context, Object? error) => Offstage(), + filters: {}, ); final mockClient = MockClient(); @@ -140,6 +89,7 @@ void main() { emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), messageSearchListController: controller, + filters: {}, ); expect(controller.loadData, isNull); @@ -175,18 +125,19 @@ void main() { errorBuilder: (BuildContext context, Object error) => Offstage( key: errorWidgetKey, ), + filters: {}, ); final mockClient = MockClient(); const error = 'Error! Error! Error!'; - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).thenThrow(error); + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).thenThrow(error); await tester.pumpWidget( StreamChatCore( @@ -201,13 +152,13 @@ void main() { expect(find.byKey(errorWidgetKey), findsOneWidget); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).called(1); }, ); @@ -223,18 +174,19 @@ void main() { loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(key: emptyWidgetKey), errorBuilder: (BuildContext context, Object error) => Offstage(), + filters: {}, ); final mockClient = MockClient(); final messageResponseList = []; - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer( + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( (_) async => SearchMessagesResponse()..results = messageResponseList, ); @@ -251,13 +203,13 @@ void main() { expect(find.byKey(emptyWidgetKey), findsOneWidget); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).called(1); }, ); @@ -274,18 +226,19 @@ void main() { loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), + filters: {}, ); final mockClient = MockClient(); final messageResponseList = _generateMessages(); - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).thenAnswer( + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( (_) async => SearchMessagesResponse()..results = messageResponseList, ); @@ -302,13 +255,13 @@ void main() { expect(find.byKey(childWidgetKey), findsOneWidget); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: anyNamed('paginationParams'), - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: any(named: 'paginationParams'), + )).called(1); }, ); @@ -324,25 +277,26 @@ void main() { childBuilder: (List messages) => Container( key: childWidgetKey, child: Text( - messages.map((e) => '${e.channel.cid}-${e.message.id}').join(','), + messages.map((e) => '${e.channel?.cid}-${e.message.id}').join(','), ), ), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), paginationParams: pagination, + filters: {}, ); final mockClient = MockClient(); final messageResponseList = _generateMessages(); - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: pagination, - )).thenAnswer( + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: pagination, + )).thenAnswer( (_) async => SearchMessagesResponse()..results = messageResponseList, ); @@ -364,19 +318,19 @@ void main() { expect( find.text( messageResponseList - .map((e) => '${e.channel.cid}-${e.message.id}') + .map((e) => '${e.channel?.cid}-${e.message.id}') .join(','), ), findsOneWidget, ); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: pagination, - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: pagination, + )).called(1); final messageSearchListCoreState = tester.state( @@ -386,13 +340,13 @@ void main() { final offset = messageResponseList.length; final paginatedMessageResponseList = _generateMessages(offset: offset); final updatedPagination = pagination.copyWith(offset: offset); - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: updatedPagination, - )).thenAnswer( + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: updatedPagination, + )).thenAnswer( (_) async => SearchMessagesResponse()..results = paginatedMessageResponseList, ); @@ -406,17 +360,17 @@ void main() { find.text([ ...messageResponseList, ...paginatedMessageResponseList, - ].map((e) => '${e.channel.cid}-${e.message.id}').join(',')), + ].map((e) => '${e.channel?.cid}-${e.message.id}').join(',')), findsOneWidget, ); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: updatedPagination, - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: updatedPagination, + )).called(1); }, ); @@ -426,8 +380,8 @@ void main() { (tester) async { const pagination = PaginationParams(); - StateSetter _stateSetter; - int limit = pagination.limit; + StateSetter? _stateSetter; + var limit = pagination.limit; const messageSearchListCoreKey = Key('messageSearchListCore'); const childWidgetKey = Key('childWidget'); @@ -438,7 +392,7 @@ void main() { key: childWidgetKey, child: Text( messages - .map((e) => '${e.channel.cid}-${e.message.id}') + .map((e) => '${e.channel?.cid}-${e.message.id}') .join(','), ), ), @@ -446,18 +400,19 @@ void main() { emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), paginationParams: pagination.copyWith(limit: limit), + filters: {}, ); final mockClient = MockClient(); final messageResponseList = _generateMessages(); - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: pagination, - )).thenAnswer( + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: pagination, + )).thenAnswer( (_) async => SearchMessagesResponse()..results = messageResponseList, ); @@ -483,32 +438,32 @@ void main() { expect( find.text( messageResponseList - .map((e) => '${e.channel.cid}-${e.message.id}') + .map((e) => '${e.channel?.cid}-${e.message.id}') .join(','), ), findsOneWidget, ); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: pagination, - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: pagination, + )).called(1); // Rebuilding MessageSearchListCore with new pagination limit - _stateSetter(() => limit = 6); + _stateSetter?.call(() => limit = 6); final updatedMessageResponseList = _generateMessages(count: limit); final updatedPagination = pagination.copyWith(limit: limit); - when(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: updatedPagination, - )).thenAnswer( + when(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: updatedPagination, + )).thenAnswer( (_) async => SearchMessagesResponse()..results = updatedMessageResponseList, ); @@ -518,18 +473,18 @@ void main() { expect(find.byKey(childWidgetKey), findsOneWidget); expect( find.text(updatedMessageResponseList - .map((e) => '${e.channel.cid}-${e.message.id}') + .map((e) => '${e.channel?.cid}-${e.message.id}') .join(',')), findsOneWidget, ); - verify(mockClient.search( - any, - query: anyNamed('query'), - sort: anyNamed('sort'), - messageFilters: anyNamed('messageFilters'), - paginationParams: updatedPagination, - )).called(1); + verify(() => mockClient.search( + any(), + query: any(named: 'query'), + sort: any(named: 'sort'), + messageFilters: any(named: 'messageFilters'), + paginationParams: updatedPagination, + )).called(1); }, ); } diff --git a/packages/stream_chat_flutter_core/test/mocks.dart b/packages/stream_chat_flutter_core/test/mocks.dart index a46b7b5c..6ca24363 100644 --- a/packages/stream_chat_flutter_core/test/mocks.dart +++ b/packages/stream_chat_flutter_core/test/mocks.dart @@ -1,19 +1,20 @@ -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/stream_chat.dart'; class MockLogger extends Mock implements Logger {} class MockClient extends Mock implements StreamChatClient { + @override final Logger logger = MockLogger(); - ClientState _state; + ClientState? _state; @override ClientState get state => _state ??= MockClientState(); } class MockClientState extends Mock implements ClientState { - OwnUser _user; + OwnUser? _user; @override OwnUser get user => _user ??= OwnUser( @@ -25,12 +26,12 @@ class MockClientState extends Mock implements ClientState { } class MockChannel extends Mock implements Channel { - ChannelClientState _state; + ChannelClientState? _state; @override ChannelClientState get state => _state ??= MockChannelState(); - StreamChatClient _client; + StreamChatClient? _client; @override StreamChatClient get client => _client ??= MockClient(); diff --git a/packages/stream_chat_flutter_core/test/stream_channel_test.dart b/packages/stream_chat_flutter_core/test/stream_channel_test.dart index ef8cb394..b48c02b1 100644 --- a/packages/stream_chat_flutter_core/test/stream_channel_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_channel_test.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'mocks.dart'; @@ -62,39 +62,13 @@ void main() { return threads ? threadMessages : messages; } - test( - 'should throw assertion error if child is null', - () async { - final mockChannel = MockChannel(); - const streamChannelKey = Key('streamChannel'); - final streamChannel = () => StreamChannel( - key: streamChannelKey, - channel: mockChannel, - child: null, - ); - expect(streamChannel, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error if channel is null', - () async { - const streamChannelKey = Key('streamChannel'); - final streamChannel = () => StreamChannel( - key: streamChannelKey, - child: Offstage(), - channel: null, - ); - expect(streamChannel, throwsA(isA())); - }, - ); - testWidgets( 'should render StreamChannel if both channel and child is provided', (tester) async { final mockChannel = MockChannel(); const streamChannelKey = Key('streamChannel'); const childKey = Key('childKey'); + when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true)); final streamChannel = StreamChannel( key: streamChannelKey, channel: mockChannel, @@ -121,8 +95,13 @@ void main() { ); final errorMessage = 'Error! Error! Error!'; - final error = DioError(type: DioErrorType.response, error: errorMessage); - when(mockChannel.initialized).thenAnswer((_) => Future.error(error)); + final error = DioError( + type: DioErrorType.response, + error: errorMessage, + requestOptions: RequestOptions(path: ''), + ); + when(() => mockChannel.initialized) + .thenAnswer((_) => Future.error(error)); await tester.pumpWidget( Directionality( @@ -135,7 +114,7 @@ void main() { expect(find.text(errorMessage), findsOneWidget); - verify(mockChannel.initialized).called(1); + verify(() => mockChannel.initialized).called(1); }, ); @@ -153,7 +132,7 @@ void main() { showLoading: true, ); - when(mockChannel.initialized).thenAnswer((_) async => false); + when(() => mockChannel.initialized).thenAnswer((_) async => false); await tester.pumpWidget( Directionality( @@ -166,7 +145,7 @@ void main() { expect(find.byType(CircularProgressIndicator), findsOneWidget); - verify(mockChannel.initialized).called(1); + verify(() => mockChannel.initialized).called(1); }, ); @@ -183,15 +162,15 @@ void main() { initialMessageId: 'testInitialMessageId', ); - when(mockChannel.initialized).thenAnswer((_) async => true); + when(() => mockChannel.initialized).thenAnswer((_) async => true); final messages = _generateMessages(); - when(mockChannel.query( - options: anyNamed('options'), - messagesPagination: anyNamed('messagesPagination'), - membersPagination: anyNamed('membersPagination'), - watchersPagination: anyNamed('watchersPagination'), - preferOffline: anyNamed('preferOffline'), - )).thenAnswer((_) async => ChannelState(messages: messages)); + when(() => mockChannel.query( + options: any(named: 'options'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + preferOffline: any(named: 'preferOffline'), + )).thenAnswer((_) async => ChannelState(messages: messages)); await tester.pumpWidget( Directionality( @@ -202,14 +181,14 @@ void main() { await tester.pumpAndSettle(); - verify(mockChannel.initialized).called(1); - verify(mockChannel.query( - options: anyNamed('options'), - messagesPagination: anyNamed('messagesPagination'), - membersPagination: anyNamed('membersPagination'), - watchersPagination: anyNamed('watchersPagination'), - preferOffline: anyNamed('preferOffline'), - )).called( + verify(() => mockChannel.initialized).called(1); + verify(() => mockChannel.query( + options: any(named: 'options'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + preferOffline: any(named: 'preferOffline'), + )).called( 2, // Fetching After messages + Fetching Before messages, ); }, @@ -219,7 +198,7 @@ void main() { 'should rebuild StreamChannel with updated widget data ' 'on calling setState()', (tester) async { - StateSetter _stateSetter; + StateSetter? _stateSetter; var initialMessageId = 'testInitialMessageId'; @@ -244,25 +223,25 @@ void main() { limit: 20, ); - when(mockChannel.initialized).thenAnswer((_) async => true); + when(() => mockChannel.initialized).thenAnswer((_) async => true); final messages = _generateMessages(); - when(mockChannel.query( - options: anyNamed('options'), - messagesPagination: beforePagination, - membersPagination: anyNamed('membersPagination'), - watchersPagination: anyNamed('watchersPagination'), - preferOffline: anyNamed('preferOffline'), - )).thenAnswer((_) async => ChannelState(messages: messages)); + when(() => mockChannel.query( + options: any(named: 'options'), + messagesPagination: beforePagination, + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + preferOffline: any(named: 'preferOffline'), + )).thenAnswer((_) async => ChannelState(messages: messages)); - when(mockChannel.query( - options: anyNamed('options'), - messagesPagination: afterPagination, - membersPagination: anyNamed('membersPagination'), - watchersPagination: anyNamed('watchersPagination'), - preferOffline: anyNamed('preferOffline'), - )).thenAnswer((_) async => ChannelState(messages: messages)); + when(() => mockChannel.query( + options: any(named: 'options'), + messagesPagination: afterPagination, + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + preferOffline: any(named: 'preferOffline'), + )).thenAnswer((_) async => ChannelState(messages: messages)); await tester.pumpWidget( Directionality( @@ -279,23 +258,23 @@ void main() { await tester.pumpAndSettle(); - verify(mockChannel.query( - options: anyNamed('options'), - messagesPagination: beforePagination, - membersPagination: anyNamed('membersPagination'), - watchersPagination: anyNamed('watchersPagination'), - preferOffline: anyNamed('preferOffline'), - )).called(1); + verify(() => mockChannel.query( + options: any(named: 'options'), + messagesPagination: beforePagination, + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + preferOffline: any(named: 'preferOffline'), + )).called(1); - verify(mockChannel.query( - options: anyNamed('options'), - messagesPagination: afterPagination, - membersPagination: anyNamed('membersPagination'), - watchersPagination: anyNamed('watchersPagination'), - preferOffline: anyNamed('preferOffline'), - )).called(1); + verify(() => mockChannel.query( + options: any(named: 'options'), + messagesPagination: afterPagination, + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + preferOffline: any(named: 'preferOffline'), + )).called(1); - _stateSetter(() => initialMessageId = 'testInitialMessageId2'); + _stateSetter?.call(() => initialMessageId = 'testInitialMessageId2'); final updatedBeforePagination = beforePagination.copyWith( lessThan: initialMessageId, @@ -305,39 +284,39 @@ void main() { greaterThanOrEqual: initialMessageId, ); - when(mockChannel.query( - options: anyNamed('options'), - messagesPagination: updatedBeforePagination, - membersPagination: anyNamed('membersPagination'), - watchersPagination: anyNamed('watchersPagination'), - preferOffline: anyNamed('preferOffline'), - )).thenAnswer((_) async => ChannelState(messages: messages)); + when(() => mockChannel.query( + options: any(named: 'options'), + messagesPagination: updatedBeforePagination, + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + preferOffline: any(named: 'preferOffline'), + )).thenAnswer((_) async => ChannelState(messages: messages)); - when(mockChannel.query( - options: anyNamed('options'), - messagesPagination: updatedAfterPagination, - membersPagination: anyNamed('membersPagination'), - watchersPagination: anyNamed('watchersPagination'), - preferOffline: anyNamed('preferOffline'), - )).thenAnswer((_) async => ChannelState(messages: messages)); + when(() => mockChannel.query( + options: any(named: 'options'), + messagesPagination: updatedAfterPagination, + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + preferOffline: any(named: 'preferOffline'), + )).thenAnswer((_) async => ChannelState(messages: messages)); await tester.pumpAndSettle(); - verify(mockChannel.query( - options: anyNamed('options'), - messagesPagination: updatedBeforePagination, - membersPagination: anyNamed('membersPagination'), - watchersPagination: anyNamed('watchersPagination'), - preferOffline: anyNamed('preferOffline'), - )).called(1); + verify(() => mockChannel.query( + options: any(named: 'options'), + messagesPagination: updatedBeforePagination, + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + preferOffline: any(named: 'preferOffline'), + )).called(1); - verify(mockChannel.query( - options: anyNamed('options'), - messagesPagination: updatedAfterPagination, - membersPagination: anyNamed('membersPagination'), - watchersPagination: anyNamed('watchersPagination'), - preferOffline: anyNamed('preferOffline'), - )).called(1); + verify(() => mockChannel.query( + options: any(named: 'options'), + messagesPagination: updatedAfterPagination, + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + preferOffline: any(named: 'preferOffline'), + )).called(1); }, ); } diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index 4adc0062..bc5324b8 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'mocks.dart'; @@ -12,29 +12,6 @@ class MockOnBackgroundEventReceived extends Mock { } void main() { - test( - 'should throw assertion error in case client is null', - () { - final streamChatCore = () => StreamChatCore( - client: null, - child: Offstage(), - ); - expect(streamChatCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case child is null', - () { - final mockClient = MockClient(); - final streamChatCore = () => StreamChatCore( - client: mockClient, - child: null, - ); - expect(streamChatCore, throwsA(isA())); - }, - ); - testWidgets( 'should render StreamChatCore if both client and child is provided', (tester) async { @@ -92,7 +69,7 @@ void main() { expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget); - when(mockClient.disconnect()).thenAnswer((_) async { + when(() => mockClient.disconnect()).thenAnswer((_) async { return; }); @@ -102,7 +79,7 @@ void main() { streamChatCoreState.didChangeAppLifecycleState(AppLifecycleState.paused); - verify(mockClient.disconnect()).called(1); + verify(() => mockClient.disconnect()).called(1); }, ); @@ -131,8 +108,8 @@ void main() { expect(find.byKey(childKey), findsOneWidget); final event = Event(); - when(mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(mockClient.disconnect()).thenAnswer((_) async { + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(() => mockClient.disconnect()).thenAnswer((_) async { return; }); @@ -143,14 +120,14 @@ void main() { streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); - await untilCalled(mockOnBackgroundEventReceived.call(event)); + await untilCalled(() => mockOnBackgroundEventReceived.call(event)); - verify(mockOnBackgroundEventReceived.call(event)).called(1); + verify(() => mockOnBackgroundEventReceived.call(event)).called(1); await Future.delayed(backgroundKeepAlive); - verify(mockClient.disconnect()).called(1); - verifyNever(mockOnBackgroundEventReceived.call(event)); + verify(() => mockClient.disconnect()).called(1); + verifyNever(() => mockOnBackgroundEventReceived.call(event)); }); }, ); @@ -180,7 +157,7 @@ void main() { expect(find.byKey(childKey), findsOneWidget); final event = Event(); - when(mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); final streamChatCoreState = tester.state( find.byKey(streamChatCoreKey), @@ -189,14 +166,14 @@ void main() { streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); - await untilCalled(mockOnBackgroundEventReceived.call(event)); + await untilCalled(() => mockOnBackgroundEventReceived.call(event)); - verify(mockOnBackgroundEventReceived.call(event)).called(1); + verify(() => mockOnBackgroundEventReceived.call(event)).called(1); streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.resumed); - verifyNever(mockOnBackgroundEventReceived.call(event)); + verifyNever(() => mockOnBackgroundEventReceived.call(event)); }); }, ); @@ -222,9 +199,10 @@ void main() { expect(find.byKey(childKey), findsOneWidget); final event = Event(); - when(mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(mockClient.connect()).thenAnswer((_) async => event); - when(mockClient.wsConnectionStatus) + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(() => mockClient.connect()).thenAnswer((_) async => event); + when(mockClient.disconnect).thenAnswer((_) async => null); + when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); final streamChatCoreState = tester.state( @@ -239,7 +217,7 @@ void main() { streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.resumed); - verify(mockClient.connect()).called(1); + verify(() => mockClient.connect()).called(1); }); }, ); @@ -265,7 +243,7 @@ void main() { expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget); - when(mockClient.state.userStream) + when(() => mockClient.state.userStream) .thenAnswer((_) => userController.stream); final streamChatCoreState = tester.state( diff --git a/packages/stream_chat_flutter_core/test/user_list_core_test.dart b/packages/stream_chat_flutter_core/test/user_list_core_test.dart index d1c7e83a..a0f8bd51 100644 --- a/packages/stream_chat_flutter_core/test/user_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/user_list_core_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter_core/src/user_list_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -33,58 +33,6 @@ void main() { ); } - test( - 'should throw assertion error in case listBuilder is null', - () { - final userListCore = () => UserListCore( - listBuilder: null, - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (Object error) => Offstage(), - ); - expect(userListCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case loadingBuilder is null', - () { - final userListCore = () => UserListCore( - listBuilder: (_, __) => Offstage(), - loadingBuilder: null, - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (Object error) => Offstage(), - ); - expect(userListCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case emptyBuilder is null', - () { - final userListCore = () => UserListCore( - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: null, - errorBuilder: (Object error) => Offstage(), - ); - expect(userListCore, throwsA(isA())); - }, - ); - - test( - 'should throw assertion error in case errorBuilder is null', - () { - final userListCore = () => UserListCore( - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: null, - ); - expect(userListCore, throwsA(isA())); - }, - ); - testWidgets( 'should throw if UserListCore is used where UsersBloc is not present ' 'in the widget tree', @@ -183,12 +131,12 @@ void main() { final mockClient = MockClient(); const error = 'Error! Error! Error!'; - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).thenThrow(error); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).thenThrow(error); await tester.pumpWidget( StreamChatCore( @@ -203,12 +151,12 @@ void main() { expect(find.byKey(errorWidgetKey), findsOneWidget); - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).called(1); }, ); @@ -228,12 +176,12 @@ void main() { final mockClient = MockClient(); const users = []; - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).thenAnswer((_) async => QueryUsersResponse()..users = users); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); await tester.pumpWidget( StreamChatCore( @@ -248,12 +196,12 @@ void main() { expect(find.byKey(emptyWidgetKey), findsOneWidget); - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).called(1); }, ); @@ -273,12 +221,12 @@ void main() { final mockClient = MockClient(); final users = _generateUsers(); - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).thenAnswer((_) async => QueryUsersResponse()..users = users); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); await tester.pumpWidget( StreamChatCore( @@ -293,12 +241,12 @@ void main() { expect(find.byKey(listWidgetKey), findsOneWidget); - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).called(1); }, ); @@ -314,7 +262,7 @@ void main() { child: ListView( children: items.map((e) { return Container( - key: Key(e.key), + key: Key(e.key ?? ''), child: e.when( headerItem: (heading) => Text(heading), userItem: (user) => Text(user.id), @@ -332,12 +280,12 @@ void main() { final mockClient = MockClient(); final users = _generateUsers(); - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).thenAnswer((_) async => QueryUsersResponse()..users = users); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); await tester.pumpWidget( Directionality( @@ -359,12 +307,12 @@ void main() { expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); } - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).called(1); }, ); @@ -382,7 +330,7 @@ void main() { child: ListView( children: items.map((e) { return Container( - key: Key(e.key), + key: Key(e.key ?? ''), child: e.when( headerItem: (heading) => Text(heading), userItem: (user) => Text(user.id), @@ -401,12 +349,12 @@ void main() { final mockClient = MockClient(); final users = _generateUsers(); - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).thenAnswer((_) async => QueryUsersResponse()..users = users); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); await tester.pumpWidget( Directionality( @@ -428,12 +376,12 @@ void main() { expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); } - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).called(1); final userListCoreState = tester.state( find.byKey(userListCoreKey), @@ -442,12 +390,14 @@ void main() { final offset = users.length; final paginatedUsers = _generateUsers(offset: offset); final updatedPagination = pagination.copyWith(offset: offset); - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: updatedPagination, - )).thenAnswer((_) async => QueryUsersResponse()..users = paginatedUsers); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: updatedPagination, + )) + .thenAnswer( + (_) async => QueryUsersResponse()..users = paginatedUsers); await userListCoreState.paginateData(); @@ -458,12 +408,12 @@ void main() { expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); } - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: updatedPagination, - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: updatedPagination, + )).called(1); }, ); @@ -473,7 +423,7 @@ void main() { (tester) async { const pagination = PaginationParams(); - StateSetter _stateSetter; + StateSetter? _stateSetter; int limit = pagination.limit; const userListCoreKey = Key('userListCore'); @@ -485,7 +435,7 @@ void main() { child: ListView( children: items.map((e) { return Container( - key: Key(e.key), + key: Key(e.key ?? ''), child: e.when( headerItem: (heading) => Text(heading), userItem: (user) => Text(user.id), @@ -504,12 +454,12 @@ void main() { final mockClient = MockClient(); final users = _generateUsers(); - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).thenAnswer((_) async => QueryUsersResponse()..users = users); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); await tester.pumpWidget( Directionality( @@ -535,24 +485,25 @@ void main() { expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); } - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).called(1); // Rebuilding UserListCore with new pagination limit - _stateSetter(() => limit = 6); + _stateSetter?.call(() => limit = 6); final updatedUsers = _generateUsers(count: limit); final updatedPagination = pagination.copyWith(limit: limit); - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: updatedPagination, - )).thenAnswer((_) async => QueryUsersResponse()..users = updatedUsers); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: updatedPagination, + )) + .thenAnswer((_) async => QueryUsersResponse()..users = updatedUsers); await tester.pumpAndSettle(); @@ -561,12 +512,12 @@ void main() { expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); } - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: updatedPagination, - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: updatedPagination, + )).called(1); }, ); } diff --git a/packages/stream_chat_flutter_core/test/users_bloc_test.dart b/packages/stream_chat_flutter_core/test/users_bloc_test.dart index e9ccd2e7..8c348211 100644 --- a/packages/stream_chat_flutter_core/test/users_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/users_bloc_test.dart @@ -1,9 +1,9 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; import 'package:stream_chat_flutter_core/src/users_bloc.dart'; -import 'package:mockito/mockito.dart'; import 'matchers/users_matcher.dart'; import 'mocks.dart'; @@ -31,18 +31,6 @@ void main() { ); } - test( - 'should throw assertion error if child is null', - () async { - const usersBlocKey = Key('usersBloc'); - final usersBloc = () => UsersBloc( - key: usersBlocKey, - child: null, - ); - expect(usersBloc, throwsA(isA())); - }, - ); - testWidgets( 'usersBlocState.queryUsers() should throw if used where ' 'StreamChat is not present in the widget tree', @@ -96,12 +84,12 @@ void main() { final users = _generateUsers(); - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).thenAnswer((_) async => QueryUsersResponse()..users = users); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); usersBlocState.queryUsers(); @@ -110,12 +98,12 @@ void main() { emits(isSameUserListAs(users)), ); - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).called(1); }, ); @@ -145,12 +133,12 @@ void main() { final error = 'Error! Error! Error!'; - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).thenThrow(error); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).thenThrow(error); usersBlocState.queryUsers(); @@ -159,12 +147,12 @@ void main() { emitsError(error), ); - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).called(1); }, ); @@ -195,12 +183,12 @@ void main() { final users = _generateUsers(); - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).thenAnswer((_) async => QueryUsersResponse()..users = users); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); usersBlocState.queryUsers(); @@ -209,23 +197,25 @@ void main() { emits(isSameUserListAs(users)), ); - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).called(1); final offset = users.length; final paginatedUsers = _generateUsers(offset: offset); final pagination = PaginationParams(offset: offset); - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: pagination, - )).thenAnswer((_) async => QueryUsersResponse()..users = paginatedUsers); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: pagination, + )) + .thenAnswer( + (_) async => QueryUsersResponse()..users = paginatedUsers); usersBlocState.queryUsers(pagination: pagination); @@ -240,12 +230,12 @@ void main() { ), ]); - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: pagination, - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: pagination, + )).called(1); }, ); @@ -276,12 +266,12 @@ void main() { final users = _generateUsers(); - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).thenAnswer((_) async => QueryUsersResponse()..users = users); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); usersBlocState.queryUsers(); @@ -290,24 +280,24 @@ void main() { emits(isSameUserListAs(users)), ); - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: anyNamed('pagination'), - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: any(named: 'pagination'), + )).called(1); final offset = users.length; final pagination = PaginationParams(offset: offset); final error = 'Error! Error! Error!'; - when(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: pagination, - )).thenThrow(error); + when(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: pagination, + )).thenThrow(error); usersBlocState.queryUsers(pagination: pagination); @@ -316,12 +306,12 @@ void main() { emitsError(error), ); - verify(mockClient.queryUsers( - filter: anyNamed('filter'), - sort: anyNamed('sort'), - options: anyNamed('options'), - pagination: pagination, - )).called(1); + verify(() => mockClient.queryUsers( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + options: any(named: 'options'), + pagination: pagination, + )).called(1); }, ); } From bc9e0d0285133e8fa5c505704e9ad028d2daf040 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 12:44:22 +0200 Subject: [PATCH 133/599] migrate code --- packages/stream_chat/lib/src/api/channel.dart | 88 +++++++++---------- .../lib/src/models/channel_model.dart | 2 +- packages/stream_chat/lib/src/models/user.dart | 16 +--- packages/stream_chat/lib/stream_chat.dart | 1 + 4 files changed, 47 insertions(+), 60 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 63fc5595..cc205772 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -81,7 +81,7 @@ class Channel { /// Channel configuration as a stream Stream? get configStream { _checkInitialized(); - return state?.channelStateStream.map((cs) => cs!.channel?.config); + return state?.channelStateStream.map((cs) => cs.channel?.config); } /// Channel user creator @@ -93,7 +93,7 @@ class Channel { /// Channel user creator as a stream Stream? get createdByStream { _checkInitialized(); - return state?.channelStateStream.map((cs) => cs!.channel?.createdBy); + return state?.channelStateStream.map((cs) => cs.channel?.createdBy); } /// Channel frozen status @@ -105,7 +105,7 @@ class Channel { /// Channel frozen status as a stream Stream? get frozenStream { _checkInitialized(); - return state?.channelStateStream.map((cs) => cs!.channel?.frozen); + return state?.channelStateStream.map((cs) => cs.channel?.frozen); } /// Channel creation date @@ -117,7 +117,7 @@ class Channel { /// Channel creation date as a stream Stream? get createdAtStream { _checkInitialized(); - return state?.channelStateStream.map((cs) => cs!.channel?.createdAt); + return state?.channelStateStream.map((cs) => cs.channel?.createdAt); } /// Channel last message date @@ -131,7 +131,7 @@ class Channel { Stream? get lastMessageAtStream { _checkInitialized(); - return state?.channelStateStream.map((cs) => cs!.channel?.lastMessageAt); + return state?.channelStateStream.map((cs) => cs.channel?.lastMessageAt); } /// Channel updated date @@ -145,7 +145,7 @@ class Channel { Stream? get updatedAtStream { _checkInitialized(); - return state?.channelStateStream.map((cs) => cs!.channel?.updatedAt); + return state?.channelStateStream.map((cs) => cs.channel?.updatedAt); } /// Channel deletion date @@ -159,7 +159,7 @@ class Channel { Stream? get deletedAtStream { _checkInitialized(); - return state?.channelStateStream.map((cs) => cs!.channel?.deletedAt); + return state?.channelStateStream.map((cs) => cs.channel?.deletedAt); } /// Channel member count @@ -173,7 +173,7 @@ class Channel { Stream? get memberCountStream { _checkInitialized(); - return state?.channelStateStream.map((cs) => cs!.channel?.memberCount); + return state?.channelStateStream.map((cs) => cs.channel?.memberCount); } /// Channel id @@ -195,7 +195,7 @@ class Channel { /// Channel extra data as a stream Stream?>? get extraDataStream { _checkInitialized(); - return state?.channelStateStream.map((cs) => cs!.channel?.extraData); + return state?.channelStateStream.map((cs) => cs.channel?.extraData); } /// The main Stream chat client @@ -842,7 +842,7 @@ class Channel { messages: state?.messages?..remove(oldMessage), )); } else { - oldMessage = state!.threads!.values + oldMessage = state!.threads.values .expand((messages) => messages) .firstWhereOrNull((m) => m.id == messageId); if (oldMessage?.parentId != null) { @@ -853,8 +853,8 @@ class Channel { state!.addMessage(parentMessage.copyWith( replyCount: parentMessage.replyCount! - 1)); } - state!.updateThreadInfo(oldMessage!.parentId, - state!.threads![oldMessage.parentId!]!..remove(oldMessage)); + state!.updateThreadInfo(oldMessage!.parentId!, + state!.threads[oldMessage.parentId!]!..remove(oldMessage)); } } @@ -1333,7 +1333,7 @@ class ChannelClientState { final _subscriptions = []; void _computeInitialUnread() { - final userRead = channelState?.read.firstWhereOrNull( + final userRead = channelState.read.firstWhereOrNull( (r) => r.user.id == _channel._client.state.user?.id, ); if (userRead != null) { @@ -1372,9 +1372,9 @@ class ChannelClientState { void _listenMemberAdded() { _subscriptions.add(_channel.on(EventType.memberAdded).listen((Event e) { final member = e.member; - updateChannelState(channelState!.copyWith( + updateChannelState(channelState.copyWith( members: [ - ...channelState!.members, + ...channelState.members, member!, ], )); @@ -1384,9 +1384,9 @@ class ChannelClientState { void _listenMemberRemoved() { _subscriptions.add(_channel.on(EventType.memberRemoved).listen((Event e) { final user = e.user; - updateChannelState(channelState!.copyWith( + updateChannelState(channelState.copyWith( members: List.from( - channelState!.members..removeWhere((m) => m.userId == user!.id)), + channelState.members..removeWhere((m) => m.userId == user!.id)), )); })); } @@ -1394,7 +1394,7 @@ class ChannelClientState { void _listenChannelUpdated() { _subscriptions.add(_channel.on(EventType.channelUpdated).listen((Event e) { final channel = e.channel!; - updateChannelState(channelState!.copyWith( + updateChannelState(channelState.copyWith( channel: channel, members: channel.members, )); @@ -1416,14 +1416,14 @@ class ChannelClientState { /// This flag should be managed by UI sdks. /// When false, any new message (received by WebSocket event /// - [EventType.messageNew]) will not be pushed on to message list. - bool get isUpToDate => _isUpToDateController.value!; + bool get isUpToDate => _isUpToDateController.value ?? true; set isUpToDate(bool isUpToDate) => _isUpToDateController.add(isUpToDate); /// [isUpToDate] flag count as a stream - Stream get isUpToDateStream => _isUpToDateController.stream; + Stream get isUpToDateStream => _isUpToDateController.stream; - final BehaviorSubject _isUpToDateController = + final BehaviorSubject _isUpToDateController = BehaviorSubject.seeded(true); /// The retry queue associated to this channel @@ -1432,7 +1432,7 @@ class ChannelClientState { /// Retry failed message Future retryFailedMessages() async { final failedMessages = - [...messages, ...threads!.values.expand((v) => v)] + [...messages, ...threads.values.expand((v) => v)] .where( (message) => message.status != MessageSendingStatus.sent && @@ -1549,7 +1549,7 @@ class ChannelClientState { } if (message.parentId != null) { - updateThreadInfo(message.parentId, [message]); + updateThreadInfo(message.parentId!, [message]); } } @@ -1592,14 +1592,14 @@ class ChannelClientState { /// Channel message list as a stream Stream?> get messagesStream => - channelStateStream.map((cs) => cs!.messages); + channelStateStream.map((cs) => cs.messages); /// Channel pinned message list List? get pinnedMessages => _channelState.pinnedMessages.toList(); /// Channel pinned message list as a stream Stream?> get pinnedMessagesStream => - channelStateStream.map((cs) => cs!.pinnedMessages.toList()); + channelStateStream.map((cs) => cs.pinnedMessages.toList()); /// Get channel last message Message? get lastMessage => _channelState.messages.isNotEmpty == true @@ -1618,7 +1618,7 @@ class ChannelClientState { /// Channel members list as a stream Stream> get membersStream => CombineLatestStream.combine2< List?, Map, List>( - channelStateStream.map((cs) => cs!.members), + channelStateStream.map((cs) => cs.members), _channel.client.state.usersStream, (members, users) => members!.map((e) => e!.copyWith(user: users[e.user!.id])).toList(), @@ -1629,7 +1629,7 @@ class ChannelClientState { /// Channel watcher count as a stream Stream get watcherCountStream => - channelStateStream.map((cs) => cs!.watcherCount); + channelStateStream.map((cs) => cs.watcherCount); /// Channel watchers list List get watchers => _channelState.watchers @@ -1639,7 +1639,7 @@ class ChannelClientState { /// Channel watchers list as a stream Stream> get watchersStream => CombineLatestStream.combine2< List?, Map, List>( - channelStateStream.map((cs) => cs!.watchers), + channelStateStream.map((cs) => cs.watchers), _channel.client.state.usersStream, (watchers, users) => watchers!.map((e) => users[e.id] ?? e).toList(), ); @@ -1648,8 +1648,7 @@ class ChannelClientState { List? get read => _channelState.read; /// Channel read list as a stream - Stream?> get readStream => - channelStateStream.map((cs) => cs!.read); + Stream?> get readStream => channelStateStream.map((cs) => cs.read); final BehaviorSubject _unreadCountController = BehaviorSubject.seeded(0); @@ -1672,17 +1671,17 @@ class ChannelClientState { } /// Update threads with updated information about messages - void updateThreadInfo(String? parentId, List? messages) { - final newThreads = Map?>.from(threads!); + void updateThreadInfo(String parentId, List messages) { + final newThreads = Map>.from(threads); if (newThreads.containsKey(parentId)) { newThreads[parentId] = [ ...newThreads[parentId] - ?.where((newMessage) => - !messages!.any((m) => m.id == newMessage.id)) + ?.where( + (newMessage) => !messages.any((m) => m.id == newMessage.id)) .toList() ?? [], - ...messages!, + ...messages, ]; newThreads[parentId]! @@ -1768,11 +1767,10 @@ class ChannelClientState { ChannelState get _channelState => _channelStateController.value!; /// The channel state related to this client as a stream - Stream get channelStateStream => - _channelStateController.stream; + Stream get channelStateStream => _channelStateController.stream; /// The channel state related to this client - ChannelState? get channelState => _channelStateController.value; + ChannelState get channelState => _channelStateController.value!; late BehaviorSubject _channelStateController; final Debounce _debouncedUpdatePersistenceChannelState; @@ -1783,19 +1781,19 @@ class ChannelClientState { } /// The channel threads related to this channel - Map>? get threads => _threadsController.value - ?.map((key, value) => MapEntry(key ?? '', value ?? [])); + Map> get threads => + _threadsController.value!.map((key, value) => MapEntry(key, value)); /// The channel threads related to this channel as a stream - Stream?>> get threadsStream => + Stream>> get threadsStream => _threadsController.stream; - final BehaviorSubject?>> _threadsController = + final BehaviorSubject>> _threadsController = BehaviorSubject.seeded({}); - set _threads(Map?> v) { + set _threads(Map> v) { _channel._client.chatPersistenceClient?.updateMessages( _channel.cid!, - v.values.expand((v) => v!).toList(), + v.values.expand((v) => v).toList(), ); _threadsController.add(v); } @@ -1888,7 +1886,7 @@ class ChannelClientState { void _startCleaningPinnedMessages() { _pinnedMessagesTimer = Timer.periodic(const Duration(seconds: 30), (_) { final now = DateTime.now(); - var expiredMessages = channelState!.pinnedMessages + var expiredMessages = channelState.pinnedMessages .where((m) => m.pinExpires?.isBefore(now) == true) .toList(); if (expiredMessages.isNotEmpty) { diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index d68edbde..2bc17761 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -27,7 +27,7 @@ class ChannelModel { createdAt = createdAt ?? DateTime.now(), updatedAt = updatedAt ?? DateTime.now(), assert( - cid != null || (id != null && type != null), + (cid != null && cid.contains(':')) || (id != null && type != null), 'provide either a cid or an id and type', ), id = id ?? cid!.split(':')[1], diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/models/user.dart index 67fc99f0..3f4c37ef 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/models/user.dart @@ -8,7 +8,7 @@ part 'user.g.dart'; class User { /// Constructor used for json serialization User({ - this.id = '', + required this.id, this.role = '', DateTime? createdAt, DateTime? updatedAt, @@ -24,18 +24,6 @@ class User { factory User.fromJson(Map json) => _$UserFromJson( Serialization.moveToExtraDataFromRoot(json, topLevelFields)); - /// Use this named constructor to create a new user instance - User.init( - this.id, { - this.online = false, - this.extraData = const {}, - required this.createdAt, - required this.updatedAt, - this.teams = const [], - required this.role, - }) : lastActive = null, - banned = false; - /// Known top level fields. /// Useful for [Serialization] methods. static const topLevelFields = [ @@ -94,7 +82,7 @@ class User { int get hashCode => id.hashCode; /// Shortcut for user name - String? get name => + String get name => (extraData.containsKey('name') == true && extraData['name'] != '') ? extraData['name'] : id; diff --git a/packages/stream_chat/lib/stream_chat.dart b/packages/stream_chat/lib/stream_chat.dart index f491c39a..ef146180 100644 --- a/packages/stream_chat/lib/stream_chat.dart +++ b/packages/stream_chat/lib/stream_chat.dart @@ -3,6 +3,7 @@ library stream_chat; export 'package:async/async.dart'; export 'package:dio/src/dio_error.dart'; export 'package:dio/src/multipart_file.dart'; +export 'package:dio/src/options.dart'; export 'package:dio/src/options.dart' show ProgressCallback; export 'package:logging/logging.dart' show Logger, Level; From 4c4473d4b5d29fd1a518a1571e792533e8420a7a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 13:14:07 +0200 Subject: [PATCH 134/599] migrate sample app --- packages/stream_chat/lib/src/api/channel.dart | 54 ++++++++---------- packages/stream_chat/lib/src/client.dart | 11 ++-- .../test/src/api/channel_test.dart | 18 +++--- .../analysis_options.yaml | 1 + .../example/lib/main.dart | 57 ++++++++++--------- .../example/pubspec.yaml | 2 +- .../lib/src/channel_list_core.dart | 1 + .../test/channels_bloc_test.dart | 4 +- 8 files changed, 77 insertions(+), 71 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index cc205772..7389448f 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -27,7 +27,7 @@ class Channel { } /// Create a channel client instance from a [ChannelState] object - Channel.fromState(this._client, ChannelState channelState) { + Channel.fromState(this._client, ChannelState channelState) : _extraData = {} { _cid = channelState.channel!.cid; _id = channelState.channel!.id; type = channelState.channel!.type; @@ -45,9 +45,9 @@ class Channel { String? _id; String? _cid; - Map? _extraData; + Map _extraData; - set extraData(Map? extraData) { + set extraData(Map extraData) { if (_initializedCompleter.isCompleted) { throw Exception( 'Once the channel is initialized you should use channel.update ' @@ -189,7 +189,7 @@ class Channel { } /// Channel extra data - Map? get extraData => + Map get extraData => state?._channelState.channel?.extraData ?? _extraData; /// Channel extra data as a stream @@ -1028,7 +1028,7 @@ class Channel { }) ..addAll(options); - if (_extraData != null) { + if (_extraData.isNotEmpty) { payload['data'] = _extraData; } @@ -1684,8 +1684,7 @@ class ChannelClientState { ...messages, ]; - newThreads[parentId]! - .sort(_sortByCreatedAt as int Function(Message, Message)?); + newThreads[parentId]!.sort(_sortByCreatedAt); } else { newThreads[parentId] = messages; } @@ -1712,7 +1711,7 @@ class ChannelClientState { .any((newMessage) => newMessage.id == m.id) != true) .toList(), - ]..sort(_sortByCreatedAt as int Function(Message, Message)?); + ]..sort(_sortByCreatedAt); final newWatchers = [ ...updatedState.watchers, @@ -1751,17 +1750,8 @@ class ChannelClientState { ); } - int? _sortByCreatedAt(a, b) { - if (a.createdAt == null) { - return 1; - } - - if (b.createdAt == null) { - return -1; - } - - return a.createdAt.compareTo(b.createdAt); - } + int _sortByCreatedAt(Message a, Message b) => + a.createdAt.compareTo(b.createdAt); /// The channel state related to this client ChannelState get _channelState => _channelStateController.value!; @@ -1799,15 +1789,15 @@ class ChannelClientState { } /// Channel related typing users last value - List? get typingEvents => _typingEventsController.value as List?; + List get typingEvents => _typingEventsController.value!; /// Channel related typing users stream - Stream> get typingEventsStream => _typingEventsController.stream; - final BehaviorSubject> _typingEventsController = + Stream> get typingEventsStream => _typingEventsController.stream; + final BehaviorSubject> _typingEventsController = BehaviorSubject.seeded([]); final Channel _channel; - final Map _typings = {}; + final Map _typings = {}; void _listenTypingEvents() { if (_channelState.channel?.config.typingEvents == false) { @@ -1818,9 +1808,12 @@ class ChannelClientState { ..add( _channel.on(EventType.typingStart).listen( (event) { - if (event.user!.id != _channel.client.state.user!.id) { - _typings[event.user] = DateTime.now(); - _typingEventsController.add(_typings.keys.toList()); + if (event.user != null) { + final user = event.user!; + if (user.id != _channel.client.state.user?.id) { + _typings[user] = DateTime.now(); + _typingEventsController.add(_typings.keys.toList()); + } } }, ), @@ -1828,9 +1821,12 @@ class ChannelClientState { ..add( _channel.on(EventType.typingStop).listen( (event) { - if (event.user!.id != _channel.client.state.user!.id) { - _typings.remove(event.user); - _typingEventsController.add(_typings.keys.toList()); + if (event.user != null) { + final user = event.user!; + if (user.id != _channel.client.state.user?.id) { + _typings.remove(event.user); + _typingEventsController.add(_typings.keys.toList()); + } } }, ), diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 403e5a2b..4689e9ec 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -780,10 +780,11 @@ class StreamChatClient { PaginationParams paginationParams = const PaginationParams(), }) async { final offlineChannels = (await _chatPersistenceClient?.getChannelStates( - filter: filter, - sort: sort, - paginationParams: paginationParams, - ))!; + filter: filter, + sort: sort, + paginationParams: paginationParams, + )) ?? + []; final updatedData = _mapChannelStateToChannel(offlineChannels); state.channels = updatedData.key; return updatedData.value; @@ -1193,7 +1194,7 @@ class StreamChatClient { Channel channel( String type, { String? id, - Map? extraData, + Map extraData = const {}, }) { if (id != null && state.channels?.containsKey('$type:$id') == true) { if (state.channels!['$type:$id'] != null) { diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 041a3e49..8f18d5e2 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -516,7 +516,7 @@ void main() { ); await channelClient.watch(); - final event = Event(type: EventType.any); + final event = const Event(type: EventType.any); when( () => mockDio.post( @@ -564,7 +564,7 @@ void main() { ); await channelClient.watch(); - final event = Event(type: EventType.typingStart); + final event = const Event(type: EventType.typingStart); when( () => mockDio.post( @@ -610,7 +610,7 @@ void main() { ); await channelClient.watch(); - final event = Event(type: EventType.typingStop); + final event = const Event(type: EventType.typingStop); when( () => mockDio.post( @@ -901,8 +901,10 @@ void main() { 'presence': true, }; - when(() => mockDio.post('/channels/messaging/query', - data: options)).thenAnswer( + when(() => mockDio.post( + '/channels/messaging/query', + data: options, + )).thenAnswer( (_) async => Response( data: r''' { @@ -1198,8 +1200,10 @@ void main() { final response = await channelClient.query(options: options); - verify(() => mockDio.post('/channels/messaging/query', - data: options)).called(1); + verify(() => mockDio.post( + '/channels/messaging/query', + data: options, + )).called(1); expect(channelClient.id, response.channel?.id); expect(channelClient.cid, response.channel?.cid); }); diff --git a/packages/stream_chat_flutter_core/analysis_options.yaml b/packages/stream_chat_flutter_core/analysis_options.yaml index 039d0230..545d5492 100644 --- a/packages/stream_chat_flutter_core/analysis_options.yaml +++ b/packages/stream_chat_flutter_core/analysis_options.yaml @@ -3,6 +3,7 @@ analyzer: - lib/**/*.g.dart - lib/**/*.freezed.dart - example/* + - test/* linter: rules: - always_use_package_imports diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index aa705db1..a9157e6e 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -38,8 +38,8 @@ class StreamExample extends StatelessWidget { /// If you'd prefer using pre-made UI widgets for your app, please see our other /// package, `stream_chat_flutter`. const StreamExample({ - Key key, - @required this.client, + Key? key, + required this.client, }) : super(key: key); /// Instance of Stream Client. @@ -55,7 +55,7 @@ class StreamExample extends StatelessWidget { home: HomeScreen(), builder: (context, child) => StreamChatCore( client: client, - child: child, + child: child!, ), ); } @@ -82,7 +82,7 @@ class HomeScreen extends StatelessWidget { 'type': 'messaging', 'members': { r'$in': [ - StreamChatCore.of(context).user.id, + StreamChatCore.of(context).user!.id, ] } }, @@ -100,10 +100,13 @@ class HomeScreen extends StatelessWidget { ), ); }, - errorBuilder: (BuildContext context, dynamic error) { + errorBuilder: ( + BuildContext context, + dynamic error, + ) { return Center( child: Text( - 'Oh no, something went wrong. Please check your config.'), + 'Oh no, something went wrong. Please check your config. ${error}'), ); }, listBuilder: ( @@ -112,20 +115,20 @@ class HomeScreen extends StatelessWidget { ) => LazyLoadScrollView( onEndOfPage: () async { - channelListController.paginateData(); + channelListController.paginateData!(); }, child: ListView.builder( itemCount: channels.length, itemBuilder: (BuildContext context, int index) { final _item = channels[index]; return ListTile( - title: Text(_item.name), - subtitle: StreamBuilder( - stream: _item.state.lastMessageStream, - initialData: _item.state.lastMessage, + title: Text(_item.name!), + subtitle: StreamBuilder( + stream: _item.state!.lastMessageStream, + initialData: _item.state!.lastMessage, builder: (context, snapshot) { if (snapshot.hasData) { - return Text(snapshot.data.text); + return Text(snapshot.data!.text!); } return SizedBox(); @@ -169,8 +172,8 @@ class MessageScreen extends StatefulWidget { } class _MessageScreenState extends State { - TextEditingController _controller; - ScrollController _scrollController; + late final TextEditingController _controller; + late final ScrollController _scrollController; final messageListController = MessageListController(); @override @@ -203,11 +206,11 @@ class _MessageScreenState extends State { return Scaffold( appBar: AppBar( title: StreamBuilder>( - initialData: channel.state.typingEvents, - stream: channel.state.typingEventsStream, + initialData: channel.state?.typingEvents, + stream: channel.state?.typingEventsStream, builder: (context, snapshot) { - if (snapshot.hasData && snapshot.data.isNotEmpty) { - return Text('${snapshot.data.first.name} is typing...'); + if (snapshot.hasData && snapshot.data!.isNotEmpty) { + return Text('${snapshot.data!.first.name} is typing...'); } return SizedBox(); }, @@ -219,7 +222,7 @@ class _MessageScreenState extends State { Expanded( child: LazyLoadScrollView( onEndOfPage: () async { - messageListController.paginateData(); + messageListController.paginateData!(); }, child: MessageListCore( emptyBuilder: (BuildContext context) { @@ -247,12 +250,12 @@ class _MessageScreenState extends State { itemBuilder: (BuildContext context, int index) { final item = messages[index]; final client = StreamChatCore.of(context).client; - if (item.user.id == client.uid) { + if (item.user!.id == client.uid) { return Align( alignment: Alignment.centerRight, child: Padding( padding: const EdgeInsets.all(8.0), - child: Text(item.text), + child: Text(item.text!), ), ); } else { @@ -260,7 +263,7 @@ class _MessageScreenState extends State { alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.all(8.0), - child: Text(item.text), + child: Text(item.text!), ), ); } @@ -268,7 +271,7 @@ class _MessageScreenState extends State { ); }, errorWidgetBuilder: (BuildContext context, error) { - print(error?.toString()); + print(error.toString()); return Center( child: SizedBox( height: 100.0, @@ -282,7 +285,7 @@ class _MessageScreenState extends State { ), ), Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Row( children: [ Expanded( @@ -308,7 +311,7 @@ class _MessageScreenState extends State { } }, child: const Padding( - padding: EdgeInsets.all(8.0), + padding: EdgeInsets.all(8), child: Center( child: Icon( Icons.send, @@ -332,12 +335,12 @@ class _MessageScreenState extends State { /// below, we add two simple extensions to the [StreamChatClient] and [Channel]. extension on StreamChatClient { /// Fetches the current user id. - String get uid => state.user.id; + String get uid => state.user!.id; } extension on Channel { /// Fetches the name of the channel by accessing [extraData] or [cid]. - String get name { + String? get name { final _channelName = extraData['name']; if (_channelName != null) { return _channelName; diff --git a/packages/stream_chat_flutter_core/example/pubspec.yaml b/packages/stream_chat_flutter_core/example/pubspec.yaml index 91cf4a01..e03eba6c 100644 --- a/packages/stream_chat_flutter_core/example/pubspec.yaml +++ b/packages/stream_chat_flutter_core/example/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 322a2780..992cae5d 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -133,6 +133,7 @@ class ChannelListCoreState extends State { stream: channelsBlocState.channelsStream, builder: (context, snapshot) { if (snapshot.hasError) { + print('snapshot: ${snapshot.stackTrace}'); return widget.errorBuilder(context, snapshot.error!); } if (!snapshot.hasData) { diff --git a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart index de8b0af1..28136add 100644 --- a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart @@ -779,8 +779,8 @@ void main() { final mockClient = MockClient(); final channels = _generateChannels(mockClient); int channelComparator(Channel a, Channel b) { - final aData = a.extraData!['extra_data_key'] as String; - final bData = b.extraData!['extra_data_key'] as String; + final aData = a.extraData['extra_data_key'] as String; + final bData = b.extraData['extra_data_key'] as String; return bData.compareTo(aData); } From 8f7106020fb0fd89503a8f29033fa67ad0b0e133 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 14:53:19 +0200 Subject: [PATCH 135/599] fix own user model --- packages/stream_chat/lib/src/models/own_user.dart | 4 ++-- packages/stream_chat/lib/src/models/own_user.g.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/models/own_user.dart b/packages/stream_chat/lib/src/models/own_user.dart index 3ce951cf..05936cdd 100644 --- a/packages/stream_chat/lib/src/models/own_user.dart +++ b/packages/stream_chat/lib/src/models/own_user.dart @@ -17,8 +17,8 @@ class OwnUser extends User { this.totalUnreadCount = 0, this.unreadChannels, this.channelMutes = const [], - String id = '', - String role = '', + required String id, + String? role, DateTime? createdAt, DateTime? updatedAt, DateTime? lastActive, diff --git a/packages/stream_chat/lib/src/models/own_user.g.dart b/packages/stream_chat/lib/src/models/own_user.g.dart index 785efb04..5683440d 100644 --- a/packages/stream_chat/lib/src/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/models/own_user.g.dart @@ -23,7 +23,7 @@ OwnUser _$OwnUserFromJson(Map json) { .toList() ?? [], id: json['id'] as String, - role: json['role'] as String, + role: json['role'] as String?, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), From 555ae57782cacf4af485d6432860d1f222f8df57 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 15:01:39 +0200 Subject: [PATCH 136/599] fix core example --- packages/stream_chat_flutter_core/example/lib/main.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index a9157e6e..de10574b 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -225,6 +225,7 @@ class _MessageScreenState extends State { messageListController.paginateData!(); }, child: MessageListCore( + messageListController: messageListController, emptyBuilder: (BuildContext context) { return Center( child: Text('Nothing here yet'), From 8f1f7b0b68b1391e2dba0065a2b95c7c6238b500 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 14:53:19 +0200 Subject: [PATCH 137/599] fix own user model --- packages/stream_chat/lib/src/models/own_user.dart | 4 ++-- packages/stream_chat/lib/src/models/own_user.g.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/models/own_user.dart b/packages/stream_chat/lib/src/models/own_user.dart index 3ce951cf..05936cdd 100644 --- a/packages/stream_chat/lib/src/models/own_user.dart +++ b/packages/stream_chat/lib/src/models/own_user.dart @@ -17,8 +17,8 @@ class OwnUser extends User { this.totalUnreadCount = 0, this.unreadChannels, this.channelMutes = const [], - String id = '', - String role = '', + required String id, + String? role, DateTime? createdAt, DateTime? updatedAt, DateTime? lastActive, diff --git a/packages/stream_chat/lib/src/models/own_user.g.dart b/packages/stream_chat/lib/src/models/own_user.g.dart index 785efb04..5683440d 100644 --- a/packages/stream_chat/lib/src/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/models/own_user.g.dart @@ -23,7 +23,7 @@ OwnUser _$OwnUserFromJson(Map json) { .toList() ?? [], id: json['id'] as String, - role: json['role'] as String, + role: json['role'] as String?, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), From 3bb1b11ee8f2b783e67d92101bd9f470603960a3 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 20 Apr 2021 19:52:06 +0530 Subject: [PATCH 138/599] migrate to nnbd Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/api/channel.dart | 2 +- .../stream_chat/lib/src/api/responses.dart | 2 +- .../lib/src/db/chat_persistence_client.dart | 8 +- .../lib/src/models/attachment.dart | 4 +- .../lib/src/models/attachment.g.dart | 4 +- .../lib/src/models/channel_model.dart | 9 +- .../lib/src/models/channel_model.g.dart | 4 +- .../stream_chat/lib/src/models/event.dart | 6 +- .../stream_chat/lib/src/models/event.g.dart | 9 +- .../stream_chat/lib/src/models/message.dart | 4 +- .../stream_chat/lib/src/models/message.g.dart | 5 +- .../stream_chat/lib/src/models/own_user.dart | 2 +- .../lib/src/models/own_user.g.dart | 4 +- .../stream_chat/lib/src/models/reaction.dart | 4 +- .../lib/src/models/reaction.g.dart | 4 +- packages/stream_chat/lib/src/models/user.dart | 15 +- .../stream_chat/lib/src/models/user.g.dart | 4 +- .../analysis_options.yaml | 7 +- .../lib/src/dao/channel_dao.dart | 6 +- .../lib/src/dao/channel_query_dao.dart | 29 +- .../lib/src/dao/connection_event_dao.dart | 9 +- .../lib/src/dao/member_dao.dart | 2 +- .../lib/src/dao/message_dao.dart | 49 ++- .../lib/src/dao/pinned_message_dao.dart | 47 +-- .../lib/src/dao/reaction_dao.dart | 2 +- .../lib/src/dao/read_dao.dart | 2 +- .../lib/src/db/moor_chat_database.dart | 7 + .../lib/src/db/moor_chat_database.g.dart | 71 ++-- .../lib/src/entity/channels.dart | 2 +- .../lib/src/entity/connection_events.dart | 2 +- .../lib/src/entity/users.dart | 2 +- .../lib/src/mapper/channel_mapper.dart | 16 +- .../lib/src/mapper/event_mapper.dart | 2 +- .../lib/src/mapper/member_mapper.dart | 6 +- .../lib/src/mapper/message_mapper.dart | 22 +- .../lib/src/mapper/pinned_message_mapper.dart | 22 +- .../lib/src/mapper/reaction_mapper.dart | 6 +- .../lib/src/mapper/read_mapper.dart | 6 +- .../src/stream_chat_persistence_client.dart | 390 ++++++++++-------- packages/stream_chat_persistence/pubspec.yaml | 2 - .../test/mock_chat_database.dart | 24 +- .../src/converter/list_coverter_test.dart | 2 +- .../test/src/dao/channel_dao_test.dart | 16 +- .../test/src/dao/channel_query_dao_test.dart | 19 +- .../src/dao/connection_event_dao_test.dart | 13 +- .../test/src/dao/member_dao_test.dart | 16 +- .../test/src/dao/message_dao_test.dart | 15 +- .../test/src/dao/pinned_message_dao_test.dart | 15 +- .../test/src/dao/reaction_dao_test.dart | 8 +- .../test/src/dao/read_dao_test.dart | 4 +- .../test/src/dao/user_dao_test.dart | 4 +- .../test/src/mapper/channel_mapper_test.dart | 28 +- .../test/src/mapper/event_mapper_test.dart | 2 +- .../test/src/mapper/member_mapper_test.dart | 12 +- .../test/src/mapper/message_mapper_test.dart | 31 +- .../mapper/pinned_message_mapper_test.dart | 31 +- .../test/src/mapper/user_mapper_test.dart | 4 +- .../test/src/utils/date_matcher.dart | 5 +- .../stream_chat_persistence_client_test.dart | 183 ++++---- 59 files changed, 642 insertions(+), 589 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 63fc5595..09992d98 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -620,7 +620,7 @@ class Channel { Future sendReaction( Message message, String type, { - Map extraData = const {}, + Map extraData = const {}, bool enforceUnique = false, }) async { _checkInitialized(); diff --git a/packages/stream_chat/lib/src/api/responses.dart b/packages/stream_chat/lib/src/api/responses.dart index a64cd64e..92493e8a 100644 --- a/packages/stream_chat/lib/src/api/responses.dart +++ b/packages/stream_chat/lib/src/api/responses.dart @@ -209,7 +209,7 @@ class GetMessageResponse extends _BaseResponse { final res = _$GetMessageResponseFromJson(json); final jsonChannel = res.message.extraData.remove('channel'); if (jsonChannel != null) { - res.channel = ChannelModel.fromJson(jsonChannel); + res.channel = ChannelModel.fromJson(jsonChannel as Map); } return res; } diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index c0f85f94..9f4c7afe 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -24,10 +24,10 @@ abstract class ChatPersistenceClient { }); /// Get stored connection event - Future getConnectionInfo(); + Future getConnectionInfo(); /// Get stored lastSyncAt - Future getLastSyncAt(); + Future getLastSyncAt(); /// Update stored connection event Future updateConnectionInfo(Event event); @@ -39,7 +39,7 @@ abstract class ChatPersistenceClient { Future> getChannelCids(); /// Get stored [ChannelModel]s by providing channel [cid] - Future getChannelByCid(String cid); + Future getChannelByCid(String cid); /// Get stored channel [Member]s by providing channel [cid] Future> getMembersByCid(String cid); @@ -90,7 +90,7 @@ abstract class ChatPersistenceClient { /// for filtering out states. Future> getChannelStates({ Map? filter, - List>? sort = const [], + List>? sort, PaginationParams? paginationParams, }); diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/models/attachment.dart index fd91454a..1bba82e7 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/models/attachment.dart @@ -111,7 +111,7 @@ class Attachment extends Equatable { /// Map of custom channel extraData @JsonKey(includeIfNull: false) - final Map? extraData; + final Map? extraData; /// The attachment ID. /// @@ -180,7 +180,7 @@ class Attachment extends Equatable { List? actions, AttachmentFile? file, UploadState? uploadState, - Map? extraData, + Map? extraData, }) => Attachment( id: id ?? this.id, diff --git a/packages/stream_chat/lib/src/models/attachment.g.dart b/packages/stream_chat/lib/src/models/attachment.g.dart index b7a7304d..d7e76e74 100644 --- a/packages/stream_chat/lib/src/models/attachment.g.dart +++ b/packages/stream_chat/lib/src/models/attachment.g.dart @@ -30,7 +30,9 @@ Attachment _$AttachmentFromJson(Map json) { ?.map((e) => Action.fromJson(e as Map)) .toList() ?? [], - extraData: json['extra_data'] as Map?, + extraData: (json['extra_data'] as Map?)?.map( + (k, e) => MapEntry(k, e as Object), + ), file: json['file'] == null ? null : AttachmentFile.fromJson(json['file'] as Map), diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index d68edbde..28a661df 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -84,7 +84,7 @@ class ChannelModel { /// Map of custom channel extraData @JsonKey(includeIfNull: false) - final Map? extraData; + final Map? extraData; /// The team the channel belongs to @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @@ -108,8 +108,9 @@ class ChannelModel { ]; /// Shortcut for channel name - String? get name => - extraData?.containsKey('name') == true ? extraData!['name'] : cid; + String get name => extraData?.containsKey('name') == true + ? extraData!['name'] as String + : cid; /// Serialize to json Map toJson() => Serialization.moveFromExtraDataToRoot( @@ -129,7 +130,7 @@ class ChannelModel { DateTime? updatedAt, DateTime? deletedAt, int? memberCount, - Map? extraData, + Map? extraData, String? team, }) => ChannelModel( diff --git a/packages/stream_chat/lib/src/models/channel_model.g.dart b/packages/stream_chat/lib/src/models/channel_model.g.dart index e71112b6..47f23071 100644 --- a/packages/stream_chat/lib/src/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/models/channel_model.g.dart @@ -31,7 +31,9 @@ ChannelModel _$ChannelModelFromJson(Map json) { ? null : DateTime.parse(json['deleted_at'] as String), memberCount: json['member_count'] as int? ?? 0, - extraData: json['extra_data'] as Map?, + extraData: (json['extra_data'] as Map?)?.map( + (k, e) => MapEntry(k, e as Object), + ), team: json['team'] as String?, ); } diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index 7e57de60..1d070230 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -93,7 +93,7 @@ class Event { /// Map of custom channel extraData @JsonKey(defaultValue: {}) - final Map extraData; + final Map extraData; /// Known top level fields. /// Useful for [Serialization] methods. @@ -140,7 +140,7 @@ class Event { int? unreadChannels, bool? online, String? parentId, - Map? extraData, + Map? extraData, }) => Event( type: type ?? this.type, @@ -180,7 +180,7 @@ class EventChannel extends ChannelModel { required DateTime updatedAt, DateTime? deletedAt, required int memberCount, - Map? extraData, + Map? extraData, }) : super( id: id, type: type, diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index d0bb0d87..2715b476 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -38,7 +38,10 @@ Event _$EventFromJson(Map json) { channelId: json['channel_id'] as String?, channelType: json['channel_type'] as String?, parentId: json['parent_id'] as String?, - extraData: json['extra_data'] as Map? ?? {}, + extraData: (json['extra_data'] as Map?)?.map( + (k, e) => MapEntry(k, e as Object), + ) ?? + {}, isLocal: json['is_local'] as bool? ?? false, ); } @@ -86,7 +89,9 @@ EventChannel _$EventChannelFromJson(Map json) { ? null : DateTime.parse(json['deleted_at'] as String), memberCount: json['member_count'] as int? ?? 0, - extraData: json['extra_data'] as Map?, + extraData: (json['extra_data'] as Map?)?.map( + (k, e) => MapEntry(k, e as Object), + ), ); } diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index 03a162c5..cec591c0 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -208,7 +208,7 @@ class Message extends Equatable { includeIfNull: false, defaultValue: {}, ) - final Map extraData; + final Map extraData; /// True if the message is a system info bool get isSystem => type == 'system'; @@ -289,7 +289,7 @@ class Message extends Equatable { DateTime? pinnedAt, Object? pinExpires = _pinExpires, User? pinnedBy, - Map? extraData, + Map? extraData, MessageSendingStatus? status, bool? skipPush, }) { diff --git a/packages/stream_chat/lib/src/models/message.g.dart b/packages/stream_chat/lib/src/models/message.g.dart index ca094a83..4168777a 100644 --- a/packages/stream_chat/lib/src/models/message.g.dart +++ b/packages/stream_chat/lib/src/models/message.g.dart @@ -63,7 +63,10 @@ Message _$MessageFromJson(Map json) { pinnedBy: json['pinned_by'] == null ? null : User.fromJson(json['pinned_by'] as Map), - extraData: json['extra_data'] as Map? ?? {}, + extraData: (json['extra_data'] as Map?)?.map( + (k, e) => MapEntry(k, e as Object), + ) ?? + {}, deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), diff --git a/packages/stream_chat/lib/src/models/own_user.dart b/packages/stream_chat/lib/src/models/own_user.dart index 05936cdd..292e0f4a 100644 --- a/packages/stream_chat/lib/src/models/own_user.dart +++ b/packages/stream_chat/lib/src/models/own_user.dart @@ -23,7 +23,7 @@ class OwnUser extends User { DateTime? updatedAt, DateTime? lastActive, bool online = false, - Map extraData = const {}, + Map extraData = const {}, bool banned = false, }) : super( id: id, diff --git a/packages/stream_chat/lib/src/models/own_user.g.dart b/packages/stream_chat/lib/src/models/own_user.g.dart index 5683440d..a5c27402 100644 --- a/packages/stream_chat/lib/src/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/models/own_user.g.dart @@ -34,7 +34,9 @@ OwnUser _$OwnUserFromJson(Map json) { ? null : DateTime.parse(json['last_active'] as String), online: json['online'] as bool? ?? false, - extraData: json['extra_data'] as Map, + extraData: (json['extra_data'] as Map).map( + (k, e) => MapEntry(k, e as Object), + ), banned: json['banned'] as bool? ?? false, ); } diff --git a/packages/stream_chat/lib/src/models/reaction.dart b/packages/stream_chat/lib/src/models/reaction.dart index 82913218..0e60e281 100644 --- a/packages/stream_chat/lib/src/models/reaction.dart +++ b/packages/stream_chat/lib/src/models/reaction.dart @@ -50,7 +50,7 @@ class Reaction { /// Reaction custom extraData @JsonKey(includeIfNull: false) - final Map? extraData; + final Map? extraData; /// Map of custom user extraData static const topLevelFields = [ @@ -75,7 +75,7 @@ class Reaction { User? user, String? userId, int? score, - Map? extraData, + Map? extraData, }) => Reaction( messageId: messageId ?? this.messageId, diff --git a/packages/stream_chat/lib/src/models/reaction.g.dart b/packages/stream_chat/lib/src/models/reaction.g.dart index 963b4d21..24b60830 100644 --- a/packages/stream_chat/lib/src/models/reaction.g.dart +++ b/packages/stream_chat/lib/src/models/reaction.g.dart @@ -18,7 +18,9 @@ Reaction _$ReactionFromJson(Map json) { : User.fromJson(json['user'] as Map), userId: json['user_id'] as String?, score: json['score'] as int? ?? 0, - extraData: json['extra_data'] as Map?, + extraData: (json['extra_data'] as Map?)?.map( + (k, e) => MapEntry(k, e as Object), + ), ); } diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/models/user.dart index 8b965589..88a7440c 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/models/user.dart @@ -75,16 +75,19 @@ class User { /// Map of custom user extraData @JsonKey(includeIfNull: false) - final Map extraData; + final Map extraData; @override int get hashCode => id.hashCode; /// Shortcut for user name - String? get name => - (extraData.containsKey('name') == true && extraData['name'] != '') - ? extraData['name'] - : id; + String get name { + if (extraData.containsKey('name')) { + final name = extraData['name'] as String; + if (name.isNotEmpty) return name; + } + return id; + } @override bool operator ==(Object other) => @@ -104,7 +107,7 @@ class User { DateTime? updatedAt, DateTime? lastActive, bool? online, - Map? extraData, + Map? extraData, bool? banned, List? teams, }) => diff --git a/packages/stream_chat/lib/src/models/user.g.dart b/packages/stream_chat/lib/src/models/user.g.dart index ef1ccf77..11538395 100644 --- a/packages/stream_chat/lib/src/models/user.g.dart +++ b/packages/stream_chat/lib/src/models/user.g.dart @@ -20,7 +20,9 @@ User _$UserFromJson(Map json) { ? null : DateTime.parse(json['last_active'] as String), online: json['online'] as bool? ?? false, - extraData: json['extra_data'] as Map, + extraData: (json['extra_data'] as Map).map( + (k, e) => MapEntry(k, e as Object), + ), banned: json['banned'] as bool? ?? false, teams: (json['teams'] as List?)?.map((e) => e as String).toList() ?? diff --git a/packages/stream_chat_persistence/analysis_options.yaml b/packages/stream_chat_persistence/analysis_options.yaml index 26001160..ff0fc87c 100644 --- a/packages/stream_chat_persistence/analysis_options.yaml +++ b/packages/stream_chat_persistence/analysis_options.yaml @@ -1,11 +1,10 @@ analyzer: - exclude: + exclude: - lib/**/*.g.dart - lib/**/*.freezed.dart - example/* - - test/* -linter: - rules: +linter: + rules: - always_use_package_imports - avoid_empty_else - avoid_relative_lib_imports diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart index ce2e1db3..fc488511 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart @@ -15,11 +15,11 @@ class ChannelDao extends DatabaseAccessor ChannelDao(MoorChatDatabase db) : super(db); /// Get channel by cid - Future getChannelByCid(String cid) async => + Future getChannelByCid(String cid) async => (select(channels)..where((c) => c.cid.equals(cid))).join([ leftOuterJoin(users, channels.createdById.equalsExp(users.id)), ]).map((rows) { - final channel = rows.readTableOrNull(channels); + final channel = rows.readTable(channels); final createdBy = rows.readTableOrNull(users); return channel.toChannelModel(createdBy: createdBy?.toUser()); }).getSingleOrNull(); @@ -30,7 +30,7 @@ class ChannelDao extends DatabaseAccessor /// 1. Channel Reads /// 2. Channel Members /// 3. Channel Messages -> Messages Reactions - Future deleteChannelByCids(List cids) async => + Future deleteChannelByCids(List cids) async => (delete(channels)..where((tbl) => tbl.cid.isIn(cids))).go(); /// Get the channel cids saved in the storage diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart index 233fd4ef..099f6798 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart @@ -18,7 +18,7 @@ class ChannelQueryDao extends DatabaseAccessor /// Creates a new channel query dao instance ChannelQueryDao(MoorChatDatabase db) : super(db); - String _computeHash(Map filter) { + String _computeHash(Map? filter) { if (filter == null) { return 'allchannels'; } @@ -58,7 +58,7 @@ class ChannelQueryDao extends DatabaseAccessor }); /// - Future> getCachedChannelCids(Map filter) { + Future> getCachedChannelCids(Map? filter) { final hash = _computeHash(filter); return (select(channelQueries)..where((c) => c.queryHash.equals(hash))) .map((c) => c.channelCid) @@ -67,9 +67,9 @@ class ChannelQueryDao extends DatabaseAccessor /// Get list of channels by filter, sort and paginationParams Future> getChannels({ - Map filter, - List> sort = const [], - PaginationParams paginationParams, + Map? filter, + List>? sort, + PaginationParams? paginationParams, }) async { assert(() { if (sort != null && sort.any((it) => it.comparator == null)) { @@ -86,20 +86,21 @@ class ChannelQueryDao extends DatabaseAccessor final cachedChannels = await (query.join([ leftOuterJoin(users, channels.createdById.equalsExp(users.id)), ]).map((row) { - final createdByEntity = row.readTable(users); + final createdByEntity = row.readTableOrNull(users); final channelEntity = row.readTable(channels); return channelEntity.toChannelModel(createdBy: createdByEntity?.toUser()); })).get(); final possibleSortingFields = cachedChannels.fold>( - ChannelModel.topLevelFields, - (previousValue, element) => - {...previousValue, ...element.extraData.keys}.toList()); + ChannelModel.topLevelFields, (previousValue, element) { + final extraData = element.extraData ?? {}; + return {...previousValue, ...extraData.keys}.toList(); + }); // ignore: parameter_assignments sort = sort ?.where((s) => possibleSortingFields.contains(s.field)) - ?.toList(growable: false); + .toList(growable: false); var chainedComparator = (ChannelModel a, ChannelModel b) { final dateA = a.lastMessageAt ?? a.createdAt; @@ -110,9 +111,9 @@ class ChannelQueryDao extends DatabaseAccessor if (sort != null && sort.isNotEmpty) { chainedComparator = (a, b) { int result; - for (final comparator in sort.map((it) => it.comparator)) { + for (final comparator in sort!.map((it) => it.comparator)) { try { - result = comparator(a, b); + result = comparator!(a, b); } catch (e) { result = 0; } @@ -125,11 +126,11 @@ class ChannelQueryDao extends DatabaseAccessor cachedChannels.sort(chainedComparator); if (paginationParams?.offset != null && cachedChannels.isNotEmpty) { - cachedChannels.removeRange(0, paginationParams.offset); + cachedChannels.removeRange(0, paginationParams!.offset); } if (paginationParams?.limit != null) { - return cachedChannels.take(paginationParams.limit).toList(); + return cachedChannels.take(paginationParams!.limit).toList(); } return cachedChannels; diff --git a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart index e80a814c..4a169cd2 100644 --- a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart @@ -15,19 +15,18 @@ class ConnectionEventDao extends DatabaseAccessor ConnectionEventDao(MoorChatDatabase db) : super(db); /// Get the latest stored connection event - Future get connectionEvent => select(connectionEvents) + Future get connectionEvent => select(connectionEvents) .map((eventEntity) => eventEntity.toEvent()) .getSingleOrNull(); /// Get the latest stored lastSyncAt - Future get lastSyncAt => + Future get lastSyncAt => select(connectionEvents).getSingleOrNull().then((r) => r?.lastSyncAt); /// Update stored connection event with latest data - Future updateConnectionEvent(Event event) async => - transaction(() async { + Future updateConnectionEvent(Event event) => transaction(() async { final connectionInfo = await select(connectionEvents).getSingleOrNull(); - await into(connectionEvents).insert( + return into(connectionEvents).insert( ConnectionEventEntity( id: 1, lastSyncAt: connectionInfo?.lastSyncAt, diff --git a/packages/stream_chat_persistence/lib/src/dao/member_dao.dart b/packages/stream_chat_persistence/lib/src/dao/member_dao.dart index e6c03996..157b0a9a 100644 --- a/packages/stream_chat_persistence/lib/src/dao/member_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/member_dao.dart @@ -26,7 +26,7 @@ class MemberDao extends DatabaseAccessor .map((row) { final userEntity = row.readTable(users); final memberEntity = row.readTable(members); - return memberEntity.toMember(user: userEntity?.toUser()); + return memberEntity.toMember(user: userEntity.toUser()); }).get(); /// Updates all the members using the new [memberList] data 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 3b7f8c2f..c2e1cde2 100644 --- a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart @@ -25,7 +25,7 @@ class MessageDao extends DatabaseAccessor /// /// This will automatically delete the following linked records /// 1. Message Reactions - Future deleteMessageByIds(List messageIds) => + Future deleteMessageByIds(List messageIds) => (delete(messages)..where((tbl) => tbl.id.isIn(messageIds))).go(); /// Removes all the messages by matching [Messages.channelCid] in [cids] @@ -38,15 +38,16 @@ class MessageDao extends DatabaseAccessor Future _messageFromJoinRow(TypedResult rows) async { final userEntity = rows.readTableOrNull(_users); final pinnedByEntity = rows.readTableOrNull(_pinnedByUsers); - final msgEntity = rows.readTableOrNull(messages); + final msgEntity = rows.readTable(messages); final latestReactions = await _db.reactionDao.getReactions(msgEntity.id); final ownReactions = await _db.reactionDao.getReactionsByUserId( msgEntity.id, _db.userId, ); - Message quotedMessage; - if (msgEntity.quotedMessageId != null) { - quotedMessage = await getMessageById(msgEntity.quotedMessageId); + Message? quotedMessage; + final quotedMessageId = msgEntity.quotedMessageId; + if (quotedMessageId != null) { + quotedMessage = await getMessageById(quotedMessageId); } return msgEntity.toMessage( user: userEntity?.toUser(), @@ -58,7 +59,7 @@ class MessageDao extends DatabaseAccessor } /// Returns a single message by matching the [Messages.id] with [id] - Future getMessageById(String id) async => + Future getMessageById(String id) async => await (select(messages).join([ leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), leftOuterJoin(_pinnedByUsers, @@ -86,7 +87,7 @@ class MessageDao extends DatabaseAccessor /// [Messages.parentId] with [parentId] Future> getThreadMessagesByParentId( String parentId, { - PaginationParams options, + PaginationParams? options, }) async { final msgList = await Future.wait(await (select(messages).join([ leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), @@ -102,7 +103,7 @@ class MessageDao extends DatabaseAccessor if (msgList.isNotEmpty) { if (options?.lessThan != null) { final lessThanIndex = msgList.indexWhere( - (m) => m.id == options.lessThan, + (m) => m.id == options!.lessThan, ); if (lessThanIndex != -1) { msgList.removeRange(lessThanIndex, msgList.length); @@ -110,14 +111,14 @@ class MessageDao extends DatabaseAccessor } if (options?.greaterThanOrEqual != null) { final greaterThanIndex = msgList.indexWhere( - (m) => m.id == options.greaterThanOrEqual, + (m) => m.id == options!.greaterThanOrEqual, ); if (greaterThanIndex != -1) { msgList.removeRange(0, greaterThanIndex); } } if (options?.limit != null) { - return msgList.take(options.limit).toList(); + return msgList.take(options!.limit).toList(); } } return msgList; @@ -127,7 +128,7 @@ class MessageDao extends DatabaseAccessor /// [Messages.channelCid] with [parentId] Future> getMessagesByCid( String cid, { - PaginationParams messagePagination, + PaginationParams? messagePagination, }) async { final msgList = await Future.wait(await (select(messages).join([ leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), @@ -145,7 +146,7 @@ class MessageDao extends DatabaseAccessor if (msgList.isNotEmpty) { if (messagePagination?.lessThan != null) { final lessThanIndex = msgList.indexWhere( - (m) => m.id == messagePagination.lessThan, + (m) => m.id == messagePagination!.lessThan, ); if (lessThanIndex != -1) { msgList.removeRange(lessThanIndex, msgList.length); @@ -153,14 +154,14 @@ class MessageDao extends DatabaseAccessor } if (messagePagination?.greaterThanOrEqual != null) { final greaterThanIndex = msgList.indexWhere( - (m) => m.id == messagePagination.greaterThanOrEqual, + (m) => m.id == messagePagination!.greaterThanOrEqual, ); if (greaterThanIndex != -1) { msgList.removeRange(0, greaterThanIndex); } } if (messagePagination?.limit != null) { - return msgList.take(messagePagination.limit).toList(); + return msgList.take(messagePagination!.limit).toList(); } } return msgList; @@ -168,17 +169,13 @@ class MessageDao extends DatabaseAccessor /// Updates the message data of a particular channel with /// the new [messageList] data - Future updateMessages(String cid, List messageList) async { - if (messageList == null) { - return; - } - - return batch((batch) { - batch.insertAll( - messages, - messageList.map((it) => it.toEntity(cid: cid)).toList(), - mode: InsertMode.insertOrReplace, + Future updateMessages(String cid, List messageList) => batch( + (batch) { + batch.insertAll( + messages, + messageList.map((it) => it.toEntity(cid: cid)).toList(), + mode: InsertMode.insertOrReplace, + ); + }, ); - }); - } } 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 4d56ba49..af6497f9 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 @@ -38,15 +38,16 @@ class PinnedMessageDao extends DatabaseAccessor Future _messageFromJoinRow(TypedResult rows) async { final userEntity = rows.readTableOrNull(users); final pinnedByEntity = rows.readTableOrNull(_pinnedByUsers); - final msgEntity = rows.readTableOrNull(pinnedMessages); + final msgEntity = rows.readTable(pinnedMessages); final latestReactions = await _db.reactionDao.getReactions(msgEntity.id); final ownReactions = await _db.reactionDao.getReactionsByUserId( msgEntity.id, _db.userId, ); - Message quotedMessage; - if (msgEntity.quotedMessageId != null) { - quotedMessage = await getMessageById(msgEntity.quotedMessageId); + Message? quotedMessage; + final quotedMessageId = msgEntity.quotedMessageId; + if (quotedMessageId != null) { + quotedMessage = await getMessageById(quotedMessageId); } return msgEntity.toMessage( user: userEntity?.toUser(), @@ -58,7 +59,7 @@ class PinnedMessageDao extends DatabaseAccessor } /// Returns a single message by matching the [PinnedMessages.id] with [id] - Future getMessageById(String id) async => + Future getMessageById(String id) async => await (select(pinnedMessages).join([ leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), leftOuterJoin(_pinnedByUsers, @@ -86,7 +87,7 @@ class PinnedMessageDao extends DatabaseAccessor /// [PinnedMessages.parentId] with [parentId] Future> getThreadMessagesByParentId( String parentId, { - PaginationParams options, + PaginationParams? options, }) async { final msgList = await Future.wait(await (select(pinnedMessages).join([ leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), @@ -102,7 +103,7 @@ class PinnedMessageDao extends DatabaseAccessor if (msgList.isNotEmpty) { if (options?.lessThan != null) { final lessThanIndex = msgList.indexWhere( - (m) => m.id == options.lessThan, + (m) => m.id == options!.lessThan, ); if (lessThanIndex != -1) { msgList.removeRange(lessThanIndex, msgList.length); @@ -110,14 +111,14 @@ class PinnedMessageDao extends DatabaseAccessor } if (options?.greaterThanOrEqual != null) { final greaterThanIndex = msgList.indexWhere( - (m) => m.id == options.greaterThanOrEqual, + (m) => m.id == options!.greaterThanOrEqual, ); if (greaterThanIndex != -1) { msgList.removeRange(0, greaterThanIndex); } } if (options?.limit != null) { - return msgList.take(options.limit).toList(); + return msgList.take(options!.limit).toList(); } } return msgList; @@ -127,7 +128,7 @@ class PinnedMessageDao extends DatabaseAccessor /// [PinnedMessages.channelCid] with [parentId] Future> getMessagesByCid( String cid, { - PaginationParams messagePagination, + PaginationParams? messagePagination, }) async { final msgList = await Future.wait(await (select(pinnedMessages).join([ leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), @@ -144,7 +145,7 @@ class PinnedMessageDao extends DatabaseAccessor if (msgList.isNotEmpty) { if (messagePagination?.lessThan != null) { final lessThanIndex = msgList.indexWhere( - (m) => m.id == messagePagination.lessThan, + (m) => m.id == messagePagination!.lessThan, ); if (lessThanIndex != -1) { msgList.removeRange(lessThanIndex, msgList.length); @@ -152,14 +153,14 @@ class PinnedMessageDao extends DatabaseAccessor } if (messagePagination?.greaterThanOrEqual != null) { final greaterThanIndex = msgList.indexWhere( - (m) => m.id == messagePagination.greaterThanOrEqual, + (m) => m.id == messagePagination!.greaterThanOrEqual, ); if (greaterThanIndex != -1) { msgList.removeRange(0, greaterThanIndex); } } if (messagePagination?.limit != null) { - return msgList.take(messagePagination.limit).toList(); + return msgList.take(messagePagination!.limit).toList(); } } return msgList; @@ -167,17 +168,13 @@ class PinnedMessageDao extends DatabaseAccessor /// Updates the message data of a particular channel with /// the new [messageList] data - Future updateMessages(String cid, List messageList) async { - if (messageList == null) { - return; - } - - return batch((batch) { - batch.insertAll( - pinnedMessages, - messageList.map((it) => it.toPinnedEntity(cid: cid)).toList(), - mode: InsertMode.insertOrReplace, + Future updateMessages(String cid, List messageList) => batch( + (batch) { + batch.insertAll( + pinnedMessages, + messageList.map((it) => it.toPinnedEntity(cid: cid)).toList(), + mode: InsertMode.insertOrReplace, + ); + }, ); - }); - } } diff --git a/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart b/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart index a2a92b2e..89da6081 100644 --- a/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart @@ -24,7 +24,7 @@ class ReactionDao extends DatabaseAccessor ..orderBy([OrderingTerm.asc(reactions.createdAt)])) .map((rows) { final userEntity = rows.readTableOrNull(users); - final reactionEntity = rows.readTableOrNull(reactions); + final reactionEntity = rows.readTable(reactions); return reactionEntity.toReaction(user: userEntity?.toUser()); }).get(); diff --git a/packages/stream_chat_persistence/lib/src/dao/read_dao.dart b/packages/stream_chat_persistence/lib/src/dao/read_dao.dart index 95e23d8b..05d807fd 100644 --- a/packages/stream_chat_persistence/lib/src/dao/read_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/read_dao.dart @@ -24,7 +24,7 @@ class ReadDao extends DatabaseAccessor with _$ReadDaoMixin { .map((row) { final userEntity = row.readTable(users); final readEntity = row.readTable(reads); - return readEntity.toRead(user: userEntity?.toUser()); + return readEntity.toRead(user: userEntity.toUser()); }).get(); /// Updates the read data of a particular channel with 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 73c7b0fa..622e0a61 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 @@ -72,6 +72,13 @@ class MoorChatDatabase extends _$MoorChatDatabase { }, ); + /// Deletes all the tables + Future flush() => batch((batch) { + allTables.forEach((table) { + delete(table).go(); + }); + }); + /// Closes the database instance Future disconnect() => close(); } diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index 1453e3d4..b85857bb 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -18,7 +18,7 @@ class ChannelEntity extends DataClass implements Insertable { final String cid; /// The channel configuration data - final Map config; + final Map config; /// True if this channel entity is frozen final bool frozen; @@ -125,7 +125,7 @@ class ChannelEntity extends DataClass implements Insertable { id: serializer.fromJson(json['id']), type: serializer.fromJson(json['type']), cid: serializer.fromJson(json['cid']), - config: serializer.fromJson>(json['config']), + config: serializer.fromJson>(json['config']), frozen: serializer.fromJson(json['frozen']), lastMessageAt: serializer.fromJson(json['lastMessageAt']), createdAt: serializer.fromJson(json['createdAt']), @@ -143,7 +143,7 @@ class ChannelEntity extends DataClass implements Insertable { 'id': serializer.toJson(id), 'type': serializer.toJson(type), 'cid': serializer.toJson(cid), - 'config': serializer.toJson>(config), + 'config': serializer.toJson>(config), 'frozen': serializer.toJson(frozen), 'lastMessageAt': serializer.toJson(lastMessageAt), 'createdAt': serializer.toJson(createdAt), @@ -159,7 +159,7 @@ class ChannelEntity extends DataClass implements Insertable { {String? id, String? type, String? cid, - Map? config, + Map? config, bool? frozen, Value lastMessageAt = const Value.absent(), DateTime? createdAt, @@ -247,7 +247,7 @@ class ChannelsCompanion extends UpdateCompanion { final Value id; final Value type; final Value cid; - final Value> config; + final Value> config; final Value frozen; final Value lastMessageAt; final Value createdAt; @@ -274,7 +274,7 @@ class ChannelsCompanion extends UpdateCompanion { required String id, required String type, required String cid, - required Map config, + required Map config, this.frozen = const Value.absent(), this.lastMessageAt = const Value.absent(), this.createdAt = const Value.absent(), @@ -291,7 +291,7 @@ class ChannelsCompanion extends UpdateCompanion { Expression? id, Expression? type, Expression? cid, - Expression>? config, + Expression>? config, Expression? frozen, Expression? lastMessageAt, Expression? createdAt, @@ -321,7 +321,7 @@ class ChannelsCompanion extends UpdateCompanion { {Value? id, Value? type, Value? cid, - Value>? config, + Value>? config, Value? frozen, Value? lastMessageAt, Value? createdAt, @@ -634,8 +634,8 @@ class $ChannelsTable extends Channels return $ChannelsTable(_db, alias); } - static TypeConverter, String> $converter0 = - MapConverter(); + static TypeConverter, String> $converter0 = + MapConverter(); static TypeConverter, String> $converter1 = MapConverter(); } @@ -3382,7 +3382,7 @@ class UserEntity extends DataClass implements Insertable { final bool banned; /// Map of custom user extraData - final Map? extraData; + final Map extraData; UserEntity( {required this.id, this.role, @@ -3391,7 +3391,7 @@ class UserEntity extends DataClass implements Insertable { this.lastActive, required this.online, required this.banned, - this.extraData}); + required this.extraData}); factory UserEntity.fromData(Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; @@ -3412,7 +3412,7 @@ class UserEntity extends DataClass implements Insertable { banned: boolType.mapFromDatabaseResponse(data['${effectivePrefix}banned'])!, extraData: $UsersTable.$converter0.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), + .mapFromDatabaseResponse(data['${effectivePrefix}extra_data']))!, ); } @override @@ -3429,9 +3429,9 @@ class UserEntity extends DataClass implements Insertable { } map['online'] = Variable(online); map['banned'] = Variable(banned); - if (!nullToAbsent || extraData != null) { + { final converter = $UsersTable.$converter0; - map['extra_data'] = Variable(converter.mapToSql(extraData)); + map['extra_data'] = Variable(converter.mapToSql(extraData)!); } return map; } @@ -3447,7 +3447,7 @@ class UserEntity extends DataClass implements Insertable { lastActive: serializer.fromJson(json['lastActive']), online: serializer.fromJson(json['online']), banned: serializer.fromJson(json['banned']), - extraData: serializer.fromJson?>(json['extraData']), + extraData: serializer.fromJson>(json['extraData']), ); } @override @@ -3461,7 +3461,7 @@ class UserEntity extends DataClass implements Insertable { 'lastActive': serializer.toJson(lastActive), 'online': serializer.toJson(online), 'banned': serializer.toJson(banned), - 'extraData': serializer.toJson?>(extraData), + 'extraData': serializer.toJson>(extraData), }; } @@ -3473,7 +3473,7 @@ class UserEntity extends DataClass implements Insertable { Value lastActive = const Value.absent(), bool? online, bool? banned, - Value?> extraData = const Value.absent()}) => + Map? extraData}) => UserEntity( id: id ?? this.id, role: role.present ? role.value : this.role, @@ -3482,7 +3482,7 @@ class UserEntity extends DataClass implements Insertable { lastActive: lastActive.present ? lastActive.value : this.lastActive, online: online ?? this.online, banned: banned ?? this.banned, - extraData: extraData.present ? extraData.value : this.extraData, + extraData: extraData ?? this.extraData, ); @override String toString() { @@ -3534,7 +3534,7 @@ class UsersCompanion extends UpdateCompanion { final Value lastActive; final Value online; final Value banned; - final Value?> extraData; + final Value> extraData; const UsersCompanion({ this.id = const Value.absent(), this.role = const Value.absent(), @@ -3553,8 +3553,9 @@ class UsersCompanion extends UpdateCompanion { this.lastActive = const Value.absent(), this.online = const Value.absent(), this.banned = const Value.absent(), - this.extraData = const Value.absent(), - }) : id = Value(id); + required Map extraData, + }) : id = Value(id), + extraData = Value(extraData); static Insertable custom({ Expression? id, Expression? role, @@ -3563,7 +3564,7 @@ class UsersCompanion extends UpdateCompanion { Expression? lastActive, Expression? online, Expression? banned, - Expression?>? extraData, + Expression>? extraData, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -3585,7 +3586,7 @@ class UsersCompanion extends UpdateCompanion { Value? lastActive, Value? online, Value? banned, - Value?>? extraData}) { + Value>? extraData}) { return UsersCompanion( id: id ?? this.id, role: role ?? this.role, @@ -3625,7 +3626,7 @@ class UsersCompanion extends UpdateCompanion { if (extraData.present) { final converter = $UsersTable.$converter0; map['extra_data'] = - Variable(converter.mapToSql(extraData.value)); + Variable(converter.mapToSql(extraData.value)!); } return map; } @@ -3722,7 +3723,7 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { return GeneratedTextColumn( 'extra_data', $tableName, - true, + false, ); } @@ -4851,7 +4852,7 @@ class ConnectionEventEntity extends DataClass final int id; /// User object of the current user - final Map? ownUser; + final Map? ownUser; /// The number of unread messages for current user final int? totalUnreadCount; @@ -4920,7 +4921,7 @@ class ConnectionEventEntity extends DataClass serializer ??= moorRuntimeOptions.defaultSerializer; return ConnectionEventEntity( id: serializer.fromJson(json['id']), - ownUser: serializer.fromJson?>(json['ownUser']), + ownUser: serializer.fromJson?>(json['ownUser']), totalUnreadCount: serializer.fromJson(json['totalUnreadCount']), unreadChannels: serializer.fromJson(json['unreadChannels']), lastEventAt: serializer.fromJson(json['lastEventAt']), @@ -4932,7 +4933,7 @@ class ConnectionEventEntity extends DataClass serializer ??= moorRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), - 'ownUser': serializer.toJson?>(ownUser), + 'ownUser': serializer.toJson?>(ownUser), 'totalUnreadCount': serializer.toJson(totalUnreadCount), 'unreadChannels': serializer.toJson(unreadChannels), 'lastEventAt': serializer.toJson(lastEventAt), @@ -4942,7 +4943,7 @@ class ConnectionEventEntity extends DataClass ConnectionEventEntity copyWith( {int? id, - Value?> ownUser = const Value.absent(), + Value?> ownUser = const Value.absent(), Value totalUnreadCount = const Value.absent(), Value unreadChannels = const Value.absent(), Value lastEventAt = const Value.absent(), @@ -4994,7 +4995,7 @@ class ConnectionEventEntity extends DataClass class ConnectionEventsCompanion extends UpdateCompanion { final Value id; - final Value?> ownUser; + final Value?> ownUser; final Value totalUnreadCount; final Value unreadChannels; final Value lastEventAt; @@ -5017,7 +5018,7 @@ class ConnectionEventsCompanion extends UpdateCompanion { }); static Insertable custom({ Expression? id, - Expression?>? ownUser, + Expression?>? ownUser, Expression? totalUnreadCount, Expression? unreadChannels, Expression? lastEventAt, @@ -5035,7 +5036,7 @@ class ConnectionEventsCompanion extends UpdateCompanion { ConnectionEventsCompanion copyWith( {Value? id, - Value?>? ownUser, + Value?>? ownUser, Value? totalUnreadCount, Value? unreadChannels, Value? lastEventAt, @@ -5222,8 +5223,8 @@ class $ConnectionEventsTable extends ConnectionEvents return $ConnectionEventsTable(_db, alias); } - static TypeConverter, String> $converter0 = - MapConverter(); + static TypeConverter, String> $converter0 = + MapConverter(); } abstract class _$MoorChatDatabase extends GeneratedDatabase { diff --git a/packages/stream_chat_persistence/lib/src/entity/channels.dart b/packages/stream_chat_persistence/lib/src/entity/channels.dart index 06a1045e..e6c280ed 100644 --- a/packages/stream_chat_persistence/lib/src/entity/channels.dart +++ b/packages/stream_chat_persistence/lib/src/entity/channels.dart @@ -15,7 +15,7 @@ class Channels extends Table { TextColumn get cid => text()(); /// The channel configuration data - TextColumn get config => text().map(MapConverter())(); + TextColumn get config => text().map(MapConverter())(); /// True if this channel entity is frozen BoolColumn get frozen => boolean().withDefault(const Constant(false))(); diff --git a/packages/stream_chat_persistence/lib/src/entity/connection_events.dart b/packages/stream_chat_persistence/lib/src/entity/connection_events.dart index 69538edc..cf0819ae 100644 --- a/packages/stream_chat_persistence/lib/src/entity/connection_events.dart +++ b/packages/stream_chat_persistence/lib/src/entity/connection_events.dart @@ -9,7 +9,7 @@ class ConnectionEvents extends Table { IntColumn get id => integer()(); /// User object of the current user - TextColumn get ownUser => text().nullable().map(MapConverter())(); + TextColumn get ownUser => text().nullable().map(MapConverter())(); /// The number of unread messages for current user IntColumn get totalUnreadCount => integer().nullable()(); diff --git a/packages/stream_chat_persistence/lib/src/entity/users.dart b/packages/stream_chat_persistence/lib/src/entity/users.dart index d496841b..49fb4687 100644 --- a/packages/stream_chat_persistence/lib/src/entity/users.dart +++ b/packages/stream_chat_persistence/lib/src/entity/users.dart @@ -27,7 +27,7 @@ class Users extends Table { BoolColumn get banned => boolean().withDefault(const Constant(false))(); /// Map of custom user extraData - TextColumn get extraData => text().nullable().map(MapConverter())(); + TextColumn get extraData => text().map(MapConverter())(); @override Set get primaryKey => {id}; diff --git a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart index 65315519..5c4ec555 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart @@ -4,8 +4,8 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// Useful mapping functions for [ChannelEntity] extension ChannelEntityX on ChannelEntity { /// Maps a [ChannelEntity] into [ChannelModel] - ChannelModel toChannelModel({User createdBy}) { - final config = ChannelConfig.fromJson(this.config ?? {}); + ChannelModel toChannelModel({User? createdBy}) { + final config = ChannelConfig.fromJson(this.config); return ChannelModel( id: id, config: config, @@ -24,11 +24,11 @@ extension ChannelEntityX on ChannelEntity { /// Maps a [ChannelEntity] into [ChannelState] ChannelState toChannelState({ - User createdBy, - List members, - List reads, - List messages, - List pinnedMessages, + User? createdBy, + List members = const [], + List reads = const [], + List messages = const [], + List pinnedMessages = const [], }) => ChannelState( members: members, @@ -46,7 +46,7 @@ extension ChannelModelX on ChannelModel { id: id, type: type, cid: cid, - config: config?.toJson(), + config: config.toJson(), frozen: frozen, lastMessageAt: lastMessageAt, createdAt: createdAt, diff --git a/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart index fc2a57ba..c5cc4198 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart @@ -5,7 +5,7 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; extension ConnectionEventX on ConnectionEventEntity { /// Maps a [ConnectionEventEntity] into [Event] Event toEvent() => Event( - me: ownUser != null ? OwnUser.fromJson(ownUser) : null, + me: ownUser != null ? OwnUser.fromJson(ownUser!) : null, totalUnreadCount: totalUnreadCount, unreadChannels: unreadChannels, ); diff --git a/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart index cbdcc297..414cab16 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart @@ -4,7 +4,7 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// Useful mapping functions for [MemberEntity] extension MemberEntityX on MemberEntity { /// Maps a [MemberEntity] into [Member] - Member toMember({User user}) => Member( + Member toMember({User? user}) => Member( user: user, userId: userId, banned: banned, @@ -22,8 +22,8 @@ extension MemberEntityX on MemberEntity { /// Useful mapping functions for [Member] extension MemberX on Member { /// Maps a [Member] into [MemberEntity] - MemberEntity toEntity({String cid}) => MemberEntity( - userId: user?.id, + MemberEntity toEntity({required String cid}) => MemberEntity( + userId: user!.id, banned: banned, shadowBanned: shadowBanned, channelCid: cid, diff --git a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart index fdf93cc5..c97cc92d 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart @@ -7,22 +7,22 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; extension MessageEntityX on MessageEntity { /// Maps a [MessageEntity] into [Message] Message toMessage({ - User user, - User pinnedBy, - List latestReactions, - List ownReactions, - Message quotedMessage, + User? user, + User? pinnedBy, + List? latestReactions, + List? ownReactions, + Message? quotedMessage, }) => Message( shadowed: shadowed, latestReactions: latestReactions, ownReactions: ownReactions, - attachments: attachments?.map((it) { + attachments: attachments.map((it) { final json = jsonDecode(it); return Attachment.fromData(json); - })?.toList(), + }).toList(), createdAt: createdAt, - extraData: extraData, + extraData: extraData ?? {}, updatedAt: updatedAt, id: id, type: type, @@ -48,10 +48,9 @@ extension MessageEntityX on MessageEntity { /// Useful mapping functions for [Message] extension MessageX on Message { /// Maps a [Message] into [MessageEntity] - MessageEntity toEntity({String cid}) => MessageEntity( + MessageEntity toEntity({String? cid}) => MessageEntity( id: id, - attachments: - attachments?.map((it) => jsonEncode(it.toData()))?.toList(), + attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), channelCid: cid, type: type, parentId: parentId, @@ -63,6 +62,7 @@ extension MessageX on Message { replyCount: replyCount, reactionScores: reactionScores, reactionCounts: reactionCounts, + mentionedUsers: mentionedUsers.map(jsonEncode).toList(), status: status, updatedAt: updatedAt, extraData: extraData, diff --git a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart index ec2a0e91..1083e239 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart @@ -7,22 +7,22 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; extension PinnedMessageEntityX on PinnedMessageEntity { /// Maps a [PinnedMessageEntity] into [Message] Message toMessage({ - User user, - User pinnedBy, - List latestReactions, - List ownReactions, - Message quotedMessage, + User? user, + User? pinnedBy, + List? latestReactions, + List? ownReactions, + Message? quotedMessage, }) => Message( shadowed: shadowed, latestReactions: latestReactions, ownReactions: ownReactions, - attachments: attachments?.map((it) { + attachments: attachments.map((it) { final json = jsonDecode(it); return Attachment.fromData(json); - })?.toList(), + }).toList(), createdAt: createdAt, - extraData: extraData, + extraData: extraData ?? {}, updatedAt: updatedAt, id: id, type: type, @@ -48,10 +48,9 @@ extension PinnedMessageEntityX on PinnedMessageEntity { /// Useful mapping functions for [Message] extension PMessageX on Message { /// Maps a [Message] into [PinnedMessageEntity] - PinnedMessageEntity toPinnedEntity({String cid}) => PinnedMessageEntity( + PinnedMessageEntity toPinnedEntity({String? cid}) => PinnedMessageEntity( id: id, - attachments: - attachments?.map((it) => jsonEncode(it.toData()))?.toList(), + attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), channelCid: cid, type: type, parentId: parentId, @@ -63,6 +62,7 @@ extension PMessageX on Message { replyCount: replyCount, reactionScores: reactionScores, reactionCounts: reactionCounts, + mentionedUsers: mentionedUsers.map(jsonEncode).toList(), status: status, updatedAt: updatedAt, extraData: extraData, diff --git a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart index 20842307..003802b9 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart @@ -4,7 +4,7 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// Useful mapping functions for [ReactionEntity] extension ReactionEntityX on ReactionEntity { /// Maps a [ReactionEntity] into [Reaction] - Reaction toReaction({User user}) => Reaction( + Reaction toReaction({User? user}) => Reaction( extraData: extraData, type: type, createdAt: createdAt, @@ -22,8 +22,8 @@ extension ReactionX on Reaction { extraData: extraData, type: type, createdAt: createdAt, - userId: userId, - messageId: messageId, + userId: userId!, + messageId: messageId!, score: score, ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart index 9664030e..889776e2 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart @@ -4,7 +4,7 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; /// Useful mapping functions for [ReadEntity] extension ReadEntityX on ReadEntity { /// Maps a [ReadEntity] into [Read] - Read toRead({User user}) => Read( + Read toRead({required User user}) => Read( user: user, lastRead: lastRead, unreadMessages: unreadMessages, @@ -14,9 +14,9 @@ extension ReadEntityX on ReadEntity { /// Useful mapping functions for [Read] extension ReadX on Read { /// Maps a [Read] into [ReadEntity] - ReadEntity toEntity({String cid}) => ReadEntity( + ReadEntity toEntity({required String cid}) => ReadEntity( lastRead: lastRead, - userId: user?.id, + userId: user.id, channelCid: cid, unreadMessages: unreadMessages, ); 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 3a8a5b5b..3f955eb2 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 @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart' show LogRecord; import 'package:meta/meta.dart'; import 'package:mutex/mutex.dart'; @@ -53,15 +54,20 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { if (record.stackTrace != null) print(record.stackTrace); } - Future _readProtected(Future Function() f) async { - T ret; - await _mutex.protectRead(() async { + Future _readProtected(AsyncValueGetter func) => + _mutex.protectRead(func); + + bool get _debugIsConnected { + assert(() { if (db == null) { - return; + throw StateError(''' + $runtimeType hasn't been connected yet or used after `disconnect` + was called. Consider calling `connect` to create a connection. + '''); } - ret = await f(); - }); - return ret; + return true; + }(), ''); + return true; } MoorChatDatabase _defaultDatabaseProvider( @@ -86,239 +92,281 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { } @override - Future getConnectionInfo() => _readProtected(() { - _logger.info('getConnectionInfo'); - return db.connectionEventDao.connectionEvent; - }); + Future getConnectionInfo() { + assert(_debugIsConnected, ''); + _logger.info('getConnectionInfo'); + return _readProtected(() => db!.connectionEventDao.connectionEvent); + } @override - Future updateConnectionInfo(Event event) => _readProtected(() { - _logger.info('updateConnectionInfo'); - return db.connectionEventDao.updateConnectionEvent(event); - }); + Future updateConnectionInfo(Event event) { + assert(_debugIsConnected, ''); + _logger.info('updateConnectionInfo'); + return _readProtected( + () => db!.connectionEventDao.updateConnectionEvent(event), + ); + } @override - Future updateLastSyncAt(DateTime lastSyncAt) => _readProtected(() { - _logger.info('updateLastSyncAt'); - return db.connectionEventDao.updateLastSyncAt(lastSyncAt); - }); + Future updateLastSyncAt(DateTime lastSyncAt) { + assert(_debugIsConnected, ''); + _logger.info('updateLastSyncAt'); + return _readProtected( + () => db!.connectionEventDao.updateLastSyncAt(lastSyncAt), + ); + } @override - Future getLastSyncAt() => _readProtected(() { - _logger.info('getLastSyncAt'); - return db.connectionEventDao.lastSyncAt; - }); + Future getLastSyncAt() { + assert(_debugIsConnected, ''); + _logger.info('getLastSyncAt'); + return _readProtected(() => db!.connectionEventDao.lastSyncAt); + } @override - Future deleteChannels(List cids) => _readProtected(() { - _logger.info('deleteChannels'); - return db.channelDao.deleteChannelByCids(cids); - }); + Future deleteChannels(List cids) { + assert(_debugIsConnected, ''); + _logger.info('deleteChannels'); + return _readProtected(() => db!.channelDao.deleteChannelByCids(cids)); + } @override - Future> getChannelCids() => _readProtected(() { - _logger.info('getChannelCids'); - return db.channelDao.cids; - }); + Future> getChannelCids() { + assert(_debugIsConnected, ''); + _logger.info('getChannelCids'); + return _readProtected(() => db!.channelDao.cids); + } @override - Future deleteMessageByIds(List messageIds) => - _readProtected(() { - _logger.info('deleteMessageByIds'); - return db.messageDao.deleteMessageByIds(messageIds); - }); + Future deleteMessageByIds(List messageIds) { + assert(_debugIsConnected, ''); + _logger.info('deleteMessageByIds'); + return _readProtected(() => db!.messageDao.deleteMessageByIds(messageIds)); + } @override - Future deletePinnedMessageByIds(List messageIds) => - _readProtected(() { - _logger.info('deletePinnedMessageByIds'); - return db.pinnedMessageDao.deleteMessageByIds(messageIds); - }); + Future deletePinnedMessageByIds(List messageIds) { + assert(_debugIsConnected, ''); + _logger.info('deletePinnedMessageByIds'); + return _readProtected( + () => db!.pinnedMessageDao.deleteMessageByIds(messageIds), + ); + } @override - Future deleteMessageByCids(List cids) => _readProtected(() { - _logger.info('deleteMessageByCids'); - return db.messageDao.deleteMessageByCids(cids); - }); + Future deleteMessageByCids(List cids) { + assert(_debugIsConnected, ''); + _logger.info('deleteMessageByCids'); + return _readProtected(() => db!.messageDao.deleteMessageByCids(cids)); + } @override - Future deletePinnedMessageByCids(List cids) => - _readProtected(() { - _logger.info('deletePinnedMessageByCids'); - return db.pinnedMessageDao.deleteMessageByCids(cids); - }); + Future deletePinnedMessageByCids(List cids) { + assert(_debugIsConnected, ''); + _logger.info('deletePinnedMessageByCids'); + return _readProtected(() => db!.pinnedMessageDao.deleteMessageByCids(cids)); + } @override - Future> getMembersByCid(String cid) => _readProtected(() { - _logger.info('getMembersByCid'); - return db.memberDao.getMembersByCid(cid); - }); + Future> getMembersByCid(String cid) { + assert(_debugIsConnected, ''); + _logger.info('getMembersByCid'); + return _readProtected(() => db!.memberDao.getMembersByCid(cid)); + } @override - Future getChannelByCid(String cid) => _readProtected(() { - _logger.info('getChannelByCid'); - return db.channelDao.getChannelByCid(cid); - }); + Future getChannelByCid(String cid) { + assert(_debugIsConnected, ''); + _logger.info('getChannelByCid'); + return _readProtected(() => db!.channelDao.getChannelByCid(cid)); + } @override Future> getMessagesByCid( String cid, { - PaginationParams messagePagination, - }) => - _readProtected(() { - _logger.info('getMessagesByCid'); - return db.messageDao.getMessagesByCid( - cid, - messagePagination: messagePagination, - ); - }); + PaginationParams? messagePagination, + }) { + assert(_debugIsConnected, ''); + _logger.info('getMessagesByCid'); + return _readProtected( + () => db!.messageDao.getMessagesByCid( + cid, + messagePagination: messagePagination, + ), + ); + } @override Future> getPinnedMessagesByCid( String cid, { - PaginationParams messagePagination, - }) => - _readProtected(() { - _logger.info('getPinnedMessagesByCid'); - return db.pinnedMessageDao.getMessagesByCid( - cid, - messagePagination: messagePagination, - ); - }); + PaginationParams? messagePagination, + }) { + assert(_debugIsConnected, ''); + _logger.info('getPinnedMessagesByCid'); + return _readProtected( + () => db!.pinnedMessageDao.getMessagesByCid( + cid, + messagePagination: messagePagination, + ), + ); + } @override - Future> getReadsByCid(String cid) => _readProtected(() { - _logger.info('getReadsByCid'); - return db.readDao.getReadsByCid(cid); - }); + Future> getReadsByCid(String cid) { + assert(_debugIsConnected, ''); + _logger.info('getReadsByCid'); + return _readProtected(() => db!.readDao.getReadsByCid(cid)); + } @override - Future>> getChannelThreads(String cid) async => - _readProtected(() async { - _logger.info('getChannelThreads'); - final messages = await db.messageDao.getThreadMessages(cid); - final messageByParentIdDictionary = >{}; - for (final message in messages) { - final parentId = message.parentId; - messageByParentIdDictionary[parentId] = [ - ...messageByParentIdDictionary[parentId] ?? [], - message - ]; - } - return messageByParentIdDictionary; - }); + Future>> getChannelThreads(String cid) { + assert(_debugIsConnected, ''); + _logger.info('getChannelThreads'); + return _readProtected(() async { + final messages = await db!.messageDao.getThreadMessages(cid); + final messageByParentIdDictionary = >{}; + for (final message in messages) { + final parentId = message.parentId!; + messageByParentIdDictionary[parentId] = [ + ...messageByParentIdDictionary[parentId] ?? [], + message + ]; + } + return messageByParentIdDictionary; + }); + } @override Future> getReplies( String parentId, { - PaginationParams options, - }) => - _readProtected(() async { - _logger.info('getReplies'); - return db.messageDao.getThreadMessagesByParentId( - parentId, - options: options, - ); - }); + PaginationParams? options, + }) { + assert(_debugIsConnected, ''); + _logger.info('getReplies'); + return _readProtected( + () => db!.messageDao.getThreadMessagesByParentId( + parentId, + options: options, + ), + ); + } @override Future> getChannelStates({ - Map filter, - List> sort = const [], - PaginationParams paginationParams, - }) async => - _readProtected(() async { - _logger.info('getChannelStates'); - final channels = await db.channelQueryDao.getChannels( + Map? filter, + List>? sort, + PaginationParams? paginationParams, + }) { + assert(_debugIsConnected, ''); + _logger.info('getChannelStates'); + return _readProtected( + () async { + final channels = await db!.channelQueryDao.getChannels( filter: filter, sort: sort, paginationParams: paginationParams, ); return Future.wait(channels.map((e) => getChannelStateByCid(e.cid))); - }); + }, + ); + } @override Future updateChannelQueries( Map filter, List cids, { bool clearQueryCache = false, - }) => - _readProtected(() async { - _logger.info('updateChannelQueries'); - return db.channelQueryDao.updateChannelQueries( - filter, - cids, - clearQueryCache: clearQueryCache, - ); - }); + }) { + assert(_debugIsConnected, ''); + _logger.info('updateChannelQueries'); + return _readProtected( + () => db!.channelQueryDao.updateChannelQueries( + filter, + cids, + clearQueryCache: clearQueryCache, + ), + ); + } @override - Future updateChannels(List channels) => - _readProtected(() async { - _logger.info('updateChannels'); - return db.channelDao.updateChannels(channels); - }); + Future updateChannels(List channels) { + assert(_debugIsConnected, ''); + _logger.info('updateChannels'); + return _readProtected(() => db!.channelDao.updateChannels(channels)); + } @override - Future updateMembers(String cid, List members) => - _readProtected(() async { - _logger.info('updateMembers'); - return db.memberDao.updateMembers(cid, members); - }); + Future updateMembers(String cid, List members) { + assert(_debugIsConnected, ''); + _logger.info('updateMembers'); + return _readProtected(() => db!.memberDao.updateMembers(cid, members)); + } @override - Future updateMessages(String cid, List messages) => - _readProtected(() async { - _logger.info('updateMessages'); - return db.messageDao.updateMessages(cid, messages); - }); + Future updateMessages(String cid, List messages) { + assert(_debugIsConnected, ''); + _logger.info('updateMessages'); + return _readProtected(() => db!.messageDao.updateMessages(cid, messages)); + } @override - Future updatePinnedMessages(String cid, List messages) => - _readProtected(() async { - _logger.info('updatePinnedMessages'); - return db.pinnedMessageDao.updateMessages(cid, messages); - }); + Future updatePinnedMessages(String cid, List messages) { + assert(_debugIsConnected, ''); + _logger.info('updatePinnedMessages'); + return _readProtected( + () => db!.pinnedMessageDao.updateMessages(cid, messages), + ); + } @override - Future updateReactions(List reactions) => - _readProtected(() async { - _logger.info('updateReactions'); - return db.reactionDao.updateReactions(reactions); - }); + Future updateReactions(List reactions) { + assert(_debugIsConnected, ''); + _logger.info('updateReactions'); + return _readProtected(() => db!.reactionDao.updateReactions(reactions)); + } @override - Future updateReads(String cid, List reads) => - _readProtected(() async { - _logger.info('updateReads'); - return db.readDao.updateReads(cid, reads); - }); + Future updateReads(String cid, List reads) { + assert(_debugIsConnected, ''); + _logger.info('updateReads'); + return _readProtected(() => db!.readDao.updateReads(cid, reads)); + } @override - Future updateUsers(List users) => _readProtected(() async { - _logger.info('updateUsers'); - return db.userDao.updateUsers(users); - }); + Future updateUsers(List users) { + assert(_debugIsConnected, ''); + _logger.info('updateUsers'); + return _readProtected(() => db!.userDao.updateUsers(users)); + } @override - Future deleteReactionsByMessageId(List messageIds) => - _readProtected(() async { - _logger.info('deleteReactionsByMessageId'); - return db.reactionDao.deleteReactionsByMessageIds(messageIds); - }); + Future deleteReactionsByMessageId(List messageIds) { + assert(_debugIsConnected, ''); + _logger.info('deleteReactionsByMessageId'); + return _readProtected( + () => db!.reactionDao.deleteReactionsByMessageIds(messageIds), + ); + } @override - Future deleteMembersByCids(List cids) => - _readProtected(() async { - _logger.info('deleteMembersByCids'); - return db.memberDao.deleteMemberByCids(cids); - }); + Future deleteMembersByCids(List cids) { + assert(_debugIsConnected, ''); + _logger.info('deleteMembersByCids'); + return _readProtected(() => db!.memberDao.deleteMemberByCids(cids)); + } @override - Future updateChannelStates(List channelStates) => - _readProtected(() async => db.transaction(() async { - await super.updateChannelStates(channelStates); - })); + Future updateChannelStates(List channelStates) { + assert(_debugIsConnected, ''); + _logger.info('updateChannelStates'); + return _readProtected( + () async => db!.transaction( + () async { + await super.updateChannelStates(channelStates); + }, + ), + ); + } @override Future disconnect({bool flush = false}) async => @@ -328,11 +376,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { _logger.info('Disconnecting'); if (flush) { _logger.info('Flushing'); - await db!.batch((batch) { - db!.allTables.forEach((table) { - db!.delete(table).go(); - }); - }); + await db!.flush(); } await db!.disconnect(); db = null; diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index f23bc79c..70c1e381 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -5,8 +5,6 @@ version: 1.5.1 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues -publish_to: none - environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/stream_chat_persistence/test/mock_chat_database.dart b/packages/stream_chat_persistence/test/mock_chat_database.dart index 53a9a178..b421fad1 100644 --- a/packages/stream_chat_persistence/test/mock_chat_database.dart +++ b/packages/stream_chat_persistence/test/mock_chat_database.dart @@ -3,53 +3,59 @@ import 'package:stream_chat_persistence/src/dao/dao.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; class MockChatDatabase extends Mock implements MoorChatDatabase { - UserDao _userDao; + UserDao? _userDao; @override UserDao get userDao => _userDao ??= MockUserDao(); - ChannelDao _channelDao; + ChannelDao? _channelDao; @override ChannelDao get channelDao => _channelDao ??= MockChannelDao(); - MessageDao _messageDao; + MessageDao? _messageDao; @override MessageDao get messageDao => _messageDao ??= MockMessageDao(); - PinnedMessageDao _pinnedMessageDao; + PinnedMessageDao? _pinnedMessageDao; @override PinnedMessageDao get pinnedMessageDao => _pinnedMessageDao ??= MockPinnedMessageDao(); - MemberDao _memberDao; + MemberDao? _memberDao; @override MemberDao get memberDao => _memberDao ??= MockMemberDao(); - ReactionDao _reactionDao; + ReactionDao? _reactionDao; @override ReactionDao get reactionDao => _reactionDao ??= MockReactionDao(); - ReadDao _readDao; + ReadDao? _readDao; @override ReadDao get readDao => _readDao ??= MockReadDao(); - ChannelQueryDao _channelQueryDao; + ChannelQueryDao? _channelQueryDao; @override ChannelQueryDao get channelQueryDao => _channelQueryDao ??= MockChannelQueryDao(); - ConnectionEventDao _connectionEventDao; + ConnectionEventDao? _connectionEventDao; @override ConnectionEventDao get connectionEventDao => _connectionEventDao ??= MockConnectionEventDao(); + + @override + Future flush() => Future.value(); + + @override + Future disconnect() => Future.value(); } class MockUserDao extends Mock implements UserDao {} diff --git a/packages/stream_chat_persistence/test/src/converter/list_coverter_test.dart b/packages/stream_chat_persistence/test/src/converter/list_coverter_test.dart index a5bedd40..73ce2452 100644 --- a/packages/stream_chat_persistence/test/src/converter/list_coverter_test.dart +++ b/packages/stream_chat_persistence/test/src/converter/list_coverter_test.dart @@ -32,7 +32,7 @@ void main() { test('should return list of String if json data list is provided', () { final data = ['data1', 'data2', 'data3']; final res = listConverter.mapToDart(jsonEncode(data)); - expect(res.length, data.length); + expect(res!.length, data.length); }); }); diff --git a/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart index 47c655a8..d6ac8123 100644 --- a/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart @@ -4,8 +4,8 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; void main() { - ChannelDao channelDao; - MoorChatDatabase database; + late ChannelDao channelDao; + late MoorChatDatabase database; setUp(() { database = MoorChatDatabase.testable('testUserId'); @@ -32,7 +32,8 @@ void main() { // Should match the dummy channel final updatedChannel = await channelDao.getChannelByCid(cid); - expect(updatedChannel.id, id); + expect(updatedChannel, isNotNull); + expect(updatedChannel!.id, id); expect(updatedChannel.cid, cid); expect(updatedChannel.type, type); }); @@ -53,7 +54,8 @@ void main() { // Should match the dummy channel final updatedChannel = await channelDao.getChannelByCid(cid); - expect(updatedChannel.id, id); + expect(updatedChannel, isNotNull); + expect(updatedChannel!.id, id); expect(updatedChannel.cid, cid); expect(updatedChannel.type, type); @@ -108,7 +110,8 @@ void main() { // Should match the dummy channel final updatedChannel = await channelDao.getChannelByCid(cid); - expect(updatedChannel.id, id); + expect(updatedChannel, isNotNull); + expect(updatedChannel!.id, id); expect(updatedChannel.cid, cid); expect(updatedChannel.type, type); @@ -119,7 +122,8 @@ void main() { // Should match the new channel final newUpdatedChannel = await channelDao.getChannelByCid(cid); - expect(newUpdatedChannel.id, id); + expect(newUpdatedChannel, isNotNull); + expect(newUpdatedChannel!.id, id); expect(newUpdatedChannel.cid, cid); expect(newUpdatedChannel.type, newType); }); 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 4566ca94..dee6e076 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 @@ -8,8 +8,8 @@ import 'package:test/test.dart'; import '../utils/date_matcher.dart'; void main() { - MoorChatDatabase database; - ChannelQueryDao channelQueryDao; + late MoorChatDatabase database; + late ChannelQueryDao channelQueryDao; setUp(() { database = MoorChatDatabase.testable('testUserId'); @@ -147,7 +147,7 @@ void main() { // Should match lastMessageAt date expect( updatedChannel.lastMessageAt, - isSameDateAs(insertedChannel.lastMessageAt), + isSameDateAs(insertedChannel.lastMessageAt!), ); } }); @@ -160,10 +160,7 @@ void main() { const pagination = PaginationParams(offset: offset, limit: limit); // Inserting test data for get channels - final insertedChannels = await _insertTestDataForGetChannel( - filter, - count: 30, - ); + await _insertTestDataForGetChannel(filter, count: 30); // Should match with the inserted channels final updatedChannels = await channelQueryDao.getChannels( @@ -210,7 +207,7 @@ void main() { // Should match lastMessageAt date expect( updatedChannel.lastMessageAt, - isSameDateAs(insertedChannel.lastMessageAt), + isSameDateAs(insertedChannel.lastMessageAt!), ); } }); @@ -226,8 +223,8 @@ void main() { test('should return sorted channels using custom field', () async { int sortComparator(ChannelModel a, ChannelModel b) { - final aData = a.extraData['test_custom_field'] as int; - final bData = b.extraData['test_custom_field'] as int; + final aData = a.extraData!['test_custom_field'] as int; + final bData = b.extraData!['test_custom_field'] as int; return bData.compareTo(aData); } @@ -261,7 +258,7 @@ void main() { // Should match lastMessageAt date expect( updatedChannel.lastMessageAt, - isSameDateAs(insertedChannel.lastMessageAt), + isSameDateAs(insertedChannel.lastMessageAt!), ); } }); diff --git a/packages/stream_chat_persistence/test/src/dao/connection_event_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/connection_event_dao_test.dart index 2969751b..536fe3bc 100644 --- a/packages/stream_chat_persistence/test/src/dao/connection_event_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/connection_event_dao_test.dart @@ -6,8 +6,8 @@ import 'package:test/test.dart'; import '../utils/date_matcher.dart'; void main() { - ConnectionEventDao eventDao; - MoorChatDatabase database; + late ConnectionEventDao eventDao; + late MoorChatDatabase database; setUp(() { database = MoorChatDatabase.testable('testUserId'); @@ -30,7 +30,8 @@ void main() { // Should match the added event final updatedEvent = await eventDao.connectionEvent; - expect(updatedEvent.me.id, newEvent.me.id); + expect(updatedEvent, isNotNull); + expect(updatedEvent!.me!.id, newEvent.me!.id); expect(updatedEvent.totalUnreadCount, newEvent.totalUnreadCount); expect(updatedEvent.unreadChannels, newEvent.unreadChannels); }); @@ -70,7 +71,8 @@ void main() { // Should match the previously added event final fetchedEvent = await eventDao.connectionEvent; - expect(fetchedEvent.me.id, event.me.id); + expect(fetchedEvent, isNotNull); + expect(fetchedEvent!.me!.id, event.me!.id); expect(fetchedEvent.totalUnreadCount, event.totalUnreadCount); expect(fetchedEvent.unreadChannels, event.unreadChannels); @@ -80,7 +82,8 @@ void main() { // Should match the updated event final fetchedNewEvent = await eventDao.connectionEvent; - expect(fetchedNewEvent.me.id, event.me.id); + expect(fetchedNewEvent, isNotNull); + expect(fetchedNewEvent!.me!.id, event.me!.id); expect(fetchedNewEvent.totalUnreadCount, event.totalUnreadCount); expect(fetchedNewEvent.unreadChannels, newEvent.unreadChannels); }); diff --git a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart index dbb8c73c..6654de4c 100644 --- a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart @@ -8,8 +8,8 @@ import 'package:test/test.dart'; import '../utils/date_matcher.dart'; void main() { - MemberDao memberDao; - MoorChatDatabase database; + late MemberDao memberDao; + late MoorChatDatabase database; setUp(() { database = MoorChatDatabase.testable('testUserId'); @@ -53,7 +53,7 @@ void main() { for (var i = 0; i < fetchedMembers.length; i++) { final member = memberList[i]; final fetchedMember = fetchedMembers[i]; - expect(fetchedMember.user.id, member.user.id); + expect(fetchedMember.user!.id, member.user!.id); expect(fetchedMember.banned, member.banned); expect(fetchedMember.shadowBanned, member.shadowBanned); expect(fetchedMember.createdAt, isSameDateAs(member.createdAt)); @@ -63,7 +63,7 @@ void main() { expect(fetchedMember.updatedAt, isSameDateAs(member.updatedAt)); expect( fetchedMember.inviteAcceptedAt, - isSameDateAs(member.inviteAcceptedAt), + isSameDateAs(member.inviteAcceptedAt!), ); } }); @@ -80,7 +80,7 @@ void main() { for (var i = 0; i < fetchedMembers.length; i++) { final member = memberList[i]; final fetchedMember = fetchedMembers[i]; - expect(fetchedMember.user.id, member.user.id); + expect(fetchedMember.user!.id, member.user!.id); expect(fetchedMember.banned, member.banned); expect(fetchedMember.shadowBanned, member.shadowBanned); expect(fetchedMember.createdAt, isSameDateAs(member.createdAt)); @@ -90,7 +90,7 @@ void main() { expect(fetchedMember.updatedAt, isSameDateAs(member.updatedAt)); expect( fetchedMember.inviteAcceptedAt, - isSameDateAs(member.inviteAcceptedAt), + isSameDateAs(member.inviteAcceptedAt!), ); } @@ -118,13 +118,13 @@ void main() { expect(newFetchedMembers.length, fetchedMembers.length + 1); expect( newFetchedMembers - .firstWhere((it) => it.user.id == copyMember.user.id) + .firstWhere((it) => it.user!.id == copyMember.user!.id) .banned, true, ); expect( newFetchedMembers - .where((it) => it.user.id == newMember.user.id) + .where((it) => it.user!.id == newMember.user!.id) .isNotEmpty, true, ); diff --git a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart index f2440ae1..0d986590 100644 --- a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart @@ -6,8 +6,8 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; void main() { - MessageDao messageDao; - MoorChatDatabase database; + late MessageDao messageDao; + late MoorChatDatabase database; setUp(() { database = MoorChatDatabase.testable('testUserId'); @@ -32,7 +32,7 @@ void main() { shadowed: math.Random().nextBool(), replyCount: index, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text #$index', pinned: math.Random().nextBool(), pinnedAt: DateTime.now(), @@ -49,7 +49,7 @@ void main() { shadowed: math.Random().nextBool(), replyCount: index, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text #$index', quotedMessageId: messages[index].id, pinned: math.Random().nextBool(), @@ -69,7 +69,7 @@ void main() { shadowed: math.Random().nextBool(), replyCount: index, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text #$index', pinned: math.Random().nextBool(), pinnedAt: DateTime.now(), @@ -168,7 +168,8 @@ void main() { // Fetched message id should match the inserted message id final fetchedMessage = await messageDao.getMessageById(id); - expect(fetchedMessage.id, insertedMessages.first.id); + expect(fetchedMessage, isNotNull); + expect(fetchedMessage!.id, insertedMessages.first.id); }); test('getThreadMessages', () async { @@ -332,7 +333,7 @@ void main() { showInChannel: math.Random().nextBool(), replyCount: 4, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text #4', pinned: math.Random().nextBool(), pinnedAt: DateTime.now(), diff --git a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart index de7c8976..33bf96b1 100644 --- a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart @@ -6,8 +6,8 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; void main() { - PinnedMessageDao pinnedMessageDao; - MoorChatDatabase database; + late PinnedMessageDao pinnedMessageDao; + late MoorChatDatabase database; setUp(() { database = MoorChatDatabase.testable('testUserId'); @@ -32,7 +32,7 @@ void main() { shadowed: math.Random().nextBool(), replyCount: index, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text #$index', pinned: math.Random().nextBool(), pinnedAt: DateTime.now(), @@ -49,7 +49,7 @@ void main() { shadowed: math.Random().nextBool(), replyCount: index, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text #$index', quotedMessageId: messages[index].id, pinned: math.Random().nextBool(), @@ -69,7 +69,7 @@ void main() { shadowed: math.Random().nextBool(), replyCount: index, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text #$index', pinned: math.Random().nextBool(), pinnedAt: DateTime.now(), @@ -168,7 +168,8 @@ void main() { // Fetched message id should match the inserted message id final fetchedMessage = await pinnedMessageDao.getMessageById(id); - expect(fetchedMessage.id, insertedMessages.first.id); + expect(fetchedMessage, isNotNull); + expect(fetchedMessage!.id, insertedMessages.first.id); }); test('getThreadMessages', () async { @@ -333,7 +334,7 @@ void main() { showInChannel: math.Random().nextBool(), replyCount: 4, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text #4', pinned: math.Random().nextBool(), pinnedAt: DateTime.now(), diff --git a/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart index 94f8ebdd..73ff63fe 100644 --- a/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart @@ -6,8 +6,8 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; void main() { - ReactionDao reactionDao; - MoorChatDatabase database; + late ReactionDao reactionDao; + late MoorChatDatabase database; setUp(() { database = MoorChatDatabase.testable('testUserId'); @@ -16,7 +16,7 @@ void main() { Future> _prepareReactionData( String messageId, { - String userId, + String? userId, int count = 3, }) async { final users = List.generate(count, (index) => User(id: 'testUserId$index')); @@ -29,7 +29,7 @@ void main() { showInChannel: math.Random().nextBool(), replyCount: 3, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text', pinned: math.Random().nextBool(), pinnedAt: DateTime.now(), diff --git a/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart index 52207f68..77b7a7f9 100644 --- a/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart @@ -6,8 +6,8 @@ import 'package:test/test.dart'; import '../utils/date_matcher.dart'; void main() { - ReadDao readDao; - MoorChatDatabase database; + late ReadDao readDao; + late MoorChatDatabase database; setUp(() { database = MoorChatDatabase.testable('testUserId'); diff --git a/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart index 8c5898a0..5332a298 100644 --- a/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart @@ -6,8 +6,8 @@ import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; void main() { - UserDao userDao; - MoorChatDatabase database; + late UserDao userDao; + late MoorChatDatabase database; setUp(() { database = MoorChatDatabase.testable('testUserId'); diff --git a/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart index dc02ee4b..93789cc7 100644 --- a/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart @@ -34,15 +34,21 @@ void main() { expect(channelModel.updatedAt, isSameDateAs(entity.updatedAt)); expect(channelModel.memberCount, entity.memberCount); expect(channelModel.cid, entity.cid); - expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt)); - expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt)); + expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt!)); + expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt!)); expect(channelModel.extraData, entity.extraData); - expect(channelModel.createdBy.id, entity.createdById); + expect(channelModel.createdBy!.id, entity.createdById); }); test('toChannelState should map entity into ChannelState ', () { final members = List.generate(3, (index) => Member()); - final reads = List.generate(3, (index) => Read()); + final reads = List.generate( + 3, + (index) => Read( + user: User(id: 'testUserId$index'), + lastRead: DateTime.now(), + ), + ); final messages = List.generate(3, (index) => Message()); final channelState = entity.toChannelState( @@ -59,7 +65,7 @@ void main() { expect(channelState.messages.length, messages.length); expect(channelState.pinnedMessages.length, messages.length); - final channelModel = channelState.channel; + final channelModel = channelState.channel!; expect(channelModel.id, entity.id); expect(channelModel.config.toJson()['max_message_length'], 33); expect(channelModel.frozen, entity.frozen); @@ -67,10 +73,10 @@ void main() { expect(channelModel.updatedAt, isSameDateAs(entity.updatedAt)); expect(channelModel.memberCount, entity.memberCount); expect(channelModel.cid, entity.cid); - expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt)); - expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt)); + expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt!)); + expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt!)); expect(channelModel.extraData, entity.extraData); - expect(channelModel.createdBy.id, entity.createdById); + expect(channelModel.createdBy!.id, entity.createdById); }); }); @@ -103,9 +109,9 @@ void main() { expect(channelEntity.updatedAt, isSameDateAs(model.updatedAt)); expect(channelEntity.memberCount, model.memberCount); expect(channelEntity.cid, model.cid); - expect(channelEntity.lastMessageAt, isSameDateAs(model.lastMessageAt)); - expect(channelEntity.deletedAt, isSameDateAs(model.deletedAt)); + expect(channelEntity.lastMessageAt, isSameDateAs(model.lastMessageAt!)); + expect(channelEntity.deletedAt, isSameDateAs(model.deletedAt!)); expect(channelEntity.extraData, model.extraData); - expect(channelEntity.createdById, model.createdBy.id); + expect(channelEntity.createdById, model.createdBy!.id); }); } diff --git a/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart index 240ce1a7..cb977aa5 100644 --- a/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart @@ -16,7 +16,7 @@ void main() { ); final event = entity.toEvent(); expect(event, isA()); - expect(event.me.id, ownUser.id); + expect(event.me!.id, ownUser.id); expect(event.totalUnreadCount, entity.totalUnreadCount); expect(event.unreadChannels, entity.unreadChannels); }); diff --git a/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart index 0f7d4f11..feca753b 100644 --- a/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart @@ -25,12 +25,12 @@ void main() { ); final member = entity.toMember(user: user); expect(member, isA()); - expect(member.user.id, entity.userId); + expect(member.user!.id, entity.userId); expect(member.createdAt, isSameDateAs(entity.createdAt)); expect(member.updatedAt, isSameDateAs(entity.updatedAt)); expect(member.role, entity.role); - expect(member.inviteAcceptedAt, isSameDateAs(entity.inviteAcceptedAt)); - expect(member.inviteRejectedAt, isSameDateAs(entity.inviteRejectedAt)); + expect(member.inviteAcceptedAt, isSameDateAs(entity.inviteAcceptedAt!)); + expect(member.inviteRejectedAt, isSameDateAs(entity.inviteRejectedAt!)); expect(member.invited, entity.invited); expect(member.banned, entity.banned); expect(member.shadowBanned, entity.shadowBanned); @@ -55,12 +55,12 @@ void main() { final entity = member.toEntity(cid: cid); expect(entity, isA()); expect(entity.channelCid, cid); - expect(entity.userId, member.user.id); + expect(entity.userId, member.user!.id); expect(entity.createdAt, isSameDateAs(member.createdAt)); expect(entity.updatedAt, isSameDateAs(member.updatedAt)); expect(entity.role, member.role); - expect(entity.inviteAcceptedAt, isSameDateAs(member.inviteAcceptedAt)); - expect(entity.inviteRejectedAt, isSameDateAs(member.inviteRejectedAt)); + expect(entity.inviteAcceptedAt, isSameDateAs(member.inviteAcceptedAt!)); + expect(entity.inviteRejectedAt, isSameDateAs(member.inviteRejectedAt!)); expect(entity.invited, member.invited); expect(entity.banned, member.banned); expect(entity.shadowBanned, member.shadowBanned); diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart index 21a05da8..f73102f8 100644 --- a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -32,7 +32,7 @@ void main() { ); final entity = MessageEntity( id: 'testMessageId', - attachments: attachments?.map((it) => jsonEncode(it.toData()))?.toList(), + attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), channelCid: 'testCid', type: 'testType', parentId: 'testParentId', @@ -46,8 +46,9 @@ void main() { reactionCounts: reactions.fold( {}, (prev, curr) => - prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1), + prev?..update(curr.type, (value) => value + 1, ifAbsent: () => 1), ), + mentionedUsers: const [], status: MessageSendingStatus.sent, updatedAt: DateTime.now(), extraData: {'extra_test_data': 'extraData'}, @@ -82,13 +83,13 @@ void main() { expect(message.status, entity.status); expect(message.updatedAt, isSameDateAs(entity.updatedAt)); expect(message.extraData, entity.extraData); - expect(message.user.id, entity.userId); - expect(message.deletedAt, isSameDateAs(entity.deletedAt)); + expect(message.user!.id, entity.userId); + expect(message.deletedAt, isSameDateAs(entity.deletedAt!)); expect(message.text, entity.messageText); expect(message.pinned, entity.pinned); - expect(message.pinExpires, isSameDateAs(entity.pinExpires)); - expect(message.pinnedAt, isSameDateAs(entity.pinnedAt)); - expect(message.pinnedBy.id, entity.pinnedByUserId); + expect(message.pinExpires, isSameDateAs(entity.pinExpires!)); + expect(message.pinnedAt, isSameDateAs(entity.pinnedAt!)); + expect(message.pinnedBy!.id, entity.pinnedByUserId); expect(message.reactionCounts, entity.reactionCounts); expect(message.reactionScores, entity.reactionScores); for (var i = 0; i < message.attachments.length; i++) { @@ -138,11 +139,11 @@ void main() { reactionCounts: reactions.fold( {}, (prev, curr) => - prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1), + prev?..update(curr.type, (value) => value + 1, ifAbsent: () => 1), ), status: MessageSendingStatus.sending, updatedAt: DateTime.now(), - extraData: {'extra_test_data': 'extraData'}, + extraData: const {'extra_test_data': 'extraData'}, user: user, deletedAt: DateTime.now(), text: 'dummy text', @@ -167,18 +168,18 @@ void main() { expect(entity.status, message.status); expect(entity.updatedAt, isSameDateAs(message.updatedAt)); expect(entity.extraData, message.extraData); - expect(entity.userId, message.user.id); - expect(entity.deletedAt, isSameDateAs(message.deletedAt)); + expect(entity.userId, message.user!.id); + expect(entity.deletedAt, isSameDateAs(message.deletedAt!)); expect(entity.messageText, message.text); expect(entity.pinned, message.pinned); - expect(entity.pinExpires, isSameDateAs(message.pinExpires)); - expect(entity.pinnedAt, isSameDateAs(message.pinnedAt)); - expect(entity.pinnedByUserId, message.pinnedBy.id); + expect(entity.pinExpires, isSameDateAs(message.pinExpires!)); + expect(entity.pinnedAt, isSameDateAs(message.pinnedAt!)); + expect(entity.pinnedByUserId, message.pinnedBy!.id); expect(entity.reactionCounts, message.reactionCounts); expect(entity.reactionScores, message.reactionScores); expect( entity.attachments, - message.attachments?.map((it) => jsonEncode(it.toData()))?.toList(), + message.attachments.map((it) => jsonEncode(it.toData())).toList(), ); }); } diff --git a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart index 0aadf08a..8b5f4db4 100644 --- a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart @@ -32,7 +32,7 @@ void main() { ); final entity = PinnedMessageEntity( id: 'testMessageId', - attachments: attachments?.map((it) => jsonEncode(it.toData()))?.toList(), + attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), channelCid: 'testCid', type: 'testType', parentId: 'testParentId', @@ -46,8 +46,9 @@ void main() { reactionCounts: reactions.fold( {}, (prev, curr) => - prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1), + prev?..update(curr.type, (value) => value + 1, ifAbsent: () => 1), ), + mentionedUsers: [], status: MessageSendingStatus.sent, updatedAt: DateTime.now(), extraData: {'extra_test_data': 'extraData'}, @@ -82,13 +83,13 @@ void main() { expect(message.status, entity.status); expect(message.updatedAt, isSameDateAs(entity.updatedAt)); expect(message.extraData, entity.extraData); - expect(message.user.id, entity.userId); - expect(message.deletedAt, isSameDateAs(entity.deletedAt)); + expect(message.user!.id, entity.userId); + expect(message.deletedAt, isSameDateAs(entity.deletedAt!)); expect(message.text, entity.messageText); expect(message.pinned, entity.pinned); - expect(message.pinExpires, isSameDateAs(entity.pinExpires)); - expect(message.pinnedAt, isSameDateAs(entity.pinnedAt)); - expect(message.pinnedBy.id, entity.pinnedByUserId); + expect(message.pinExpires, isSameDateAs(entity.pinExpires!)); + expect(message.pinnedAt, isSameDateAs(entity.pinnedAt!)); + expect(message.pinnedBy!.id, entity.pinnedByUserId); expect(message.reactionCounts, entity.reactionCounts); expect(message.reactionScores, entity.reactionScores); for (var i = 0; i < message.attachments.length; i++) { @@ -138,11 +139,11 @@ void main() { reactionCounts: reactions.fold( {}, (prev, curr) => - prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1), + prev?..update(curr.type, (value) => value + 1, ifAbsent: () => 1), ), status: MessageSendingStatus.sending, updatedAt: DateTime.now(), - extraData: {'extra_test_data': 'extraData'}, + extraData: const {'extra_test_data': 'extraData'}, user: user, deletedAt: DateTime.now(), text: 'dummy text', @@ -167,18 +168,18 @@ void main() { expect(entity.status, message.status); expect(entity.updatedAt, isSameDateAs(message.updatedAt)); expect(entity.extraData, message.extraData); - expect(entity.userId, message.user.id); - expect(entity.deletedAt, isSameDateAs(message.deletedAt)); + expect(entity.userId, message.user!.id); + expect(entity.deletedAt, isSameDateAs(message.deletedAt!)); expect(entity.messageText, message.text); expect(entity.pinned, message.pinned); - expect(entity.pinExpires, isSameDateAs(message.pinExpires)); - expect(entity.pinnedAt, isSameDateAs(message.pinnedAt)); - expect(entity.pinnedByUserId, message.pinnedBy.id); + expect(entity.pinExpires, isSameDateAs(message.pinExpires!)); + expect(entity.pinnedAt, isSameDateAs(message.pinnedAt!)); + expect(entity.pinnedByUserId, message.pinnedBy!.id); expect(entity.reactionCounts, message.reactionCounts); expect(entity.reactionScores, message.reactionScores); expect( entity.attachments, - message.attachments?.map((it) => jsonEncode(it.toData()))?.toList(), + message.attachments.map((it) => jsonEncode(it.toData())).toList(), ); }); } diff --git a/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart index 5e40f237..adc05c58 100644 --- a/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart @@ -24,7 +24,7 @@ void main() { expect(user.role, entity.role); expect(user.createdAt, isSameDateAs(entity.createdAt)); expect(user.updatedAt, isSameDateAs(entity.updatedAt)); - expect(user.lastActive, isSameDateAs(entity.lastActive)); + expect(user.lastActive, isSameDateAs(entity.lastActive!)); expect(user.online, entity.online); expect(user.banned, entity.banned); expect(user.extraData, entity.extraData); @@ -47,7 +47,7 @@ void main() { expect(entity.role, user.role); expect(entity.createdAt, isSameDateAs(user.createdAt)); expect(entity.updatedAt, isSameDateAs(user.updatedAt)); - expect(entity.lastActive, isSameDateAs(user.lastActive)); + expect(entity.lastActive, isSameDateAs(user.lastActive!)); expect(entity.online, user.online); expect(entity.banned, user.banned); expect(entity.extraData, user.extraData); diff --git a/packages/stream_chat_persistence/test/src/utils/date_matcher.dart b/packages/stream_chat_persistence/test/src/utils/date_matcher.dart index 909e58b0..dec68db0 100644 --- a/packages/stream_chat_persistence/test/src/utils/date_matcher.dart +++ b/packages/stream_chat_persistence/test/src/utils/date_matcher.dart @@ -1,13 +1,10 @@ import 'package:test/test.dart'; -import 'package:meta/meta.dart'; Matcher isSameDateAs(DateTime targetDate) => _IsSameDateAs(targetDate: targetDate); class _IsSameDateAs extends Matcher { - const _IsSameDateAs({ - @required this.targetDate, - }) : assert(targetDate != null, ''); + const _IsSameDateAs({required this.targetDate}); final DateTime targetDate; diff --git a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart index 2d44ca15..26d0bfb5 100644 --- a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart +++ b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart @@ -10,22 +10,6 @@ MoorChatDatabase _testDatabaseProvider(String userId, ConnectionMode mode) => MoorChatDatabase.testable(userId); void main() { - group('client constructor', () { - test('throws assertion error if null connectionMode is provided', () { - expect( - () => StreamChatPersistenceClient(connectionMode: null), - throwsA(isA()), - ); - }); - - test('throws assertion error if null logLevel is provided', () { - expect( - () => StreamChatPersistenceClient(logLevel: null), - throwsA(isA()), - ); - }); - }); - group('connect', () { const userId = 'testUserId'; test('successfully connects with the Database', () async { @@ -34,7 +18,7 @@ void main() { await client.connect(userId, databaseProvider: _testDatabaseProvider); expect(client.db, isNotNull); expect(client.db, isA()); - expect(client.db.userId, userId); + expect(client.db!.userId, userId); addTearDown(() async { await client.disconnect(); @@ -48,7 +32,7 @@ void main() { expect(client.db, isNotNull); expect(client.db, isNotNull); expect(client.db, isA()); - expect(client.db.userId, userId); + expect(client.db!.userId, userId); expect( () => client.connect(userId, databaseProvider: _testDatabaseProvider), throwsException, @@ -73,7 +57,7 @@ void main() { const userId = 'testUserId'; final mockDatabase = MockChatDatabase(); MoorChatDatabase _mockDatabaseProvider(_, __) => mockDatabase; - StreamChatPersistenceClient client; + late StreamChatPersistenceClient client; setUp(() async { client = StreamChatPersistenceClient(logLevel: Level.ALL); @@ -95,12 +79,13 @@ void main() { }); test('getConnectionInfo', () async { - final event = Event(type: 'testEvent'); + const event = Event(type: 'testEvent'); when(() => mockDatabase.connectionEventDao.connectionEvent) .thenAnswer((_) async => event); final fetchedEvent = await client.getConnectionInfo(); - expect(fetchedEvent.type, event.type); + expect(fetchedEvent, isNotNull); + expect(fetchedEvent!.type, event.type); verify(() => mockDatabase.connectionEventDao.connectionEvent).called(1); }); @@ -115,11 +100,9 @@ void main() { }); test('updateConnectionInfo', () async { - final event = Event(type: 'testEvent'); + const event = Event(type: 'testEvent'); when(() => mockDatabase.connectionEventDao.updateConnectionEvent(event)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) async => 1); await client.updateConnectionInfo(event); verify(() => mockDatabase.connectionEventDao.updateConnectionEvent(event)) @@ -129,9 +112,7 @@ void main() { test('updateLastSyncAt', () async { final lastSync = DateTime.now(); when(() => mockDatabase.connectionEventDao.updateLastSyncAt(lastSync)) - .thenAnswer((_) { - return; - }); + .thenAnswer((_) async => 1); await client.updateLastSyncAt(lastSync); verify(() => mockDatabase.connectionEventDao.updateLastSyncAt(lastSync)) @@ -149,13 +130,14 @@ void main() { }); test('getChannelByCid', () async { - const cid = 'testCid'; + const cid = 'testType:testId'; final channelModel = ChannelModel(cid: cid); when(() => mockDatabase.channelDao.getChannelByCid(cid)) .thenAnswer((_) async => channelModel); final fetchedChannelModel = await client.getChannelByCid(cid); - expect(fetchedChannelModel.cid, channelModel.cid); + expect(fetchedChannelModel, isNotNull); + expect(fetchedChannelModel!.cid, channelModel.cid); verify(() => mockDatabase.channelDao.getChannelByCid(cid)).called(1); }); @@ -172,7 +154,13 @@ void main() { test('getReadsByCid', () async { const cid = 'testCid'; - final reads = List.generate(3, (index) => Read()); + final reads = List.generate( + 3, + (index) => Read( + user: User(id: 'testUserId$index'), + lastRead: DateTime.now(), + ), + ); when(() => mockDatabase.readDao.getReadsByCid(cid)) .thenAnswer((_) async => reads); @@ -205,10 +193,16 @@ void main() { }); test('getChannelStateByCid', () async { - const cid = 'testCid'; + const cid = 'testType:testId'; final messages = List.generate(3, (index) => Message()); final members = List.generate(3, (index) => Member()); - final reads = List.generate(3, (index) => Read()); + final reads = List.generate( + 3, + (index) => Read( + user: User(id: 'testUserId$index'), + lastRead: DateTime.now(), + ), + ); final channel = ChannelModel(cid: cid); when(() => mockDatabase.memberDao.getMembersByCid(cid)) @@ -227,7 +221,7 @@ void main() { expect(fetchedChannelState.pinnedMessages.length, messages.length); expect(fetchedChannelState.members.length, members.length); expect(fetchedChannelState.read.length, reads.length); - expect(fetchedChannelState.channel.cid, channel.cid); + expect(fetchedChannelState.channel!.cid, channel.cid); verify(() => mockDatabase.memberDao.getMembersByCid(cid)).called(1); verify(() => mockDatabase.readDao.getReadsByCid(cid)).called(1); @@ -238,11 +232,17 @@ void main() { }); test('getChannelStates', () async { - const cid = 'testCid'; + const cid = 'testType:testId'; final channels = List.generate(3, (index) => ChannelModel(cid: cid)); final messages = List.generate(3, (index) => Message()); final members = List.generate(3, (index) => Member()); - final reads = List.generate(3, (index) => Read()); + final reads = List.generate( + 3, + (index) => Read( + user: User(id: 'testUserId$index'), + lastRead: DateTime.now(), + ), + ); final channel = ChannelModel(cid: cid); final channelStates = channels .map( @@ -279,7 +279,7 @@ void main() { expect(fetched.messages.length, original.messages.length); expect(fetched.pinnedMessages.length, original.pinnedMessages.length); expect(fetched.read.length, original.read.length); - expect(fetched.channel.cid, original.channel.cid); + expect(fetched.channel!.cid, original.channel!.cid); } verify(() => mockDatabase.channelQueryDao.getChannels()).called(1); @@ -296,9 +296,7 @@ void main() { const cids = []; when(() => mockDatabase.channelQueryDao.updateChannelQueries(filter, cids)) - .thenAnswer((realInvocation) async { - return; - }); + .thenAnswer((_) => Future.value()); await client.updateChannelQueries(filter, cids); verify(() => @@ -309,9 +307,7 @@ void main() { test('deleteMessageById', () async { const messageId = 'testMessageId'; when(() => mockDatabase.messageDao.deleteMessageByIds([messageId])) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) async => 1); await client.deleteMessageById(messageId); verify(() => mockDatabase.messageDao.deleteMessageByIds([messageId])) @@ -321,9 +317,7 @@ void main() { test('deletePinnedMessageById', () async { const messageId = 'testMessageId'; when(() => mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId])) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) async => 1); await client.deletePinnedMessageById(messageId); verify(() => @@ -334,9 +328,7 @@ void main() { test('deleteMessageByIds', () async { const messageIds = []; when(() => mockDatabase.messageDao.deleteMessageByIds(messageIds)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) async => 1); await client.deleteMessageByIds(messageIds); verify(() => mockDatabase.messageDao.deleteMessageByIds(messageIds)) @@ -346,9 +338,7 @@ void main() { test('deletePinnedMessageByIds', () async { const messageIds = []; when(() => mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) async => 1); await client.deletePinnedMessageByIds(messageIds); verify(() => mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds)) @@ -358,9 +348,7 @@ void main() { test('deleteMessageByCid', () async { const cid = 'testCid'; when(() => mockDatabase.messageDao.deleteMessageByCids([cid])) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) async => 1); await client.deleteMessageByCid(cid); verify(() => mockDatabase.messageDao.deleteMessageByCids([cid])) @@ -370,9 +358,7 @@ void main() { test('deletePinnedMessageByCid', () async { const cid = 'testCid'; when(() => mockDatabase.pinnedMessageDao.deleteMessageByCids([cid])) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) async => 1); await client.deletePinnedMessageByCid(cid); verify(() => mockDatabase.pinnedMessageDao.deleteMessageByCids([cid])) @@ -382,9 +368,7 @@ void main() { test('deleteMessageByCids', () async { const cids = []; when(() => mockDatabase.messageDao.deleteMessageByCids(cids)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) async => 1); await client.deleteMessageByCids(cids); verify(() => mockDatabase.messageDao.deleteMessageByCids(cids)).called(1); @@ -393,9 +377,7 @@ void main() { test('deletePinnedMessageByCids', () async { const cids = []; when(() => mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) async => 1); await client.deletePinnedMessageByCids(cids); verify(() => mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)) @@ -405,9 +387,7 @@ void main() { test('deleteChannels', () async { const cids = []; when(() => mockDatabase.channelDao.deleteChannelByCids(cids)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) async => 1); await client.deleteChannels(cids); verify(() => mockDatabase.channelDao.deleteChannelByCids(cids)).called(1); @@ -417,9 +397,7 @@ void main() { const cid = 'testCid'; final messages = List.generate(3, (index) => Message()); when(() => mockDatabase.messageDao.updateMessages(cid, messages)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) => Future.value()); await client.updateMessages(cid, messages); verify(() => mockDatabase.messageDao.updateMessages(cid, messages)) @@ -430,9 +408,7 @@ void main() { const cid = 'testCid'; final messages = List.generate(3, (index) => Message()); when(() => mockDatabase.pinnedMessageDao.updateMessages(cid, messages)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) => Future.value()); await client.updatePinnedMessages(cid, messages); verify(() => mockDatabase.pinnedMessageDao.updateMessages(cid, messages)) @@ -445,14 +421,12 @@ void main() { List.generate(3, (index) => Message(parentId: 'testParentId$index')); final threads = messages.fold>>( {}, - (prev, curr) { - return prev - ..update( - curr.parentId, - (value) => [...value, curr], - ifAbsent: () => [], - ); - }, + (prev, curr) => prev + ..update( + curr.parentId!, + (value) => [...value, curr], + ifAbsent: () => [], + ), ); when(() => mockDatabase.messageDao.getThreadMessages(cid)) .thenAnswer((realInvocation) async => messages); @@ -469,11 +443,10 @@ void main() { }); test('updateChannels', () async { - final channels = List.generate(3, (index) => ChannelModel()); + const cid = 'testType:testId'; + final channels = List.generate(3, (index) => ChannelModel(cid: cid)); when(() => mockDatabase.channelDao.updateChannels(channels)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) => Future.value()); await client.updateChannels(channels); verify(() => mockDatabase.channelDao.updateChannels(channels)).called(1); @@ -483,9 +456,7 @@ void main() { const cid = 'testCid'; final members = List.generate(3, (index) => Member()); when(() => mockDatabase.memberDao.updateMembers(cid, members)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) => Future.value()); await client.updateMembers(cid, members); verify(() => mockDatabase.memberDao.updateMembers(cid, members)) @@ -494,32 +465,36 @@ void main() { test('updateReads', () async { const cid = 'testCid'; - final reads = List.generate(3, (index) => Read()); + final reads = List.generate( + 3, + (index) => Read( + user: User(id: 'testUserId$index'), + lastRead: DateTime.now(), + ), + ); when(() => mockDatabase.readDao.updateReads(cid, reads)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) => Future.value()); await client.updateReads(cid, reads); verify(() => mockDatabase.readDao.updateReads(cid, reads)).called(1); }); test('updateUsers', () async { - final users = List.generate(3, (index) => User()); - when(() => mockDatabase.userDao.updateUsers(users)).thenAnswer((_) async { - return; - }); + final users = List.generate(3, (index) => User(id: 'testUserId$index')); + when(() => mockDatabase.userDao.updateUsers(users)) + .thenAnswer((_) => Future.value()); await client.updateUsers(users); verify(() => mockDatabase.userDao.updateUsers(users)).called(1); }); test('updateReactions', () async { - final reactions = List.generate(3, (index) => Reaction()); + final reactions = List.generate( + 3, + (index) => Reaction(type: 'testType$index'), + ); when(() => mockDatabase.reactionDao.updateReactions(reactions)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) => Future.value()); await client.updateReactions(reactions); verify(() => mockDatabase.reactionDao.updateReactions(reactions)) @@ -530,9 +505,7 @@ void main() { final messageIds = []; when(() => mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) => Future.value()); await client.deleteReactionsByMessageId(messageIds); verify(() => @@ -543,9 +516,7 @@ void main() { test('deleteMembersByCids', () async { final cids = []; when(() => mockDatabase.memberDao.deleteMemberByCids(cids)) - .thenAnswer((_) async { - return; - }); + .thenAnswer((_) => Future.value()); await client.deleteMembersByCids(cids); verify(() => mockDatabase.memberDao.deleteMemberByCids(cids)).called(1); From c5647652e2e46e456907c3dd23fc11da8b686415 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 16:23:41 +0200 Subject: [PATCH 139/599] remove log --- packages/stream_chat_flutter_core/lib/src/channel_list_core.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 992cae5d..322a2780 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -133,7 +133,6 @@ class ChannelListCoreState extends State { stream: channelsBlocState.channelsStream, builder: (context, snapshot) { if (snapshot.hasError) { - print('snapshot: ${snapshot.stackTrace}'); return widget.errorBuilder(context, snapshot.error!); } if (!snapshot.hasData) { From 4f37190fccdb2f28f9c1fe2a59a9582ddbda2cf8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 16:39:10 +0200 Subject: [PATCH 140/599] change default direction in paginate data --- packages/stream_chat_flutter_core/example/lib/main.dart | 9 +++++---- .../lib/src/message_list_core.dart | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index de10574b..308e63de 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -4,7 +4,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; Future main() async { /// Create a new instance of [StreamChatClient] passing the apikey obtained from your /// project dashboard. - final client = StreamChatClient('b67pax5b2wdq'); + final client = StreamChatClient('kv7mcsxr24p8'); /// Set the current user. In a production scenario, this should be done using /// a backend to generate a user token using our server SDK. @@ -12,13 +12,14 @@ Future main() async { /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ await client.connectUser( User( - id: 'cool-shadow-7', + id: 'salvatore', extraData: { + 'name': 'Salvatore Giordano', 'image': - 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', + 'https://avatars.githubusercontent.com/u/20601437?s=460&u=3f66c22a7483980624804054ae7f357cf102c784&v=4', }, ), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.pgiJz7sIc7iP29BHKFwe3nLm5-OaR_1l2P-SlgiC9a8', ); runApp( diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index f7bce6cd..e1fed859 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -162,9 +162,9 @@ class MessageListCoreState extends State { /// Fetches more messages with updated pagination and updates the widget. /// - /// Optionally pass the fetch direction, defaults to [QueryDirection.bottom] + /// Optionally pass the fetch direction, defaults to [QueryDirection.top] Future paginateData({ - QueryDirection direction = QueryDirection.bottom, + QueryDirection direction = QueryDirection.top, }) { if (!_isThreadConversation) { return _streamChannel.queryMessages(direction: direction); From 408bbe14f59e89f3d4c4dc7bf8a0489506453905 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Apr 2021 16:40:46 +0200 Subject: [PATCH 141/599] restore example --- packages/stream_chat_flutter_core/example/lib/main.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index 308e63de..de10574b 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -4,7 +4,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; Future main() async { /// Create a new instance of [StreamChatClient] passing the apikey obtained from your /// project dashboard. - final client = StreamChatClient('kv7mcsxr24p8'); + final client = StreamChatClient('b67pax5b2wdq'); /// Set the current user. In a production scenario, this should be done using /// a backend to generate a user token using our server SDK. @@ -12,14 +12,13 @@ Future main() async { /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ await client.connectUser( User( - id: 'salvatore', + id: 'cool-shadow-7', extraData: { - 'name': 'Salvatore Giordano', 'image': - 'https://avatars.githubusercontent.com/u/20601437?s=460&u=3f66c22a7483980624804054ae7f357cf102c784&v=4', + 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.pgiJz7sIc7iP29BHKFwe3nLm5-OaR_1l2P-SlgiC9a8', + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', ); runApp( From 4e9f4baf23ef020f607bdfe57ac0f5cc3d989213 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 20 Apr 2021 21:17:43 +0530 Subject: [PATCH 142/599] fix example Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/api/channel.dart | 18 ++---- .../lib/src/db/chat_persistence_client.dart | 17 +++--- .../src/extensions/iterable_extension.dart | 9 +++ .../example/lib/main.dart | 57 ++++++++++--------- .../example/pubspec.yaml | 2 +- 5 files changed, 54 insertions(+), 49 deletions(-) create mode 100644 packages/stream_chat/lib/src/extensions/iterable_extension.dart diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 09992d98..e9291568 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1685,8 +1685,7 @@ class ChannelClientState { ...messages!, ]; - newThreads[parentId]! - .sort(_sortByCreatedAt as int Function(Message, Message)?); + newThreads[parentId]!.sort(_sortByCreatedAt); } else { newThreads[parentId] = messages; } @@ -1713,7 +1712,7 @@ class ChannelClientState { .any((newMessage) => newMessage.id == m.id) != true) .toList(), - ]..sort(_sortByCreatedAt as int Function(Message, Message)?); + ]..sort(_sortByCreatedAt); final newWatchers = [ ...updatedState.watchers, @@ -1752,17 +1751,8 @@ class ChannelClientState { ); } - int? _sortByCreatedAt(a, b) { - if (a.createdAt == null) { - return 1; - } - - if (b.createdAt == null) { - return -1; - } - - return a.createdAt.compareTo(b.createdAt); - } + int _sortByCreatedAt(Message a, Message b) => + a.createdAt.compareTo(b.createdAt); /// The channel state related to this client ChannelState get _channelState => _channelStateController.value!; diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 9f4c7afe..479743b2 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -7,6 +7,7 @@ import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/reaction.dart'; import 'package:stream_chat/src/models/read.dart'; import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/extensions/iterable_extension.dart'; /// A simple client used for persisting chat data locally. abstract class ChatPersistenceClient { @@ -78,7 +79,7 @@ abstract class ChatPersistenceClient { return ChannelState( members: data[0] as List, read: data[1] as List, - channel: data[2] as ChannelModel, + channel: data[2] as ChannelModel?, messages: data[3] as List, pinnedMessages: data[4] as List, ); @@ -192,17 +193,17 @@ abstract class ChatPersistenceClient { deleteMembers, ]); - final channels = cleanedChannelStates - .map((it) => it.channel) - .where((it) => it != null) as Iterable; + final channels = cleanedChannelStates.map((it) => it.channel).withNullifyer; - final reactions = - cleanedChannelStates.expand((it) => it.messages).expand((it) => [ + final reactions = cleanedChannelStates + .expand((it) => it.messages) + .expand((it) => [ if (it.ownReactions != null) ...it.ownReactions!.where((r) => r.userId != null), if (it.latestReactions != null) ...it.latestReactions!.where((r) => r.userId != null), - ]); + ]) + .withNullifyer; final users = cleanedChannelStates .map((cs) => [ @@ -220,7 +221,7 @@ abstract class ChatPersistenceClient { ...cs.members.map((m) => m.user), ]) .expand((it) => it) - .where((it) => it != null) as Iterable; + .withNullifyer; final updateMessagesFuture = cleanedChannelStates.map((it) { final cid = it.channel!.cid; diff --git a/packages/stream_chat/lib/src/extensions/iterable_extension.dart b/packages/stream_chat/lib/src/extensions/iterable_extension.dart new file mode 100644 index 00000000..a7d30f76 --- /dev/null +++ b/packages/stream_chat/lib/src/extensions/iterable_extension.dart @@ -0,0 +1,9 @@ +/// Useful extension functions for [Iterable] +extension IterableX on Iterable { + /// Removes all the null values + /// and converts `Iterable` into `Iterable` + Iterable get withNullifyer => [ + for (final item in this) + if (item != null) item + ]; +} diff --git a/packages/stream_chat_persistence/example/lib/main.dart b/packages/stream_chat_persistence/example/lib/main.dart index b356d96b..f61bfed0 100644 --- a/packages/stream_chat_persistence/example/lib/main.dart +++ b/packages/stream_chat_persistence/example/lib/main.dart @@ -50,15 +50,16 @@ Future main() async { /// Example using Stream's Low Level Dart client. class StreamExample extends StatelessWidget { - /// To initialize this example, an instance of [client] and [channel] is required. + /// To initialize this example, an instance of + /// [client] and [channel] is required. const StreamExample({ - Key key, - @required this.client, - @required this.channel, + Key? key, + required this.client, + required this.channel, }) : super(key: key); - /// Instance of [StreamChatClient] we created earlier. This contains information about - /// our application and connection state. + /// Instance of [StreamChatClient] we created earlier. + /// This contains information about our application and connection state. final StreamChatClient client; /// The channel we'd like to observe and participate. @@ -77,28 +78,31 @@ class StreamExample extends StatelessWidget { /// containing the channel name and a [MessageView] displaying recent messages. class HomeScreen extends StatelessWidget { /// [HomeScreen] is constructed using the [Channel] we defined earlier. - const HomeScreen({Key key, @required this.channel}) : super(key: key); + const HomeScreen({ + Key? key, + required this.channel, + }) : super(key: key); /// Channel object containing the [Channel.id] we'd like to observe. final Channel channel; @override Widget build(BuildContext context) { - final messages = channel.state.channelStateStream; + final messages = channel.state!.channelStateStream; return Scaffold( appBar: AppBar( title: Text('Channel: ${channel.id}'), ), body: SafeArea( - child: StreamBuilder( + child: StreamBuilder( stream: messages, builder: ( BuildContext context, - AsyncSnapshot snapshot, + AsyncSnapshot snapshot, ) { if (snapshot.hasData && snapshot.data != null) { return MessageView( - messages: snapshot.data.messages.reversed.toList(), + messages: snapshot.data!.messages.reversed.toList(), channel: channel, ); } else if (snapshot.hasError) { @@ -110,8 +114,8 @@ class HomeScreen extends StatelessWidget { } return const Center( child: SizedBox( - width: 100.0, - height: 100.0, + width: 100, + height: 100, child: CircularProgressIndicator(), ), ); @@ -127,9 +131,9 @@ class HomeScreen extends StatelessWidget { class MessageView extends StatefulWidget { /// Message takes the latest list of messages and the current channel. const MessageView({ - Key key, - @required this.messages, - @required this.channel, + Key? key, + required this.messages, + required this.channel, }) : super(key: key); /// List of messages sent in the given channel. @@ -143,8 +147,8 @@ class MessageView extends StatefulWidget { } class _MessageViewState extends State { - TextEditingController _controller; - ScrollController _scrollController; + late final TextEditingController _controller; + late final ScrollController _scrollController; List get _messages => widget.messages; @@ -182,20 +186,20 @@ class _MessageViewState extends State { reverse: true, itemBuilder: (BuildContext context, int index) { final item = _messages[index]; - if (item.user.id == widget.channel.client.uid) { + if (item.user?.id == widget.channel.client.uid) { return Align( alignment: Alignment.centerRight, child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(item.text), + padding: const EdgeInsets.all(8), + child: Text(item.text ?? ''), ), ); } else { return Align( alignment: Alignment.centerLeft, child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(item.text), + padding: const EdgeInsets.all(8), + child: Text(item.text ?? ''), ), ); } @@ -203,7 +207,7 @@ class _MessageViewState extends State { ), ), Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Row( children: [ Expanded( @@ -251,7 +255,8 @@ class _MessageViewState extends State { } } -/// Helper extension for quickly retrieving the current user id from a [StreamChatClient]. +/// Helper extension for quickly retrieving +/// the current user id from a [StreamChatClient]. extension on StreamChatClient { - String get uid => state.user.id; + String get uid => state.user!.id; } diff --git a/packages/stream_chat_persistence/example/pubspec.yaml b/packages/stream_chat_persistence/example/pubspec.yaml index 7e3a1549..e6c30c94 100644 --- a/packages/stream_chat_persistence/example/pubspec.yaml +++ b/packages/stream_chat_persistence/example/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: From 89be28cadf096258154bc7a906fa1eaaaeadf553 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 21 Apr 2021 12:44:52 +0530 Subject: [PATCH 143/599] Fix dispose bug for controller --- packages/stream_chat_flutter_core/example/lib/main.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index de10574b..f5f803c8 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -307,8 +307,10 @@ class _MessageScreenState extends State { await channel.sendMessage( Message(text: _controller.value.text), ); - _controller.clear(); - _updateList(); + if (mounted) { + _controller.clear(); + _updateList(); + } } }, child: const Padding( From 59cfbf756026d1af65eae5c82e5dc237e7a51517 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 23 Apr 2021 15:11:26 +0530 Subject: [PATCH 144/599] add onLinkTap --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 9af5bc4a..423acc3e 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -142,6 +142,7 @@ class MessageListView extends StatefulWidget { this.onSystemMessageTap, this.onAttachmentTap, this.textBuilder, + this.onLinkTap, }) : super(key: key); /// Function used to build a custom message widget @@ -237,6 +238,8 @@ class MessageListView extends StatefulWidget { /// Customize the MessageWidget textBuilder final void Function(BuildContext context, Message message) textBuilder; + final void Function(String link) onLinkTap; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -844,6 +847,7 @@ class _MessageListViewState extends State { FocusScope.of(context).unfocus(); }, textBuilder: widget.textBuilder, + onLinkTap: widget.onLinkTap, ); } @@ -1021,6 +1025,7 @@ class _MessageListViewState extends State { }, onAttachmentTap: widget.onAttachmentTap, textBuilder: widget.textBuilder, + onLinkTap: widget.onLinkTap, ); if (!message.isDeleted && From ac995b2fc9810902b37ab25eb23d44dad54d4911 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 26 Apr 2021 19:55:50 +0530 Subject: [PATCH 145/599] feat(llc): add support for type safe filters Signed-off-by: Sahil Kumar --- .../stream_chat/lib/src/models/filter.dart | 197 +++++++++++++++++ packages/stream_chat/lib/stream_chat.dart | 1 + .../test/src/models/filter_test.dart | 198 ++++++++++++++++++ 3 files changed, 396 insertions(+) create mode 100644 packages/stream_chat/lib/src/models/filter.dart create mode 100644 packages/stream_chat/test/src/models/filter_test.dart diff --git a/packages/stream_chat/lib/src/models/filter.dart b/packages/stream_chat/lib/src/models/filter.dart new file mode 100644 index 00000000..2a2f3410 --- /dev/null +++ b/packages/stream_chat/lib/src/models/filter.dart @@ -0,0 +1,197 @@ +// ignore_for_file: non_constant_identifier_names, constant_identifier_names + +const _groupOperators = [ + FilterOperator.and, + FilterOperator.or, + FilterOperator.nor, +]; + +/// Possible operators to use in filters. +enum FilterOperator { + /// Matches values that are equal to a specified value. + equal, + + /// Matches all values that are not equal to a specified value. + notEqual, + + /// Matches values that are greater than a specified value. + greater, + + /// Matches values that are greater than a specified value. + greaterOrEqual, + + /// Matches values that are less than a specified value. + less, + + /// Matches values that are less than or equal to a specified value. + lessOrEqual, + + /// Matches any of the values specified in an array. + in_, + + /// Matches none of the values specified in an array. + notIn, + + /// Matches values by performing text search with the specified value. + query, + + /// Matches values with the specified prefix. + autoComplete, + + /// Matches values that exist/don't exist based on the specified boolean value. + exists, + + /// Matches all the values specified in an array. + and, + + /// Matches at least one of the values specified in an array. + or, + + /// Matches none of the values specified in an array. + nor, +} + +/// Helper extension for [FilterOperator] +extension FilterOperatorX on FilterOperator { + /// Converts [FilterOperator] into rew values + String get rawValue => { + FilterOperator.equal: '\$eq', + FilterOperator.notEqual: '\$ne', + FilterOperator.greater: '\$gt', + FilterOperator.greaterOrEqual: '\$gte', + FilterOperator.less: '\$lt', + FilterOperator.lessOrEqual: '\$lte', + FilterOperator.in_: '\$in', + FilterOperator.notIn: '\$nin', + FilterOperator.query: '\$q', + FilterOperator.autoComplete: '\$autocomplete', + FilterOperator.exists: '\$exists', + FilterOperator.and: '\$and', + FilterOperator.or: '\$or', + FilterOperator.nor: '\$nor', + }[this]!; +} + +/// Stream supports a limited set of filters for querying channels, +/// users and members. The example below shows how to filter for channels +/// of type messaging where the current user is a member +/// +/// ```dart +/// val filter = Filter.and( +/// Filter.equal('type', 'messaging'), +/// Filter.in_('members', [user.id]) +/// ) +/// ``` +/// See Query Channels Documentation +class Filter { + const Filter.__({ + required this.operator, + required this.value, + this.key, + }); + + Filter._({ + required FilterOperator operator, + required this.value, + this.key, + }) : operator = operator.rawValue; + + /// Combines the provided filters and matches the values + /// matched by all filters. + factory Filter.and(List filters) => + Filter._(operator: FilterOperator.and, value: filters); + + /// Combines the provided filters and matches the values + /// matched by at least one of the filters. + factory Filter.or(List filters) => + Filter._(operator: FilterOperator.or, value: filters); + + /// Combines the provided filters and matches the values + /// not matched by all the filters. + factory Filter.nor(List filters) => + Filter._(operator: FilterOperator.nor, value: filters); + + /// Matches values that are equal to a specified value. + factory Filter.equal(String key, Object value) => + Filter._(operator: FilterOperator.equal, key: key, value: value); + + /// Matches all values that are not equal to a specified value. + factory Filter.notEqual(String key, Object value) => + Filter._(operator: FilterOperator.notEqual, key: key, value: value); + + /// Matches values that are greater than a specified value. + factory Filter.greater(String key, Object value) => + Filter._(operator: FilterOperator.greater, key: key, value: value); + + /// Matches values that are greater than a specified value. + factory Filter.greaterOrEqual(String key, Object value) => + Filter._(operator: FilterOperator.greaterOrEqual, key: key, value: value); + + /// Matches values that are less than a specified value. + factory Filter.less(String key, Object value) => + Filter._(operator: FilterOperator.less, key: key, value: value); + + /// Matches values that are less than or equal to a specified value. + factory Filter.lessOrEqual(String key, Object value) => + Filter._(operator: FilterOperator.lessOrEqual, key: key, value: value); + + /// Matches any of the values specified in an array. + factory Filter.in_(String key, List values) => + Filter._(operator: FilterOperator.in_, key: key, value: values); + + /// Matches none of the values specified in an array. + factory Filter.notIn(String key, List values) => + Filter._(operator: FilterOperator.notIn, key: key, value: values); + + /// Matches values by performing text search with the specified value. + factory Filter.query(String key, String text) => + Filter._(operator: FilterOperator.query, key: key, value: text); + + /// Matches values with the specified prefix. + factory Filter.autoComplete(String key, String text) => + Filter._(operator: FilterOperator.autoComplete, key: key, value: text); + + /// Matches values that exist/don't exist based on the specified boolean value. + factory Filter.exists(String key, {bool exists = true}) => + Filter._(operator: FilterOperator.exists, key: key, value: exists); + + /// Creates a custom [Filter] if there isn't one already available. + factory Filter.custom({ + required String operator, + required Object value, + String? key, + }) = Filter.__; + + /// Operator to use in filter. + final String operator; + + /// + final String? key; + + /// + final Object /*List|List|String*/ value; + + /// + Map toJson() { + final json = {}; + final groupOperators = _groupOperators.map((it) => it.rawValue); + + assert( + groupOperators.contains(operator) || key != null, + 'Filter must contain the `key` when the operator is not a ' + 'group operator.', + ); + + if (groupOperators.contains(operator)) { + // Filters with group operators are encoded in the following form: + // { $: [ , ] } + json[operator] = value; + } else { + // Normal filters are encoded in the following form: + // { key: { $: } } + json[key!] = {operator: value}; + } + + return json; + } +} diff --git a/packages/stream_chat/lib/stream_chat.dart b/packages/stream_chat/lib/stream_chat.dart index ef146180..2ac56b10 100644 --- a/packages/stream_chat/lib/stream_chat.dart +++ b/packages/stream_chat/lib/stream_chat.dart @@ -28,6 +28,7 @@ export './src/models/channel_state.dart'; export './src/models/command.dart'; export './src/models/device.dart'; export './src/models/event.dart'; +export './src/models/filter.dart' show Filter; export './src/models/member.dart'; export './src/models/message.dart'; export './src/models/mute.dart'; diff --git a/packages/stream_chat/test/src/models/filter_test.dart b/packages/stream_chat/test/src/models/filter_test.dart new file mode 100644 index 00000000..3a61a9c1 --- /dev/null +++ b/packages/stream_chat/test/src/models/filter_test.dart @@ -0,0 +1,198 @@ +import 'dart:convert'; + +import 'package:test/test.dart'; +import 'package:stream_chat/src/models/filter.dart'; + +void main() { + group('operators', () { + test('equal', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.equal(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.equal.rawValue); + }); + + test('notEqual', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.notEqual(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.notEqual.rawValue); + }); + + test('greater', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.greater(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.greater.rawValue); + }); + + test('greaterOrEqual', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.greaterOrEqual(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.greaterOrEqual.rawValue); + }); + + test('less', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.less(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.less.rawValue); + }); + + test('lessOrEqual', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.lessOrEqual(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.lessOrEqual.rawValue); + }); + + test('in', () { + const key = 'testKey'; + const values = ['testValue']; + final filter = Filter.in_(key, values); + expect(filter.key, key); + expect(filter.value, values); + expect(filter.operator, FilterOperator.in_.rawValue); + }); + + test('in', () { + const key = 'testKey'; + const values = ['testValue']; + final filter = Filter.in_(key, values); + expect(filter.key, key); + expect(filter.value, values); + expect(filter.operator, FilterOperator.in_.rawValue); + }); + + test('notIn', () { + const key = 'testKey'; + const values = ['testValue']; + final filter = Filter.notIn(key, values); + expect(filter.key, key); + expect(filter.value, values); + expect(filter.operator, FilterOperator.notIn.rawValue); + }); + + test('query', () { + const key = 'testKey'; + const value = 'testQuery'; + final filter = Filter.query(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.query.rawValue); + }); + + test('autoComplete', () { + const key = 'testKey'; + const value = 'testQuery'; + final filter = Filter.autoComplete(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.autoComplete.rawValue); + }); + + test('exists', () { + const key = 'testKey'; + final filter = Filter.exists(key); + expect(filter.key, key); + expect(filter.value, isTrue); + expect(filter.operator, FilterOperator.exists.rawValue); + }); + + test('notExists', () { + const key = 'testKey'; + final filter = Filter.exists(key, exists: false); + expect(filter.key, key); + expect(filter.value, isFalse); + expect(filter.operator, FilterOperator.exists.rawValue); + }); + + test('custom', () { + const key = 'testKey'; + const value = 'testValue'; + const operator = '\$customOperator'; + final filter = Filter.custom(operator: operator, key: key, value: value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, operator); + }); + + group('groupedOperator', () { + final filter1 = Filter.equal('testKey', 'testValue'); + final filter2 = Filter.in_('testKey', ['testValue']); + final filters = [filter1, filter2]; + + test('and', () { + final filter = Filter.and(filters); + expect(filter.key, isNull); + expect(filter.value, filters); + expect(filter.operator, FilterOperator.and.rawValue); + }); + + test('or', () { + final filter = Filter.or(filters); + expect(filter.key, isNull); + expect(filter.value, filters); + expect(filter.operator, FilterOperator.or.rawValue); + }); + + test('nor', () { + final filter = Filter.nor(filters); + expect(filter.key, isNull); + expect(filter.value, filters); + expect(filter.operator, FilterOperator.nor.rawValue); + }); + }); + }); + + group('encoding', () { + group('nonGroupedFilter', () { + test('simpleValue', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.equal(key, value); + final encoded = json.encode(filter); + expect( + encoded, + '{"$key":{"${FilterOperator.equal.rawValue}":${json.encode(value)}}}', + ); + }); + test('listValue', () { + const key = 'testKey'; + const values = ['testValue']; + final filter = Filter.in_(key, values); + final encoded = json.encode(filter); + expect( + encoded, + '{"$key":{"${FilterOperator.in_.rawValue}":${json.encode(values)}}}', + ); + }); + }); + + test('groupedFilter', () { + final filter1 = Filter.equal('testKey', 'testValue'); + final filter2 = Filter.in_('testKey', ['testValue']); + final filters = [filter1, filter2]; + + final filter = Filter.and(filters); + final encoded = json.encode(filter); + expect( + encoded, + '{"${FilterOperator.and.rawValue}":${json.encode(filters)}}', + ); + }); + }); +} From f6839615815723c53cf6ea5614dd2d57d59642cd Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 26 Apr 2021 20:39:17 +0530 Subject: [PATCH 146/599] Update packages/stream_chat/lib/src/models/filter.dart --- packages/stream_chat/lib/src/models/filter.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/models/filter.dart b/packages/stream_chat/lib/src/models/filter.dart index 2a2f3410..423617c7 100644 --- a/packages/stream_chat/lib/src/models/filter.dart +++ b/packages/stream_chat/lib/src/models/filter.dart @@ -77,7 +77,7 @@ extension FilterOperatorX on FilterOperator { /// of type messaging where the current user is a member /// /// ```dart -/// val filter = Filter.and( +/// final filter = Filter.and( /// Filter.equal('type', 'messaging'), /// Filter.in_('members', [user.id]) /// ) From 8c666caa52b8a9ec9bf7f4bbabcd93438a66dd28 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Apr 2021 15:41:00 +0530 Subject: [PATCH 147/599] refactor (core <-> ui <-> persistence) with the new filter changes. Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/api/channel.dart | 83 ++++++++++--------- packages/stream_chat/lib/src/client.dart | 67 +++++++-------- .../lib/src/db/chat_persistence_client.dart | 5 +- .../lib/src/models/channel_model.dart | 10 +-- .../stream_chat/lib/src/models/filter.dart | 7 +- .../test/src/api/channel_test.dart | 6 +- .../stream_chat/test/src/client_test.dart | 28 +++---- .../lib/src/channel_list_view.dart | 2 +- .../lib/src/message_input.dart | 2 +- .../lib/src/message_search_list_view.dart | 2 +- .../analysis_options.yaml | 2 +- .../example/lib/main.dart | 14 ++-- .../lib/src/channel_list_core.dart | 2 +- .../lib/src/channels_bloc.dart | 7 +- .../lib/src/message_search_bloc.dart | 4 +- .../lib/src/message_search_list_core.dart | 2 +- .../test/channel_list_core_test.dart | 2 +- .../test/channels_bloc_test.dart | 4 +- .../test/message_search_bloc_test.dart | 40 ++++----- .../test/message_search_list_core_test.dart | 46 +++++----- .../lib/src/dao/channel_query_dao.dart | 8 +- .../src/stream_chat_persistence_client.dart | 4 +- .../test/src/dao/channel_query_dao_test.dart | 26 ++---- .../stream_chat_persistence_client_test.dart | 2 +- 24 files changed, 180 insertions(+), 195 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index f2e8e0fc..2de3789a 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -19,19 +19,24 @@ class Channel { /// Create a channel client instance. Channel( this._client, - this.type, - this._id, - this._extraData, - ) : _cid = _id != null ? '$type:$_id' : null { + this._type, + this._id, { + Map extraData = const {}, + }) : _cid = _id != null ? '$_type:$_id' : null, + _extraData = extraData { _client.logger.info('New Channel instance not initialized created'); } /// Create a channel client instance from a [ChannelState] object - Channel.fromState(this._client, ChannelState channelState) : _extraData = {} { - _cid = channelState.channel!.cid; - _id = channelState.channel!.id; - type = channelState.channel!.type; - + Channel.fromState(this._client, ChannelState channelState) + : assert( + channelState.channel != null, + 'No channel found inside channel state', + ), + _id = channelState.channel!.id, + _type = channelState.channel!.type, + _cid = channelState.channel!.cid, + _extraData = channelState.channel!.extraData ?? {} { state = ChannelClientState(this, channelState); _initializedCompleter.complete(true); _client.logger.info('New Channel instance initialized created'); @@ -41,19 +46,20 @@ class Channel { ChannelClientState? state; /// The channel type - String? type; + final String _type; String? _id; String? _cid; - Map _extraData; + final Map _extraData; set extraData(Map extraData) { if (_initializedCompleter.isCompleted) { - throw Exception( - 'Once the channel is initialized you should use channel.update ' - 'to update channel data'); + throw StateError( + 'Once the channel is initialized you should use channel.update ' + 'to update channel data', + ); } - _extraData = extraData; + _extraData.addAll(extraData); } /// Returns true if the channel is muted @@ -179,6 +185,9 @@ class Channel { /// Channel id String? get id => state?._channelState.channel?.id ?? _id; + /// Channel type + String get type => state?._channelState.channel?.type ?? _type; + /// Channel cid String? get cid => state?._channelState.channel?.cid ?? _cid; @@ -193,9 +202,11 @@ class Channel { state?._channelState.channel?.extraData ?? _extraData; /// Channel extra data as a stream - Stream?>? get extraDataStream { + Stream> get extraDataStream { _checkInitialized(); - return state?.channelStateStream.map((cs) => cs.channel?.extraData); + return state!.channelStateStream.map( + (cs) => cs.channel?.extraData ?? _extraData, + ); } /// The main Stream chat client @@ -384,7 +395,7 @@ class Channel { message = await attachmentsUploadCompleter.future; } - final response = await _client.sendMessage(message, id!, type!); + final response = await _client.sendMessage(message, id!, type); state!.addMessage(response.message); return response; } catch (error) { @@ -537,7 +548,7 @@ class Channel { return _client.sendFile( file, id!, - type!, + type, onSendProgress: onSendProgress, cancelToken: cancelToken, ); @@ -553,7 +564,7 @@ class Channel { return _client.sendImage( file, id!, - type!, + type, onSendProgress: onSendProgress, cancelToken: cancelToken, ); @@ -565,18 +576,16 @@ class Channel { Map? messageFilters, List? sort, PaginationParams? paginationParams, - }) => - _client.search( - { - 'cid': { - r'$in': [cid], - }, - }, - sort: sort, - query: query, - paginationParams: paginationParams, - messageFilters: messageFilters, - ); + }) { + _checkInitialized(); + return _client.search( + Filter.in_('cid', [cid!]), + sort: sort, + query: query, + paginationParams: paginationParams, + messageFilters: messageFilters, + ); + } /// Delete a file from this channel Future deleteFile( @@ -587,7 +596,7 @@ class Channel { return _client.deleteFile( url, id!, - type!, + type, cancelToken: cancelToken, ); } @@ -601,7 +610,7 @@ class Channel { return _client.deleteImage( url, id!, - type!, + type, cancelToken: cancelToken, ); } @@ -903,7 +912,7 @@ class Channel { void _initState(ChannelState channelState) { state = ChannelClientState(this, channelState); - client.state.channels![cid] = this; + client.state.channels[cid!] = this; if (!_initializedCompleter.isCompleted) { _initializedCompleter.complete(true); } @@ -1018,9 +1027,7 @@ class Channel { bool preferOffline = false, }) async { var path = '/channels/$type'; - if (id != null) { - path = '$path/$id'; - } + if (id != null) path = '$path/$id'; path = '$path/query'; final payload = Map.from({ diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 4689e9ec..28be5ac6 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -29,6 +29,8 @@ import 'package:stream_chat/src/platform_detector/platform_detector.dart'; import 'package:stream_chat/version.dart'; import 'package:uuid/uuid.dart'; +import 'package:stream_chat/src/models/filter.dart'; + /// Handler function used for logging records. Function requires a single /// [LogRecord] as the only parameter. typedef LogHandlerFunction = void Function(LogRecord record); @@ -552,13 +554,16 @@ class StreamChatClient { type: EventType.connectionRecovered, online: true, )); - if (state.channels?.isNotEmpty == true) { + if (state.channels.isNotEmpty == true) { // ignore: unawaited_futures - queryChannelsOnline(filter: { - 'cid': { - '\$in': state.channels!.keys.toList(), - }, - }).then( + queryChannelsOnline( + filter: Filter.in_('cid', state.channels.keys.toList()), + // { + // 'cid': { + // '\$in': state.channels!.keys.toList(), + // }, + // } + ).then( (_) async { await resync(); }, @@ -641,7 +646,7 @@ class StreamChatClient { /// Requests channels with a given query. Stream> queryChannels({ - Map? filter, + Filter? filter, List>? sort, Map? options, PaginationParams paginationParams = const PaginationParams(), @@ -684,7 +689,7 @@ class StreamChatClient { /// Requests channels with a given query from the API. Future> queryChannelsOnline({ - required Map? filter, + Filter? filter, List>? sort, Map? options, int? messageLimit, @@ -764,7 +769,7 @@ class StreamChatClient { final updateData = _mapChannelStateToChannel(channels); await _chatPersistenceClient?.updateChannelQueries( - filter ?? {}, + filter, channels.map((c) => c.channel!.cid).toList(), clearQueryCache: paginationParams.offset == 0, ); @@ -775,7 +780,7 @@ class StreamChatClient { /// Requests channels with a given query from the Persistence client. Future> queryChannelsOffline({ - required Map? filter, + required Filter? filter, required List>? sort, PaginationParams paginationParams = const PaginationParams(), }) async { @@ -790,10 +795,10 @@ class StreamChatClient { return updatedData.value; } - MapEntry, List> _mapChannelStateToChannel( + MapEntry, List> _mapChannelStateToChannel( List channelStates, ) { - final channels = {...state.channels ?? {}}; + final channels = {...state.channels}; final newChannels = []; for (final channelState in channelStates) { final channel = channels[channelState.channel!.cid]; @@ -802,7 +807,7 @@ class StreamChatClient { newChannels.add(channel); } else { final newChannel = Channel.fromState(this, channelState); - channels[newChannel.cid] = newChannel; + channels[newChannel.cid!] = newChannel; newChannels.add(newChannel); } } @@ -1061,16 +1066,13 @@ class StreamChatClient { /// A message search. Future search( - Map filters, { + Filter filter, { String? query, List? sort, PaginationParams? paginationParams, Map? messageFilters, }) async { assert(() { - if (filters.isEmpty) { - throw ArgumentError('`filters` cannot be set as null or empty'); - } if (query == null && messageFilters == null) { throw ArgumentError('Provide at least `query` or `messageFilters`'); } @@ -1083,7 +1085,7 @@ class StreamChatClient { }(), 'Check incoming params.'); final payload = { - 'filter_conditions': filters, + 'filter_conditions': filter, 'message_filter_conditions': messageFilters, 'query': query, 'sort': sort, @@ -1194,15 +1196,12 @@ class StreamChatClient { Channel channel( String type, { String? id, - Map extraData = const {}, + Map extraData = const {}, }) { - if (id != null && state.channels?.containsKey('$type:$id') == true) { - if (state.channels!['$type:$id'] != null) { - return state.channels!['$type:$id'] as Channel; - } + if (id != null && state.channels.containsKey('$type:$id')) { + return state.channels['$type:$id']!; } - - return Channel(this, type, id, extraData); + return Channel(this, type, id, extraData: extraData); } /// Update or Create the given user object. @@ -1443,9 +1442,7 @@ class ClientState { if (cid != null) { _client.chatPersistenceClient?.deleteChannels([cid]); } - if (channels != null) { - channels = channels?..removeWhere((cid, ch) => cid == event.cid); - } + channels = channels..removeWhere((cid, ch) => cid == event.cid); })); } @@ -1468,9 +1465,7 @@ class ClientState { .listen((Event event) async { final eventChannel = event.channel!; await _client.chatPersistenceClient?.deleteChannels([eventChannel.cid]); - if (channels != null) { - channels = channels?..remove(eventChannel.cid); - } + channels = channels..remove(eventChannel.cid); })); } @@ -1521,13 +1516,13 @@ class ClientState { _channelsController.stream; /// The current list of channels in memory - Map? get channels => _channelsController.value; + Map get channels => _channelsController.value!; - set channels(Map? v) { - _channelsController.add(v); + set channels(Map? v) { + if (v != null) _channelsController.add(v); } - final BehaviorSubject?> _channelsController = + final BehaviorSubject> _channelsController = BehaviorSubject.seeded({}); final BehaviorSubject _userController = BehaviorSubject(); final BehaviorSubject> _usersController = @@ -1541,7 +1536,7 @@ class ClientState { _userController.close(); _unreadChannelsController.close(); _totalUnreadCountController.close(); - channels!.values.forEach((c) => c.dispose()); + channels.values.forEach((c) => c.dispose()); _channelsController.close(); } } diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 479743b2..e9c21956 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -2,6 +2,7 @@ import 'package:stream_chat/src/api/requests.dart'; import 'package:stream_chat/src/models/channel_model.dart'; import 'package:stream_chat/src/models/channel_state.dart'; import 'package:stream_chat/src/models/event.dart'; +import 'package:stream_chat/src/models/filter.dart'; import 'package:stream_chat/src/models/member.dart'; import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/reaction.dart'; @@ -90,7 +91,7 @@ abstract class ChatPersistenceClient { /// Optionally, pass [filter], [sort], [paginationParams] /// for filtering out states. Future> getChannelStates({ - Map? filter, + Filter? filter, List>? sort, PaginationParams? paginationParams, }); @@ -100,7 +101,7 @@ abstract class ChatPersistenceClient { /// If [clearQueryCache] is true before the insert /// the list of matching rows will be deleted Future updateChannelQueries( - Map filter, + Filter? filter, List cids, { bool clearQueryCache = false, }); diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index fa9f6eab..95145296 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -23,16 +23,16 @@ class ChannelModel { this.memberCount = 0, this.extraData, this.team, - }) : config = config ?? ChannelConfig(), - createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(), - assert( + }) : assert( (cid != null && cid.contains(':')) || (id != null && type != null), 'provide either a cid or an id and type', ), id = id ?? cid!.split(':')[1], type = type ?? cid!.split(':')[0], - cid = cid ?? '$type:$id'; + cid = cid ?? '$type:$id', + config = config ?? ChannelConfig(), + createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json factory ChannelModel.fromJson(Map json) => diff --git a/packages/stream_chat/lib/src/models/filter.dart b/packages/stream_chat/lib/src/models/filter.dart index 423617c7..35fcf51f 100644 --- a/packages/stream_chat/lib/src/models/filter.dart +++ b/packages/stream_chat/lib/src/models/filter.dart @@ -1,5 +1,7 @@ // ignore_for_file: non_constant_identifier_names, constant_identifier_names +import 'package:equatable/equatable.dart'; + const _groupOperators = [ FilterOperator.and, FilterOperator.or, @@ -83,7 +85,7 @@ extension FilterOperatorX on FilterOperator { /// ) /// ``` /// See Query Channels Documentation -class Filter { +class Filter extends Equatable { const Filter.__({ required this.operator, required this.value, @@ -171,6 +173,9 @@ class Filter { /// final Object /*List|List|String*/ value; + @override + List get props => [operator, key, value]; + /// Map toJson() { final json = {}; diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 8f18d5e2..49a12cb4 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -516,7 +516,7 @@ void main() { ); await channelClient.watch(); - final event = const Event(type: EventType.any); + const event = Event(type: EventType.any); when( () => mockDio.post( @@ -564,7 +564,7 @@ void main() { ); await channelClient.watch(); - final event = const Event(type: EventType.typingStart); + const event = Event(type: EventType.typingStart); when( () => mockDio.post( @@ -610,7 +610,7 @@ void main() { ); await channelClient.watch(); - final event = const Event(type: EventType.typingStop); + const event = Event(type: EventType.typingStop); when( () => mockDio.post( diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index c93059d5..553ad88b 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -10,6 +10,7 @@ import 'package:stream_chat/src/api/requests.dart'; import 'package:stream_chat/src/client.dart'; import 'package:stream_chat/src/exceptions.dart'; import 'package:stream_chat/src/models/channel_model.dart'; +import 'package:stream_chat/src/models/filter.dart'; import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/user.dart'; import 'package:test/test.dart'; @@ -89,7 +90,7 @@ void main() { test('Channel', () { final client = StreamChatClient('test'); - final data = {'test': 1}; + final data = {'test': 1}; final channelClient = client.channel('type', id: 'id', extraData: data); expect(channelClient.type, 'type'); expect(channelClient.id, 'id'); @@ -140,11 +141,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final queryFilter = { - 'id': { - '\$in': ['test'], - }, - }; + final queryFilter = Filter.in_('id', ['test']); final sortOptions = >[]; final options = {'state': false, 'watch': false, 'presence': true}; const paginationParams = PaginationParams(offset: 2); @@ -192,11 +189,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final filter = { - 'cid': { - r'$in': ['messaging:testId'] - } - }; + final filter = Filter.in_('cid', ['messaging:testId']); const query = 'hello'; final queryParams = { 'payload': json.encode({ @@ -229,11 +222,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final filters = { - 'id': { - '\$in': ['test'], - }, - }; + final filters = Filter.in_('id', ['test']); const sortOptions = [SortOption('name')]; const query = 'query'; final queryParams = { @@ -1163,8 +1152,11 @@ void main() { httpClient: mockDio, ); - final channelClient = - client.channel('type', id: 'id', extraData: {'name': 'init'}); + final channelClient = client.channel( + 'type', + id: 'id', + extraData: {'name': 'init'}, + ); const update = { 'set': {'name': 'demo'} 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 9751ad6d..90afbd64 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -84,7 +84,7 @@ class ChannelListView extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map filter; + final Filter filter; /// Query channels options. /// diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index b4b2176c..2d0e0af4 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -2096,7 +2096,7 @@ class MessageInputState extends State { } return sendingFuture.then((resp) { - if (resp.message?.type == 'error') { + if (resp.message?._type == 'error') { _parseExistingMessage(message); } if (widget.onMessageSent != null) { 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 6431a311..d7ece34e 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 @@ -71,7 +71,7 @@ class MessageSearchListView extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map filters; + final Filter filters; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be provided. diff --git a/packages/stream_chat_flutter_core/analysis_options.yaml b/packages/stream_chat_flutter_core/analysis_options.yaml index 545d5492..891101e0 100644 --- a/packages/stream_chat_flutter_core/analysis_options.yaml +++ b/packages/stream_chat_flutter_core/analysis_options.yaml @@ -3,7 +3,7 @@ analyzer: - lib/**/*.g.dart - lib/**/*.freezed.dart - example/* - - test/* + linter: rules: - always_use_package_imports diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index f5f803c8..e7ef400a 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -78,14 +78,12 @@ class HomeScreen extends StatelessWidget { body: ChannelsBloc( child: ChannelListCore( channelListController: channelListController, - filter: { - 'type': 'messaging', - 'members': { - r'$in': [ - StreamChatCore.of(context).user!.id, - ] - } - }, + filter: Filter.and([ + Filter.equal('type', 'messaging'), + Filter.in_('members', [ + StreamChatCore.of(context).user!.id, + ]) + ]), emptyBuilder: (BuildContext context) { return Center( child: Text('Looks like you are not in any channels'), diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 322a2780..8ead2637 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -92,7 +92,7 @@ class ChannelListCore extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map? filter; + final Filter? filter; /// Query channels options. /// 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 7910ab7e..32e63bdc 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -88,7 +88,7 @@ class ChannelsBlocState extends State /// Calls [client.queryChannels] updating [queryChannelsLoading] stream Future queryChannels({ - Map? filter, + Filter? filter, List>? sortOptions, PaginationParams paginationParams = const PaginationParams(limit: 30), Map? options, @@ -160,9 +160,8 @@ class ChannelsBlocState extends State newChannels.insert(0, _hiddenChannels[hiddenIndex]); _hiddenChannels.removeAt(hiddenIndex); } else { - if (client.state.channels != null && - client.state.channels?[e.cid] != null) { - newChannels.insert(0, client.state.channels![e.cid]!); + if (client.state.channels[e.cid] != null) { + newChannels.insert(0, client.state.channels[e.cid]!); } } } 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 8586e1ff..d6220cf3 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 @@ -9,7 +9,7 @@ import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; /// [MessageSearchBloc] can be access at anytime by using the static [of] method /// using Flutter's [BuildContext]. /// -// API docs: https://getstream.io/chat/docs/flutter-dart/send_message/ +/// API docs: https://getstream.io/chat/docs/flutter-dart/send_message/ class MessageSearchBloc extends StatefulWidget { /// Instantiate a new MessageSearchBloc const MessageSearchBloc({ @@ -58,7 +58,7 @@ class MessageSearchBlocState extends State /// Calls [StreamChatClient.search] updating /// [messagesStream] and [queryMessagesLoading] stream Future search({ - required Map filter, + required Filter filter, Map? messageFilter, List? sort, String? query, diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index ca661dc6..2a2d31f5 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -67,7 +67,7 @@ class MessageSearchListCore extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map filters; + final Filter filters; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be diff --git a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart index 5bbdaf39..53303231 100644 --- a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart @@ -24,7 +24,7 @@ void main() { client, 'testType$index', 'testId$index', - {'extra_data_key': 'extra_data_value_$index'}, + extraData: {'extra_data_key': 'extra_data_value_$index'}, ); }, ); diff --git a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart index 28136add..19d327bd 100644 --- a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart @@ -26,7 +26,7 @@ void main() { client, 'testType$index', 'testId$index', - {'extra_data_key': 'extra_data_value_$index'}, + extraData: {'extra_data_key': 'extra_data_value_$index'}, ); }, ); @@ -703,7 +703,7 @@ void main() { final mockClient = MockClient(); final channels = _generateChannels(mockClient); final stateChannels = { - for (var c in _generateChannels(mockClient, offset: 5)) c.cid: c + for (var c in _generateChannels(mockClient, offset: 5)) c.cid!: c }; const channelsBlocKey = Key('channelsBloc'); final channelsBloc = ChannelsBloc( diff --git a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart index 5b427c02..91c07435 100644 --- a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart @@ -8,6 +8,8 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'matchers/get_message_response_matcher.dart'; import 'mocks.dart'; +final testFilter = Filter.custom(operator: '\$test', value: 'testValue'); + void main() { List _generateMessages({ int count = 3, @@ -50,7 +52,7 @@ void main() { ); try { - await usersBlocState.search(filter: {}); + await usersBlocState.search(filter: testFilter); } catch (e) { expect(e, isInstanceOf()); } @@ -82,7 +84,7 @@ void main() { final messageResponseList = _generateMessages(); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -91,7 +93,7 @@ void main() { (_) async => SearchMessagesResponse()..results = messageResponseList, ); - messageSearchBlocState.search(filter: {}); + messageSearchBlocState.search(filter: testFilter); await expectLater( messageSearchBlocState.messagesStream, @@ -99,7 +101,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -133,14 +135,14 @@ void main() { const error = 'Error! Error! Error!'; when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), paginationParams: any(named: 'paginationParams'), )).thenThrow(error); - messageSearchBlocState.search(filter: {}); + messageSearchBlocState.search(filter: testFilter); await expectLater( messageSearchBlocState.messagesStream, @@ -148,7 +150,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -185,7 +187,7 @@ void main() { final messageResponseList = _generateMessages(); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -194,7 +196,7 @@ void main() { (_) async => SearchMessagesResponse()..results = messageResponseList, ); - messageSearchBlocState.search(filter: {}); + messageSearchBlocState.search(filter: testFilter); await expectLater( messageSearchBlocState.messagesStream, @@ -202,7 +204,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -214,7 +216,7 @@ void main() { final pagination = PaginationParams(offset: offset); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -224,7 +226,7 @@ void main() { SearchMessagesResponse()..results = paginatedMessageResponseList, ); - messageSearchBlocState.search(pagination: pagination, filter: {}); + messageSearchBlocState.search(pagination: pagination, filter: testFilter); await Future.wait([ expectLater( @@ -240,7 +242,7 @@ void main() { ]); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -277,7 +279,7 @@ void main() { final messageResponseList = _generateMessages(); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -286,7 +288,7 @@ void main() { (_) async => SearchMessagesResponse()..results = messageResponseList, ); - messageSearchBlocState.search(filter: {}); + messageSearchBlocState.search(filter: testFilter); await expectLater( messageSearchBlocState.messagesStream, @@ -294,7 +296,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -306,14 +308,14 @@ void main() { const error = 'Error! Error! Error!'; when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), paginationParams: pagination, )).thenThrow(error); - messageSearchBlocState.search(pagination: pagination, filter: {}); + messageSearchBlocState.search(pagination: pagination, filter: testFilter); await expectLater( messageSearchBlocState.queryMessagesLoading, @@ -321,7 +323,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), diff --git a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart index 00aee32a..1f7414d0 100644 --- a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart @@ -6,6 +6,8 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'mocks.dart'; +final testFilter = Filter.custom(operator: '\$test', value: 'testValue'); + void main() { List _generateMessages({ int count = 3, @@ -37,7 +39,7 @@ void main() { loadingBuilder: (BuildContext context) => const Offstage(), emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object? error) => const Offstage(), - filters: const {}, + filters: testFilter, ); await tester.pumpWidget(messageSearchListCore); @@ -58,7 +60,7 @@ void main() { loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object? error) => Offstage(), - filters: {}, + filters: testFilter, ); final mockClient = MockClient(); @@ -89,7 +91,7 @@ void main() { emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), messageSearchListController: controller, - filters: {}, + filters: testFilter, ); expect(controller.loadData, isNull); @@ -125,14 +127,14 @@ void main() { errorBuilder: (BuildContext context, Object error) => Offstage( key: errorWidgetKey, ), - filters: {}, + filters: testFilter, ); final mockClient = MockClient(); const error = 'Error! Error! Error!'; when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -153,7 +155,7 @@ void main() { expect(find.byKey(errorWidgetKey), findsOneWidget); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -174,14 +176,14 @@ void main() { loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(key: emptyWidgetKey), errorBuilder: (BuildContext context, Object error) => Offstage(), - filters: {}, + filters: testFilter, ); final mockClient = MockClient(); final messageResponseList = []; when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -204,7 +206,7 @@ void main() { expect(find.byKey(emptyWidgetKey), findsOneWidget); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -226,14 +228,14 @@ void main() { loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), - filters: {}, + filters: testFilter, ); final mockClient = MockClient(); final messageResponseList = _generateMessages(); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -256,7 +258,7 @@ void main() { expect(find.byKey(childWidgetKey), findsOneWidget); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -284,14 +286,14 @@ void main() { emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), paginationParams: pagination, - filters: {}, + filters: testFilter, ); final mockClient = MockClient(); final messageResponseList = _generateMessages(); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -325,7 +327,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -341,7 +343,7 @@ void main() { final paginatedMessageResponseList = _generateMessages(offset: offset); final updatedPagination = pagination.copyWith(offset: offset); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -365,7 +367,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -400,14 +402,14 @@ void main() { emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), paginationParams: pagination.copyWith(limit: limit), - filters: {}, + filters: testFilter, ); final mockClient = MockClient(); final messageResponseList = _generateMessages(); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -445,7 +447,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -458,7 +460,7 @@ void main() { final updatedMessageResponseList = _generateMessages(count: limit); final updatedPagination = pagination.copyWith(limit: limit); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -479,7 +481,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart index 099f6798..5bff63dc 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart @@ -18,7 +18,7 @@ class ChannelQueryDao extends DatabaseAccessor /// Creates a new channel query dao instance ChannelQueryDao(MoorChatDatabase db) : super(db); - String _computeHash(Map? filter) { + String _computeHash(Filter? filter) { if (filter == null) { return 'allchannels'; } @@ -30,7 +30,7 @@ class ChannelQueryDao extends DatabaseAccessor /// If [clearQueryCache] is true before the insert /// the list of matching rows will be deleted Future updateChannelQueries( - Map filter, + Filter? filter, List cids, { bool clearQueryCache = false, }) async => @@ -58,7 +58,7 @@ class ChannelQueryDao extends DatabaseAccessor }); /// - Future> getCachedChannelCids(Map? filter) { + Future> getCachedChannelCids(Filter? filter) { final hash = _computeHash(filter); return (select(channelQueries)..where((c) => c.queryHash.equals(hash))) .map((c) => c.channelCid) @@ -67,7 +67,7 @@ class ChannelQueryDao extends DatabaseAccessor /// Get list of channels by filter, sort and paginationParams Future> getChannels({ - Map? filter, + Filter? filter, List>? sort, PaginationParams? paginationParams, }) async { 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 3f955eb2..20c4e327 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 @@ -253,7 +253,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { @override Future> getChannelStates({ - Map? filter, + Filter? filter, List>? sort, PaginationParams? paginationParams, }) { @@ -273,7 +273,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { @override Future updateChannelQueries( - Map filter, + Filter? filter, List cids, { bool clearQueryCache = false, }) { 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 dee6e076..3f04711d 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 @@ -17,11 +17,7 @@ void main() { }); test('updateChannelQueries', () async { - const filter = { - 'members': { - r'$in': ['testUserId'], - }, - }; + final filter = Filter.in_('members', const ['testUserId']); const cids = ['testCid1', 'testCid2', 'testCid3']; @@ -36,11 +32,7 @@ void main() { }); test('clear queryCache before updateChannelQueries', () async { - const filter = { - 'members': { - r'$in': ['testUserId'], - }, - }; + final filter = Filter.in_('members', const ['testUserId']); const cids = ['testCid1', 'testCid2', 'testCid3']; @@ -59,11 +51,7 @@ void main() { }); test('getCachedChannelCids', () async { - const filter = { - 'members': { - r'$in': ['testUserId'], - }, - }; + final filter = Filter.in_('members', const ['testUserId']); const cids = ['testCid1', 'testCid2', 'testCid3']; @@ -78,7 +66,7 @@ void main() { }); Future> _insertTestDataForGetChannel( - Map filter, { + Filter filter, { int count = 3, }) async { final now = DateTime.now(); @@ -110,11 +98,7 @@ void main() { } group('getChannels', () { - const filter = { - 'members': { - r'$in': ['testUserId'], - }, - }; + final filter = Filter.in_('members', const ['testUserId']); test('should return empty list of channels', () async { final channels = await channelQueryDao.getChannels(filter: filter); diff --git a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart index 26d0bfb5..8a275201 100644 --- a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart +++ b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart @@ -292,7 +292,7 @@ void main() { }); test('updateChannelQueries', () async { - const filter = {}; + final filter = Filter.in_('members', const ['testUserId']); const cids = []; when(() => mockDatabase.channelQueryDao.updateChannelQueries(filter, cids)) From 64318f4e469abdbee83566266d691ef4a3418ba6 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Apr 2021 15:44:06 +0530 Subject: [PATCH 148/599] cleanup Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/client.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 28be5ac6..17e45e93 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -558,11 +558,6 @@ class StreamChatClient { // ignore: unawaited_futures queryChannelsOnline( filter: Filter.in_('cid', state.channels.keys.toList()), - // { - // 'cid': { - // '\$in': state.channels!.keys.toList(), - // }, - // } ).then( (_) async { await resync(); From d862da504a02fde4230abc7ad6fbed7c7a2067c5 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Apr 2021 15:45:53 +0530 Subject: [PATCH 149/599] cleanup Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/client.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 17e45e93..bf6a3f3f 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -775,8 +775,8 @@ class StreamChatClient { /// Requests channels with a given query from the Persistence client. Future> queryChannelsOffline({ - required Filter? filter, - required List>? sort, + Filter? filter, + List>? sort, PaginationParams paginationParams = const PaginationParams(), }) async { final offlineChannels = (await _chatPersistenceClient?.getChannelStates( From 697e14457ea1193125ee9bf4eeda3b04157bf32b Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Apr 2021 15:47:00 +0530 Subject: [PATCH 150/599] cleanup Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/models/filter.dart | 2 +- packages/stream_chat/test/src/client_test.dart | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat/lib/src/models/filter.dart b/packages/stream_chat/lib/src/models/filter.dart index 35fcf51f..6cda6f91 100644 --- a/packages/stream_chat/lib/src/models/filter.dart +++ b/packages/stream_chat/lib/src/models/filter.dart @@ -158,7 +158,7 @@ class Filter extends Equatable { Filter._(operator: FilterOperator.exists, key: key, value: exists); /// Creates a custom [Filter] if there isn't one already available. - factory Filter.custom({ + const factory Filter.custom({ required String operator, required Object value, String? key, diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 553ad88b..944b476f 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -127,7 +127,7 @@ void main() { ), ); - await client.queryChannelsOnline(filter: null, waitForConnect: false); + await client.queryChannelsOnline(waitForConnect: false); verify(() => mockDio.get('/channels', queryParameters: queryParams)) @@ -141,7 +141,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final queryFilter = Filter.in_('id', ['test']); + final queryFilter = Filter.in_('id', const ['test']); final sortOptions = >[]; final options = {'state': false, 'watch': false, 'presence': true}; const paginationParams = PaginationParams(offset: 2); @@ -189,7 +189,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final filter = Filter.in_('cid', ['messaging:testId']); + final filter = Filter.in_('cid', const ['messaging:testId']); const query = 'hello'; final queryParams = { 'payload': json.encode({ @@ -222,7 +222,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final filters = Filter.in_('id', ['test']); + final filters = Filter.in_('id', const ['test']); const sortOptions = [SortOption('name')]; const query = 'query'; final queryParams = { From 2ad8adca6079d03a48932cc5e7d71e87eb4e9fd4 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Apr 2021 16:02:11 +0530 Subject: [PATCH 151/599] fix analyzer warnings Signed-off-by: Sahil Kumar --- packages/stream_chat_flutter_core/analysis_options.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter_core/analysis_options.yaml b/packages/stream_chat_flutter_core/analysis_options.yaml index 891101e0..cf09a170 100644 --- a/packages/stream_chat_flutter_core/analysis_options.yaml +++ b/packages/stream_chat_flutter_core/analysis_options.yaml @@ -3,6 +3,7 @@ analyzer: - lib/**/*.g.dart - lib/**/*.freezed.dart - example/* + - test/* linter: rules: From 8735901c7595aa90144c79339414c0327c5dcb79 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Apr 2021 16:07:41 +0530 Subject: [PATCH 152/599] add equality tests for filters Signed-off-by: Sahil Kumar --- .../test/src/models/filter_test.dart | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/test/src/models/filter_test.dart b/packages/stream_chat/test/src/models/filter_test.dart index 3a61a9c1..c73c0d0b 100644 --- a/packages/stream_chat/test/src/models/filter_test.dart +++ b/packages/stream_chat/test/src/models/filter_test.dart @@ -124,7 +124,7 @@ void main() { const key = 'testKey'; const value = 'testValue'; const operator = '\$customOperator'; - final filter = Filter.custom(operator: operator, key: key, value: value); + const filter = Filter.custom(operator: operator, key: key, value: value); expect(filter.key, key); expect(filter.value, value); expect(filter.operator, operator); @@ -132,7 +132,7 @@ void main() { group('groupedOperator', () { final filter1 = Filter.equal('testKey', 'testValue'); - final filter2 = Filter.in_('testKey', ['testValue']); + final filter2 = Filter.in_('testKey', const ['testValue']); final filters = [filter1, filter2]; test('and', () { @@ -184,7 +184,7 @@ void main() { test('groupedFilter', () { final filter1 = Filter.equal('testKey', 'testValue'); - final filter2 = Filter.in_('testKey', ['testValue']); + final filter2 = Filter.in_('testKey', const ['testValue']); final filters = [filter1, filter2]; final filter = Filter.and(filters); @@ -194,5 +194,19 @@ void main() { '{"${FilterOperator.and.rawValue}":${json.encode(filters)}}', ); }); + + group('equality', () { + test('simpleFilter', () { + final filter1 = Filter.equal('testKey', 'testValue'); + final filter2 = Filter.equal('testKey', 'testValue'); + expect(filter1, filter2); + }); + + test('groupedFilter', () { + final filter1 = Filter.and([Filter.equal('testKey', 'testValue')]); + final filter2 = Filter.and([Filter.equal('testKey', 'testValue')]); + expect(filter1, filter2); + }); + }); }); } From f412fdc0ddb499b7cb75fe3fed579f8e4f24fea8 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 28 Apr 2021 13:12:13 +0530 Subject: [PATCH 153/599] fix pinMessage and add docs --- packages/stream_chat/lib/src/client.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 8b9d3a75..51b21257 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1333,6 +1333,8 @@ class StreamChatClient { } /// Pins provided message + /// [timeoutOrExpirationDate] can either be a [DateTime] or a value in seconds + /// to be added to [DateTime.now] Future pinMessage( Message message, Object timeoutOrExpirationDate, @@ -1350,9 +1352,11 @@ class StreamChatClient { if (timeoutOrExpirationDate is DateTime) { pinExpires = timeoutOrExpirationDate.toUtc(); } else if (timeoutOrExpirationDate is num) { - pinExpires = DateTime.now().add( - Duration(seconds: timeoutOrExpirationDate.toInt()), - ); + pinExpires = DateTime.now() + .add( + Duration(seconds: timeoutOrExpirationDate.toInt()), + ) + .toUtc(); } return updateMessage( message.copyWith(pinned: true, pinExpires: pinExpires), From 75b59f0b518f0baf2c2a7f90e2359dc62386e7f1 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 28 Apr 2021 15:45:08 +0530 Subject: [PATCH 154/599] add doc comments Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/models/filter.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/models/filter.dart b/packages/stream_chat/lib/src/models/filter.dart index 6cda6f91..2356c57b 100644 --- a/packages/stream_chat/lib/src/models/filter.dart +++ b/packages/stream_chat/lib/src/models/filter.dart @@ -164,19 +164,25 @@ class Filter extends Equatable { String? key, }) = Filter.__; - /// Operator to use in filter. + /// An operator used for the filter. The operator string must start with `$` final String operator; + /// The "left-hand" side of the filter. + /// Specifies the name of the field the filter should match. /// + /// Some operators like `and` or `or`, + /// don't require the key value to be present. + /// see-more : [_groupOperators] final String? key; - /// + /// The "right-hand" side of the filter. + /// Specifies the [value] the filter should match. final Object /*List|List|String*/ value; @override List get props => [operator, key, value]; - /// + /// Serializes to json object Map toJson() { final json = {}; final groupOperators = _groupOperators.map((it) => it.rawValue); From 9c5aceeb912deb6946cd060938a36cdf074f2b1d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Apr 2021 13:40:05 +0200 Subject: [PATCH 155/599] update examples --- packages/stream_chat_flutter/example/lib/split_view.dart | 9 ++++----- .../stream_chat_flutter/example/lib/tutorial-part-2.dart | 9 ++++----- .../stream_chat_flutter/example/lib/tutorial-part-3.dart | 9 ++++----- .../stream_chat_flutter/example/lib/tutorial-part-4.dart | 9 ++++----- .../stream_chat_flutter/example/lib/tutorial-part-5.dart | 9 ++++----- .../stream_chat_flutter/example/lib/tutorial-part-6.dart | 9 ++++----- 6 files changed, 24 insertions(+), 30 deletions(-) diff --git a/packages/stream_chat_flutter/example/lib/split_view.dart b/packages/stream_chat_flutter/example/lib/split_view.dart index 2026b101..0432a0b4 100644 --- a/packages/stream_chat_flutter/example/lib/split_view.dart +++ b/packages/stream_chat_flutter/example/lib/split_view.dart @@ -93,11 +93,10 @@ class ChannelListPage extends StatelessWidget { onTap(channel); } : null, - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user.id], + ), sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart index 8b3009c8..57ee471b 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart @@ -56,11 +56,10 @@ class ChannelListPage extends StatelessWidget { return Scaffold( body: ChannelsBloc( child: ChannelListView( - filter: { - // 'members': { - // '\$in': [StreamChat.of(context).user.id], - // } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user.id], + ), sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart index 3a34dc01..89613b82 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart @@ -57,11 +57,10 @@ class ChannelListPage extends StatelessWidget { return Scaffold( body: ChannelsBloc( child: ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user.id], + ), channelPreviewBuilder: _channelPreviewBuilder, // sort: [SortOption('last_message_at')], pagination: PaginationParams( diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart index 5bdd5472..82589355 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart @@ -49,11 +49,10 @@ class ChannelListPage extends StatelessWidget { return Scaffold( body: ChannelsBloc( child: ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user.id], + ), sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart index 18ae5ff8..b265eef3 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart @@ -52,11 +52,10 @@ class ChannelListPage extends StatelessWidget { return Scaffold( body: ChannelsBloc( child: ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user.id], + ), sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart index 110cc1dc..7d732eac 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart @@ -80,11 +80,10 @@ class ChannelListPage extends StatelessWidget { return Scaffold( body: ChannelsBloc( child: ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user.id], + ), sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, From a2d8ddc72bb09984a14e5deb6bde9871f3e37bb0 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 30 Apr 2021 14:21:10 +0530 Subject: [PATCH 156/599] add type safe filters in `queryUsers` and `queryMembers` Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/api/channel.dart | 2 +- packages/stream_chat/lib/src/client.dart | 4 ++-- packages/stream_chat/test/src/client_test.dart | 8 ++------ packages/stream_chat_flutter/lib/src/message_input.dart | 9 ++++----- packages/stream_chat_flutter/lib/src/user_list_view.dart | 2 +- .../stream_chat_flutter_core/lib/src/user_list_core.dart | 2 +- .../stream_chat_flutter_core/lib/src/users_bloc.dart | 2 +- 7 files changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 2de3789a..f5cc5482 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1089,7 +1089,7 @@ class Channel { /// Query channel members Future queryMembers({ - Map? filter, + Filter? filter, List? sort, PaginationParams? pagination, }) async { diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index bf6a3f3f..04bb8ae1 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1020,7 +1020,7 @@ class StreamChatClient { /// Requests users with a given query. Future queryUsers({ - Map? filter, + Filter? filter, List? sort, Map? options, PaginationParams? pagination, @@ -1030,7 +1030,7 @@ class StreamChatClient { }; final payload = { - 'filter_conditions': filter ?? {}, + 'filter_conditions': filter, 'sort': sort, }..addAll(defaultOptions); diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 944b476f..b2a5bbac 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -363,7 +363,7 @@ void main() { final client = StreamChatClient('api-key', httpClient: mockDio); final queryParams = { 'payload': json.encode({ - 'filter_conditions': {}, + 'filter_conditions': null, 'sort': null, 'presence': false, }), @@ -396,11 +396,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final queryFilter = { - 'id': { - '\$in': ['test'], - }, - }; + final queryFilter = Filter.in_('id', const ['test']); const sortOptions = []; final options = {'presence': true}; final queryParams = { diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 2d0e0af4..70307936 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1243,11 +1243,10 @@ class MessageInputState extends State { Future> queryMembers; if (query.isNotEmpty) { - queryMembers = StreamChannel.of(context).channel.queryMembers(filter: { - 'name': { - '\$autocomplete': query, - }, - }).then((res) => res.members); + queryMembers = StreamChannel.of(context) + .channel + .queryMembers(filter: Filter.autoComplete('name', query)) + .then((res) => res.members); } final members = StreamChannel.of(context).channel.state.members?.where((m) { 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 416f7ba0..c300971d 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -72,7 +72,7 @@ class UserListView extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map filter; + final Filter filter; /// Query channels options. /// diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 1443ef17..694c52bc 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -90,7 +90,7 @@ class UserListCore extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map? filter; + final Filter? filter; /// Query channels options. /// 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 dcc3252f..9b33f75c 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -58,7 +58,7 @@ class UsersBlocState extends State /// online/offline. /// [API Reference](https://getstream.io/chat/docs/flutter-dart/query_users/?language=dart) Future queryUsers({ - Map? filter, + Filter? filter, List? sort, Map? options, PaginationParams? pagination, From 8659b4db7a30119830540af736db0e2438379b25 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 9 Apr 2021 12:23:35 +0200 Subject: [PATCH 157/599] feat: bump dependencies --- packages/stream_chat_flutter/pubspec.yaml | 51 +++++++++++------------ 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index ffee617b..6ae2c732 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -14,39 +14,38 @@ dependencies: flutter: sdk: flutter stream_chat_flutter_core: ^1.5.1 - photo_view: ^0.11.0 + photo_view: ^0.11.1 rxdart: ^0.26.0 - scrollable_positioned_list: ^0.1.8 - jiffy: ^3.0.1 - flutter_svg: ^0.21.0 - flutter_portal: ^0.3.0 - cached_network_image: ">=3.0.0-nullsafety <3.0.0" - shimmer: ^1.1.2 - flutter_markdown: ^0.5.2 - url_launcher: ^6.0.0 + scrollable_positioned_list: ^0.2.0-nullsafety.0 + jiffy: ^4.1.0 + flutter_svg: ^0.21.0+1 + flutter_portal: ^0.4.0-nullsafety.0 + cached_network_image: ^3.0.0 + shimmer: ^2.0.0-nullsafety.0 + flutter_markdown: ^0.6.1 + url_launcher: ^6.0.3 emojis: ^0.9.3 - video_player: ^2.0.0 + video_player: ^2.1.1 chewie: ^1.0.0 - file_picker: ^3.0.0 - image_picker: ^0.7.0 + file_picker: ^3.0.1 + image_picker: ^0.7.4 flutter_keyboard_visibility: ^5.0.0 - video_compress: ^2.1.1 - visibility_detector: ^0.1.5 + video_compress: ^3.0.0 + visibility_detector: ^0.2.0 http_parser: ^4.0.0 meta: ^1.3.0 - lottie: ^1.0.0 + lottie: ^1.0.1 substring_highlight: ^0.1.2 - flutter_slidable: ^0.5.7 - image_gallery_saver: ^1.6.7 + flutter_slidable: ^0.6.0-nullsafety.0 + image_gallery_saver: ^1.6.9 share_plus: ^2.0.0 - photo_manager: ^1.0.0 - ezanimation: ^0.4.1 + photo_manager: ^1.1.0 + ezanimation: ^0.5.0 synchronized: ^3.0.0 - characters: ^1.0.0 dio: ^4.0.0 - path_provider: ^2.0.0 - video_thumbnail: ^0.2.5+1 - + characters: ^1.1.0 + path_provider: ^2.0.1 + video_thumbnail: ^0.3.3 dependency_overrides: stream_chat: @@ -65,7 +64,7 @@ flutter: dev_dependencies: flutter_test: sdk: flutter - mockito: ^4.1.3 - pedantic: ^1.9.2 - golden_toolkit: ^0.6.0 + mockito: ^5.0.3 + pedantic: ^1.11.0 + golden_toolkit: ^0.9.0 From 2c02f0f631ec11698ebd5b8fa1bb8cd409a25eb9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 12 Apr 2021 11:27:04 +0200 Subject: [PATCH 158/599] use git for not yet migrated dependencies --- packages/stream_chat_flutter/pubspec.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 6ae2c732..4114051e 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -24,7 +24,8 @@ dependencies: shimmer: ^2.0.0-nullsafety.0 flutter_markdown: ^0.6.1 url_launcher: ^6.0.3 - emojis: ^0.9.3 + emojis: + git: git@github.com:Parkar99/emojis.git video_player: ^2.1.1 chewie: ^1.0.0 file_picker: ^3.0.1 @@ -35,7 +36,8 @@ dependencies: http_parser: ^4.0.0 meta: ^1.3.0 lottie: ^1.0.1 - substring_highlight: ^0.1.2 + substring_highlight: + git: git@github.com:ArturAntin/substring_highlight.git flutter_slidable: ^0.6.0-nullsafety.0 image_gallery_saver: ^1.6.9 share_plus: ^2.0.0 From 34888d0fffe833a2f21e8029cfb53b5c044267fb Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 26 Apr 2021 15:29:41 +0200 Subject: [PATCH 159/599] migrate ui --- packages/stream_chat/lib/src/api/channel.dart | 5 +- packages/stream_chat/lib/src/client.dart | 8 +- .../lib/src/models/attachment.dart | 9 +- .../lib/src/models/attachment.g.dart | 7 +- .../lib/src/models/attachment_file.dart | 2 +- .../lib/src/models/channel_model.dart | 14 +- .../lib/src/models/channel_model.g.dart | 7 +- .../stream_chat/lib/src/models/event.dart | 2 +- .../stream_chat/lib/src/models/event.g.dart | 7 +- .../lib/src/attachment/attachment_title.dart | 29 +- .../attachment_upload_state_builder.dart | 98 ++-- .../lib/src/attachment/attachment_widget.dart | 34 +- .../lib/src/attachment/file_attachment.dart | 118 ++-- .../lib/src/attachment/giphy_attachment.dart | 60 +-- .../lib/src/attachment/image_attachment.dart | 53 +- .../lib/src/attachment/video_attachment.dart | 34 +- .../lib/src/attachment_actions_modal.dart | 61 ++- .../lib/src/back_button.dart | 8 +- .../lib/src/channel_bottom_sheet.dart | 36 +- .../lib/src/channel_header.dart | 20 +- .../lib/src/channel_image.dart | 69 +-- .../lib/src/channel_info.dart | 35 +- .../lib/src/channel_list_header.dart | 94 ++-- .../lib/src/channel_list_view.dart | 121 +++-- .../lib/src/channel_name.dart | 28 +- .../lib/src/channel_preview.dart | 150 +++--- .../lib/src/connection_status_builder.dart | 19 +- .../lib/src/date_divider.dart | 4 +- .../lib/src/deleted_message.dart | 18 +- .../lib/src/extension.dart | 74 +-- .../lib/src/full_screen_media.dart | 80 +-- .../lib/src/group_image.dart | 25 +- .../lib/src/image_footer.dart | 87 ++- .../lib/src/image_group.dart | 23 +- .../lib/src/image_header.dart | 16 +- .../lib/src/info_tile.dart | 14 +- .../lib/src/media_list_view.dart | 26 +- .../lib/src/mention_tile.dart | 17 +- .../lib/src/message_action.dart | 6 +- .../lib/src/message_actions_modal.dart | 72 ++- .../lib/src/message_input.dart | 275 +++++----- .../lib/src/message_list_view.dart | 244 ++++----- .../lib/src/message_reactions_modal.dart | 60 +-- .../lib/src/message_search_item.dart | 42 +- .../lib/src/message_search_list_view.dart | 46 +- .../lib/src/message_text.dart | 47 +- .../lib/src/message_widget.dart | 237 ++++---- .../lib/src/option_list_tile.dart | 18 +- .../lib/src/quoted_message_widget.dart | 80 +-- .../lib/src/reaction_bubble.dart | 18 +- .../lib/src/reaction_icon.dart | 4 +- .../lib/src/reaction_picker.dart | 15 +- .../lib/src/sending_indicator.dart | 10 +- .../lib/src/stream_chat.dart | 29 +- .../lib/src/stream_chat_theme.dart | 509 ++++++++++-------- .../lib/src/stream_neumorphic_button.dart | 6 +- .../lib/src/stream_svg_icon.dart | 300 +++++------ .../lib/src/swipeable.dart | 32 +- .../lib/src/system_message.dart | 10 +- .../lib/src/thread_header.dart | 18 +- .../lib/src/typing_indicator.dart | 12 +- .../lib/src/unread_indicator.dart | 12 +- .../lib/src/upload_progress_indicator.dart | 25 +- .../lib/src/url_attachment.dart | 14 +- .../lib/src/user_avatar.dart | 35 +- .../lib/src/user_item.dart | 18 +- .../lib/src/user_list_view.dart | 57 +- .../lib/src/user_reaction_display.dart | 21 +- .../stream_chat_flutter/lib/src/utils.dart | 76 +-- .../lib/src/video_service.dart | 9 +- .../lib/src/video_thumbnail_image.dart | 28 +- packages/stream_chat_flutter/pubspec.yaml | 11 +- .../src/attachment_actions_modal_test.dart | 107 ++-- .../test/src/attachment_widgets_test.dart | 6 +- .../test/src/back_button_test.dart | 12 +- .../test/src/channel_header_test.dart | 187 ++++--- .../test/src/channel_image_test.dart | 62 +-- .../test/src/channel_list_header_test.dart | 38 +- .../test/src/channel_name_test.dart | 33 +- .../test/src/channel_preview_test.dart | 43 +- .../test/src/date_divider_test.dart | 6 +- .../test/src/deleted_message_test.dart | 63 ++- .../test/src/full_screen_media_test.dart | 40 +- .../test/src/goldens/reaction_bubble_2.png | Bin 3179 -> 2955 bytes .../src/goldens/reaction_bubble_3_dark.png | Bin 1935 -> 1963 bytes .../src/goldens/reaction_bubble_3_light.png | Bin 2064 -> 2123 bytes .../test/src/image_footer_test.dart | 24 +- .../test/src/info_tile_test.dart | 10 +- .../test/src/message_action_modal_test.dart | 129 +++-- .../test/src/message_input_test.dart | 39 +- .../src/message_reactions_modal_test.dart | 14 +- .../test/src/message_text_test.dart | 22 +- .../stream_chat_flutter/test/src/mocks.dart | 12 +- .../test/src/reaction_bubble_test.dart | 62 +-- .../test/src/simple_frame.dart | 2 +- .../test/src/system_message_test.dart | 58 +- .../test/src/thread_header_test.dart | 62 ++- .../test/src/typing_indicator_test.dart | 39 +- .../test/src/unread_indicator_test.dart | 62 +-- .../lib/src/dao/channel_query_dao.dart | 3 +- .../lib/src/db/moor_chat_database.g.dart | 28 +- .../lib/src/mapper/channel_mapper.dart | 2 +- .../test/src/dao/channel_query_dao_test.dart | 4 +- 103 files changed, 2622 insertions(+), 2455 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index f5cc5482..35989a72 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -912,7 +912,10 @@ class Channel { void _initState(ChannelState channelState) { state = ChannelClientState(this, channelState); - client.state.channels[cid!] = this; + + if (cid != null) { + client.state.channels[cid!] = this; + } if (!_initializedCompleter.isCompleted) { _initializedCompleter.complete(true); } diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 04bb8ae1..12820f8c 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -798,11 +798,13 @@ class StreamChatClient { for (final channelState in channelStates) { final channel = channels[channelState.channel!.cid]; if (channel != null) { - channel.state!.updateChannelState(channelState); + channel.state?.updateChannelState(channelState); newChannels.add(channel); } else { final newChannel = Channel.fromState(this, channelState); - channels[newChannel.cid!] = newChannel; + if (newChannel.cid != null) { + channels[newChannel.cid!] = newChannel; + } newChannels.add(newChannel); } } @@ -1513,7 +1515,7 @@ class ClientState { /// The current list of channels in memory Map get channels => _channelsController.value!; - set channels(Map? v) { + set channels(Map v) { if (v != null) _channelsController.add(v); } diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/models/attachment.dart index 1bba82e7..09ea4f58 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/models/attachment.dart @@ -33,7 +33,7 @@ class Attachment extends Equatable { this.authorIcon, this.assetUrl, List? actions, - this.extraData, + this.extraData = const {}, this.file, UploadState? uploadState, }) : id = id ?? const Uuid().v4(), @@ -110,8 +110,11 @@ class Attachment extends Equatable { late final UploadState uploadState; /// Map of custom channel extraData - @JsonKey(includeIfNull: false) - final Map? extraData; + @JsonKey( + includeIfNull: false, + defaultValue: {}, + ) + final Map extraData; /// The attachment ID. /// diff --git a/packages/stream_chat/lib/src/models/attachment.g.dart b/packages/stream_chat/lib/src/models/attachment.g.dart index d7e76e74..c42aaf5f 100644 --- a/packages/stream_chat/lib/src/models/attachment.g.dart +++ b/packages/stream_chat/lib/src/models/attachment.g.dart @@ -31,8 +31,9 @@ Attachment _$AttachmentFromJson(Map json) { .toList() ?? [], extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k, e as Object), - ), + (k, e) => MapEntry(k, e as Object), + ) ?? + {}, file: json['file'] == null ? null : AttachmentFile.fromJson(json['file'] as Map), @@ -71,7 +72,7 @@ Map _$AttachmentToJson(Attachment instance) { val['actions'] = instance.actions.map((e) => e.toJson()).toList(); writeNotNull('file', instance.file?.toJson()); val['upload_state'] = instance.uploadState.toJson(); - writeNotNull('extra_data', instance.extraData); + val['extra_data'] = instance.extraData; val['id'] = instance.id; return val; } diff --git a/packages/stream_chat/lib/src/models/attachment_file.dart b/packages/stream_chat/lib/src/models/attachment_file.dart index d72cecbd..8dbb70ad 100644 --- a/packages/stream_chat/lib/src/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/models/attachment_file.dart @@ -59,10 +59,10 @@ String? _toString(Uint8List? bytes) { class AttachmentFile { /// Creates a new [AttachmentFile] instance. const AttachmentFile({ + required this.size, this.path, this.name, this.bytes, - this.size, }) : assert( path != null || bytes != null, 'Either path or bytes should be != null', diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index 95145296..98d86539 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -21,7 +21,7 @@ class ChannelModel { DateTime? updatedAt, this.deletedAt, this.memberCount = 0, - this.extraData, + this.extraData = const {}, this.team, }) : assert( (cid != null && cid.contains(':')) || (id != null && type != null), @@ -83,8 +83,11 @@ class ChannelModel { final int memberCount; /// Map of custom channel extraData - @JsonKey(includeIfNull: false) - final Map? extraData; + @JsonKey( + includeIfNull: false, + defaultValue: {}, + ) + final Map extraData; /// The team the channel belongs to @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @@ -108,9 +111,8 @@ class ChannelModel { ]; /// Shortcut for channel name - String get name => extraData?.containsKey('name') == true - ? extraData!['name'] as String - : cid; + String get name => + extraData.containsKey('name') ? extraData['name'] as String : cid; /// Serialize to json Map toJson() => Serialization.moveFromExtraDataToRoot( diff --git a/packages/stream_chat/lib/src/models/channel_model.g.dart b/packages/stream_chat/lib/src/models/channel_model.g.dart index 47f23071..78e93910 100644 --- a/packages/stream_chat/lib/src/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/models/channel_model.g.dart @@ -32,8 +32,9 @@ ChannelModel _$ChannelModelFromJson(Map json) { : DateTime.parse(json['deleted_at'] as String), memberCount: json['member_count'] as int? ?? 0, extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k, e as Object), - ), + (k, e) => MapEntry(k, e as Object), + ) ?? + {}, team: json['team'] as String?, ); } @@ -59,7 +60,7 @@ Map _$ChannelModelToJson(ChannelModel instance) { writeNotNull('updated_at', readonly(instance.updatedAt)); writeNotNull('deleted_at', readonly(instance.deletedAt)); writeNotNull('member_count', readonly(instance.memberCount)); - writeNotNull('extra_data', instance.extraData); + val['extra_data'] = instance.extraData; writeNotNull('team', readonly(instance.team)); return val; } diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index 1d070230..1675197d 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -193,7 +193,7 @@ class EventChannel extends ChannelModel { updatedAt: updatedAt, deletedAt: deletedAt, memberCount: memberCount, - extraData: extraData, + extraData: extraData ?? {}, ); /// Create a new instance from a json diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index 2715b476..b76c675c 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -90,8 +90,9 @@ EventChannel _$EventChannelFromJson(Map json) { : DateTime.parse(json['deleted_at'] as String), memberCount: json['member_count'] as int? ?? 0, extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k, e as Object), - ), + (k, e) => MapEntry(k, e as Object), + ) ?? + {}, ); } @@ -116,7 +117,7 @@ Map _$EventChannelToJson(EventChannel instance) { writeNotNull('updated_at', readonly(instance.updatedAt)); writeNotNull('deleted_at', readonly(instance.deletedAt)); writeNotNull('member_count', readonly(instance.memberCount)); - writeNotNull('extra_data', instance.extraData); + val['extra_data'] = instance.extraData; val['members'] = instance.members?.map((e) => e.toJson()).toList(); return val; } diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart index 176bbbd3..0ad06cc0 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart @@ -6,12 +6,12 @@ import '../utils.dart'; class AttachmentTitle extends StatelessWidget { const AttachmentTitle({ - Key key, - @required this.attachment, - @required this.messageTheme, + Key? key, + required this.attachment, + required this.messageTheme, }) : super(key: key); - final MessageTheme messageTheme; + final MessageTheme? messageTheme; final Attachment attachment; @override @@ -19,7 +19,7 @@ class AttachmentTitle extends StatelessWidget { return GestureDetector( onTap: () { if (attachment.titleLink != null) { - launchURL(context, attachment.titleLink); + launchURL(context, attachment.titleLink!); } }, child: Padding( @@ -28,17 +28,18 @@ class AttachmentTitle extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text( - attachment.title, - overflow: TextOverflow.ellipsis, - style: messageTheme.messageText.copyWith( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - fontWeight: FontWeight.bold, + if (attachment.title != null) + Text( + attachment.title!, + overflow: TextOverflow.ellipsis, + style: messageTheme?.messageText?.copyWith( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + fontWeight: FontWeight.bold, + ), ), - ), if (attachment.titleLink != null || attachment.ogScrapeUrl != null) Text( - Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl) + Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl!) .authority .split('.') .reversed @@ -46,7 +47,7 @@ class AttachmentTitle extends StatelessWidget { .toList() .reversed .join('.'), - style: messageTheme.messageText, + style: messageTheme?.messageText, ), ], ), 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 2425b966..2aa71516 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 @@ -8,55 +8,52 @@ typedef FailedBuilder = Widget Function(BuildContext, String); class AttachmentUploadStateBuilder extends StatelessWidget { final Message message; final Attachment attachment; - final FailedBuilder failedBuilder; - final WidgetBuilder successBuilder; - final InProgressBuilder inProgressBuilder; - final WidgetBuilder preparingBuilder; + final FailedBuilder? failedBuilder; + final WidgetBuilder? successBuilder; + final InProgressBuilder? inProgressBuilder; + final WidgetBuilder? preparingBuilder; const AttachmentUploadStateBuilder({ - Key key, - @required this.message, - @required this.attachment, + Key? key, + required this.message, + required this.attachment, this.failedBuilder, this.successBuilder, this.inProgressBuilder, this.preparingBuilder, - }) : assert(message != null), - assert(attachment != null), - super(key: key); + }) : super(key: key); @override Widget build(BuildContext context) { - if (message.status == null || message.status == MessageSendingStatus.sent) { + if (message.status == MessageSendingStatus.sent) { return Offstage(); } final messageId = message.id; final attachmentId = attachment.id; - var inProgress = inProgressBuilder; - inProgress ??= (context, int sent, int total) { - return _InProgressState( - sent: sent, - total: total, - attachmentId: attachmentId, - ); - }; + final inProgress = inProgressBuilder ?? + (context, int sent, int total) { + return _InProgressState( + sent: sent, + total: total, + attachmentId: attachmentId, + ); + }; - var failed = failedBuilder; - failed ??= (context, error) { - return _FailedState( - error: error, - messageId: messageId, - attachmentId: attachmentId, - ); - }; + final failed = failedBuilder ?? + (context, error) { + return _FailedState( + error: error, + messageId: messageId, + attachmentId: attachmentId, + ); + }; - var success = successBuilder; - success ??= (context) => _SuccessState(); + final success = successBuilder ?? (context) => _SuccessState(); - var preparing = preparingBuilder; - preparing ??= (context) => _PreparingState(attachmentId: attachmentId); + final preparing = preparingBuilder ?? + (context) => _PreparingState(attachmentId: attachmentId); return attachment.uploadState.when( preparing: () => preparing(context), @@ -68,13 +65,13 @@ class AttachmentUploadStateBuilder extends StatelessWidget { } class _IconButton extends StatelessWidget { - final Widget icon; + final Widget? icon; final double iconSize; - final VoidCallback onPressed; - final Color fillColor; + final VoidCallback? onPressed; + final Color? fillColor; const _IconButton({ - Key key, + Key? key, this.icon, this.iconSize = 24.0, this.onPressed, @@ -95,7 +92,9 @@ class _IconButton extends StatelessWidget { onPressed: onPressed, fillColor: fillColor ?? StreamChatTheme.of(context).colorTheme.overlayDark, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), child: icon, ), ); @@ -106,8 +105,8 @@ class _PreparingState extends StatelessWidget { final String attachmentId; const _PreparingState({ - Key key, - @required this.attachmentId, + Key? key, + required this.attachmentId, }) : super(key: key); @override @@ -144,10 +143,10 @@ class _InProgressState extends StatelessWidget { final String attachmentId; const _InProgressState({ - Key key, - @required this.sent, - @required this.total, - @required this.attachmentId, + Key? key, + required this.sent, + required this.total, + required this.attachmentId, }) : super(key: key); @override @@ -179,15 +178,15 @@ class _InProgressState extends StatelessWidget { } class _FailedState extends StatelessWidget { - final String error; + final String? error; final String messageId; final String attachmentId; const _FailedState({ - Key key, + Key? key, this.error, - @required this.messageId, - @required this.attachmentId, + required this.messageId, + required this.attachmentId, }) : super(key: key); @override @@ -203,7 +202,7 @@ class _FailedState extends StatelessWidget { color: theme.colorTheme.white, ), onPressed: () { - return channel.retryAttachmentUpload(messageId, attachmentId); + channel.retryAttachmentUpload(messageId, attachmentId); }, ), Center( @@ -213,7 +212,10 @@ class _FailedState extends StatelessWidget { color: theme.colorTheme.overlayDark.withOpacity(0.6), ), child: Padding( - padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), + padding: const EdgeInsets.symmetric( + vertical: 6, + horizontal: 12, + ), child: Text( 'UPLOAD ERROR', style: theme.textTheme.footnote.copyWith( 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 bd58462d..d80109c2 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart @@ -13,15 +13,9 @@ extension AttachmentSourceX on AttachmentSource { /// Its prototype depends on the AttachmentSource defined. // ignore: missing_return T when({ - @required T Function() local, - @required T Function() network, + required T Function() local, + required T Function() network, }) { - assert(() { - if (local == null || network == null) { - throw 'check for all possible cases'; - } - return true; - }()); switch (this) { case AttachmentSource.local: return local(); @@ -32,30 +26,32 @@ extension AttachmentSourceX on AttachmentSource { } abstract class AttachmentWidget extends StatelessWidget { - final Size size; + final Size? size; + final AttachmentSource? _source; final Message message; final Attachment attachment; - final AttachmentSource _source; - AttachmentSource get source => _source ?? attachment.file != null - ? AttachmentSource.local - : AttachmentSource.network; + AttachmentSource get source => + _source ?? + (attachment.file != null + ? AttachmentSource.local + : AttachmentSource.network); const AttachmentWidget({ - Key key, - @required this.message, - @required this.attachment, + Key? key, + required this.message, + required this.attachment, this.size, - AttachmentSource source, + AttachmentSource? source, }) : _source = source, super(key: key); } class AttachmentError extends StatelessWidget { - final Size size; + final Size? size; const AttachmentError({ - Key key, + Key? key, this.size, }) : super(key: key); 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 1003c767..929cddc2 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -11,19 +11,24 @@ import '../upload_progress_indicator.dart'; import 'attachment_widget.dart'; class FileAttachment extends AttachmentWidget { - final Widget title; - final Widget trailing; - final VoidCallback onAttachmentTap; + final Widget? title; + final Widget? trailing; + final VoidCallback? onAttachmentTap; const FileAttachment({ - Key key, - @required Message message, - @required Attachment attachment, - Size size, + Key? key, + required Message message, + required Attachment attachment, + Size? size, this.title, this.trailing, this.onAttachmentTap, - }) : super(key: key, message: message, attachment: attachment, size: size); + }) : super( + key: key, + message: message, + attachment: attachment, + size: size, + ); bool get isVideoAttachment => attachment.title?.mimeType?.type == 'video'; @@ -31,6 +36,7 @@ class FileAttachment extends AttachmentWidget { @override Widget build(BuildContext context) { + final colorTheme = StreamChatTheme.of(context).colorTheme; return Material( child: GestureDetector( onTap: onAttachmentTap, @@ -38,10 +44,10 @@ class FileAttachment extends AttachmentWidget { width: size?.width ?? 100, height: 56.0, decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: colorTheme.white, borderRadius: BorderRadius.circular(12), border: Border.all( - color: StreamChatTheme.of(context).colorTheme.greyWhisper, + color: colorTheme.greyWhisper, ), ), child: Row( @@ -60,7 +66,7 @@ class FileAttachment extends AttachmentWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - attachment?.title ?? 'File', + attachment.title ?? 'File', style: StreamChatTheme.of(context).textTheme.bodyBold, maxLines: 1, overflow: TextOverflow.ellipsis, @@ -93,34 +99,51 @@ class FileAttachment extends AttachmentWidget { type: MaterialType.transparency, shape: _getDefaultShape(context), child: source.when( - local: () => Image.memory( - attachment.file.bytes, - fit: BoxFit.cover, - errorBuilder: (_, obj, trace) { - return getFileTypeImage(attachment.extraData['other']); - }, - ), - network: () => CachedNetworkImage( - imageUrl: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, - fit: BoxFit.cover, - errorWidget: (_, obj, trace) { - return getFileTypeImage(attachment.extraData['other']); - }, - placeholder: (_, __) { - return Shimmer.fromColors( - baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - child: Image.asset( + local: () { + if (attachment.file?.bytes == null) { + return getFileTypeImage(attachment.extraData['other'] as String?); + } + return Image.memory( + attachment.file!.bytes!, + fit: BoxFit.cover, + errorBuilder: (_, obj, trace) { + return getFileTypeImage( + attachment.extraData['other'] as String?); + }, + ); + }, + network: () { + if ((attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl) == + null) { + return getFileTypeImage(attachment.extraData['other'] as String?); + } + return CachedNetworkImage( + imageUrl: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl!, + fit: BoxFit.cover, + errorWidget: (_, obj, trace) { + return getFileTypeImage( + attachment.extraData['other'] as String?); + }, + placeholder: (_, __) { + final image = Image.asset( 'images/placeholder.png', fit: BoxFit.cover, package: 'stream_chat_flutter', - ), - ); - }, - ), + ); + + final colorTheme = StreamChatTheme.of(context).colorTheme; + return Shimmer.fromColors( + baseColor: colorTheme.greyGainsboro, + highlightColor: colorTheme.whiteSmoke, + child: image, + ); + }, + ); + }, ), ); } @@ -132,7 +155,7 @@ class FileAttachment extends AttachmentWidget { shape: _getDefaultShape(context), child: source.when( local: () => VideoThumbnailImage( - video: attachment.file.path, + video: attachment.file?.path, placeholderBuilder: (_) { return Center( child: Container( @@ -158,14 +181,14 @@ class FileAttachment extends AttachmentWidget { ), ); } - return getFileTypeImage(attachment.extraData['mime_type']); + return getFileTypeImage(attachment.extraData['mime_type'] as String?); } Widget _buildButton({ - Widget icon, + Widget? icon, double iconSize = 24.0, - VoidCallback onPressed, - Color fillColor, + VoidCallback? onPressed, + Color? fillColor, }) { return Container( height: iconSize, @@ -189,7 +212,7 @@ class FileAttachment extends AttachmentWidget { final channel = StreamChannel.of(context).channel; final attachmentId = attachment.id; var trailingWidget = trailing; - trailingWidget ??= attachment.uploadState?.when( + trailingWidget ??= attachment.uploadState.when( preparing: () => Padding( padding: const EdgeInsets.all(8.0), child: _buildButton( @@ -220,7 +243,7 @@ class FileAttachment extends AttachmentWidget { icon: StreamSvgIcon.retry(color: theme.colorTheme.white), fillColor: theme.colorTheme.overlayDark, onPressed: () => channel.retryAttachmentUpload( - message?.id, + message.id, attachmentId, ), ), @@ -236,9 +259,7 @@ class FileAttachment extends AttachmentWidget { }, ); - if (message != null && - (message.status == null || - message.status == MessageSendingStatus.sent)) { + if (message.status == MessageSendingStatus.sent) { trailingWidget = IconButton( icon: StreamSvgIcon.cloudDownload(color: theme.colorTheme.black), padding: const EdgeInsets.all(8), @@ -262,11 +283,8 @@ class FileAttachment extends AttachmentWidget { final textStyle = theme.textTheme.footnote.copyWith( color: theme.colorTheme.grey, ); - return attachment.uploadState?.when( + return attachment.uploadState.when( preparing: () { - if (message == null) { - return Text('${fileSize(size, 2)}', style: textStyle); - } return UploadProgressIndicator( uploaded: 0, total: double.maxFinite.toInt(), 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 8b8bb4c1..cb8d3556 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -9,17 +9,15 @@ import '../stream_svg_icon.dart'; import 'attachment_widget.dart'; class GiphyAttachment extends AttachmentWidget { - final MessageTheme messageTheme; - final ShowMessageCallback onShowMessage; - final ValueChanged onReturnAction; - final VoidCallback onAttachmentTap; + final ShowMessageCallback? onShowMessage; + final ValueChanged? onReturnAction; + final VoidCallback? onAttachmentTap; const GiphyAttachment({ - Key key, - @required Message message, - @required Attachment attachment, - Size size, - this.messageTheme, + Key? key, + required Message message, + required Attachment attachment, + Size? size, this.onShowMessage, this.onReturnAction, this.onAttachmentTap, @@ -29,10 +27,10 @@ class GiphyAttachment extends AttachmentWidget { Widget build(BuildContext context) { final imageUrl = attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl; - if (imageUrl == null && source == AttachmentSource.network) { + if (imageUrl == null) { return AttachmentError(); } - if (attachment.actions != null) { + if (attachment.actions.isNotEmpty) { return _buildSendingAttachment(context, imageUrl); } return _buildSentAttachment(context, imageUrl); @@ -73,7 +71,7 @@ class GiphyAttachment extends AttachmentWidget { if (attachment.title != null) Flexible( child: Text( - attachment.title, + attachment.title!, style: TextStyle( color: StreamChatTheme.of(context) .colorTheme @@ -199,10 +197,11 @@ class GiphyAttachment extends AttachmentWidget { child: Text( 'Send', style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - fontWeight: FontWeight.bold), + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, + fontWeight: FontWeight.bold, + ), ), ), ), @@ -259,8 +258,7 @@ class GiphyAttachment extends AttachmentWidget { channel: channel, child: FullScreenMedia( mediaAttachments: [attachment], - userName: message.user.name, - sentAt: message.createdAt, + userName: message.user?.name, message: message, onShowMessage: onShowMessage, ), @@ -268,7 +266,7 @@ class GiphyAttachment extends AttachmentWidget { }, ), ); - if (res != null) onReturnAction(res); + if (res != null) onReturnAction?.call(res); } Widget _buildSentAttachment(BuildContext context, String imageUrl) { @@ -282,14 +280,13 @@ class GiphyAttachment extends AttachmentWidget { channel: channel, child: FullScreenMedia( mediaAttachments: [attachment], - userName: message.user.name, - sentAt: message.createdAt, + userName: message.user?.name, message: message, onShowMessage: onShowMessage, ), ); })); - if (res != null) onReturnAction(res); + if (res != null) onReturnAction!(res); }, child: Stack( children: [ @@ -297,16 +294,17 @@ class GiphyAttachment extends AttachmentWidget { height: size?.height, width: size?.width, placeholder: (_, __) { + final image = Image.asset( + 'images/placeholder.png', + fit: BoxFit.cover, + package: 'stream_chat_flutter', + ); + + final colorTheme = StreamChatTheme.of(context).colorTheme; return Shimmer.fromColors( - baseColor: - StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - child: Image.asset( - 'images/placeholder.png', - fit: BoxFit.cover, - package: 'stream_chat_flutter', - ), + baseColor: colorTheme.greyGainsboro, + highlightColor: colorTheme.whiteSmoke, + child: image, ); }, imageUrl: imageUrl, diff --git a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart index aa8d1246..6ac48f59 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart @@ -10,35 +10,40 @@ import 'attachment_title.dart'; import 'attachment_widget.dart'; class ImageAttachment extends AttachmentWidget { - final MessageTheme messageTheme; + final MessageTheme? messageTheme; final bool showTitle; - final ShowMessageCallback onShowMessage; - final ValueChanged onReturnAction; - final VoidCallback onAttachmentTap; + final ShowMessageCallback? onShowMessage; + final ValueChanged? onReturnAction; + final VoidCallback? onAttachmentTap; const ImageAttachment({ - Key key, - @required Message message, - @required Attachment attachment, - Size size, + Key? key, + required Message message, + required Attachment attachment, + Size? size, this.messageTheme, this.showTitle = false, this.onShowMessage, this.onReturnAction, this.onAttachmentTap, - }) : super(key: key, message: message, attachment: attachment, size: size); + }) : super( + key: key, + message: message, + attachment: attachment, + size: size, + ); @override Widget build(BuildContext context) { return source.when( local: () { - if (attachment.localUri == null) { + if (attachment.localUri == null || attachment.file?.bytes == null) { return AttachmentError(size: size); } return _buildImageAttachment( context, Image.memory( - attachment.file.bytes, + attachment.file!.bytes!, height: size?.height, width: size?.width, fit: BoxFit.cover, @@ -85,15 +90,16 @@ class ImageAttachment extends AttachmentWidget { height: size?.height, width: size?.width, placeholder: (_, __) { + final image = Image.asset( + 'images/placeholder.png', + fit: BoxFit.cover, + package: 'stream_chat_flutter', + ); + final colorTheme = StreamChatTheme.of(context).colorTheme; return Shimmer.fromColors( - baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - child: Image.asset( - 'images/placeholder.png', - fit: BoxFit.cover, - package: 'stream_chat_flutter', - ), + baseColor: colorTheme.greyGainsboro, + highlightColor: colorTheme.whiteSmoke, + child: image, ); }, imageUrl: imageUrl, @@ -109,7 +115,7 @@ class ImageAttachment extends AttachmentWidget { Widget _buildImageAttachment(BuildContext context, Widget imageWidget) { return ConstrainedBox( - constraints: BoxConstraints.loose(size), + constraints: BoxConstraints.loose(size!), child: Column( children: [ Expanded( @@ -127,8 +133,7 @@ class ImageAttachment extends AttachmentWidget { channel: channel, child: FullScreenMedia( mediaAttachments: [attachment], - userName: message.user.name, - sentAt: message.createdAt, + userName: message.user?.name, message: message, onShowMessage: onShowMessage, ), @@ -136,7 +141,7 @@ class ImageAttachment extends AttachmentWidget { }, ), ); - if (result != null) onReturnAction(result); + if (result != null) onReturnAction?.call(result); }, child: imageWidget, ), @@ -152,7 +157,7 @@ class ImageAttachment extends AttachmentWidget { ), if (showTitle && attachment.title != null) Material( - color: messageTheme.messageBackgroundColor, + color: messageTheme?.messageBackgroundColor, child: AttachmentTitle( messageTheme: messageTheme, attachment: attachment, diff --git a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart index d2ffc335..05c511b5 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart @@ -8,21 +8,26 @@ import 'attachment_upload_state_builder.dart'; import 'attachment_widget.dart'; class VideoAttachment extends AttachmentWidget { - final MessageTheme messageTheme; - final ShowMessageCallback onShowMessage; - final ValueChanged onReturnAction; - final VoidCallback onAttachmentTap; + final MessageTheme? messageTheme; + final ShowMessageCallback? onShowMessage; + final ValueChanged? onReturnAction; + final VoidCallback? onAttachmentTap; const VideoAttachment({ - Key key, - @required Message message, - @required Attachment attachment, - Size size, + Key? key, + required Message message, + required Attachment attachment, + Size? size, this.messageTheme, this.onShowMessage, this.onReturnAction, this.onAttachmentTap, - }) : super(key: key, message: message, attachment: attachment, size: size); + }) : super( + key: key, + message: message, + attachment: attachment, + size: size, + ); @override Widget build(BuildContext context) { @@ -34,7 +39,7 @@ class VideoAttachment extends AttachmentWidget { return _buildVideoAttachment( context, VideoThumbnailImage( - video: attachment.file.path, + video: attachment.file?.path, height: size?.height, width: size?.width, fit: BoxFit.cover, @@ -62,7 +67,7 @@ class VideoAttachment extends AttachmentWidget { Widget _buildVideoAttachment(BuildContext context, Widget videoWidget) { return ConstrainedBox( - constraints: BoxConstraints.loose(size), + constraints: BoxConstraints.loose(size ?? Size.infinite), child: Column( children: [ Expanded( @@ -77,15 +82,14 @@ class VideoAttachment extends AttachmentWidget { channel: channel, child: FullScreenMedia( mediaAttachments: [attachment], - userName: message.user.name, - sentAt: message.createdAt, + userName: message.user?.name, message: message, onShowMessage: onShowMessage, ), ), ), ); - if (res != null) onReturnAction(res); + if (res != null) onReturnAction?.call(res); }, child: Stack( children: [ @@ -112,7 +116,7 @@ class VideoAttachment extends AttachmentWidget { ), if (attachment.title != null) Material( - color: messageTheme.messageBackgroundColor, + color: messageTheme?.messageBackgroundColor, child: AttachmentTitle( messageTheme: messageTheme, attachment: attachment, 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 0c8eac7d..642b8b00 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart @@ -12,7 +12,7 @@ import 'extension.dart'; /// Callback to download an attachment asset typedef AttachmentDownloader = Future Function( Attachment attachment, { - ProgressCallback progressCallback, + ProgressCallback? progressCallback, }); /// Widget that shows the options in the gallery view @@ -21,25 +21,25 @@ class AttachmentActionsModal extends StatelessWidget { final Message message; /// Current page index - final currentIndex; + final int currentIndex; /// Callback to show the message - final VoidCallback onShowMessage; + final VoidCallback? onShowMessage; /// Callback to download images - final AttachmentDownloader imageDownloader; + final AttachmentDownloader? imageDownloader; /// Callback to provide download files - final AttachmentDownloader fileDownloader; + final AttachmentDownloader? fileDownloader; /// Returns a new [AttachmentActionsModal] const AttachmentActionsModal({ - @required this.currentIndex, - this.message, + required this.currentIndex, + required this.message, this.onShowMessage, this.imageDownloader, this.fileDownloader, - }) : assert(currentIndex != null, 'currentIndex cannot be null'); + }); @override Widget build(BuildContext context) { @@ -99,11 +99,16 @@ class AttachmentActionsModal extends StatelessWidget { () { final attachment = message.attachments[currentIndex]; final isImage = attachment.type == 'image'; - final saveFile = fileDownloader ?? _downloadAttachment; - final saveImage = imageDownloader ?? _downloadAttachment; + 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>( + final progressNotifier = + ValueNotifier<_DownloadProgress?>( _DownloadProgress.initial(), ); @@ -134,7 +139,7 @@ class AttachmentActionsModal extends StatelessWidget { ); }, ), - if (StreamChat.of(context).user.id == message.user.id) + if (StreamChat.of(context).user?.id == message.user?.id) _buildButton( context, 'Delete', @@ -164,8 +169,10 @@ class AttachmentActionsModal extends StatelessWidget { color: theme.colorTheme.accentRed, ), ] - .map((e) => - Align(alignment: Alignment.centerRight, child: e)) + .map((e) => Align( + alignment: Alignment.centerRight, + child: e, + )) .insertBetween( Container( height: 1, @@ -184,9 +191,9 @@ class AttachmentActionsModal extends StatelessWidget { context, String title, StreamSvgIcon icon, - VoidCallback onTap, { - Color color, - Key key, + VoidCallback? onTap, { + Color? color, + Key? key, }) { return Material( key: key, @@ -215,16 +222,16 @@ class AttachmentActionsModal extends StatelessWidget { Widget _buildDownloadProgressDialog( BuildContext context, - ValueNotifier<_DownloadProgress> progressNotifier, + ValueNotifier<_DownloadProgress?> progressNotifier, ) { final theme = StreamChatTheme.of(context); return WillPopScope( onWillPop: () => Future.value(false), child: ValueListenableBuilder( valueListenable: progressNotifier, - builder: (_, _DownloadProgress progress, __) { + builder: (_, _DownloadProgress? progress, __) { // Pop the dialog in case the progress is null or it's completed. - if (progress == null || progress?.toProgressIndicatorValue == 1.0) { + if (progress == null || progress.toProgressIndicatorValue == 1.0) { Future.delayed( const Duration(milliseconds: 500), Navigator.of(context).maybePop, @@ -291,23 +298,23 @@ class AttachmentActionsModal extends StatelessWidget { ); } - Future _downloadAttachment( + Future _downloadAttachment( Attachment attachment, { - ProgressCallback progressCallback, + ProgressCallback? progressCallback, }) async { - String filePath; + String? filePath; final appDocDir = await getTemporaryDirectory(); await Dio().download( - attachment.assetUrl ?? attachment.imageUrl ?? attachment.thumbUrl, + attachment.assetUrl ?? attachment.imageUrl ?? attachment.thumbUrl!, (Headers responseHeaders) { - final contentType = responseHeaders[Headers.contentTypeHeader]; - final mimeType = contentType.first?.split('/')?.last; + final contentType = responseHeaders[Headers.contentTypeHeader]!; + final mimeType = contentType.first.split('/').last; filePath ??= '${appDocDir.path}/${attachment.id}.$mimeType'; return filePath; }, onReceiveProgress: progressCallback, ); - final result = await ImageGallerySaver.saveFile(filePath); + final result = await ImageGallerySaver.saveFile(filePath!); return (result as Map)['filePath']; } } diff --git a/packages/stream_chat_flutter/lib/src/back_button.dart b/packages/stream_chat_flutter/lib/src/back_button.dart index f49fa98c..3d38364a 100644 --- a/packages/stream_chat_flutter/lib/src/back_button.dart +++ b/packages/stream_chat_flutter/lib/src/back_button.dart @@ -6,17 +6,17 @@ import '../stream_chat_flutter.dart'; class StreamBackButton extends StatelessWidget { const StreamBackButton({ - Key key, + Key? key, this.onPressed, this.showUnreads = false, this.cid, }) : super(key: key); - final VoidCallback onPressed; + final VoidCallback? onPressed; final bool showUnreads; /// Channel cid used to retrieve unread count - final String cid; + final String? cid; @override Widget build(BuildContext context) { @@ -34,7 +34,7 @@ class StreamBackButton extends StatelessWidget { hoverElevation: 0, onPressed: () { if (onPressed != null) { - onPressed(); + onPressed!(); } else { Navigator.maybePop(context); } 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 2f3a26b2..c4218632 100644 --- a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart @@ -5,7 +5,7 @@ import 'channel_info.dart'; import 'option_list_tile.dart'; class ChannelBottomSheet extends StatefulWidget { - final VoidCallback onViewInfoTap; + final VoidCallback? onViewInfoTap; ChannelBottomSheet({this.onViewInfoTap}); @@ -18,13 +18,13 @@ class _ChannelBottomSheetState extends State { @override Widget build(BuildContext context) { - var channel = StreamChannel.of(context).channel; + final channel = StreamChannel.of(context).channel; - var members = channel.state.members; + final members = channel.state?.members ?? []; - var userAsMember = - members.firstWhere((e) => e.user.id == StreamChat.of(context).user.id); - var isOwner = userAsMember.role == 'owner'; + final userAsMember = members + .firstWhere((e) => e.user?.id == StreamChat.of(context).user?.id); + final isOwner = userAsMember.role == 'owner'; return Material( color: StreamChatTheme.of(context).colorTheme.white, @@ -73,8 +73,8 @@ class _ChannelBottomSheetState extends State { UserAvatar( user: members .firstWhere( - (e) => e.user.id != userAsMember.user.id) - .user, + (e) => e.user?.id != userAsMember.user?.id) + .user!, constraints: BoxConstraints( maxHeight: 64.0, maxWidth: 64.0, @@ -88,10 +88,11 @@ class _ChannelBottomSheetState extends State { ), Text( members - .firstWhere( - (e) => e.user.id != userAsMember.user.id) - .user - .name, + .firstWhere( + (e) => e.user?.id != userAsMember.user?.id) + .user + ?.name ?? + '', style: StreamChatTheme.of(context).textTheme.footnoteBold, maxLines: 1, @@ -113,7 +114,7 @@ class _ChannelBottomSheetState extends State { child: Column( children: [ UserAvatar( - user: members[index].user, + user: members[index].user!, constraints: BoxConstraints.tightFor( height: 64.0, width: 64.0, @@ -126,7 +127,7 @@ class _ChannelBottomSheetState extends State { height: 6.0, ), Text( - members[index].user.name, + members[index].user?.name ?? '', style: StreamChatTheme.of(context) .textTheme .footnoteBold, @@ -220,7 +221,7 @@ class _ChannelBottomSheetState extends State { color: StreamChatTheme.of(context).colorTheme.accentRed, ), ); - var channel = StreamChannel.of(context).channel; + final channel = StreamChannel.of(context).channel; if (res == true) { await channel.delete(); Navigator.pop(context); @@ -240,7 +241,10 @@ class _ChannelBottomSheetState extends State { ); if (res == true) { final channel = StreamChannel.of(context).channel; - await channel.removeMembers([StreamChat.of(context).user.id]); + final user = StreamChat.of(context).user; + if (user != null) { + await channel.removeMembers([user.id]); + } Navigator.pop(context); } } diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index c2927a6e..82270e3b 100644 --- a/packages/stream_chat_flutter/lib/src/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_header.dart @@ -58,13 +58,13 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { /// Callback to call when pressing the back button. /// By default it calls [Navigator.pop] - final VoidCallback onBackPressed; + final VoidCallback? onBackPressed; /// Callback to call when the header is tapped. - final VoidCallback onTitleTap; + final VoidCallback? onTitleTap; /// Callback to call when the image is tapped. - final VoidCallback onImageTap; + final VoidCallback? onImageTap; /// If true the typing indicator will be rendered if a user is typing final bool showTypingIndicator; @@ -72,21 +72,21 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { final bool showConnectionStateTile; /// Title widget - final Widget title; + final Widget? title; /// Subtitle widget - final Widget subtitle; + final Widget? subtitle; /// Leading widget - final Widget leading; + final Widget? leading; /// AppBar actions /// By default it shows the [ChannelImage] - final List actions; + final List? actions; /// Creates a channel header ChannelHeader({ - Key key, + Key? key, this.showBackButton = true, this.onBackPressed, this.onTitleTap, @@ -151,12 +151,12 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { .channelTheme .channelHeaderTheme .avatarTheme - .borderRadius, + ?.borderRadius, constraints: StreamChatTheme.of(context) .channelTheme .channelHeaderTheme .avatarTheme - .constraints, + ?.constraints, onTap: onImageTap, ), ), diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 4b3edce3..041e0e03 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -46,7 +46,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class ChannelImage extends StatelessWidget { /// Instantiate a new ChannelImage const ChannelImage({ - Key key, + Key? key, this.channel, this.constraints, this.onTap, @@ -56,20 +56,20 @@ class ChannelImage extends StatelessWidget { this.selectionThickness = 4, }) : super(key: key); - final BorderRadius borderRadius; + final BorderRadius? borderRadius; /// The channel to show the image of - final Channel channel; + final Channel? channel; /// The diameter of the image - final BoxConstraints constraints; + final BoxConstraints? constraints; /// The function called when the image is tapped - final VoidCallback onTap; + final VoidCallback? onTap; final bool selected; - final Color selectionColor; + final Color? selectionColor; final double selectionThickness; @@ -81,30 +81,30 @@ class ChannelImage extends StatelessWidget { stream: channel.extraDataStream, initialData: channel.extraData, builder: (context, snapshot) { - String image; - if (snapshot.data?.containsKey('image') == true) { - image = snapshot.data['image']; - } else if (channel.state.members?.length == 2) { - final otherMember = channel.state.members - .firstWhere((member) => member.user.id != streamChat.user.id); + String? image; + if (snapshot.data!.containsKey('image') == true) { + image = snapshot.data!['image']; + } else if (channel.state?.members.length == 2) { + final otherMember = channel.state?.members + .firstWhere((member) => member.user?.id != streamChat.user?.id); return StreamBuilder( - stream: streamChat.client.state.usersStream - .map((users) => users[otherMember.userId]), - initialData: otherMember.user, + stream: streamChat.client.state.usersStream.map( + (users) => users[otherMember?.userId] ?? otherMember!.user!), + initialData: otherMember!.user, builder: (context, snapshot) { return UserAvatar( borderRadius: borderRadius ?? StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme - .borderRadius, - user: snapshot.data ?? otherMember.user, + ?.borderRadius, + user: snapshot.data ?? otherMember.user!, constraints: constraints ?? StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme - .constraints, - onTap: onTap != null ? (_) => onTap() : null, + ?.constraints, + onTap: onTap != null ? (_) => onTap!() : null, selected: selected, selectionColor: selectionColor ?? StreamChatTheme.of(context).colorTheme.accentBlue, @@ -112,25 +112,25 @@ class ChannelImage extends StatelessWidget { ); }); } else { - final images = channel.state.members + final images = channel.state?.members .where((member) => - member.user.id != streamChat.user.id && - member.user.extraData['image'] != null) + member.user?.id != streamChat.user?.id && + member.user?.extraData['image'] != null) .take(4) - .map((e) => e.user.extraData['image'] as String) + .map((e) => e.user?.extraData['image'] as String?) .toList(); return GroupImage( - images: images, + images: images ?? [], borderRadius: borderRadius ?? StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme - .borderRadius, + ?.borderRadius, constraints: constraints ?? StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme - .constraints, + ?.constraints, onTap: onTap, selected: selected, selectionColor: selectionColor ?? @@ -144,13 +144,13 @@ class ChannelImage extends StatelessWidget { StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme - .borderRadius, + ?.borderRadius, child: Container( constraints: constraints ?? StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme - .constraints, + ?.constraints, decoration: BoxDecoration( color: StreamChatTheme.of(context).colorTheme.accentBlue, ), @@ -165,7 +165,7 @@ class ChannelImage extends StatelessWidget { return Center( child: Text( snapshot.data?.containsKey('name') ?? false - ? snapshot.data['name'][0] + ? snapshot.data!['name'][0] : '', style: TextStyle( color: StreamChatTheme.of(context) @@ -178,8 +178,10 @@ class ChannelImage extends StatelessWidget { }, fit: BoxFit.cover, ) - : StreamChatTheme.of(context) - .defaultChannelImage(context, channel), + : StreamChatTheme.of(context).defaultChannelImage( + context, + channel, + ), Material( color: Colors.transparent, child: InkWell( @@ -197,14 +199,15 @@ class ChannelImage extends StatelessWidget { StreamChatTheme.of(context) .ownMessageTheme .avatarTheme - .borderRadius) + + ?.borderRadius ?? + BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( constraints: constraints ?? StreamChatTheme.of(context) .ownMessageTheme .avatarTheme - .constraints, + ?.constraints, color: selectionColor ?? StreamChatTheme.of(context).colorTheme.accentBlue, child: Padding( diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index 1d6f6fe2..dfcaa749 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -8,14 +9,14 @@ class ChannelInfo extends StatelessWidget { final Channel channel; /// The style of the text displayed - final TextStyle textStyle; + final TextStyle? textStyle; /// If true the typing indicator will be rendered if a user is typing final bool showTypingIndicator; const ChannelInfo({ - Key key, - @required this.channel, + Key? key, + required this.channel, this.textStyle, this.showTypingIndicator = true, }) : super(key: key); @@ -24,8 +25,8 @@ class ChannelInfo extends StatelessWidget { Widget build(BuildContext context) { final client = StreamChat.of(context).client; return StreamBuilder>( - stream: channel.state.membersStream, - initialData: channel.state.members, + stream: channel.state?.membersStream, + initialData: channel.state?.members, builder: (context, snapshot) { return ConnectionStatusBuilder( statusBuilder: (context, status) { @@ -45,12 +46,13 @@ class ChannelInfo extends StatelessWidget { ); } - Widget _buildConnectedTitleState(BuildContext context, List members) { + Widget _buildConnectedTitleState( + BuildContext context, List? members) { var alternativeWidget; - if (channel.memberCount != null && channel.memberCount > 2) { + if (channel.memberCount != null && channel.memberCount! > 2) { var text = '${channel.memberCount} Members'; - final watcherCount = channel.state.watcherCount ?? 0; + final watcherCount = channel.state?.watcherCount ?? 0; if (watcherCount > 0) text += ' $watcherCount Online'; alternativeWidget = Text( text, @@ -60,20 +62,19 @@ class ChannelInfo extends StatelessWidget { .subtitle, ); } else { - final otherMember = members.firstWhere( - (element) => element.userId != StreamChat.of(context).user.id, - orElse: () => null, + final otherMember = members?.firstWhereOrNull( + (element) => element.userId != StreamChat.of(context).user?.id, ); if (otherMember != null) { - if (otherMember.user.online) { + if (otherMember.user?.online == true) { alternativeWidget = Text( 'Online', style: textStyle, ); } else { alternativeWidget = Text( - 'Last seen ${Jiffy(otherMember.user.lastActive).fromNow()}', + 'Last seen ${Jiffy(otherMember.user?.lastActive).fromNow()}', style: textStyle, ); } @@ -112,7 +113,9 @@ class ChannelInfo extends StatelessWidget { } Widget _buildDisconnectedTitleState( - BuildContext context, StreamChatClient client) { + BuildContext context, + StreamChatClient client, + ) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -131,11 +134,11 @@ class ChannelInfo extends StatelessWidget { ), onPressed: () async { await client.disconnect(); - return client.connect(); + await client.connect(); }, child: Text( 'Try Again', - style: textStyle.copyWith( + style: textStyle?.copyWith( color: StreamChatTheme.of(context).colorTheme.accentBlue, ), ), diff --git a/packages/stream_chat_flutter/lib/src/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel_list_header.dart index 88b329e6..f3889e22 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -10,7 +10,7 @@ import 'connection_status_builder.dart'; import 'info_tile.dart'; import 'stream_chat.dart'; -typedef _TitleBuilder = Widget Function( +typedef TitleBuilder = Widget Function( BuildContext context, ConnectionStatus status, StreamChatClient client, @@ -50,7 +50,7 @@ typedef _TitleBuilder = Widget Function( class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { /// Instantiates a ChannelListHeader const ChannelListHeader({ - Key key, + Key? key, this.client, this.titleBuilder, this.onUserAvatarTap, @@ -63,32 +63,32 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { }) : super(key: key); /// Pass this if you don't have a [StreamChatClient] in your widget tree. - final StreamChatClient client; + final StreamChatClient? client; /// Use this to build your own title as per different [ConnectionStatus] - final _TitleBuilder titleBuilder; + final TitleBuilder? titleBuilder; /// Callback to call when pressing the user avatar button. /// By default it calls Scaffold.of(context).openDrawer() - final Function(User) onUserAvatarTap; + final Function(User)? onUserAvatarTap; /// Callback to call when pressing the new chat button. - final VoidCallback onNewChatButtonTap; + final VoidCallback? onNewChatButtonTap; final bool showConnectionStateTile; - final VoidCallback preNavigationCallback; + final VoidCallback? preNavigationCallback; /// Subtitle widget - final Widget subtitle; + final Widget? subtitle; /// Leading widget /// By default it shows the logged in user avatar - final Widget leading; + final Widget? leading; /// AppBar actions /// By default it shows the new chat button - final List actions; + final List? actions; @override Widget build(BuildContext context) { @@ -123,25 +123,27 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { centerTitle: true, leading: leading ?? Center( - child: UserAvatar( - user: user, - showOnlineStatus: false, - onTap: onUserAvatarTap ?? - (_) { - if (preNavigationCallback != null) { - preNavigationCallback(); - } - Scaffold.of(context).openDrawer(); - }, - borderRadius: StreamChatTheme.of(context) - .channelListHeaderTheme - .avatarTheme - .borderRadius, - constraints: StreamChatTheme.of(context) - .channelListHeaderTheme - .avatarTheme - .constraints, - ), + child: user != null + ? UserAvatar( + user: user, + showOnlineStatus: false, + onTap: onUserAvatarTap ?? + (_) { + if (preNavigationCallback != null) { + preNavigationCallback!(); + } + Scaffold.of(context).openDrawer(); + }, + borderRadius: StreamChatTheme.of(context) + .channelListHeaderTheme + .avatarTheme + ?.borderRadius, + constraints: StreamChatTheme.of(context) + .channelListHeaderTheme + .avatarTheme + ?.constraints, + ) + : Offstage(), ), actions: actions ?? [ @@ -181,7 +183,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { Builder( builder: (context) { if (titleBuilder != null) { - return titleBuilder(context, status, _client); + return titleBuilder!(context, status, _client); } switch (status) { case ConnectionStatus.connected: @@ -225,40 +227,46 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { SizedBox(width: 10), Text( 'Searching for Network', - style: - StreamChatTheme.of(context).channelListHeaderTheme.title.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + style: StreamChatTheme.of(context) + .channelListHeaderTheme + .title + ?.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), ], ); } Widget _buildDisconnectedTitleState( - BuildContext context, StreamChatClient client) { + BuildContext context, + StreamChatClient client, + ) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Offline...', - style: - StreamChatTheme.of(context).channelListHeaderTheme.title.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + style: StreamChatTheme.of(context) + .channelListHeaderTheme + .title + ?.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), TextButton( onPressed: () async { await client.disconnect(); - return client.connect(); + await client.connect(); }, child: Text( 'Try Again', style: StreamChatTheme.of(context) .channelListHeaderTheme .title - .copyWith( + ?.copyWith( fontSize: 16, fontWeight: FontWeight.bold, color: StreamChatTheme.of(context).colorTheme.accentBlue, 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 90afbd64..a210501e 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; @@ -11,7 +12,7 @@ import 'channel_bottom_sheet.dart'; import 'channel_preview.dart'; /// Callback called when tapping on a channel -typedef ChannelTapCallback = void Function(Channel, Widget); +typedef ChannelTapCallback = void Function(Channel, Widget?); /// Builder used to create a custom [ChannelPreview] from a [Channel] typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel); @@ -54,7 +55,7 @@ typedef ViewInfoCallback = void Function(Channel); class ChannelListView extends StatefulWidget { /// Instantiate a new ChannelListView ChannelListView({ - Key key, + Key? key, this.filter, this.options, this.sort, @@ -90,67 +91,67 @@ class ChannelListView extends StatefulWidget { /// /// state: if true returns the Channel state /// watch: if true listen to changes to this Channel in real time. - final Map options; + final Map? options; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be provided. /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. /// Direction can be ascending or descending. - final List> sort; + final List>? sort; /// Pagination parameters /// limit: the number of channels to return (max is 30) /// offset: the offset (max is 1000) /// message_limit: how many messages should be included to each channel - final PaginationParams pagination; + final PaginationParams? pagination; /// Function called when tapping on a channel /// By default it calls [Navigator.push] building a [MaterialPageRoute] /// with the widget [channelWidget] as child. - final ChannelTapCallback onChannelTap; + final ChannelTapCallback? onChannelTap; /// Function called when long pressing on a channel - final Function(Channel) onChannelLongPress; + final Function(Channel)? onChannelLongPress; /// Widget used when opening a channel - final Widget channelWidget; + final Widget? channelWidget; /// Builder used to create a custom channel preview - final ChannelPreviewBuilder channelPreviewBuilder; + final ChannelPreviewBuilder? channelPreviewBuilder; /// Builder used to create a custom item separator - final Function(BuildContext, int) separatorBuilder; + final Function(BuildContext, int)? separatorBuilder; /// The function called when the image is tapped - final Function(Channel) onImageTap; + final Function(Channel)? onImageTap; /// Set it to false to disable the pull-to-refresh widget final bool pullToRefresh; /// Callback used in the default empty list widget - final VoidCallback onStartChatPressed; + final VoidCallback? onStartChatPressed; /// The number of children in the cross axis. final int crossAxisCount; /// The amount of space by which to inset the children. - final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry? padding; final List selectedChannels; - final ViewInfoCallback onViewInfoTap; + final ViewInfoCallback? onViewInfoTap; /// The builder that will be used in case of error - final ErrorBuilder errorBuilder; + final ErrorBuilder? errorBuilder; /// The builder that will be used in case of loading - final WidgetBuilder loadingBuilder; + final WidgetBuilder? loadingBuilder; /// The builder which is used when list of channels loads - final Function(BuildContext, List) listBuilder; + final Function(BuildContext, List)? listBuilder; /// The builder used when the channel list is empty. - final WidgetBuilder emptyBuilder; + final WidgetBuilder? emptyBuilder; @override _ChannelListViewState createState() => _ChannelListViewState(); @@ -164,7 +165,10 @@ class _ChannelListViewState extends State { @override Widget build(BuildContext context) { Widget child = ChannelListCore( - pagination: widget.pagination, + pagination: widget.pagination ?? + const PaginationParams( + limit: 25, + ), options: widget.options, sort: widget.sort, filter: widget.filter, @@ -177,26 +181,27 @@ class _ChannelListViewState extends State { if (widget.pullToRefresh) { child = RefreshIndicator( - onRefresh: () => _channelListController.loadData(), + onRefresh: () => _channelListController.loadData!(), child: child, ); } return LazyLoadScrollView( - onEndOfPage: () => _channelListController.paginateData(), + onEndOfPage: () => _channelListController.paginateData!(), child: child, ); } Widget _buildListView(BuildContext context, List channels) { - Widget child; + late Widget child; if (channels.isNotEmpty) { if (widget.crossAxisCount > 1) { child = GridView.builder( padding: widget.padding, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: widget.crossAxisCount), + crossAxisCount: widget.crossAxisCount, + ), itemCount: channels.length, physics: AlwaysScrollableScrollPhysics(), itemBuilder: (context, index) { @@ -211,7 +216,7 @@ class _ChannelListViewState extends State { channels.isNotEmpty ? channels.length + 1 : channels.length, separatorBuilder: (_, index) { if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, index); + return widget.separatorBuilder!(context, index); } return _separatorBuilder(context, index); }, @@ -317,7 +322,7 @@ class _ChannelListViewState extends State { if (widget.crossAxisCount == 1) { if (i % 2 != 0) { if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, i); + return widget.separatorBuilder!(context, i); } return _separatorBuilder(context, i); } @@ -447,7 +452,7 @@ class _ChannelListViewState extends State { style: Theme.of(context).textTheme.headline6, ), TextButton( - onPressed: () => _channelListController.loadData(), + onPressed: () => _channelListController.loadData!(), child: Text('Retry'), ), ], @@ -459,24 +464,7 @@ class _ChannelListViewState extends State { final channelsProvider = ChannelsBloc.of(context); if (i < channels.length) { final channel = channels[i]; - ChannelTapCallback onTap; - if (widget.onChannelTap != null) { - onTap = widget.onChannelTap; - } else { - onTap = (client, _) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return StreamChannel( - channel: client, - child: widget.channelWidget, - ); - }, - ), - ); - }; - } + final onTap = _getChannelTap(context); final backgroundColor = StreamChatTheme.of(context).colorTheme.whiteSmoke; return StreamChannel( @@ -509,7 +497,7 @@ class _ChannelListViewState extends State { channel: channel, child: ChannelBottomSheet( onViewInfoTap: () { - widget.onViewInfoTap(channel); + widget.onViewInfoTap?.call(channel); }, ), ); @@ -520,9 +508,9 @@ class _ChannelListViewState extends State { if ([ 'admin', 'owner', - ].contains(channel.state.members - .firstWhere((m) => m.userId == channel.client.state.user.id, - orElse: () => null) + ].contains(channel.state!.members + .firstWhereOrNull( + (m) => m.userId == channel.client.state.user?.id) ?.role)) IconSlideAction( color: backgroundColor, @@ -567,10 +555,35 @@ class _ChannelListViewState extends State { } } - Widget _gridItemBuilder(BuildContext context, int i, List channels) { - var channel = channels[i]; + ChannelTapCallback _getChannelTap(BuildContext context) { + ChannelTapCallback onTap; + if (widget.onChannelTap != null) { + onTap = widget.onChannelTap!; + } else { + onTap = (client, _) { + if (widget.channelWidget == null) { + return; + } + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return StreamChannel( + channel: client, + child: widget.channelWidget!, + ); + }, + ), + ); + }; + } + return onTap; + } - var selected = widget.selectedChannels.contains(channel); + Widget _gridItemBuilder(BuildContext context, int i, List channels) { + final channel = channels[i]; + + final selected = widget.selectedChannels.contains(channel); return Container( key: ValueKey('CHANNEL-${channel.id}'), @@ -586,7 +599,7 @@ class _ChannelListViewState extends State { width: 64, height: 64, ), - onTap: () => widget.onChannelTap(channel, null), + onTap: () => _getChannelTap(context), ), SizedBox(height: 7), Padding( @@ -628,7 +641,7 @@ class _ChannelListViewState extends State { ), ); } - return snapshot.data + return snapshot.data! ? Center( child: Padding( padding: const EdgeInsets.all(16.0), @@ -644,7 +657,7 @@ class _ChannelListViewState extends State { return Container( height: 1, - color: effect.color.withOpacity(effect.alpha ?? 1.0), + color: effect.color!.withOpacity(effect.alpha ?? 1.0), ); } } diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart index 53817563..48657419 100644 --- a/packages/stream_chat_flutter/lib/src/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel_name.dart @@ -10,12 +10,12 @@ import '../stream_chat_flutter.dart'; class ChannelName extends StatelessWidget { /// Instantiate a new ChannelName const ChannelName({ - Key key, + Key? key, this.textStyle, }) : super(key: key); /// The style of the text displayed - final TextStyle textStyle; + final TextStyle? textStyle; @override Widget build(BuildContext context) { @@ -26,31 +26,31 @@ class ChannelName extends StatelessWidget { stream: channel.extraDataStream, initialData: channel.extraData, builder: (context, snapshot) { - return _buildName(snapshot.data, channel.state.members, client); + return _buildName(snapshot.data!, channel.state?.members, client); }, ); } Widget _buildName( Map extraData, - List members, + List? members, StreamChatState client, ) { return LayoutBuilder( builder: (context, constraints) { - String title; + String? title; if (extraData['name'] == null) { final otherMembers = - members.where((member) => member.userId != client.user.id); - if (otherMembers.length == 1) { - title = otherMembers.first.user.name; - } else if (otherMembers.isNotEmpty) { + members?.where((member) => member.userId != client.user!.id); + if (otherMembers?.length == 1) { + title = otherMembers!.first.user?.name; + } else if (otherMembers?.isNotEmpty == true) { final maxWidth = constraints.maxWidth; - final maxChars = maxWidth / textStyle.fontSize; + final maxChars = maxWidth / (textStyle?.fontSize ?? 1); var currentChars = 0; final currentMembers = []; - otherMembers.forEach((element) { - final newLength = currentChars + element.user.name.length; + otherMembers!.forEach((element) { + final newLength = currentChars + (element.user?.name.length ?? 0); if (newLength < maxChars) { currentChars = newLength; currentMembers.add(element); @@ -60,7 +60,7 @@ class ChannelName extends StatelessWidget { final exceedingMembers = otherMembers.length - currentMembers.length; title = - '${currentMembers.map((e) => e.user.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + '${currentMembers.map((e) => e.user?.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; } else { title = 'No title'; } @@ -69,7 +69,7 @@ class ChannelName extends StatelessWidget { } return Text( - title, + title!, style: textStyle, overflow: TextOverflow.ellipsis, ); diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 959342a6..f28e9368 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:jiffy/jiffy.dart'; @@ -20,35 +21,35 @@ import 'channel_name.dart'; /// Modify it to change the widget appearance. class ChannelPreview extends StatelessWidget { /// Function called when tapping this widget - final void Function(Channel) onTap; + final void Function(Channel)? onTap; /// Function called when long pressing this widget - final void Function(Channel) onLongPress; + final void Function(Channel)? onLongPress; /// Channel displayed final Channel channel; /// The function called when the image is tapped - final VoidCallback onImageTap; + final VoidCallback? onImageTap; /// Widget rendering the title - final Widget title; + final Widget? title; /// Widget rendering the subtitle - final Widget subtitle; + final Widget? subtitle; /// Widget rendering the leading element, by default it shows the [ChannelImage] - final Widget leading; + final Widget? leading; /// Widget rendering the trailing element, by default it shows the last message date - final Widget trailing; + final Widget? trailing; /// Widget rendering the sending indicator, by default it uses the [SendingIndicator] widget - final Widget sendingIndicator; + final Widget? sendingIndicator; ChannelPreview({ - @required this.channel, - Key key, + required this.channel, + Key? key, this.onTap, this.onLongPress, this.onImageTap, @@ -67,7 +68,7 @@ class ChannelPreview extends StatelessWidget { initialData: channel.isMuted, builder: (context, snapshot) { return Opacity( - opacity: snapshot.data ? 0.5 : 1, + opacity: snapshot.data! ? 0.5 : 1, child: ListTile( visualDensity: VisualDensity.compact, contentPadding: const EdgeInsets.symmetric( @@ -75,12 +76,12 @@ class ChannelPreview extends StatelessWidget { ), onTap: () { if (onTap != null) { - onTap(channel); + onTap!(channel); } }, onLongPress: () { if (onLongPress != null) { - onLongPress(channel); + onLongPress!(channel); } }, leading: leading ?? @@ -97,13 +98,13 @@ class ChannelPreview extends StatelessWidget { ), ), StreamBuilder>( - stream: channel.state.membersStream, - initialData: channel.state.members, + stream: channel.state?.membersStream, + initialData: channel.state?.members, builder: (context, snapshot) { if (!snapshot.hasData || - snapshot.data.isEmpty || - !snapshot.data.any((Member e) => - e.user.id == channel.client.state.user.id)) { + snapshot.data!.isEmpty || + !snapshot.data!.any((Member e) => + e.user!.id == channel.client.state.user?.id)) { return SizedBox(); } return UnreadIndicator( @@ -120,24 +121,24 @@ class ChannelPreview extends StatelessWidget { sendingIndicator ?? Builder( builder: (context) { - final lastMessage = channel.state.messages.lastWhere( + final lastMessage = + channel.state?.messages.lastWhereOrNull( (m) => !m.isDeleted && m.shadowed != true, - orElse: () => null, ); if (lastMessage?.user?.id == - StreamChat.of(context).user.id) { + StreamChat.of(context).user?.id) { return Padding( padding: const EdgeInsets.only(right: 4.0), child: SendingIndicator( - message: lastMessage, + message: lastMessage!, size: channelPreviewTheme.indicatorIconSize, - isMessageRead: channel.state.read + isMessageRead: channel.state!.read ?.where((element) => element.user.id != - channel.client.state.user.id) - ?.where((element) => element.lastRead + channel.client.state.user!.id) + .where((element) => element.lastRead .isAfter(lastMessage.createdAt)) - ?.isNotEmpty == + .isNotEmpty == true, ), ); @@ -154,14 +155,14 @@ class ChannelPreview extends StatelessWidget { } Widget _buildDate(BuildContext context) { - return StreamBuilder( + return StreamBuilder( stream: channel.lastMessageAtStream, initialData: channel.lastMessageAt, builder: (context, snapshot) { if (!snapshot.hasData) { return SizedBox(); } - final lastMessageAt = snapshot.data.toLocal(); + final lastMessageAt = snapshot.data!.toLocal(); String stringDate; final now = DateTime.now(); @@ -211,60 +212,58 @@ class ChannelPreview extends StatelessWidget { } Widget _buildLastMessage(BuildContext context) { - return StreamBuilder>( - stream: channel.state.messagesStream, - initialData: channel.state.messages, + return StreamBuilder?>( + stream: channel.state!.messagesStream, + initialData: channel.state!.messages, builder: (context, snapshot) { - final lastMessage = snapshot.data?.lastWhere( - (m) => m.shadowed != true && !m.isDeleted, - orElse: () => null); + final lastMessage = snapshot.data + ?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted); if (lastMessage == null) { return SizedBox(); } var text = lastMessage.text; - if (lastMessage.attachments != null) { - final parts = [ - ...lastMessage.attachments.map((e) { - if (e.type == 'image') { - return '📷'; - } else if (e.type == 'video') { - return 'đŸŽŦ'; - } else if (e.type == 'giphy') { - return '[GIF]'; - } - return e == lastMessage.attachments.last - ? (e.title ?? 'File') - : '${e.title ?? 'File'} , '; - }).where((e) => e != null), - lastMessage.text ?? '', - ]; + final parts = [ + ...lastMessage.attachments.map((e) { + if (e.type == 'image') { + return '📷'; + } else if (e.type == 'video') { + return 'đŸŽŦ'; + } else if (e.type == 'giphy') { + return '[GIF]'; + } + return e == lastMessage.attachments.last + ? (e.title ?? 'File') + : '${e.title ?? 'File'} , '; + }), + lastMessage.text ?? '', + ]; - text = parts.join(' '); - } + text = parts.join(' '); return Text.rich( _getDisplayText( text, lastMessage.mentionedUsers, lastMessage.attachments, - StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( + StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( color: StreamChatTheme.of(context) .channelPreviewTheme .subtitle - .color, + ?.color, fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) ? FontStyle.italic : FontStyle.normal), - StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( - color: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - .color, - fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) - ? FontStyle.italic - : FontStyle.normal, - fontWeight: FontWeight.bold), + StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( + color: StreamChatTheme.of(context) + .channelPreviewTheme + .subtitle + ?.color, + fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + fontWeight: FontWeight.bold, + ), ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -274,29 +273,28 @@ class ChannelPreview extends StatelessWidget { } TextSpan _getDisplayText( - String text, - List mentions, - List attachments, - TextStyle normalTextStyle, - TextStyle mentionsTextStyle) { - var textList = text.split(' '); - var resList = []; - for (var e in textList) { - if (mentions != null && - mentions.isNotEmpty && + String text, + List mentions, + List attachments, + TextStyle? normalTextStyle, + TextStyle? mentionsTextStyle, + ) { + final textList = text.split(' '); + final resList = []; + for (final e in textList) { + if (mentions.isNotEmpty && mentions.any((element) => '@${element.name}' == e)) { resList.add(TextSpan( text: '$e ', style: mentionsTextStyle, )); - } else if (attachments != null && - attachments.isNotEmpty && + } else if (attachments.isNotEmpty && attachments .where((e) => e.title != null) .any((element) => element.title == e)) { resList.add(TextSpan( text: '$e ', - style: normalTextStyle.copyWith(fontStyle: FontStyle.italic), + style: normalTextStyle?.copyWith(fontStyle: FontStyle.italic), )); } else { resList.add(TextSpan( diff --git a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart index 01632b1b..620c41ac 100644 --- a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart +++ b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart @@ -11,26 +11,25 @@ import 'stream_chat.dart'; class ConnectionStatusBuilder extends StatelessWidget { /// Creates a new ConnectionStatusBuilder const ConnectionStatusBuilder({ - Key key, - @required this.statusBuilder, + Key? key, + required this.statusBuilder, this.initialStatus = ConnectionStatus.disconnected, this.connectionStatusStream, this.errorBuilder, this.loadingBuilder, - }) : assert(statusBuilder != null), - super(key: key); + }) : super(key: key); /// The connection status that will be used to create the initial snapshot. final ConnectionStatus initialStatus; /// The asynchronous computation to which this builder is currently connected. - final Stream connectionStatusStream; + final Stream? connectionStatusStream; /// The builder that will be used in case of error - final Widget Function(BuildContext context, Object error) errorBuilder; + final Widget Function(BuildContext context, Object? error)? errorBuilder; /// The builder that will be used in case of loading - final WidgetBuilder loadingBuilder; + final WidgetBuilder? loadingBuilder; /// The builder that will be used in case of data final Widget Function(BuildContext context, ConnectionStatus status) @@ -46,15 +45,15 @@ class ConnectionStatusBuilder extends StatelessWidget { builder: (context, snapshot) { if (snapshot.hasError) { if (errorBuilder != null) { - return errorBuilder(context, snapshot.error); + return errorBuilder!(context, snapshot.error); } return Offstage(); } if (!snapshot.hasData) { - if (loadingBuilder != null) return loadingBuilder(context); + if (loadingBuilder != null) return loadingBuilder!(context); return Offstage(); } - return statusBuilder(context, snapshot.data); + return statusBuilder(context, snapshot.data!); }, ); } diff --git a/packages/stream_chat_flutter/lib/src/date_divider.dart b/packages/stream_chat_flutter/lib/src/date_divider.dart index 20b8151d..3674e085 100644 --- a/packages/stream_chat_flutter/lib/src/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/date_divider.dart @@ -8,8 +8,8 @@ class DateDivider extends StatelessWidget { final bool uppercase; const DateDivider({ - Key key, - @required this.dateTime, + Key? key, + required this.dateTime, this.uppercase = false, }) : super(key: key); diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart index bd014770..c0387982 100644 --- a/packages/stream_chat_flutter/lib/src/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart @@ -5,8 +5,8 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; class DeletedMessage extends StatelessWidget { const DeletedMessage({ - Key key, - @required this.messageTheme, + Key? key, + required this.messageTheme, this.borderRadiusGeometry, this.shape, this.borderSide, @@ -14,16 +14,16 @@ class DeletedMessage extends StatelessWidget { }) : super(key: key); /// The theme of the message - final MessageTheme messageTheme; + final MessageTheme? messageTheme; /// The border radius of the message text - final BorderRadiusGeometry borderRadiusGeometry; + final BorderRadiusGeometry? borderRadiusGeometry; /// The shape of the message text - final ShapeBorder shape; + final ShapeBorder? shape; /// The borderside of the message text - final BorderSide borderSide; + final BorderSide? borderSide; /// If true the widget will be mirrored final bool reverse; @@ -34,7 +34,7 @@ class DeletedMessage extends StatelessWidget { transform: Matrix4.rotationY(reverse ? pi : 0), alignment: Alignment.center, child: Material( - color: messageTheme.messageBackgroundColor, + color: messageTheme?.messageBackgroundColor, shape: shape ?? RoundedRectangleBorder( borderRadius: borderRadiusGeometry ?? BorderRadius.zero, @@ -61,9 +61,9 @@ class DeletedMessage extends StatelessWidget { alignment: Alignment.center, child: Text( 'Message deleted', - style: messageTheme.messageText.copyWith( + style: messageTheme?.messageText?.copyWith( fontStyle: FontStyle.italic, - color: messageTheme.createdAt.color, + color: messageTheme?.createdAt?.color, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index daa8a69c..ef73d9ad 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -47,53 +47,53 @@ extension PlatformFileX on PlatformFile { } extension InputDecorationX on InputDecoration { - InputDecoration merge(InputDecoration other) { + InputDecoration merge(InputDecoration? other) { if (other == null) return this; return copyWith( - icon: other?.icon, - labelText: other?.labelText, + icon: other.icon, + labelText: other.labelText, labelStyle: labelStyle?.merge(other.labelStyle) ?? other.labelStyle, - helperText: other?.helperText, + helperText: other.helperText, helperStyle: helperStyle?.merge(other.helperStyle) ?? other.helperStyle, - helperMaxLines: other?.helperMaxLines, - hintText: other?.hintText, + helperMaxLines: other.helperMaxLines, + hintText: other.hintText, hintStyle: hintStyle?.merge(other.hintStyle) ?? other.hintStyle, - hintTextDirection: other?.hintTextDirection, - hintMaxLines: other?.hintMaxLines, - errorText: other?.errorText, + hintTextDirection: other.hintTextDirection, + hintMaxLines: other.hintMaxLines, + errorText: other.errorText, errorStyle: errorStyle?.merge(other.errorStyle) ?? other.errorStyle, - errorMaxLines: other?.errorMaxLines, - floatingLabelBehavior: other?.floatingLabelBehavior, - isCollapsed: other?.isCollapsed, - isDense: other?.isDense, - contentPadding: other?.contentPadding, - prefixIcon: other?.prefixIcon, - prefix: other?.prefix, - prefixText: other?.prefixText, - prefixIconConstraints: other?.prefixIconConstraints, + errorMaxLines: other.errorMaxLines, + floatingLabelBehavior: other.floatingLabelBehavior, + isCollapsed: other.isCollapsed, + isDense: other.isDense, + contentPadding: other.contentPadding, + prefixIcon: other.prefixIcon, + prefix: other.prefix, + prefixText: other.prefixText, + prefixIconConstraints: other.prefixIconConstraints, prefixStyle: prefixStyle?.merge(other.prefixStyle) ?? other.prefixStyle, - suffixIcon: other?.suffixIcon, - suffix: other?.suffix, - suffixText: other?.suffixText, + suffixIcon: other.suffixIcon, + suffix: other.suffix, + suffixText: other.suffixText, suffixStyle: suffixStyle?.merge(other.suffixStyle) ?? other.suffixStyle, - suffixIconConstraints: other?.suffixIconConstraints, - counter: other?.counter, - counterText: other?.counterText, + suffixIconConstraints: other.suffixIconConstraints, + counter: other.counter, + counterText: other.counterText, counterStyle: counterStyle?.merge(other.counterStyle) ?? other.counterStyle, - filled: other?.filled, - fillColor: other?.fillColor, - focusColor: other?.focusColor, - hoverColor: other?.hoverColor, - errorBorder: other?.errorBorder, - focusedBorder: other?.focusedBorder, - focusedErrorBorder: other?.focusedErrorBorder, - disabledBorder: other?.disabledBorder, - enabledBorder: other?.enabledBorder, - border: other?.border, - enabled: other?.enabled, - semanticCounterText: other?.semanticCounterText, - alignLabelWithHint: other?.alignLabelWithHint, + filled: other.filled, + fillColor: other.fillColor, + focusColor: other.focusColor, + hoverColor: other.hoverColor, + errorBorder: other.errorBorder, + focusedBorder: other.focusedBorder, + focusedErrorBorder: other.focusedErrorBorder, + disabledBorder: other.disabledBorder, + enabledBorder: other.enabledBorder, + border: other.border, + enabled: other.enabled, + semanticCounterText: other.semanticCounterText, + alignLabelWithHint: other.alignLabelWithHint, ); } } 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 aa35ed15..4956fb28 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -24,19 +24,18 @@ class FullScreenMedia extends StatefulWidget { final int startIndex; final String userName; - final DateTime sentAt; - final ShowMessageCallback onShowMessage; + final ShowMessageCallback? onShowMessage; /// Instantiate a new FullScreenImage const FullScreenMedia({ - Key key, - @required this.mediaAttachments, - this.message, + Key? key, + required this.mediaAttachments, + required this.message, this.startIndex = 0, - this.userName = '', - this.sentAt, + String? userName, this.onShowMessage, - }) : super(key: key); + }) : userName = userName ?? '', + super(key: key); @override _FullScreenMediaState createState() => _FullScreenMediaState(); @@ -46,10 +45,10 @@ class _FullScreenMediaState extends State with SingleTickerProviderStateMixin { bool _optionsShown = true; - AnimationController _controller; - PageController _pageController; + late final AnimationController _controller; + late final PageController _pageController; - int _currentPage; + late int _currentPage; final videoPackages = {}; @@ -101,10 +100,11 @@ class _FullScreenMediaState extends State attachment.assetUrl ?? attachment.thumbUrl; return PhotoView( - imageProvider: - imageUrl == null && attachment.localUri != null - ? Image.memory(attachment.file.bytes).image - : CachedNetworkImageProvider(imageUrl), + 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( @@ -112,12 +112,12 @@ class _FullScreenMediaState extends State ), backgroundDecoration: BoxDecoration( color: ColorTween( - begin: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .color, - end: Colors.black) - .lerp(_controller.value), + begin: StreamChatTheme.of(context) + .channelTheme + .channelHeaderTheme + .color, + end: Colors.black, + ).lerp(_controller.value), ), onTapUp: (a, b, c) { setState(() { @@ -131,7 +131,7 @@ class _FullScreenMediaState extends State }, ); } else if (attachment.type == 'video') { - final controller = videoPackages[attachment.id]; + final controller = videoPackages[attachment.id]!; if (!controller.initialized) { return Center( child: CircularProgressIndicator(), @@ -153,7 +153,7 @@ class _FullScreenMediaState extends State vertical: 50.0, ), child: Chewie( - controller: controller.chewieController, + controller: controller.chewieController!, ), ), ); @@ -171,9 +171,8 @@ class _FullScreenMediaState extends State children: [ ImageHeader( userName: widget.userName, - sentAt: widget.message.createdAt == null - ? '' - : 'Sent ${getDay(widget.message.createdAt)} at ${Jiffy(widget.sentAt.toLocal()).format('HH:mm')}', + sentAt: + 'Sent ${getDay(widget.message.createdAt.toLocal())} at ${Jiffy(widget.message.createdAt.toLocal()).format('HH:mm')}', onBackPressed: () { Navigator.of(context).pop(); }, @@ -181,8 +180,10 @@ class _FullScreenMediaState extends State urls: widget.mediaAttachments, currentIndex: _currentPage, onShowMessage: () { - widget.onShowMessage( - widget.message, StreamChannel.of(context).channel); + widget.onShowMessage?.call( + widget.message, + StreamChannel.of(context).channel, + ); }, ), if (widget.message.type != 'ephemeral') @@ -194,9 +195,11 @@ class _FullScreenMediaState extends State mediaSelectedCallBack: (val) { setState(() { _currentPage = val; - _pageController.animateToPage(val, - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut); + _pageController.animateToPage( + val, + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); Navigator.pop(context); }); }, @@ -210,7 +213,7 @@ class _FullScreenMediaState extends State } String getDay(DateTime dateTime) { - var now = DateTime.now(); + final now = DateTime.now(); if (DateTime(dateTime.year, dateTime.month, dateTime.day) == DateTime(now.year, now.month, now.day)) { @@ -238,11 +241,11 @@ class VideoPackage { final bool _showControls; final bool _autoInitialize; final VideoPlayerController _videoPlayerController; - ChewieController _chewieController; + ChewieController? _chewieController; VideoPlayerController get videoPlayer => _videoPlayerController; - ChewieController get chewieController => _chewieController; + ChewieController? get chewieController => _chewieController; bool get initialized => _videoPlayerController.value.isInitialized; @@ -250,12 +253,11 @@ class VideoPackage { Attachment attachment, { bool showControls = false, bool autoInitialize = true, - }) : assert(attachment != null), - _showControls = showControls, + }) : _showControls = showControls, _autoInitialize = autoInitialize, _videoPlayerController = attachment.localUri != null - ? VideoPlayerController.file(File.fromUri(attachment.localUri)) - : VideoPlayerController.network(attachment.assetUrl); + ? VideoPlayerController.file(File.fromUri(attachment.localUri!)) + : VideoPlayerController.network(attachment.assetUrl!); Future initialize() { return _videoPlayerController.initialize().then((_) { @@ -278,6 +280,6 @@ class VideoPackage { Future dispose() { _chewieController?.dispose(); - return _videoPlayerController?.dispose(); + return _videoPlayerController.dispose(); } } diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index 4cfd655a..d6ec45e0 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -5,8 +5,8 @@ import '../stream_chat_flutter.dart'; class GroupImage extends StatelessWidget { const GroupImage({ - Key key, - @required this.images, + Key? key, + required this.images, this.constraints, this.onTap, this.borderRadius, @@ -15,12 +15,12 @@ class GroupImage extends StatelessWidget { this.selectionThickness = 4, }) : super(key: key); - final List images; - final BoxConstraints constraints; - final VoidCallback onTap; + final List images; + final BoxConstraints? constraints; + final VoidCallback? onTap; final bool selected; - final BorderRadius borderRadius; - final Color selectionColor; + final BorderRadius? borderRadius; + final Color? selectionColor; final double selectionThickness; @override @@ -35,13 +35,13 @@ class GroupImage extends StatelessWidget { StreamChatTheme.of(context) .ownMessageTheme .avatarTheme - .borderRadius, + ?.borderRadius, child: Container( constraints: constraints ?? StreamChatTheme.of(context) .ownMessageTheme .avatarTheme - .constraints, + ?.constraints, decoration: BoxDecoration( color: StreamChatTheme.of(context).colorTheme.accentBlue, ), @@ -64,7 +64,7 @@ class GroupImage extends StatelessWidget { child: Transform.scale( scale: 1.2, child: CachedNetworkImage( - imageUrl: url, + imageUrl: url!, fit: BoxFit.cover, ), ), @@ -89,7 +89,7 @@ class GroupImage extends StatelessWidget { child: Transform.scale( scale: 1.2, child: CachedNetworkImage( - imageUrl: url, + imageUrl: url!, fit: BoxFit.cover, ), ), @@ -107,7 +107,8 @@ class GroupImage extends StatelessWidget { if (selected) { avatar = ClipRRect( borderRadius: (borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) + + streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius ?? + BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( color: selectionColor ?? diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/image_footer.dart index 6833a52f..a25d1be3 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/image_footer.dart @@ -5,8 +5,8 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:share_plus/share_plus.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:share_plus/share_plus.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -15,13 +15,13 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class ImageFooter extends StatefulWidget implements PreferredSizeWidget { /// Callback to call when pressing the back button. /// By default it calls [Navigator.pop] - final VoidCallback onBackPressed; + final VoidCallback? onBackPressed; /// Callback to call when the header is tapped. - final VoidCallback onTitleTap; + final VoidCallback? onTitleTap; /// Callback to call when the image is tapped. - final VoidCallback onImageTap; + final VoidCallback? onImageTap; final int currentPage; final int totalPages; @@ -29,18 +29,18 @@ class ImageFooter extends StatefulWidget implements PreferredSizeWidget { final List mediaAttachments; final Message message; - final ValueChanged mediaSelectedCallBack; + final ValueChanged? mediaSelectedCallBack; /// Creates a channel header ImageFooter({ - Key key, + Key? key, + required this.message, this.onBackPressed, this.onTitleTap, this.onImageTap, this.currentPage = 0, this.totalPages = 0, - this.mediaAttachments, - this.message, + this.mediaAttachments = const [], this.mediaSelectedCallBack, }) : preferredSize = Size.fromHeight(kToolbarHeight), super(key: key); @@ -53,14 +53,11 @@ class ImageFooter extends StatefulWidget implements PreferredSizeWidget { } class _ImageFooterState extends State { - TextEditingController _searchController; final TextEditingController _messageController = TextEditingController(); final FocusNode _messageFocusNode = FocusNode(); final List _selectedChannels = []; - Function modalSetStateCallback; - @override void initState() { super.initState(); @@ -69,13 +66,6 @@ class _ImageFooterState extends State { }); } - @override - void dispose() { - _searchController?.clear(); - _searchController?.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { final showShareButton = !kIsWeb; @@ -106,10 +96,10 @@ class _ImageFooterState extends State { widget.mediaAttachments[widget.currentPage]; final url = attachment.imageUrl ?? attachment.assetUrl ?? - attachment.thumbUrl; + attachment.thumbUrl!; final type = attachment.type == 'image' ? 'jpg' - : url?.split('?')?.first?.split('.')?.last ?? 'jpg'; + : url.split('?').first.split('.').last; final request = await HttpClient().getUrl(Uri.parse(url)); final response = await request.close(); @@ -227,7 +217,7 @@ class _ImageFooterState extends State { final attachment = widget.mediaAttachments[index]; if (attachment.type == 'video') { media = InkWell( - onTap: () => widget.mediaSelectedCallBack(index), + onTap: () => widget.mediaSelectedCallBack!(index), child: FittedBox( fit: BoxFit.cover, child: VideoThumbnailImage( @@ -238,13 +228,13 @@ class _ImageFooterState extends State { ); } else { media = InkWell( - onTap: () => widget.mediaSelectedCallBack(index), + onTap: () => widget.mediaSelectedCallBack!(index), child: AspectRatio( aspectRatio: 1.0, child: CachedNetworkImage( imageUrl: attachment.imageUrl ?? attachment.assetUrl ?? - attachment.thumbUrl, + attachment.thumbUrl!, fit: BoxFit.cover, ), ), @@ -254,32 +244,33 @@ class _ImageFooterState extends State { return Stack( children: [ media, - Padding( - padding: EdgeInsets.all(8.0), - child: Container( - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withOpacity(0.6), - boxShadow: [ - BoxShadow( - blurRadius: 8.0, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.3), - ), - ], - ), - padding: const EdgeInsets.all(2), - child: UserAvatar( - user: widget.message.user, - constraints: - BoxConstraints.tight(Size(24, 24)), - showOnlineStatus: false, + if (widget.message.user != null) + Padding( + padding: EdgeInsets.all(8.0), + child: Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.6), + boxShadow: [ + BoxShadow( + blurRadius: 8.0, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.3), + ), + ], + ), + padding: const EdgeInsets.all(2), + child: UserAvatar( + user: widget.message.user!, + constraints: + BoxConstraints.tight(Size(24, 24)), + showOnlineStatus: false, + ), ), ), - ), ], ); }, @@ -302,7 +293,7 @@ class _ImageFooterState extends State { _messageController.clear(); - for (var channel in _selectedChannels) { + for (final channel in _selectedChannels) { final message = Message( text: text, attachments: [attachments[widget.currentPage]], diff --git a/packages/stream_chat_flutter/lib/src/image_group.dart b/packages/stream_chat_flutter/lib/src/image_group.dart index 39c2883a..85e28497 100644 --- a/packages/stream_chat_flutter/lib/src/image_group.dart +++ b/packages/stream_chat_flutter/lib/src/image_group.dart @@ -1,23 +1,23 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/full_screen_media.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class ImageGroup extends StatelessWidget { const ImageGroup({ - Key key, - @required this.images, - @required this.message, - @required this.messageTheme, - @required this.size, + Key? key, + required this.images, + required this.message, + required this.messageTheme, + required this.size, this.onShowMessage, }) : super(key: key); final List images; final Message message; - final MessageTheme messageTheme; + final MessageTheme? messageTheme; final Size size; - final ShowMessageCallback onShowMessage; + final ShowMessageCallback? onShowMessage; @override Widget build(BuildContext context) { @@ -106,9 +106,9 @@ class ImageGroup extends StatelessWidget { } void _onTap( - BuildContext context, [ + BuildContext context, int index, - ]) { + ) { final channel = StreamChannel.of(context).channel; Navigator.push( @@ -119,8 +119,7 @@ class ImageGroup extends StatelessWidget { child: FullScreenMedia( mediaAttachments: images, startIndex: index, - userName: message.user.name, - sentAt: message.createdAt, + userName: message.user?.name, message: message, onShowMessage: onShowMessage, ), diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/image_header.dart index 023c8eaa..1cec9c36 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/image_header.dart @@ -11,16 +11,16 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { /// Callback to call when pressing the back button. /// By default it calls [Navigator.pop] - final VoidCallback onBackPressed; + final VoidCallback? onBackPressed; /// Callback to call when pressing the show message button. - final VoidCallback onShowMessage; + final VoidCallback? onShowMessage; /// Callback to call when the header is tapped. - final VoidCallback onTitleTap; + final VoidCallback? onTitleTap; /// Callback to call when the image is tapped. - final VoidCallback onImageTap; + final VoidCallback? onImageTap; final Message message; @@ -32,9 +32,9 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { /// Creates a channel header ImageHeader({ - Key key, - this.message, - this.urls, + Key? key, + required this.message, + this.urls = const [], this.currentIndex, this.showBackButton = true, this.onBackPressed, @@ -109,7 +109,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { void _showMessageActionModalBottomSheet(BuildContext context) async { final channel = StreamChannel.of(context).channel; - var result = await showDialog( + final result = await showDialog( context: context, barrierColor: StreamChatTheme.of(context).colorTheme.overlay, builder: (context) { diff --git a/packages/stream_chat_flutter/lib/src/info_tile.dart b/packages/stream_chat_flutter/lib/src/info_tile.dart index 72a077cc..deb30877 100644 --- a/packages/stream_chat_flutter/lib/src/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/info_tile.dart @@ -6,15 +6,15 @@ class InfoTile extends StatelessWidget { final String message; final Widget child; final bool showMessage; - final Alignment tileAnchor; - final Alignment childAnchor; - final TextStyle textStyle; - final Color backgroundColor; + final Alignment? tileAnchor; + final Alignment? childAnchor; + final TextStyle? textStyle; + final Color? backgroundColor; InfoTile({ - this.message, - this.child, - this.showMessage, + required this.message, + required this.child, + required this.showMessage, this.tileAnchor, this.childAnchor, this.textStyle, 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 ddaf2c24..3ba266bc 100644 --- a/packages/stream_chat_flutter/lib/src/media_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/media_list_view.dart @@ -21,10 +21,10 @@ extension on Duration { class MediaListView extends StatefulWidget { final List selectedIds; - final void Function(AssetEntity media) onSelect; + final void Function(AssetEntity media)? onSelect; const MediaListView({ - Key key, + Key? key, this.selectedIds = const [], this.onSelect, }) : super(key: key); @@ -58,7 +58,7 @@ class _MediaListViewState extends State { child: InkWell( onTap: () { if (widget.onSelect != null) { - widget.onSelect(media); + widget.onSelect!(media); } }, child: Stack( @@ -147,7 +147,7 @@ class _MediaListViewState extends State { final assetList = await PhotoManager.getAssetPathList( hasAll: true, ).then((value) { - if (value?.isNotEmpty == true) { + if (value.isNotEmpty == true) { return value.singleWhere((element) => element.isAll); } }); @@ -169,29 +169,29 @@ class _MediaListViewState extends State { class MediaThumbnailProvider extends ImageProvider { const MediaThumbnailProvider({ - @required this.media, - }) : assert(media != null); + required this.media, + }); final AssetEntity media; @override ImageStreamCompleter load(key, decode) { return MultiFrameImageStreamCompleter( - codec: _loadAsync(key, decode), + codec: _loadAsync(key, decode) as Future, scale: 1.0, informationCollector: () sync* { - yield ErrorDescription('Id: ${media?.id}'); + yield ErrorDescription('Id: ${media.id}'); }, ); } - Future _loadAsync( + Future _loadAsync( MediaThumbnailProvider key, DecoderCallback decode) async { assert(key == this); final bytes = await media.thumbData; if (bytes?.isNotEmpty != true) return null; - return await decode(bytes); + return await decode(bytes!); } @override @@ -203,12 +203,12 @@ class MediaThumbnailProvider extends ImageProvider { bool operator ==(dynamic other) { if (other.runtimeType != runtimeType) return false; final MediaThumbnailProvider typedOther = other; - return media?.id == typedOther.media?.id; + return media.id == typedOther.media.id; } @override - int get hashCode => media?.id?.hashCode ?? 0; + int get hashCode => media.id.hashCode; @override - String toString() => '$runtimeType("${media?.id}")'; + String toString() => '$runtimeType("${media.id}")'; } diff --git a/packages/stream_chat_flutter/lib/src/mention_tile.dart b/packages/stream_chat_flutter/lib/src/mention_tile.dart index 7d8b0ada..37e4e9ad 100644 --- a/packages/stream_chat_flutter/lib/src/mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/mention_tile.dart @@ -9,16 +9,16 @@ class MentionTile extends StatelessWidget { final Member member; /// Widget to display as title - final Widget title; + final Widget? title; /// Widget to display below [title] - final Widget subtitle; + final Widget? subtitle; /// Widget at the start of the tile - final Widget leading; + final Widget? leading; /// Widget at the end of tile - final Widget trailing; + final Widget? trailing; MentionTile( this.member, { @@ -46,7 +46,7 @@ class MentionTile extends StatelessWidget { 40, ), ), - user: member.user, + user: member.user!, ), SizedBox( width: 8.0, @@ -60,7 +60,7 @@ class MentionTile extends StatelessWidget { children: [ title ?? Text( - '${member.user.name}', + '${member.user!.name}', maxLines: 1, overflow: TextOverflow.ellipsis, style: StreamChatTheme.of(context).textTheme.bodyBold, @@ -87,7 +87,10 @@ class MentionTile extends StatelessWidget { ), trailing ?? Padding( - padding: const EdgeInsets.only(right: 18.0, left: 8.0), + padding: const EdgeInsets.only( + right: 18.0, + left: 8.0, + ), child: StreamSvgIcon.mentions( color: StreamChatTheme.of(context).colorTheme.accentBlue, ), diff --git a/packages/stream_chat_flutter/lib/src/message_action.dart b/packages/stream_chat_flutter/lib/src/message_action.dart index 5cedc962..6d4bd93c 100644 --- a/packages/stream_chat_flutter/lib/src/message_action.dart +++ b/packages/stream_chat_flutter/lib/src/message_action.dart @@ -4,13 +4,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Class describing a message action class MessageAction { /// leading widget - final Widget leading; + final Widget? leading; /// title widget - final Widget title; + final Widget? title; /// callback called on tap - final OnMessageTap onTap; + final OnMessageTap? onTap; /// returns a new instance of a [MessageAction] MessageAction({ 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 0fdabf7d..5d97bdf6 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -16,13 +16,13 @@ import 'stream_chat.dart'; import 'stream_chat_theme.dart'; class MessageActionsModal extends StatefulWidget { - final Widget Function(BuildContext, Message) editMessageInputBuilder; - final OnMessageTap onThreadReplyTap; - final OnMessageTap onReplyTap; + final Widget Function(BuildContext, Message)? editMessageInputBuilder; + final OnMessageTap? onThreadReplyTap; + final OnMessageTap? onReplyTap; final Message message; - final MessageTheme messageTheme; + final MessageTheme? messageTheme; final bool showReactions; - final OnMessageTap onCopyTap; + final OnMessageTap? onCopyTap; final bool showDeleteMessage; final bool showCopyMessage; final bool showEditMessage; @@ -31,18 +31,18 @@ class MessageActionsModal extends StatefulWidget { final bool showThreadReplyMessage; final bool showFlagButton; final bool reverse; - final ShapeBorder messageShape; - final ShapeBorder attachmentShape; + final ShapeBorder? messageShape; + final ShapeBorder? attachmentShape; final DisplayWidget showUserAvatar; - final BorderRadius attachmentBorderRadiusGeometry; + final BorderRadius? attachmentBorderRadiusGeometry; /// List of custom actions final List customActions; const MessageActionsModal({ - Key key, - @required this.message, - @required this.messageTheme, + Key? key, + required this.message, + required this.messageTheme, this.showReactions = true, this.showDeleteMessage = true, this.showEditMessage = true, @@ -80,24 +80,26 @@ class _MessageActionsModalState extends State { final user = StreamChat.of(context).user; final roughMaxSize = 2 * size.width / 3; - var messageTextLength = widget.message.text.length; + var messageTextLength = widget.message.text!.length; if (widget.message.quotedMessage != null) { - var quotedMessageLength = widget.message.quotedMessage.text.length + 40; - if (widget.message.quotedMessage.attachments?.isNotEmpty == true) { + var quotedMessageLength = + (widget.message.quotedMessage!.text?.length ?? 0) + 40; + if (widget.message.quotedMessage!.attachments.isNotEmpty) { quotedMessageLength += 40; } if (quotedMessageLength > messageTextLength) { messageTextLength = quotedMessageLength; } } - final roughSentenceSize = - messageTextLength * widget.messageTheme.messageText.fontSize * 1.2; - final divFactor = widget.message.attachments?.isNotEmpty == true + final roughSentenceSize = messageTextLength * + (widget.messageTheme?.messageText?.fontSize ?? 1) * + 1.2; + final divFactor = widget.message.attachments.isNotEmpty == true ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); final hasFileAttachment = - widget.message.attachments?.any((it) => it.type == 'file') == true; + widget.message.attachments.any((it) => it.type == 'file') == true; return GestureDetector( behavior: HitTestBehavior.translucent, @@ -134,11 +136,10 @@ class _MessageActionsModalState extends State { children: [ if (widget.showReactions && (widget.message.status == - MessageSendingStatus.sent || - widget.message.status == null)) + MessageSendingStatus.sent)) Align( alignment: Alignment( - user.id == widget.message.user.id + user?.id == widget.message.user?.id ? (divFactor > 1.0 ? 0.0 : (1.0 - divFactor)) @@ -159,8 +160,8 @@ class _MessageActionsModalState extends State { attachmentBorderRadiusGeometry: widget.attachmentBorderRadiusGeometry, message: widget.message.copyWith( - text: widget.message.text.length > 200 - ? '${widget.message.text.substring(0, 200)}...' + text: widget.message.text!.length > 200 + ? '${widget.message.text!.substring(0, 200)}...' : widget.message.text, ), messageTheme: widget.messageTheme, @@ -177,15 +178,14 @@ class _MessageActionsModalState extends State { padding: const EdgeInsets.all(0), textPadding: EdgeInsets.symmetric( vertical: 8.0, - horizontal: widget.message.text.isOnlyEmoji + horizontal: widget.message.text!.isOnlyEmoji ? 0 : 16.0, ), showReactionPickerIndicator: widget.showReactions && (widget.message.status == - MessageSendingStatus.sent || - widget.message.status == null), + MessageSendingStatus.sent), showInChannelIndicator: false, showSendingIndicator: false, shape: widget.messageShape, @@ -212,14 +212,12 @@ class _MessageActionsModalState extends State { CrossAxisAlignment.stretch, children: [ if (widget.showReplyMessage && - widget.message.status == - MessageSendingStatus.sent || - widget.message.status == null) + widget.message.status == + MessageSendingStatus.sent) _buildReplyButton(context), if (widget.showThreadReplyMessage && (widget.message.status == - MessageSendingStatus.sent || - widget.message.status == null) && + MessageSendingStatus.sent) && widget.message.parentId == null) _buildThreadReplyButton(context), if (widget.showResendMessage) @@ -315,7 +313,7 @@ class _MessageActionsModalState extends State { okText: 'OK', ); } catch (err) { - if (json.decode(err?.body ?? {})['code'] == 4) { + if (err is ApiError && json.decode(err.body ?? '{}')['code'] == 4) { await showInfoDialog( context, icon: StreamSvgIcon.flag( @@ -337,7 +335,7 @@ class _MessageActionsModalState extends State { setState(() { _showActions = false; }); - var answer = await showConfirmationDialog( + final answer = await showConfirmationDialog( context, title: 'Delete message', icon: StreamSvgIcon.flag( @@ -349,7 +347,7 @@ class _MessageActionsModalState extends State { cancelText: 'CANCEL', ); - if (answer) { + if (answer == true) { try { Navigator.pop(context); await StreamChannel.of(context).channel.deleteMessage(widget.message); @@ -381,7 +379,7 @@ class _MessageActionsModalState extends State { onTap: () { Navigator.pop(context); if (widget.onReplyTap != null) { - widget.onReplyTap(widget.message); + widget.onReplyTap!(widget.message); } }, child: Padding( @@ -578,7 +576,7 @@ class _MessageActionsModalState extends State { ), ), widget.editMessageInputBuilder != null - ? widget.editMessageInputBuilder(context, widget.message) + ? widget.editMessageInputBuilder!(context, widget.message) : MessageInput( editMessage: widget.message, preMessageSending: (m) { @@ -599,7 +597,7 @@ class _MessageActionsModalState extends State { onTap: () { Navigator.pop(context); if (widget.onThreadReplyTap != null) { - widget.onThreadReplyTap(widget.message); + widget.onThreadReplyTap!(widget.message); } }, child: Padding( diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 70307936..12ee9e93 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; @@ -19,6 +20,7 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:substring_highlight/substring_highlight.dart'; +import 'package:video_compress/video_compress.dart'; import '../stream_chat_flutter.dart'; import 'attachment/attachment.dart'; @@ -109,7 +111,7 @@ const _kMaxAttachmentSize = 20971520; // 20MB in Bytes class MessageInput extends StatefulWidget { /// Instantiate a new MessageInput MessageInput({ - Key key, + Key? key, this.onMessageSent, this.preMessageSending, this.parentMessage, @@ -135,20 +137,20 @@ class MessageInput extends StatefulWidget { }) : super(key: key); /// Message to edit - final Message editMessage; + final Message? editMessage; /// Message to start with - final Message initialMessage; + final Message? initialMessage; /// Function called after sending the message - final void Function(Message) onMessageSent; + final void Function(Message)? onMessageSent; /// Function called right before sending the message /// Use this to transform the message - final FutureOr Function(Message) preMessageSending; + final FutureOr Function(Message)? preMessageSending; /// Parent message in case of a thread - final Message parentMessage; + final Message? parentMessage; /// Maximum Height for the TextField to grow before it starts scrolling final double maxHeight; @@ -166,25 +168,25 @@ class MessageInput extends StatefulWidget { final bool hideSendAsDm; /// The text controller of the TextField - final TextEditingController textEditingController; + final TextEditingController? textEditingController; /// List of action widgets - final List actions; + final List? actions; /// The location of the custom actions final ActionsLocation actionsLocation; /// Map that defines a thumbnail builder for an attachment type - final Map attachmentThumbnailBuilders; + final Map? attachmentThumbnailBuilders; /// The focus node associated to the TextField - final FocusNode focusNode; + final FocusNode? focusNode; /// - final Message quotedMessage; + final Message? quotedMessage; /// - final VoidCallback onQuotedMessageCleared; + final VoidCallback? onQuotedMessageCleared; /// The location of the send button final SendButtonLocation sendButtonLocation; @@ -193,20 +195,20 @@ class MessageInput extends StatefulWidget { final bool autofocus; /// Send button widget in an idle state - final Widget idleSendButton; + final Widget? idleSendButton; /// Send button widget in an active state - final Widget activeSendButton; + final Widget? activeSendButton; /// Customize the tile for the mentions overlay - final MentionTileBuilder mentionsTileBuilder; + final MentionTileBuilder? mentionsTileBuilder; @override MessageInputState createState() => MessageInputState(); /// Use this method to get the current [StreamChatState] instance static MessageInputState of(BuildContext context) { - MessageInputState messageInputState; + MessageInputState? messageInputState; messageInputState = context.findAncestorStateOfType(); @@ -224,15 +226,15 @@ class MessageInputState extends State { final List _mentionedUsers = []; final _imagePicker = ImagePicker(); - FocusNode _focusNode; + late final FocusNode _focusNode; bool _inputEnabled = true; bool _messageIsPresent = false; bool _animateContainer = true; bool _commandEnabled = false; - OverlayEntry _commandsOverlay, _mentionsOverlay, _emojiOverlay; - Iterable _emojiNames; + OverlayEntry? _commandsOverlay, _mentionsOverlay, _emojiOverlay; + late Iterable _emojiNames; - Command _chosenCommand; + Command? _chosenCommand; bool _actionsShrunk = false; bool _sendAsDm = false; bool _openFilePickerSection = false; @@ -242,7 +244,7 @@ class MessageInputState extends State { KeyboardVisibilityController(); /// The editing controller passed to the input TextField - TextEditingController textEditingController; + late final TextEditingController textEditingController; bool get _hasQuotedMessage => widget.quotedMessage != null; @@ -250,7 +252,8 @@ class MessageInputState extends State { void initState() { super.initState(); _focusNode = widget.focusNode ?? FocusNode(); - _emojiNames = Emoji.all().map((e) => e.name); + _emojiNames = + Emoji.all().where((it) => it.name != null).map((e) => e.name!); if (!kIsWeb) { _keyboardListener = @@ -264,7 +267,7 @@ class MessageInputState extends State { textEditingController = widget.textEditingController ?? TextEditingController(); if (widget.editMessage != null || widget.initialMessage != null) { - _parseExistingMessage(widget.editMessage ?? widget.initialMessage); + _parseExistingMessage(widget.editMessage ?? widget.initialMessage!); } textEditingController.addListener(() { @@ -447,7 +450,7 @@ class MessageInputState extends State { firstChild: sendButton, secondChild: widget.idleSendButton ?? _buildIdleSendButton(context), duration: - StreamChatTheme.of(context).messageInputTheme.sendAnimationDuration, + StreamChatTheme.of(context).messageInputTheme.sendAnimationDuration!, alignment: Alignment.center, ); } @@ -490,9 +493,9 @@ class MessageInputState extends State { widget.editMessage == null && StreamChannel.of(context) .channel - ?.config + .config ?.commands - ?.isNotEmpty == + .isNotEmpty == true) _buildCommandButton(), ...widget.actions ?? [], @@ -571,7 +574,7 @@ class MessageInputState extends State { return InputDecoration( isDense: true, hintText: _getHint(), - hintStyle: theme.messageInputTheme.inputTextStyle.copyWith( + hintStyle: theme.messageInputTheme.inputTextStyle!.copyWith( color: theme.colorTheme.grey, ), border: OutlineInputBorder( @@ -621,7 +624,7 @@ class MessageInputState extends State { size: 16.0, ), Text( - _chosenCommand?.name?.toUpperCase() ?? '', + _chosenCommand?.name.toUpperCase() ?? '', style: StreamChatTheme.of(context) .textTheme .footnoteBold @@ -676,9 +679,9 @@ class MessageInputState extends State { ).merge(passedDecoration); } - Timer _debounce; + Timer? _debounce; - String _previousValue; + String? _previousValue; void _onChanged(BuildContext context, String s) { if (s == _previousValue) { @@ -686,14 +689,14 @@ class MessageInputState extends State { } _previousValue = s; - if (_debounce?.isActive == true) _debounce.cancel(); + if (_debounce?.isActive == true) _debounce!.cancel(); _debounce = Timer( const Duration(milliseconds: 350), () { if (!mounted) { return; } - StreamChannel.of(context).channel.keyStroke()?.catchError((e) {}); + StreamChannel.of(context).channel.keyStroke().catchError((e) {}); setState(() { _messageIsPresent = s.trim().isNotEmpty; @@ -721,7 +724,7 @@ class MessageInputState extends State { } String _getHint() { - if (_commandEnabled && _chosenCommand.name == 'giphy') { + if (_commandEnabled && _chosenCommand!.name == 'giphy') { return 'Search GIFs'; } if (_attachments.isNotEmpty) { @@ -742,7 +745,7 @@ class MessageInputState extends State { final textToSelection = textEditingController.text .substring(0, textEditingController.value.selection.start); final splits = textToSelection.split(':'); - final query = splits[splits.length - 2]?.toLowerCase(); + final query = splits[splits.length - 2].toLowerCase(); final emoji = Emoji.byName(query); if (textToSelection.endsWith(':') && emoji != null) { @@ -751,7 +754,7 @@ class MessageInputState extends State { _emojiOverlay = _buildEmojiOverlay(); if (_emojiOverlay != null) { - Overlay.of(context).insert(_emojiOverlay); + Overlay.of(context)!.insert(_emojiOverlay!); } } } @@ -767,7 +770,7 @@ class MessageInputState extends State { .contains('@')) { _mentionsOverlay = _buildMentionsOverlayEntry(); if (_mentionsOverlay != null) { - Overlay.of(context).insert(_mentionsOverlay); + Overlay.of(context)!.insert(_mentionsOverlay!); } } } @@ -778,8 +781,8 @@ class MessageInputState extends State { .channel .config ?.commands - ?.where((element) => element.name == s.substring(1)) - ?.toList() ?? + .where((element) => element.name == s.substring(1)) + .toList() ?? []; if (matchedCommandsList.length == 1) { @@ -789,32 +792,32 @@ class MessageInputState extends State { setState(() { _commandEnabled = true; }); - _commandsOverlay.remove(); + _commandsOverlay!.remove(); _commandsOverlay = null; } else { _commandsOverlay = _buildCommandsOverlayEntry(); if (_commandsOverlay != null) { - Overlay.of(context).insert(_commandsOverlay); + Overlay.of(context)!.insert(_commandsOverlay!); } } } } - OverlayEntry _buildCommandsOverlayEntry() { + OverlayEntry? _buildCommandsOverlayEntry() { final text = textEditingController.text.trimLeft(); final commands = StreamChannel.of(context) .channel .config ?.commands - ?.where((c) => c.name.contains(text.replaceFirst('/', ''))) - ?.toList() ?? + .where((c) => c.name.contains(text.replaceFirst('/', ''))) + .toList() ?? []; if (commands.isEmpty) { return null; } - RenderBox renderBox = context.findRenderObject(); + final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; return OverlayEntry(builder: (context) { @@ -951,7 +954,6 @@ class MessageInputState extends State { .colorTheme .black .withOpacity(0.2)); - break; case 1: return _attachmentContainsFile ? StreamChatTheme.of(context).colorTheme.accentBlue @@ -964,17 +966,14 @@ class MessageInputState extends State { .colorTheme .black .withOpacity(0.2)); - break; case 2: return _attachmentContainsFile && _attachments.isNotEmpty ? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); - break; case 3: return _attachmentContainsFile && _attachments.isNotEmpty ? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); - break; default: return Colors.black; } @@ -1106,10 +1105,10 @@ class MessageInputState extends State { } void _addAttachment(AssetEntity medium) async { - final mediaFile = await medium.originFile.timeout( + final mediaFile = await (medium.originFile.timeout( Duration(seconds: 5), onTimeout: () => medium.originFile, - ); + ) as FutureOr); var file = AttachmentFile( path: mediaFile.path, @@ -1117,11 +1116,12 @@ class MessageInputState extends State { bytes: mediaFile.readAsBytesSync(), ); - if (file.size > _kMaxAttachmentSize) { - if (medium?.type == AssetType.video) { - final mediaInfo = await VideoService.compressVideo(file.path); + if (file.size! > _kMaxAttachmentSize) { + if (medium.type == AssetType.video) { + final mediaInfo = await (VideoService.compressVideo(file.path) + as FutureOr); - if (mediaInfo.filesize > _kMaxAttachmentSize) { + if (mediaInfo.filesize! > _kMaxAttachmentSize) { _showErrorAlert( 'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.', ); @@ -1129,8 +1129,8 @@ class MessageInputState extends State { } file = AttachmentFile( name: file.name, - size: mediaInfo.filesize, - bytes: await mediaInfo.file.readAsBytes(), + size: mediaInfo.filesize!, + bytes: await mediaInfo.file?.readAsBytes(), path: mediaInfo.path, ); } else { @@ -1159,7 +1159,6 @@ class MessageInputState extends State { size: 24.0, ), ); - break; case 'ban': return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1169,7 +1168,6 @@ class MessageInputState extends State { color: Colors.white, ), ); - break; case 'flag': return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1179,7 +1177,6 @@ class MessageInputState extends State { color: Colors.white, ), ); - break; case 'imgur': return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1190,7 +1187,6 @@ class MessageInputState extends State { ), ), ); - break; case 'mute': return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1200,7 +1196,6 @@ class MessageInputState extends State { color: Colors.white, ), ); - break; case 'unban': return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1210,7 +1205,6 @@ class MessageInputState extends State { color: Colors.white, ), ); - break; case 'unmute': return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1220,7 +1214,6 @@ class MessageInputState extends State { color: Colors.white, ), ); - break; default: return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1230,17 +1223,16 @@ class MessageInputState extends State { color: Colors.white, ), ); - break; } } - OverlayEntry _buildMentionsOverlayEntry() { + OverlayEntry? _buildMentionsOverlayEntry() { final splits = textEditingController.text .substring(0, textEditingController.value.selection.start) .split('@'); final query = splits.last.toLowerCase(); - Future> queryMembers; + Future>? queryMembers; if (query.isNotEmpty) { queryMembers = StreamChannel.of(context) @@ -1249,16 +1241,16 @@ class MessageInputState extends State { .then((res) => res.members); } - final members = StreamChannel.of(context).channel.state.members?.where((m) { - return m.user.name.toLowerCase().contains(query); - })?.toList() ?? + final members = StreamChannel.of(context).channel.state?.members.where((m) { + return m.user?.name.toLowerCase().contains(query) == true; + }).toList() ?? []; if (members.isEmpty) { return null; } - RenderBox renderBox = context.findRenderObject(); + final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; return OverlayEntry( @@ -1298,7 +1290,9 @@ class MessageInputState extends State { SizedBox( height: 8.0, ), - ...snapshot.data.map( + ...snapshot.data! + .where((it) => it.user != null) + .map( (m) { return Material( color: StreamChatTheme.of(context) @@ -1306,9 +1300,11 @@ class MessageInputState extends State { .white, child: InkWell( onTap: () { - _mentionedUsers.add(m.user); + if (m.user != null) { + _mentionedUsers.add(m.user!); + } - splits[splits.length - 1] = m.user.name; + splits[splits.length - 1] = m.user!.name; final rejoin = splits.join('@'); textEditingController.value = @@ -1321,12 +1317,13 @@ class MessageInputState extends State { offset: rejoin.length, ), ); - _debounce.cancel(); + _debounce!.cancel(); _mentionsOverlay?.remove(); _mentionsOverlay = null; }, child: widget.mentionsTileBuilder != null - ? widget.mentionsTileBuilder(context, m) + ? widget.mentionsTileBuilder!( + context, m) : MentionTile(m), ), ); @@ -1349,7 +1346,7 @@ class MessageInputState extends State { ); } - OverlayEntry _buildEmojiOverlay() { + OverlayEntry? _buildEmojiOverlay() { final splits = textEditingController.text .substring(0, textEditingController.value.selection.start) .split(':'); @@ -1368,7 +1365,7 @@ class MessageInputState extends State { return null; } - RenderBox renderBox = context.findRenderObject(); + final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; return OverlayEntry(builder: (context) { @@ -1431,19 +1428,20 @@ class MessageInputState extends State { ); } - final emoji = emojis.elementAt(i - 1); + final emoji = emojis.elementAt(i - 1)!; return ListTile( title: SubstringHighlight( - text: "${emoji.char} ${emoji.name.replaceAll('_', ' ')}", + text: "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", term: query, textStyleHighlight: - Theme.of(context).textTheme.headline6.copyWith( + Theme.of(context).textTheme.headline6!.copyWith( fontSize: 14.5, fontWeight: FontWeight.bold, ), - textStyle: Theme.of(context).textTheme.headline6.copyWith( - fontSize: 14.5, - ), + textStyle: + Theme.of(context).textTheme.headline6!.copyWith( + fontSize: 14.5, + ), ), onTap: () { _chooseEmoji(splits, emoji); @@ -1457,7 +1455,7 @@ class MessageInputState extends State { } void _chooseEmoji(List splits, Emoji emoji) { - final rejoin = splits.sublist(0, splits.length - 1).join(':') + emoji.char; + final rejoin = splits.sublist(0, splits.length - 1).join(':') + emoji.char!; textEditingController.value = TextEditingValue( text: rejoin + @@ -1485,8 +1483,8 @@ class MessageInputState extends State { Widget _buildReplyToMessage() { if (!_hasQuotedMessage) return Offstage(); - final containsUrl = widget.quotedMessage.attachments - ?.any((element) => element.ogScrapeUrl != null) == + final containsUrl = widget.quotedMessage!.attachments + .any((element) => element.ogScrapeUrl != null) == true; return Transform( transform: Matrix4.rotationY(pi), @@ -1494,7 +1492,7 @@ class MessageInputState extends State { child: QuotedMessageWidget( reverse: true, showBorder: !containsUrl, - message: widget.quotedMessage, + message: widget.quotedMessage!, messageTheme: StreamChatTheme.of(context).otherMessageTheme, padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), ), @@ -1525,7 +1523,7 @@ class MessageInputState extends State { borderRadius: BorderRadius.circular(10), clipBehavior: Clip.antiAlias, child: FileAttachment( - message: null, + message: Message(), // dummy message attachment: e, size: Size( MediaQuery.of(context).size.width * 0.65, @@ -1609,11 +1607,9 @@ class MessageInputState extends State { } Widget _buildAttachment(Attachment attachment) { - if (attachment == null) return Offstage(); - if (widget.attachmentThumbnailBuilders?.containsKey(attachment.type) == true) { - return widget.attachmentThumbnailBuilders[attachment.type]( + return widget.attachmentThumbnailBuilders![attachment.type!]!( context, attachment, ); @@ -1624,7 +1620,7 @@ class MessageInputState extends State { case 'giphy': return attachment.file != null ? Image.memory( - attachment.file.bytes, + attachment.file!.bytes!, fit: BoxFit.cover, errorBuilder: (context, _, __) { return Image.asset( @@ -1636,10 +1632,11 @@ class MessageInputState extends State { : CachedNetworkImage( imageUrl: attachment.imageUrl ?? attachment.assetUrl ?? - attachment.thumbUrl, + attachment.thumbUrl!, fit: BoxFit.cover, errorWidget: (_, obj, trace) { - return getFileTypeImage(attachment.extraData['other']); + return getFileTypeImage( + attachment.extraData['other'] as String?); }, progressIndicatorBuilder: (context, _, progress) { return Shimmer.fromColors( @@ -1717,7 +1714,7 @@ class MessageInputState extends State { setState(() { _commandsOverlay = _buildCommandsOverlayEntry(); if (_commandsOverlay != null) { - Overlay.of(context).insert(_commandsOverlay); + Overlay.of(context)!.insert(_commandsOverlay!); } }); } else { @@ -1852,7 +1849,7 @@ class MessageInputState extends State { void addAttachment(Attachment attachment) { setState(() { _attachments[attachment.id] = attachment.copyWith( - uploadState: attachment.uploadState ?? UploadState.success(), + uploadState: attachment.uploadState, ); }); } @@ -1862,8 +1859,8 @@ class MessageInputState extends State { void pickFile(DefaultAttachmentTypes fileType, [bool camera = false]) async { setState(() => _inputEnabled = false); - AttachmentFile file; - String attachmentType; + AttachmentFile? file; + String? attachmentType; if (fileType == DefaultAttachmentTypes.image) { attachmentType = 'image'; @@ -1874,7 +1871,7 @@ class MessageInputState extends State { } if (camera) { - PickedFile pickedFile; + PickedFile? pickedFile; if (fileType == DefaultAttachmentTypes.image) { pickedFile = await _imagePicker.getImage(source: ImageSource.camera); } else if (fileType == DefaultAttachmentTypes.video) { @@ -1890,7 +1887,7 @@ class MessageInputState extends State { bytes: bytes, ); } else { - FileType type; + late FileType type; if (fileType == DefaultAttachmentTypes.image) { type = FileType.image; } else if (fileType == DefaultAttachmentTypes.video) { @@ -1902,8 +1899,8 @@ class MessageInputState extends State { type: type, withData: true, ); - if (res?.files?.isNotEmpty == true) { - file = res.files.single.toAttachmentFile; + if (res?.files.isNotEmpty == true) { + file = res!.files.single.toAttachmentFile; } } @@ -1911,29 +1908,28 @@ class MessageInputState extends State { if (file == null) return; - final mimeType = file.name?.mimeType ?? file.path.split('/').last.mimeType; + final mimeType = file.name?.mimeType ?? file.path!.split('/').last.mimeType; - final extraDataMap = {}; + final extraDataMap = {}; if (mimeType?.subtype != null) { - extraDataMap['mime_type'] = mimeType.subtype.toLowerCase(); + extraDataMap['mime_type'] = mimeType!.subtype.toLowerCase(); } - if (file.size != null) { - extraDataMap['file_size'] = file.size; - } + extraDataMap['file_size'] = file.size!; final attachment = Attachment( file: file, type: attachmentType, - extraData: extraDataMap.isNotEmpty ? extraDataMap : null, + extraData: extraDataMap, ); - if (file.size > _kMaxAttachmentSize) { + if (file.size! > _kMaxAttachmentSize) { if (attachmentType == 'Video') { - final mediaInfo = await VideoService.compressVideo(file.path); + final mediaInfo = await (VideoService.compressVideo(file.path) + as FutureOr); - if (mediaInfo.filesize > _kMaxAttachmentSize) { + if (mediaInfo.filesize! > _kMaxAttachmentSize) { _showErrorAlert( 'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.', ); @@ -1941,8 +1937,8 @@ class MessageInputState extends State { } file = AttachmentFile( name: file.name, - size: mediaInfo.filesize, - bytes: await mediaInfo.file.readAsBytes(), + size: mediaInfo.filesize!, + bytes: await mediaInfo.file!.readAsBytes(), path: mediaInfo.path, ); } else { @@ -1959,7 +1955,8 @@ class MessageInputState extends State { _attachments.update(attachment.id, (it) { return it.copyWith( file: file, - extraData: {...it.extraData}..update('file_size', (_) => file.size), + extraData: {...it.extraData} + ..update('file_size', ((_) => file!.size!)), ); }); }); @@ -2023,7 +2020,7 @@ class MessageInputState extends State { final shouldUnfocus = _commandEnabled; if (_commandEnabled) { - text = '/${_chosenCommand.name} ' + text; + text = '/${_chosenCommand!.name} ' + text; } final attachments = [..._attachments.values]; @@ -2031,7 +2028,7 @@ class MessageInputState extends State { textEditingController.clear(); _attachments.clear(); if (widget.onQuotedMessageCleared != null) { - widget.onQuotedMessageCleared(); + widget.onQuotedMessageCleared!(); } setState(() { @@ -2047,7 +2044,7 @@ class MessageInputState extends State { Future sendingFuture; Message message; if (widget.editMessage != null) { - message = widget.editMessage.copyWith( + message = widget.editMessage!.copyWith( text: text, attachments: attachments, mentionedUsers: @@ -2066,25 +2063,25 @@ class MessageInputState extends State { if (widget.quotedMessage != null) { message = message.copyWith( - quotedMessageId: widget.quotedMessage.id, + quotedMessageId: widget.quotedMessage!.id, ); } if (widget.preMessageSending != null) { - message = await widget.preMessageSending(message); + message = await widget.preMessageSending!(message); } final streamChannel = StreamChannel.of(context); final channel = streamChannel.channel; - if (!channel.state.isUpToDate) { + if (!channel.state!.isUpToDate) { await streamChannel.reloadChannel(); } _mentionedUsers.clear(); if (widget.editMessage == null || - widget.editMessage.status == MessageSendingStatus.failed || - widget.editMessage.status == MessageSendingStatus.sending) { + widget.editMessage!.status == MessageSendingStatus.failed || + widget.editMessage!.status == MessageSendingStatus.sending) { sendingFuture = channel.sendMessage(message); } else { sendingFuture = channel.updateMessage(message); @@ -2099,12 +2096,12 @@ class MessageInputState extends State { _parseExistingMessage(message); } if (widget.onMessageSent != null) { - widget.onMessageSent(resp.message); + widget.onMessageSent!(resp.message); } }); } - StreamSubscription _keyboardListener; + StreamSubscription? _keyboardListener; void _showErrorAlert(String description) { showModalBottomSheet( @@ -2178,14 +2175,12 @@ class MessageInputState extends State { } void _parseExistingMessage(Message message) { - textEditingController.text = message.text; + textEditingController.text = message.text!; _messageIsPresent = true; - if (message.attachments != null) { - for (final attachment in message.attachments) { - _attachments[attachment.id] = attachment.copyWith( - uploadState: attachment.uploadState ?? UploadState.success(), - ); - } + for (final attachment in message.attachments) { + _attachments[attachment.id] = attachment.copyWith( + uploadState: attachment.uploadState, + ); } } @@ -2266,12 +2261,12 @@ class _PickerWidget extends StatefulWidget { final void Function(AssetEntity) onMediaSelected; const _PickerWidget({ - Key key, - @required this.filePickerIndex, - @required this.containsFile, - @required this.selectedMedias, - @required this.onAddMoreFilesClick, - @required this.onMediaSelected, + Key? key, + required this.filePickerIndex, + required this.containsFile, + required this.selectedMedias, + required this.onAddMoreFilesClick, + required this.onMediaSelected, }) : super(key: key); @override @@ -2279,7 +2274,7 @@ class _PickerWidget extends StatefulWidget { } class __PickerWidgetState extends State<_PickerWidget> { - Future requestPermission; + Future? requestPermission; @override void initState() { @@ -2299,7 +2294,7 @@ class __PickerWidgetState extends State<_PickerWidget> { return const Center(child: CircularProgressIndicator()); } - if (snapshot.data) { + if (snapshot.data!) { if (widget.containsFile) { return GestureDetector( onTap: () { 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 9af5bc4a..f444224d 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -26,14 +26,14 @@ typedef MessageBuilder = Widget Function( ); typedef ParentMessageBuilder = Widget Function( BuildContext, - Message, + Message?, ); typedef SystemMessageBuilder = Widget Function( BuildContext, Message, ); -typedef ThreadBuilder = Widget Function(BuildContext context, Message parent); -typedef ThreadTapCallback = void Function(Message, Widget); +typedef ThreadBuilder = Widget Function(BuildContext context, Message? parent); +typedef ThreadTapCallback = void Function(Message, Widget?); typedef OnMessageSwiped = void Function(Message); typedef OnMessageTap = void Function(Message); @@ -41,13 +41,13 @@ typedef ReplyTapCallback = void Function(Message); class MessageDetails { /// True if the message belongs to the current user - bool isMyMessage; + bool? isMyMessage; /// True if the user message is the same of the previous message - bool isLastUser; + bool? isLastUser; /// True if the user message is the same of the next message - bool isNextUser; + bool? isNextUser; /// The message Message message; @@ -61,11 +61,11 @@ class MessageDetails { List messages, this.index, ) { - isMyMessage = message.user.id == StreamChat.of(context).user.id; + isMyMessage = message.user?.id == StreamChat.of(context).user?.id; isLastUser = index + 1 < messages.length && - message.user.id == messages[index + 1]?.user?.id; + message.user?.id == messages[index + 1].user?.id; isNextUser = - index - 1 >= 0 && message.user.id == messages[index - 1]?.user?.id; + index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id; } } @@ -112,7 +112,7 @@ class MessageDetails { class MessageListView extends StatefulWidget { /// Instantiate a new MessageListView MessageListView({ - Key key, + Key? key, this.showScrollToBottom = true, this.messageBuilder, this.parentMessageBuilder, @@ -145,52 +145,52 @@ class MessageListView extends StatefulWidget { }) : super(key: key); /// Function used to build a custom message widget - final MessageBuilder messageBuilder; + final MessageBuilder? messageBuilder; /// Function used to build a custom system message widget - final SystemMessageBuilder systemMessageBuilder; + final SystemMessageBuilder? systemMessageBuilder; /// Function used to build a custom parent message widget - final ParentMessageBuilder parentMessageBuilder; + final ParentMessageBuilder? parentMessageBuilder; /// Function used to build a custom thread widget - final ThreadBuilder threadBuilder; + final ThreadBuilder? threadBuilder; /// Function called when tapping on a thread /// By default it calls [Navigator.push] using the widget built using [threadBuilder] - final ThreadTapCallback onThreadTap; + final ThreadTapCallback? onThreadTap; /// If true will show a scroll to bottom message when there are new messages and the scroll offset is not zero final bool showScrollToBottom; /// Parent message in case of a thread - final Message parentMessage; + final Message? parentMessage; /// Builder used to render date dividers - final Widget Function(DateTime) dateDividerBuilder; + final Widget Function(DateTime)? dateDividerBuilder; /// Index of an item to initially align within the viewport. - final int initialScrollIndex; + final int? initialScrollIndex; /// Determines where the leading edge of the item at [initialScrollIndex] /// should be placed. - final double initialAlignment; + final double? initialAlignment; /// Controller for jumping or scrolling to an item. - final ItemScrollController scrollController; + final ItemScrollController? scrollController; /// Provides a listenable iterable of [itemPositions] of items that are on /// screen and their locations. - final ItemPositionsListener itemPositionListener; + final ItemPositionsListener? itemPositionListener; /// The ScrollPhysics used by the ListView final ScrollPhysics scrollPhysics; /// Called when message item gets swiped - final OnMessageSwiped onMessageSwiped; + final OnMessageSwiped? onMessageSwiped; /// - final ReplyTapCallback onReplyTap; + final ReplyTapCallback? onReplyTap; /// If true the list will highlight the initialMessage if there is any. /// @@ -198,64 +198,64 @@ class MessageListView extends StatefulWidget { final bool highlightInitialMessage; /// Color used while highlighting initial message - final Color messageHighlightColor; + final Color? messageHighlightColor; - final ShowMessageCallback onShowMessage; + final ShowMessageCallback? onShowMessage; final bool showConnectionStateTile; /// Function called when messages are fetched - final Widget Function(BuildContext, List) messageListBuilder; + final Widget Function(BuildContext, List)? messageListBuilder; /// Function used to build a loading widget - final WidgetBuilder loadingBuilder; + final WidgetBuilder? loadingBuilder; /// Function used to build an empty widget - final WidgetBuilder emptyBuilder; + final WidgetBuilder? emptyBuilder; /// Callback triggered when an error occurs while performing the given request. /// This parameter can be used to display an error message to users in the event /// of a connection failure. - final ErrorBuilder errorWidgetBuilder; + final ErrorBuilder? errorWidgetBuilder; /// Predicate used to filter messages - final bool Function(Message) messageFilter; + final bool Function(Message)? messageFilter; /// Attachment builders for the default message widget /// Please change this in the [MessageWidget] if you are using a custom implementation - final Map customAttachmentBuilders; + final Map? customAttachmentBuilders; /// Called when any message is tapped except a system message (use [onSystemMessageTap] instead) - final OnMessageTap onMessageTap; + final OnMessageTap? onMessageTap; /// Called when system message is tapped - final OnMessageTap onSystemMessageTap; + final OnMessageTap? onSystemMessageTap; /// Customize onTap on attachment - final void Function(Message message, Attachment attachment) onAttachmentTap; + final void Function(Message message, Attachment attachment)? onAttachmentTap; /// Customize the MessageWidget textBuilder - final void Function(BuildContext context, Message message) textBuilder; + final void Function(BuildContext context, Message message)? textBuilder; @override _MessageListViewState createState() => _MessageListViewState(); } class _MessageListViewState extends State { - ItemScrollController _scrollController; - Function _onThreadTap; + ItemScrollController? _scrollController; + Function? _onThreadTap; bool _showScrollToBottom = false; - ItemPositionsListener _itemPositionListener; - int _messageListLength; - StreamChannelState streamChannel; + late final ItemPositionsListener _itemPositionListener; + int? _messageListLength; + StreamChannelState? streamChannel; - int get _initialIndex { + int? get _initialIndex { if (widget.initialScrollIndex != null) return widget.initialScrollIndex; - if (streamChannel.initialMessageId != null) { - final messages = streamChannel.channel.state.messages; + if (streamChannel!.initialMessageId != null) { + final messages = streamChannel!.channel.state!.messages; final totalMessages = messages.length; final messageIndex = messages.indexWhere((e) { - return e.id == streamChannel.initialMessageId; + return e.id == streamChannel!.initialMessageId; }); final index = totalMessages - messageIndex; if (index != 0) return index - 1; @@ -264,24 +264,24 @@ class _MessageListViewState extends State { return 0; } - double get _initialAlignment { + double? get _initialAlignment { if (widget.initialAlignment != null) return widget.initialAlignment; return 0; } bool _isInitialMessage(String id) { - return streamChannel.initialMessageId == id; + return streamChannel!.initialMessageId == id; } - bool get _upToDate => streamChannel.channel.state.isUpToDate; + bool get _upToDate => streamChannel!.channel.state!.isUpToDate; bool get _isThreadConversation => widget.parentMessage != null; bool _topPaginationActive = false; bool _bottomPaginationActive = false; - int initialIndex; - double initialAlignment; + int? initialIndex; + double? initialAlignment; List messages = []; @@ -343,9 +343,9 @@ class _MessageListViewState extends State { if (_messageListLength != null) { if (_bottomPaginationActive || (_inBetweenList && _upToDate)) { - if (_itemPositionListener.itemPositions.value?.isNotEmpty == true) { + if (_itemPositionListener.itemPositions.value.isNotEmpty == true) { final first = _itemPositionListener.itemPositions.value.first; - final diff = newMessagesListLength - _messageListLength; + final diff = newMessagesListLength - _messageListLength!; if (diff > 0) { initialIndex = first.index + diff; initialAlignment = first.itemLeadingEdge; @@ -413,7 +413,7 @@ class _MessageListViewState extends State { _inBetweenList = true; }, child: ScrollablePositionedList.separated( - key: ValueKey(initialIndex + initialAlignment), + key: ValueKey(initialIndex! + initialAlignment!), itemPositionsListener: _itemPositionListener, addAutomaticKeepAlives: true, initialScrollIndex: initialIndex ?? 0, @@ -427,7 +427,7 @@ class _MessageListViewState extends State { if (i == messages.length) return Offstage(); if (i == 0) return SizedBox(height: 30); if (i == messages.length + 1) { - final replyCount = widget.parentMessage.replyCount; + final replyCount = widget.parentMessage!.replyCount; return Container( decoration: BoxDecoration( gradient: @@ -454,7 +454,7 @@ class _MessageListViewState extends State { Units.DAY, )) { final divider = widget.dateDividerBuilder != null - ? widget.dateDividerBuilder( + ? widget.dateDividerBuilder!( nextMessage.createdAt.toLocal(), ) : DateDivider( @@ -472,8 +472,8 @@ class _MessageListViewState extends State { ); final isNextUserSame = - message.user.id == nextMessage.user?.id; - final isThread = message.replyCount > 0; + message.user!.id == nextMessage.user?.id; + final isThread = message.replyCount! > 0; final isDeleted = message.isDeleted; if (timeDiff >= 1 || !isNextUserSame || @@ -486,12 +486,12 @@ class _MessageListViewState extends State { itemBuilder: (context, i) { if (i == messages.length + 2) { if (widget.parentMessageBuilder != null) { - return widget.parentMessageBuilder( + return widget.parentMessageBuilder!( context, widget.parentMessage, ); } else { - return buildParentMessage(widget.parentMessage); + return buildParentMessage(widget.parentMessage!); } } if (i == messages.length + 1) { @@ -528,7 +528,7 @@ class _MessageListViewState extends State { if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('MESSAGE-${message.id}'), - builder: (context) => widget.messageBuilder( + builder: (context) => widget.messageBuilder!( context, MessageDetails( context, @@ -555,7 +555,7 @@ class _MessageListViewState extends State { child: ValueListenableBuilder>( valueListenable: _itemPositionListener.itemPositions, builder: (context, values, _) { - final items = _itemPositionListener.itemPositions?.value; + final items = _itemPositionListener.itemPositions.value; if (items.isEmpty || messages.isEmpty) { return SizedBox(); } @@ -571,7 +571,7 @@ class _MessageListViewState extends State { } return widget.dateDividerBuilder != null - ? widget.dateDividerBuilder( + ? widget.dateDividerBuilder!( messages[index].createdAt.toLocal(), ) : DateDivider( @@ -585,8 +585,8 @@ class _MessageListViewState extends State { } Future _paginateData( - StreamChannelState channel, QueryDirection direction) { - return _messageListController.paginateData(direction: direction); + StreamChannelState? channel, QueryDirection direction) { + return _messageListController.paginateData!(direction: direction); } ItemPosition _getTopElement(Iterable values) { @@ -599,8 +599,8 @@ class _MessageListViewState extends State { Widget _buildScrollToBottom() { return StreamBuilder>( stream: Rx.combineLatest2( - streamChannel.channel.state.isUpToDateStream, - streamChannel.channel.state.unreadCountStream, + streamChannel!.channel.state!.isUpToDateStream, + streamChannel!.channel.state!.unreadCountStream, (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), ), builder: (_, snapshot) { @@ -609,15 +609,15 @@ class _MessageListViewState extends State { } else if (!snapshot.hasData) { return Offstage(); } - final isUpToDate = snapshot.data.item1; + final isUpToDate = snapshot.data!.item1; final showScrollToBottom = !isUpToDate || _showScrollToBottom; if (!showScrollToBottom) { return Offstage(); } - final unreadCount = snapshot.data.item2; + final unreadCount = snapshot.data!.item2; final showUnreadCount = unreadCount > 0 && - streamChannel.channel.state.members.any( - (e) => e.userId == streamChannel.channel.client.state.user.id); + streamChannel!.channel.state!.members.any((e) => + e.userId == streamChannel!.channel.client.state.user!.id); return Positioned( bottom: 8, right: 8, @@ -630,15 +630,15 @@ class _MessageListViewState extends State { backgroundColor: StreamChatTheme.of(context).colorTheme.white, onPressed: () { if (unreadCount > 0) { - streamChannel.channel.markRead(); + streamChannel!.channel.markRead(); } if (!_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; - streamChannel.reloadChannel(); + streamChannel!.reloadChannel(); } else { setState(() => _showScrollToBottom = false); - _scrollController.scrollTo( + _scrollController!.scrollTo( index: 0, duration: Duration(seconds: 1), curve: Curves.easeInOut, @@ -676,12 +676,12 @@ class _MessageListViewState extends State { } Widget _buildLoadingIndicator( - StreamChannelState streamChannel, + StreamChannelState? streamChannel, QueryDirection direction, ) { final stream = direction == QueryDirection.top - ? streamChannel.queryTopMessages - : streamChannel.queryBottomMessages; + ? streamChannel!.queryTopMessages + : streamChannel!.queryBottomMessages; return StreamBuilder( key: Key('LOADING-INDICATOR'), stream: stream, @@ -698,7 +698,7 @@ class _MessageListViewState extends State { ), ); } - if (!snapshot.data) { + if (!snapshot.data!) { if (!_isThreadConversation && direction == QueryDirection.top) { return Container( height: 52, @@ -721,13 +721,13 @@ class _MessageListViewState extends State { BuildContext context, Message message, List messages, - StreamChannelState streamChannel, + StreamChannelState? streamChannel, ) { Widget messageWidget; if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('TOP-MESSAGE'), - builder: (_) => widget.messageBuilder( + builder: (_) => widget.messageBuilder!( context, MessageDetails( context, @@ -748,13 +748,13 @@ class _MessageListViewState extends State { BuildContext context, Message message, List messages, - StreamChannelState streamChannel, + StreamChannelState? streamChannel, ) { Widget messageWidget; if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('BOTTOM-MESSAGE-${message.id}'), - builder: (_) => widget.messageBuilder( + builder: (_) => widget.messageBuilder!( context, MessageDetails( context, @@ -774,10 +774,10 @@ class _MessageListViewState extends State { onVisibilityChanged: (visibility) { final isVisible = visibility.visibleBounds != Rect.zero; if (isVisible) { - final channel = streamChannel.channel; + final channel = streamChannel!.channel; if (_upToDate && channel.config?.readEvents == true && - channel.state.unreadCount > 0) { + channel.state!.unreadCount! > 0) { streamChannel.channel.markRead(); } } @@ -792,8 +792,8 @@ class _MessageListViewState extends State { Widget buildParentMessage( Message message, ) { - final isMyMessage = message.user.id == StreamChat.of(context).user.id; - final isOnlyEmoji = message.text.isOnlyEmoji; + final isMyMessage = message.user!.id == StreamChat.of(context).user!.id; + final isOnlyEmoji = message.text!.isOnlyEmoji; return MessageWidget( showThreadReplyIndicator: false, @@ -809,7 +809,7 @@ class _MessageListViewState extends State { showUsername: !isMyMessage, padding: const EdgeInsets.all(8.0), showSendingIndicator: false, - onThreadTap: _onThreadTap, + onThreadTap: _onThreadTap as void Function(Message)?, borderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(16), bottomLeft: Radius.circular(2), @@ -832,18 +832,19 @@ class _MessageListViewState extends State { break; case ReturnActionType.reply: FocusScope.of(context).unfocus(); - widget.onMessageSwiped(message); + widget.onMessageSwiped!(message); break; } }, customAttachmentBuilders: widget.customAttachmentBuilders, onMessageTap: (message) { if (widget.onMessageTap != null) { - widget.onMessageTap(message); + widget.onMessageTap!(message); } FocusScope.of(context).unfocus(); }, - textBuilder: widget.textBuilder, + textBuilder: + widget.textBuilder as Widget Function(BuildContext, Message)?, ); } @@ -860,18 +861,18 @@ class _MessageListViewState extends State { message: message, onMessageTap: (message) { if (widget.onSystemMessageTap != null) { - widget.onSystemMessageTap(message); + widget.onSystemMessageTap!(message); } FocusScope.of(context).unfocus(); }, ); } - final userId = StreamChat.of(context).user.id; - final isMyMessage = message.user.id == userId; + final userId = StreamChat.of(context).user!.id; + final isMyMessage = message.user!.id == userId; final nextMessage = index - 2 >= 0 ? messages[index - 2] : null; final isNextUserSame = - nextMessage != null && message.user.id == nextMessage.user.id; + nextMessage != null && message.user!.id == nextMessage.user!.id; num timeDiff = 0; if (nextMessage != null) { @@ -881,27 +882,26 @@ class _MessageListViewState extends State { ); } - final channel = streamChannel.channel; + final channel = streamChannel!.channel; final readList = channel.state?.read?.where((read) { if (read.user.id == userId) return false; return (read.lastRead.isAfter(message.createdAt) || read.lastRead.isAtSameMomentAs(message.createdAt)); - })?.toList() ?? + }).toList() ?? []; 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') == true; final isThreadMessage = - message?.parentId != null && message?.showInChannel == true; + message.parentId != null && message.showInChannel == true; - final hasReplies = message.replyCount > 0; + final hasReplies = message.replyCount! > 0; final attachmentBorderRadius = hasFileAttachment ? 12.0 : 14.0; - final showTimeStamp = message.createdAt != null && - (!isThreadMessage || _isThreadConversation) && + final showTimeStamp = (!isThreadMessage || _isThreadConversation) && !hasReplies && (timeDiff >= 1 || !isNextUserSame); @@ -921,10 +921,10 @@ class _MessageListViewState extends State { final showInChannelIndicator = !_isThreadConversation && isThreadMessage; final showThreadReplyIndicator = !_isThreadConversation && hasReplies; - final isOnlyEmoji = message.text.isOnlyEmoji; + final isOnlyEmoji = message.text!.isOnlyEmoji; final hasUrlAttachment = - message.attachments?.any((it) => it.ogScrapeUrl != null) == true; + message.attachments.any((it) => it.ogScrapeUrl != null) == true; final borderSide = isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment) @@ -954,8 +954,8 @@ class _MessageListViewState extends State { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); } else { - await streamChannel.loadChannelAtMessage(quotedMessageId).then((_) { - WidgetsBinding.instance.addPostFrameCallback((_) { + await streamChannel!.loadChannelAtMessage(quotedMessageId).then((_) { + WidgetsBinding.instance!.addPostFrameCallback((_) { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); } @@ -968,7 +968,7 @@ class _MessageListViewState extends State { showThreadReplyMessage: !isThreadMessage, showFlagButton: !isMyMessage, borderSide: borderSide, - onThreadTap: _onThreadTap, + onThreadTap: _onThreadTap as void Function(Message)?, onReplyTap: widget.onReplyTap, attachmentBorderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(attachmentBorderRadius), @@ -1008,19 +1008,20 @@ class _MessageListViewState extends State { break; case ReturnActionType.reply: FocusScope.of(context).unfocus(); - widget.onMessageSwiped(message); + widget.onMessageSwiped!(message); break; } }, customAttachmentBuilders: widget.customAttachmentBuilders, onMessageTap: (message) { if (widget.onMessageTap != null) { - widget.onMessageTap(message); + widget.onMessageTap!(message); } FocusScope.of(context).unfocus(); }, onAttachmentTap: widget.onAttachmentTap, - textBuilder: widget.textBuilder, + textBuilder: + widget.textBuilder as Widget Function(BuildContext, Message)?, ); if (!message.isDeleted && @@ -1033,7 +1034,7 @@ class _MessageListViewState extends State { child: Swipeable( onSwipeEnd: () { FocusScope.of(context).unfocus(); - widget.onMessageSwiped(message); + widget.onMessageSwiped!(message); }, backgroundIcon: StreamSvgIcon.reply( color: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1049,7 +1050,7 @@ class _MessageListViewState extends State { final colorTheme = StreamChatTheme.of(context).colorTheme; final highlightColor = widget.messageHighlightColor ?? colorTheme.highlight; - child = TweenAnimationBuilder( + child = TweenAnimationBuilder( tween: ColorTween( begin: highlightColor, end: colorTheme.white.withOpacity(0), @@ -1071,7 +1072,7 @@ class _MessageListViewState extends State { return child; } - StreamSubscription _messageNewListener; + StreamSubscription? _messageNewListener; @override void initState() { @@ -1085,13 +1086,14 @@ class _MessageListViewState extends State { initialAlignment = _initialAlignment; _messageNewListener = - streamChannel.channel.on(EventType.messageNew).listen((event) { + streamChannel!.channel.on(EventType.messageNew).listen((event) { if (_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; } - if (event.message.user.id == streamChannel.channel.client.state.user.id) { - WidgetsBinding.instance.addPostFrameCallback((_) { + if (event.message!.user!.id == + streamChannel!.channel.client.state.user!.id) { + WidgetsBinding.instance!.addPostFrameCallback((_) { _scrollController?.jumpTo( index: 0, ); @@ -1100,7 +1102,7 @@ class _MessageListViewState extends State { }); if (_isThreadConversation) { - streamChannel.getReplies(widget.parentMessage.id); + streamChannel!.getReplies(widget.parentMessage!.id); } _getOnThreadTap(); @@ -1110,10 +1112,10 @@ class _MessageListViewState extends State { void _getOnThreadTap() { if (widget.onThreadTap != null) { _onThreadTap = (Message message) { - widget.onThreadTap( + widget.onThreadTap!( message, widget.threadBuilder != null - ? widget.threadBuilder(context, message) + ? widget.threadBuilder!(context, message) : null); }; } else if (widget.threadBuilder != null) { @@ -1122,14 +1124,14 @@ class _MessageListViewState extends State { context, MaterialPageRoute(builder: (_) { return StreamBuilder( - stream: streamChannel.channel.state.messagesStream.map( + stream: streamChannel!.channel.state!.messagesStream.map( (messages) => - messages.firstWhere((m) => m.id == message.id)), + messages!.firstWhere((m) => m.id == message.id)), initialData: message, builder: (_, snapshot) { return StreamChannel( - channel: streamChannel.channel, - child: widget.threadBuilder(context, snapshot.data), + channel: streamChannel!.channel, + child: widget.threadBuilder!(context, snapshot.data), ); }); }), @@ -1141,7 +1143,7 @@ class _MessageListViewState extends State { @override void dispose() { if (!_upToDate) { - streamChannel.reloadChannel(); + streamChannel!.reloadChannel(); } _messageNewListener?.cancel(); super.dispose(); 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 e2171078..ed9534ac 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -14,19 +14,19 @@ import 'stream_chat_theme.dart'; class MessageReactionsModal extends StatelessWidget { final Message message; - final MessageTheme messageTheme; + final MessageTheme? messageTheme; final bool reverse; final bool showReactions; final DisplayWidget showUserAvatar; - final ShapeBorder messageShape; - final ShapeBorder attachmentShape; - final void Function(User) onUserAvatarTap; - final BorderRadius attachmentBorderRadiusGeometry; + final ShapeBorder? messageShape; + final ShapeBorder? attachmentShape; + final void Function(User)? onUserAvatarTap; + final BorderRadius? attachmentBorderRadiusGeometry; const MessageReactionsModal({ - Key key, - @required this.message, - @required this.messageTheme, + Key? key, + required this.message, + required this.messageTheme, this.showReactions = true, this.messageShape, this.attachmentShape, @@ -42,10 +42,10 @@ class MessageReactionsModal extends StatelessWidget { final user = StreamChat.of(context).user; final roughMaxSize = 2 * size.width / 3; - var messageTextLength = message.text.length; + var messageTextLength = message.text!.length; if (message.quotedMessage != null) { - var quotedMessageLength = message.quotedMessage.text.length + 40; - if (message.quotedMessage.attachments?.isNotEmpty == true) { + var quotedMessageLength = message.quotedMessage!.text!.length + 40; + if (message.quotedMessage!.attachments.isNotEmpty == true) { quotedMessageLength += 40; } if (quotedMessageLength > messageTextLength) { @@ -53,8 +53,8 @@ class MessageReactionsModal extends StatelessWidget { } } final roughSentenceSize = - messageTextLength * messageTheme.messageText.fontSize * 1.2; - final divFactor = message.attachments?.isNotEmpty == true + messageTextLength * (messageTheme?.messageText?.fontSize ?? 1) * 1.2; + final divFactor = message.attachments.isNotEmpty == true ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); @@ -64,7 +64,7 @@ class MessageReactionsModal extends StatelessWidget { curve: Curves.easeInOutBack, builder: (context, val, snapshot) { final hasFileAttachment = - message.attachments?.any((it) => it.type == 'file') == true; + message.attachments.any((it) => it.type == 'file') == true; return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => Navigator.maybePop(context), @@ -92,11 +92,10 @@ class MessageReactionsModal extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (showReactions && - (message.status == MessageSendingStatus.sent || - message.status == null)) + (message.status == MessageSendingStatus.sent)) Align( alignment: Alignment( - user.id == message.user.id + user!.id == message.user!.id ? (divFactor > 1.0 ? 0.0 : (1.0 - divFactor)) @@ -115,8 +114,8 @@ class MessageReactionsModal extends StatelessWidget { key: Key('MessageWidget'), reverse: reverse, message: message.copyWith( - text: message.text.length > 200 - ? '${message.text.substring(0, 200)}...' + text: message.text!.length > 200 + ? '${message.text!.substring(0, 200)}...' : message.text, ), messageTheme: messageTheme, @@ -138,12 +137,11 @@ class MessageReactionsModal extends StatelessWidget { showInChannelIndicator: false, textPadding: EdgeInsets.symmetric( vertical: 8.0, - horizontal: message.text.isOnlyEmoji ? 0 : 16.0, + horizontal: + message.text!.isOnlyEmoji ? 0 : 16.0, ), showReactionPickerIndicator: showReactions && - (message.status == - MessageSendingStatus.sent || - message.status == null), + (message.status == MessageSendingStatus.sent), ), ), if (message.latestReactions?.isNotEmpty == true) ...[ @@ -188,10 +186,10 @@ class MessageReactionsModal extends StatelessWidget { spacing: 16, runSpacing: 16, alignment: WrapAlignment.start, - children: message.latestReactions + children: message.latestReactions! .map((e) => _buildReaction( e, - currentUser, + currentUser!, context, )) .toList(), @@ -209,7 +207,7 @@ class MessageReactionsModal extends StatelessWidget { User currentUser, BuildContext context, ) { - final isCurrentUser = reaction.user.id == currentUser.id; + final isCurrentUser = reaction.user?.id == currentUser.id; return ConstrainedBox( constraints: BoxConstraints.loose(Size( 64, @@ -225,7 +223,7 @@ class MessageReactionsModal extends StatelessWidget { children: [ UserAvatar( onTap: onUserAvatarTap, - user: reaction.user, + user: reaction.user!, constraints: BoxConstraints.tightFor( height: 64, width: 64, @@ -246,8 +244,10 @@ class MessageReactionsModal extends StatelessWidget { child: ReactionBubble( reactions: [reaction], flipTail: !reverse, - borderColor: messageTheme.reactionsBorderColor, - backgroundColor: messageTheme.reactionsBackgroundColor, + borderColor: messageTheme?.reactionsBorderColor ?? + Colors.transparent, + backgroundColor: messageTheme?.reactionsBackgroundColor ?? + Colors.transparent, maskColor: StreamChatTheme.of(context).colorTheme.white, tailCirclesSpacing: 1, highlightOwnReactions: false, @@ -258,7 +258,7 @@ class MessageReactionsModal extends StatelessWidget { ), const SizedBox(height: 8), Text( - reaction.user.name.split(' ')[0], + reaction.user!.name.split(' ')[0], style: StreamChatTheme.of(context).textTheme.footnoteBold, textAlign: TextAlign.center, ), 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 4cf7b5ed..87b55aaa 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_item.dart @@ -12,8 +12,8 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class MessageSearchItem extends StatelessWidget { /// Instantiate a new MessageSearchItem const MessageSearchItem({ - Key key, - @required this.getMessageResponse, + Key? key, + required this.getMessageResponse, this.onTap, this.showOnlineStatus = true, }) : super(key: key); @@ -22,7 +22,7 @@ class MessageSearchItem extends StatelessWidget { final GetMessageResponse getMessageResponse; /// Function called when tapping this widget - final VoidCallback onTap; + final VoidCallback? onTap; /// If true the [MessageSearchItem] will show the current online Status final bool showOnlineStatus; @@ -31,8 +31,8 @@ class MessageSearchItem extends StatelessWidget { Widget build(BuildContext context) { final message = getMessageResponse.message; final channel = getMessageResponse.channel; - final channelName = channel.extraData['name']; - final user = message.user; + final channelName = channel?.extraData['name']; + final user = message.user!; return ListTile( onTap: onTap, leading: UserAvatar( @@ -46,7 +46,7 @@ class MessageSearchItem extends StatelessWidget { title: Row( children: [ Text( - user.id == StreamChat.of(context).user.id ? 'You' : user.name, + user.id == StreamChat.of(context).user?.id ? 'You' : user.name, style: StreamChatTheme.of(context).channelPreviewTheme.title, ), if (channelName != null) ...[ @@ -55,12 +55,12 @@ class MessageSearchItem extends StatelessWidget { style: StreamChatTheme.of(context) .channelPreviewTheme .title - .copyWith( + ?.copyWith( fontWeight: FontWeight.normal, ), ), Text( - channelName, + channelName as String, style: StreamChatTheme.of(context).channelPreviewTheme.title, ), ], @@ -96,14 +96,10 @@ class MessageSearchItem extends StatelessWidget { } Widget _buildSubtitle(BuildContext context, Message message) { - if (message == null) { - return SizedBox(); - } - var text = message.text; if (message.isDeleted) { text = 'This message was deleted.'; - } else if (message.attachments != null) { + } else if (message.attachments.isNotEmpty) { final parts = [ ...message.attachments.map((e) { if (e.type == 'image') { @@ -116,7 +112,7 @@ class MessageSearchItem extends StatelessWidget { return e == message.attachments.last ? (e.title ?? 'File') : '${e.title ?? 'File'} , '; - }).where((e) => e != null), + }), message.text ?? '', ]; @@ -125,15 +121,15 @@ class MessageSearchItem extends StatelessWidget { return Text.rich( _getDisplayText( - text, + text!, message.mentionedUsers, message.attachments, - StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( + StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( fontStyle: (message.isSystem || message.isDeleted) ? FontStyle.italic : FontStyle.normal, ), - StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( + StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( fontStyle: (message.isSystem || message.isDeleted) ? FontStyle.italic : FontStyle.normal, @@ -149,26 +145,24 @@ class MessageSearchItem extends StatelessWidget { String text, List mentions, List attachments, - TextStyle normalTextStyle, - TextStyle mentionsTextStyle) { + TextStyle? normalTextStyle, + TextStyle? mentionsTextStyle) { var textList = text.split(' '); var resList = []; for (var e in textList) { - if (mentions != null && - mentions.isNotEmpty && + if (mentions.isNotEmpty && mentions.any((element) => '@${element.name}' == e)) { resList.add(TextSpan( text: '$e ', style: mentionsTextStyle, )); - } else if (attachments != null && - attachments.isNotEmpty && + } else if (attachments.isNotEmpty && attachments .where((e) => e.title != null) .any((element) => element.title == e)) { resList.add(TextSpan( text: '$e ', - style: normalTextStyle.copyWith(fontStyle: FontStyle.italic), + style: normalTextStyle?.copyWith(fontStyle: FontStyle.italic), )); } else { resList.add(TextSpan( 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 d7ece34e..4eff2f8f 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 @@ -10,11 +10,15 @@ typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); /// Builder used to create a custom [ListUserItem] from a [User] typedef MessageSearchItemBuilder = Widget Function( - BuildContext, GetMessageResponse); + BuildContext, + GetMessageResponse, +); /// Builder used when [MessageSearchListView] is empty typedef EmptyMessageSearchBuilder = Widget Function( - BuildContext context, String searchQuery); + BuildContext context, + String searchQuery, +); /// /// It shows the list of searched messages. @@ -47,9 +51,9 @@ typedef EmptyMessageSearchBuilder = Widget Function( class MessageSearchListView extends StatefulWidget { /// Instantiate a new MessageSearchListView const MessageSearchListView({ - Key key, + Key? key, + required this.filters, this.messageQuery, - this.filters, this.sortOptions, this.paginationParams, this.messageFilters, @@ -66,7 +70,7 @@ class MessageSearchListView extends StatefulWidget { }) : super(key: key); /// Message String to search on - final String messageQuery; + final String? messageQuery; /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. @@ -77,27 +81,27 @@ class MessageSearchListView extends StatefulWidget { /// Sorting is based on field and direction, multiple sorting options can be provided. /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. /// Direction can be ascending or descending. - final List sortOptions; + final List? sortOptions; /// Pagination parameters /// limit: the number of users to return (max is 30) /// offset: the offset (max is 1000) /// message_limit: how many messages should be included to each channel - final PaginationParams paginationParams; + final PaginationParams? paginationParams; /// The message query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map messageFilters; + final Map? messageFilters; /// Builder used to create a custom item preview - final MessageSearchItemBuilder itemBuilder; + final MessageSearchItemBuilder? itemBuilder; /// Function called when tapping on a [MessageSearchItem] - final MessageSearchItemTapCallback onItemTap; + final MessageSearchItemTapCallback? onItemTap; /// Builder used to create a custom item separator - final IndexedWidgetBuilder separatorBuilder; + final IndexedWidgetBuilder? separatorBuilder; /// Set it to false to hide total results text final bool showResultCount; @@ -108,16 +112,16 @@ class MessageSearchListView extends StatefulWidget { final bool showErrorTile; /// The builder that is used when the search messages are fetched - final Widget Function(List) childBuilder; + final Widget Function(List)? childBuilder; /// The builder used when the channel list is empty. - final WidgetBuilder emptyBuilder; + final WidgetBuilder? emptyBuilder; /// The builder that will be used in case of error - final ErrorBuilder errorBuilder; + final ErrorBuilder? errorBuilder; /// The builder that will be used in case of loading - final WidgetBuilder loadingBuilder; + final WidgetBuilder? loadingBuilder; @override _MessageSearchListViewState createState() => _MessageSearchListViewState(); @@ -202,11 +206,11 @@ class _MessageSearchListViewState extends State { Widget _listItemBuilder( BuildContext context, GetMessageResponse getMessageResponse) { if (widget.itemBuilder != null) { - return widget.itemBuilder(context, getMessageResponse); + return widget.itemBuilder!(context, getMessageResponse); } return MessageSearchItem( getMessageResponse: getMessageResponse, - onTap: () => widget.onItemTap(getMessageResponse), + onTap: () => widget.onItemTap!(getMessageResponse), ); } @@ -235,7 +239,7 @@ class _MessageSearchListViewState extends State { height: 100, padding: EdgeInsets.all(32), child: Center( - child: snapshot.data ? CircularProgressIndicator() : Container(), + child: snapshot.data! ? CircularProgressIndicator() : Container(), ), ); }); @@ -249,7 +253,7 @@ class _MessageSearchListViewState extends State { itemCount: items.isNotEmpty ? items.length + 1 : items.length, separatorBuilder: (_, index) { if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, index); + return widget.separatorBuilder!(context, index); } return _separatorBuilder(context, index); }, @@ -262,13 +266,13 @@ class _MessageSearchListViewState extends State { ); if (widget.pullToRefresh) { child = RefreshIndicator( - onRefresh: () => _messageSearchListController.loadData(), + onRefresh: () => _messageSearchListController.loadData!(), child: child, ); } child = LazyLoadScrollView( - onEndOfPage: () => _messageSearchListController.paginateData(), + onEndOfPage: () => _messageSearchListController.paginateData!(), child: child, ); diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index 3afa60f9..eb80b89b 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -7,43 +8,45 @@ import 'utils.dart'; class MessageText extends StatelessWidget { const MessageText({ - Key key, - @required this.message, - @required this.messageTheme, + Key? key, + required this.message, + required this.messageTheme, this.onMentionTap, this.onLinkTap, }) : super(key: key); final Message message; - final void Function(User) onMentionTap; - final void Function(String) onLinkTap; - final MessageTheme messageTheme; + final void Function(User)? onMentionTap; + final void Function(String)? onLinkTap; + final MessageTheme? messageTheme; @override Widget build(BuildContext context) { - final text = _replaceMentions(message.text).replaceAll('\n', '\\\n'); + final text = _replaceMentions(message.text)!.replaceAll('\n', '\\\n'); return MarkdownBody( data: text, onTapLink: ( String link, - String href, + String? href, String title, ) { if (link.startsWith('@')) { - final mentionedUser = message.mentionedUsers.firstWhere( + final mentionedUser = message.mentionedUsers.firstWhereOrNull( (u) => '@${u.name}' == link, - orElse: () => null, ); + if (mentionedUser == null) { + return; + } if (onMentionTap != null) { - onMentionTap(mentionedUser); + onMentionTap!(mentionedUser); } else { print('tap on ${mentionedUser.name}'); } } else { if (onLinkTap != null) { - onLinkTap(link); + onLinkTap!(link); } else { launchURL(context, link); } @@ -52,23 +55,23 @@ class MessageText extends StatelessWidget { styleSheet: MarkdownStyleSheet.fromTheme( Theme.of(context).copyWith( textTheme: Theme.of(context).textTheme.apply( - bodyColor: messageTheme.messageText.color, - decoration: messageTheme.messageText.decoration, - decorationColor: messageTheme.messageText.decorationColor, - decorationStyle: messageTheme.messageText.decorationStyle, - fontFamily: messageTheme.messageText.fontFamily, + bodyColor: messageTheme!.messageText!.color, + decoration: messageTheme!.messageText!.decoration, + decorationColor: messageTheme!.messageText!.decorationColor, + decorationStyle: messageTheme!.messageText!.decorationStyle, + fontFamily: messageTheme!.messageText!.fontFamily, ), ), ).copyWith( - a: messageTheme.messageLinks, - p: messageTheme.messageText, + a: messageTheme!.messageLinks, + p: messageTheme!.messageText, ), ); } - String _replaceMentions(String text) { - message.mentionedUsers?.map((u) => u.name)?.toSet()?.forEach((userName) { - text = text.replaceAll( + String? _replaceMentions(String? text) { + message.mentionedUsers.map((u) => u.name).toSet().forEach((userName) { + text = text!.replaceAll( '@$userName', '[@$userName](@${userName.replaceAll(' ', '')})'); }); return text; diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 09feeb74..d39240ab 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -26,7 +26,7 @@ typedef AttachmentBuilder = Widget Function( Message, List, ); -typedef OnQuotedMessageTap = void Function(String); +typedef OnQuotedMessageTap = void Function(String?); /// The display behaviour of a widget enum DisplayWidget { @@ -51,46 +51,46 @@ enum DisplayWidget { /// Modify it to change the widget appearance. class MessageWidget extends StatefulWidget { /// Function called on mention tap - final void Function(User) onMentionTap; + final void Function(User)? onMentionTap; /// The function called when tapping on replies - final void Function(Message) onThreadTap; - final void Function(Message) onReplyTap; - final Widget Function(BuildContext, Message) editMessageInputBuilder; - final Widget Function(BuildContext, Message) textBuilder; + final void Function(Message)? onThreadTap; + final void Function(Message)? onReplyTap; + final Widget Function(BuildContext, Message)? editMessageInputBuilder; + final Widget Function(BuildContext, Message)? textBuilder; /// Function called on long press - final void Function(BuildContext, Message) onMessageActions; + final void Function(BuildContext, Message)? onMessageActions; /// The message final Message message; /// The message theme - final MessageTheme messageTheme; + final MessageTheme? messageTheme; /// If true the widget will be mirrored final bool reverse; /// The shape of the message text - final ShapeBorder shape; + final ShapeBorder? shape; /// The shape of an attachment - final ShapeBorder attachmentShape; + final ShapeBorder? attachmentShape; /// The borderside of the message text - final BorderSide borderSide; + final BorderSide? borderSide; /// The borderside of an attachment - final BorderSide attachmentBorderSide; + final BorderSide? attachmentBorderSide; /// The border radius of the message text - final BorderRadiusGeometry borderRadiusGeometry; + final BorderRadiusGeometry? borderRadiusGeometry; /// The border radius of an attachment - final BorderRadiusGeometry attachmentBorderRadiusGeometry; + final BorderRadiusGeometry? attachmentBorderRadiusGeometry; /// The padding of the widget - final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry? padding; /// The internal padding of the message text final EdgeInsetsGeometry textPadding; @@ -116,18 +116,18 @@ class MessageWidget extends StatefulWidget { final bool showInChannelIndicator; /// The function called when tapping on UserAvatar - final void Function(User) onUserAvatarTap; + final void Function(User)? onUserAvatarTap; /// The function called when tapping on a link - final void Function(String) onLinkTap; + final void Function(String)? onLinkTap; /// Used in [MessageReactionsModal] and [MessageActionsModal] final bool showReactionPickerIndicator; - final List readList; + final List? readList; - final ShowMessageCallback onShowMessage; - final ValueChanged onReturnAction; + final ShowMessageCallback? onShowMessage; + final ValueChanged? onReturnAction; /// If true show the users username next to the timestamp of the message final bool showUsername; @@ -147,22 +147,22 @@ class MessageWidget extends StatefulWidget { final bool translateUserAvatar; /// Function called when quotedMessage is tapped - final OnQuotedMessageTap onQuotedMessageTap; + final OnQuotedMessageTap? onQuotedMessageTap; /// Function called when message is tapped - final void Function(Message) onMessageTap; + final void Function(Message)? onMessageTap; /// List of custom actions shown on message long tap final List customActions; // Customize onTap on attachment - final void Function(Message message, Attachment attachment) onAttachmentTap; + final void Function(Message message, Attachment attachment)? onAttachmentTap; /// MessageWidget({ - Key key, - @required this.message, - @required this.messageTheme, + Key? key, + required this.message, + required this.messageTheme, this.reverse = false, this.translateUserAvatar = true, this.shape, @@ -197,7 +197,7 @@ class MessageWidget extends StatefulWidget { this.editMessageInputBuilder, this.textBuilder, this.onReturnAction, - Map customAttachmentBuilders, + Map? customAttachmentBuilders, this.readList, this.padding, this.textPadding = const EdgeInsets.symmetric( @@ -222,7 +222,7 @@ class MessageWidget extends StatefulWidget { child: wrapAttachmentWidget( context, Material( - color: messageTheme.messageBackgroundColor, + color: messageTheme?.messageBackgroundColor, child: ImageGroup( size: Size( MediaQuery.of(context).size.width * 0.8, @@ -236,7 +236,8 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry ?? BorderRadius.zero, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, ), ); } @@ -255,13 +256,14 @@ class MessageWidget extends StatefulWidget { onReturnAction: onReturnAction, onAttachmentTap: onAttachmentTap != null ? () { - onAttachmentTap?.call(message, attachments[0]); + onAttachmentTap.call(message, attachments[0]); } : null, ), border, reverse, - attachmentBorderRadiusGeometry ?? BorderRadius.zero, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, ); }, 'video': (context, message, attachments) { @@ -286,7 +288,7 @@ class MessageWidget extends StatefulWidget { onReturnAction: onReturnAction, onAttachmentTap: onAttachmentTap != null ? () { - onAttachmentTap?.call(message, attachment); + onAttachmentTap(message, attachment); } : null, ); @@ -294,11 +296,12 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry ?? BorderRadius.zero, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, ); }, 'giphy': (context, message, attachments) { - var border = RoundedRectangleBorder( + final border = RoundedRectangleBorder( side: BorderSide.none, borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, ); @@ -309,7 +312,6 @@ class MessageWidget extends StatefulWidget { children: attachments.map((attachment) { return GiphyAttachment( attachment: attachment, - messageTheme: messageTheme, message: message, size: Size( MediaQuery.of(context).size.width * 0.8, @@ -322,7 +324,8 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry ?? BorderRadius.zero, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, ); }, 'file': (context, message, attachments) { @@ -349,7 +352,8 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry ?? BorderRadius.zero, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, ); }) .insertBetween(SizedBox( @@ -381,7 +385,7 @@ class _MessageWidgetState extends State bool get showInChannel => widget.showInChannelIndicator; - bool get hasQuotedMessage => widget.message?.quotedMessage != null; + bool get hasQuotedMessage => widget.message.quotedMessage != null; bool get isSendFailed => widget.message.status == MessageSendingStatus.failed; @@ -394,17 +398,17 @@ class _MessageWidgetState extends State bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed; bool get isGiphy => - widget.message.attachments?.any((element) => element.type == 'giphy') == + widget.message.attachments.any((element) => element.type == 'giphy') == true; bool get hasNonUrlAttachments => widget.message.attachments - ?.where((it) => it.ogScrapeUrl == null) - ?.isNotEmpty == + .where((it) => it.ogScrapeUrl == null) + .isNotEmpty == true; bool get hasUrlAttachments => - widget.message.attachments?.any((it) => it.ogScrapeUrl != null) == true; + widget.message.attachments.any((it) => it.ogScrapeUrl != null) == true; bool get showBottomRow => showThreadReplyIndicator || @@ -415,12 +419,13 @@ class _MessageWidgetState extends State isDeleted; @override - bool get wantKeepAlive => widget.message.attachments?.isNotEmpty == true; + bool get wantKeepAlive => widget.message.attachments.isNotEmpty == true; @override Widget build(BuildContext context) { super.build(context); - final avatarWidth = widget.messageTheme.avatarTheme.constraints.maxWidth; + final avatarWidth = + widget.messageTheme?.avatarTheme?.constraints.maxWidth ?? 40; var leftPadding = widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; @@ -429,7 +434,7 @@ class _MessageWidgetState extends State child: Portal( child: InkWell( onTap: () { - widget.onMessageTap(widget.message); + widget.onMessageTap!(widget.message); }, onLongPress: widget.message.isDeleted && !isFailedState ? null @@ -460,7 +465,8 @@ class _MessageWidgetState extends State mainAxisSize: MainAxisSize.min, children: [ if (widget.showUserAvatar == - DisplayWidget.show) ...[ + DisplayWidget.show && + widget.message.user != null) ...[ _buildUserAvatar(), SizedBox(width: 4), ], @@ -533,13 +539,14 @@ class _MessageWidgetState extends State ), shape: widget.shape ?? RoundedRectangleBorder( - side: - widget.borderSide ?? - BorderSide( - color: widget + side: widget + .borderSide ?? + BorderSide( + color: widget .messageTheme - .messageBorderColor, - ), + ?.messageBorderColor ?? + Colors.grey, + ), borderRadius: widget .borderRadiusGeometry ?? BorderRadius.zero, @@ -616,14 +623,14 @@ class _MessageWidgetState extends State Widget _buildQuotedMessage() { final isMyMessage = - widget.message.user.id == StreamChat.of(context).user.id; - final onTap = widget.message?.quotedMessage?.isDeleted != true && + widget.message.user?.id == StreamChat.of(context).user?.id; + final onTap = widget.message.quotedMessage?.isDeleted != true && widget.onQuotedMessageTap != null - ? () => widget.onQuotedMessageTap(widget.message.quotedMessageId) + ? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId) : null; return QuotedMessageWidget( onTap: onTap, - message: widget.message.quotedMessage, + message: widget.message.quotedMessage!, messageTheme: isMyMessage ? StreamChatTheme.of(context).otherMessageTheme : StreamChatTheme.of(context).ownMessageTheme, @@ -660,12 +667,12 @@ class _MessageWidgetState extends State var children = []; - final threadParticipants = widget.message?.threadParticipants?.take(2); + final threadParticipants = widget.message.threadParticipants?.take(2); final showThreadParticipants = threadParticipants?.isNotEmpty == true; final replyCount = widget.message.replyCount; var msg = 'Thread Reply'; - if (showThreadReplyIndicator && replyCount > 1) { + if (showThreadReplyIndicator && replyCount! > 1) { msg = '$replyCount Thread Replies'; } @@ -674,9 +681,9 @@ class _MessageWidgetState extends State var message = widget.message; if (showInChannel) { final channel = StreamChannel.of(context); - message = await channel.getMessage(widget.message.parentId); + message = await channel.getMessage(widget.message.parentId!); } - return widget.onThreadTap(message); + return widget.onThreadTap!(message); } catch (e, stk) { print(e); print(stk); @@ -690,7 +697,7 @@ class _MessageWidgetState extends State if (showInChannel || showThreadReplyIndicator) ...[ if (showThreadParticipants) SizedBox.fromSize( - size: Size((threadParticipants.length * 8.0) + 8, 16), + size: Size((threadParticipants!.length * 8.0) + 8, 16), child: _buildThreadParticipantsIndicator(threadParticipants), ), InkWell( @@ -700,16 +707,16 @@ class _MessageWidgetState extends State ], if (showUsername) Text( - widget.message.user.name, + widget.message.user!.name, maxLines: 1, key: usernameKey, - style: widget.messageTheme.messageAuthor, + style: widget.messageTheme?.messageAuthor, overflow: TextOverflow.ellipsis, ), if (showTimeStamp) Text( Jiffy(widget.message.createdAt.toLocal()).jm, - style: widget.messageTheme.createdAt, + style: widget.messageTheme?.createdAt, ), if (showSendingIndicator) _buildSendingIndicator(), ]); @@ -724,13 +731,13 @@ class _MessageWidgetState extends State Container( margin: EdgeInsets.only( bottom: context.textScaleFactor * - (widget.messageTheme.replies.fontSize / 2), + ((widget.messageTheme?.replies?.fontSize ?? 1) / 2), ), child: CustomPaint( size: Size(16, 32) * context.textScaleFactor, painter: _ThreadReplyPainter( context: context, - color: widget.messageTheme.messageBorderColor, + color: widget.messageTheme?.messageBorderColor, ), ), ), @@ -758,7 +765,7 @@ class _MessageWidgetState extends State var urlAttachment = widget.message.attachments .firstWhere((element) => element.ogScrapeUrl != null); - var host = Uri.parse(urlAttachment.ogScrapeUrl).host; + var host = Uri.parse(urlAttachment.ogScrapeUrl!).host; var splitList = host.split('.'); var hostName = splitList.length == 3 ? splitList[1] : splitList[0]; var hostDisplayName = urlAttachment.authorName?.capitalize() ?? @@ -768,7 +775,7 @@ class _MessageWidgetState extends State return UrlAttachment( urlAttachment: urlAttachment, hostDisplayName: hostDisplayName, - textPadding: widget.textPadding, + textPadding: widget.textPadding as EdgeInsets, ); } @@ -801,15 +808,16 @@ class _MessageWidgetState extends State Widget _buildReactionIndicator( BuildContext context, ) { - final ownId = StreamChat.of(context).user.id; + final ownId = StreamChat.of(context).user!.id; final reactionsMap = {}; widget.message.latestReactions?.forEach((element) { - if (!reactionsMap.containsKey(element.type) || element.user.id == ownId) { + if (!reactionsMap.containsKey(element.type) || + element.user!.id == ownId) { reactionsMap[element.type] = element; } }); final reactionsList = reactionsMap.values.toList() - ..sort((a, b) => a.user.id == ownId ? 1 : -1); + ..sort((a, b) => a.user!.id == ownId ? 1 : -1); return AnimatedSwitcher( duration: Duration(milliseconds: 300), @@ -822,9 +830,13 @@ class _MessageWidgetState extends State key: ValueKey('${widget.message.id}.reactions'), reverse: widget.reverse, flipTail: widget.reverse, - backgroundColor: widget.messageTheme.reactionsBackgroundColor, - borderColor: widget.messageTheme.reactionsBorderColor, - maskColor: widget.messageTheme.reactionsMaskColor, + backgroundColor: + widget.messageTheme?.reactionsBackgroundColor ?? + Colors.transparent, + borderColor: widget.messageTheme?.reactionsBorderColor ?? + Colors.transparent, + maskColor: widget.messageTheme?.reactionsMaskColor ?? + Colors.transparent, reactions: reactionsList, ), ) @@ -845,9 +857,9 @@ class _MessageWidgetState extends State onCopyTap: (message) => Clipboard.setData(ClipboardData(text: message.text)), attachmentBorderRadiusGeometry: - widget.attachmentBorderRadiusGeometry, + widget.attachmentBorderRadiusGeometry as BorderRadius?, showUserAvatar: - widget.message.user.id == channel.client.state.user.id + widget.message.user!.id == channel.client.state.user!.id ? DisplayWidget.gone : DisplayWidget.show, messageTheme: widget.messageTheme, @@ -864,11 +876,11 @@ class _MessageWidgetState extends State widget.showResendMessage && (isSendFailed || isUpdateFailed), showCopyMessage: widget.showCopyMessage && !isFailedState && - widget.message.text?.trim()?.isNotEmpty == true, + widget.message.text?.trim().isNotEmpty == true, showEditMessage: widget.showEditMessage && !isDeleteFailed && widget.message.attachments - ?.any((element) => element.type == 'giphy') != + .any((element) => element.type == 'giphy') != true, showReactions: widget.showReactions, showReplyMessage: widget.showReplyMessage && @@ -894,9 +906,9 @@ class _MessageWidgetState extends State channel: channel, child: MessageReactionsModal( attachmentBorderRadiusGeometry: - widget.attachmentBorderRadiusGeometry, + widget.attachmentBorderRadiusGeometry as BorderRadius?, showUserAvatar: - widget.message.user.id == channel.client.state.user.id + widget.message.user!.id == channel.client.state.user!.id ? DisplayWidget.gone : DisplayWidget.show, onUserAvatarTap: widget.onUserAvatarTap, @@ -914,7 +926,7 @@ class _MessageWidgetState extends State ShapeBorder _getDefaultAttachmentShape(BuildContext context) { final hasFiles = - widget.message.attachments?.any((it) => it.type == 'file') == true; + widget.message.attachments.any((it) => it.type == 'file') == true; return RoundedRectangleBorder( side: hasFiles ? widget.attachmentBorderSide ?? @@ -940,13 +952,13 @@ class _MessageWidgetState extends State final attachmentGroups = >{}; widget.message.attachments - .where((element) => element.ogScrapeUrl == null) + .where((element) => element.ogScrapeUrl == null && element.type != null) .forEach((e) { if (attachmentGroups[e.type] == null) { - attachmentGroups[e.type] = []; + attachmentGroups[e.type!] = []; } - attachmentGroups[e.type].add(e); + attachmentGroups[e.type]?.add(e); }); final attachmentList = []; @@ -954,7 +966,7 @@ class _MessageWidgetState extends State attachmentGroups.forEach((type, attachments) { final attachmentBuilder = widget.attachmentBuilders[type]; - if (attachmentBuilder == null) return SizedBox(); + if (attachmentBuilder == null) return; final attachmentWidget = attachmentBuilder( context, widget.message, @@ -967,10 +979,9 @@ class _MessageWidgetState extends State padding: widget.attachmentPadding, child: Column( mainAxisSize: MainAxisSize.min, - children: attachmentList?.insertBetween(SizedBox( - height: widget.attachmentPadding.vertical / 2, - )) ?? - [], + children: attachmentList.insertBetween(SizedBox( + height: widget.attachmentPadding.vertical / 2, + )), ), ); } @@ -982,7 +993,7 @@ class _MessageWidgetState extends State } if (widget.onMessageActions != null) { - widget.onMessageActions(context, widget.message); + widget.onMessageActions!(context, widget.message); } else { _showMessageActionModalBottomSheet(context); } @@ -990,7 +1001,7 @@ class _MessageWidgetState extends State } Widget _buildSendingIndicator() { - final style = widget.messageTheme.createdAt; + final style = widget.messageTheme?.createdAt; final message = widget.message; if (hasNonUrlAttachments && @@ -1002,8 +1013,8 @@ class _MessageWidgetState extends State }).length; if (uploadRemaining == 0) { return StreamSvgIcon.check( - size: style.fontSize, - color: IconTheme.of(context).color.withOpacity(0.5), + size: style!.fontSize, + color: IconTheme.of(context).color!.withOpacity(0.5), ); } return Text( @@ -1015,14 +1026,14 @@ class _MessageWidgetState extends State Widget child = SendingIndicator( message: message, isMessageRead: isMessageRead, - size: style.fontSize, + size: style!.fontSize, ); if (isMessageRead) { child = Row( children: [ - if (StreamChannel.of(context).channel.memberCount > 2) + if (StreamChannel.of(context).channel.memberCount! > 2) Text( - widget.readList.length.toString(), + widget.readList!.length.toString(), style: style.copyWith( color: StreamChatTheme.of(context).colorTheme.accentBlue, ), @@ -1042,21 +1053,23 @@ class _MessageWidgetState extends State offset: Offset( 0, widget.translateUserAvatar - ? widget.messageTheme.avatarTheme.constraints.maxHeight / 2 + ? (widget.messageTheme?.avatarTheme?.constraints.maxHeight ?? + 40) / + 2 : 0, ), child: UserAvatar( - user: widget.message.user, + user: widget.message.user!, onTap: widget.onUserAvatarTap, - constraints: widget.messageTheme.avatarTheme.constraints, - borderRadius: widget.messageTheme.avatarTheme.borderRadius, + constraints: widget.messageTheme?.avatarTheme!.constraints, + borderRadius: widget.messageTheme?.avatarTheme!.borderRadius, showOnlineStatus: false, ), ), ); Widget _buildTextBubble() { - if (widget.message.text.trim().isEmpty) return Offstage(); + if (widget.message.text!.trim().isEmpty) return Offstage(); return Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, @@ -1066,15 +1079,15 @@ class _MessageWidgetState extends State Padding( padding: isOnlyEmoji ? EdgeInsets.zero : widget.textPadding, child: widget.textBuilder != null - ? widget.textBuilder(context, widget.message) + ? widget.textBuilder!(context, widget.message) : MessageText( onLinkTap: widget.onLinkTap, message: widget.message, onMentionTap: widget.onMentionTap, messageTheme: isOnlyEmoji - ? widget.messageTheme.copyWith( + ? widget.messageTheme?.copyWith( messageText: - widget.messageTheme.messageText.copyWith( + widget.messageTheme?.messageText!.copyWith( fontSize: 42, )) : widget.messageTheme, @@ -1086,11 +1099,11 @@ class _MessageWidgetState extends State ); } - bool get isOnlyEmoji => widget.message.text.isOnlyEmoji; + bool get isOnlyEmoji => widget.message.text!.isOnlyEmoji; - Color _getBackgroundColor() { + Color? _getBackgroundColor() { if (hasQuotedMessage) { - return widget.messageTheme.messageBackgroundColor; + return widget.messageTheme?.messageBackgroundColor; } if (hasUrlAttachments) { @@ -1105,7 +1118,7 @@ class _MessageWidgetState extends State return Colors.transparent; } - return widget.messageTheme.messageBackgroundColor; + return widget.messageTheme?.messageBackgroundColor; } void retryMessage(BuildContext context) { @@ -1127,15 +1140,15 @@ class _MessageWidgetState extends State } class _ThreadReplyPainter extends CustomPainter { - final Color color; - final BuildContext context; + final Color? color; + final BuildContext? context; - const _ThreadReplyPainter({this.context, @required this.color}); + const _ThreadReplyPainter({this.context, required this.color}); @override void paint(Canvas canvas, Size size) { final paint = Paint() - ..color = color ?? StreamChatTheme.of(context).colorTheme.greyGainsboro + ..color = color ?? StreamChatTheme.of(context!).colorTheme.greyGainsboro ..style = PaintingStyle.stroke ..strokeWidth = 1 ..strokeCap = StrokeCap.round; diff --git a/packages/stream_chat_flutter/lib/src/option_list_tile.dart b/packages/stream_chat_flutter/lib/src/option_list_tile.dart index 61ce939e..03af584b 100644 --- a/packages/stream_chat_flutter/lib/src/option_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/option_list_tile.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; class OptionListTile extends StatelessWidget { - final String title; - final Widget leading; - final Widget trailing; - final VoidCallback onTap; - final Color titleColor; - final Color tileColor; - final Color separatorColor; - final TextStyle titleTextStyle; + final String? title; + final Widget? leading; + final Widget? trailing; + final VoidCallback? onTap; + final Color? titleColor; + final Color? tileColor; + final Color? separatorColor; + final TextStyle? titleTextStyle; OptionListTile({ this.title, @@ -47,7 +47,7 @@ class OptionListTile extends StatelessWidget { Expanded( flex: 4, child: Text( - title, + title!, style: titleTextStyle ?? (titleColor == null ? StreamChatTheme.of(context).textTheme.bodyBold 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 4d2b2e38..bc77e97a 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -22,8 +22,8 @@ class _VideoAttachmentThumbnail extends StatefulWidget { final Attachment attachment; const _VideoAttachmentThumbnail({ - Key key, - @required this.attachment, + Key? key, + required this.attachment, this.size = const Size(32, 32), }) : super(key: key); @@ -33,12 +33,12 @@ class _VideoAttachmentThumbnail extends StatefulWidget { } class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> { - VideoPlayerController _controller; + late VideoPlayerController _controller; @override void initState() { super.initState(); - _controller = VideoPlayerController.network(widget.attachment.assetUrl) + _controller = VideoPlayerController.network(widget.attachment.assetUrl!) ..initialize().then((_) { setState(() {}); //when your thumbnail will show. }); @@ -67,7 +67,7 @@ class QuotedMessageWidget extends StatelessWidget { final Message message; /// The message theme - final MessageTheme messageTheme; + final MessageTheme? messageTheme; /// If true the widget will be mirrored final bool reverse; @@ -79,18 +79,18 @@ class QuotedMessageWidget extends StatelessWidget { final int textLimit; /// Map that defines a thumbnail builder for an attachment type - final Map + final Map? attachmentThumbnailBuilders; final EdgeInsetsGeometry padding; - final GestureTapCallback onTap; + final GestureTapCallback? onTap; /// QuotedMessageWidget({ - Key key, - @required this.message, - @required this.messageTheme, + Key? key, + required this.message, + required this.messageTheme, this.reverse = false, this.showBorder = false, this.textLimit = 170, @@ -99,13 +99,12 @@ class QuotedMessageWidget extends StatelessWidget { this.onTap, }) : super(key: key); - bool get _hasAttachments => message.attachments?.isNotEmpty == true; + bool get _hasAttachments => message.attachments.isNotEmpty == true; bool get _containsScrapeUrl => - message.attachments?.any((element) => element.ogScrapeUrl != null) == - true; + message.attachments.any((element) => element.ogScrapeUrl != null) == true; - bool get _containsText => message?.text?.isNotEmpty == true; + bool get _containsText => message.text?.isNotEmpty == true; @override Widget build(BuildContext context) { @@ -119,7 +118,7 @@ class QuotedMessageWidget extends StatelessWidget { children: [ Flexible(child: _buildMessage(context)), SizedBox(width: 8), - _buildUserAvatar(), + if (message.user != null) _buildUserAvatar(), ], ), ), @@ -127,17 +126,17 @@ class QuotedMessageWidget extends StatelessWidget { } Widget _buildMessage(BuildContext context) { - final isOnlyEmoji = message.text.isOnlyEmoji; + final isOnlyEmoji = message.text!.isOnlyEmoji; var msg = _hasAttachments && !_containsText - ? message.copyWith(text: message.attachments.last?.title ?? '') + ? message.copyWith(text: message.attachments.last.title ?? '') : message; - if (msg.text.length > textLimit) { - msg = msg.copyWith(text: '${msg.text.substring(0, textLimit - 3)}...'); + if (msg.text!.length > textLimit) { + msg = msg.copyWith(text: '${msg.text!.substring(0, textLimit - 3)}...'); } final children = [ if (_hasAttachments) _parseAttachments(context), - if (msg.text.isNotEmpty) + if (msg.text!.isNotEmpty) Flexible( child: Transform( transform: Matrix4.rotationY(reverse ? pi : 0), @@ -145,12 +144,12 @@ class QuotedMessageWidget extends StatelessWidget { child: MessageText( message: msg, messageTheme: isOnlyEmoji && _containsText - ? messageTheme.copyWith( - messageText: messageTheme.messageText.copyWith( + ? messageTheme?.copyWith( + messageText: messageTheme?.messageText?.copyWith( fontSize: 32, )) - : messageTheme.copyWith( - messageText: messageTheme.messageText.copyWith( + : messageTheme?.copyWith( + messageText: messageTheme?.messageText?.copyWith( fontSize: 12, )), ), @@ -193,7 +192,7 @@ class QuotedMessageWidget extends StatelessWidget { image: DecorationImage( fit: BoxFit.cover, image: CachedNetworkImageProvider( - attachment.imageUrl, + attachment.imageUrl!, ), ), ), @@ -211,16 +210,16 @@ class QuotedMessageWidget extends StatelessWidget { ); child = _buildUrlAttachment(attachment); } else { - QuotedMessageAttachmentThumbnailBuilder attachmentBuilder; + QuotedMessageAttachmentThumbnailBuilder? attachmentBuilder; attachment = message.attachments.last; - if (attachmentThumbnailBuilders?.containsKey(attachment?.type) == true) { - attachmentBuilder = attachmentThumbnailBuilders[attachment?.type]; + if (attachmentThumbnailBuilders?.containsKey(attachment.type) == true) { + attachmentBuilder = attachmentThumbnailBuilders![attachment.type]; } - attachmentBuilder = _defaultAttachmentBuilder[attachment?.type]; + attachmentBuilder = _defaultAttachmentBuilder[attachment.type]; if (attachmentBuilder == null) { child = Offstage(); } - child = attachmentBuilder(context, attachment); + child = attachmentBuilder!(context, attachment); } child = AbsorbPointer(child: child); return Transform( @@ -247,7 +246,7 @@ class QuotedMessageWidget extends StatelessWidget { transform: Matrix4.rotationY(reverse ? pi : 0), alignment: Alignment.center, child: UserAvatar( - user: message.user, + user: message.user!, constraints: BoxConstraints.tightFor( height: 24, width: 24, @@ -277,19 +276,20 @@ class QuotedMessageWidget extends StatelessWidget { 'giphy': (_, attachment) { final size = Size(32, 32); return CachedNetworkImage( - height: size?.height, - width: size?.width, + height: size.height, + width: size.width, placeholder: (_, __) { return Container( - width: size?.width, - height: size?.height, + width: size.width, + height: size.height, child: Center( child: CircularProgressIndicator(), ), ); }, - imageUrl: - attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl!, errorWidget: (context, url, error) { return AttachmentError(size: size); }, @@ -300,16 +300,16 @@ class QuotedMessageWidget extends StatelessWidget { return Container( height: 32, width: 32, - child: getFileTypeImage(attachment.extraData['mime_type']), + child: getFileTypeImage(attachment.extraData['mime_type'] as String?), ); }, }; } - Color _getBackgroundColor(BuildContext context) { + Color? _getBackgroundColor(BuildContext context) { if (_containsScrapeUrl) { return StreamChatTheme.of(context).colorTheme.blueAlice; } - return messageTheme.messageBackgroundColor; + return messageTheme?.messageBackgroundColor; } } diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index 51ec7aa5..bcf3127c 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -9,11 +10,11 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class ReactionBubble extends StatelessWidget { const ReactionBubble({ - Key key, - @required this.reactions, - @required this.borderColor, - @required this.backgroundColor, - @required this.maskColor, + Key? key, + required this.reactions, + required this.borderColor, + required this.backgroundColor, + required this.maskColor, this.reverse = false, this.flipTail = false, this.highlightOwnReactions = true, @@ -108,9 +109,8 @@ class ReactionBubble extends StatelessWidget { Reaction reaction, BuildContext context, ) { - final reactionIcon = reactionIcons.firstWhere( + final reactionIcon = reactionIcons.firstWhereOrNull( (r) => r.type == reaction.type, - orElse: () => null, ); return Padding( @@ -123,7 +123,7 @@ class ReactionBubble extends StatelessWidget { width: 16, height: 16, color: (!highlightOwnReactions || - reaction.user.id == StreamChat.of(context).user.id) + reaction.user?.id == StreamChat.of(context).user?.id) ? StreamChatTheme.of(context).colorTheme.accentBlue : StreamChatTheme.of(context) .colorTheme @@ -134,7 +134,7 @@ class ReactionBubble extends StatelessWidget { Icons.help_outline_rounded, size: 16, color: (!highlightOwnReactions || - reaction.user.id == StreamChat.of(context).user.id) + reaction.user?.id == StreamChat.of(context).user?.id) ? StreamChatTheme.of(context).colorTheme.accentBlue : StreamChatTheme.of(context) .colorTheme diff --git a/packages/stream_chat_flutter/lib/src/reaction_icon.dart b/packages/stream_chat_flutter/lib/src/reaction_icon.dart index 99b93328..66e19fe7 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_icon.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_icon.dart @@ -3,7 +3,7 @@ class ReactionIcon { final String assetName; ReactionIcon({ - this.type, - this.assetName, + required this.type, + required this.assetName, }); } diff --git a/packages/stream_chat_flutter/lib/src/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reaction_picker.dart index a33e7b52..7cd01eb7 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_picker.dart @@ -16,13 +16,13 @@ import 'extension.dart'; class ReactionPicker extends StatefulWidget { const ReactionPicker({ - Key key, - @required this.message, - @required this.messageTheme, + Key? key, + required this.message, + required this.messageTheme, }) : super(key: key); final Message message; - final MessageTheme messageTheme; + final MessageTheme? messageTheme; @override _ReactionPickerState createState() => _ReactionPickerState(); @@ -98,7 +98,8 @@ class _ReactionPickerState extends State if (ownReactionIndex != -1) { removeReaction( context, - widget.message.ownReactions[ownReactionIndex], + widget + .message.ownReactions![ownReactionIndex], ); } else { sendReaction( @@ -129,7 +130,7 @@ class _ReactionPickerState extends State .accentBlue : Theme.of(context) .iconTheme - .color + .color! .withOpacity(.5), ), ); @@ -181,7 +182,7 @@ class _ReactionPickerState extends State @override void dispose() { for (var a in animations) { - a?.dispose(); + a.dispose(); } super.dispose(); } diff --git a/packages/stream_chat_flutter/lib/src/sending_indicator.dart b/packages/stream_chat_flutter/lib/src/sending_indicator.dart index 3fca2fa9..68fc5a0d 100644 --- a/packages/stream_chat_flutter/lib/src/sending_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/sending_indicator.dart @@ -5,11 +5,11 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class SendingIndicator extends StatelessWidget { final Message message; final bool isMessageRead; - final double size; + final double? size; const SendingIndicator({ - Key key, - this.message, + Key? key, + required this.message, this.isMessageRead = false, this.size = 12, }) : super(key: key); @@ -22,10 +22,10 @@ class SendingIndicator extends StatelessWidget { color: StreamChatTheme.of(context).colorTheme.accentBlue, ); } - if (message.status == MessageSendingStatus.sent || message.status == null) { + if (message.status == MessageSendingStatus.sent) { return StreamSvgIcon.check( size: size, - color: IconTheme.of(context).color.withOpacity(0.5), + color: IconTheme.of(context).color!.withOpacity(0.5), ); } if (message.status == MessageSendingStatus.sending || diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 470591e7..d3a196a1 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -7,7 +8,6 @@ import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'dart:ui' as ui; /// Widget used to provide information about the chat to the widget tree /// @@ -32,8 +32,8 @@ import 'dart:ui' as ui; /// Use [StreamChat.of] to get the current [StreamChatState] instance. class StreamChat extends StatefulWidget { final StreamChatClient client; - final Widget child; - final StreamChatThemeData streamChatThemeData; + final Widget? child; + final StreamChatThemeData? streamChatThemeData; /// The amount of time that will pass before disconnecting the client in the background final Duration backgroundKeepAlive; @@ -41,12 +41,12 @@ class StreamChat extends StatefulWidget { /// Handler called whenever the [client] receives a new [Event] while the app /// is in background. Can be used to display various notifications depending /// upon the [Event.type] - final EventHandler onBackgroundEventReceived; + final EventHandler? onBackgroundEventReceived; StreamChat({ - Key key, - @required this.client, - @required this.child, + Key? key, + required this.client, + required this.child, this.streamChatThemeData, this.onBackgroundEventReceived, this.backgroundKeepAlive = const Duration(minutes: 1), @@ -59,7 +59,7 @@ class StreamChat extends StatefulWidget { /// Use this method to get the current [StreamChatState] instance static StreamChatState of(BuildContext context) { - StreamChatState streamChatState; + StreamChatState? streamChatState; streamChatState = context.findAncestorStateOfType(); @@ -96,7 +96,7 @@ class StreamChatState extends State { client: client, onBackgroundEventReceived: widget.onBackgroundEventReceived, backgroundKeepAlive: widget.backgroundKeepAlive, - child: widget.child, + child: widget.child!, ), ); }, @@ -107,17 +107,18 @@ class StreamChatState extends State { StreamChatThemeData _getTheme( BuildContext context, - StreamChatThemeData themeData, + StreamChatThemeData? themeData, ) { - final defaultTheme = StreamChatThemeData.getDefaultTheme(Theme.of(context)); - return defaultTheme.merge(themeData) ?? themeData; + final appBrightness = Theme.of(context).brightness; + final defaultTheme = StreamChatThemeData(brightness: appBrightness); + return defaultTheme.merge(themeData); } /// The current user - User get user => widget.client.state.user; + User? get user => widget.client.state.user; /// The current user as a stream - Stream get userStream => widget.client.state.userStream; + Stream get userStream => widget.client.state.userStream; @override void initState() { 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 e54c0fbe..9b28d1f6 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -2,10 +2,11 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/channel_header.dart'; import 'package:stream_chat_flutter/src/channel_preview.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/reaction_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Inherited widget providing the [StreamChatThemeData] to the widget tree @@ -13,9 +14,9 @@ class StreamChatTheme extends InheritedWidget { final StreamChatThemeData data; StreamChatTheme({ - Key key, - @required this.data, - Widget child, + Key? key, + required this.data, + required Widget child, }) : super( key: key, child: child, @@ -31,13 +32,12 @@ class StreamChatTheme extends InheritedWidget { final streamChatTheme = context.dependOnInheritedWidgetOfExactType(); - if (streamChatTheme == null) { - throw Exception( - 'You must have a StreamChatTheme widget at the top of your widget tree', - ); - } + assert( + streamChatTheme != null, + 'You must have a StreamChatTheme widget at the top of your widget tree', + ); - return streamChatTheme.data; + return streamChatTheme!.data; } } @@ -80,49 +80,96 @@ class StreamChatThemeData { final List reactionIcons; /// Create a theme from scratch - const StreamChatThemeData({ - this.textTheme, - this.colorTheme, - this.channelListHeaderTheme, - this.channelPreviewTheme, - this.channelTheme, - this.otherMessageTheme, - this.ownMessageTheme, - this.messageInputTheme, - this.defaultChannelImage, - this.defaultUserImage, - this.primaryIconTheme, - this.reactionIcons, + factory StreamChatThemeData({ + Brightness? brightness, + TextTheme? textTheme, + ColorTheme? colorTheme, + ChannelListHeaderTheme? channelListHeaderTheme, + ChannelPreviewTheme? channelPreviewTheme, + ChannelTheme? channelTheme, + MessageTheme? otherMessageTheme, + MessageTheme? ownMessageTheme, + MessageInputTheme? messageInputTheme, + Widget Function(BuildContext, Channel)? defaultChannelImage, + Widget Function(BuildContext, User)? defaultUserImage, + IconThemeData? primaryIconTheme, + List? reactionIcons, + }) { + brightness ??= colorTheme?.brightness ?? Brightness.light; + final isDark = brightness == Brightness.dark; + textTheme ??= isDark ? TextTheme.dark() : TextTheme.light(); + colorTheme ??= isDark ? ColorTheme.dark() : ColorTheme.light(); + + final defaultData = fromColorAndTextTheme( + colorTheme, + textTheme, + ); + + final customizedData = defaultData.copyWith( + channelListHeaderTheme: channelListHeaderTheme, + channelPreviewTheme: channelPreviewTheme, + channelTheme: channelTheme, + otherMessageTheme: otherMessageTheme, + ownMessageTheme: ownMessageTheme, + messageInputTheme: messageInputTheme, + defaultChannelImage: defaultChannelImage, + defaultUserImage: defaultUserImage, + primaryIconTheme: primaryIconTheme, + reactionIcons: reactionIcons, + ); + + return defaultData.merge(customizedData); + } + + factory StreamChatThemeData.light() => + StreamChatThemeData(brightness: Brightness.light); + + factory StreamChatThemeData.dark() => + StreamChatThemeData(brightness: Brightness.dark); + + const StreamChatThemeData.raw({ + required this.textTheme, + required this.colorTheme, + required this.channelListHeaderTheme, + required this.channelPreviewTheme, + required this.channelTheme, + required this.otherMessageTheme, + required this.ownMessageTheme, + required this.messageInputTheme, + required this.defaultChannelImage, + required this.defaultUserImage, + required this.primaryIconTheme, + required this.reactionIcons, }); /// Create a theme from a Material [Theme] factory StreamChatThemeData.fromTheme(ThemeData theme) { - final defaultTheme = getDefaultTheme(theme); + final defaultTheme = StreamChatThemeData(brightness: theme.brightness); final customizedTheme = StreamChatThemeData.fromColorAndTextTheme( defaultTheme.colorTheme.copyWith( accentBlue: theme.accentColor, ), defaultTheme.textTheme, ); - return defaultTheme.merge(customizedTheme) ?? customizedTheme; + return defaultTheme.merge(customizedTheme); } /// Creates a copy of [StreamChatThemeData] with specified attributes overridden. StreamChatThemeData copyWith({ - TextTheme textTheme, - ColorTheme colorTheme, - ChannelPreviewTheme channelPreviewTheme, - ChannelTheme channelTheme, - MessageTheme ownMessageTheme, - MessageTheme otherMessageTheme, - MessageInputTheme messageInputTheme, - Widget Function(BuildContext, Channel) defaultChannelImage, - Widget Function(BuildContext, User) defaultUserImage, - IconThemeData primaryIconTheme, - ChannelListHeaderTheme channelListHeaderTheme, - List reactionIcons, + TextTheme? textTheme, + ColorTheme? colorTheme, + ChannelPreviewTheme? channelPreviewTheme, + ChannelTheme? channelTheme, + MessageTheme? ownMessageTheme, + MessageTheme? otherMessageTheme, + MessageInputTheme? messageInputTheme, + Widget Function(BuildContext, Channel)? defaultChannelImage, + Widget Function(BuildContext, User)? defaultUserImage, + IconThemeData? primaryIconTheme, + ChannelListHeaderTheme? channelListHeaderTheme, + List? reactionIcons, }) => - StreamChatThemeData( + StreamChatThemeData.raw( channelListHeaderTheme: channelListHeaderTheme ?? this.channelListHeaderTheme, textTheme: textTheme ?? this.textTheme, @@ -138,28 +185,21 @@ class StreamChatThemeData { reactionIcons: reactionIcons ?? this.reactionIcons, ); - StreamChatThemeData merge(StreamChatThemeData other) { + StreamChatThemeData merge(StreamChatThemeData? other) { if (other == null) return this; return copyWith( channelListHeaderTheme: - channelListHeaderTheme?.merge(other.channelListHeaderTheme) ?? - other.channelListHeaderTheme, - textTheme: textTheme?.merge(other.textTheme) ?? other.textTheme, - colorTheme: colorTheme?.merge(other.colorTheme) ?? other.colorTheme, + channelListHeaderTheme.merge(other.channelListHeaderTheme), + textTheme: textTheme.merge(other.textTheme), + colorTheme: colorTheme.merge(other.colorTheme), primaryIconTheme: other.primaryIconTheme, defaultChannelImage: other.defaultChannelImage, defaultUserImage: other.defaultUserImage, - channelPreviewTheme: - channelPreviewTheme?.merge(other.channelPreviewTheme) ?? - other.channelPreviewTheme, - channelTheme: - channelTheme?.merge(other.channelTheme) ?? other.channelTheme, - ownMessageTheme: ownMessageTheme?.merge(other.ownMessageTheme) ?? - other.ownMessageTheme, - otherMessageTheme: otherMessageTheme?.merge(other.otherMessageTheme) ?? - other.otherMessageTheme, - messageInputTheme: messageInputTheme?.merge(other.messageInputTheme) ?? - other.messageInputTheme, + channelPreviewTheme: channelPreviewTheme.merge(other.channelPreviewTheme), + channelTheme: channelTheme.merge(other.channelTheme), + ownMessageTheme: ownMessageTheme.merge(other.ownMessageTheme), + otherMessageTheme: otherMessageTheme.merge(other.otherMessageTheme), + messageInputTheme: messageInputTheme.merge(other.messageInputTheme), reactionIcons: other.reactionIcons, ); } @@ -169,7 +209,7 @@ class StreamChatThemeData { TextTheme textTheme, ) { final accentColor = colorTheme.accentBlue; - return StreamChatThemeData( + return StreamChatThemeData.raw( textTheme: textTheme, colorTheme: colorTheme, primaryIconTheme: IconThemeData(color: colorTheme.black.withOpacity(.5)), @@ -314,22 +354,6 @@ class StreamChatThemeData { ], ); } - - /// Get the default Stream Chat theme - static StreamChatThemeData getDefaultTheme(ThemeData theme) { - final isDark = theme.brightness == Brightness.dark; - final textTheme = isDark ? TextTheme.dark() : TextTheme.light(); - final colorTheme = isDark ? ColorTheme.dark() : ColorTheme.light(); - return fromColorAndTextTheme( - colorTheme, - textTheme, - ); - } -} - -enum TextThemeType { - light, - dark, } class TextTheme { @@ -427,17 +451,17 @@ class TextTheme { }); TextTheme copyWith({ - TextThemeType type = TextThemeType.light, - TextStyle body, - TextStyle title, - TextStyle headlineBold, - TextStyle headline, - TextStyle bodyBold, - TextStyle footnoteBold, - TextStyle footnote, - TextStyle captionBold, + Brightness brightness = Brightness.light, + TextStyle? body, + TextStyle? title, + TextStyle? headlineBold, + TextStyle? headline, + TextStyle? bodyBold, + TextStyle? footnoteBold, + TextStyle? footnote, + TextStyle? captionBold, }) { - return type == TextThemeType.light + return brightness == Brightness.light ? TextTheme.light( body: body ?? this.body, title: title ?? this.title, @@ -460,28 +484,21 @@ class TextTheme { ); } - TextTheme merge(TextTheme other) { + TextTheme merge(TextTheme? other) { if (other == null) return this; return copyWith( - body: body?.merge(other.body) ?? other.body, - title: title?.merge(other.title) ?? other.title, - headlineBold: - headlineBold?.merge(other.headlineBold) ?? other.headlineBold, - headline: headline?.merge(other.headline) ?? other.headline, - bodyBold: bodyBold?.merge(other.bodyBold) ?? other.bodyBold, - footnoteBold: - footnoteBold?.merge(other.footnoteBold) ?? other.footnoteBold, - footnote: footnote?.merge(other.footnote) ?? other.footnote, - captionBold: captionBold?.merge(other.captionBold) ?? other.captionBold, + body: body.merge(other.body), + title: title.merge(other.title), + headlineBold: headlineBold.merge(other.headlineBold), + headline: headline.merge(other.headline), + bodyBold: bodyBold.merge(other.bodyBold), + footnoteBold: footnoteBold.merge(other.footnoteBold), + footnote: footnote.merge(other.footnote), + captionBold: captionBold.merge(other.captionBold), ); } } -enum ColorThemeType { - light, - dark, -} - class ColorTheme { final Color black; final Color grey; @@ -502,6 +519,7 @@ class ColorTheme { final Color overlay; final Color overlayDark; final Gradient bgGradient; + final Brightness brightness; ColorTheme.light({ this.black = const Color(0xff000000), @@ -536,7 +554,7 @@ class ColorTheme { sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0), this.modalShadow = const Effect( sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8.0), - }); + }) : brightness = Brightness.light; ColorTheme.dark({ this.black = const Color(0xffffffff), @@ -551,13 +569,32 @@ class ColorTheme { this.accentRed = const Color(0xffFF3742), this.accentGreen = const Color(0xff20E070), this.borderTop = const Effect( - sigmaX: 0, sigmaY: -1, color: Color(0xff141924), blur: 0.0), + sigmaX: 0, + sigmaY: -1, + color: Color(0xff141924), + blur: 0.0, + ), this.borderBottom = const Effect( - sigmaX: 0, sigmaY: 1, color: Color(0xff141924), blur: 0.0, alpha: 1.0), + sigmaX: 0, + sigmaY: 1, + color: Color(0xff141924), + blur: 0.0, + alpha: 1.0, + ), this.shadowIconButton = const Effect( - sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0), + sigmaX: 0, + sigmaY: 2, + color: Color(0xff000000), + alpha: 0.5, + blur: 4.0, + ), this.modalShadow = const Effect( - sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8.0), + sigmaX: 0, + sigmaY: 0, + color: Color(0xff000000), + alpha: 1, + blur: 8.0, + ), this.highlight = const Color(0xff302d22), this.overlay = const Color.fromRGBO(0, 0, 0, 0.4), this.overlayDark = const Color.fromRGBO(255, 255, 255, 0.6), @@ -570,31 +607,31 @@ class ColorTheme { ], stops: [0, 1], ), - }); + }) : brightness = Brightness.dark; ColorTheme copyWith({ - ColorThemeType type = ColorThemeType.light, - Color black, - Color grey, - Color greyGainsboro, - Color greyWhisper, - Color whiteSmoke, - Color whiteSnow, - Color white, - Color blueAlice, - Color accentBlue, - Color accentRed, - Color accentGreen, - Effect borderTop, - Effect borderBottom, - Effect shadowIconButton, - Effect modalShadow, - Color highlight, - Color overlay, - Color overlayDark, - Gradient bgGradient, + Brightness brightness = Brightness.light, + Color? black, + Color? grey, + Color? greyGainsboro, + Color? greyWhisper, + Color? whiteSmoke, + Color? whiteSnow, + Color? white, + Color? blueAlice, + Color? accentBlue, + Color? accentRed, + Color? accentGreen, + Effect? borderTop, + Effect? borderBottom, + Effect? shadowIconButton, + Effect? modalShadow, + Color? highlight, + Color? overlay, + Color? overlayDark, + Gradient? bgGradient, }) { - return type == ColorThemeType.light + return brightness == Brightness.light ? ColorTheme.light( black: black ?? this.black, grey: grey ?? this.grey, @@ -639,7 +676,7 @@ class ColorTheme { ); } - ColorTheme merge(ColorTheme other) { + ColorTheme merge(ColorTheme? other) { if (other == null) return this; return copyWith( black: other.black, @@ -671,65 +708,77 @@ class ChannelTheme { final ChannelHeaderTheme channelHeaderTheme; ChannelTheme({ - this.channelHeaderTheme, + required this.channelHeaderTheme, }); /// Creates a copy of [ChannelTheme] with specified attributes overridden. ChannelTheme copyWith({ - ChannelHeaderTheme channelHeaderTheme, + ChannelHeaderTheme? channelHeaderTheme, }) => ChannelTheme( channelHeaderTheme: channelHeaderTheme ?? this.channelHeaderTheme, ); - ChannelTheme merge(ChannelTheme other) { + ChannelTheme merge(ChannelTheme? other) { if (other == null) return this; return copyWith( - channelHeaderTheme: channelHeaderTheme?.merge(other.channelHeaderTheme) ?? - other.channelHeaderTheme, + channelHeaderTheme: channelHeaderTheme.merge(other.channelHeaderTheme), ); } } class AvatarTheme { - final BoxConstraints constraints; - final BorderRadius borderRadius; + final BoxConstraints? _constraints; + final BorderRadius? _borderRadius; + + BoxConstraints get constraints { + return _constraints ?? + BoxConstraints.tightFor( + height: 32, + width: 32, + ); + } + + BorderRadius get borderRadius { + return _borderRadius ?? BorderRadius.circular(20); + } AvatarTheme({ - this.constraints, - this.borderRadius, - }); + BoxConstraints? constraints, + BorderRadius? borderRadius, + }) : _constraints = constraints, + _borderRadius = borderRadius; AvatarTheme copyWith({ - BoxConstraints constraints, - BorderRadius borderRadius, + BoxConstraints? constraints, + BorderRadius? borderRadius, }) => AvatarTheme( - constraints: constraints ?? this.constraints, - borderRadius: borderRadius ?? this.borderRadius, + constraints: constraints ?? _constraints, + borderRadius: borderRadius ?? _borderRadius, ); - AvatarTheme merge(AvatarTheme other) { + AvatarTheme merge(AvatarTheme? other) { if (other == null) return this; return copyWith( - constraints: other.constraints, - borderRadius: other.borderRadius, + constraints: other._constraints, + borderRadius: other._borderRadius, ); } } class MessageTheme { - final TextStyle messageText; - final TextStyle messageAuthor; - final TextStyle messageLinks; - final TextStyle createdAt; - final TextStyle replies; - final Color messageBackgroundColor; - final Color messageBorderColor; - final Color reactionsBackgroundColor; - final Color reactionsBorderColor; - final Color reactionsMaskColor; - final AvatarTheme avatarTheme; + final TextStyle? messageText; + final TextStyle? messageAuthor; + final TextStyle? messageLinks; + final TextStyle? createdAt; + final TextStyle? replies; + final Color? messageBackgroundColor; + final Color? messageBorderColor; + final Color? reactionsBackgroundColor; + final Color? reactionsBorderColor; + final Color? reactionsMaskColor; + final AvatarTheme? avatarTheme; const MessageTheme({ this.replies, @@ -746,17 +795,17 @@ class MessageTheme { }); MessageTheme copyWith({ - TextStyle messageText, - TextStyle messageAuthor, - TextStyle messageLinks, - TextStyle createdAt, - TextStyle replies, - Color messageBackgroundColor, - Color messageBorderColor, - AvatarTheme avatarTheme, - Color reactionsBackgroundColor, - Color reactionsBorderColor, - Color reactionsMaskColor, + TextStyle? messageText, + TextStyle? messageAuthor, + TextStyle? messageLinks, + TextStyle? createdAt, + TextStyle? replies, + Color? messageBackgroundColor, + Color? messageBorderColor, + AvatarTheme? avatarTheme, + Color? reactionsBackgroundColor, + Color? reactionsBorderColor, + Color? reactionsMaskColor, }) => MessageTheme( messageText: messageText ?? this.messageText, @@ -774,7 +823,7 @@ class MessageTheme { reactionsMaskColor: reactionsMaskColor ?? this.reactionsMaskColor, ); - MessageTheme merge(MessageTheme other) { + MessageTheme merge(MessageTheme? other) { if (other == null) return this; return copyWith( messageText: messageText?.merge(other.messageText) ?? other.messageText, @@ -795,12 +844,12 @@ class MessageTheme { } class ChannelPreviewTheme { - final TextStyle title; - final TextStyle subtitle; - final TextStyle lastMessageAt; - final AvatarTheme avatarTheme; - final Color unreadCounterColor; - final double indicatorIconSize; + final TextStyle? title; + final TextStyle? subtitle; + final TextStyle? lastMessageAt; + final AvatarTheme? avatarTheme; + final Color? unreadCounterColor; + final double? indicatorIconSize; const ChannelPreviewTheme({ this.title, @@ -812,12 +861,12 @@ class ChannelPreviewTheme { }); ChannelPreviewTheme copyWith({ - TextStyle title, - TextStyle subtitle, - TextStyle lastMessageAt, - AvatarTheme avatarTheme, - Color unreadCounterColor, - double indicatorIconSize, + TextStyle? title, + TextStyle? subtitle, + TextStyle? lastMessageAt, + AvatarTheme? avatarTheme, + Color? unreadCounterColor, + double? indicatorIconSize, }) => ChannelPreviewTheme( title: title ?? this.title, @@ -828,7 +877,7 @@ class ChannelPreviewTheme { indicatorIconSize: indicatorIconSize ?? this.indicatorIconSize, ); - ChannelPreviewTheme merge(ChannelPreviewTheme other) { + ChannelPreviewTheme merge(ChannelPreviewTheme? other) { if (other == null) return this; return copyWith( title: title?.merge(other.title) ?? other.title, @@ -842,10 +891,10 @@ class ChannelPreviewTheme { } class ChannelHeaderTheme { - final TextStyle title; - final TextStyle subtitle; - final AvatarTheme avatarTheme; - final Color color; + final TextStyle? title; + final TextStyle? subtitle; + final AvatarTheme? avatarTheme; + final Color? color; const ChannelHeaderTheme({ this.title, @@ -855,10 +904,10 @@ class ChannelHeaderTheme { }); ChannelHeaderTheme copyWith({ - TextStyle title, - TextStyle subtitle, - AvatarTheme avatarTheme, - Color color, + TextStyle? title, + TextStyle? subtitle, + AvatarTheme? avatarTheme, + Color? color, }) => ChannelHeaderTheme( title: title ?? this.title, @@ -867,7 +916,7 @@ class ChannelHeaderTheme { color: color ?? this.color, ); - ChannelHeaderTheme merge(ChannelHeaderTheme other) { + ChannelHeaderTheme merge(ChannelHeaderTheme? other) { if (other == null) return this; return copyWith( title: title?.merge(other.title) ?? other.title, @@ -881,13 +930,13 @@ class ChannelHeaderTheme { /// Theme dedicated to the [ChannelListHeader] class ChannelListHeaderTheme { /// Style of the title text - final TextStyle title; + final TextStyle? title; /// Theme dedicated to the userAvatar - final AvatarTheme avatarTheme; + final AvatarTheme? avatarTheme; /// Background color of the appbar - final Color color; + final Color? color; /// Returns a new [ChannelListHeaderTheme] const ChannelListHeaderTheme({ @@ -898,9 +947,9 @@ class ChannelListHeaderTheme { /// Returns a new [ChannelListHeaderTheme] replacing some of its properties ChannelListHeaderTheme copyWith({ - TextStyle title, - AvatarTheme avatarTheme, - Color color, + TextStyle? title, + AvatarTheme? avatarTheme, + Color? color, }) => ChannelListHeaderTheme( title: title ?? this.title, @@ -909,7 +958,7 @@ class ChannelListHeaderTheme { ); /// Merges [this] [ChannelListHeaderTheme] with the [other] - ChannelListHeaderTheme merge(ChannelListHeaderTheme other) { + ChannelListHeaderTheme merge(ChannelListHeaderTheme? other) { if (other == null) return this; return copyWith( title: title?.merge(other.title) ?? other.title, @@ -922,40 +971,40 @@ class ChannelListHeaderTheme { /// Defines the theme dedicated to the [MessageInput] widget class MessageInputTheme { /// Duration of the [MessageInput] send button animation - final Duration sendAnimationDuration; + final Duration? sendAnimationDuration; /// Background color of [MessageInput] send button - final Color sendButtonColor; + final Color? sendButtonColor; /// Background color of [MessageInput] action buttons - final Color actionButtonColor; + final Color? actionButtonColor; /// Background color of [MessageInput] send button - final Color sendButtonIdleColor; + final Color? sendButtonIdleColor; /// Background color of [MessageInput] action buttons - final Color actionButtonIdleColor; + final Color? actionButtonIdleColor; /// Background color of [MessageInput] expand button - final Color expandButtonColor; + final Color? expandButtonColor; /// Background color of [MessageInput] - final Color inputBackground; + final Color? inputBackground; /// TextStyle of [MessageInput] - final TextStyle inputTextStyle; + final TextStyle? inputTextStyle; /// InputDecoration of [MessageInput] - final InputDecoration inputDecoration; + final InputDecoration? inputDecoration; /// Border gradient when the [MessageInput] is not focused - final Gradient idleBorderGradient; + final Gradient? idleBorderGradient; /// Border gradient when the [MessageInput] is focused - final Gradient activeBorderGradient; + final Gradient? activeBorderGradient; /// Border radius of [MessageInput] - final BorderRadius borderRadius; + final BorderRadius? borderRadius; /// Returns a new [MessageInputTheme] const MessageInputTheme({ @@ -975,18 +1024,18 @@ class MessageInputTheme { /// Returns a new [MessageInputTheme] replacing some of its properties MessageInputTheme copyWith({ - Duration sendAnimationDuration, - Color inputBackground, - Color actionButtonColor, - Color sendButtonColor, - Color actionButtonIdleColor, - Color sendButtonIdleColor, - Color expandButtonColor, - TextStyle inputTextStyle, - InputDecoration inputDecoration, - Gradient activeBorderGradient, - Gradient idleBorderGradient, - BorderRadius borderRadius, + Duration? sendAnimationDuration, + Color? inputBackground, + Color? actionButtonColor, + Color? sendButtonColor, + Color? actionButtonIdleColor, + Color? sendButtonIdleColor, + Color? expandButtonColor, + TextStyle? inputTextStyle, + InputDecoration? inputDecoration, + Gradient? activeBorderGradient, + Gradient? idleBorderGradient, + BorderRadius? borderRadius, }) => MessageInputTheme( sendAnimationDuration: @@ -1006,7 +1055,7 @@ class MessageInputTheme { ); /// Merges [this] [MessageInputTheme] with the [other] - MessageInputTheme merge(MessageInputTheme other) { + MessageInputTheme merge(MessageInputTheme? other) { if (other == null) return this; return copyWith( sendAnimationDuration: other.sendAnimationDuration, @@ -1027,11 +1076,11 @@ class MessageInputTheme { } class Effect { - final double sigmaX; - final double sigmaY; - final Color color; - final double alpha; - final double blur; + final double? sigmaX; + final double? sigmaY; + final Color? color; + final double? alpha; + final double? blur; const Effect({ this.sigmaX, @@ -1042,17 +1091,17 @@ class Effect { }); Effect copyWith({ - double sigmaX, - double sigmaY, - Color color, - double alpha, - double blur, + double? sigmaX, + double? sigmaY, + Color? color, + double? alpha, + double? blur, }) => Effect( sigmaX: sigmaX ?? this.sigmaX, sigmaY: sigmaY ?? this.sigmaY, color: color ?? this.color, - alpha: color ?? this.alpha, + alpha: color as double? ?? this.alpha, blur: blur ?? this.blur, ); } diff --git a/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart b/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart index 35e98960..95b3c70e 100644 --- a/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart +++ b/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart @@ -5,8 +5,8 @@ class StreamNeumorphicButton extends StatelessWidget { final Color backgroundColor; const StreamNeumorphicButton({ - Key key, - @required this.child, + Key? key, + required this.child, this.backgroundColor = Colors.white, }) : super(key: key); @@ -21,7 +21,7 @@ class StreamNeumorphicButton extends StatelessWidget { shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: Colors.grey[700], + color: Colors.grey.shade700, offset: Offset(0, 1.0), blurRadius: 0.5, spreadRadius: 0, diff --git a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart index 374b9ba1..8dc38ba2 100644 --- a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart +++ b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; class StreamSvgIcon extends StatelessWidget { - final String assetName; - final double width; - final double height; - final Color color; + final String? assetName; + final double? width; + final double? height; + final Color? color; const StreamSvgIcon({ this.assetName, @@ -30,8 +30,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.settings({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'settings.svg', @@ -42,8 +42,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.down({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_down.svg', @@ -54,8 +54,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.attach({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_attach.svg', @@ -66,8 +66,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.smile({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_smile.svg', @@ -78,8 +78,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.mentions({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'mentions.svg', @@ -90,8 +90,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.record({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_record.svg', @@ -102,8 +102,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.camera({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_camera.svg', @@ -114,8 +114,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.files({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'files.svg', @@ -126,8 +126,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.pictures({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'pictures.svg', @@ -138,8 +138,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.left({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_left.svg', @@ -150,8 +150,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.user({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_user.svg', @@ -162,8 +162,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.userAdd({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_User_add.svg', @@ -174,8 +174,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.check({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_check.svg', @@ -186,8 +186,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.checkAll({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_check_all.svg', @@ -198,8 +198,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.checkSend({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_check_send.svg', @@ -210,8 +210,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.penWrite({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_pen-write.svg', @@ -222,8 +222,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.contacts({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_contacts.svg', @@ -234,8 +234,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.close({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_close.svg', @@ -246,8 +246,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.search({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_search.svg', @@ -258,8 +258,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.right({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_right.svg', @@ -270,8 +270,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.mute({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_mute.svg', @@ -282,8 +282,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.userRemove({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_User_deselect.svg', @@ -294,8 +294,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.lightning({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_lightning-command runner.svg', @@ -306,8 +306,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.emptyCircleLeft({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_empty_circle_left.svg', @@ -318,8 +318,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.message({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_message.svg', @@ -330,8 +330,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.thread({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_Thread_Reply.svg', @@ -342,8 +342,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.reply({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_curve_line_left_up_big.svg', @@ -354,8 +354,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.edit({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_edit.svg', @@ -366,8 +366,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.download({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_download.svg', @@ -378,8 +378,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.cloudDownload({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_cloud_download.svg', @@ -390,8 +390,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.copy({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_copy.svg', @@ -402,8 +402,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.delete({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_delete.svg', @@ -414,8 +414,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.eye({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_eye-off.svg', @@ -426,8 +426,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.arrowRight({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_arrow_right.svg', @@ -438,8 +438,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.closeSmall({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_close_sml.svg', @@ -450,8 +450,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconCurveLineLeftUp({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_curve_line_left_up.svg', @@ -462,8 +462,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconMoon({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'icon_moon.svg', @@ -474,8 +474,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconShare({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'icon_SHARE.svg', @@ -486,8 +486,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconGrid({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_grid.svg', @@ -498,8 +498,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconSendMessage({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_send_message.svg', @@ -510,8 +510,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconMenuPoint({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_menu_point_v.svg', @@ -522,8 +522,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconSave({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_save.svg', @@ -534,8 +534,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.shareArrow({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'share_arrow.svg', @@ -546,8 +546,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetype7z({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_7z.svg', @@ -558,8 +558,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeCsv({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_CSV.svg', @@ -570,8 +570,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeDoc({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_DOC.svg', @@ -582,8 +582,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeDocx({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_DOCX.svg', @@ -594,8 +594,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeGeneric({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_Generic.svg', @@ -606,8 +606,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeHtml({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_html.svg', @@ -618,8 +618,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeMd({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_MD.svg', @@ -630,8 +630,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeOdt({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_ODT.svg', @@ -642,8 +642,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypePdf({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_PDF.svg', @@ -654,8 +654,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypePpt({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_PPT.svg', @@ -666,8 +666,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypePptx({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_PPTX.svg', @@ -678,8 +678,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeRar({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_RAR.svg', @@ -690,8 +690,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeRtf({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_RTF.svg', @@ -702,8 +702,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeTar({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_TAR.svg', @@ -714,8 +714,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeTxt({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_TXT.svg', @@ -726,8 +726,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeXls({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_XLS.svg', @@ -738,8 +738,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeXlsx({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_XLSX.svg', @@ -750,8 +750,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeZip({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_ZIP.svg', @@ -762,8 +762,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconGroup({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_group.svg', @@ -774,8 +774,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconNotification({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_notification.svg', @@ -786,8 +786,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconUserDelete({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_user_delete.svg', @@ -798,8 +798,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.error({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_error.svg', @@ -810,8 +810,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.circleUp({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_circle_up.svg', @@ -822,8 +822,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconUserSettings({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_user_settings.svg', @@ -834,8 +834,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.giphyIcon({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'giphy_icon.svg', @@ -846,8 +846,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.imgur({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'imgur.svg', @@ -858,8 +858,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.volumeUp({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'volume-up.svg', @@ -870,8 +870,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.flag({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'flag.svg', @@ -882,8 +882,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconFlag({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'icon_flag.svg', @@ -894,8 +894,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.retry({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'icon_retry.svg', diff --git a/packages/stream_chat_flutter/lib/src/swipeable.dart b/packages/stream_chat_flutter/lib/src/swipeable.dart index 8280bf24..90146e4c 100644 --- a/packages/stream_chat_flutter/lib/src/swipeable.dart +++ b/packages/stream_chat_flutter/lib/src/swipeable.dart @@ -8,15 +8,15 @@ import 'stream_chat_theme.dart'; class Swipeable extends StatefulWidget { final Widget child; final Widget backgroundIcon; - final VoidCallback onSwipeStart; - final VoidCallback onSwipeCancel; - final VoidCallback onSwipeEnd; + final VoidCallback? onSwipeStart; + final VoidCallback? onSwipeCancel; + final VoidCallback? onSwipeEnd; final double threshold; /// const Swipeable({ - @required this.child, - @required this.backgroundIcon, + required this.child, + required this.backgroundIcon, this.onSwipeStart, this.onSwipeCancel, this.onSwipeEnd, @@ -29,11 +29,11 @@ class Swipeable extends StatefulWidget { class _SwipeableState extends State with TickerProviderStateMixin { double _dragExtent = 0.0; - AnimationController _moveController; - AnimationController _iconMoveController; - Animation _moveAnimation; - Animation _iconTransitionAnimation; - Animation _iconFadeAnimation; + late AnimationController _moveController; + late AnimationController _iconMoveController; + late Animation _moveAnimation; + late Animation _iconTransitionAnimation; + late Animation _iconFadeAnimation; bool _pastThreshold = false; final _animationDuration = const Duration(milliseconds: 200); @@ -67,18 +67,18 @@ class _SwipeableState extends State with TickerProviderStateMixin { void _handleDragStart(DragStartDetails details) { if (widget.onSwipeStart != null) { - widget.onSwipeStart(); + widget.onSwipeStart!(); } } void _handleDragUpdate(DragUpdateDetails details) { - final delta = details.primaryDelta; + final delta = details.primaryDelta!; _dragExtent += delta; if (_dragExtent.isNegative) return; var movePastThresholdPixels = widget.threshold; - var newPos = _dragExtent.abs() / context.size.width; + var newPos = _dragExtent.abs() / context.size!.width; if (_dragExtent.abs() > movePastThresholdPixels) { // how many "thresholds" past the threshold we are. 1 = the threshold 2 @@ -90,7 +90,7 @@ class _SwipeableState extends State with TickerProviderStateMixin { var reducedThreshold = math.pow(n, 0.3); var adjustedPixelPos = movePastThresholdPixels * reducedThreshold; - newPos = adjustedPixelPos / context.size.width; + newPos = adjustedPixelPos / context.size!.width; if (_dragExtent > 0 && !_pastThreshold) { _iconMoveController.value = 1; @@ -100,7 +100,7 @@ class _SwipeableState extends State with TickerProviderStateMixin { // Send a cancel event if the user has swiped back underneath the // threshold if (_pastThreshold && widget.onSwipeCancel != null) { - widget.onSwipeCancel(); + widget.onSwipeCancel!(); } _pastThreshold = false; } @@ -115,7 +115,7 @@ class _SwipeableState extends State with TickerProviderStateMixin { _iconMoveController.animateTo(0.0, duration: _animationDuration); _dragExtent = 0.0; if (_pastThreshold && widget.onSwipeEnd != null) { - widget.onSwipeEnd(); + widget.onSwipeEnd!(); } } diff --git a/packages/stream_chat_flutter/lib/src/system_message.dart b/packages/stream_chat_flutter/lib/src/system_message.dart index ac3176ce..cdb9294f 100644 --- a/packages/stream_chat_flutter/lib/src/system_message.dart +++ b/packages/stream_chat_flutter/lib/src/system_message.dart @@ -7,11 +7,11 @@ class SystemMessage extends StatelessWidget { final Message message; /// The function called when tapping on the message when the message is not failed - final void Function(Message) onMessageTap; + final void Function(Message)? onMessageTap; const SystemMessage({ - Key key, - @required this.message, + Key? key, + required this.message, this.onMessageTap, }) : super(key: key); @@ -22,11 +22,11 @@ class SystemMessage extends StatelessWidget { behavior: HitTestBehavior.opaque, onTap: () { if (onMessageTap != null) { - onMessageTap(message); + onMessageTap!(message); } }, child: Text( - message.text, + message.text!, textAlign: TextAlign.center, softWrap: true, style: theme.textTheme.captionBold.copyWith( diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart index 449d4584..9dc8f98c 100644 --- a/packages/stream_chat_flutter/lib/src/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/thread_header.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'back_button.dart'; import 'channel_name.dart'; @@ -61,30 +61,30 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { /// Callback to call when pressing the back button. /// By default it calls [Navigator.pop] - final VoidCallback onBackPressed; + final VoidCallback? onBackPressed; /// Callback to call when the title is tapped. - final VoidCallback onTitleTap; + final VoidCallback? onTitleTap; /// The message parent of this thread final Message parent; /// Title widget - final Widget title; + final Widget? title; /// Subtitle widget - final Widget subtitle; + final Widget? subtitle; /// Leading widget - final Widget leading; + final Widget? leading; /// AppBar actions - final List actions; + final List? actions; /// Instantiate a new ThreadHeader ThreadHeader({ - Key key, - @required this.parent, + Key? key, + required this.parent, this.showBackButton = true, this.onBackPressed, this.title, diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart index 8a53c3e8..aecc3bf1 100644 --- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart @@ -6,7 +6,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class TypingIndicator extends StatelessWidget { /// Instantiate a new TypingIndicator const TypingIndicator({ - Key key, + Key? key, this.channel, this.alternativeWidget, this.style, @@ -15,13 +15,13 @@ class TypingIndicator extends StatelessWidget { }) : super(key: key); /// Style of the text widget - final TextStyle style; + final TextStyle? style; /// List of typing users - final Channel channel; + final Channel? channel; /// Widget built when no typings is happening - final Widget alternativeWidget; + final Widget? alternativeWidget; /// The padding of this widget final EdgeInsets padding; @@ -31,7 +31,7 @@ class TypingIndicator extends StatelessWidget { @override Widget build(BuildContext context) { final channelState = - channel?.state ?? StreamChannel.of(context).channel.state; + channel?.state ?? StreamChannel.of(context).channel.state!; return StreamBuilder>( initialData: channelState.typingEvents, stream: channelState.typingEventsStream, @@ -53,7 +53,7 @@ class TypingIndicator extends StatelessWidget { height: 4, ), Text( - ' ${snapshot.data[0].name}${snapshot.data.length == 1 ? '' : ' and ${snapshot.data.length - 1} more'} ${snapshot.data.length == 1 ? 'is' : 'are'} typing', + ' ${snapshot.data![0].name}${snapshot.data!.length == 1 ? '' : ' and ${snapshot.data!.length - 1} more'} ${snapshot.data!.length == 1 ? 'is' : 'are'} typing', maxLines: 1, style: style, ), diff --git a/packages/stream_chat_flutter/lib/src/unread_indicator.dart b/packages/stream_chat_flutter/lib/src/unread_indicator.dart index 0c30e9a1..d55cd4f1 100644 --- a/packages/stream_chat_flutter/lib/src/unread_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/unread_indicator.dart @@ -4,23 +4,23 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class UnreadIndicator extends StatelessWidget { const UnreadIndicator({ - Key key, + Key? key, this.cid, }) : super(key: key); /// Channel cid used to retrieve unread count - final String cid; + final String? cid; @override Widget build(BuildContext context) { final client = StreamChat.of(context).client; return IgnorePointer( - child: StreamBuilder( + child: StreamBuilder( stream: cid != null - ? client.state.channels[cid].state.unreadCountStream + ? client.state.channels[cid]?.state?.unreadCountStream : client.state.totalUnreadCountStream, initialData: cid != null - ? client.state.channels[cid].state.unreadCount + ? client.state.channels[cid]?.state?.unreadCount : client.state.totalUnreadCount, builder: (context, snapshot) { if (!snapshot.hasData || snapshot.data == 0) { @@ -40,7 +40,7 @@ class UnreadIndicator extends StatelessWidget { ), child: Center( child: Text( - '${snapshot.data > 99 ? '99+' : snapshot.data}', + '${snapshot.data! > 99 ? '99+' : snapshot.data}', style: TextStyle( fontSize: 11, color: Colors.white, diff --git a/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart b/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart index 53c121e2..ec06de8a 100644 --- a/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart @@ -5,20 +5,27 @@ import 'stream_chat_theme.dart'; class UploadProgressIndicator extends StatelessWidget { final int uploaded; final int total; - final Color progressIndicatorColor; + late final Color progressIndicatorColor; final EdgeInsetsGeometry padding; final bool showBackground; - final TextStyle textStyle; + final TextStyle? textStyle; - const UploadProgressIndicator({ - Key key, - @required this.uploaded, - @required this.total, - this.progressIndicatorColor = const Color(0xffb2b2b2), - this.padding = const EdgeInsets.only(top: 5, bottom: 5, right: 11, left: 5), + UploadProgressIndicator({ + Key? key, + required this.uploaded, + required this.total, + Color? progressIndicatorColor, + this.padding = const EdgeInsets.only( + top: 5, + bottom: 5, + right: 11, + left: 5, + ), this.showBackground = true, this.textStyle, - }) : super(key: key); + }) : progressIndicatorColor = + progressIndicatorColor ?? const Color(0xffb2b2b2), + super(key: key); @override Widget build(BuildContext context) { diff --git a/packages/stream_chat_flutter/lib/src/url_attachment.dart b/packages/stream_chat_flutter/lib/src/url_attachment.dart index 9dc5cdc8..d9beab20 100644 --- a/packages/stream_chat_flutter/lib/src/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/url_attachment.dart @@ -9,9 +9,9 @@ class UrlAttachment extends StatelessWidget { final EdgeInsets textPadding; UrlAttachment({ - @required this.urlAttachment, - @required this.hostDisplayName, - @required this.textPadding, + required this.urlAttachment, + required this.hostDisplayName, + required this.textPadding, }); @override @@ -19,7 +19,7 @@ class UrlAttachment extends StatelessWidget { return GestureDetector( onTap: () => launchURL( context, - urlAttachment.ogScrapeUrl, + urlAttachment.ogScrapeUrl!, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -35,7 +35,7 @@ class UrlAttachment extends StatelessWidget { children: [ CachedNetworkImage( width: double.infinity, - imageUrl: urlAttachment.imageUrl, + imageUrl: urlAttachment.imageUrl!, fit: BoxFit.cover, ), Positioned( @@ -78,7 +78,7 @@ class UrlAttachment extends StatelessWidget { children: [ if (urlAttachment.title != null) Text( - urlAttachment.title.trim(), + urlAttachment.title!.trim(), maxLines: 1, overflow: TextOverflow.ellipsis, style: StreamChatTheme.of(context) @@ -88,7 +88,7 @@ class UrlAttachment extends StatelessWidget { ), if (urlAttachment.text != null) Text( - urlAttachment.text, + urlAttachment.text!, style: StreamChatTheme.of(context) .textTheme .body diff --git a/packages/stream_chat_flutter/lib/src/user_avatar.dart b/packages/stream_chat_flutter/lib/src/user_avatar.dart index 41860a7f..96753b99 100644 --- a/packages/stream_chat_flutter/lib/src/user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/user_avatar.dart @@ -6,8 +6,8 @@ import '../stream_chat_flutter.dart'; class UserAvatar extends StatelessWidget { const UserAvatar({ - Key key, - @required this.user, + Key? key, + required this.user, this.constraints, this.onlineIndicatorConstraints, this.onTap, @@ -22,19 +22,19 @@ class UserAvatar extends StatelessWidget { final User user; final Alignment onlineIndicatorAlignment; - final BoxConstraints constraints; - final BorderRadius borderRadius; - final BoxConstraints onlineIndicatorConstraints; - final void Function(User) onTap; - final void Function(User) onLongPress; + final BoxConstraints? constraints; + final BorderRadius? borderRadius; + final BoxConstraints? onlineIndicatorConstraints; + final void Function(User)? onTap; + final void Function(User)? onLongPress; final bool showOnlineStatus; final bool selected; - final Color selectionColor; + final Color? selectionColor; final double selectionThickness; @override Widget build(BuildContext context) { - final hasImage = user.extraData?.containsKey('image') == true && + final hasImage = user.extraData.containsKey('image') && user.extraData['image'] != null && user.extraData['image'] != ''; final streamChatTheme = StreamChatTheme.of(context); @@ -44,17 +44,17 @@ class UserAvatar extends StatelessWidget { child: ClipRRect( clipBehavior: Clip.antiAlias, borderRadius: borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme.borderRadius, + streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius, child: Container( constraints: constraints ?? - streamChatTheme.ownMessageTheme.avatarTheme.constraints, + streamChatTheme.ownMessageTheme.avatarTheme?.constraints, decoration: BoxDecoration( color: streamChatTheme.colorTheme.accentBlue, ), child: hasImage ? CachedNetworkImage( filterQuality: FilterQuality.high, - imageUrl: user.extraData['image'], + imageUrl: user.extraData['image'] as String, errorWidget: (_, __, ___) { return streamChatTheme.defaultUserImage(context, user); }, @@ -68,11 +68,12 @@ class UserAvatar extends StatelessWidget { if (selected) { avatar = ClipRRect( borderRadius: (borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) + + streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius ?? + BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( constraints: constraints ?? - streamChatTheme.ownMessageTheme.avatarTheme.constraints, + streamChatTheme.ownMessageTheme.avatarTheme?.constraints, color: selectionColor ?? StreamChatTheme.of(context).colorTheme.accentBlue, child: Padding( @@ -83,12 +84,12 @@ class UserAvatar extends StatelessWidget { ); } return GestureDetector( - onTap: onTap != null ? () => onTap(user) : null, - onLongPress: onLongPress != null ? () => onLongPress(user) : null, + onTap: onTap != null ? () => onTap!(user) : null, + onLongPress: onLongPress != null ? () => onLongPress!(user) : null, child: Stack( children: [ avatar, - if (showOnlineStatus && user.online == true) + if (showOnlineStatus && user.online) Positioned.fill( child: Align( alignment: onlineIndicatorAlignment, diff --git a/packages/stream_chat_flutter/lib/src/user_item.dart b/packages/stream_chat_flutter/lib/src/user_item.dart index cb82e311..258855d8 100644 --- a/packages/stream_chat_flutter/lib/src/user_item.dart +++ b/packages/stream_chat_flutter/lib/src/user_item.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/user_list_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'stream_chat_theme.dart'; @@ -19,8 +19,8 @@ import 'stream_chat_theme.dart'; class UserItem extends StatelessWidget { /// Instantiate a new UserItem const UserItem({ - Key key, - @required this.user, + Key? key, + required this.user, this.onTap, this.onLongPress, this.onImageTap, @@ -29,16 +29,16 @@ class UserItem extends StatelessWidget { }) : super(key: key); /// Function called when tapping this widget - final void Function(User) onTap; + final void Function(User)? onTap; /// Function called when long pressing this widget - final void Function(User) onLongPress; + final void Function(User)? onLongPress; /// User displayed final User user; /// The function called when the image is tapped - final void Function(User) onImageTap; + final void Function(User)? onImageTap; /// If true the [UserItem] will show a trailing checkmark final bool selected; @@ -51,12 +51,12 @@ class UserItem extends StatelessWidget { return ListTile( onTap: () { if (onTap != null) { - onTap(user); + onTap!(user); } }, onLongPress: () { if (onLongPress != null) { - onLongPress(user); + onLongPress!(user); } }, leading: UserAvatar( @@ -64,7 +64,7 @@ class UserItem extends StatelessWidget { showOnlineStatus: true, onTap: (user) { if (onImageTap != null) { - onImageTap(user); + onImageTap!(user); } }, constraints: BoxConstraints.tightFor( 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 c300971d..2f0e13b9 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -5,7 +5,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'user_item.dart'; /// Callback called when tapping on a user -typedef UserTapCallback = void Function(User, Widget); +typedef UserTapCallback = void Function(User, Widget?); /// Builder used to create a custom [ListUserItem] from a [User] typedef UserItemBuilder = Widget Function(BuildContext, User, bool); @@ -44,7 +44,7 @@ typedef UserItemBuilder = Widget Function(BuildContext, User, bool); class UserListView extends StatefulWidget { /// Instantiate a new UserListView const UserListView({ - Key key, + Key? key, this.filter, this.options, this.sort, @@ -72,51 +72,51 @@ class UserListView extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Filter filter; + final Filter? filter; /// Query channels options. /// /// state: if true returns the Channel state /// watch: if true listen to changes to this Channel in real time. - final Map options; + final Map? options; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be provided. /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. /// Direction can be ascending or descending. - final List sort; + final List? sort; /// Pagination parameters /// limit: the number of users to return (max is 30) /// offset: the offset (max is 1000) /// message_limit: how many messages should be included to each channel - final PaginationParams pagination; + final PaginationParams? pagination; /// Function called when tapping on a channel /// By default it calls [Navigator.push] building a [MaterialPageRoute] /// with the widget [userWidget] as child. - final UserTapCallback onUserTap; + final UserTapCallback? onUserTap; /// Function called when long pressing on a channel - final Function(User) onUserLongPress; + final Function(User)? onUserLongPress; /// Widget used when opening a channel - final Widget userWidget; + final Widget? userWidget; /// Builder used to create a custom user preview - final UserItemBuilder userItemBuilder; + final UserItemBuilder? userItemBuilder; /// Builder used to create a custom item separator - final Function(BuildContext, int) separatorBuilder; + final Function(BuildContext, int)? separatorBuilder; /// The function called when the image is tapped - final Function(User) onImageTap; + final Function(User)? onImageTap; /// Set it to false to disable the pull-to-refresh widget final bool pullToRefresh; /// Sets a blue trailing checkMark in [ListUserItem] for all the [selectedUsers] - final Set selectedUsers; + final Set? selectedUsers; /// Set it to true to group users by their first character /// @@ -127,16 +127,17 @@ class UserListView extends StatefulWidget { final int crossAxisCount; /// The builder that will be used in case of error - final Widget Function(Error error) errorBuilder; + final Widget Function(Error error)? errorBuilder; /// The builder that will be used to build the list - final Widget Function(BuildContext context, List users) listBuilder; + final Widget Function(BuildContext context, List users)? + listBuilder; /// The builder that will be used for loading - final WidgetBuilder loadingBuilder; + final WidgetBuilder? loadingBuilder; /// The builder used when the channel list is empty. - final WidgetBuilder emptyBuilder; + final WidgetBuilder? emptyBuilder; @override _UserListViewState createState() => _UserListViewState(); @@ -151,9 +152,9 @@ class _UserListViewState extends State @override Widget build(BuildContext context) { var child = UserListCore( - errorBuilder: widget.errorBuilder ?? + errorBuilder: widget.errorBuilder as Widget Function(Object)? ?? (err) { - return _buildError(err); + return _buildError(err as Error); }, emptyBuilder: widget.emptyBuilder ?? (context) { @@ -193,7 +194,7 @@ class _UserListViewState extends State return child; } else { return RefreshIndicator( - onRefresh: () => _userListController.loadData(), + onRefresh: () => _userListController.loadData!(), child: child, ); } @@ -241,7 +242,7 @@ class _UserListViewState extends State child: Text(message), ), TextButton( - onPressed: () => _userListController.loadData(), + onPressed: () => _userListController.loadData!(), child: Text('Retry'), ), ], @@ -276,7 +277,7 @@ class _UserListViewState extends State itemCount: items.isNotEmpty ? items.length + 1 : items.length, separatorBuilder: (_, index) { if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, index); + return widget.separatorBuilder!(context, index); } return _separatorBuilder(context, index); }, @@ -296,7 +297,7 @@ class _UserListViewState extends State ); return LazyLoadScrollView( - onEndOfPage: () => _userListController.paginateData(), + onEndOfPage: () => _userListController.paginateData!(), child: child, ); } @@ -329,10 +330,10 @@ class _UserListViewState extends State return Container( key: ValueKey('USER-${user.id}'), child: widget.userItemBuilder != null - ? widget.userItemBuilder(context, user, selected) + ? widget.userItemBuilder!(context, user, selected) : UserItem( user: user, - onTap: (user) => widget.onUserTap(user, widget.userWidget), + onTap: (user) => widget.onUserTap!(user, widget.userWidget), onLongPress: widget.onUserLongPress, onImageTap: widget.onImageTap, selected: selected, @@ -356,7 +357,7 @@ class _UserListViewState extends State return Container( key: ValueKey('USER-${user.id}'), child: widget.userItemBuilder != null - ? widget.userItemBuilder(context, user, selected) + ? widget.userItemBuilder!(context, user, selected) : Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, @@ -374,7 +375,7 @@ class _UserListViewState extends State width: 12, ), onTap: (user) => - widget.onUserTap(user, widget.userWidget), + widget.onUserTap!(user, widget.userWidget), onLongPress: widget.onUserLongPress, ), SizedBox(height: 4), @@ -424,7 +425,7 @@ class _UserListViewState extends State height: 100, padding: EdgeInsets.all(32), child: Center( - child: snapshot.data ? CircularProgressIndicator() : Container(), + child: snapshot.data! ? CircularProgressIndicator() : Container(), ), ); }); diff --git a/packages/stream_chat_flutter/lib/src/user_reaction_display.dart b/packages/stream_chat_flutter/lib/src/user_reaction_display.dart index af2797ca..492baef5 100644 --- a/packages/stream_chat_flutter/lib/src/user_reaction_display.dart +++ b/packages/stream_chat_flutter/lib/src/user_reaction_display.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class UserReactionDisplay extends StatelessWidget { const UserReactionDisplay({ - Key key, - @required this.reactionToEmoji, - @required this.message, + Key? key, + required this.reactionToEmoji, + required this.message, this.size = 30, }) : super(key: key); @@ -23,12 +23,13 @@ class UserReactionDisplay extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: reactionToEmoji.keys.map((reactionType) { - var firstUserReaction = message.latestReactions.firstWhere( - (element) => element.type == reactionType, orElse: () { - return null; - }); + var firstUserReaction = message.latestReactions! + .firstWhere((element) => element.type == reactionType, + orElse: () { + return null; + } as Reaction Function()?); - if (firstUserReaction == null) { + if (firstUserReaction.user == null) { return IconButton( iconSize: size, icon: Container(), @@ -39,7 +40,7 @@ class UserReactionDisplay extends StatelessWidget { return IconButton( iconSize: size, icon: UserAvatar( - user: firstUserReaction.user, + user: firstUserReaction.user!, constraints: BoxConstraints( maxHeight: size - 5, maxWidth: size - 5, diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 8950a77c..ebb2a22e 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -7,8 +7,8 @@ import 'package:url_launcher/url_launcher.dart'; import '../stream_chat_flutter.dart'; import 'stream_svg_icon.dart'; -Future launchURL(BuildContext context, String url) async { - if (await canLaunch(url)) { +Future launchURL(BuildContext context, String? url) async { + if (url != null && await canLaunch(url)) { await launch(url); } else { // ignore: deprecated_member_use @@ -20,13 +20,13 @@ Future launchURL(BuildContext context, String url) async { } } -Future showConfirmationDialog( +Future showConfirmationDialog( BuildContext context, { - String title, - Widget icon, - String question, - String okText, - String cancelText, + String? title, + Widget? icon, + String? question, + String? okText, + String? cancelText, }) { return showModalBottomSheet( backgroundColor: StreamChatTheme.of(context).colorTheme.white, @@ -46,17 +46,17 @@ Future showConfirmationDialog( if (icon != null) icon, SizedBox(height: 26.0), Text( - title, + title!, style: StreamChatTheme.of(context).textTheme.headlineBold, ), SizedBox(height: 7.0), Text( - question, + question!, textAlign: TextAlign.center, ), SizedBox(height: 36.0), Container( - color: effect.color.withOpacity(effect.alpha ?? 1), + color: effect.color!.withOpacity(effect.alpha ?? 1), height: 1, ), Row( @@ -69,7 +69,7 @@ Future showConfirmationDialog( Navigator.of(context).pop(false); }, child: Text( - cancelText, + cancelText!, style: StreamChatTheme.of(context) .textTheme .bodyBold @@ -90,7 +90,7 @@ Future showConfirmationDialog( Navigator.pop(context, true); }, child: Text( - okText, + okText!, style: StreamChatTheme.of(context) .textTheme .bodyBold @@ -110,17 +110,17 @@ Future showConfirmationDialog( }); } -Future showInfoDialog( +Future showInfoDialog( BuildContext context, { - String title, - Widget icon, - String details, - String okText, - StreamChatThemeData theme, + String? title, + Widget? icon, + String? details, + String? okText, + StreamChatThemeData? theme, }) { return showModalBottomSheet( - backgroundColor: theme?.colorTheme?.white ?? - StreamChatTheme.of(context).colorTheme.white, + backgroundColor: + theme?.colorTheme.white ?? StreamChatTheme.of(context).colorTheme.white, context: context, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -140,19 +140,19 @@ Future showInfoDialog( height: 26.0, ), Text( - title, - style: theme?.textTheme?.headlineBold ?? + title!, + style: theme?.textTheme.headlineBold ?? StreamChatTheme.of(context).textTheme.headlineBold, ), SizedBox( height: 7.0, ), - Text(details), + Text(details!), SizedBox( height: 36.0, ), Container( - color: theme?.colorTheme?.black?.withOpacity(.08) ?? + color: theme?.colorTheme.black.withOpacity(.08) ?? StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), height: 1.0, ), @@ -162,9 +162,9 @@ Future showInfoDialog( Navigator.of(context).pop(); }, child: Text( - okText, + okText!, style: TextStyle( - color: theme?.colorTheme?.black?.withOpacity(0.5) ?? + color: theme?.colorTheme.black.withOpacity(0.5) ?? StreamChatTheme.of(context).colorTheme.accentBlue, fontWeight: FontWeight.w400, ), @@ -183,7 +183,7 @@ String getRandomPicUrl(User user) => 'https://getstream.io/random_png/?id=${user.id}&name=${user.name}'; /// Get websiteName from [hostName] -String getWebsiteName(String hostName) { +String? getWebsiteName(String hostName) { switch (hostName) { case 'reddit': return 'Reddit'; @@ -292,62 +292,44 @@ String fileSize(dynamic size, [int round = 2]) { } /// -StreamSvgIcon getFileTypeImage(String type) { +StreamSvgIcon getFileTypeImage(String? type) { switch (type) { case '7z': return StreamSvgIcon.filetype7z(); - break; case 'csv': return StreamSvgIcon.filetypeCsv(); - break; case 'doc': return StreamSvgIcon.filetypeDoc(); - break; case 'docx': return StreamSvgIcon.filetypeDocx(); - break; case 'html': return StreamSvgIcon.filetypeHtml(); - break; case 'md': return StreamSvgIcon.filetypeMd(); - break; case 'odt': return StreamSvgIcon.filetypeOdt(); - break; case 'pdf': return StreamSvgIcon.filetypePdf(); - break; case 'ppt': return StreamSvgIcon.filetypePpt(); - break; case 'pptx': return StreamSvgIcon.filetypePptx(); - break; case 'rar': return StreamSvgIcon.filetypeRar(); - break; case 'rtf': return StreamSvgIcon.filetypeRtf(); - break; case 'tar': return StreamSvgIcon.filetypeTar(); - break; case 'txt': return StreamSvgIcon.filetypeTxt(); - break; case 'xls': return StreamSvgIcon.filetypeXls(); - break; case 'xlsx': return StreamSvgIcon.filetypeXlsx(); - break; case 'zip': return StreamSvgIcon.filetypeZip(); - break; default: return StreamSvgIcon.filetypeGeneric(); - break; } } diff --git a/packages/stream_chat_flutter/lib/src/video_service.dart b/packages/stream_chat_flutter/lib/src/video_service.dart index 30d06c55..a4d0d1ff 100644 --- a/packages/stream_chat_flutter/lib/src/video_service.dart +++ b/packages/stream_chat_flutter/lib/src/video_service.dart @@ -4,7 +4,6 @@ import 'dart:typed_data'; import 'package:synchronized/synchronized.dart'; import 'package:video_compress/video_compress.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; -import 'package:meta/meta.dart'; class IVideoService { static final IVideoService instance = IVideoService._(); @@ -27,10 +26,10 @@ class IVideoService { /// ); /// debugPrint(info.toJson()); /// ``` - Future compressVideo(String path) async { + Future compressVideo(String? path) async { return _lock.synchronized(() { return VideoCompress.compressVideo( - path, + path!, ); }); } @@ -39,8 +38,8 @@ class IVideoService { /// The video can be a local video file, or an URL repreents iOS or Android native supported video format. /// Speicify the maximum height or width for the thumbnail or 0 for same resolution as the original video. /// The lower quality value creates lower quality of the thumbnail image, but it gets ignored for PNG format. - Future generateVideoThumbnail({ - @required String video, + Future generateVideoThumbnail({ + required String video, ImageFormat imageFormat = ImageFormat.PNG, int maxHeight = 0, int maxWidth = 0, diff --git a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart index 80607b8e..df8373eb 100644 --- a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart +++ b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart @@ -9,17 +9,17 @@ import 'stream_svg_icon.dart'; import 'video_service.dart'; class VideoThumbnailImage extends StatefulWidget { - final String video; - final double width; - final double height; - final BoxFit fit; + final String? video; + final double? width; + final double? height; + final BoxFit? fit; final ImageFormat format; - final Widget Function(BuildContext, Object) errorBuilder; - final WidgetBuilder placeholderBuilder; + final Widget Function(BuildContext, Object?)? errorBuilder; + final WidgetBuilder? placeholderBuilder; const VideoThumbnailImage({ - Key key, - @required this.video, + Key? key, + required this.video, this.width, this.height, this.fit, @@ -33,12 +33,12 @@ class VideoThumbnailImage extends StatefulWidget { } class _VideoThumbnailImageState extends State { - Future thumbnailFuture; + late Future thumbnailFuture; @override void initState() { thumbnailFuture = VideoService.generateVideoThumbnail( - video: widget.video, + video: widget.video!, imageFormat: widget.format, ); super.initState(); @@ -48,7 +48,7 @@ class _VideoThumbnailImageState extends State { void didUpdateWidget(covariant VideoThumbnailImage oldWidget) { if (oldWidget.video != widget.video || oldWidget.format != widget.format) { thumbnailFuture = VideoService.generateVideoThumbnail( - video: widget.video, + video: widget.video!, imageFormat: widget.format, ); } @@ -57,13 +57,13 @@ class _VideoThumbnailImageState extends State { @override Widget build(BuildContext context) { - return FutureBuilder( + return FutureBuilder( future: thumbnailFuture, builder: (context, snapshot) { return AnimatedSwitcher( duration: const Duration(milliseconds: 350), child: Builder( - key: ValueKey>(snapshot), + key: ValueKey>(snapshot), builder: (_) { if (snapshot.hasError) { return widget.errorBuilder?.call(context, snapshot.error) ?? @@ -90,7 +90,7 @@ class _VideoThumbnailImageState extends State { ); } return Image.memory( - snapshot.data, + snapshot.data!, fit: widget.fit, height: widget.height, width: widget.width, diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 4114051e..942df52d 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -8,7 +8,7 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues publish_to: none environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: @@ -19,12 +19,12 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 jiffy: ^4.1.0 flutter_svg: ^0.21.0+1 - flutter_portal: ^0.4.0-nullsafety.0 + flutter_portal: ^0.4.0 cached_network_image: ^3.0.0 shimmer: ^2.0.0-nullsafety.0 flutter_markdown: ^0.6.1 url_launcher: ^6.0.3 - emojis: + emojis: git: git@github.com:Parkar99/emojis.git video_player: ^2.1.1 chewie: ^1.0.0 @@ -36,7 +36,7 @@ dependencies: http_parser: ^4.0.0 meta: ^1.3.0 lottie: ^1.0.1 - substring_highlight: + substring_highlight: git: git@github.com:ArturAntin/substring_highlight.git flutter_slidable: ^0.6.0-nullsafety.0 image_gallery_saver: ^1.6.9 @@ -48,6 +48,7 @@ dependencies: characters: ^1.1.0 path_provider: ^2.0.1 video_thumbnail: ^0.3.3 + collection: ^1.15.0 dependency_overrides: stream_chat: @@ -66,7 +67,7 @@ flutter: dev_dependencies: flutter_test: sdk: flutter - mockito: ^5.0.3 + mocktail: ^0.1.2 pedantic: ^1.11.0 golden_toolkit: ^0.9.0 diff --git a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart index 1ef02cd9..361d7cfc 100644 --- a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart @@ -2,19 +2,19 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/attachment_actions_modal.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; class MockAttachmentDownloader extends Mock { - ProgressCallback progressCallback; + ProgressCallback? progressCallback; Completer completer = Completer(); Future call( Attachment attachment, { - ProgressCallback progressCallback, + ProgressCallback? progressCallback, }) { this.progressCallback = progressCallback; return completer.future; @@ -22,17 +22,22 @@ class MockAttachmentDownloader extends Mock { } void main() { + setUpAll(() { + registerFallbackValue(MaterialPageRoute(builder: (context) => SizedBox())); + registerFallbackValue(Message()); + }); + testWidgets( 'it should show all the actions', (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( theme: themeData, @@ -70,11 +75,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id2')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id2')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( theme: themeData, @@ -112,11 +117,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( theme: themeData, @@ -153,11 +158,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); final mockObserver = MockNavigatorObserver(); @@ -190,7 +195,7 @@ void main() { ), ); await tester.tap(find.text('Reply')); - verify(mockObserver.didPop(any, any)); + verify(() => mockObserver.didPop(any(), any())); }, ); @@ -200,11 +205,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); final onShowMessage = MockVoidCallback(); await tester.pumpWidget( @@ -234,7 +239,7 @@ void main() { ), ); await tester.tap(find.text('Show in Chat')); - verify(onShowMessage.call()).called(1); + verify(() => onShowMessage.call()).called(1); }, ); @@ -245,11 +250,11 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(mockChannel.updateMessage(any)).thenAnswer((_) { - return; + when(() => mockChannel.updateMessage(any())).thenAnswer((_) async { + return UpdateMessageResponse(); }); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final message = Message( text: 'test', @@ -287,11 +292,11 @@ void main() { ), ); await tester.tap(find.text('Delete')); - verify(mockChannel.updateMessage(message.copyWith( - attachments: [ - message.attachments[1], - ], - ))).called(1); + verify(() => mockChannel.updateMessage(message.copyWith( + attachments: [ + message.attachments[1], + ], + ))).called(1); }, ); @@ -302,11 +307,11 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(mockChannel.updateMessage(any)).thenAnswer((_) { - return; + when(() => mockChannel.updateMessage(any())).thenAnswer((_) async { + return UpdateMessageResponse(); }); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final message = Message( text: 'test', @@ -340,9 +345,9 @@ void main() { ), ); await tester.tap(find.text('Delete')); - verify(mockChannel.updateMessage(message.copyWith( - attachments: [], - ))).called(1); + verify(() => mockChannel.updateMessage(message.copyWith( + attachments: [], + ))).called(1); }, ); @@ -353,11 +358,11 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(mockChannel.deleteMessage(any)).thenAnswer((_) { - return; + when(() => mockChannel.deleteMessage(any())).thenAnswer((_) async { + return EmptyResponse(); }); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final message = Message( user: User( @@ -390,7 +395,7 @@ void main() { ), ); await tester.tap(find.text('Delete')); - verify(mockChannel.deleteMessage(message)).called(1); + verify(() => mockChannel.deleteMessage(message)).called(1); }, ); @@ -400,8 +405,8 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final imageDownloader = MockAttachmentDownloader(); @@ -435,15 +440,15 @@ void main() { await tester.tap(find.text('Save Image')); - imageDownloader.progressCallback(0, 100); + imageDownloader.progressCallback!(0, 100); await tester.pump(); expect(find.text('0%'), findsOneWidget); - imageDownloader.progressCallback(50, 100); + imageDownloader.progressCallback!(50, 100); await tester.pump(); expect(find.text('50%'), findsOneWidget); - imageDownloader.progressCallback(100, 100); + imageDownloader.progressCallback!(100, 100); imageDownloader.completer.complete('path'); await tester.pump(); expect(find.byKey(Key('completedIcon')), findsOneWidget); @@ -457,8 +462,8 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final fileDownloader = MockAttachmentDownloader(); @@ -492,15 +497,15 @@ void main() { await tester.tap(find.text('Save Video')); - fileDownloader.progressCallback(0, 100); + fileDownloader.progressCallback!(0, 100); await tester.pump(); expect(find.text('0%'), findsOneWidget); - fileDownloader.progressCallback(50, 100); + fileDownloader.progressCallback!(50, 100); await tester.pump(); expect(find.text('50%'), findsOneWidget); - fileDownloader.progressCallback(100, 100); + fileDownloader.progressCallback!(100, 100); fileDownloader.completer.complete('path'); await tester.pump(); expect(find.byKey(Key('completedIcon')), findsOneWidget); diff --git a/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart b/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart index b547e80f..b1eb70e2 100644 --- a/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -12,10 +12,10 @@ void main() { final channel = MockChannel(); final channelState = MockChannelState(); - when(channel.state).thenReturn(channelState); + when(() => channel.state).thenReturn(channelState); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/src/back_button_test.dart b/packages/stream_chat_flutter/test/src/back_button_test.dart index 3ee88ed1..a9c5f724 100644 --- a/packages/stream_chat_flutter/test/src/back_button_test.dart +++ b/packages/stream_chat_flutter/test/src/back_button_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/back_button.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -19,7 +19,7 @@ void main() { return Material( child: Center( child: StreamChatTheme( - data: StreamChatThemeData.getDefaultTheme(theme), + data: StreamChatThemeData.fromTheme(theme), child: StreamBackButton(), ), ), @@ -51,7 +51,7 @@ void main() { home: Material( child: Center( child: StreamChatTheme( - data: StreamChatThemeData.getDefaultTheme(theme), + data: StreamChatThemeData.fromTheme(theme), child: StreamBackButton(), ), ), @@ -82,7 +82,7 @@ void main() { return Material( child: Center( child: StreamChatTheme( - data: StreamChatThemeData.getDefaultTheme(theme), + data: StreamChatThemeData.fromTheme(theme), child: StreamBackButton( onPressed: () => customCallbackWasCalled = true, ), @@ -121,7 +121,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); + when(() => client.state).thenReturn(clientState); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(0)); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/src/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel_header_test.dart index 1a5cb79d..c284cad5 100644 --- a/packages/stream_chat_flutter/test/src/channel_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_header_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/channel_info.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -16,28 +16,33 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), @@ -72,35 +77,38 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(client.wsConnectionStatusStream) + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(1)); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -131,36 +139,38 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(client.wsConnectionStatusStream) + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(1)); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -194,33 +204,38 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); + when(() => client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(1)); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -263,34 +278,35 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(client.wsConnectionStatusStream) + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); await tester.pumpWidget(MaterialApp( @@ -328,33 +344,38 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); + when(() => client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(1)); var backPressed = false; var imageTapped = false; diff --git a/packages/stream_chat_flutter/test/src/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel_image_test.dart index cea48d2f..1068bb52 100644 --- a/packages/stream_chat_flutter/test/src/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_image_test.dart @@ -1,7 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/group_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -16,15 +16,15 @@ void main() { final channel = MockChannel(); final channelState = MockChannelState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', 'image': 'imagetest', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', 'image': 'imagetest', }); @@ -55,17 +55,17 @@ void main() { final channel = MockChannel(); final channelState = MockChannelState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), @@ -80,7 +80,7 @@ void main() { ), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id2', user: User( @@ -95,7 +95,7 @@ void main() { user: User(id: 'user-id'), ) ]); - when(clientState.usersStream).thenAnswer((i) => Stream.value({ + when(() => clientState.usersStream).thenAnswer((i) => Stream.value({ 'user-id2': User( id: 'user-id2', extraData: { @@ -103,7 +103,7 @@ void main() { }, ), })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); @@ -133,17 +133,17 @@ void main() { final channel = MockChannel(); final channelState = MockChannelState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User( @@ -172,7 +172,7 @@ void main() { ), ), ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User( @@ -230,15 +230,15 @@ void main() { final channel = MockChannel(); final channelState = MockChannelState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', 'image': 'imagetest', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', 'image': 'imagetest', }); diff --git a/packages/stream_chat_flutter/test/src/channel_list_header_test.dart b/packages/stream_chat_flutter/test/src/channel_list_header_test.dart index 6fc4d428..f77ea04a 100644 --- a/packages/stream_chat_flutter/test/src/channel_list_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_list_header_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -12,9 +12,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(client.wsConnectionStatusStream) + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); await tester.pumpWidget( @@ -42,9 +42,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(client.wsConnectionStatusStream) + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); await tester.pumpWidget( @@ -71,9 +71,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(client.wsConnectionStatusStream) + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); await tester.pumpWidget( @@ -100,9 +100,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(client.wsConnectionStatusStream) + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); await tester.pumpWidget( @@ -140,9 +140,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(client.wsConnectionStatusStream) + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); var tapped = false; @@ -174,9 +174,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(client.wsConnectionStatusStream) + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); var tapped = 0; diff --git a/packages/stream_chat_flutter/test/src/channel_name_test.dart b/packages/stream_chat_flutter/test/src/channel_name_test.dart index bf6f7e55..586a0776 100644 --- a/packages/stream_chat_flutter/test/src/channel_name_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_name_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,40 +15,41 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(channelState.messages).thenReturn([ + when(() => channelState.messages).thenReturn([ Message( text: 'hello', user: User(id: 'other-user'), ) ]); - when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), diff --git a/packages/stream_chat_flutter/test/src/channel_preview_test.dart b/packages/stream_chat_flutter/test/src/channel_preview_test.dart index d8ed9f72..b913489e 100644 --- a/packages/stream_chat_flutter/test/src/channel_preview_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_preview_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,50 +15,55 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(channel.cid).thenReturn('cid'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => channel.cid).thenReturn('cid'); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test name', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test name', }); - when(clientState.channels).thenReturn({ - channel.cid: channel, + when(() => clientState.channels).thenReturn({ + channel.cid!: channel, }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(channelState.messages).thenReturn([ + when(() => channelState.messages).thenReturn([ Message( text: 'hello', user: User(id: 'other-user'), ) ]); - when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), ) ])); + when(() => channelState.typingEvents).thenReturn([]); + when(() => channelState.typingEventsStream) + .thenAnswer((_) => Stream.value([])); + await tester.pumpWidget(MaterialApp( home: StreamChat( client: client, diff --git a/packages/stream_chat_flutter/test/src/date_divider_test.dart b/packages/stream_chat_flutter/test/src/date_divider_test.dart index 4856a0d3..eb5e9b70 100644 --- a/packages/stream_chat_flutter/test/src/date_divider_test.dart +++ b/packages/stream_chat_flutter/test/src/date_divider_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -12,8 +12,8 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidget(MaterialApp( home: StreamChat( diff --git a/packages/stream_chat_flutter/test/src/deleted_message_test.dart b/packages/stream_chat_flutter/test/src/deleted_message_test.dart index bb93b28b..854c2492 100644 --- a/packages/stream_chat_flutter/test/src/deleted_message_test.dart +++ b/packages/stream_chat_flutter/test/src/deleted_message_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -13,8 +13,8 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -45,21 +45,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); final materialTheme = ThemeData.light(); @@ -98,21 +97,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); final materialTheme = ThemeData.dark(); @@ -151,21 +149,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); final materialTheme = ThemeData.light(); diff --git a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart index 55adf7d3..81c342eb 100644 --- a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart +++ b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:photo_view/photo_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -16,51 +16,52 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(channelState.messages).thenReturn([ + when(() => channelState.messages).thenReturn([ Message( text: 'hello', user: User(id: 'other-user'), ) ]); - when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), ) ])); - when(channelState.typingEvents).thenAnswer((i) => [ + when(() => channelState.typingEvents).thenAnswer((i) => [ User(id: 'other-user', extraData: {'name': 'demo'}) ]); - when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([ - User(id: 'other-user', extraData: {'name': 'demo'}), - User(id: 'other-user', extraData: {'name': 'demo'}), - ])); + when(() => channelState.typingEventsStream) + .thenAnswer((i) => Stream.value([ + User(id: 'other-user', extraData: {'name': 'demo'}), + User(id: 'other-user', extraData: {'name': 'demo'}), + ])); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -78,7 +79,6 @@ void main() { message: Message( createdAt: DateTime.now(), ), - sentAt: DateTime.now(), ), ), ), diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_2.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_2.png index a05377cbb9c8e5710088aaed6bbaf2b12d8c0094..ea03e3e62621f1cafc5de489c167bc22968af59f 100644 GIT binary patch delta 2445 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 LNkvXXu0mjf&_V!M delta 2665 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 X8D7eUbfX*R00000NkvXXu0mjfZQ6~m diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png index 9ee602b39480ddd7d8406ab5b4b0dc1781d4394a..cb7e48944232484a7034c59383491205874f5641 100644 GIT binary patch delta 1632 zcmV-m2A}zl533K5L4Q?AL_t(|obBB`kQ-+m$MN6uw!6|Dkv!mI#tfaBhNU0@g8uWX`&MdG>iIR+5e5 zd-?CJeA&C-r}Id9?y>wm?LN}8mI4q#bV~ZXCqVteU`T|)kdtQyUXu_3LVxT!2Gzzw z8v~^blu}Uoa&#F(K#UPF)(CZtQ2XsE)M@%Rd22*`_qv41>6j1hTJ05M`T z8UsM{i)PlcKwUd|FiJHixM&VTF$Cmg+4a0GoaJ-0jK(EdFISV|V3y0~G|k|wMNw7j znAe3=lqHi|LjL= z8^7)RV~i-P3d7+Lz<=ZCvQI?8Xk!7Tkhla!>sG6wmv*+XwRII=ef3rGV^8DS?VA|A zvW=Y|d}l4=pLPC~_b^%`am_1r-_B)(!59EQnq}~H-RcMK?CbylW)B~>di+VgaO)-j zV1EB!YZFBp%Q+@-_M006Fj z?kW!EbI}jFaew_a+_?VQk+*-idl$dC^R{TrC%ydR*YWZjuOE5)M{m7@KmG9Mt;g?r z{+q)g_W!*P04V1FZ9T8&?YZnbcxh+**e4qRX0uuA@vq&!d2B2I@aX8SGP>50V3G|CEeo;0np{v)wvexmtp1;-__2Q7;=6}$Bo`k_PN3R%yXj9bkIDeeI zz4j-m<1cNWc%P5&{J!=0UC&=Bwb~yOJKB!)yvTwH&C00q9#*g%Eb`+&X!~&YE*?zU zuR5yP!Lhf0^wv9=?(Vf7zw7y1V^AzUDp)?JeIW`aESAzmp2G-&`TGgKeII=AXUQ}^ z;p02MJAd}}y; z0DsuUCUDP9;Ez3hnaRd*2&FaZx`y{25x88JnV<>97=suB9zA)PNe@qQpVrBRY}(lW z+oPJe1Zmc6;e=8!m*i5}I)vsN&wCVk&I{R&AF0`+YOL$!=H|7Cu{i`SR+bk*cfmYi zbuqp+P`G3l<0B#lUW`u!fd4-X2{9NFVUfd#f00uKV_gEJ*cJiyq&pbvu<^ne08kvx zQ5?<@qz zPBJ`wP)eM`{Lvi7y48K@;(O*Q$-3qwcRU_LK>;yhFvt)?gs*EDV^CEk4)a5V+N1W> ebDcDupZ@{gV3cVYIYRIN000050M~1>J$l(Hpn+fn~&f=ac;dErwFWV;s6G06&95s%k_$!%b9ah3`t$ALT-Fq2CG6_YLjGLtR=GLtR= zB7b7Hu~BU-v@uZ1Kq&>KccaS~0%DAau|a4WgvMX>kb0-k*Y{Q{1!EnoO#r2O+2r8G z2=6O2z6Rp;_2yB1k`nnsw`2}Rr`Y&QCG#a z*yC&*j7?G)n*adPG((=}Fj~Vm4SdreE`MIcU7)pwQVP}>SYyzHfZ1$@vMc}qjj!OV z@`^{fnT>+6Nd{vb0N~(Y3}-E>x<>7X|4!WUzAcvlXee0ot_|sFz7u^r|FBaZgm(Mk8dJ|fTAoJ%YO^R z7*UiZ5F-u_#sJX1(acsRs9PrwMyd7$7wusvhJd1~`X1M{vwV$~(YPcVF~5mj9y&vO7zUVqCW5e1`-1(ZVK5*V#J&4%tC-NE7EEqwpYH^tAr zjQZ7kX!c)6`JG>EW&4|szxEzRYb37SQxEN2Mi`6%0Hj$4-!z?m;nC3%0APB0+UfZh zsc*jr0MPRXTiJdeA-2YH?Ptv_|Z3_4X$}eh*pN&afU%Cdkqhhlhs% zfX_bvT)e&K&A#`0M7IwBFsH|eZXeD5>i_`z-w!w5eqZB{F|KAv3d5Qyi7wow`8NOn zx4v}?XS12;7rpcLTX^T~w|~z4`N#K<@y9=XAll~Bio3r?arCQmf1W&eANfCj*?IeY zkN-xV{n~5amW@itzq0-xb5nhfUu%tKaY*lSXunFrVA`Ws41YniC~A3}U+0rg z{w*2GGn)N3E_{spqWj^l@9`_8R_kM8Ps@>B7FjT%of*~M!wRN@WwHK)wvX>09Qzb1i}34f_L9XAN@lzjIVHC z{CVxq(@+1f(f0cuzke|X^&*(i@p{%(F#vvh*$2lMU~PgX1dOr_N~udNj+#s+m`+cj z09a>NQ`5=h5&rVmzim9FHmAq!g*pYmWY0{k^ZUQqcuL*p_?@-TO5yDBS-XUDU~6N< z>tU=*VO)YV&2Zz!4a|#z%Nj08DTPs%;q2@TWm%x9EBJa?>wjXz(`xT4AV!pB0q_0l ziX~kvO%izT+a;Te1>!>^zYeol&1aQ zne62`l-6jP24~)TL}0gWGeH}QF$OUNJbwB%lL4OOKChP>*|f3GtWix|f;4NFa6&1V z9l2Gu386j5^B!eU@J6=vTWZ&+8tVqRxOppLY!5+;ndNoRUocNu-Hfjd6n5-pd_=^+ zoAHSN@c)M)A(KG?ACoQtGLtR=GLtR=34d~_V601^6x$-eUi1KC9rpIe0Dy8nLph%z z#@+Z1NBXJ@zhG-`|0Z0LuKpZjU^qMM!e9=*dHaRmN4*TEn4N8FZ1gp>_Qb@w1p1us zYg9@j9qnypQ1tb6ZZIy(paleFy>OOfU3-!{9*?1*fEY0vWr!idHw}z2sOt*zVvf*wG`@bRgQlzV Ye}GVwHO8lAEC2ui07*qoM6N<$g0mwXC;$Ke diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png index bd4f4710ddd8c6aa58418236309423ed551773f0..8caefa4f674cb29e2070c5e2743698fa0de5b275 100644 GIT binary patch delta 1791 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 Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); @@ -37,7 +37,9 @@ void main() { child: WillPopScope( onWillPop: () async => false, child: Scaffold( - body: ImageFooter(), + body: ImageFooter( + message: Message(), + ), ), ), ), diff --git a/packages/stream_chat_flutter/test/src/info_tile_test.dart b/packages/stream_chat_flutter/test/src/info_tile_test.dart index d304d48e..091c64c7 100644 --- a/packages/stream_chat_flutter/test/src/info_tile_test.dart +++ b/packages/stream_chat_flutter/test/src/info_tile_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -13,8 +13,8 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -43,8 +43,8 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidget(MaterialApp( home: StreamChat( diff --git a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart index c744a6bb..8a6e8c33 100644 --- a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart @@ -1,23 +1,28 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/message_actions_modal.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; void main() { + setUpAll(() { + registerFallbackValue(MaterialPageRoute(builder: (context) => SizedBox())); + registerFallbackValue(Message()); + }); + testWidgets( 'it should show the all actions', (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( theme: themeData, @@ -55,11 +60,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( theme: themeData, @@ -102,11 +107,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); var tapped = false; await tester.pumpWidget( @@ -156,11 +161,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); var tapped = false; @@ -173,7 +178,7 @@ void main() { child: Container( child: MessageActionsModal( onReplyTap: (m) { - return tapped = true; + tapped = true; }, message: Message( text: 'test', @@ -201,11 +206,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); var tapped = false; @@ -218,7 +223,7 @@ void main() { child: Container( child: MessageActionsModal( onThreadReplyTap: (m) { - return tapped = true; + tapped = true; }, message: Message( text: 'test', @@ -247,12 +252,11 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -298,12 +302,11 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -352,12 +355,11 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); var tapped = false; @@ -404,12 +406,13 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.sendMessage(any())) + .thenAnswer((_) async => SendMessageResponse()); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -443,7 +446,7 @@ void main() { await tester.tap(find.text('Resend')); - verify(channel.sendMessage(any)).called(1); + verify(() => channel.sendMessage(any())).called(1); }, ); @@ -454,12 +457,13 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.updateMessage(any())) + .thenAnswer((_) async => UpdateMessageResponse()); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -493,7 +497,7 @@ void main() { await tester.tap(find.text('Resend Edited Message')); - verify(channel.updateMessage(any)).called(1); + verify(() => channel.updateMessage(any())).called(1); }, ); @@ -504,12 +508,11 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -549,7 +552,7 @@ void main() { await tester.tap(find.text('FLAG')); await tester.pumpAndSettle(); - verify(client.flagMessage('testid')).called(1); + verify(() => client.flagMessage('testid')).called(1); }, ); @@ -560,16 +563,15 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(client.flagMessage(any)).thenThrow(ApiError( + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.flagMessage(any())).thenThrow(ApiError( '{}', 500, )); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -620,16 +622,15 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(client.flagMessage(any)).thenThrow(ApiError( + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.flagMessage(any())).thenThrow(ApiError( '{"code":4}', 400, )); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -680,12 +681,11 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -725,7 +725,7 @@ void main() { await tester.tap(find.text('DELETE')); await tester.pumpAndSettle(); - verify(channel.deleteMessage(any)).called(1); + verify(() => channel.deleteMessage(any())).called(1); }, ); @@ -736,16 +736,15 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.deleteMessage(any)).thenThrow(ApiError( + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.deleteMessage(any())).thenThrow(ApiError( '{}', 500, )); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/src/message_input_test.dart b/packages/stream_chat_flutter/test/src/message_input_test.dart index 38e80565..39e7ae3f 100644 --- a/packages/stream_chat_flutter/test/src/message_input_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,51 +15,52 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(channelState.messages).thenReturn([ + when(() => channelState.messages).thenReturn([ Message( text: 'hello', user: User(id: 'other-user'), ) ]); - when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), ) ])); - when(channelState.typingEvents).thenAnswer((i) => [ + when(() => channelState.typingEvents).thenAnswer((i) => [ User(id: 'other-user', extraData: {'name': 'demo'}) ]); - when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([ - User(id: 'other-user', extraData: {'name': 'demo'}), - User(id: 'other-user', extraData: {'name': 'demo'}), - ])); + when(() => channelState.typingEventsStream) + .thenAnswer((i) => Stream.value([ + User(id: 'other-user', extraData: {'name': 'demo'}), + User(id: 'other-user', extraData: {'name': 'demo'}), + ])); await tester.pumpWidget(MaterialApp( home: StreamChat( diff --git a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart index 8745cb45..e1136ac5 100644 --- a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -15,10 +15,10 @@ void main() { final clientState = MockClientState(); final themeData = ThemeData(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); final message = Message( id: 'test', @@ -75,10 +75,10 @@ void main() { final clientState = MockClientState(); final themeData = ThemeData(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); final message = Message( id: 'test', diff --git a/packages/stream_chat_flutter/test/src/message_text_test.dart b/packages/stream_chat_flutter/test/src/message_text_test.dart index 1d2ac8be..a8aba86a 100644 --- a/packages/stream_chat_flutter/test/src/message_text_test.dart +++ b/packages/stream_chat_flutter/test/src/message_text_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -16,19 +16,19 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); diff --git a/packages/stream_chat_flutter/test/src/mocks.dart b/packages/stream_chat_flutter/test/src/mocks.dart index 50f7ee6a..842c1c2f 100644 --- a/packages/stream_chat_flutter/test/src/mocks.dart +++ b/packages/stream_chat_flutter/test/src/mocks.dart @@ -1,12 +1,20 @@ import 'package:flutter/material.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class MockClient extends Mock implements StreamChatClient {} class MockClientState extends Mock implements ClientState {} -class MockChannel extends Mock implements Channel {} +class MockChannel extends Mock implements Channel { + @override + Future get initialized async => true; + + @override + Future keyStroke([String? parentId]) async { + return; + } +} class MockChannelState extends Mock implements ChannelClientState {} diff --git a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart index d4a3b6a6..fd5d31ce 100644 --- a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart +++ b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -39,10 +39,10 @@ void main() { final clientState = MockClientState(); final themeData = ThemeData.light(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); - final theme = StreamChatThemeData.getDefaultTheme(themeData); + final theme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidgetBuilder( StreamChat( client: client, @@ -55,9 +55,9 @@ void main() { user: User(id: 'test'), ), ], - borderColor: theme.ownMessageTheme.reactionsBorderColor, - backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, - maskColor: theme.ownMessageTheme.reactionsMaskColor, + borderColor: theme.ownMessageTheme.reactionsBorderColor!, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, + maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), ), @@ -73,15 +73,15 @@ void main() { final client = MockClient(); final clientState = MockClientState(); final themeData = ThemeData.dark(); - final theme = StreamChatThemeData.getDefaultTheme(themeData); + final theme = StreamChatThemeData.fromTheme(themeData); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidgetBuilder( StreamChat( client: client, - streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), + streamChatThemeData: StreamChatThemeData.fromTheme(themeData), child: Container( color: Colors.black, child: ReactionBubble( @@ -91,9 +91,9 @@ void main() { user: User(id: 'test'), ), ], - borderColor: theme.ownMessageTheme.reactionsBorderColor, - backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, - maskColor: theme.ownMessageTheme.reactionsMaskColor, + borderColor: theme.ownMessageTheme.reactionsBorderColor!, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, + maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), ), @@ -109,15 +109,15 @@ void main() { final client = MockClient(); final clientState = MockClientState(); final themeData = ThemeData.light(); - final theme = StreamChatThemeData.getDefaultTheme(themeData); + final theme = StreamChatThemeData.fromTheme(themeData); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidgetBuilder( StreamChat( client: client, - streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), + streamChatThemeData: StreamChatThemeData.fromTheme(themeData), child: Container( color: Colors.black, child: ReactionBubble( @@ -135,9 +135,9 @@ void main() { user: User(id: 'test'), ), ], - borderColor: theme.ownMessageTheme.reactionsBorderColor, - backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, - maskColor: theme.ownMessageTheme.reactionsMaskColor, + borderColor: theme.ownMessageTheme.reactionsBorderColor!, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, + maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), ), @@ -153,15 +153,15 @@ void main() { final client = MockClient(); final clientState = MockClientState(); final themeData = ThemeData.dark(); - final theme = StreamChatThemeData.getDefaultTheme(themeData); + final theme = StreamChatThemeData.fromTheme(themeData); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidgetBuilder( StreamChat( client: client, - streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), + streamChatThemeData: StreamChatThemeData.fromTheme(themeData), child: Container( color: Colors.black, child: ReactionBubble( @@ -179,9 +179,9 @@ void main() { user: User(id: 'test'), ), ], - borderColor: theme.ownMessageTheme.reactionsBorderColor, - backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, - maskColor: theme.ownMessageTheme.reactionsMaskColor, + borderColor: theme.ownMessageTheme.reactionsBorderColor!, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, + maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), ), @@ -198,13 +198,13 @@ void main() { final clientState = MockClientState(); final themeData = ThemeData(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidgetBuilder( StreamChat( client: client, - streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), + streamChatThemeData: StreamChatThemeData.fromTheme(themeData), child: Container( child: ReactionBubble( reactions: [ diff --git a/packages/stream_chat_flutter/test/src/simple_frame.dart b/packages/stream_chat_flutter/test/src/simple_frame.dart index c472df85..18d7ae55 100644 --- a/packages/stream_chat_flutter/test/src/simple_frame.dart +++ b/packages/stream_chat_flutter/test/src/simple_frame.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class SimpleFrame extends StatelessWidget { final Widget child; - const SimpleFrame({Key key, @required this.child}) : super(key: key); + const SimpleFrame({Key? key, required this.child}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/packages/stream_chat_flutter/test/src/system_message_test.dart b/packages/stream_chat_flutter/test/src/system_message_test.dart index 04967c39..53741864 100644 --- a/packages/stream_chat_flutter/test/src/system_message_test.dart +++ b/packages/stream_chat_flutter/test/src/system_message_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -16,20 +16,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); var tapped = false; @@ -67,21 +67,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); await tester.pumpWidgetBuilder( @@ -119,21 +118,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); await tester.pumpWidgetBuilder( diff --git a/packages/stream_chat_flutter/test/src/thread_header_test.dart b/packages/stream_chat_flutter/test/src/thread_header_test.dart index 25987e31..9e64abb7 100644 --- a/packages/stream_chat_flutter/test/src/thread_header_test.dart +++ b/packages/stream_chat_flutter/test/src/thread_header_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,34 +15,38 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); + when(() => client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(1)); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -75,29 +79,29 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), diff --git a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart index bbdbeb3e..8698de91 100644 --- a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,51 +15,52 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(channelState.messages).thenReturn([ + when(() => channelState.messages).thenReturn([ Message( text: 'hello', user: User(id: 'other-user'), ) ]); - when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), ) ])); - when(channelState.typingEvents).thenAnswer((i) => [ + when(() => channelState.typingEvents).thenAnswer((i) => [ User(id: 'other-user', extraData: {'name': 'demo'}) ]); - when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([ - User(id: 'other-user', extraData: {'name': 'demo'}), - User(id: 'other-user', extraData: {'name': 'demo'}), - ])); + when(() => channelState.typingEventsStream) + .thenAnswer((i) => Stream.value([ + User(id: 'other-user', extraData: {'name': 'demo'}), + User(id: 'other-user', extraData: {'name': 'demo'}), + ])); await tester.pumpWidget(MaterialApp( home: StreamChat( diff --git a/packages/stream_chat_flutter/test/src/unread_indicator_test.dart b/packages/stream_chat_flutter/test/src/unread_indicator_test.dart index 87018263..9b1b5e1e 100644 --- a/packages/stream_chat_flutter/test/src/unread_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/unread_indicator_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,20 +15,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); await tester.pumpWidget(MaterialApp( @@ -54,19 +54,20 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); - when(channel.cid).thenReturn('cid'); + when(() => channel.cid).thenReturn('cid'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(clientState.channels).thenReturn({ - channel.cid: channel, + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.channels).thenReturn({ + channel.cid!: channel, }); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channelState.unreadCount).thenReturn(0); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(0)); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channelState.unreadCount).thenReturn(0); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(0)); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -92,20 +93,21 @@ void main() { final client = MockClient(); final clientState = MockClientState(); final channel = MockChannel(); - when(channel.cid).thenReturn('cid'); + when(() => channel.cid).thenReturn('cid'); final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(clientState.channels).thenReturn({ - channel.cid: channel, + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.channels).thenReturn({ + channel.cid!: channel, }); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channelState.unreadCount).thenReturn(100); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(100)); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channelState.unreadCount).thenReturn(100); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(100)); await tester.pumpWidget(MaterialApp( home: StreamChat( diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart index 5bff63dc..2cacdb91 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart @@ -6,7 +6,6 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/entity/channel_queries.dart'; import 'package:stream_chat_persistence/src/entity/channels.dart'; import 'package:stream_chat_persistence/src/entity/users.dart'; - import 'package:stream_chat_persistence/src/mapper/mapper.dart'; part 'channel_query_dao.g.dart'; @@ -93,7 +92,7 @@ class ChannelQueryDao extends DatabaseAccessor final possibleSortingFields = cachedChannels.fold>( ChannelModel.topLevelFields, (previousValue, element) { - final extraData = element.extraData ?? {}; + final extraData = element.extraData; return {...previousValue, ...extraData.keys}.toList(); }); diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index b85857bb..8220ee4e 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -43,19 +43,21 @@ class ChannelEntity extends DataClass implements Insertable { /// Map of custom channel extraData final Map? extraData; - ChannelEntity( - {required this.id, - required this.type, - required this.cid, - required this.config, - required this.frozen, - this.lastMessageAt, - required this.createdAt, - required this.updatedAt, - this.deletedAt, - required this.memberCount, - this.createdById, - this.extraData}); + + ChannelEntity({ + required this.id, + required this.type, + required this.cid, + required this.config, + required this.frozen, + this.lastMessageAt, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.memberCount, + this.createdById, + this.extraData, + }); factory ChannelEntity.fromData( Map data, GeneratedDatabase db, {String? prefix}) { diff --git a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart index 5c4ec555..71f5b708 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart @@ -17,7 +17,7 @@ extension ChannelEntityX on ChannelEntity { cid: cid, lastMessageAt: lastMessageAt, deletedAt: deletedAt, - extraData: extraData, + extraData: extraData ?? {}, createdBy: createdBy, ); } 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 3f04711d..50ff7a7c 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 @@ -207,8 +207,8 @@ void main() { test('should return sorted channels using custom field', () async { int sortComparator(ChannelModel a, ChannelModel b) { - final aData = a.extraData!['test_custom_field'] as int; - final bData = b.extraData!['test_custom_field'] as int; + final aData = a.extraData['test_custom_field'] as int; + final bData = b.extraData['test_custom_field'] as int; return bData.compareTo(aData); } From 3283f008ebe9bb243624a50a808930f0d348adbd Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 26 Apr 2021 15:58:47 +0200 Subject: [PATCH 160/599] igrate example --- .../stream_chat_flutter/example/lib/main.dart | 4 ++-- .../example/lib/split_view.dart | 20 ++++++++--------- .../example/lib/tutorial-part-1.dart | 4 ++-- .../example/lib/tutorial-part-2.dart | 2 +- .../example/lib/tutorial-part-3.dart | 22 +++++++++---------- .../example/lib/tutorial-part-4.dart | 12 +++++----- .../example/lib/tutorial-part-5.dart | 12 +++++----- .../example/lib/tutorial-part-6.dart | 12 +++++----- .../stream_chat_flutter/example/pubspec.yaml | 5 +++-- .../lib/src/attachment/giphy_attachment.dart | 8 ++++++- .../lib/src/media_list_view.dart | 5 ++--- .../lib/src/message_input.dart | 11 ++++++---- .../lib/src/message_widget.dart | 2 +- 13 files changed, 64 insertions(+), 55 deletions(-) diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 731ef080..1f3b51d2 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -61,8 +61,8 @@ class MyApp extends StatelessWidget { themeMode: ThemeMode.system, builder: (context, widget) { return StreamChat( - child: widget, client: client, + child: widget, ); }, home: StreamChannel( @@ -80,7 +80,7 @@ class MyApp extends StatelessWidget { class ChannelPage extends StatelessWidget { /// Creates the page that shows the list of messages const ChannelPage({ - Key key, + Key? key, }) : super(key: key); @override diff --git a/packages/stream_chat_flutter/example/lib/split_view.dart b/packages/stream_chat_flutter/example/lib/split_view.dart index 0432a0b4..1c833e23 100644 --- a/packages/stream_chat_flutter/example/lib/split_view.dart +++ b/packages/stream_chat_flutter/example/lib/split_view.dart @@ -25,8 +25,8 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( builder: (context, child) => StreamChat( - child: child, client: client, + child: child, ), home: SplitView(), ); @@ -39,7 +39,7 @@ class SplitView extends StatefulWidget { } class _SplitViewState extends State { - Channel selectedChannel; + Channel? selectedChannel; @override Widget build(BuildContext context) { @@ -47,6 +47,7 @@ class _SplitViewState extends State { direction: Axis.horizontal, children: [ Flexible( + flex: 1, child: ChannelListPage( onTap: (channel) { setState(() { @@ -54,15 +55,15 @@ class _SplitViewState extends State { }); }, ), - flex: 1, ), Flexible( + flex: 2, child: Scaffold( body: selectedChannel != null ? StreamChannel( - key: ValueKey(selectedChannel.cid), + key: ValueKey(selectedChannel!.cid), + channel: selectedChannel!, child: ChannelPage(), - channel: selectedChannel, ) : Center( child: Text( @@ -71,7 +72,6 @@ class _SplitViewState extends State { ), ), ), - flex: 2, ), ], ); @@ -79,7 +79,7 @@ class _SplitViewState extends State { } class ChannelListPage extends StatelessWidget { - final void Function(Channel) onTap; + final void Function(Channel)? onTap; ChannelListPage({this.onTap}); @@ -90,12 +90,12 @@ class ChannelListPage extends StatelessWidget { child: ChannelListView( onChannelTap: onTap != null ? (channel, _) { - onTap(channel); + onTap!(channel); } : null, filter: Filter.in_( 'members', - [StreamChat.of(context).user.id], + [StreamChat.of(context).user!.id], ), sort: [SortOption('last_message_at')], pagination: PaginationParams( @@ -109,7 +109,7 @@ class ChannelListPage extends StatelessWidget { class ChannelPage extends StatelessWidget { const ChannelPage({ - Key key, + Key? key, }) : super(key: key); @override diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-1.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-1.dart index 1fd53f55..2a720c70 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-1.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-1.dart @@ -55,8 +55,8 @@ class MyApp extends StatelessWidget { return MaterialApp( builder: (context, widget) { return StreamChat( - child: widget, client: client, + child: widget, ); }, home: StreamChannel( @@ -69,7 +69,7 @@ class MyApp extends StatelessWidget { class ChannelPage extends StatelessWidget { const ChannelPage({ - Key key, + Key? key, }) : super(key: key); @override diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart index 57ee471b..d1b39774 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart @@ -73,7 +73,7 @@ class ChannelListPage extends StatelessWidget { class ChannelPage extends StatelessWidget { const ChannelPage({ - Key key, + Key? key, }) : super(key: key); @override diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart index 89613b82..fc55f6d9 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart @@ -1,4 +1,5 @@ // ignore_for_file: public_member_api_docs +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -43,8 +44,8 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( builder: (context, child) => StreamChat( - child: child, client: client, + child: child, ), home: ChannelListPage(), ); @@ -59,7 +60,7 @@ class ChannelListPage extends StatelessWidget { child: ChannelListView( filter: Filter.in_( 'members', - [StreamChat.of(context).user.id], + [StreamChat.of(context).user!.id], ), channelPreviewBuilder: _channelPreviewBuilder, // sort: [SortOption('last_message_at')], @@ -73,13 +74,12 @@ class ChannelListPage extends StatelessWidget { } Widget _channelPreviewBuilder(BuildContext context, Channel channel) { - final lastMessage = channel.state.messages.reversed.firstWhere( + final lastMessage = channel.state?.messages.reversed.firstWhereOrNull( (message) => !message.isDeleted, - orElse: () => null, ); - final subtitle = (lastMessage == null ? 'nothing yet' : lastMessage.text); - final opacity = channel.state.unreadCount > .0 ? 1.0 : 0.5; + final subtitle = (lastMessage == null ? 'nothing yet' : lastMessage.text!); + final opacity = (channel.state?.unreadCount ?? 0) > 0 ? 1.0 : 0.5; return ListTile( onTap: () { @@ -87,8 +87,8 @@ class ChannelListPage extends StatelessWidget { context, MaterialPageRoute( builder: (_) => StreamChannel( - child: ChannelPage(), channel: channel, + child: ChannelPage(), ), ), ); @@ -98,7 +98,7 @@ class ChannelListPage extends StatelessWidget { ), title: ChannelName( textStyle: - StreamChatTheme.of(context).channelPreviewTheme.title.copyWith( + StreamChatTheme.of(context).channelPreviewTheme.title!.copyWith( color: StreamChatTheme.of(context) .colorTheme .black @@ -106,10 +106,10 @@ class ChannelListPage extends StatelessWidget { ), ), subtitle: Text(subtitle), - trailing: channel.state.unreadCount > 0 + trailing: channel.state!.unreadCount! > 0 ? CircleAvatar( radius: 10, - child: Text(channel.state.unreadCount.toString()), + child: Text(channel.state!.unreadCount.toString()), ) : SizedBox(), ); @@ -118,7 +118,7 @@ class ChannelListPage extends StatelessWidget { class ChannelPage extends StatelessWidget { const ChannelPage({ - Key key, + Key? key, }) : super(key: key); @override diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart index 82589355..5a85339f 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart @@ -33,8 +33,8 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( builder: (context, child) => StreamChat( - child: child, client: client, + child: child, ), home: Container( child: ChannelListPage(), @@ -51,7 +51,7 @@ class ChannelListPage extends StatelessWidget { child: ChannelListView( filter: Filter.in_( 'members', - [StreamChat.of(context).user.id], + [StreamChat.of(context).user!.id], ), sort: [SortOption('last_message_at')], pagination: PaginationParams( @@ -66,7 +66,7 @@ class ChannelListPage extends StatelessWidget { class ChannelPage extends StatelessWidget { const ChannelPage({ - Key key, + Key? key, }) : super(key: key); @override @@ -92,10 +92,10 @@ class ChannelPage extends StatelessWidget { } class ThreadPage extends StatelessWidget { - final Message parent; + final Message? parent; ThreadPage({ - Key key, + Key? key, this.parent, }) : super(key: key); @@ -103,7 +103,7 @@ class ThreadPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: ThreadHeader( - parent: parent, + parent: parent!, ), body: Column( children: [ diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart index b265eef3..7a75c037 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart @@ -38,8 +38,8 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( builder: (context, child) => StreamChat( - child: child, client: client, + child: child, ), home: ChannelListPage(), ); @@ -54,7 +54,7 @@ class ChannelListPage extends StatelessWidget { child: ChannelListView( filter: Filter.in_( 'members', - [StreamChat.of(context).user.id], + [StreamChat.of(context).user!.id], ), sort: [SortOption('last_message_at')], pagination: PaginationParams( @@ -69,7 +69,7 @@ class ChannelListPage extends StatelessWidget { class ChannelPage extends StatelessWidget { const ChannelPage({ - Key key, + Key? key, }) : super(key: key); @override @@ -95,7 +95,7 @@ class ChannelPage extends StatelessWidget { List messages, ) { final message = details.message; - final isCurrentUser = StreamChat.of(context).user.id == message.user.id; + final isCurrentUser = StreamChat.of(context).user!.id == message.user!.id; final textAlign = isCurrentUser ? TextAlign.right : TextAlign.left; final color = isCurrentUser ? Colors.blueGrey : Colors.blue; @@ -110,11 +110,11 @@ class ChannelPage extends StatelessWidget { ), child: ListTile( title: Text( - message.text, + message.text!, textAlign: textAlign, ), subtitle: Text( - message.user.extraData['name'], + message.user!.extraData['name'] as String, textAlign: textAlign, ), ), diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart index 7d732eac..c49ffaf1 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart @@ -64,9 +64,9 @@ class MyApp extends StatelessWidget { theme: themeData, builder: (context, child) { return StreamChat( - child: child, client: client, streamChatThemeData: customTheme, + child: child, ); }, home: ChannelListPage(), @@ -82,7 +82,7 @@ class ChannelListPage extends StatelessWidget { child: ChannelListView( filter: Filter.in_( 'members', - [StreamChat.of(context).user.id], + [StreamChat.of(context).user!.id], ), sort: [SortOption('last_message_at')], pagination: PaginationParams( @@ -97,7 +97,7 @@ class ChannelListPage extends StatelessWidget { class ChannelPage extends StatelessWidget { const ChannelPage({ - Key key, + Key? key, }) : super(key: key); @override @@ -123,10 +123,10 @@ class ChannelPage extends StatelessWidget { } class ThreadPage extends StatelessWidget { - final Message parent; + final Message? parent; ThreadPage({ - Key key, + Key? key, this.parent, }) : super(key: key); @@ -134,7 +134,7 @@ class ThreadPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: ThreadHeader( - parent: parent, + parent: parent!, ), body: Column( children: [ diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index af8595d5..106517fb 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: @@ -28,7 +28,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.0 + cupertino_icons: ^1.0.2 + collection: ^1.15.0 dependency_overrides: stream_chat: 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 cb8d3556..8e2ede71 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -21,7 +21,12 @@ class GiphyAttachment extends AttachmentWidget { this.onShowMessage, this.onReturnAction, this.onAttachmentTap, - }) : super(key: key, message: message, attachment: attachment, size: size); + }) : super( + key: key, + message: message, + attachment: attachment, + size: size, + ); @override Widget build(BuildContext context) { @@ -173,6 +178,7 @@ class GiphyAttachment extends AttachmentWidget { .black .withOpacity(0.5), ), + maxLines: 1, ), ), ), 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 3ba266bc..703fb092 100644 --- a/packages/stream_chat_flutter/lib/src/media_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/media_list_view.dart @@ -177,7 +177,7 @@ class MediaThumbnailProvider extends ImageProvider { @override ImageStreamCompleter load(key, decode) { return MultiFrameImageStreamCompleter( - codec: _loadAsync(key, decode) as Future, + codec: _loadAsync(key, decode), scale: 1.0, informationCollector: () sync* { yield ErrorDescription('Id: ${media.id}'); @@ -185,11 +185,10 @@ class MediaThumbnailProvider extends ImageProvider { ); } - Future _loadAsync( + Future _loadAsync( MediaThumbnailProvider key, DecoderCallback decode) async { assert(key == this); final bytes = await media.thumbData; - if (bytes?.isNotEmpty != true) return null; return await decode(bytes!); } diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 12ee9e93..c7ddb5f1 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; @@ -610,7 +609,7 @@ class MessageInputState extends State { Padding( padding: const EdgeInsets.all(8.0), child: Container( - constraints: BoxConstraints.tight(Size(64, 24)), + constraints: BoxConstraints.tight(Size(84, 24)), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: theme.colorTheme.accentBlue, @@ -1105,10 +1104,14 @@ class MessageInputState extends State { } void _addAttachment(AssetEntity medium) async { - final mediaFile = await (medium.originFile.timeout( + final mediaFile = await medium.originFile.timeout( Duration(seconds: 5), onTimeout: () => medium.originFile, - ) as FutureOr); + ); + + if (mediaFile == null) { + return; + } var file = AttachmentFile( path: mediaFile.path, diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index d39240ab..1e8049f8 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -446,7 +446,7 @@ class _MessageWidgetState extends State transform: Matrix4.rotationY(widget.reverse ? pi : 0), child: FractionallySizedBox( alignment: Alignment.centerLeft, - widthFactor: 0.75, + widthFactor: 0.78, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, From 0d12e701a0725946623af572c4196a9f5c299127 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 26 Apr 2021 16:58:28 +0200 Subject: [PATCH 161/599] fix tests --- packages/stream_chat/analysis_options.yaml | 1 - packages/stream_chat/test/src/api/channel_test.dart | 10 ++++++++-- .../test/src/models/channel_state_test.dart | 6 +++--- .../stream_chat/test/src/models/channel_test.dart | 4 ++-- packages/stream_chat/test/version_test.dart | 12 ++++++------ .../test/src/dao/channel_query_dao_test.dart | 7 ++++++- 6 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/stream_chat/analysis_options.yaml b/packages/stream_chat/analysis_options.yaml index ddc691d0..c0e14133 100644 --- a/packages/stream_chat/analysis_options.yaml +++ b/packages/stream_chat/analysis_options.yaml @@ -3,7 +3,6 @@ analyzer: - lib/**/*.g.dart - lib/**/*.freezed.dart - example/* - - test/* linter: rules: - always_use_package_imports diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 49a12cb4..c3409b7e 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -223,7 +223,10 @@ void main() { final mockDio = MockDio(); final mockUploader = MockAttachmentUploader(); - const file = AttachmentFile(path: 'filePath/fileName.pdf'); + const file = AttachmentFile( + path: 'filePath/fileName.pdf', + size: 100, + ); const channelId = 'testId'; const channelType = 'messaging'; @@ -262,7 +265,10 @@ void main() { final mockDio = MockDio(); final mockUploader = MockAttachmentUploader(); - const image = AttachmentFile(path: 'imagePath/imageName.jpeg'); + const image = AttachmentFile( + path: 'imagePath/imageName.jpeg', + size: 100, + ); const channelId = 'testId'; const channelType = 'messaging'; diff --git a/packages/stream_chat/test/src/models/channel_state_test.dart b/packages/stream_chat/test/src/models/channel_state_test.dart index afe384f5..a93166b4 100644 --- a/packages/stream_chat/test/src/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/models/channel_state_test.dart @@ -861,10 +861,10 @@ void main() { DateTime.parse('2019-04-03T18:43:33.213374Z')); expect(channelState.channel?.createdBy, isA()); expect(channelState.channel?.frozen, true); - expect(channelState.channel?.extraData!['example'], 1); - expect(channelState.channel?.extraData!['name'], '#dev'); + expect(channelState.channel?.extraData['example'], 1); + expect(channelState.channel?.extraData['name'], '#dev'); expect( - channelState.channel?.extraData!['image'], + channelState.channel?.extraData['image'], 'https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png', ); expect(channelState.messages, hasLength(25)); diff --git a/packages/stream_chat/test/src/models/channel_test.dart b/packages/stream_chat/test/src/models/channel_test.dart index 01c06fee..1e69565c 100644 --- a/packages/stream_chat/test/src/models/channel_test.dart +++ b/packages/stream_chat/test/src/models/channel_test.dart @@ -20,8 +20,8 @@ void main() { expect(channel.id, equals('test')); expect(channel.type, equals('livestream')); expect(channel.cid, equals('livestream:test')); - expect(channel.extraData!['cats'], equals(true)); - expect(channel.extraData!['fruit'], equals(['bananas', 'apples'])); + expect(channel.extraData['cats'], equals(true)); + expect(channel.extraData['fruit'], equals(['bananas', 'apples'])); }); test('should serialize to json correctly', () { diff --git a/packages/stream_chat/test/version_test.dart b/packages/stream_chat/test/version_test.dart index 9bbd446f..34ecc750 100644 --- a/packages/stream_chat/test/version_test.dart +++ b/packages/stream_chat/test/version_test.dart @@ -1,8 +1,7 @@ import 'dart:io'; -import 'package:rxdart/rxdart.dart'; -import 'package:test/test.dart'; import 'package:stream_chat/version.dart'; +import 'package:test/test.dart'; void prepareTest() { // https://github.com/flutter/flutter/issues/20907 @@ -14,10 +13,11 @@ void prepareTest() { void main() { prepareTest(); test('stream chat version matches pubspec', () { - final String pubspecPath = '${Directory.current.path}/pubspec.yaml'; - final String pubspec = File(pubspecPath).readAsStringSync(); - final RegExp regex = RegExp('version:\s*(.*)'); - final RegExpMatch? match = regex.firstMatch(pubspec); + final pubspecPath = '${Directory.current.path}/pubspec.yaml'; + final pubspec = File(pubspecPath).readAsStringSync(); + // ignore: unnecessary_string_escapes + final regex = RegExp('version:\s*(.*)'); + final match = regex.firstMatch(pubspec); expect(match, isNotNull); expect(PACKAGE_VERSION, match?.group(1)?.trim()); }); 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 50ff7a7c..9b5e3470 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 @@ -168,7 +168,12 @@ void main() { // Should match with the inserted channels final updatedChannels = await channelQueryDao.getChannels( filter: filter, - sort: [SortOption('member_count', comparator: sortComparator)], + sort: [ + SortOption( + 'member_count', + comparator: sortComparator, + ) + ], ); expect(updatedChannels.length, insertedChannels.length); From 5bbe82c7eb2fb4dbcdeed03197931c67c7536a6b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Apr 2021 17:39:14 +0200 Subject: [PATCH 162/599] fix review --- packages/stream_chat/lib/src/api/channel.dart | 3 +- .../lib/src/attachment/attachment_title.dart | 6 +- .../lib/src/attachment/file_attachment.dart | 4 +- .../lib/src/attachment/image_attachment.dart | 6 +- .../lib/src/attachment/video_attachment.dart | 10 +-- .../lib/src/deleted_message.dart | 8 +-- .../lib/src/full_screen_media.dart | 1 - .../lib/src/image_footer.dart | 4 +- .../lib/src/image_group.dart | 2 +- .../lib/src/image_header.dart | 6 +- .../lib/src/message_actions_modal.dart | 5 +- .../lib/src/message_input.dart | 2 +- .../lib/src/message_list_view.dart | 6 +- .../lib/src/message_reactions_modal.dart | 11 ++-- .../lib/src/message_text.dart | 22 +++---- .../lib/src/message_widget.dart | 47 +++++++------- .../lib/src/quoted_message_widget.dart | 12 ++-- .../lib/src/reaction_picker.dart | 2 - .../lib/src/stream_chat.dart | 4 +- .../lib/src/upload_progress_indicator.dart | 8 +-- .../lib/src/url_attachment.dart | 15 +++-- .../stream_chat_flutter/lib/src/utils.dart | 64 ++++++++++--------- .../lib/src/video_thumbnail_image.dart | 6 +- .../src/attachment_actions_modal_test.dart | 10 +-- .../test/src/message_action_modal_test.dart | 20 +++--- 25 files changed, 141 insertions(+), 143 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 35989a72..17755b6d 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -204,7 +204,8 @@ class Channel { /// Channel extra data as a stream Stream> get extraDataStream { _checkInitialized(); - return state!.channelStateStream.map( + return state!.channelStateStream + .map( (cs) => cs.channel?.extraData ?? _extraData, ); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart index 0ad06cc0..e2686638 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart @@ -11,7 +11,7 @@ class AttachmentTitle extends StatelessWidget { required this.messageTheme, }) : super(key: key); - final MessageTheme? messageTheme; + final MessageTheme messageTheme; final Attachment attachment; @override @@ -32,7 +32,7 @@ class AttachmentTitle extends StatelessWidget { Text( attachment.title!, overflow: TextOverflow.ellipsis, - style: messageTheme?.messageText?.copyWith( + style: messageTheme.messageText?.copyWith( color: StreamChatTheme.of(context).colorTheme.accentBlue, fontWeight: FontWeight.bold, ), @@ -47,7 +47,7 @@ class AttachmentTitle extends StatelessWidget { .toList() .reversed .join('.'), - style: messageTheme?.messageText, + style: messageTheme.messageText, ), ], ), 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 929cddc2..90ef92b1 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -155,7 +155,7 @@ class FileAttachment extends AttachmentWidget { shape: _getDefaultShape(context), child: source.when( local: () => VideoThumbnailImage( - video: attachment.file?.path, + video: attachment.file!.path!, placeholderBuilder: (_) { return Center( child: Container( @@ -167,7 +167,7 @@ class FileAttachment extends AttachmentWidget { }, ), network: () => VideoThumbnailImage( - video: attachment.assetUrl, + video: attachment.assetUrl!, placeholderBuilder: (_) { return Center( child: Container( diff --git a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart index 6ac48f59..1ff9b8a9 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart @@ -10,7 +10,7 @@ import 'attachment_title.dart'; import 'attachment_widget.dart'; class ImageAttachment extends AttachmentWidget { - final MessageTheme? messageTheme; + final MessageTheme messageTheme; final bool showTitle; final ShowMessageCallback? onShowMessage; final ValueChanged? onReturnAction; @@ -20,8 +20,8 @@ class ImageAttachment extends AttachmentWidget { Key? key, required Message message, required Attachment attachment, + required this.messageTheme, Size? size, - this.messageTheme, this.showTitle = false, this.onShowMessage, this.onReturnAction, @@ -157,7 +157,7 @@ class ImageAttachment extends AttachmentWidget { ), if (showTitle && attachment.title != null) Material( - color: messageTheme?.messageBackgroundColor, + color: messageTheme.messageBackgroundColor, child: AttachmentTitle( messageTheme: messageTheme, attachment: attachment, diff --git a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart index 05c511b5..4ffbee1b 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart @@ -8,7 +8,7 @@ import 'attachment_upload_state_builder.dart'; import 'attachment_widget.dart'; class VideoAttachment extends AttachmentWidget { - final MessageTheme? messageTheme; + final MessageTheme messageTheme; final ShowMessageCallback? onShowMessage; final ValueChanged? onReturnAction; final VoidCallback? onAttachmentTap; @@ -17,8 +17,8 @@ class VideoAttachment extends AttachmentWidget { Key? key, required Message message, required Attachment attachment, + required this.messageTheme, Size? size, - this.messageTheme, this.onShowMessage, this.onReturnAction, this.onAttachmentTap, @@ -39,7 +39,7 @@ class VideoAttachment extends AttachmentWidget { return _buildVideoAttachment( context, VideoThumbnailImage( - video: attachment.file?.path, + video: attachment.file!.path!, height: size?.height, width: size?.width, fit: BoxFit.cover, @@ -54,7 +54,7 @@ class VideoAttachment extends AttachmentWidget { return _buildVideoAttachment( context, VideoThumbnailImage( - video: attachment.assetUrl, + video: attachment.assetUrl!, height: size?.height, width: size?.width, fit: BoxFit.cover, @@ -116,7 +116,7 @@ class VideoAttachment extends AttachmentWidget { ), if (attachment.title != null) Material( - color: messageTheme?.messageBackgroundColor, + color: messageTheme.messageBackgroundColor, child: AttachmentTitle( messageTheme: messageTheme, attachment: attachment, diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart index c0387982..b3071904 100644 --- a/packages/stream_chat_flutter/lib/src/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart @@ -14,7 +14,7 @@ class DeletedMessage extends StatelessWidget { }) : super(key: key); /// The theme of the message - final MessageTheme? messageTheme; + final MessageTheme messageTheme; /// The border radius of the message text final BorderRadiusGeometry? borderRadiusGeometry; @@ -34,7 +34,7 @@ class DeletedMessage extends StatelessWidget { transform: Matrix4.rotationY(reverse ? pi : 0), alignment: Alignment.center, child: Material( - color: messageTheme?.messageBackgroundColor, + color: messageTheme.messageBackgroundColor, shape: shape ?? RoundedRectangleBorder( borderRadius: borderRadiusGeometry ?? BorderRadius.zero, @@ -61,9 +61,9 @@ class DeletedMessage extends StatelessWidget { alignment: Alignment.center, child: Text( 'Message deleted', - style: messageTheme?.messageText?.copyWith( + style: messageTheme.messageText?.copyWith( fontStyle: FontStyle.italic, - color: messageTheme?.createdAt?.color, + color: messageTheme.createdAt?.color, ), ), ), 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 4956fb28..f960b5ee 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -177,7 +177,6 @@ class _FullScreenMediaState extends State Navigator.of(context).pop(); }, message: widget.message, - urls: widget.mediaAttachments, currentIndex: _currentPage, onShowMessage: () { widget.onShowMessage?.call( diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/image_footer.dart index a25d1be3..27c92674 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/image_footer.dart @@ -221,8 +221,8 @@ class _ImageFooterState extends State { child: FittedBox( fit: BoxFit.cover, child: VideoThumbnailImage( - video: attachment.file?.path ?? - attachment.assetUrl, + video: (attachment.file?.path ?? + attachment.assetUrl)!, ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/image_group.dart b/packages/stream_chat_flutter/lib/src/image_group.dart index 85e28497..a08d13de 100644 --- a/packages/stream_chat_flutter/lib/src/image_group.dart +++ b/packages/stream_chat_flutter/lib/src/image_group.dart @@ -15,7 +15,7 @@ class ImageGroup extends StatelessWidget { final List images; final Message message; - final MessageTheme? messageTheme; + final MessageTheme messageTheme; final Size size; final ShowMessageCallback? onShowMessage; diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/image_header.dart index 1cec9c36..6837722c 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/image_header.dart @@ -27,15 +27,13 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { final String userName; final String sentAt; - final List urls; - final currentIndex; + final int currentIndex; /// Creates a channel header ImageHeader({ Key? key, required this.message, - this.urls = const [], - this.currentIndex, + this.currentIndex = 0, this.showBackButton = true, this.onBackPressed, this.onShowMessage, 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 5d97bdf6..b737bd09 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -20,7 +20,7 @@ class MessageActionsModal extends StatefulWidget { final OnMessageTap? onThreadReplyTap; final OnMessageTap? onReplyTap; final Message message; - final MessageTheme? messageTheme; + final MessageTheme messageTheme; final bool showReactions; final OnMessageTap? onCopyTap; final bool showDeleteMessage; @@ -92,7 +92,7 @@ class _MessageActionsModalState extends State { } } final roughSentenceSize = messageTextLength * - (widget.messageTheme?.messageText?.fontSize ?? 1) * + (widget.messageTheme.messageText?.fontSize ?? 1) * 1.2; final divFactor = widget.message.attachments.isNotEmpty == true ? 1 @@ -149,7 +149,6 @@ class _MessageActionsModalState extends State { 0.0), child: ReactionPicker( message: widget.message, - messageTheme: widget.messageTheme, ), ), SizedBox(height: 8), diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index c7ddb5f1..33917de0 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1661,7 +1661,7 @@ class MessageInputState extends State { VideoThumbnailImage( height: 104, width: 104, - video: attachment.file?.path ?? attachment.assetUrl, + video: (attachment.file?.path ?? attachment.assetUrl)!, fit: BoxFit.cover, ), Positioned( 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 f444224d..8f36baeb 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -832,7 +832,7 @@ class _MessageListViewState extends State { break; case ReturnActionType.reply: FocusScope.of(context).unfocus(); - widget.onMessageSwiped!(message); + widget.onMessageSwiped?.call(message); break; } }, @@ -1008,7 +1008,7 @@ class _MessageListViewState extends State { break; case ReturnActionType.reply: FocusScope.of(context).unfocus(); - widget.onMessageSwiped!(message); + widget.onMessageSwiped?.call(message); break; } }, @@ -1034,7 +1034,7 @@ class _MessageListViewState extends State { child: Swipeable( onSwipeEnd: () { FocusScope.of(context).unfocus(); - widget.onMessageSwiped!(message); + widget.onMessageSwiped?.call(message); }, backgroundIcon: StreamSvgIcon.reply( color: StreamChatTheme.of(context).colorTheme.accentBlue, 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 ed9534ac..9fa35cb6 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -14,7 +14,7 @@ import 'stream_chat_theme.dart'; class MessageReactionsModal extends StatelessWidget { final Message message; - final MessageTheme? messageTheme; + final MessageTheme messageTheme; final bool reverse; final bool showReactions; final DisplayWidget showUserAvatar; @@ -53,7 +53,7 @@ class MessageReactionsModal extends StatelessWidget { } } final roughSentenceSize = - messageTextLength * (messageTheme?.messageText?.fontSize ?? 1) * 1.2; + messageTextLength * (messageTheme.messageText?.fontSize ?? 1) * 1.2; final divFactor = message.attachments.isNotEmpty == true ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); @@ -105,7 +105,6 @@ class MessageReactionsModal extends StatelessWidget { 0.0), child: ReactionPicker( message: message, - messageTheme: messageTheme, ), ), const SizedBox(height: 8), @@ -244,9 +243,9 @@ class MessageReactionsModal extends StatelessWidget { child: ReactionBubble( reactions: [reaction], flipTail: !reverse, - borderColor: messageTheme?.reactionsBorderColor ?? - Colors.transparent, - backgroundColor: messageTheme?.reactionsBackgroundColor ?? + borderColor: + messageTheme.reactionsBorderColor ?? Colors.transparent, + backgroundColor: messageTheme.reactionsBackgroundColor ?? Colors.transparent, maskColor: StreamChatTheme.of(context).colorTheme.white, tailCirclesSpacing: 1, diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index eb80b89b..b0099eff 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -18,11 +18,11 @@ class MessageText extends StatelessWidget { final Message message; final void Function(User)? onMentionTap; final void Function(String)? onLinkTap; - final MessageTheme? messageTheme; + final MessageTheme messageTheme; @override Widget build(BuildContext context) { - final text = _replaceMentions(message.text)!.replaceAll('\n', '\\\n'); + final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\\\n'); return MarkdownBody( data: text, @@ -55,23 +55,23 @@ class MessageText extends StatelessWidget { styleSheet: MarkdownStyleSheet.fromTheme( Theme.of(context).copyWith( textTheme: Theme.of(context).textTheme.apply( - bodyColor: messageTheme!.messageText!.color, - decoration: messageTheme!.messageText!.decoration, - decorationColor: messageTheme!.messageText!.decorationColor, - decorationStyle: messageTheme!.messageText!.decorationStyle, - fontFamily: messageTheme!.messageText!.fontFamily, + bodyColor: messageTheme.messageText?.color, + decoration: messageTheme.messageText?.decoration, + decorationColor: messageTheme.messageText?.decorationColor, + decorationStyle: messageTheme.messageText?.decorationStyle, + fontFamily: messageTheme.messageText?.fontFamily, ), ), ).copyWith( - a: messageTheme!.messageLinks, - p: messageTheme!.messageText, + a: messageTheme.messageLinks, + p: messageTheme.messageText, ), ); } - String? _replaceMentions(String? text) { + String _replaceMentions(String text) { message.mentionedUsers.map((u) => u.name).toSet().forEach((userName) { - text = text!.replaceAll( + text = text.replaceAll( '@$userName', '[@$userName](@${userName.replaceAll(' ', '')})'); }); return text; diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 1e8049f8..b05762ba 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -66,7 +66,7 @@ class MessageWidget extends StatefulWidget { final Message message; /// The message theme - final MessageTheme? messageTheme; + final MessageTheme messageTheme; /// If true the widget will be mirrored final bool reverse; @@ -93,7 +93,7 @@ class MessageWidget extends StatefulWidget { final EdgeInsetsGeometry? padding; /// The internal padding of the message text - final EdgeInsetsGeometry textPadding; + final EdgeInsets textPadding; /// The internal padding of an attachment final EdgeInsetsGeometry attachmentPadding; @@ -222,7 +222,7 @@ class MessageWidget extends StatefulWidget { child: wrapAttachmentWidget( context, Material( - color: messageTheme?.messageBackgroundColor, + color: messageTheme.messageBackgroundColor, child: ImageGroup( size: Size( MediaQuery.of(context).size.width * 0.8, @@ -425,7 +425,7 @@ class _MessageWidgetState extends State Widget build(BuildContext context) { super.build(context); final avatarWidth = - widget.messageTheme?.avatarTheme?.constraints.maxWidth ?? 40; + widget.messageTheme.avatarTheme?.constraints.maxWidth ?? 40; var leftPadding = widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; @@ -544,7 +544,7 @@ class _MessageWidgetState extends State BorderSide( color: widget .messageTheme - ?.messageBorderColor ?? + .messageBorderColor ?? Colors.grey, ), borderRadius: widget @@ -702,7 +702,7 @@ class _MessageWidgetState extends State ), InkWell( onTap: widget.onThreadTap != null ? onThreadTap : null, - child: Text(msg, style: widget.messageTheme?.replies), + child: Text(msg, style: widget.messageTheme.replies), ), ], if (showUsername) @@ -710,13 +710,13 @@ class _MessageWidgetState extends State widget.message.user!.name, maxLines: 1, key: usernameKey, - style: widget.messageTheme?.messageAuthor, + style: widget.messageTheme.messageAuthor, overflow: TextOverflow.ellipsis, ), if (showTimeStamp) Text( Jiffy(widget.message.createdAt.toLocal()).jm, - style: widget.messageTheme?.createdAt, + style: widget.messageTheme.createdAt, ), if (showSendingIndicator) _buildSendingIndicator(), ]); @@ -731,13 +731,13 @@ class _MessageWidgetState extends State Container( margin: EdgeInsets.only( bottom: context.textScaleFactor * - ((widget.messageTheme?.replies?.fontSize ?? 1) / 2), + ((widget.messageTheme.replies?.fontSize ?? 1) / 2), ), child: CustomPaint( size: Size(16, 32) * context.textScaleFactor, painter: _ThreadReplyPainter( context: context, - color: widget.messageTheme?.messageBorderColor, + color: widget.messageTheme.messageBorderColor, ), ), ), @@ -775,7 +775,7 @@ class _MessageWidgetState extends State return UrlAttachment( urlAttachment: urlAttachment, hostDisplayName: hostDisplayName, - textPadding: widget.textPadding as EdgeInsets, + textPadding: widget.textPadding, ); } @@ -830,12 +830,11 @@ class _MessageWidgetState extends State key: ValueKey('${widget.message.id}.reactions'), reverse: widget.reverse, flipTail: widget.reverse, - backgroundColor: - widget.messageTheme?.reactionsBackgroundColor ?? - Colors.transparent, - borderColor: widget.messageTheme?.reactionsBorderColor ?? + backgroundColor: widget.messageTheme.reactionsBackgroundColor ?? Colors.transparent, - maskColor: widget.messageTheme?.reactionsMaskColor ?? + borderColor: widget.messageTheme.reactionsBorderColor ?? + Colors.transparent, + maskColor: widget.messageTheme.reactionsMaskColor ?? Colors.transparent, reactions: reactionsList, ), @@ -1001,7 +1000,7 @@ class _MessageWidgetState extends State } Widget _buildSendingIndicator() { - final style = widget.messageTheme?.createdAt; + final style = widget.messageTheme.createdAt; final message = widget.message; if (hasNonUrlAttachments && @@ -1053,7 +1052,7 @@ class _MessageWidgetState extends State offset: Offset( 0, widget.translateUserAvatar - ? (widget.messageTheme?.avatarTheme?.constraints.maxHeight ?? + ? (widget.messageTheme.avatarTheme?.constraints.maxHeight ?? 40) / 2 : 0, @@ -1061,8 +1060,8 @@ class _MessageWidgetState extends State child: UserAvatar( user: widget.message.user!, onTap: widget.onUserAvatarTap, - constraints: widget.messageTheme?.avatarTheme!.constraints, - borderRadius: widget.messageTheme?.avatarTheme!.borderRadius, + constraints: widget.messageTheme.avatarTheme!.constraints, + borderRadius: widget.messageTheme.avatarTheme!.borderRadius, showOnlineStatus: false, ), ), @@ -1085,9 +1084,9 @@ class _MessageWidgetState extends State message: widget.message, onMentionTap: widget.onMentionTap, messageTheme: isOnlyEmoji - ? widget.messageTheme?.copyWith( + ? widget.messageTheme.copyWith( messageText: - widget.messageTheme?.messageText!.copyWith( + widget.messageTheme.messageText!.copyWith( fontSize: 42, )) : widget.messageTheme, @@ -1103,7 +1102,7 @@ class _MessageWidgetState extends State Color? _getBackgroundColor() { if (hasQuotedMessage) { - return widget.messageTheme?.messageBackgroundColor; + return widget.messageTheme.messageBackgroundColor; } if (hasUrlAttachments) { @@ -1118,7 +1117,7 @@ class _MessageWidgetState extends State return Colors.transparent; } - return widget.messageTheme?.messageBackgroundColor; + return widget.messageTheme.messageBackgroundColor; } void retryMessage(BuildContext context) { 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 bc77e97a..369fb4db 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -67,7 +67,7 @@ class QuotedMessageWidget extends StatelessWidget { final Message message; /// The message theme - final MessageTheme? messageTheme; + final MessageTheme messageTheme; /// If true the widget will be mirrored final bool reverse; @@ -144,12 +144,12 @@ class QuotedMessageWidget extends StatelessWidget { child: MessageText( message: msg, messageTheme: isOnlyEmoji && _containsText - ? messageTheme?.copyWith( - messageText: messageTheme?.messageText?.copyWith( + ? messageTheme.copyWith( + messageText: messageTheme.messageText?.copyWith( fontSize: 32, )) - : messageTheme?.copyWith( - messageText: messageTheme?.messageText?.copyWith( + : messageTheme.copyWith( + messageText: messageTheme.messageText?.copyWith( fontSize: 12, )), ), @@ -310,6 +310,6 @@ class QuotedMessageWidget extends StatelessWidget { if (_containsScrapeUrl) { return StreamChatTheme.of(context).colorTheme.blueAlice; } - return messageTheme?.messageBackgroundColor; + return messageTheme.messageBackgroundColor; } } diff --git a/packages/stream_chat_flutter/lib/src/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reaction_picker.dart index 7cd01eb7..cd358b2a 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_picker.dart @@ -18,11 +18,9 @@ class ReactionPicker extends StatefulWidget { const ReactionPicker({ Key? key, required this.message, - required this.messageTheme, }) : super(key: key); final Message message; - final MessageTheme? messageTheme; @override _ReactionPickerState createState() => _ReactionPickerState(); diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index d3a196a1..d7e3ecea 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -32,7 +32,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Use [StreamChat.of] to get the current [StreamChatState] instance. class StreamChat extends StatefulWidget { final StreamChatClient client; - final Widget? child; + final Widget child; final StreamChatThemeData? streamChatThemeData; /// The amount of time that will pass before disconnecting the client in the background @@ -96,7 +96,7 @@ class StreamChatState extends State { client: client, onBackgroundEventReceived: widget.onBackgroundEventReceived, backgroundKeepAlive: widget.backgroundKeepAlive, - child: widget.child!, + child: widget.child, ), ); }, diff --git a/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart b/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart index ec06de8a..e0e7e306 100644 --- a/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart @@ -5,7 +5,7 @@ import 'stream_chat_theme.dart'; class UploadProgressIndicator extends StatelessWidget { final int uploaded; final int total; - late final Color progressIndicatorColor; + final Color progressIndicatorColor; final EdgeInsetsGeometry padding; final bool showBackground; final TextStyle? textStyle; @@ -14,7 +14,7 @@ class UploadProgressIndicator extends StatelessWidget { Key? key, required this.uploaded, required this.total, - Color? progressIndicatorColor, + this.progressIndicatorColor = const Color(0xffb2b2b2), this.padding = const EdgeInsets.only( top: 5, bottom: 5, @@ -23,9 +23,7 @@ class UploadProgressIndicator extends StatelessWidget { ), this.showBackground = true, this.textStyle, - }) : progressIndicatorColor = - progressIndicatorColor ?? const Color(0xffb2b2b2), - super(key: key); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/packages/stream_chat_flutter/lib/src/url_attachment.dart b/packages/stream_chat_flutter/lib/src/url_attachment.dart index d9beab20..e8e7b8a8 100644 --- a/packages/stream_chat_flutter/lib/src/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/url_attachment.dart @@ -11,16 +11,21 @@ class UrlAttachment extends StatelessWidget { UrlAttachment({ required this.urlAttachment, required this.hostDisplayName, - required this.textPadding, + this.textPadding = const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), }); @override Widget build(BuildContext context) { return GestureDetector( - onTap: () => launchURL( - context, - urlAttachment.ogScrapeUrl!, - ), + onTap: () { + launchURL( + context, + urlAttachment.ogScrapeUrl, + ); + }, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index ebb2a22e..79bd210e 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -22,10 +22,10 @@ Future launchURL(BuildContext context, String? url) async { Future showConfirmationDialog( BuildContext context, { - String? title, + required String title, + required String okText, Widget? icon, String? question, - String? okText, String? cancelText, }) { return showModalBottomSheet( @@ -46,14 +46,15 @@ Future showConfirmationDialog( if (icon != null) icon, SizedBox(height: 26.0), Text( - title!, + title, style: StreamChatTheme.of(context).textTheme.headlineBold, ), SizedBox(height: 7.0), - Text( - question!, - textAlign: TextAlign.center, - ), + if (question != null) + Text( + question, + textAlign: TextAlign.center, + ), SizedBox(height: 36.0), Container( color: effect.color!.withOpacity(effect.alpha ?? 1), @@ -61,27 +62,28 @@ Future showConfirmationDialog( ), Row( children: [ - Flexible( - child: Container( - alignment: Alignment.center, - child: TextButton( - onPressed: () { - Navigator.of(context).pop(false); - }, - child: Text( - cancelText!, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5)), + if (cancelText != null) + Flexible( + child: Container( + alignment: Alignment.center, + child: TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text( + cancelText, + style: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), + ), ), ), ), - ), Flexible( child: Container( alignment: Alignment.center, @@ -90,7 +92,7 @@ Future showConfirmationDialog( Navigator.pop(context, true); }, child: Text( - okText!, + okText, style: StreamChatTheme.of(context) .textTheme .bodyBold @@ -112,10 +114,10 @@ Future showConfirmationDialog( Future showInfoDialog( BuildContext context, { - String? title, + required String title, + required String okText, Widget? icon, String? details, - String? okText, StreamChatThemeData? theme, }) { return showModalBottomSheet( @@ -140,14 +142,14 @@ Future showInfoDialog( height: 26.0, ), Text( - title!, + title, style: theme?.textTheme.headlineBold ?? StreamChatTheme.of(context).textTheme.headlineBold, ), SizedBox( height: 7.0, ), - Text(details!), + if (details != null) Text(details), SizedBox( height: 36.0, ), @@ -162,7 +164,7 @@ Future showInfoDialog( Navigator.of(context).pop(); }, child: Text( - okText!, + okText, style: TextStyle( color: theme?.colorTheme.black.withOpacity(0.5) ?? StreamChatTheme.of(context).colorTheme.accentBlue, diff --git a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart index df8373eb..3d9d5429 100644 --- a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart +++ b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart @@ -9,7 +9,7 @@ import 'stream_svg_icon.dart'; import 'video_service.dart'; class VideoThumbnailImage extends StatefulWidget { - final String? video; + final String video; final double? width; final double? height; final BoxFit? fit; @@ -38,7 +38,7 @@ class _VideoThumbnailImageState extends State { @override void initState() { thumbnailFuture = VideoService.generateVideoThumbnail( - video: widget.video!, + video: widget.video, imageFormat: widget.format, ); super.initState(); @@ -48,7 +48,7 @@ class _VideoThumbnailImageState extends State { void didUpdateWidget(covariant VideoThumbnailImage oldWidget) { if (oldWidget.video != widget.video || oldWidget.format != widget.format) { thumbnailFuture = VideoService.generateVideoThumbnail( - video: widget.video!, + video: widget.video, imageFormat: widget.format, ); } diff --git a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart index 361d7cfc..762d4fe9 100644 --- a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart @@ -278,7 +278,7 @@ void main() { builder: (context, child) { return StreamChat( client: client, - child: child, + child: child!, ); }, home: StreamChannel( @@ -331,7 +331,7 @@ void main() { builder: (context, child) { return StreamChat( client: client, - child: child, + child: child!, ); }, home: StreamChannel( @@ -381,7 +381,7 @@ void main() { builder: (context, child) { return StreamChat( client: client, - child: child, + child: child!, ); }, home: StreamChannel( @@ -415,7 +415,7 @@ void main() { builder: (context, child) { return StreamChat( client: client, - child: child, + child: child!, ); }, home: Container( @@ -472,7 +472,7 @@ void main() { builder: (context, child) { return StreamChat( client: client, - child: child, + child: child!, ); }, home: Container( diff --git a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart index 8a6e8c33..0be509ae 100644 --- a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart @@ -264,7 +264,7 @@ void main() { return StreamChat( client: client, streamChatThemeData: streamTheme, - child: child, + child: child!, ); }, theme: themeData, @@ -314,7 +314,7 @@ void main() { return StreamChat( client: client, streamChatThemeData: streamTheme, - child: child, + child: child!, ); }, theme: themeData, @@ -369,7 +369,7 @@ void main() { return StreamChat( client: client, streamChatThemeData: streamTheme, - child: child, + child: child!, ); }, theme: themeData, @@ -420,7 +420,7 @@ void main() { return StreamChat( client: client, streamChatThemeData: streamTheme, - child: child, + child: child!, ); }, theme: themeData, @@ -471,7 +471,7 @@ void main() { return StreamChat( client: client, streamChatThemeData: streamTheme, - child: child, + child: child!, ); }, theme: themeData, @@ -520,7 +520,7 @@ void main() { return StreamChat( client: client, streamChatThemeData: streamTheme, - child: child, + child: child!, ); }, theme: themeData, @@ -579,7 +579,7 @@ void main() { return StreamChat( client: client, streamChatThemeData: streamTheme, - child: child, + child: child!, ); }, theme: themeData, @@ -638,7 +638,7 @@ void main() { return StreamChat( client: client, streamChatThemeData: streamTheme, - child: child, + child: child!, ); }, theme: themeData, @@ -693,7 +693,7 @@ void main() { return StreamChat( client: client, streamChatThemeData: streamTheme, - child: child, + child: child!, ); }, theme: themeData, @@ -752,7 +752,7 @@ void main() { return StreamChat( client: client, streamChatThemeData: streamTheme, - child: child, + child: child!, ); }, theme: themeData, From a4d540bbfe6ea54b9418c5f9a5271299000662fe Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Apr 2021 17:41:40 +0200 Subject: [PATCH 163/599] revert streamchat.child nullability --- packages/stream_chat_flutter/lib/src/stream_chat.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index d7e3ecea..f5461895 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -32,7 +32,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Use [StreamChat.of] to get the current [StreamChatState] instance. class StreamChat extends StatefulWidget { final StreamChatClient client; - final Widget child; + final Widget? child; final StreamChatThemeData? streamChatThemeData; /// The amount of time that will pass before disconnecting the client in the background @@ -96,7 +96,7 @@ class StreamChatState extends State { client: client, onBackgroundEventReceived: widget.onBackgroundEventReceived, backgroundKeepAlive: widget.backgroundKeepAlive, - child: widget.child, + child: widget.child ?? Offstage(), ), ); }, From 236f0a4f1592aea0899d4231ea8e71cf4dab3c4e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Apr 2021 09:46:08 +0200 Subject: [PATCH 164/599] fix messageinput padding --- packages/stream_chat_flutter/lib/src/message_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 33917de0..9d944567 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -609,7 +609,7 @@ class MessageInputState extends State { Padding( padding: const EdgeInsets.all(8.0), child: Container( - constraints: BoxConstraints.tight(Size(84, 24)), + constraints: BoxConstraints.tight(Size(64, 24)), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: theme.colorTheme.accentBlue, From 75e167b4ed9545a59372afde54a5e0a5ff691cfa Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Apr 2021 10:40:08 +0200 Subject: [PATCH 165/599] fix download image --- .../lib/src/attachment_actions_modal.dart | 135 +++++++++--------- 1 file changed, 66 insertions(+), 69 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart index 642b8b00..a91c18df 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart @@ -120,7 +120,7 @@ class AttachmentActionsModal extends StatelessWidget { received, ); }, - ).catchError((_) { + ).catchError((e, stk) { progressNotifier.value = null; }); @@ -225,76 +225,73 @@ class AttachmentActionsModal extends StatelessWidget { ValueNotifier<_DownloadProgress?> progressNotifier, ) { final theme = StreamChatTheme.of(context); - return WillPopScope( - onWillPop: () => Future.value(false), - child: ValueListenableBuilder( - valueListenable: progressNotifier, - builder: (_, _DownloadProgress? progress, __) { - // Pop the dialog in case the progress is null or it's completed. - if (progress == null || progress.toProgressIndicatorValue == 1.0) { - Future.delayed( - const Duration(milliseconds: 500), - Navigator.of(context).maybePop, - ); - } - return Material( - type: MaterialType.transparency, - child: Center( - child: Container( - height: 182, - width: 182, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: theme.colorTheme.white, - ), - child: Center( - child: progress == null - ? Container( - height: 100, - width: 100, - child: StreamSvgIcon.error( - color: theme.colorTheme.greyGainsboro, - ), - ) - : progress.toProgressIndicatorValue == 1.0 - ? Container( - key: Key('completedIcon'), - height: 160, - width: 160, - child: StreamSvgIcon.check( - color: theme.colorTheme.greyGainsboro, - ), - ) - : Container( - height: 100, - width: 100, - child: Stack( - fit: StackFit.expand, - children: [ - CircularProgressIndicator( - value: progress.toProgressIndicatorValue, - strokeWidth: 8.0, - valueColor: AlwaysStoppedAnimation( - theme.colorTheme.accentBlue, - ), - ), - Center( - child: Text( - '${progress.toPercentage}%', - style: theme.textTheme.headline.copyWith( - color: theme.colorTheme.grey, - ), - ), - ), - ], - ), + return ValueListenableBuilder( + valueListenable: progressNotifier, + builder: (_, _DownloadProgress? progress, __) { + // Pop the dialog in case the progress is null or it's completed. + if (progress == null || progress.toProgressIndicatorValue == 1.0) { + Future.delayed( + const Duration(milliseconds: 500), + () => Navigator.of(context).maybePop(), + ); + } + return Material( + type: MaterialType.transparency, + child: Center( + child: Container( + height: 182, + width: 182, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: theme.colorTheme.white, + ), + child: Center( + child: progress == null + ? Container( + height: 100, + width: 100, + child: StreamSvgIcon.error( + color: theme.colorTheme.greyGainsboro, + ), + ) + : progress.toProgressIndicatorValue == 1.0 + ? Container( + key: Key('completedIcon'), + height: 160, + width: 160, + child: StreamSvgIcon.check( + color: theme.colorTheme.greyGainsboro, ), - ), + ) + : Container( + height: 100, + width: 100, + child: Stack( + fit: StackFit.expand, + children: [ + CircularProgressIndicator( + value: progress.toProgressIndicatorValue, + strokeWidth: 8.0, + valueColor: AlwaysStoppedAnimation( + theme.colorTheme.accentBlue, + ), + ), + Center( + child: Text( + '${progress.toPercentage}%', + style: theme.textTheme.headline.copyWith( + color: theme.colorTheme.grey, + ), + ), + ), + ], + ), + ), ), ), - ); - }, - ), + ), + ); + }, ); } @@ -310,7 +307,7 @@ class AttachmentActionsModal extends StatelessWidget { final contentType = responseHeaders[Headers.contentTypeHeader]!; final mimeType = contentType.first.split('/').last; filePath ??= '${appDocDir.path}/${attachment.id}.$mimeType'; - return filePath; + return filePath!; }, onReceiveProgress: progressCallback, ); From 55c5f2ff864506af12bece7f48fff6d69ef020f0 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Apr 2021 12:00:00 +0200 Subject: [PATCH 166/599] fix group image --- packages/stream_chat_flutter/lib/src/channel_image.dart | 2 +- packages/stream_chat_flutter/lib/src/group_image.dart | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 041e0e03..e24fe08b 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -117,7 +117,7 @@ class ChannelImage extends StatelessWidget { member.user?.id != streamChat.user?.id && member.user?.extraData['image'] != null) .take(4) - .map((e) => e.user?.extraData['image'] as String?) + .map((e) => e.user?.extraData['image'] as String) .toList(); return GroupImage( images: images ?? [], diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index d6ec45e0..f39abf59 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -15,7 +15,7 @@ class GroupImage extends StatelessWidget { this.selectionThickness = 4, }) : super(key: key); - final List images; + final List images; final BoxConstraints? constraints; final VoidCallback? onTap; final bool selected; @@ -64,7 +64,7 @@ class GroupImage extends StatelessWidget { child: Transform.scale( scale: 1.2, child: CachedNetworkImage( - imageUrl: url!, + imageUrl: url, fit: BoxFit.cover, ), ), @@ -89,7 +89,7 @@ class GroupImage extends StatelessWidget { child: Transform.scale( scale: 1.2, child: CachedNetworkImage( - imageUrl: url!, + imageUrl: url, fit: BoxFit.cover, ), ), From 1509739f2484ce1f3e773c1966ea6cc14cd8e344 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Apr 2021 12:57:40 +0200 Subject: [PATCH 167/599] fix quoted message --- .../stream_chat_flutter/lib/src/quoted_message_widget.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 369fb4db..43b198eb 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -218,8 +218,9 @@ class QuotedMessageWidget extends StatelessWidget { attachmentBuilder = _defaultAttachmentBuilder[attachment.type]; if (attachmentBuilder == null) { child = Offstage(); + } else { + child = attachmentBuilder(context, attachment); } - child = attachmentBuilder!(context, attachment); } child = AbsorbPointer(child: child); return Transform( From 452d0c5717b1e0248e403abecec84e16765f5d53 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 30 Apr 2021 15:20:03 +0200 Subject: [PATCH 168/599] fix rebase --- packages/stream_chat/lib/src/api/channel.dart | 7 +++---- packages/stream_chat/lib/src/client.dart | 7 +++---- .../stream_chat_flutter/lib/src/channel_list_view.dart | 2 +- .../lib/src/message_search_list_view.dart | 2 +- .../lib/src/message_search_bloc.dart | 2 +- .../lib/src/message_search_list_core.dart | 2 +- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 17755b6d..1674e5eb 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -36,7 +36,7 @@ class Channel { _id = channelState.channel!.id, _type = channelState.channel!.type, _cid = channelState.channel!.cid, - _extraData = channelState.channel!.extraData ?? {} { + _extraData = channelState.channel!.extraData { state = ChannelClientState(this, channelState); _initializedCompleter.complete(true); _client.logger.info('New Channel instance initialized created'); @@ -204,8 +204,7 @@ class Channel { /// Channel extra data as a stream Stream> get extraDataStream { _checkInitialized(); - return state!.channelStateStream - .map( + return state!.channelStateStream.map( (cs) => cs.channel?.extraData ?? _extraData, ); } @@ -574,7 +573,7 @@ class Channel { /// A message search. Future search({ String? query, - Map? messageFilters, + Filter? messageFilters, List? sort, PaginationParams? paginationParams, }) { diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 12820f8c..675cf999 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -22,6 +22,7 @@ import 'package:stream_chat/src/models/attachment_file.dart'; import 'package:stream_chat/src/models/channel_model.dart'; import 'package:stream_chat/src/models/channel_state.dart'; import 'package:stream_chat/src/models/event.dart'; +import 'package:stream_chat/src/models/filter.dart'; import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/own_user.dart'; import 'package:stream_chat/src/models/user.dart'; @@ -29,8 +30,6 @@ import 'package:stream_chat/src/platform_detector/platform_detector.dart'; import 'package:stream_chat/version.dart'; import 'package:uuid/uuid.dart'; -import 'package:stream_chat/src/models/filter.dart'; - /// Handler function used for logging records. Function requires a single /// [LogRecord] as the only parameter. typedef LogHandlerFunction = void Function(LogRecord record); @@ -1067,7 +1066,7 @@ class StreamChatClient { String? query, List? sort, PaginationParams? paginationParams, - Map? messageFilters, + Filter? messageFilters, }) async { assert(() { if (query == null && messageFilters == null) { @@ -1516,7 +1515,7 @@ class ClientState { Map get channels => _channelsController.value!; set channels(Map v) { - if (v != null) _channelsController.add(v); + _channelsController.add(v); } final BehaviorSubject> _channelsController = 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 a210501e..6d317d97 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -85,7 +85,7 @@ class ChannelListView extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Filter filter; + final Filter? filter; /// Query channels options. /// 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 4eff2f8f..358623e1 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 @@ -92,7 +92,7 @@ class MessageSearchListView extends StatefulWidget { /// The message query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map? messageFilters; + final Filter? messageFilters; /// Builder used to create a custom item preview final MessageSearchItemBuilder? itemBuilder; 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 d6220cf3..6c9be4f0 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 @@ -59,7 +59,7 @@ class MessageSearchBlocState extends State /// [messagesStream] and [queryMessagesLoading] stream Future search({ required Filter filter, - Map? messageFilter, + Filter? messageFilter, List? sort, String? query, PaginationParams? pagination, diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index 2a2d31f5..cb8c9387 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -85,7 +85,7 @@ class MessageSearchListCore extends StatefulWidget { /// The message query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map? messageFilters; + final Filter? messageFilters; /// The builder that is used when the search messages are fetched final Widget Function(List) childBuilder; From 08813389dd2bb24301e4f2b6344a13d2224a6f36 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 30 Apr 2021 16:22:45 +0200 Subject: [PATCH 169/599] update emoji dependency using local copy --- .../stream_chat_flutter/analysis_options.yaml | 1 + .../lib/src/emoji/emoji.dart | 114464 +++++++++++++++ .../lib/src/extension.dart | 2 +- .../lib/src/message_input.dart | 2 +- packages/stream_chat_flutter/pubspec.yaml | 2 - 5 files changed, 114467 insertions(+), 4 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/emoji/emoji.dart diff --git a/packages/stream_chat_flutter/analysis_options.yaml b/packages/stream_chat_flutter/analysis_options.yaml index 36d1fbb7..370654dc 100644 --- a/packages/stream_chat_flutter/analysis_options.yaml +++ b/packages/stream_chat_flutter/analysis_options.yaml @@ -6,6 +6,7 @@ analyzer: exclude: - lib/**/*.g.dart - example/** + - lib/src/emoji linter: rules: diff --git a/packages/stream_chat_flutter/lib/src/emoji/emoji.dart b/packages/stream_chat_flutter/lib/src/emoji/emoji.dart new file mode 100644 index 00000000..617e372a --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/emoji/emoji.dart @@ -0,0 +1,114464 @@ +// Copyright 2020 Naji. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Naji nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import 'package:collection/collection.dart' show IterableExtension; + +/// All Groups +enum EmojiGroup { + smileysEmotion, + activities, + peopleBody, + objects, + travelPlaces, + component, + animalsNature, + foodDrink, + symbols, + flags +} + +/// All Subgroups +enum EmojiSubgroup { + faceSmiling, + faceAffection, + faceSleepy, + faceTongue, + faceNeutralSkeptical, + faceGlasses, + faceHat, + faceConcerned, + faceNegative, + faceUnwell, + faceHand, + faceCostume, + event, + catFace, + hands, + handFingersClosed, + handFingersPartial, + handSingleFinger, + handFingersOpen, + bodyParts, + handProp, + clothing, + emotion, + personSymbol, + person, + personRole, + personFantasy, + personGesture, + personActivity, + family, + artsCrafts, + office, + hotel, + skyWeather, + hairStyle, + animalMammal, + animalAmphibian, + monkeyFace, + animalBird, + animalBug, + animalReptile, + animalMarine, + foodMarine, + plantOther, + foodVegetable, + placeBuilding, + plantFlower, + placeMap, + foodFruit, + foodAsian, + foodPrepared, + foodSweet, + drink, + dishware, + sport, + tool, + game, + transportGround, + personSport, + transportAir, + personResting, + awardMedal, + placeOther, + lightVideo, + music, + musicalInstrument, + transportWater, + otherObject, + placeGeographic, + placeReligious, + time, + phone, + computer, + science, + household, + money, + medical, + transportSign, + lock, + mail, + bookPaper, + sound, + writing, + religion, + zodiac, + alphanum, + warning, + avSymbol, + otherSymbol, + punctuation, + geometric, + keycap, + arrow, + math, + currency, + gender, + flag, + countryFlag, + subdivisionFlag, + skinTone, + regional +} + +/// List of All Emojis. +final List _emojis = [ + Emoji( + name: 'grinning face', + char: '\u{1F600}', + shortName: 'grinning', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'face', + 'grin', + 'uc6', + 'smiley', + 'happy', + 'silly', + 'laugh', + 'thank you', + 'awesome', + 'smile', + 'friend', + 'pleased', + 'teeth', + 'pacman', + 'fun', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'laughing', + 'lol', + 'rofl', + 'lmao', + 'lmfao', + 'hilarious', + 'ha', + 'haha', + 'chuckle', + 'comedy', + 'giggle', + 'hehe', + 'joyful', + 'laugh out loud', + 'rire', + 'tee hee', + 'jaja', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'smiles', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'please', + 'chill', + 'confident', + 'content', + 'dentist', + 'pac man' + ]), + Emoji( + name: 'grinning face with big eyes', + char: '\u{1F603}', + shortName: 'smiley', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'face', + 'mouth', + 'open', + 'smile', + 'uc6', + 'smiley', + 'happy', + 'silly', + 'laugh', + 'good', + 'smile', + 'teeth', + 'fun', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'laughing', + 'lol', + 'rofl', + 'lmao', + 'lmfao', + 'hilarious', + 'ha', + 'haha', + 'chuckle', + 'comedy', + 'giggle', + 'hehe', + 'joyful', + 'laugh out loud', + 'rire', + 'tee hee', + 'jaja', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'smiles', + 'dentist', + ':-D', + '=D' + ]), + Emoji( + name: 'grinning face with smiling eyes', + char: '\u{1F604}', + shortName: 'smile', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'eye', + 'face', + 'mouth', + 'open', + 'smile', + 'uc6', + 'smiley', + 'happy', + 'laugh', + 'smile', + 'teeth', + 'fun', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'laughing', + 'lol', + 'rofl', + 'lmao', + 'lmfao', + 'hilarious', + 'ha', + 'haha', + 'chuckle', + 'comedy', + 'giggle', + 'hehe', + 'joyful', + 'laugh out loud', + 'rire', + 'tee hee', + 'jaja', + 'smiles', + 'dentist', + ':D' + ]), + Emoji( + name: 'beaming face with smiling eyes', + char: '\u{1F601}', + shortName: 'grin', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'eye', + 'face', + 'grin', + 'smile', + 'uc6', + 'smiley', + 'happy', + 'silly', + 'laugh', + 'thank you', + 'good', + 'beautiful', + 'selfie', + 'smile', + 'friend', + 'teeth', + 'dumb', + 'grimace', + 'fun', + 'proud', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'laughing', + 'lol', + 'rofl', + 'lmao', + 'lmfao', + 'hilarious', + 'ha', + 'haha', + 'chuckle', + 'comedy', + 'giggle', + 'hehe', + 'joyful', + 'laugh out loud', + 'rire', + 'tee hee', + 'jaja', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'smiles', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'dentist', + 'idiot', + 'ignorant', + 'stupid' + ]), + Emoji( + name: 'grinning squinting face', + char: '\u{1F606}', + shortName: 'laughing', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'face', + 'laugh', + 'mouth', + 'open', + 'satisfied', + 'smile', + 'uc6', + 'smiley', + 'happy', + 'silly', + 'laugh', + 'smile', + 'teeth', + 'dumb', + 'fun', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'laughing', + 'lol', + 'rofl', + 'lmao', + 'lmfao', + 'hilarious', + 'ha', + 'haha', + 'chuckle', + 'comedy', + 'giggle', + 'hehe', + 'joyful', + 'laugh out loud', + 'rire', + 'tee hee', + 'jaja', + 'smiles', + 'dentist', + 'idiot', + 'ignorant', + 'stupid', + '>:)', + '>;)', + '>:-)', + '>=)' + ]), + Emoji( + name: 'grinning face with sweat', + char: '\u{1F605}', + shortName: 'sweat_smile', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'cold', + 'face', + 'open', + 'smile', + 'sweat', + 'uc6', + 'smiley', + 'happy', + 'laugh', + 'sweat', + 'smile', + 'tease', + 'drip', + 'guilty', + 'porn', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'laughing', + 'lol', + 'rofl', + 'lmao', + 'lmfao', + 'hilarious', + 'ha', + 'haha', + 'chuckle', + 'comedy', + 'giggle', + 'hehe', + 'joyful', + 'laugh out loud', + 'rire', + 'tee hee', + 'jaja', + 'smiles', + 'joke', + 'kidding', + ':)', + ':-)', + '=)', + ':D', + ':-D', + '=D' + ]), + Emoji( + name: 'face with tears of joy', + char: '\u{1F602}', + shortName: 'joy', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'face', + 'joy', + 'laugh', + 'tear', + 'uc6', + 'smiley', + 'happy', + 'silly', + 'cry', + 'laugh', + 'sarcastic', + 'smile', + 'tease', + 'crazy', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'crying', + 'weeping', + 'weep', + 'sob', + 'sobbing', + 'tear', + 'tears', + 'bawling', + 'laughing', + 'lol', + 'rofl', + 'lmao', + 'lmfao', + 'hilarious', + 'ha', + 'haha', + 'chuckle', + 'comedy', + 'giggle', + 'hehe', + 'joyful', + 'laugh out loud', + 'rire', + 'tee hee', + 'jaja', + 'sarcasm', + 'smiles', + 'joke', + 'kidding', + 'weird', + 'awkward', + 'insane', + 'wild', + ":')", + ":'-)" + ]), + Emoji( + name: 'rolling on the floor laughing', + char: '\u{1F923}', + shortName: 'rofl', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'face', + 'floor', + 'laugh', + 'rolling', + 'uc9', + 'smiley', + 'happy', + 'silly', + 'laugh', + 'tease', + 'crazy', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'laughing', + 'lol', + 'rofl', + 'lmao', + 'lmfao', + 'hilarious', + 'ha', + 'haha', + 'chuckle', + 'comedy', + 'giggle', + 'hehe', + 'joyful', + 'laugh out loud', + 'rire', + 'tee hee', + 'jaja', + 'joke', + 'kidding', + 'weird', + 'awkward', + 'insane', + 'wild' + ]), + Emoji( + name: 'smiling face', + char: '\u{263A}\u{FE0F}', + shortName: 'relaxed', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceAffection, + keywords: [ + 'face', + 'outlined', + 'relaxed', + 'smile', + 'uc1', + 'smiley', + 'happy', + 'beautiful', + 'smile', + 'blush', + 'pleased', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'smiles', + 'blushing', + 'bella', + 'embarrassed', + 'creep', + 'please', + 'chill', + 'confident', + 'content' + ]), + Emoji( + name: 'smiling face with smiling eyes', + char: '\u{1F60A}', + shortName: 'blush', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'blush', + 'eye', + 'face', + 'smile', + 'uc6', + 'smiley', + 'happy', + 'good', + 'beautiful', + 'smile', + 'blush', + 'pleased', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'smiles', + 'blushing', + 'bella', + 'embarrassed', + 'creep', + 'please', + 'chill', + 'confident', + 'content' + ]), + Emoji( + name: 'smiling face with halo', + char: '\u{1F607}', + shortName: 'innocent', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'angel', + 'face', + 'fairy tale', + 'fantasy', + 'halo', + 'innocent', + 'smile', + 'uc6', + 'smiley', + 'silly', + 'pray', + 'smile', + 'blush', + 'fantasy', + 'soul', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'funny', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'smiles', + 'blushing', + 'bella', + 'embarrassed', + 'creep', + 'O:-)', + '0:-3', + '0:3', + '0:-)', + '0:)', + '0;^)', + 'O:)', + 'O;-)', + 'O=)', + '0;-)', + 'O:-3', + 'O:3' + ]), + Emoji( + name: 'slightly smiling face', + char: '\u{1F642}', + shortName: 'slight_smile', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'face', + 'smile', + 'uc7', + 'smiley', + 'happy', + 'awesome', + 'smile', + 'blush', + 'pleased', + 'fun', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'smiles', + 'blushing', + 'bella', + 'embarrassed', + 'creep', + 'please', + 'chill', + 'confident', + 'content', + ':)', + ':-)', + '=]', + '=)', + ':]' + ]), + Emoji( + name: 'upside-down face', + char: '\u{1F643}', + shortName: 'upside_down', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'face', + 'upside-down', + 'uc8', + 'smiley', + 'happy', + 'silly', + 'sarcastic', + 'smile', + 'pleased', + 'dumb', + 'what', + 'clever', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'sarcasm', + 'smiles', + 'please', + 'chill', + 'confident', + 'content', + 'idiot', + 'ignorant', + 'stupid', + 'witty' + ]), + Emoji( + name: 'winking face', + char: '\u{1F609}', + shortName: 'wink', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'face', + 'wink', + 'uc6', + 'smiley', + 'happy', + 'silly', + 'sarcastic', + 'selfie', + 'smile', + 'tease', + 'clever', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'sarcasm', + 'smiles', + 'joke', + 'kidding', + 'witty', + ';)', + ';-)', + '*-)', + '*)', + ';-]', + ';]', + ';D', + ';^)' + ]), + Emoji( + name: 'relieved face', + char: '\u{1F60C}', + shortName: 'relieved', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSleepy, + keywords: [ + 'face', + 'relieved', + 'uc6', + 'smiley', + 'happy', + 'smile', + 'pleased', + 'calm', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'smiles', + 'please', + 'chill', + 'confident', + 'content' + ]), + Emoji( + name: 'smiling face with tear', + char: '\u{1F972}', + shortName: 'smiling_face_with_tear', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceAffection, + keywords: [ + 'uc13', + 'smiley', + 'happy', + 'cry', + 'thank you', + 'beautiful', + 'smile', + 'blush', + 'pleased', + 'drip', + 'hope', + 'proud', + 'sentimental', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'crying', + 'weeping', + 'weep', + 'sob', + 'sobbing', + 'tear', + 'tears', + 'bawling', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'smiles', + 'blushing', + 'bella', + 'embarrassed', + 'creep', + 'please', + 'chill', + 'confident', + 'content', + 'swear', + 'promise', + 'nostalgic', + 'tender', + 'dreamy', + 'touched' + ]), + Emoji( + name: 'smiling face with heart-eyes', + char: '\u{1F60D}', + shortName: 'heart_eyes', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceAffection, + keywords: [ + 'eye', + 'face', + 'love', + 'smile', + 'uc6', + 'smiley', + 'happy', + 'love', + 'heart eyes', + 'beautiful', + 'smile', + 'hola', + 'facebook', + 'porn', + 'heart', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'smiles', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur' + ]), + Emoji( + name: 'smiling face with hearts', + char: '\u{1F970}', + shortName: 'smiling_face_with_3_hearts', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceAffection, + keywords: [ + 'uc11', + 'smiley', + 'wedding', + 'happy', + 'love', + 'hug', + 'smile', + 'friend', + 'blush', + 'pleased', + 'heart', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'embrace', + 'hugs', + 'smiles', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'blushing', + 'bella', + 'embarrassed', + 'creep', + 'please', + 'chill', + 'confident', + 'content', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur' + ]), + Emoji( + name: 'face blowing a kiss', + char: '\u{1F618}', + shortName: 'kissing_heart', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceAffection, + keywords: [ + 'face', + 'kiss', + 'uc6', + 'smiley', + 'wedding', + 'love', + 'sexy', + 'beautiful', + 'disney', + 'kisses', + 'hit', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'cartoon', + 'bisous', + 'beijos', + 'besos', + 'bise', + 'blowing kisses', + 'kissy', + 'punch', + 'pow', + 'bam', + ':*', + ':-*', + '=*', + ':^*' + ]), + Emoji( + name: 'kissing face', + char: '\u{1F617}', + shortName: 'kissing', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceAffection, + keywords: [ + 'face', + 'kiss', + 'uc6', + 'smiley', + 'sexy', + 'beautiful', + 'selfie', + 'kisses', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'bisous', + 'beijos', + 'besos', + 'bise', + 'blowing kisses', + 'kissy' + ]), + Emoji( + name: 'kissing face with smiling eyes', + char: '\u{1F619}', + shortName: 'kissing_smiling_eyes', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceAffection, + keywords: [ + 'eye', + 'face', + 'kiss', + 'smile', + 'uc6', + 'smiley', + 'love', + 'sexy', + 'kisses', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'bisous', + 'beijos', + 'besos', + 'bise', + 'blowing kisses', + 'kissy' + ]), + Emoji( + name: 'kissing face with closed eyes', + char: '\u{1F61A}', + shortName: 'kissing_closed_eyes', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceAffection, + keywords: [ + 'closed', + 'eye', + 'face', + 'kiss', + 'uc6', + 'smiley', + 'love', + 'sexy', + 'blush', + 'kisses', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'blushing', + 'bella', + 'embarrassed', + 'creep', + 'bisous', + 'beijos', + 'besos', + 'bise', + 'blowing kisses', + 'kissy' + ]), + Emoji( + name: 'face savoring food', + char: '\u{1F60B}', + shortName: 'yum', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceTongue, + keywords: [ + 'delicious', + 'face', + 'savouring', + 'smile', + 'um', + 'yum', + 'uc6', + 'smiley', + 'food', + 'happy', + 'silly', + 'sarcastic', + 'good', + 'smile', + 'pink', + 'lick', + 'tongue', + 'dinner', + 'picnic', + 'delicious', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'sarcasm', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'smiles', + 'rose', + 'toung', + 'tounge', + 'lunch', + 'savour' + ]), + Emoji( + name: 'face with tongue', + char: '\u{1F61B}', + shortName: 'stuck_out_tongue', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceTongue, + keywords: [ + 'face', + 'tongue', + 'uc6', + 'smiley', + 'happy', + 'silly', + 'pink', + 'tease', + 'lick', + 'tongue', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'rose', + 'joke', + 'kidding', + 'toung', + 'tounge', + ':P', + ':-P', + '=P', + ':-Þ', + ':Þ', + ':-b', + ':b' + ]), + Emoji( + name: 'squinting face with tongue', + char: '\u{1F61D}', + shortName: 'stuck_out_tongue_closed_eyes', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceTongue, + keywords: [ + 'eye', + 'face', + 'horrible', + 'taste', + 'tongue', + 'uc6', + 'smiley', + 'happy', + 'silly', + 'laugh', + 'pink', + 'tease', + 'grimace', + 'lick', + 'tongue', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'laughing', + 'lol', + 'rofl', + 'lmao', + 'lmfao', + 'hilarious', + 'ha', + 'haha', + 'chuckle', + 'comedy', + 'giggle', + 'hehe', + 'joyful', + 'laugh out loud', + 'rire', + 'tee hee', + 'jaja', + 'rose', + 'joke', + 'kidding', + 'toung', + 'tounge' + ]), + Emoji( + name: 'winking face with tongue', + char: '\u{1F61C}', + shortName: 'stuck_out_tongue_winking_eye', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceTongue, + keywords: [ + 'eye', + 'face', + 'joke', + 'tongue', + 'wink', + 'uc6', + 'smiley', + 'happy', + 'silly', + 'pink', + 'tease', + 'pleased', + 'lick', + 'porn', + 'crazy', + 'tongue', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'rose', + 'joke', + 'kidding', + 'please', + 'chill', + 'confident', + 'content', + 'weird', + 'awkward', + 'insane', + 'wild', + 'toung', + 'tounge', + '>:P', + 'X-P' + ]), + Emoji( + name: 'zany face', + char: '\u{1F92A}', + shortName: 'zany_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceTongue, + keywords: [ + 'eye', + 'large', + 'small', + 'uc10', + 'smiley', + 'silly', + 'nutcase', + 'crazy', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'funny', + 'weird', + 'awkward', + 'insane', + 'wild' + ]), + Emoji( + name: 'face with raised eyebrow', + char: '\u{1F928}', + shortName: 'face_with_raised_eyebrow', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNeutralSkeptical, + keywords: [ + 'uc10', + 'smiley', + 'doubt', + 'jealous', + 'colbert', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical' + ]), + Emoji( + name: 'face with monocle', + char: '\u{1F9D0}', + shortName: 'face_with_monocle', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceGlasses, + keywords: [ + 'uc10', + 'smiley', + 'nerd', + 'rich', + 'mystery', + 'proud', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'smart', + 'geek', + 'serious', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'nerd face', + char: '\u{1F913}', + shortName: 'nerd', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceGlasses, + keywords: [ + 'face', + 'geek', + 'nerd', + 'uc8', + 'smiley', + 'glasses', + 'nerd', + 'google', + 'brain', + 'teeth', + 'dumb', + 'disguise', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'eyeglasses', + 'eye glasses', + 'smart', + 'geek', + 'serious', + 'mind', + 'memory', + 'thought', + 'conscience', + 'dentist', + 'idiot', + 'ignorant', + 'stupid' + ]), + Emoji( + name: 'smiling face with sunglasses', + char: '\u{1F60E}', + shortName: 'sunglasses', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceGlasses, + keywords: [ + 'bright', + 'cool', + 'eye', + 'eyewear', + 'face', + 'glasses', + 'smile', + 'sun', + 'sunglasses', + 'uc6', + 'smiley', + 'happy', + 'silly', + 'glasses', + 'emojione', + 'awesome', + 'beautiful', + 'boys night', + 'smile', + 'sunglasses', + 'hawaii', + 'california', + 'florida', + 'las vegas', + 'fun', + 'summer', + 'clever', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'eyeglasses', + 'eye glasses', + 'emoji one', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'guys night', + 'smiles', + 'shades', + 'lunettes de soleil', + 'sun glasses', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'vegas', + 'weekend', + 'witty', + 'B-)', + 'B)', + '8)', + '8-)', + 'B-D', + '8-D' + ]), + Emoji( + name: 'star-struck', + char: '\u{1F929}', + shortName: 'star_struck', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceAffection, + keywords: [ + 'uc10', + 'smiley', + 'happy', + 'selfie', + 'fame', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'partying face', + char: '\u{1F973}', + shortName: 'partying_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceHat, + keywords: [ + 'uc11', + 'smiley', + 'holidays', + 'happy', + 'silly', + 'hat', + 'cheers', + 'happy birthday', + 'confetti', + 'celebrate', + 'fun', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'holiday', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'hats', + 'cap', + 'caps', + 'gān bēi', + 'Na zdravi', + 'Proost', + 'Prost', + 'SlÃĄinte', + 'Cin cin', + 'Kanpai', + 'Na zdrowie', + 'SaÃēde', + 'На ĐˇĐ´ĐžŅ€ĐžĐ˛ŅŒĐĩ', + 'Salud', + 'SkÃĨl', + 'Sei gesund', + 'santÊ', + 'Bon anniversaire', + 'joyeux anniversaire', + 'buon compleanno', + 'feliz cumpleaÃąos', + 'alles Gute zum Geburtstag', + 'feliz AniversÃĄrio', + 'Gratulerer med dagen', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar' + ]), + Emoji( + name: 'smirking face', + char: '\u{1F60F}', + shortName: 'smirk', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNeutralSkeptical, + keywords: [ + 'face', + 'smirk', + 'uc6', + 'smiley', + 'happy', + 'silly', + 'sexy', + 'sarcastic', + 'smile', + 'pleased', + 'clever', + 'proud', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'sarcasm', + 'smiles', + 'please', + 'chill', + 'confident', + 'content', + 'witty' + ]), + Emoji( + name: 'unamused face', + char: '\u{1F612}', + shortName: 'unamused', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNeutralSkeptical, + keywords: [ + 'face', + 'unamused', + 'unhappy', + 'uc6', + 'smiley', + 'sad', + 'tired', + 'angry', + 'bored', + 'hate', + 'doubt', + 'grimace', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'boring', + 'agree', + 'whatever', + 'boredom', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical' + ]), + Emoji( + name: 'disappointed face', + char: '\u{1F61E}', + shortName: 'disappointed', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'disappointed', + 'face', + 'uc6', + 'smiley', + 'sad', + 'tired', + 'angry', + 'bored', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'boring', + 'agree', + 'whatever', + 'boredom', + '>:[', + ':-(', + ':(', + ':-[', + ':[', + '=(' + ]), + Emoji( + name: 'pensive face', + char: '\u{1F614}', + shortName: 'pensive', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSleepy, + keywords: [ + 'dejected', + 'face', + 'pensive', + 'uc6', + 'smiley', + 'sad', + 'rip', + 'guilty', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'rest in peace' + ]), + Emoji( + name: 'worried face', + char: '\u{1F61F}', + shortName: 'worried', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'face', + 'worried', + 'uc6', + 'smiley', + 'sad', + 'angry', + 'doubt', + 'guilty', + 'jealous', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical' + ]), + Emoji( + name: 'confused face', + char: '\u{1F615}', + shortName: 'confused', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'confused', + 'face', + 'uc6', + 'smiley', + 'nurse', + 'doubt', + 'what', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + '>:\\', + '>:/', + ':-/', + ':-.', + ':/', + ':\\', + '=/', + '=\\', + ':L', + '=L' + ]), + Emoji( + name: 'slightly frowning face', + char: '\u{1F641}', + shortName: 'slight_frown', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'face', + 'frown', + 'uc7', + 'smiley', + 'sad', + 'angry', + 'hate', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no' + ]), + Emoji( + name: 'frowning face', + char: '\u{2639}\u{FE0F}', + shortName: 'frowning2', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'face', + 'frown', + 'uc1', + 'smiley', + 'sad', + 'angry', + 'hate', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no' + ]), + Emoji( + name: 'persevering face', + char: '\u{1F623}', + shortName: 'persevere', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'face', + 'persevere', + 'uc6', + 'smiley', + 'angry', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + '>.<' + ]), + Emoji( + name: 'confounded face', + char: '\u{1F616}', + shortName: 'confounded', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'confounded', + 'face', + 'uc6', + 'smiley', + 'angry', + 'wow', + 'hate', + 'stinky', + 'ugly', + 'confused', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'smell', + 'stink', + 'odor', + 'perplexed' + ]), + Emoji( + name: 'tired face', + char: '\u{1F62B}', + shortName: 'tired_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'face', + 'tired', + 'uc6', + 'smiley', + 'sad', + 'tired', + 'angry', + 'sick', + 'wow', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown' + ]), + Emoji( + name: 'weary face', + char: '\u{1F629}', + shortName: 'weary', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'face', + 'tired', + 'weary', + 'uc6', + 'smiley', + 'sad', + 'tired', + 'angry', + 'stressed', + 'wow', + 'shame', + 'lazy', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown' + ]), + Emoji( + name: 'pleading face', + char: '\u{1F97A}', + shortName: 'pleading_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'uc11', + 'smiley', + 'sad', + 'cry', + 'condolence', + 'omg', + 'heartbreak', + 'blush', + 'begging', + 'doubt', + 'guilty', + 'help', + 'shame', + 'hope', + 'liar', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'crying', + 'weeping', + 'weep', + 'sob', + 'sobbing', + 'tear', + 'tears', + 'bawling', + 'compassion', + 'omfg', + 'oh my god', + 'broken heart', + 'heartbroken', + 'blushing', + 'bella', + 'embarrassed', + 'creep', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'swear', + 'promise', + 'lies', + 'lying' + ]), + Emoji( + name: 'crying face', + char: '\u{1F622}', + shortName: 'cry', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'cry', + 'face', + 'sad', + 'tear', + 'uc6', + 'smiley', + 'sad', + 'cry', + 'rip', + 'heartbreak', + 'drip', + 'guilty', + 'covid', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'crying', + 'weeping', + 'weep', + 'sob', + 'sobbing', + 'tear', + 'tears', + 'bawling', + 'rest in peace', + 'broken heart', + 'heartbroken', + ":'(", + ":'-(", + ';(', + ';-(' + ]), + Emoji( + name: 'loudly crying face', + char: '\u{1F62D}', + shortName: 'sob', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'cry', + 'face', + 'sad', + 'sob', + 'tear', + 'uc6', + 'smiley', + 'sad', + 'cry', + 'rip', + 'heartbreak', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'crying', + 'weeping', + 'weep', + 'sob', + 'sobbing', + 'tear', + 'tears', + 'bawling', + 'rest in peace', + 'broken heart', + 'heartbroken' + ]), + Emoji( + name: 'face with steam from nose', + char: '\u{1F624}', + shortName: 'triumph', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNegative, + keywords: [ + 'face', + 'triumph', + 'won', + 'uc6', + 'smiley', + 'angry', + 'smoking', + 'steam', + 'breathe', + 'proud', + 'festivus', + 'booger', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'smoke', + 'cigarette', + 'puff', + 'steaming', + 'piping', + 'sigh', + 'inhale' + ]), + Emoji( + name: 'angry face', + char: '\u{1F620}', + shortName: 'angry', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNegative, + keywords: [ + 'angry', + 'face', + 'mad', + 'uc6', + 'smiley', + 'angry', + 'hate', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + '>:(', + '>:-(', + ':@' + ]), + Emoji( + name: 'pouting face', + char: '\u{1F621}', + shortName: 'rage', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNegative, + keywords: [ + 'angry', + 'face', + 'mad', + 'pouting', + 'rage', + 'red', + 'uc6', + 'smiley', + 'angry', + 'hate', + 'bitch', + 'donald trump', + 'guilty', + 'las vegas', + 'killer', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'trump', + 'vegas', + 'savage', + 'scary clown' + ]), + Emoji( + name: 'face with symbols on mouth', + char: '\u{1F92C}', + shortName: 'face_with_symbols_over_mouth', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNegative, + keywords: [ + 'uc10', + 'smiley', + 'angry', + 'hate', + 'donald trump', + 'swearing', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'trump', + 'cussing', + 'cursing' + ]), + Emoji( + name: 'exploding head', + char: '\u{1F92F}', + shortName: 'exploding_head', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceUnwell, + keywords: [ + 'shocked', + 'uc10', + 'smiley', + 'angry', + 'wow', + 'omg', + 'donald trump', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown', + 'omfg', + 'oh my god', + 'trump' + ]), + Emoji( + name: 'flushed face', + char: '\u{1F633}', + shortName: 'flushed', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'dazed', + 'face', + 'flushed', + 'uc6', + 'smiley', + 'omg', + 'blush', + 'guilty', + 'porn', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'omfg', + 'oh my god', + 'blushing', + 'bella', + 'embarrassed', + 'creep', + ':\$', + '=\$' + ]), + Emoji( + name: 'hot face', + char: '\u{1F975}', + shortName: 'hot_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceUnwell, + keywords: [ + 'uc11', + 'weather', + 'smiley', + 'stressed', + 'sweat', + 'hot', + 'hate', + 'summer', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'weekend' + ]), + Emoji( + name: 'cold face', + char: '\u{1F976}', + shortName: 'cold_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceUnwell, + keywords: [ + 'uc11', + 'weather', + 'smiley', + 'winter', + 'snow', + 'cold', + 'grimace', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'freeze', + 'frozen', + 'frost', + 'ice cube', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'face screaming in fear', + char: '\u{1F631}', + shortName: 'scream', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'face', + 'fear', + 'fearful', + 'munch', + 'scared', + 'scream', + 'uc6', + 'smiley', + 'halloween', + 'wow', + 'omg', + 'donald trump', + 'fame', + 'porn', + 'ugly', + 'what', + 'crazy', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'samhain', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown', + 'omfg', + 'oh my god', + 'trump', + 'famous', + 'celebrity', + 'weird', + 'awkward', + 'insane', + 'wild' + ]), + Emoji( + name: 'fearful face', + char: '\u{1F628}', + shortName: 'fearful', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'face', + 'fear', + 'fearful', + 'scared', + 'uc6', + 'smiley', + 'halloween', + 'stressed', + 'wow', + 'guilty', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'samhain', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown', + 'D:' + ]), + Emoji( + name: 'anxious face with sweat', + char: '\u{1F630}', + shortName: 'cold_sweat', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'blue', + 'cold', + 'face', + 'mouth', + 'open', + 'rushed', + 'sweat', + 'uc6', + 'smiley', + 'halloween', + 'angry', + 'stressed', + 'sweat', + 'drip', + 'porn', + 'covid', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'samhain', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad' + ]), + Emoji( + name: 'sad but relieved face', + char: '\u{1F625}', + shortName: 'disappointed_relieved', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'disappointed', + 'face', + 'relieved', + 'whew', + 'uc6', + 'smiley', + 'sad', + 'cry', + 'stressed', + 'sweat', + 'calm', + 'drip', + 'guilty', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'crying', + 'weeping', + 'weep', + 'sob', + 'sobbing', + 'tear', + 'tears', + 'bawling' + ]), + Emoji( + name: 'downcast face with sweat', + char: '\u{1F613}', + shortName: 'sweat', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'cold', + 'face', + 'sweat', + 'uc6', + 'smiley', + 'sad', + 'stressed', + 'sweat', + 'drip', + 'guilty', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + "':(", + "':-(", + "'=(" + ]), + Emoji( + name: 'hugging face', + char: '\u{1F917}', + shortName: 'hugging', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceHand, + keywords: [ + 'face', + 'hug', + 'hugging', + 'uc8', + 'smiley', + 'happy', + 'tired', + 'love', + 'hug', + 'thank you', + 'friend', + 'blush', + 'facebook', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'embrace', + 'hugs', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'blushing', + 'bella', + 'embarrassed', + 'creep' + ]), + Emoji( + name: 'thinking face', + char: '\u{1F914}', + shortName: 'thinking', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceHand, + keywords: [ + 'face', + 'thinking', + 'uc8', + 'smiley', + 'boys night', + 'dream', + 'brain', + 'doubt', + 'idea', + 'confused', + 'what', + 'mystery', + 'innovate', + 'question', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'guys night', + 'dreams', + 'mind', + 'memory', + 'thought', + 'conscience', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'perplexed', + 'innovation', + 'inquire', + 'quiz', + 'puzzled' + ]), + Emoji( + name: 'face with hand over mouth', + char: '\u{1F92D}', + shortName: 'face_with_hand_over_mouth', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceHand, + keywords: [ + 'uc10', + 'smiley', + 'tired', + 'blush', + 'tease', + 'quiet', + 'what', + 'secret', + 'yawn', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted', + 'blushing', + 'bella', + 'embarrassed', + 'creep', + 'joke', + 'kidding', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'shhhhh' + ]), + Emoji( + name: 'yawning face', + char: '\u{1F971}', + shortName: 'yawning_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'uc12', + 'smiley', + 'tired', + 'goodnight', + 'bored', + 'calm', + 'quiet', + 'wait', + 'yawn', + 'lazy', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted', + 'boring', + 'agree', + 'whatever', + 'boredom', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'hours' + ]), + Emoji( + name: 'shushing face', + char: '\u{1F92B}', + shortName: 'shushing_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceHand, + keywords: [ + 'quiet', + 'shush', + 'uc10', + 'smiley', + 'quiet', + 'secret', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'shhhhh' + ]), + Emoji( + name: 'lying face', + char: '\u{1F925}', + shortName: 'lying_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNeutralSkeptical, + keywords: [ + 'face', + 'lie', + 'pinocchio', + 'uc9', + 'smiley', + 'donald trump', + 'guilty', + 'crazy', + 'liar', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'trump', + 'weird', + 'awkward', + 'insane', + 'wild', + 'lies', + 'lying' + ]), + Emoji( + name: 'face without mouth', + char: '\u{1F636}', + shortName: 'no_mouth', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNeutralSkeptical, + keywords: [ + 'face', + 'mouth', + 'quiet', + 'silent', + 'uc6', + 'smiley', + 'neutral', + 'hate', + 'dumb', + 'quiet', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'idiot', + 'ignorant', + 'stupid', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + ':-X', + ':X', + ':-#', + ':#', + '=X', + '=#' + ]), + Emoji( + name: 'neutral face', + char: '\u{1F610}', + shortName: 'neutral_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNeutralSkeptical, + keywords: [ + 'deadpan', + 'face', + 'neutral', + 'uc6', + 'smiley', + 'shrug', + 'neutral', + 'bored', + 'calm', + 'doubt', + 'dumb', + 'quiet', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'boring', + 'agree', + 'whatever', + 'boredom', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh' + ]), + Emoji( + name: 'expressionless face', + char: '\u{1F611}', + shortName: 'expressionless', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNeutralSkeptical, + keywords: [ + 'expressionless', + 'face', + 'inexpressive', + 'unexpressive', + 'uc6', + 'smiley', + 'neutral', + 'bored', + 'calm', + 'doubt', + 'dumb', + 'quiet', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'boring', + 'agree', + 'whatever', + 'boredom', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + '-_-', + '-__-', + '-___-' + ]), + Emoji( + name: 'grimacing face', + char: '\u{1F62C}', + shortName: 'grimacing', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNeutralSkeptical, + keywords: [ + 'face', + 'grimace', + 'uc6', + 'smiley', + 'silly', + 'selfie', + 'teeth', + 'grimace', + 'help', + 'porn', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'funny', + 'dentist' + ]), + Emoji( + name: 'face with rolling eyes', + char: '\u{1F644}', + shortName: 'rolling_eyes', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNeutralSkeptical, + keywords: [ + 'eyes', + 'face', + 'rolling', + 'uc8', + 'smiley', + 'rolling eyes', + 'sarcastic', + 'bored', + 'hate', + 'doubt', + 'eyeroll', + 'jealous', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'eye roll', + 'side eye', + 'sarcasm', + 'boring', + 'agree', + 'whatever', + 'boredom', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical' + ]), + Emoji( + name: 'hushed face', + char: '\u{1F62F}', + shortName: 'hushed', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'face', + 'hushed', + 'stunned', + 'surprised', + 'uc6', + 'smiley', + 'wow', + 'what', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown' + ]), + Emoji( + name: 'frowning face with open mouth', + char: '\u{1F626}', + shortName: 'frowning', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'face', + 'frown', + 'mouth', + 'open', + 'uc6', + 'smiley', + 'sad', + 'jealous', + 'what', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness' + ]), + Emoji( + name: 'anguished face', + char: '\u{1F627}', + shortName: 'anguished', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'anguished', + 'face', + 'uc6', + 'smiley', + 'sad', + 'stressed', + 'wow', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown' + ]), + Emoji( + name: 'face with open mouth', + char: '\u{1F62E}', + shortName: 'open_mouth', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'face', + 'mouth', + 'open', + 'sympathy', + 'uc6', + 'smiley', + 'wow', + 'dumb', + 'what', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown', + 'idiot', + 'ignorant', + 'stupid', + ':-O', + ':O', + 'O_O', + '>:O' + ]), + Emoji( + name: 'astonished face', + char: '\u{1F632}', + shortName: 'astonished', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceConcerned, + keywords: [ + 'astonished', + 'face', + 'shocked', + 'totally', + 'uc6', + 'smiley', + 'wow', + 'omg', + 'donald trump', + 'crazy', + 'mystery', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown', + 'omfg', + 'oh my god', + 'trump', + 'weird', + 'awkward', + 'insane', + 'wild' + ]), + Emoji( + name: 'sleeping face', + char: '\u{1F634}', + shortName: 'sleeping', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSleepy, + keywords: [ + 'face', + 'sleep', + 'zzz', + 'uc6', + 'smiley', + 'tired', + 'goodnight', + 'coffee', + 'dream', + 'calm', + 'lazy', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted', + 'starbucks', + 'dreams' + ]), + Emoji( + name: 'drooling face', + char: '\u{1F924}', + shortName: 'drooling_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSleepy, + keywords: [ + 'drooling', + 'face', + 'uc9', + 'smiley', + 'beautiful', + 'dumb', + 'porn', + 'ugly', + 'what', + 'crazy', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'idiot', + 'ignorant', + 'stupid', + 'weird', + 'awkward', + 'insane', + 'wild' + ]), + Emoji( + name: 'sleepy face', + char: '\u{1F62A}', + shortName: 'sleepy', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSleepy, + keywords: [ + 'face', + 'sleep', + 'uc6', + 'smiley', + 'sad', + 'sick', + 'costume', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'triste', + 'depression', + 'negative', + 'sadness', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew' + ]), + Emoji( + name: 'dizzy face', + char: '\u{1F635}', + shortName: 'dizzy_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceUnwell, + keywords: [ + 'dizzy', + 'face', + 'uc6', + 'smiley', + 'dead', + 'wow', + 'nutcase', + 'omg', + 'hate', + 'drunk', + 'dumb', + 'las vegas', + 'what', + 'crazy', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown', + 'omfg', + 'oh my god', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'flustered', + 'dizzy', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild', + '#-)', + '#)', + '%-)', + '%)', + 'X)', + 'X-)' + ]), + Emoji( + name: 'zipper-mouth face', + char: '\u{1F910}', + shortName: 'zipper_mouth', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNeutralSkeptical, + keywords: [ + 'face', + 'mouth', + 'zipper', + 'uc8', + 'smiley', + 'angry', + 'fight', + 'dumb', + 'quiet', + 'crazy', + 'secret', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'idiot', + 'ignorant', + 'stupid', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'weird', + 'awkward', + 'insane', + 'wild', + 'shhhhh' + ]), + Emoji( + name: 'woozy face', + char: '\u{1F974}', + shortName: 'woozy_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceUnwell, + keywords: [ + 'uc11', + 'smiley', + 'silly', + 'drugs', + 'sick', + 'drunk', + 'dumb', + 'ugly', + 'crazy', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'funny', + 'drug', + 'narcotics', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'flustered', + 'dizzy', + 'idiot', + 'ignorant', + 'stupid', + 'weird', + 'awkward', + 'insane', + 'wild' + ]), + Emoji( + name: 'nauseated face', + char: '\u{1F922}', + shortName: 'nauseated_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceUnwell, + keywords: [ + 'face', + 'nauseated', + 'vomit', + 'uc9', + 'smiley', + 'bathroom', + 'sick', + 'hate', + 'drunk', + 'stinky', + 'donald trump', + 'poison', + 'full', + 'Nauseated', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'flustered', + 'dizzy', + 'smell', + 'stink', + 'odor', + 'trump', + 'toxic', + 'toxins', + 'green face' + ]), + Emoji( + name: 'face vomiting', + char: '\u{1F92E}', + shortName: 'face_vomiting', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceUnwell, + keywords: [ + 'sick', + 'vomit', + 'uc10', + 'smiley', + 'bathroom', + 'sick', + 'hate', + 'donald trump', + 'Nauseated', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'trump', + 'green face' + ]), + Emoji( + name: 'sneezing face', + char: '\u{1F927}', + shortName: 'sneezing_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceUnwell, + keywords: [ + 'face', + 'gesundheit', + 'sneeze', + 'uc9', + 'smiley', + 'sick', + 'nurse', + 'stinky', + 'booger', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'smell', + 'stink', + 'odor' + ]), + Emoji( + name: 'face with medical mask', + char: '\u{1F637}', + shortName: 'mask', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceUnwell, + keywords: [ + 'cold', + 'doctor', + 'face', + 'mask', + 'medicine', + 'sick', + 'uc6', + 'smiley', + 'dead', + 'health', + 'sick', + 'teeth', + 'nurse', + 'clean', + 'poison', + 'mask', + 'virus', + 'covid', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'dentist', + 'toxic', + 'toxins', + 'corona' + ]), + Emoji( + name: 'face with thermometer', + char: '\u{1F912}', + shortName: 'thermometer_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceUnwell, + keywords: [ + 'face', + 'ill', + 'sick', + 'thermometer', + 'uc8', + 'smiley', + 'health', + 'sick', + 'nurse', + 'virus', + 'covid', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'corona' + ]), + Emoji( + name: 'face with head-bandage', + char: '\u{1F915}', + shortName: 'head_bandage', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceUnwell, + keywords: [ + 'bandage', + 'face', + 'hurt', + 'injury', + 'uc8', + 'smiley', + 'health', + 'sick', + 'nurse', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew' + ]), + Emoji( + name: 'money-mouth face', + char: '\u{1F911}', + shortName: 'money_mouth', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceTongue, + keywords: [ + 'face', + 'money', + 'mouth', + 'uc8', + 'smiley', + 'money', + 'win', + 'boys night', + 'power', + 'stinky', + 'coins', + 'discount', + 'donald trump', + 'jealous', + 'las vegas', + 'rich', + 'greed', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'cash', + 'dollars', + 'dollar', + 'bucks', + 'currency', + 'funds', + 'payment', + 'money face', + 'reward', + 'thief', + 'bank', + 'benjamins', + 'argent', + 'dinero', + 'i soldi', + 'Geld', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'guys night', + 'smell', + 'stink', + 'odor', + 'sale', + 'bargain', + 'trump', + 'vegas', + 'grand', + 'expensive', + 'fancy', + 'selfish' + ]), + Emoji( + name: 'cowboy hat face', + char: '\u{1F920}', + shortName: 'cowboy', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceHat, + keywords: [ + 'cowboy', + 'cowgirl', + 'face', + 'hat', + 'uc9', + 'smiley', + 'america', + 'hat', + 'halloween', + 'boys night', + 'magic', + 'disney', + 'fame', + 'super hero', + 'texas', + 'costume', + 'independence day', + 'disguise', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'usa', + 'united states', + 'united states of america', + 'american', + 'hats', + 'cap', + 'caps', + 'samhain', + 'guys night', + 'spell', + 'genie', + 'magical', + 'cartoon', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + '4th of july' + ]), + Emoji( + name: 'disguised face', + char: '\u{1F978}', + shortName: 'disguised_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceHat, + keywords: [ + 'uc13', + 'smiley', + 'silly', + 'halloween', + 'eyes', + 'boys night', + 'celebrate', + 'crazy', + 'mystery', + 'costume', + 'clever', + 'disguise', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'funny', + 'samhain', + 'eye', + 'eyebrow', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'weird', + 'awkward', + 'insane', + 'wild', + 'witty' + ]), + Emoji( + name: 'smiling face with horns', + char: '\u{1F608}', + shortName: 'smiling_imp', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNegative, + keywords: [ + 'face', + 'fairy tale', + 'fantasy', + 'horns', + 'smile', + 'uc6', + 'smiley', + 'silly', + 'halloween', + 'angry', + 'monster', + 'boys night', + 'evil', + 'guilty', + 'jealous', + 'porn', + 'crazy', + 'killer', + 'disguise', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'funny', + 'samhain', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'monsters', + 'beast', + 'guys night', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'weird', + 'awkward', + 'insane', + 'wild', + 'savage', + 'scary clown' + ]), + Emoji( + name: 'angry face with horns', + char: '\u{1F47F}', + shortName: 'imp', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNegative, + keywords: [ + 'demon', + 'devil', + 'face', + 'fairy tale', + 'fantasy', + 'imp', + 'uc6', + 'smiley', + 'halloween', + 'angry', + 'monster', + 'wth', + 'fight', + 'evil', + 'dumb', + 'vampire', + 'crazy', + 'killer', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'samhain', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'monsters', + 'beast', + 'what the hell', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'idiot', + 'ignorant', + 'stupid', + 'dracula', + 'weird', + 'awkward', + 'insane', + 'wild', + 'savage', + 'scary clown' + ]), + Emoji( + name: 'ogre', + char: '\u{1F479}', + shortName: 'japanese_ogre', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceCostume, + keywords: [ + 'uc6', + 'halloween', + 'japan', + 'angry', + 'monster', + 'wow', + 'evil', + 'super hero', + 'ugly', + 'crazy', + 'killer', + 'disguise', + 'samhain', + 'japanese', + 'ninja', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'monsters', + 'beast', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'superhero', + 'superman', + 'batman', + 'weird', + 'awkward', + 'insane', + 'wild', + 'savage', + 'scary clown' + ]), + Emoji( + name: 'goblin', + char: '\u{1F47A}', + shortName: 'japanese_goblin', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceCostume, + keywords: [ + 'creature', + 'face', + 'fairy tale', + 'fantasy', + 'monster', + 'uc6', + 'halloween', + 'japan', + 'angry', + 'monster', + 'wow', + 'mustache', + 'evil', + 'super hero', + 'ugly', + 'crazy', + 'killer', + 'mask', + 'disguise', + 'samhain', + 'japanese', + 'ninja', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'monsters', + 'beast', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'superhero', + 'superman', + 'batman', + 'weird', + 'awkward', + 'insane', + 'wild', + 'savage', + 'scary clown' + ]), + Emoji( + name: 'clown face', + char: '\u{1F921}', + shortName: 'clown', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceCostume, + keywords: [ + 'clown', + 'face', + 'uc9', + 'smiley', + 'silly', + 'halloween', + 'laugh', + 'circus', + 'magic', + 'donald trump', + 'mcdonalds', + 'super hero', + 'crazy', + 'killer', + 'costume', + 'disguise', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'funny', + 'samhain', + 'laughing', + 'lol', + 'rofl', + 'lmao', + 'lmfao', + 'hilarious', + 'ha', + 'haha', + 'chuckle', + 'comedy', + 'giggle', + 'hehe', + 'joyful', + 'laugh out loud', + 'rire', + 'tee hee', + 'jaja', + 'circus tent', + 'clown', + 'clowns', + 'spell', + 'genie', + 'magical', + 'trump', + 'ronald mcdonald', + 'macdo', + 'superhero', + 'superman', + 'batman', + 'weird', + 'awkward', + 'insane', + 'wild', + 'savage', + 'scary clown' + ]), + Emoji( + name: 'pile of poo', + char: '\u{1F4A9}', + shortName: 'poop', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceCostume, + keywords: [ + 'comic', + 'dung', + 'face', + 'monster', + 'poo', + 'poop', + 'uc6', + 'silly', + 'bathroom', + 'dead', + 'sol', + 'diarrhea', + 'shit', + 'bitch', + 'stinky', + 'donald trump', + 'dumb', + 'ugly', + 'funny', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'shit outta luck', + 'shit out of luck', + 'bad luck', + 'shits', + 'the shits', + 'poop', + 'turd', + 'feces', + 'pile', + 'merde', + 'butthole', + 'caca', + 'crap', + 'dirty', + 'pooo', + 'mess', + 'brown', + 'poopoo', + 'puta', + 'pute', + 'smell', + 'stink', + 'odor', + 'trump', + 'idiot', + 'ignorant', + 'stupid' + ]), + Emoji( + name: 'ghost', + char: '\u{1F47B}', + shortName: 'ghost', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceCostume, + keywords: [ + 'creature', + 'face', + 'fairy tale', + 'fantasy', + 'monster', + 'uc6', + 'holidays', + 'halloween', + 'dead', + 'monster', + 'wow', + 'disney', + 'pacman', + 'disguise', + 'holiday', + 'samhain', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'monsters', + 'beast', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown', + 'cartoon', + 'pac man' + ]), + Emoji( + name: 'skull', + char: '\u{1F480}', + shortName: 'skull', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNegative, + keywords: [ + 'death', + 'face', + 'fairy tale', + 'monster', + 'uc6', + 'halloween', + 'dead', + 'skull', + 'wow', + 'harry potter', + 'pirate', + 'poison', + 'super hero', + 'killer', + 'bones', + 'samhain', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'skull and crossbones', + 'skeleton', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown', + 'toxic', + 'toxins', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'Os', + 'hueso' + ]), + Emoji( + name: 'skull and crossbones', + char: '\u{2620}\u{FE0F}', + shortName: 'skull_crossbones', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceNegative, + keywords: [ + 'crossbones', + 'death', + 'face', + 'monster', + 'skull', + 'uc1', + 'halloween', + 'dead', + 'skull', + 'wow', + 'deadpool', + 'pirate', + 'danger', + 'disney', + 'poison', + 'killer', + 'bones', + 'samhain', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'skull and crossbones', + 'skeleton', + 'surprised', + 'scared', + 'shocked', + 'whoa', + 'surprise', + 'scary', + 'nervous', + 'shaking', + 'afraid', + 'amaze', + 'amazing', + 'creepy', + 'cringe', + 'gasp', + 'anxious', + 'mind blown', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'cartoon', + 'toxic', + 'toxins', + 'savage', + 'scary clown', + 'Os', + 'hueso' + ]), + Emoji( + name: 'alien', + char: '\u{1F47D}', + shortName: 'alien', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceCostume, + keywords: [ + 'creature', + 'extraterrestrial', + 'face', + 'fairy tale', + 'fantasy', + 'monster', + 'ufo', + 'uc6', + 'halloween', + 'space', + 'monster', + 'alien', + 'scientology', + 'star wars', + 'disguise', + 'samhain', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'monsters', + 'beast', + 'ufo', + 'scientologist' + ]), + Emoji( + name: 'alien monster', + char: '\u{1F47E}', + shortName: 'space_invader', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceCostume, + keywords: [ + 'alien', + 'creature', + 'extraterrestrial', + 'face', + 'fairy tale', + 'fantasy', + 'monster', + 'ufo', + 'uc6', + 'halloween', + 'space', + 'monster', + 'alien', + 'star wars', + 'vintage', + 'pacman', + 'samhain', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'monsters', + 'beast', + 'ufo', + 'pac man' + ]), + Emoji( + name: 'robot', + char: '\u{1F916}', + shortName: 'robot', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceCostume, + keywords: [ + 'face', + 'monster', + 'robot', + 'uc8', + 'halloween', + 'monster', + 'disney', + 'drone', + 'samhain', + 'monsters', + 'beast', + 'cartoon' + ]), + Emoji( + name: 'jack-o-lantern', + char: '\u{1F383}', + shortName: 'jack_o_lantern', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'celebration', + 'halloween', + 'jack', + 'lantern', + 'uc6', + 'holidays', + 'halloween', + 'pumpkin', + 'minecraft', + 'holiday', + 'samhain', + 'jack o lantern', + 'zucca', + 'citrouille' + ]), + Emoji( + name: 'grinning cat', + char: '\u{1F63A}', + shortName: 'smiley_cat', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.catFace, + keywords: [ + 'cat', + 'face', + 'mouth', + 'open', + 'smile', + 'uc6', + 'animal', + 'happy', + 'silly', + 'cat', + 'animals', + 'animal kingdom', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow' + ]), + Emoji( + name: 'grinning cat with smiling eyes', + char: '\u{1F638}', + shortName: 'smile_cat', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.catFace, + keywords: [ + 'cat', + 'eye', + 'face', + 'grin', + 'smile', + 'uc6', + 'animal', + 'happy', + 'silly', + 'cat', + 'porn', + 'animals', + 'animal kingdom', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow' + ]), + Emoji( + name: 'cat with tears of joy', + char: '\u{1F639}', + shortName: 'joy_cat', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.catFace, + keywords: [ + 'cat', + 'face', + 'joy', + 'tear', + 'uc6', + 'animal', + 'happy', + 'silly', + 'cry', + 'laugh', + 'cat', + 'sarcastic', + 'tease', + 'animals', + 'animal kingdom', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'crying', + 'weeping', + 'weep', + 'sob', + 'sobbing', + 'tear', + 'tears', + 'bawling', + 'laughing', + 'lol', + 'rofl', + 'lmao', + 'lmfao', + 'hilarious', + 'ha', + 'haha', + 'chuckle', + 'comedy', + 'giggle', + 'hehe', + 'joyful', + 'laugh out loud', + 'rire', + 'tee hee', + 'jaja', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow', + 'sarcasm', + 'joke', + 'kidding' + ]), + Emoji( + name: 'smiling cat with heart-eyes', + char: '\u{1F63B}', + shortName: 'heart_eyes_cat', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.catFace, + keywords: [ + 'cat', + 'eye', + 'face', + 'love', + 'smile', + 'uc6', + 'animal', + 'happy', + 'love', + 'cat', + 'heart eyes', + 'beautiful', + 'pussy', + 'porn', + 'animals', + 'animal kingdom', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'condom' + ]), + Emoji( + name: 'cat with wry smile', + char: '\u{1F63C}', + shortName: 'smirk_cat', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.catFace, + keywords: [ + 'cat', + 'face', + 'ironic', + 'smile', + 'wry', + 'uc6', + 'animal', + 'cat', + 'animals', + 'animal kingdom', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow' + ]), + Emoji( + name: 'kissing cat', + char: '\u{1F63D}', + shortName: 'kissing_cat', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.catFace, + keywords: [ + 'cat', + 'eye', + 'face', + 'kiss', + 'uc6', + 'animal', + 'love', + 'cat', + 'kisses', + 'animals', + 'animal kingdom', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow', + 'bisous', + 'beijos', + 'besos', + 'bise', + 'blowing kisses', + 'kissy' + ]), + Emoji( + name: 'weary cat', + char: '\u{1F640}', + shortName: 'scream_cat', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.catFace, + keywords: [ + 'cat', + 'face', + 'oh', + 'surprised', + 'weary', + 'uc6', + 'animal', + 'cat', + 'animals', + 'animal kingdom', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow' + ]), + Emoji( + name: 'crying cat', + char: '\u{1F63F}', + shortName: 'crying_cat_face', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.catFace, + keywords: [ + 'cat', + 'cry', + 'face', + 'sad', + 'tear', + 'uc6', + 'animal', + 'cry', + 'cat', + 'animals', + 'animal kingdom', + 'crying', + 'weeping', + 'weep', + 'sob', + 'sobbing', + 'tear', + 'tears', + 'bawling', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow' + ]), + Emoji( + name: 'pouting cat', + char: '\u{1F63E}', + shortName: 'pouting_cat', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.catFace, + keywords: [ + 'cat', + 'face', + 'pouting', + 'uc6', + 'animal', + 'cat', + 'animals', + 'animal kingdom', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow' + ]), + Emoji( + name: 'palms up together', + char: '\u{1F932}', + shortName: 'palms_up_together', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'uc10', + 'diversity', + 'body', + 'hands', + 'pray', + 'soul', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering' + ]), + Emoji( + name: 'palms up together: light skin tone', + char: '\u{1F932}\u{1F3FB}', + shortName: 'palms_up_together_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'light skin tone', + 'prayer', + 'uc10', + 'diversity', + 'body', + 'hands', + 'pray', + 'soul', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering' + ], + modifiable: true), + Emoji( + name: 'palms up together: medium-light skin tone', + char: '\u{1F932}\u{1F3FC}', + shortName: 'palms_up_together_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'medium-light skin tone', + 'prayer', + 'uc10', + 'diversity', + 'body', + 'hands', + 'pray', + 'soul', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering' + ], + modifiable: true), + Emoji( + name: 'palms up together: medium skin tone', + char: '\u{1F932}\u{1F3FD}', + shortName: 'palms_up_together_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'medium skin tone', + 'prayer', + 'uc10', + 'diversity', + 'body', + 'hands', + 'pray', + 'soul', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering' + ], + modifiable: true), + Emoji( + name: 'palms up together: medium-dark skin tone', + char: '\u{1F932}\u{1F3FE}', + shortName: 'palms_up_together_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'medium-dark skin tone', + 'prayer', + 'uc10', + 'diversity', + 'body', + 'hands', + 'pray', + 'soul', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering' + ], + modifiable: true), + Emoji( + name: 'palms up together: dark skin tone', + char: '\u{1F932}\u{1F3FF}', + shortName: 'palms_up_together_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'dark skin tone', + 'prayer', + 'uc10', + 'diversity', + 'body', + 'hands', + 'pray', + 'soul', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering' + ], + modifiable: true), + Emoji( + name: 'open hands', + char: '\u{1F450}', + shortName: 'open_hands', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'hand', + 'open', + 'uc6', + 'diversity', + 'body', + 'hands', + 'hi', + 'thank you', + 'condolence', + 'private', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'compassion', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ]), + Emoji( + name: 'open hands: light skin tone', + char: '\u{1F450}\u{1F3FB}', + shortName: 'open_hands_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'hand', + 'light skin tone', + 'open', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'thank you', + 'condolence', + 'private', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'compassion', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'open hands: medium-light skin tone', + char: '\u{1F450}\u{1F3FC}', + shortName: 'open_hands_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'hand', + 'medium-light skin tone', + 'open', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'thank you', + 'condolence', + 'private', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'compassion', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'open hands: medium skin tone', + char: '\u{1F450}\u{1F3FD}', + shortName: 'open_hands_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'hand', + 'medium skin tone', + 'open', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'thank you', + 'condolence', + 'private', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'compassion', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'open hands: medium-dark skin tone', + char: '\u{1F450}\u{1F3FE}', + shortName: 'open_hands_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'hand', + 'medium-dark skin tone', + 'open', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'thank you', + 'condolence', + 'private', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'compassion', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'open hands: dark skin tone', + char: '\u{1F450}\u{1F3FF}', + shortName: 'open_hands_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'dark skin tone', + 'hand', + 'open', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'thank you', + 'condolence', + 'private', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'compassion', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'raising hands', + char: '\u{1F64C}', + shortName: 'raised_hands', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'celebration', + 'gesture', + 'hand', + 'hooray', + 'raised', + 'uc6', + 'diversity', + 'happy', + 'body', + 'hands', + 'award', + 'hi', + 'thank you', + 'perfect', + 'pray', + 'good', + 'girls night', + 'easter', + 'fame', + 'festivus', + 'language', + 'protest', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'ladies night', + 'girls only', + 'girlfriend', + 'famous', + 'celebrity', + 'blm', + 'demonstration' + ]), + Emoji( + name: 'raising hands: light skin tone', + char: '\u{1F64C}\u{1F3FB}', + shortName: 'raised_hands_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'celebration', + 'gesture', + 'hand', + 'hooray', + 'light skin tone', + 'raised', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'award', + 'hi', + 'thank you', + 'perfect', + 'pray', + 'good', + 'girls night', + 'easter', + 'fame', + 'festivus', + 'language', + 'protest', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'ladies night', + 'girls only', + 'girlfriend', + 'famous', + 'celebrity', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'raising hands: medium-light skin tone', + char: '\u{1F64C}\u{1F3FC}', + shortName: 'raised_hands_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'celebration', + 'gesture', + 'hand', + 'hooray', + 'medium-light skin tone', + 'raised', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'award', + 'hi', + 'thank you', + 'perfect', + 'pray', + 'good', + 'girls night', + 'easter', + 'fame', + 'festivus', + 'language', + 'protest', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'ladies night', + 'girls only', + 'girlfriend', + 'famous', + 'celebrity', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'raising hands: medium skin tone', + char: '\u{1F64C}\u{1F3FD}', + shortName: 'raised_hands_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'celebration', + 'gesture', + 'hand', + 'hooray', + 'medium skin tone', + 'raised', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'award', + 'hi', + 'thank you', + 'perfect', + 'pray', + 'good', + 'girls night', + 'easter', + 'fame', + 'festivus', + 'language', + 'protest', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'ladies night', + 'girls only', + 'girlfriend', + 'famous', + 'celebrity', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'raising hands: medium-dark skin tone', + char: '\u{1F64C}\u{1F3FE}', + shortName: 'raised_hands_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'celebration', + 'gesture', + 'hand', + 'hooray', + 'medium-dark skin tone', + 'raised', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'award', + 'hi', + 'thank you', + 'perfect', + 'pray', + 'good', + 'girls night', + 'easter', + 'fame', + 'festivus', + 'language', + 'protest', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'ladies night', + 'girls only', + 'girlfriend', + 'famous', + 'celebrity', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'raising hands: dark skin tone', + char: '\u{1F64C}\u{1F3FF}', + shortName: 'raised_hands_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'celebration', + 'dark skin tone', + 'gesture', + 'hand', + 'hooray', + 'raised', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'award', + 'hi', + 'thank you', + 'perfect', + 'pray', + 'good', + 'girls night', + 'easter', + 'fame', + 'festivus', + 'language', + 'protest', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'ladies night', + 'girls only', + 'girlfriend', + 'famous', + 'celebrity', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'clapping hands', + char: '\u{1F44F}', + shortName: 'clap', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'clap', + 'hand', + 'uc6', + 'diversity', + 'happy', + 'body', + 'hands', + 'thank you', + 'win', + 'awesome', + 'good', + 'beautiful', + 'clap', + 'pussy', + 'celebrate', + 'pleased', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'clapping', + 'claps', + 'clapping hands', + 'applause', + 'condom', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'please', + 'chill', + 'confident', + 'content' + ]), + Emoji( + name: 'clapping hands: light skin tone', + char: '\u{1F44F}\u{1F3FB}', + shortName: 'clap_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'clap', + 'hand', + 'light skin tone', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'thank you', + 'win', + 'awesome', + 'good', + 'beautiful', + 'clap', + 'pussy', + 'celebrate', + 'pleased', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'clapping', + 'claps', + 'clapping hands', + 'applause', + 'condom', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'please', + 'chill', + 'confident', + 'content' + ], + modifiable: true), + Emoji( + name: 'clapping hands: medium-light skin tone', + char: '\u{1F44F}\u{1F3FC}', + shortName: 'clap_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'clap', + 'hand', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'thank you', + 'win', + 'awesome', + 'good', + 'beautiful', + 'clap', + 'pussy', + 'celebrate', + 'pleased', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'clapping', + 'claps', + 'clapping hands', + 'applause', + 'condom', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'please', + 'chill', + 'confident', + 'content' + ], + modifiable: true), + Emoji( + name: 'clapping hands: medium skin tone', + char: '\u{1F44F}\u{1F3FD}', + shortName: 'clap_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'clap', + 'hand', + 'medium skin tone', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'thank you', + 'win', + 'awesome', + 'good', + 'beautiful', + 'clap', + 'pussy', + 'celebrate', + 'pleased', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'clapping', + 'claps', + 'clapping hands', + 'applause', + 'condom', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'please', + 'chill', + 'confident', + 'content' + ], + modifiable: true), + Emoji( + name: 'clapping hands: medium-dark skin tone', + char: '\u{1F44F}\u{1F3FE}', + shortName: 'clap_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'clap', + 'hand', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'thank you', + 'win', + 'awesome', + 'good', + 'beautiful', + 'clap', + 'pussy', + 'celebrate', + 'pleased', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'clapping', + 'claps', + 'clapping hands', + 'applause', + 'condom', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'please', + 'chill', + 'confident', + 'content' + ], + modifiable: true), + Emoji( + name: 'clapping hands: dark skin tone', + char: '\u{1F44F}\u{1F3FF}', + shortName: 'clap_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'clap', + 'dark skin tone', + 'hand', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'thank you', + 'win', + 'awesome', + 'good', + 'beautiful', + 'clap', + 'pussy', + 'celebrate', + 'pleased', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'clapping', + 'claps', + 'clapping hands', + 'applause', + 'condom', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'please', + 'chill', + 'confident', + 'content' + ], + modifiable: true), + Emoji( + name: 'handshake', + char: '\u{1F91D}', + shortName: 'handshake', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'agreement', + 'hand', + 'handshake', + 'meeting', + 'shake', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'business', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ]), + Emoji( + name: 'thumbs up', + char: '\u{1F44D}', + shortName: 'thumbsup', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + '+1', + 'hand', + 'thumb', + 'up', + 'uc6', + 'diversity', + 'happy', + 'body', + 'hands', + 'award', + 'hi', + 'luck', + 'thank you', + 'perfect', + 'awesome', + 'good', + 'beautiful', + 'correct', + 'fun', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'good luck', + 'lucky', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'passing grade', + '(y)' + ]), + Emoji( + name: 'thumbs up: light skin tone', + char: '\u{1F44D}\u{1F3FB}', + shortName: 'thumbsup_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + '+1', + 'hand', + 'light skin tone', + 'thumb', + 'up', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'award', + 'hi', + 'luck', + 'thank you', + 'perfect', + 'awesome', + 'good', + 'beautiful', + 'correct', + 'fun', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'good luck', + 'lucky', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'thumbs up: medium-light skin tone', + char: '\u{1F44D}\u{1F3FC}', + shortName: 'thumbsup_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + '+1', + 'hand', + 'medium-light skin tone', + 'thumb', + 'up', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'award', + 'hi', + 'luck', + 'thank you', + 'perfect', + 'awesome', + 'good', + 'beautiful', + 'correct', + 'fun', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'good luck', + 'lucky', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'thumbs up: medium skin tone', + char: '\u{1F44D}\u{1F3FD}', + shortName: 'thumbsup_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + '+1', + 'hand', + 'medium skin tone', + 'thumb', + 'up', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'award', + 'hi', + 'luck', + 'thank you', + 'perfect', + 'awesome', + 'good', + 'beautiful', + 'correct', + 'fun', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'good luck', + 'lucky', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'thumbs up: medium-dark skin tone', + char: '\u{1F44D}\u{1F3FE}', + shortName: 'thumbsup_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + '+1', + 'hand', + 'medium-dark skin tone', + 'thumb', + 'up', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'award', + 'hi', + 'luck', + 'thank you', + 'perfect', + 'awesome', + 'good', + 'beautiful', + 'correct', + 'fun', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'good luck', + 'lucky', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'thumbs up: dark skin tone', + char: '\u{1F44D}\u{1F3FF}', + shortName: 'thumbsup_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + '+1', + 'dark skin tone', + 'hand', + 'thumb', + 'up', + 'uc8', + 'diversity', + 'happy', + 'body', + 'hands', + 'award', + 'hi', + 'luck', + 'thank you', + 'perfect', + 'awesome', + 'good', + 'beautiful', + 'correct', + 'fun', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'good luck', + 'lucky', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'thumbs down', + char: '\u{1F44E}', + shortName: 'thumbsdown', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + '-1', + 'down', + 'hand', + 'thumb', + 'uc6', + 'diversity', + 'sad', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ]), + Emoji( + name: 'thumbs down: light skin tone', + char: '\u{1F44E}\u{1F3FB}', + shortName: 'thumbsdown_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + '-1', + 'down', + 'hand', + 'light skin tone', + 'thumb', + 'uc8', + 'diversity', + 'sad', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'thumbs down: medium-light skin tone', + char: '\u{1F44E}\u{1F3FC}', + shortName: 'thumbsdown_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + '-1', + 'down', + 'hand', + 'medium-light skin tone', + 'thumb', + 'uc8', + 'diversity', + 'sad', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'thumbs down: medium skin tone', + char: '\u{1F44E}\u{1F3FD}', + shortName: 'thumbsdown_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + '-1', + 'down', + 'hand', + 'medium skin tone', + 'thumb', + 'uc8', + 'diversity', + 'sad', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'thumbs down: medium-dark skin tone', + char: '\u{1F44E}\u{1F3FE}', + shortName: 'thumbsdown_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + '-1', + 'down', + 'hand', + 'medium-dark skin tone', + 'thumb', + 'uc8', + 'diversity', + 'sad', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'thumbs down: dark skin tone', + char: '\u{1F44E}\u{1F3FF}', + shortName: 'thumbsdown_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + '-1', + 'dark skin tone', + 'down', + 'hand', + 'thumb', + 'uc8', + 'diversity', + 'sad', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'oncoming fist', + char: '\u{1F44A}', + shortName: 'punch', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'clenched', + 'fist', + 'hand', + 'punch', + 'uc6', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'awesome', + 'boys night', + 'friend', + 'fight', + 'hit', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'guys night', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ]), + Emoji( + name: 'oncoming fist: light skin tone', + char: '\u{1F44A}\u{1F3FB}', + shortName: 'punch_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'clenched', + 'fist', + 'hand', + 'light skin tone', + 'punch', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'awesome', + 'boys night', + 'friend', + 'fight', + 'hit', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'guys night', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'oncoming fist: medium-light skin tone', + char: '\u{1F44A}\u{1F3FC}', + shortName: 'punch_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'clenched', + 'fist', + 'hand', + 'medium-light skin tone', + 'punch', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'awesome', + 'boys night', + 'friend', + 'fight', + 'hit', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'guys night', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'oncoming fist: medium skin tone', + char: '\u{1F44A}\u{1F3FD}', + shortName: 'punch_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'clenched', + 'fist', + 'hand', + 'medium skin tone', + 'punch', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'awesome', + 'boys night', + 'friend', + 'fight', + 'hit', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'guys night', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'oncoming fist: medium-dark skin tone', + char: '\u{1F44A}\u{1F3FE}', + shortName: 'punch_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'clenched', + 'fist', + 'hand', + 'medium-dark skin tone', + 'punch', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'awesome', + 'boys night', + 'friend', + 'fight', + 'hit', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'guys night', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'oncoming fist: dark skin tone', + char: '\u{1F44A}\u{1F3FF}', + shortName: 'punch_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'clenched', + 'dark skin tone', + 'fist', + 'hand', + 'punch', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'awesome', + 'boys night', + 'friend', + 'fight', + 'hit', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'guys night', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'raised fist', + char: '\u{270A}', + shortName: 'fist', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'clenched', + 'fist', + 'hand', + 'punch', + 'uc6', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'condolence', + 'proud', + 'language', + 'protest', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'compassion', + 'blm', + 'demonstration' + ]), + Emoji( + name: 'raised fist: light skin tone', + char: '\u{270A}\u{1F3FB}', + shortName: 'fist_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'clenched', + 'fist', + 'hand', + 'light skin tone', + 'punch', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'condolence', + 'proud', + 'language', + 'protest', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'compassion', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'raised fist: medium-light skin tone', + char: '\u{270A}\u{1F3FC}', + shortName: 'fist_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'clenched', + 'fist', + 'hand', + 'medium-light skin tone', + 'punch', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'condolence', + 'proud', + 'language', + 'protest', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'compassion', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'raised fist: medium skin tone', + char: '\u{270A}\u{1F3FD}', + shortName: 'fist_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'clenched', + 'fist', + 'hand', + 'medium skin tone', + 'punch', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'condolence', + 'proud', + 'language', + 'protest', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'compassion', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'raised fist: medium-dark skin tone', + char: '\u{270A}\u{1F3FE}', + shortName: 'fist_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'clenched', + 'fist', + 'hand', + 'medium-dark skin tone', + 'punch', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'condolence', + 'proud', + 'language', + 'protest', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'compassion', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'raised fist: dark skin tone', + char: '\u{270A}\u{1F3FF}', + shortName: 'fist_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'clenched', + 'dark skin tone', + 'fist', + 'hand', + 'punch', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'condolence', + 'proud', + 'language', + 'protest', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'compassion', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'left-facing fist', + char: '\u{1F91B}', + shortName: 'left_facing_fist', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'fist', + 'leftwards', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'friend', + 'hit', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ]), + Emoji( + name: 'left-facing fist: light skin tone', + char: '\u{1F91B}\u{1F3FB}', + shortName: 'left_facing_fist_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'fist', + 'leftwards', + 'light skin tone', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'friend', + 'hit', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'left-facing fist: medium-light skin tone', + char: '\u{1F91B}\u{1F3FC}', + shortName: 'left_facing_fist_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'fist', + 'leftwards', + 'medium-light skin tone', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'friend', + 'hit', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'left-facing fist: medium skin tone', + char: '\u{1F91B}\u{1F3FD}', + shortName: 'left_facing_fist_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'fist', + 'leftwards', + 'medium skin tone', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'friend', + 'hit', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'left-facing fist: medium-dark skin tone', + char: '\u{1F91B}\u{1F3FE}', + shortName: 'left_facing_fist_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'fist', + 'leftwards', + 'medium-dark skin tone', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'friend', + 'hit', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'left-facing fist: dark skin tone', + char: '\u{1F91B}\u{1F3FF}', + shortName: 'left_facing_fist_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'dark skin tone', + 'fist', + 'leftwards', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'win', + 'friend', + 'hit', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'right-facing fist', + char: '\u{1F91C}', + shortName: 'right_facing_fist', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'fist', + 'rightwards', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'friend', + 'hit', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ]), + Emoji( + name: 'right-facing fist: light skin tone', + char: '\u{1F91C}\u{1F3FB}', + shortName: 'right_facing_fist_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'fist', + 'light skin tone', + 'rightwards', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'friend', + 'hit', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'right-facing fist: medium-light skin tone', + char: '\u{1F91C}\u{1F3FC}', + shortName: 'right_facing_fist_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'fist', + 'medium-light skin tone', + 'rightwards', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'friend', + 'hit', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'right-facing fist: medium skin tone', + char: '\u{1F91C}\u{1F3FD}', + shortName: 'right_facing_fist_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'fist', + 'medium skin tone', + 'rightwards', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'friend', + 'hit', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'right-facing fist: medium-dark skin tone', + char: '\u{1F91C}\u{1F3FE}', + shortName: 'right_facing_fist_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'fist', + 'medium-dark skin tone', + 'rightwards', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'friend', + 'hit', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'right-facing fist: dark skin tone', + char: '\u{1F91C}\u{1F3FF}', + shortName: 'right_facing_fist_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersClosed, + keywords: [ + 'dark skin tone', + 'fist', + 'rightwards', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'fist bump', + 'friend', + 'hit', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fist', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'punch', + 'pow', + 'bam' + ], + modifiable: true), + Emoji( + name: 'crossed fingers', + char: '\u{1F91E}', + shortName: 'fingers_crossed', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'cross', + 'finger', + 'hand', + 'luck', + 'uc9', + 'diversity', + 'body', + 'hands', + 'donald trump', + 'irish', + 'hope', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'trump', + 'saint patricks day', + 'st patricks day', + 'leprechaun', + 'swear', + 'promise' + ]), + Emoji( + name: 'crossed fingers: light skin tone', + char: '\u{1F91E}\u{1F3FB}', + shortName: 'fingers_crossed_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'cross', + 'finger', + 'hand', + 'light skin tone', + 'luck', + 'uc9', + 'diversity', + 'body', + 'hands', + 'donald trump', + 'irish', + 'hope', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'trump', + 'saint patricks day', + 'st patricks day', + 'leprechaun', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'crossed fingers: medium-light skin tone', + char: '\u{1F91E}\u{1F3FC}', + shortName: 'fingers_crossed_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'cross', + 'finger', + 'hand', + 'luck', + 'medium-light skin tone', + 'uc9', + 'diversity', + 'body', + 'hands', + 'donald trump', + 'irish', + 'hope', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'trump', + 'saint patricks day', + 'st patricks day', + 'leprechaun', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'crossed fingers: medium skin tone', + char: '\u{1F91E}\u{1F3FD}', + shortName: 'fingers_crossed_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'cross', + 'finger', + 'hand', + 'luck', + 'medium skin tone', + 'uc9', + 'diversity', + 'body', + 'hands', + 'donald trump', + 'irish', + 'hope', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'trump', + 'saint patricks day', + 'st patricks day', + 'leprechaun', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'crossed fingers: medium-dark skin tone', + char: '\u{1F91E}\u{1F3FE}', + shortName: 'fingers_crossed_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'cross', + 'finger', + 'hand', + 'luck', + 'medium-dark skin tone', + 'uc9', + 'diversity', + 'body', + 'hands', + 'donald trump', + 'irish', + 'hope', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'trump', + 'saint patricks day', + 'st patricks day', + 'leprechaun', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'crossed fingers: dark skin tone', + char: '\u{1F91E}\u{1F3FF}', + shortName: 'fingers_crossed_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'cross', + 'dark skin tone', + 'finger', + 'hand', + 'luck', + 'uc9', + 'diversity', + 'body', + 'hands', + 'donald trump', + 'irish', + 'hope', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'trump', + 'saint patricks day', + 'st patricks day', + 'leprechaun', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'victory hand', + char: '\u{270C}\u{FE0F}', + shortName: 'v', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'hand', + 'v', + 'victory', + 'uc1', + 'diversity', + 'peace', + 'body', + 'hands', + 'hi', + 'thank you', + 'girls night', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'ladies night', + 'girls only', + 'girlfriend' + ]), + Emoji( + name: 'victory hand: light skin tone', + char: '\u{270C}\u{1F3FB}', + shortName: 'v_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'hand', + 'light skin tone', + 'v', + 'victory', + 'uc8', + 'diversity', + 'peace', + 'body', + 'hands', + 'hi', + 'thank you', + 'girls night', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'ladies night', + 'girls only', + 'girlfriend' + ], + modifiable: true), + Emoji( + name: 'victory hand: medium-light skin tone', + char: '\u{270C}\u{1F3FC}', + shortName: 'v_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'hand', + 'medium-light skin tone', + 'v', + 'victory', + 'uc8', + 'diversity', + 'peace', + 'body', + 'hands', + 'hi', + 'thank you', + 'girls night', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'ladies night', + 'girls only', + 'girlfriend' + ], + modifiable: true), + Emoji( + name: 'victory hand: medium skin tone', + char: '\u{270C}\u{1F3FD}', + shortName: 'v_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'hand', + 'medium skin tone', + 'v', + 'victory', + 'uc8', + 'diversity', + 'peace', + 'body', + 'hands', + 'hi', + 'thank you', + 'girls night', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'ladies night', + 'girls only', + 'girlfriend' + ], + modifiable: true), + Emoji( + name: 'victory hand: medium-dark skin tone', + char: '\u{270C}\u{1F3FE}', + shortName: 'v_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'hand', + 'medium-dark skin tone', + 'v', + 'victory', + 'uc8', + 'diversity', + 'peace', + 'body', + 'hands', + 'hi', + 'thank you', + 'girls night', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'ladies night', + 'girls only', + 'girlfriend' + ], + modifiable: true), + Emoji( + name: 'victory hand: dark skin tone', + char: '\u{270C}\u{1F3FF}', + shortName: 'v_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'dark skin tone', + 'hand', + 'v', + 'victory', + 'uc8', + 'diversity', + 'peace', + 'body', + 'hands', + 'hi', + 'thank you', + 'girls night', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'ladies night', + 'girls only', + 'girlfriend' + ], + modifiable: true), + Emoji( + name: 'love-you gesture', + char: '\u{1F91F}', + shortName: 'love_you_gesture', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'ILY', + 'hand', + 'uc10', + 'diversity', + 'body', + 'hands', + 'love', + 'beautiful', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ]), + Emoji( + name: 'love-you gesture: light skin tone', + char: '\u{1F91F}\u{1F3FB}', + shortName: 'love_you_gesture_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'ILY', + 'hand', + 'light skin tone', + 'uc10', + 'diversity', + 'body', + 'hands', + 'love', + 'beautiful', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ], + modifiable: true), + Emoji( + name: 'love-you gesture: medium-light skin tone', + char: '\u{1F91F}\u{1F3FC}', + shortName: 'love_you_gesture_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'ILY', + 'hand', + 'medium-light skin tone', + 'uc10', + 'diversity', + 'body', + 'hands', + 'love', + 'beautiful', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ], + modifiable: true), + Emoji( + name: 'love-you gesture: medium skin tone', + char: '\u{1F91F}\u{1F3FD}', + shortName: 'love_you_gesture_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'ILY', + 'hand', + 'medium skin tone', + 'uc10', + 'diversity', + 'body', + 'hands', + 'love', + 'beautiful', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ], + modifiable: true), + Emoji( + name: 'love-you gesture: medium-dark skin tone', + char: '\u{1F91F}\u{1F3FE}', + shortName: 'love_you_gesture_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'ILY', + 'hand', + 'medium-dark skin tone', + 'uc10', + 'diversity', + 'body', + 'hands', + 'love', + 'beautiful', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ], + modifiable: true), + Emoji( + name: 'love-you gesture: dark skin tone', + char: '\u{1F91F}\u{1F3FF}', + shortName: 'love_you_gesture_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'ILY', + 'dark skin tone', + 'hand', + 'uc10', + 'diversity', + 'body', + 'hands', + 'love', + 'beautiful', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ], + modifiable: true), + Emoji( + name: 'sign of the horns', + char: '\u{1F918}', + shortName: 'metal', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'finger', + 'hand', + 'horns', + 'rock-on', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'boys night', + 'rock and roll', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'guys night' + ]), + Emoji( + name: 'sign of the horns: light skin tone', + char: '\u{1F918}\u{1F3FB}', + shortName: 'metal_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'finger', + 'hand', + 'horns', + 'light skin tone', + 'rock-on', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'boys night', + 'rock and roll', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'guys night' + ], + modifiable: true), + Emoji( + name: 'sign of the horns: medium-light skin tone', + char: '\u{1F918}\u{1F3FC}', + shortName: 'metal_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'finger', + 'hand', + 'horns', + 'medium-light skin tone', + 'rock-on', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'boys night', + 'rock and roll', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'guys night' + ], + modifiable: true), + Emoji( + name: 'sign of the horns: medium skin tone', + char: '\u{1F918}\u{1F3FD}', + shortName: 'metal_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'finger', + 'hand', + 'horns', + 'medium skin tone', + 'rock-on', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'boys night', + 'rock and roll', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'guys night' + ], + modifiable: true), + Emoji( + name: 'sign of the horns: medium-dark skin tone', + char: '\u{1F918}\u{1F3FE}', + shortName: 'metal_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'finger', + 'hand', + 'horns', + 'medium-dark skin tone', + 'rock-on', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'boys night', + 'rock and roll', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'guys night' + ], + modifiable: true), + Emoji( + name: 'sign of the horns: dark skin tone', + char: '\u{1F918}\u{1F3FF}', + shortName: 'metal_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'dark skin tone', + 'finger', + 'hand', + 'horns', + 'rock-on', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'boys night', + 'rock and roll', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'guys night' + ], + modifiable: true), + Emoji( + name: 'OK hand', + char: '\u{1F44C}', + shortName: 'ok_hand', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'OK', + 'hand', + 'uc6', + 'diversity', + 'happy', + 'butt', + 'body', + 'hands', + 'hi', + 'sex', + 'thank you', + 'perfect', + 'awesome', + 'good', + 'beautiful', + 'google', + 'correct', + 'porn', + 'fun', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'ass', + 'booty', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'passing grade' + ]), + Emoji( + name: 'OK hand: light skin tone', + char: '\u{1F44C}\u{1F3FB}', + shortName: 'ok_hand_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'OK', + 'hand', + 'light skin tone', + 'uc8', + 'diversity', + 'happy', + 'butt', + 'body', + 'hands', + 'hi', + 'sex', + 'thank you', + 'perfect', + 'awesome', + 'good', + 'beautiful', + 'google', + 'correct', + 'porn', + 'fun', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'ass', + 'booty', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'OK hand: medium-light skin tone', + char: '\u{1F44C}\u{1F3FC}', + shortName: 'ok_hand_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'OK', + 'hand', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'happy', + 'butt', + 'body', + 'hands', + 'hi', + 'sex', + 'thank you', + 'perfect', + 'awesome', + 'good', + 'beautiful', + 'google', + 'correct', + 'porn', + 'fun', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'ass', + 'booty', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'OK hand: medium skin tone', + char: '\u{1F44C}\u{1F3FD}', + shortName: 'ok_hand_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'OK', + 'hand', + 'medium skin tone', + 'uc8', + 'diversity', + 'happy', + 'butt', + 'body', + 'hands', + 'hi', + 'sex', + 'thank you', + 'perfect', + 'awesome', + 'good', + 'beautiful', + 'google', + 'correct', + 'porn', + 'fun', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'ass', + 'booty', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'OK hand: medium-dark skin tone', + char: '\u{1F44C}\u{1F3FE}', + shortName: 'ok_hand_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'OK', + 'hand', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'happy', + 'butt', + 'body', + 'hands', + 'hi', + 'sex', + 'thank you', + 'perfect', + 'awesome', + 'good', + 'beautiful', + 'google', + 'correct', + 'porn', + 'fun', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'ass', + 'booty', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'OK hand: dark skin tone', + char: '\u{1F44C}\u{1F3FF}', + shortName: 'ok_hand_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'OK', + 'dark skin tone', + 'hand', + 'uc8', + 'diversity', + 'happy', + 'butt', + 'body', + 'hands', + 'hi', + 'sex', + 'thank you', + 'perfect', + 'awesome', + 'good', + 'beautiful', + 'google', + 'correct', + 'porn', + 'fun', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'ass', + 'booty', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'pinching hand', + char: '\u{1F90F}', + shortName: 'pinching_hand', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'uc12', + 'diversity', + 'penis', + 'body', + 'hands', + 'donald trump', + 'half', + 'quiet', + 'tiny', + 'greed', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'dick', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'trump', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'petite bite', + 'small dick', + 'small', + 'selfish' + ]), + Emoji( + name: 'pinching hand: light skin tone', + char: '\u{1F90F}\u{1F3FB}', + shortName: 'pinching_hand_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'uc12', + 'diversity', + 'penis', + 'body', + 'hands', + 'donald trump', + 'half', + 'quiet', + 'tiny', + 'greed', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'dick', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'trump', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'petite bite', + 'small dick', + 'small', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'pinching hand: medium-light skin tone', + char: '\u{1F90F}\u{1F3FC}', + shortName: 'pinching_hand_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'uc12', + 'diversity', + 'penis', + 'body', + 'hands', + 'donald trump', + 'half', + 'quiet', + 'tiny', + 'greed', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'dick', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'trump', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'petite bite', + 'small dick', + 'small', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'pinching hand: medium skin tone', + char: '\u{1F90F}\u{1F3FD}', + shortName: 'pinching_hand_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'uc12', + 'diversity', + 'penis', + 'body', + 'hands', + 'donald trump', + 'half', + 'quiet', + 'tiny', + 'greed', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'dick', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'trump', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'petite bite', + 'small dick', + 'small', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'pinching hand: medium-dark skin tone', + char: '\u{1F90F}\u{1F3FE}', + shortName: 'pinching_hand_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'uc12', + 'diversity', + 'penis', + 'body', + 'hands', + 'donald trump', + 'half', + 'quiet', + 'tiny', + 'greed', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'dick', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'trump', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'petite bite', + 'small dick', + 'small', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'pinching hand: dark skin tone', + char: '\u{1F90F}\u{1F3FF}', + shortName: 'pinching_hand_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'uc12', + 'diversity', + 'penis', + 'body', + 'hands', + 'donald trump', + 'half', + 'quiet', + 'tiny', + 'greed', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'dick', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'trump', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'petite bite', + 'small dick', + 'small', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'pinched fingers', + char: '\u{1F90C}', + shortName: 'pinched_fingers', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'uc13', + 'diversity', + 'italian', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'italy', + 'italie', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ]), + Emoji( + name: 'pinched fingers: medium-light skin tone', + char: '\u{1F90C}\u{1F3FC}', + shortName: 'pinched_fingers_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'uc13', + 'diversity', + 'italian', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'italy', + 'italie', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'pinched fingers: light skin tone', + char: '\u{1F90C}\u{1F3FB}', + shortName: 'pinched_fingers_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'uc13', + 'diversity', + 'italian', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'italy', + 'italie', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'pinched fingers: medium skin tone', + char: '\u{1F90C}\u{1F3FD}', + shortName: 'pinched_fingers_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'uc13', + 'diversity', + 'italian', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'italy', + 'italie', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'pinched fingers: medium-dark skin tone', + char: '\u{1F90C}\u{1F3FE}', + shortName: 'pinched_fingers_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'uc13', + 'diversity', + 'italian', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'italy', + 'italie', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'pinched fingers: dark skin tone', + char: '\u{1F90C}\u{1F3FF}', + shortName: 'pinched_fingers_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'uc13', + 'diversity', + 'italian', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'italy', + 'italie', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing left', + char: '\u{1F448}', + shortName: 'point_left', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'point', + 'uc6', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ]), + Emoji( + name: 'backhand index pointing left: light skin tone', + char: '\u{1F448}\u{1F3FB}', + shortName: 'point_left_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'light skin tone', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing left: medium-light skin tone', + char: '\u{1F448}\u{1F3FC}', + shortName: 'point_left_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'medium-light skin tone', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing left: medium skin tone', + char: '\u{1F448}\u{1F3FD}', + shortName: 'point_left_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'medium skin tone', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing left: medium-dark skin tone', + char: '\u{1F448}\u{1F3FE}', + shortName: 'point_left_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'medium-dark skin tone', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing left: dark skin tone', + char: '\u{1F448}\u{1F3FF}', + shortName: 'point_left_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'dark skin tone', + 'finger', + 'hand', + 'index', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing right', + char: '\u{1F449}', + shortName: 'point_right', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'point', + 'uc6', + 'diversity', + 'body', + 'hands', + 'hi', + 'sex', + 'download', + 'porn', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fuck', + 'fucking', + 'horny', + 'humping' + ]), + Emoji( + name: 'backhand index pointing right: light skin tone', + char: '\u{1F449}\u{1F3FB}', + shortName: 'point_right_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'light skin tone', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'sex', + 'download', + 'porn', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fuck', + 'fucking', + 'horny', + 'humping' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing right: medium-light skin tone', + char: '\u{1F449}\u{1F3FC}', + shortName: 'point_right_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'medium-light skin tone', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'sex', + 'download', + 'porn', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fuck', + 'fucking', + 'horny', + 'humping' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing right: medium skin tone', + char: '\u{1F449}\u{1F3FD}', + shortName: 'point_right_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'medium skin tone', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'sex', + 'download', + 'porn', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fuck', + 'fucking', + 'horny', + 'humping' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing right: medium-dark skin tone', + char: '\u{1F449}\u{1F3FE}', + shortName: 'point_right_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'medium-dark skin tone', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'sex', + 'download', + 'porn', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fuck', + 'fucking', + 'horny', + 'humping' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing right: dark skin tone', + char: '\u{1F449}\u{1F3FF}', + shortName: 'point_right_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'dark skin tone', + 'finger', + 'hand', + 'index', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'sex', + 'download', + 'porn', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'fuck', + 'fucking', + 'horny', + 'humping' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing up', + char: '\u{1F446}', + shortName: 'point_up_2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'point', + 'up', + 'uc6', + 'diversity', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ]), + Emoji( + name: 'backhand index pointing up: light skin tone', + char: '\u{1F446}\u{1F3FB}', + shortName: 'point_up_2_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'light skin tone', + 'point', + 'up', + 'uc8', + 'diversity', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing up: medium-light skin tone', + char: '\u{1F446}\u{1F3FC}', + shortName: 'point_up_2_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'medium-light skin tone', + 'point', + 'up', + 'uc8', + 'diversity', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing up: medium skin tone', + char: '\u{1F446}\u{1F3FD}', + shortName: 'point_up_2_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'medium skin tone', + 'point', + 'up', + 'uc8', + 'diversity', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing up: medium-dark skin tone', + char: '\u{1F446}\u{1F3FE}', + shortName: 'point_up_2_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'finger', + 'hand', + 'index', + 'medium-dark skin tone', + 'point', + 'up', + 'uc8', + 'diversity', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing up: dark skin tone', + char: '\u{1F446}\u{1F3FF}', + shortName: 'point_up_2_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'dark skin tone', + 'finger', + 'hand', + 'index', + 'point', + 'up', + 'uc8', + 'diversity', + 'body', + 'hands', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing down', + char: '\u{1F447}', + shortName: 'point_down', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'down', + 'finger', + 'hand', + 'index', + 'point', + 'uc6', + 'diversity', + 'body', + 'hands', + 'click', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ]), + Emoji( + name: 'backhand index pointing down: light skin tone', + char: '\u{1F447}\u{1F3FB}', + shortName: 'point_down_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'down', + 'finger', + 'hand', + 'index', + 'light skin tone', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'click', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing down: medium-light skin tone', + char: '\u{1F447}\u{1F3FC}', + shortName: 'point_down_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'down', + 'finger', + 'hand', + 'index', + 'medium-light skin tone', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'click', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing down: medium skin tone', + char: '\u{1F447}\u{1F3FD}', + shortName: 'point_down_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'down', + 'finger', + 'hand', + 'index', + 'medium skin tone', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'click', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing down: medium-dark skin tone', + char: '\u{1F447}\u{1F3FE}', + shortName: 'point_down_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'down', + 'finger', + 'hand', + 'index', + 'medium-dark skin tone', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'click', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'backhand index pointing down: dark skin tone', + char: '\u{1F447}\u{1F3FF}', + shortName: 'point_down_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'backhand', + 'dark skin tone', + 'down', + 'finger', + 'hand', + 'index', + 'point', + 'uc8', + 'diversity', + 'body', + 'hands', + 'click', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers' + ], + modifiable: true), + Emoji( + name: 'index pointing up', + char: '\u{261D}\u{FE0F}', + shortName: 'point_up', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'finger', + 'hand', + 'index', + 'point', + 'up', + 'uc1', + 'diversity', + 'body', + 'hands', + 'emojione', + 'porn', + 'important', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'emoji one' + ]), + Emoji( + name: 'index pointing up: light skin tone', + char: '\u{261D}\u{1F3FB}', + shortName: 'point_up_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'finger', + 'hand', + 'index', + 'light skin tone', + 'point', + 'up', + 'uc8', + 'diversity', + 'body', + 'hands', + 'emojione', + 'porn', + 'important', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'emoji one' + ], + modifiable: true), + Emoji( + name: 'index pointing up: medium-light skin tone', + char: '\u{261D}\u{1F3FC}', + shortName: 'point_up_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'finger', + 'hand', + 'index', + 'medium-light skin tone', + 'point', + 'up', + 'uc8', + 'diversity', + 'body', + 'hands', + 'emojione', + 'porn', + 'important', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'emoji one' + ], + modifiable: true), + Emoji( + name: 'index pointing up: medium skin tone', + char: '\u{261D}\u{1F3FD}', + shortName: 'point_up_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'finger', + 'hand', + 'index', + 'medium skin tone', + 'point', + 'up', + 'uc8', + 'diversity', + 'body', + 'hands', + 'emojione', + 'porn', + 'important', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'emoji one' + ], + modifiable: true), + Emoji( + name: 'index pointing up: medium-dark skin tone', + char: '\u{261D}\u{1F3FE}', + shortName: 'point_up_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'finger', + 'hand', + 'index', + 'medium-dark skin tone', + 'point', + 'up', + 'uc8', + 'diversity', + 'body', + 'hands', + 'emojione', + 'porn', + 'important', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'emoji one' + ], + modifiable: true), + Emoji( + name: 'index pointing up: dark skin tone', + char: '\u{261D}\u{1F3FF}', + shortName: 'point_up_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'dark skin tone', + 'finger', + 'hand', + 'index', + 'point', + 'up', + 'uc8', + 'diversity', + 'body', + 'hands', + 'emojione', + 'porn', + 'important', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'emoji one' + ], + modifiable: true), + Emoji( + name: 'raised hand', + char: '\u{270B}', + shortName: 'raised_hand', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'hand', + 'uc6', + 'diversity', + 'body', + 'hands', + 'hi', + 'girls night', + 'high five', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend' + ]), + Emoji( + name: 'raised hand: light skin tone', + char: '\u{270B}\u{1F3FB}', + shortName: 'raised_hand_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'hand', + 'light skin tone', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'girls night', + 'high five', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend' + ], + modifiable: true), + Emoji( + name: 'raised hand: medium-light skin tone', + char: '\u{270B}\u{1F3FC}', + shortName: 'raised_hand_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'hand', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'girls night', + 'high five', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend' + ], + modifiable: true), + Emoji( + name: 'raised hand: medium skin tone', + char: '\u{270B}\u{1F3FD}', + shortName: 'raised_hand_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'hand', + 'medium skin tone', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'girls night', + 'high five', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend' + ], + modifiable: true), + Emoji( + name: 'raised hand: medium-dark skin tone', + char: '\u{270B}\u{1F3FE}', + shortName: 'raised_hand_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'hand', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'girls night', + 'high five', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend' + ], + modifiable: true), + Emoji( + name: 'raised hand: dark skin tone', + char: '\u{270B}\u{1F3FF}', + shortName: 'raised_hand_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'dark skin tone', + 'hand', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'girls night', + 'high five', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend' + ], + modifiable: true), + Emoji( + name: 'raised back of hand', + char: '\u{1F91A}', + shortName: 'raised_back_of_hand', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'backhand', + 'raised', + 'uc9', + 'diversity', + 'body', + 'hands', + 'award', + 'hi', + 'hate', + 'private', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ]), + Emoji( + name: 'raised back of hand: light skin tone', + char: '\u{1F91A}\u{1F3FB}', + shortName: 'raised_back_of_hand_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'backhand', + 'light skin tone', + 'raised', + 'uc9', + 'diversity', + 'body', + 'hands', + 'award', + 'hi', + 'hate', + 'private', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'raised back of hand: medium-light skin tone', + char: '\u{1F91A}\u{1F3FC}', + shortName: 'raised_back_of_hand_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'backhand', + 'medium-light skin tone', + 'raised', + 'uc9', + 'diversity', + 'body', + 'hands', + 'award', + 'hi', + 'hate', + 'private', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'raised back of hand: medium skin tone', + char: '\u{1F91A}\u{1F3FD}', + shortName: 'raised_back_of_hand_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'backhand', + 'medium skin tone', + 'raised', + 'uc9', + 'diversity', + 'body', + 'hands', + 'award', + 'hi', + 'hate', + 'private', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'raised back of hand: medium-dark skin tone', + char: '\u{1F91A}\u{1F3FE}', + shortName: 'raised_back_of_hand_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'backhand', + 'medium-dark skin tone', + 'raised', + 'uc9', + 'diversity', + 'body', + 'hands', + 'award', + 'hi', + 'hate', + 'private', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'raised back of hand: dark skin tone', + char: '\u{1F91A}\u{1F3FF}', + shortName: 'raised_back_of_hand_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'backhand', + 'dark skin tone', + 'raised', + 'uc9', + 'diversity', + 'body', + 'hands', + 'award', + 'hi', + 'hate', + 'private', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'hand with fingers splayed', + char: '\u{1F590}', + shortName: 'hand_splayed', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'finger', + 'hand', + 'splayed', + 'uc7', + 'diversity', + 'body', + 'hands', + 'hi', + 'gay pride', + 'high five', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ]), + Emoji( + name: 'hand with fingers splayed: light skin tone', + char: '\u{1F590}\u{1F3FB}', + shortName: 'hand_splayed_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'finger', + 'hand', + 'light skin tone', + 'splayed', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'gay pride', + 'high five', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'hand with fingers splayed: medium-light skin tone', + char: '\u{1F590}\u{1F3FC}', + shortName: 'hand_splayed_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'finger', + 'hand', + 'medium-light skin tone', + 'splayed', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'gay pride', + 'high five', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'hand with fingers splayed: medium skin tone', + char: '\u{1F590}\u{1F3FD}', + shortName: 'hand_splayed_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'finger', + 'hand', + 'medium skin tone', + 'splayed', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'gay pride', + 'high five', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'hand with fingers splayed: medium-dark skin tone', + char: '\u{1F590}\u{1F3FE}', + shortName: 'hand_splayed_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'finger', + 'hand', + 'medium-dark skin tone', + 'splayed', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'gay pride', + 'high five', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'hand with fingers splayed: dark skin tone', + char: '\u{1F590}\u{1F3FF}', + shortName: 'hand_splayed_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'dark skin tone', + 'finger', + 'hand', + 'splayed', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'gay pride', + 'high five', + 'proud', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'vulcan salute', + char: '\u{1F596}', + shortName: 'vulcan', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'finger', + 'hand', + 'spock', + 'vulcan', + 'uc7', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ]), + Emoji( + name: 'vulcan salute: light skin tone', + char: '\u{1F596}\u{1F3FB}', + shortName: 'vulcan_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'finger', + 'hand', + 'light skin tone', + 'spock', + 'vulcan', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'vulcan salute: medium-light skin tone', + char: '\u{1F596}\u{1F3FC}', + shortName: 'vulcan_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'finger', + 'hand', + 'medium-light skin tone', + 'spock', + 'vulcan', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'vulcan salute: medium skin tone', + char: '\u{1F596}\u{1F3FD}', + shortName: 'vulcan_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'finger', + 'hand', + 'medium skin tone', + 'spock', + 'vulcan', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'vulcan salute: medium-dark skin tone', + char: '\u{1F596}\u{1F3FE}', + shortName: 'vulcan_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'finger', + 'hand', + 'medium-dark skin tone', + 'spock', + 'vulcan', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'vulcan salute: dark skin tone', + char: '\u{1F596}\u{1F3FF}', + shortName: 'vulcan_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'dark skin tone', + 'finger', + 'hand', + 'spock', + 'vulcan', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'waving hand', + char: '\u{1F44B}', + shortName: 'wave', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'hand', + 'wave', + 'waving', + 'uc6', + 'diversity', + 'body', + 'hands', + 'hi', + 'hola', + 'friend', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo' + ]), + Emoji( + name: 'waving hand: light skin tone', + char: '\u{1F44B}\u{1F3FB}', + shortName: 'wave_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'hand', + 'light skin tone', + 'wave', + 'waving', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'hola', + 'friend', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo' + ], + modifiable: true), + Emoji( + name: 'waving hand: medium-light skin tone', + char: '\u{1F44B}\u{1F3FC}', + shortName: 'wave_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'hand', + 'medium-light skin tone', + 'wave', + 'waving', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'hola', + 'friend', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo' + ], + modifiable: true), + Emoji( + name: 'waving hand: medium skin tone', + char: '\u{1F44B}\u{1F3FD}', + shortName: 'wave_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'hand', + 'medium skin tone', + 'wave', + 'waving', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'hola', + 'friend', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo' + ], + modifiable: true), + Emoji( + name: 'waving hand: medium-dark skin tone', + char: '\u{1F44B}\u{1F3FE}', + shortName: 'wave_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'hand', + 'medium-dark skin tone', + 'wave', + 'waving', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'hola', + 'friend', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo' + ], + modifiable: true), + Emoji( + name: 'waving hand: dark skin tone', + char: '\u{1F44B}\u{1F3FF}', + shortName: 'wave_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersOpen, + keywords: [ + 'dark skin tone', + 'hand', + 'wave', + 'waving', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'hola', + 'friend', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo' + ], + modifiable: true), + Emoji( + name: 'call me hand', + char: '\u{1F919}', + shortName: 'call_me', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'call', + 'hand', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ]), + Emoji( + name: 'call me hand: light skin tone', + char: '\u{1F919}\u{1F3FB}', + shortName: 'call_me_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'call', + 'hand', + 'light skin tone', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'call me hand: medium-light skin tone', + char: '\u{1F919}\u{1F3FC}', + shortName: 'call_me_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'call', + 'hand', + 'medium-light skin tone', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'call me hand: medium skin tone', + char: '\u{1F919}\u{1F3FD}', + shortName: 'call_me_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'call', + 'hand', + 'medium skin tone', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'call me hand: medium-dark skin tone', + char: '\u{1F919}\u{1F3FE}', + shortName: 'call_me_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'call', + 'hand', + 'medium-dark skin tone', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'call me hand: dark skin tone', + char: '\u{1F919}\u{1F3FF}', + shortName: 'call_me_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handFingersPartial, + keywords: [ + 'call', + 'dark skin tone', + 'hand', + 'uc9', + 'diversity', + 'body', + 'hands', + 'hi', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle' + ], + modifiable: true), + Emoji( + name: 'flexed biceps', + char: '\u{1F4AA}', + shortName: 'muscle', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'biceps', + 'comic', + 'flex', + 'muscle', + 'uc6', + 'sport', + 'diversity', + 'body', + 'hands', + 'flex', + 'weight lifting', + 'win', + 'feminist', + 'boys night', + 'power', + 'handsome', + 'festivus', + 'protest', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'strong', + 'weight lifter', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'feminism', + 'strong woman', + 'guys night', + 'stud', + 'blm', + 'demonstration' + ]), + Emoji( + name: 'flexed biceps: light skin tone', + char: '\u{1F4AA}\u{1F3FB}', + shortName: 'muscle_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'biceps', + 'comic', + 'flex', + 'light skin tone', + 'muscle', + 'uc8', + 'sport', + 'diversity', + 'body', + 'hands', + 'flex', + 'weight lifting', + 'win', + 'feminist', + 'boys night', + 'power', + 'handsome', + 'festivus', + 'protest', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'strong', + 'weight lifter', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'feminism', + 'strong woman', + 'guys night', + 'stud', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'flexed biceps: medium-light skin tone', + char: '\u{1F4AA}\u{1F3FC}', + shortName: 'muscle_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'biceps', + 'comic', + 'flex', + 'medium-light skin tone', + 'muscle', + 'uc8', + 'sport', + 'diversity', + 'body', + 'hands', + 'flex', + 'weight lifting', + 'win', + 'feminist', + 'boys night', + 'power', + 'handsome', + 'festivus', + 'protest', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'strong', + 'weight lifter', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'feminism', + 'strong woman', + 'guys night', + 'stud', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'flexed biceps: medium skin tone', + char: '\u{1F4AA}\u{1F3FD}', + shortName: 'muscle_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'biceps', + 'comic', + 'flex', + 'medium skin tone', + 'muscle', + 'uc8', + 'sport', + 'diversity', + 'body', + 'hands', + 'flex', + 'weight lifting', + 'win', + 'feminist', + 'boys night', + 'power', + 'handsome', + 'festivus', + 'protest', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'strong', + 'weight lifter', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'feminism', + 'strong woman', + 'guys night', + 'stud', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'flexed biceps: medium-dark skin tone', + char: '\u{1F4AA}\u{1F3FE}', + shortName: 'muscle_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'biceps', + 'comic', + 'flex', + 'medium-dark skin tone', + 'muscle', + 'uc8', + 'sport', + 'diversity', + 'body', + 'hands', + 'flex', + 'weight lifting', + 'win', + 'feminist', + 'boys night', + 'power', + 'handsome', + 'festivus', + 'protest', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'strong', + 'weight lifter', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'feminism', + 'strong woman', + 'guys night', + 'stud', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'flexed biceps: dark skin tone', + char: '\u{1F4AA}\u{1F3FF}', + shortName: 'muscle_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'biceps', + 'comic', + 'dark skin tone', + 'flex', + 'muscle', + 'uc8', + 'sport', + 'diversity', + 'body', + 'hands', + 'flex', + 'weight lifting', + 'win', + 'feminist', + 'boys night', + 'power', + 'handsome', + 'festivus', + 'protest', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'strong', + 'weight lifter', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'feminism', + 'strong woman', + 'guys night', + 'stud', + 'blm', + 'demonstration' + ], + modifiable: true), + Emoji( + name: 'mechanical arm', + char: '\u{1F9BE}', + shortName: 'mechanical_arm', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc12', + 'body', + 'science', + 'handicap', + 'prosthetic', + 'fake arm', + 'accessibility', + 'body part', + 'anatomy', + 'lab', + 'disabled', + 'disability', + 'prosthetics', + 'robotic arm' + ]), + Emoji( + name: 'middle finger', + char: '\u{1F595}', + shortName: 'middle_finger', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'finger', + 'hand', + 'uc7', + 'diversity', + 'penis', + 'body', + 'hands', + 'angry', + 'middle finger', + 'sex', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'dick', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'flipping off', + 'fu', + 'the finger', + 'fuck you', + 'fuck off', + 'fuck', + 'fucking', + 'horny', + 'humping' + ]), + Emoji( + name: 'middle finger: light skin tone', + char: '\u{1F595}\u{1F3FB}', + shortName: 'middle_finger_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'finger', + 'hand', + 'light skin tone', + 'uc8', + 'diversity', + 'penis', + 'body', + 'hands', + 'angry', + 'middle finger', + 'sex', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'dick', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'flipping off', + 'fu', + 'the finger', + 'fuck you', + 'fuck off', + 'fuck', + 'fucking', + 'horny', + 'humping' + ], + modifiable: true), + Emoji( + name: 'middle finger: medium-light skin tone', + char: '\u{1F595}\u{1F3FC}', + shortName: 'middle_finger_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'finger', + 'hand', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'penis', + 'body', + 'hands', + 'angry', + 'middle finger', + 'sex', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'dick', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'flipping off', + 'fu', + 'the finger', + 'fuck you', + 'fuck off', + 'fuck', + 'fucking', + 'horny', + 'humping' + ], + modifiable: true), + Emoji( + name: 'middle finger: medium skin tone', + char: '\u{1F595}\u{1F3FD}', + shortName: 'middle_finger_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'finger', + 'hand', + 'medium skin tone', + 'uc8', + 'diversity', + 'penis', + 'body', + 'hands', + 'angry', + 'middle finger', + 'sex', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'dick', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'flipping off', + 'fu', + 'the finger', + 'fuck you', + 'fuck off', + 'fuck', + 'fucking', + 'horny', + 'humping' + ], + modifiable: true), + Emoji( + name: 'middle finger: medium-dark skin tone', + char: '\u{1F595}\u{1F3FE}', + shortName: 'middle_finger_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'finger', + 'hand', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'penis', + 'body', + 'hands', + 'angry', + 'middle finger', + 'sex', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'dick', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'flipping off', + 'fu', + 'the finger', + 'fuck you', + 'fuck off', + 'fuck', + 'fucking', + 'horny', + 'humping' + ], + modifiable: true), + Emoji( + name: 'middle finger: dark skin tone', + char: '\u{1F595}\u{1F3FF}', + shortName: 'middle_finger_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handSingleFinger, + keywords: [ + 'dark skin tone', + 'finger', + 'hand', + 'uc8', + 'diversity', + 'penis', + 'body', + 'hands', + 'angry', + 'middle finger', + 'sex', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'dick', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'flipping off', + 'fu', + 'the finger', + 'fuck you', + 'fuck off', + 'fuck', + 'fucking', + 'horny', + 'humping' + ], + modifiable: true), + Emoji( + name: 'writing hand', + char: '\u{270D}\u{FE0F}', + shortName: 'writing_hand', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'hand', + 'write', + 'uc1', + 'diversity', + 'body', + 'hands', + 'write', + 'color', + 'correct', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'writing', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch', + 'passing grade' + ]), + Emoji( + name: 'writing hand: light skin tone', + char: '\u{270D}\u{1F3FB}', + shortName: 'writing_hand_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'hand', + 'light skin tone', + 'write', + 'uc8', + 'diversity', + 'body', + 'hands', + 'write', + 'color', + 'correct', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'writing', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'writing hand: medium-light skin tone', + char: '\u{270D}\u{1F3FC}', + shortName: 'writing_hand_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'hand', + 'medium-light skin tone', + 'write', + 'uc8', + 'diversity', + 'body', + 'hands', + 'write', + 'color', + 'correct', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'writing', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'writing hand: medium skin tone', + char: '\u{270D}\u{1F3FD}', + shortName: 'writing_hand_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'hand', + 'medium skin tone', + 'write', + 'uc8', + 'diversity', + 'body', + 'hands', + 'write', + 'color', + 'correct', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'writing', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'writing hand: medium-dark skin tone', + char: '\u{270D}\u{1F3FE}', + shortName: 'writing_hand_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'hand', + 'medium-dark skin tone', + 'write', + 'uc8', + 'diversity', + 'body', + 'hands', + 'write', + 'color', + 'correct', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'writing', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'writing hand: dark skin tone', + char: '\u{270D}\u{1F3FF}', + shortName: 'writing_hand_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'dark skin tone', + 'hand', + 'write', + 'uc8', + 'diversity', + 'body', + 'hands', + 'write', + 'color', + 'correct', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'writing', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch', + 'passing grade' + ], + modifiable: true), + Emoji( + name: 'folded hands', + char: '\u{1F64F}', + shortName: 'pray', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'ask', + 'bow', + 'folded', + 'gesture', + 'hand', + 'please', + 'pray', + 'thanks', + 'uc6', + 'diversity', + 'body', + 'hands', + 'hi', + 'luck', + 'thank you', + 'pray', + 'scientology', + 'jesus', + 'pleased', + 'yoga', + 'easter', + 'begging', + 'help', + 'hope', + 'soul', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'good luck', + 'lucky', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'scientologist', + 'please', + 'chill', + 'confident', + 'content', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'swear', + 'promise' + ]), + Emoji( + name: 'folded hands: light skin tone', + char: '\u{1F64F}\u{1F3FB}', + shortName: 'pray_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'ask', + 'bow', + 'folded', + 'gesture', + 'hand', + 'light skin tone', + 'please', + 'pray', + 'thanks', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'luck', + 'thank you', + 'pray', + 'scientology', + 'jesus', + 'pleased', + 'yoga', + 'easter', + 'begging', + 'help', + 'hope', + 'soul', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'good luck', + 'lucky', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'scientologist', + 'please', + 'chill', + 'confident', + 'content', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'folded hands: medium-light skin tone', + char: '\u{1F64F}\u{1F3FC}', + shortName: 'pray_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'ask', + 'bow', + 'folded', + 'gesture', + 'hand', + 'medium-light skin tone', + 'please', + 'pray', + 'thanks', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'luck', + 'thank you', + 'pray', + 'scientology', + 'jesus', + 'pleased', + 'yoga', + 'easter', + 'begging', + 'help', + 'hope', + 'soul', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'good luck', + 'lucky', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'scientologist', + 'please', + 'chill', + 'confident', + 'content', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'folded hands: medium skin tone', + char: '\u{1F64F}\u{1F3FD}', + shortName: 'pray_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'ask', + 'bow', + 'folded', + 'gesture', + 'hand', + 'medium skin tone', + 'please', + 'pray', + 'thanks', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'luck', + 'thank you', + 'pray', + 'scientology', + 'jesus', + 'pleased', + 'yoga', + 'easter', + 'begging', + 'help', + 'hope', + 'soul', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'good luck', + 'lucky', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'scientologist', + 'please', + 'chill', + 'confident', + 'content', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'folded hands: medium-dark skin tone', + char: '\u{1F64F}\u{1F3FE}', + shortName: 'pray_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'ask', + 'bow', + 'folded', + 'gesture', + 'hand', + 'medium-dark skin tone', + 'please', + 'pray', + 'thanks', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'luck', + 'thank you', + 'pray', + 'scientology', + 'jesus', + 'pleased', + 'yoga', + 'easter', + 'begging', + 'help', + 'hope', + 'soul', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'good luck', + 'lucky', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'scientologist', + 'please', + 'chill', + 'confident', + 'content', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'folded hands: dark skin tone', + char: '\u{1F64F}\u{1F3FF}', + shortName: 'pray_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.hands, + keywords: [ + 'ask', + 'bow', + 'dark skin tone', + 'folded', + 'gesture', + 'hand', + 'please', + 'pray', + 'thanks', + 'uc8', + 'diversity', + 'body', + 'hands', + 'hi', + 'luck', + 'thank you', + 'pray', + 'scientology', + 'jesus', + 'pleased', + 'yoga', + 'easter', + 'begging', + 'help', + 'hope', + 'soul', + 'language', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'good luck', + 'lucky', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'scientologist', + 'please', + 'chill', + 'confident', + 'content', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'foot', + char: '\u{1F9B6}', + shortName: 'foot', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'feet', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'toes', + 'heel', + 'ankle' + ]), + Emoji( + name: 'foot: light skin tone', + char: '\u{1F9B6}\u{1F3FB}', + shortName: 'foot_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'feet', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'toes', + 'heel', + 'ankle' + ], + modifiable: true), + Emoji( + name: 'foot: medium-light skin tone', + char: '\u{1F9B6}\u{1F3FC}', + shortName: 'foot_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'feet', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'toes', + 'heel', + 'ankle' + ], + modifiable: true), + Emoji( + name: 'foot: medium skin tone', + char: '\u{1F9B6}\u{1F3FD}', + shortName: 'foot_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'feet', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'toes', + 'heel', + 'ankle' + ], + modifiable: true), + Emoji( + name: 'foot: medium-dark skin tone', + char: '\u{1F9B6}\u{1F3FE}', + shortName: 'foot_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'feet', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'toes', + 'heel', + 'ankle' + ], + modifiable: true), + Emoji( + name: 'foot: dark skin tone', + char: '\u{1F9B6}\u{1F3FF}', + shortName: 'foot_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'feet', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'toes', + 'heel', + 'ankle' + ], + modifiable: true), + Emoji( + name: 'leg', + char: '\u{1F9B5}', + shortName: 'leg', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'feet', + 'knee', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'toes', + 'heel', + 'ankle', + 'thigh', + 'calf' + ]), + Emoji( + name: 'leg: light skin tone', + char: '\u{1F9B5}\u{1F3FB}', + shortName: 'leg_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'feet', + 'knee', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'toes', + 'heel', + 'ankle', + 'thigh', + 'calf' + ], + modifiable: true), + Emoji( + name: 'leg: medium-light skin tone', + char: '\u{1F9B5}\u{1F3FC}', + shortName: 'leg_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'feet', + 'knee', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'toes', + 'heel', + 'ankle', + 'thigh', + 'calf' + ], + modifiable: true), + Emoji( + name: 'leg: medium skin tone', + char: '\u{1F9B5}\u{1F3FD}', + shortName: 'leg_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'feet', + 'knee', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'toes', + 'heel', + 'ankle', + 'thigh', + 'calf' + ], + modifiable: true), + Emoji( + name: 'leg: medium-dark skin tone', + char: '\u{1F9B5}\u{1F3FE}', + shortName: 'leg_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'feet', + 'knee', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'toes', + 'heel', + 'ankle', + 'thigh', + 'calf' + ], + modifiable: true), + Emoji( + name: 'leg: dark skin tone', + char: '\u{1F9B5}\u{1F3FF}', + shortName: 'leg_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'feet', + 'knee', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'toes', + 'heel', + 'ankle', + 'thigh', + 'calf' + ], + modifiable: true), + Emoji( + name: 'mechanical leg', + char: '\u{1F9BF}', + shortName: 'mechanical_leg', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc12', + 'body', + 'science', + 'handicap', + 'feet', + 'knee', + 'prosthetic', + 'fake leg', + 'accessibility', + 'medical', + 'body part', + 'anatomy', + 'lab', + 'disabled', + 'disability', + 'toes', + 'heel', + 'ankle', + 'thigh', + 'calf', + 'prosthetics', + 'robotic leg' + ]), + Emoji( + name: 'lipstick', + char: '\u{1F484}', + shortName: 'lipstick', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'cosmetics', + 'makeup', + 'uc6', + 'fashion', + 'women', + 'love', + 'sexy', + 'lipstick', + 'beautiful', + 'girls night', + 'color', + 'mirror', + 'clothes', + 'clothing', + 'style', + 'woman', + 'female', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'mouth', + 'mouths', + 'makeup', + 'lip', + 'lips', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch' + ]), + Emoji( + name: 'kiss mark', + char: '\u{1F48B}', + shortName: 'kiss', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'kiss', + 'lips', + 'uc6', + 'women', + 'love', + 'sexy', + 'lipstick', + 'beautiful', + 'girls night', + 'pink', + 'kisses', + 'mirror', + 'porn', + 'woman', + 'female', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'mouth', + 'mouths', + 'makeup', + 'lip', + 'lips', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'rose', + 'bisous', + 'beijos', + 'besos', + 'bise', + 'blowing kisses', + 'kissy' + ]), + Emoji( + name: 'mouth', + char: '\u{1F444}', + shortName: 'lips', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'lips', + 'uc6', + 'women', + 'body', + 'sexy', + 'lipstick', + 'beautiful', + 'porn', + 'woman', + 'female', + 'body part', + 'anatomy', + 'mouth', + 'mouths', + 'makeup', + 'lip', + 'lips', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ]), + Emoji( + name: 'tooth', + char: '\u{1F9B7}', + shortName: 'tooth', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'body', + 'teeth', + 'bite', + 'bones', + 'medical', + 'body part', + 'anatomy', + 'dentist', + 'Os', + 'hueso' + ]), + Emoji( + name: 'bone', + char: '\u{1F9B4}', + shortName: 'bone', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc11', + 'body', + 'science', + 'mystery', + 'bones', + 'medical', + 'body part', + 'anatomy', + 'lab', + 'Os', + 'hueso' + ]), + Emoji( + name: 'tongue', + char: '\u{1F445}', + shortName: 'tongue', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'uc6', + 'body', + 'sexy', + 'sex', + 'lipstick', + 'pussy', + 'pink', + 'lick', + 'porn', + 'tongue', + 'medical', + 'body part', + 'anatomy', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'mouth', + 'mouths', + 'makeup', + 'lip', + 'lips', + 'condom', + 'rose', + 'toung', + 'tounge' + ]), + Emoji( + name: 'ear', + char: '\u{1F442}', + shortName: 'ear', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'uc6', + 'diversity', + 'body', + 'sound', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear' + ]), + Emoji( + name: 'ear: light skin tone', + char: '\u{1F442}\u{1F3FB}', + shortName: 'ear_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'light skin tone', + 'uc8', + 'diversity', + 'body', + 'sound', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear' + ], + modifiable: true), + Emoji( + name: 'ear: medium-light skin tone', + char: '\u{1F442}\u{1F3FC}', + shortName: 'ear_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'body', + 'sound', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear' + ], + modifiable: true), + Emoji( + name: 'ear: medium skin tone', + char: '\u{1F442}\u{1F3FD}', + shortName: 'ear_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'medium skin tone', + 'uc8', + 'diversity', + 'body', + 'sound', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear' + ], + modifiable: true), + Emoji( + name: 'ear: medium-dark skin tone', + char: '\u{1F442}\u{1F3FE}', + shortName: 'ear_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'body', + 'sound', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear' + ], + modifiable: true), + Emoji( + name: 'ear: dark skin tone', + char: '\u{1F442}\u{1F3FF}', + shortName: 'ear_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'dark skin tone', + 'uc8', + 'diversity', + 'body', + 'sound', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear' + ], + modifiable: true), + Emoji( + name: 'ear with hearing aid', + char: '\u{1F9BB}', + shortName: 'ear_with_hearing_aid', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'earphone', + 'sound', + 'deaf', + 'accessibility', + 'medical', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'earbud', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ]), + Emoji( + name: 'ear with hearing aid: light skin tone', + char: '\u{1F9BB}\u{1F3FB}', + shortName: 'ear_with_hearing_aid_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'earphone', + 'sound', + 'deaf', + 'accessibility', + 'medical', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'earbud', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'ear with hearing aid: medium-light skin tone', + char: '\u{1F9BB}\u{1F3FC}', + shortName: 'ear_with_hearing_aid_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'earphone', + 'sound', + 'deaf', + 'accessibility', + 'medical', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'earbud', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'ear with hearing aid: medium skin tone', + char: '\u{1F9BB}\u{1F3FD}', + shortName: 'ear_with_hearing_aid_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'earphone', + 'sound', + 'deaf', + 'accessibility', + 'medical', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'earbud', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'ear with hearing aid: medium-dark skin tone', + char: '\u{1F9BB}\u{1F3FE}', + shortName: 'ear_with_hearing_aid_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'earphone', + 'sound', + 'deaf', + 'accessibility', + 'medical', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'earbud', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'ear with hearing aid: dark skin tone', + char: '\u{1F9BB}\u{1F3FF}', + shortName: 'ear_with_hearing_aid_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'earphone', + 'sound', + 'deaf', + 'accessibility', + 'medical', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'earbud', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'nose', + char: '\u{1F443}', + shortName: 'nose', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'uc6', + 'diversity', + 'body', + 'stinky', + 'booger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'smell', + 'stink', + 'odor' + ]), + Emoji( + name: 'nose: light skin tone', + char: '\u{1F443}\u{1F3FB}', + shortName: 'nose_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'light skin tone', + 'uc8', + 'diversity', + 'body', + 'stinky', + 'booger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'smell', + 'stink', + 'odor' + ], + modifiable: true), + Emoji( + name: 'nose: medium-light skin tone', + char: '\u{1F443}\u{1F3FC}', + shortName: 'nose_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'body', + 'stinky', + 'booger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'smell', + 'stink', + 'odor' + ], + modifiable: true), + Emoji( + name: 'nose: medium skin tone', + char: '\u{1F443}\u{1F3FD}', + shortName: 'nose_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'medium skin tone', + 'uc8', + 'diversity', + 'body', + 'stinky', + 'booger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'smell', + 'stink', + 'odor' + ], + modifiable: true), + Emoji( + name: 'nose: medium-dark skin tone', + char: '\u{1F443}\u{1F3FE}', + shortName: 'nose_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'body', + 'stinky', + 'booger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'smell', + 'stink', + 'odor' + ], + modifiable: true), + Emoji( + name: 'nose: dark skin tone', + char: '\u{1F443}\u{1F3FF}', + shortName: 'nose_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'dark skin tone', + 'uc8', + 'diversity', + 'body', + 'stinky', + 'booger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'smell', + 'stink', + 'odor' + ], + modifiable: true), + Emoji( + name: 'footprints', + char: '\u{1F463}', + shortName: 'footprints', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSymbol, + keywords: [ + 'clothing', + 'footprint', + 'print', + 'uc6', + 'baby', + 'paws', + 'feet', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'toes', + 'heel', + 'ankle' + ]), + Emoji( + name: 'eye', + char: '\u{1F441}\u{FE0F}', + shortName: 'eye', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'body', + 'uc7', + 'body', + 'eyes', + 'search', + 'medical', + 'body part', + 'anatomy', + 'eye', + 'eyebrow', + 'look', + 'find', + 'looking', + 'see' + ]), + Emoji( + name: 'eyes', + char: '\u{1F440}', + shortName: 'eyes', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'eye', + 'face', + 'uc6', + 'halloween', + 'body', + 'rolling eyes', + 'eyes', + 'google', + 'brain', + 'disney', + 'search', + 'eyeroll', + 'porn', + 'samhain', + 'body part', + 'anatomy', + 'eye roll', + 'side eye', + 'eye', + 'eyebrow', + 'mind', + 'memory', + 'thought', + 'conscience', + 'cartoon', + 'look', + 'find', + 'looking', + 'see' + ]), + Emoji( + name: 'brain', + char: '\u{1F9E0}', + shortName: 'brain', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'intelligent', + 'uc10', + 'halloween', + 'body', + 'science', + 'nerd', + 'brain', + 'medical', + 'samhain', + 'body part', + 'anatomy', + 'lab', + 'smart', + 'geek', + 'serious', + 'mind', + 'memory', + 'thought', + 'conscience' + ]), + Emoji( + name: 'anatomical heart', + char: '\u{1FAC0}', + shortName: 'anatomical_heart', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc13', + 'body', + 'science', + 'heart', + 'covid', + 'medical', + 'body part', + 'anatomy', + 'lab', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur' + ]), + Emoji( + name: 'lungs', + char: '\u{1FAC1}', + shortName: 'lungs', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.bodyParts, + keywords: [ + 'uc13', + 'body', + 'smoking', + 'science', + 'breathe', + 'covid', + 'medical', + 'body part', + 'anatomy', + 'smoke', + 'cigarette', + 'puff', + 'lab', + 'sigh', + 'inhale' + ]), + Emoji( + name: 'speaking head', + char: '\u{1F5E3}\u{FE0F}', + shortName: 'speaking_head', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSymbol, + keywords: [ + 'face', + 'head', + 'silhouette', + 'speak', + 'speaking', + 'uc7', + 'talk', + 'sound', + 'language', + 'talking', + 'speech', + 'social', + 'chat', + 'voice', + 'speechless', + 'speak', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear' + ]), + Emoji( + name: 'bust in silhouette', + char: '\u{1F464}', + shortName: 'bust_in_silhouette', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSymbol, + keywords: [ + 'bust', + 'silhouette', + 'uc6', + 'facebook', + 'fame', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'busts in silhouette', + char: '\u{1F465}', + shortName: 'busts_in_silhouette', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSymbol, + keywords: ['bust', 'silhouette', 'uc6', 'magnet', 'facebook', 'network']), + Emoji( + name: 'people hugging', + char: '\u{1FAC2}', + shortName: 'people_hugging', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSymbol, + keywords: ['uc13', 'hug', 'embrace', 'hugs']), + Emoji( + name: 'baby', + char: '\u{1F476}', + shortName: 'baby', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'baby', + 'young', + 'uc6', + 'diversity', + 'baby', + 'christmas', + 'human', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'baby: light skin tone', + char: '\u{1F476}\u{1F3FB}', + shortName: 'baby_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'baby', + 'light skin tone', + 'young', + 'uc8', + 'diversity', + 'baby', + 'christmas', + 'human', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'baby: medium-light skin tone', + char: '\u{1F476}\u{1F3FC}', + shortName: 'baby_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'baby', + 'medium-light skin tone', + 'young', + 'uc8', + 'diversity', + 'baby', + 'christmas', + 'human', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'baby: medium skin tone', + char: '\u{1F476}\u{1F3FD}', + shortName: 'baby_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'baby', + 'medium skin tone', + 'young', + 'uc8', + 'diversity', + 'baby', + 'christmas', + 'human', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'baby: medium-dark skin tone', + char: '\u{1F476}\u{1F3FE}', + shortName: 'baby_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'baby', + 'medium-dark skin tone', + 'young', + 'uc8', + 'diversity', + 'baby', + 'christmas', + 'human', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'baby: dark skin tone', + char: '\u{1F476}\u{1F3FF}', + shortName: 'baby_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'baby', + 'dark skin tone', + 'young', + 'uc8', + 'diversity', + 'baby', + 'christmas', + 'human', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'girl', + char: '\u{1F467}', + shortName: 'girl', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'Virgo', + 'young', + 'zodiac', + 'uc6', + 'diversity', + 'women', + 'beautiful', + 'human', + 'wife', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'girl: light skin tone', + char: '\u{1F467}\u{1F3FB}', + shortName: 'girl_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'Virgo', + 'light skin tone', + 'young', + 'zodiac', + 'uc8', + 'diversity', + 'women', + 'beautiful', + 'human', + 'wife', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'girl: medium-light skin tone', + char: '\u{1F467}\u{1F3FC}', + shortName: 'girl_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'Virgo', + 'medium-light skin tone', + 'young', + 'zodiac', + 'uc8', + 'diversity', + 'women', + 'beautiful', + 'human', + 'wife', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'girl: medium skin tone', + char: '\u{1F467}\u{1F3FD}', + shortName: 'girl_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'Virgo', + 'medium skin tone', + 'young', + 'zodiac', + 'uc8', + 'diversity', + 'women', + 'beautiful', + 'human', + 'wife', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'girl: medium-dark skin tone', + char: '\u{1F467}\u{1F3FE}', + shortName: 'girl_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'Virgo', + 'medium-dark skin tone', + 'young', + 'zodiac', + 'uc8', + 'diversity', + 'women', + 'beautiful', + 'human', + 'wife', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'girl: dark skin tone', + char: '\u{1F467}\u{1F3FF}', + shortName: 'girl_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'Virgo', + 'dark skin tone', + 'young', + 'zodiac', + 'uc8', + 'diversity', + 'women', + 'beautiful', + 'human', + 'wife', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'child', + char: '\u{1F9D2}', + shortName: 'child', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc10', + 'men', + 'feminist', + 'beautiful', + 'human', + 'child', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'child: light skin tone', + char: '\u{1F9D2}\u{1F3FB}', + shortName: 'child_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'gender-neutral', + 'light skin tone', + 'young', + 'uc10', + 'men', + 'feminist', + 'beautiful', + 'human', + 'child', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'child: medium-light skin tone', + char: '\u{1F9D2}\u{1F3FC}', + shortName: 'child_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'gender-neutral', + 'medium-light skin tone', + 'young', + 'uc10', + 'men', + 'feminist', + 'beautiful', + 'human', + 'child', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'child: medium skin tone', + char: '\u{1F9D2}\u{1F3FD}', + shortName: 'child_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'gender-neutral', + 'medium skin tone', + 'young', + 'uc10', + 'men', + 'feminist', + 'beautiful', + 'human', + 'child', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'child: medium-dark skin tone', + char: '\u{1F9D2}\u{1F3FE}', + shortName: 'child_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'gender-neutral', + 'medium-dark skin tone', + 'young', + 'uc10', + 'men', + 'feminist', + 'beautiful', + 'human', + 'child', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'child: dark skin tone', + char: '\u{1F9D2}\u{1F3FF}', + shortName: 'child_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'dark skin tone', + 'gender-neutral', + 'young', + 'uc10', + 'men', + 'feminist', + 'beautiful', + 'human', + 'child', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'boy', + char: '\u{1F466}', + shortName: 'boy', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'boy', + 'young', + 'uc6', + 'diversity', + 'men', + 'human', + 'handsome', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'stud', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'boy: light skin tone', + char: '\u{1F466}\u{1F3FB}', + shortName: 'boy_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'boy', + 'light skin tone', + 'young', + 'uc8', + 'diversity', + 'men', + 'human', + 'handsome', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'stud', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'boy: medium-light skin tone', + char: '\u{1F466}\u{1F3FC}', + shortName: 'boy_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'boy', + 'medium-light skin tone', + 'young', + 'uc8', + 'diversity', + 'men', + 'human', + 'handsome', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'stud', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'boy: medium skin tone', + char: '\u{1F466}\u{1F3FD}', + shortName: 'boy_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'boy', + 'medium skin tone', + 'young', + 'uc8', + 'diversity', + 'men', + 'human', + 'handsome', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'stud', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'boy: medium-dark skin tone', + char: '\u{1F466}\u{1F3FE}', + shortName: 'boy_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'boy', + 'medium-dark skin tone', + 'young', + 'uc8', + 'diversity', + 'men', + 'human', + 'handsome', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'stud', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'boy: dark skin tone', + char: '\u{1F466}\u{1F3FF}', + shortName: 'boy_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'boy', + 'dark skin tone', + 'young', + 'uc8', + 'diversity', + 'men', + 'human', + 'handsome', + 'child', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'stud', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'woman', + char: '\u{1F469}', + shortName: 'woman', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'woman', + 'uc6', + 'diversity', + 'lesbian', + 'women', + 'feminist', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman: light skin tone', + char: '\u{1F469}\u{1F3FB}', + shortName: 'woman_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'light skin tone', + 'woman', + 'uc8', + 'diversity', + 'lesbian', + 'women', + 'feminist', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}', + shortName: 'woman_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'medium-light skin tone', + 'woman', + 'uc8', + 'diversity', + 'lesbian', + 'women', + 'feminist', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: medium skin tone', + char: '\u{1F469}\u{1F3FD}', + shortName: 'woman_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'medium skin tone', + 'woman', + 'uc8', + 'diversity', + 'lesbian', + 'women', + 'feminist', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}', + shortName: 'woman_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'medium-dark skin tone', + 'woman', + 'uc8', + 'diversity', + 'lesbian', + 'women', + 'feminist', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: dark skin tone', + char: '\u{1F469}\u{1F3FF}', + shortName: 'woman_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'dark skin tone', + 'woman', + 'uc8', + 'diversity', + 'lesbian', + 'women', + 'feminist', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person', + char: '\u{1F9D1}', + shortName: 'adult', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc10', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'girls night', + 'boys night', + 'human', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'gender', + 'people', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person: light skin tone', + char: '\u{1F9D1}\u{1F3FB}', + shortName: 'adult_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'gender-neutral', + 'light skin tone', + 'uc10', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'girls night', + 'boys night', + 'human', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'gender', + 'people', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}', + shortName: 'adult_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'gender-neutral', + 'medium-light skin tone', + 'uc10', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'girls night', + 'boys night', + 'human', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'gender', + 'people', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}', + shortName: 'adult_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'gender-neutral', + 'medium skin tone', + 'uc10', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'girls night', + 'boys night', + 'human', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'gender', + 'people', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}', + shortName: 'adult_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'gender-neutral', + 'medium-dark skin tone', + 'uc10', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'girls night', + 'boys night', + 'human', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'gender', + 'people', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}', + shortName: 'adult_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'dark skin tone', + 'gender-neutral', + 'uc10', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'girls night', + 'boys night', + 'human', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'gender', + 'people', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man', + char: '\u{1F468}', + shortName: 'man', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'man', + 'uc6', + 'diversity', + 'men', + 'boys night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ]), + Emoji( + name: 'man: light skin tone', + char: '\u{1F468}\u{1F3FB}', + shortName: 'man_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'light skin tone', + 'man', + 'uc8', + 'diversity', + 'men', + 'boys night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}', + shortName: 'man_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'man', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'men', + 'boys night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man: medium skin tone', + char: '\u{1F468}\u{1F3FD}', + shortName: 'man_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'man', + 'medium skin tone', + 'uc8', + 'diversity', + 'men', + 'boys night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}', + shortName: 'man_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'man', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'men', + 'boys night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man: dark skin tone', + char: '\u{1F468}\u{1F3FF}', + shortName: 'man_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'dark skin tone', + 'man', + 'uc8', + 'diversity', + 'men', + 'boys night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'person: curly hair', + char: '\u{1F9D1}\u{200D}\u{1F9B1}', + shortName: 'person_curly_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'afro', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + "'fro", + 'curls', + 'frizzy', + 'perm' + ]), + Emoji( + name: 'person: light skin tone, curly hair', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F9B1}', + shortName: 'person_tone1_curly_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'afro', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'person: medium-light skin tone, curly hair', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F9B1}', + shortName: 'person_tone2_curly_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'afro', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'person: medium skin tone, curly hair', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F9B1}', + shortName: 'person_tone3_curly_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'afro', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'person: medium-dark skin tone, curly hair', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F9B1}', + shortName: 'person_tone4_curly_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'afro', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'person: dark skin tone, curly hair', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F9B1}', + shortName: 'person_tone5_curly_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'afro', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'woman: curly hair', + char: '\u{1F469}\u{200D}\u{1F9B1}', + shortName: 'woman_curly_haired', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + "'fro", + 'curls', + 'frizzy', + 'perm' + ]), + Emoji( + name: 'woman: light skin tone, curly hair', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F9B1}', + shortName: 'woman_curly_haired_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'woman: medium-light skin tone, curly hair', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F9B1}', + shortName: 'woman_curly_haired_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'woman: medium skin tone, curly hair', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F9B1}', + shortName: 'woman_curly_haired_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'woman: medium-dark skin tone, curly hair', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F9B1}', + shortName: 'woman_curly_haired_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'woman: dark skin tone, curly hair', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F9B1}', + shortName: 'woman_curly_haired_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'man: curly hair', + char: '\u{1F468}\u{200D}\u{1F9B1}', + shortName: 'man_curly_haired', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + "'fro", + 'curls', + 'frizzy', + 'perm' + ]), + Emoji( + name: 'man: light skin tone, curly hair', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F9B1}', + shortName: 'man_curly_haired_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'man: medium-light skin tone, curly hair', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F9B1}', + shortName: 'man_curly_haired_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'man: medium skin tone, curly hair', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F9B1}', + shortName: 'man_curly_haired_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'man: medium-dark skin tone, curly hair', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F9B1}', + shortName: 'man_curly_haired_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'man: dark skin tone, curly hair', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F9B1}', + shortName: 'man_curly_haired_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + "'fro", + 'curls', + 'frizzy', + 'perm' + ], + modifiable: true), + Emoji( + name: 'person: red hair', + char: '\u{1F9D1}\u{200D}\u{1F9B0}', + shortName: 'person_red_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'lesbian', + 'men', + 'girls night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'ginger', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person: light skin tone, red hair', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F9B0}', + shortName: 'person_tone1_red_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'lesbian', + 'men', + 'girls night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'ginger', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: medium-light skin tone, red hair', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F9B0}', + shortName: 'person_tone2_red_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'lesbian', + 'men', + 'girls night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'ginger', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: medium skin tone, red hair', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F9B0}', + shortName: 'person_tone3_red_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'lesbian', + 'men', + 'girls night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'ginger', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: medium-dark skin tone, red hair', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F9B0}', + shortName: 'person_tone4_red_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'lesbian', + 'men', + 'girls night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'ginger', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: dark skin tone, red hair', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F9B0}', + shortName: 'person_tone5_red_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'lesbian', + 'men', + 'girls night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'ginger', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: red hair', + char: '\u{1F469}\u{200D}\u{1F9B0}', + shortName: 'woman_red_haired', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman: light skin tone, red hair', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F9B0}', + shortName: 'woman_red_haired_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: medium-light skin tone, red hair', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F9B0}', + shortName: 'woman_red_haired_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: medium skin tone, red hair', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F9B0}', + shortName: 'woman_red_haired_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: medium-dark skin tone, red hair', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F9B0}', + shortName: 'woman_red_haired_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: dark skin tone, red hair', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F9B0}', + shortName: 'woman_red_haired_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man: red hair', + char: '\u{1F468}\u{200D}\u{1F9B0}', + shortName: 'man_red_haired', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ]), + Emoji( + name: 'man: light skin tone, red hair', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F9B0}', + shortName: 'man_red_haired_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man: medium-light skin tone, red hair', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F9B0}', + shortName: 'man_red_haired_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man: medium skin tone, red hair', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F9B0}', + shortName: 'man_red_haired_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man: medium-dark skin tone, red hair', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F9B0}', + shortName: 'man_red_haired_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man: dark skin tone, red hair', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F9B0}', + shortName: 'man_red_haired_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'woman: blond hair', + char: '\u{1F471}\u{200D}\u{2640}\u{FE0F}', + shortName: 'blond-haired_woman', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blonde', + 'woman', + 'uc6', + 'diversity', + 'lesbian', + 'women', + 'feminist', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman: light skin tone, blond hair', + char: '\u{1F471}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'blond-haired_woman_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blonde', + 'light skin tone', + 'woman', + 'uc8', + 'diversity', + 'lesbian', + 'women', + 'feminist', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: medium-light skin tone, blond hair', + char: '\u{1F471}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'blond-haired_woman_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blonde', + 'medium-light skin tone', + 'woman', + 'uc8', + 'diversity', + 'lesbian', + 'women', + 'feminist', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: medium skin tone, blond hair', + char: '\u{1F471}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'blond-haired_woman_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blonde', + 'medium skin tone', + 'woman', + 'uc8', + 'diversity', + 'lesbian', + 'women', + 'feminist', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: medium-dark skin tone, blond hair', + char: '\u{1F471}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'blond-haired_woman_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blonde', + 'medium-dark skin tone', + 'woman', + 'uc8', + 'diversity', + 'lesbian', + 'women', + 'feminist', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: dark skin tone, blond hair', + char: '\u{1F471}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'blond-haired_woman_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blonde', + 'dark skin tone', + 'woman', + 'uc8', + 'diversity', + 'lesbian', + 'women', + 'feminist', + 'beautiful', + 'girls night', + 'human', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'feminism', + 'strong woman', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: blond hair', + char: '\u{1F471}', + shortName: 'blond_haired_person', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blond', + 'uc6', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'boys night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person: light skin tone, blond hair', + char: '\u{1F471}\u{1F3FB}', + shortName: 'blond_haired_person_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blond', + 'light skin tone', + 'uc8', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'boys night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: medium-light skin tone, blond hair', + char: '\u{1F471}\u{1F3FC}', + shortName: 'blond_haired_person_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blond', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'boys night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: medium skin tone, blond hair', + char: '\u{1F471}\u{1F3FD}', + shortName: 'blond_haired_person_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blond', + 'medium skin tone', + 'uc8', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'boys night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: medium-dark skin tone, blond hair', + char: '\u{1F471}\u{1F3FE}', + shortName: 'blond_haired_person_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blond', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'boys night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: dark skin tone, blond hair', + char: '\u{1F471}\u{1F3FF}', + shortName: 'blond_haired_person_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blond', + 'dark skin tone', + 'uc8', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'boys night', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man: blond hair', + char: '\u{1F471}\u{200D}\u{2642}\u{FE0F}', + shortName: 'blond-haired_man', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blond', + 'man', + 'uc6', + 'diversity', + 'men', + 'boys night', + 'human', + 'daddy', + 'parent', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ]), + Emoji( + name: 'man: light skin tone, blond hair', + char: '\u{1F471}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'blond-haired_man_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blond', + 'light skin tone', + 'man', + 'uc8', + 'diversity', + 'men', + 'boys night', + 'human', + 'daddy', + 'parent', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'man: medium-light skin tone, blond hair', + char: '\u{1F471}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'blond-haired_man_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blond', + 'man', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'men', + 'boys night', + 'human', + 'daddy', + 'parent', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'man: medium skin tone, blond hair', + char: '\u{1F471}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'blond-haired_man_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blond', + 'man', + 'medium skin tone', + 'uc8', + 'diversity', + 'men', + 'boys night', + 'human', + 'daddy', + 'parent', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'man: medium-dark skin tone, blond hair', + char: '\u{1F471}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'blond-haired_man_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blond', + 'man', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'men', + 'boys night', + 'human', + 'daddy', + 'parent', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'man: dark skin tone, blond hair', + char: '\u{1F471}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'blond-haired_man_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'blond', + 'dark skin tone', + 'man', + 'uc8', + 'diversity', + 'men', + 'boys night', + 'human', + 'daddy', + 'parent', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'person: white hair', + char: '\u{1F9D1}\u{200D}\u{1F9B3}', + shortName: 'person_white_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'old people', + 'lesbian', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ]), + Emoji( + name: 'person: light skin tone, white hair', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F9B3}', + shortName: 'person_tone1_white_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'old people', + 'lesbian', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'person: medium-light skin tone, white hair', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F9B3}', + shortName: 'person_tone2_white_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'old people', + 'lesbian', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'person: medium skin tone, white hair', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F9B3}', + shortName: 'person_tone3_white_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'old people', + 'lesbian', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'person: medium-dark skin tone, white hair', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F9B3}', + shortName: 'person_tone4_white_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'old people', + 'lesbian', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'person: dark skin tone, white hair', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F9B3}', + shortName: 'person_tone5_white_hair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'old people', + 'lesbian', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'woman: white hair', + char: '\u{1F469}\u{200D}\u{1F9B3}', + shortName: 'woman_white_haired', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ]), + Emoji( + name: 'woman: light skin tone, white hair', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F9B3}', + shortName: 'woman_white_haired_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'woman: medium-light skin tone, white hair', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F9B3}', + shortName: 'woman_white_haired_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'woman: medium skin tone, white hair', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F9B3}', + shortName: 'woman_white_haired_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'woman: medium-dark skin tone, white hair', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F9B3}', + shortName: 'woman_white_haired_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'woman: dark skin tone, white hair', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F9B3}', + shortName: 'woman_white_haired_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'man: white hair', + char: '\u{1F468}\u{200D}\u{1F9B3}', + shortName: 'man_white_haired', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'silver hair' + ]), + Emoji( + name: 'man: light skin tone, white hair', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F9B3}', + shortName: 'man_white_haired_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'man: medium-light skin tone, white hair', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F9B3}', + shortName: 'man_white_haired_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'man: medium skin tone, white hair', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F9B3}', + shortName: 'man_white_haired_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'man: medium-dark skin tone, white hair', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F9B3}', + shortName: 'man_white_haired_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'man: dark skin tone, white hair', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F9B3}', + shortName: 'man_white_haired_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'person: bald', + char: '\u{1F9D1}\u{200D}\u{1F9B2}', + shortName: 'person_bald', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'lesbian', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'shaved head', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person: light skin tone, bald', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F9B2}', + shortName: 'person_tone1_bald', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'lesbian', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'shaved head', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: medium-light skin tone, bald', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F9B2}', + shortName: 'person_tone2_bald', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'lesbian', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'shaved head', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: medium skin tone, bald', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F9B2}', + shortName: 'person_tone3_bald', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'lesbian', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'shaved head', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: medium-dark skin tone, bald', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F9B2}', + shortName: 'person_tone4_bald', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'lesbian', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'shaved head', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person: dark skin tone, bald', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F9B2}', + shortName: 'person_tone5_bald', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc12', + 'lesbian', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'shaved head', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman: bald', + char: '\u{1F469}\u{200D}\u{1F9B2}', + shortName: 'woman_bald', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'balding' + ]), + Emoji( + name: 'woman: light skin tone, bald', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F9B2}', + shortName: 'woman_bald_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'balding' + ], + modifiable: true), + Emoji( + name: 'woman: medium-light skin tone, bald', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F9B2}', + shortName: 'woman_bald_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'balding' + ], + modifiable: true), + Emoji( + name: 'woman: medium skin tone, bald', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F9B2}', + shortName: 'woman_bald_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'balding' + ], + modifiable: true), + Emoji( + name: 'woman: medium-dark skin tone, bald', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F9B2}', + shortName: 'woman_bald_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'balding' + ], + modifiable: true), + Emoji( + name: 'woman: dark skin tone, bald', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F9B2}', + shortName: 'woman_bald_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'beautiful', + 'human', + 'parent', + 'wife', + 'mom', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'balding' + ], + modifiable: true), + Emoji( + name: 'man: bald', + char: '\u{1F468}\u{200D}\u{1F9B2}', + shortName: 'man_bald', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'balding' + ]), + Emoji( + name: 'man: light skin tone, bald', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F9B2}', + shortName: 'man_bald_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'balding' + ], + modifiable: true), + Emoji( + name: 'man: medium-light skin tone, bald', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F9B2}', + shortName: 'man_bald_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'balding' + ], + modifiable: true), + Emoji( + name: 'man: medium skin tone, bald', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F9B2}', + shortName: 'man_bald_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'balding' + ], + modifiable: true), + Emoji( + name: 'man: medium-dark skin tone, bald', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F9B2}', + shortName: 'man_bald_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'balding' + ], + modifiable: true), + Emoji( + name: 'man: dark skin tone, bald', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F9B2}', + shortName: 'man_bald_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'balding' + ], + modifiable: true), + Emoji( + name: 'man: beard', + char: '\u{1F9D4}', + shortName: 'bearded_person', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc10', + 'diversity', + 'men', + 'boys night', + 'mustache', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'beard', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ]), + Emoji( + name: 'man: light skin tone, beard', + char: '\u{1F9D4}\u{1F3FB}', + shortName: 'bearded_person_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'beard', + 'light skin tone', + 'uc10', + 'diversity', + 'men', + 'boys night', + 'mustache', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'beard', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man: medium-light skin tone, beard', + char: '\u{1F9D4}\u{1F3FC}', + shortName: 'bearded_person_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'beard', + 'medium-light skin tone', + 'uc10', + 'diversity', + 'men', + 'boys night', + 'mustache', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'beard', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man: medium skin tone, beard', + char: '\u{1F9D4}\u{1F3FD}', + shortName: 'bearded_person_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'beard', + 'medium skin tone', + 'uc10', + 'diversity', + 'men', + 'boys night', + 'mustache', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'beard', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man: medium-dark skin tone, beard', + char: '\u{1F9D4}\u{1F3FE}', + shortName: 'bearded_person_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'beard', + 'medium-dark skin tone', + 'uc10', + 'diversity', + 'men', + 'boys night', + 'mustache', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'beard', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man: dark skin tone, beard', + char: '\u{1F9D4}\u{1F3FF}', + shortName: 'bearded_person_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'beard', + 'dark skin tone', + 'uc10', + 'diversity', + 'men', + 'boys night', + 'mustache', + 'human', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'beard', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'old woman', + char: '\u{1F475}', + shortName: 'older_woman', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'old', + 'woman', + 'uc6', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'vintage', + 'human', + 'cane', + 'parent', + 'wife', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ]), + Emoji( + name: 'old woman: light skin tone', + char: '\u{1F475}\u{1F3FB}', + shortName: 'older_woman_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'light skin tone', + 'old', + 'woman', + 'uc8', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'vintage', + 'human', + 'cane', + 'parent', + 'wife', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'old woman: medium-light skin tone', + char: '\u{1F475}\u{1F3FC}', + shortName: 'older_woman_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'medium-light skin tone', + 'old', + 'woman', + 'uc8', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'vintage', + 'human', + 'cane', + 'parent', + 'wife', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'old woman: medium skin tone', + char: '\u{1F475}\u{1F3FD}', + shortName: 'older_woman_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'medium skin tone', + 'old', + 'woman', + 'uc8', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'vintage', + 'human', + 'cane', + 'parent', + 'wife', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'old woman: medium-dark skin tone', + char: '\u{1F475}\u{1F3FE}', + shortName: 'older_woman_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'medium-dark skin tone', + 'old', + 'woman', + 'uc8', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'vintage', + 'human', + 'cane', + 'parent', + 'wife', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'old woman: dark skin tone', + char: '\u{1F475}\u{1F3FF}', + shortName: 'older_woman_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'dark skin tone', + 'old', + 'woman', + 'uc8', + 'old people', + 'diversity', + 'lesbian', + 'women', + 'vintage', + 'human', + 'cane', + 'parent', + 'wife', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'older person', + char: '\u{1F9D3}', + shortName: 'older_adult', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'uc10', + 'old people', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'vintage', + 'human', + 'cane', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ]), + Emoji( + name: 'older person: light skin tone', + char: '\u{1F9D3}\u{1F3FB}', + shortName: 'older_adult_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'gender-neutral', + 'light skin tone', + 'old', + 'uc10', + 'old people', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'vintage', + 'human', + 'cane', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'older person: medium-light skin tone', + char: '\u{1F9D3}\u{1F3FC}', + shortName: 'older_adult_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'gender-neutral', + 'medium-light skin tone', + 'old', + 'uc10', + 'old people', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'vintage', + 'human', + 'cane', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'older person: medium skin tone', + char: '\u{1F9D3}\u{1F3FD}', + shortName: 'older_adult_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'gender-neutral', + 'medium skin tone', + 'old', + 'uc10', + 'old people', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'vintage', + 'human', + 'cane', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'older person: medium-dark skin tone', + char: '\u{1F9D3}\u{1F3FE}', + shortName: 'older_adult_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'gender-neutral', + 'medium-dark skin tone', + 'old', + 'uc10', + 'old people', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'vintage', + 'human', + 'cane', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'older person: dark skin tone', + char: '\u{1F9D3}\u{1F3FF}', + shortName: 'older_adult_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'dark skin tone', + 'gender-neutral', + 'old', + 'uc10', + 'old people', + 'diversity', + 'lesbian', + 'men', + 'feminist', + 'vintage', + 'human', + 'cane', + 'daddy', + 'parent', + 'handsome', + 'wife', + 'husband', + 'mom', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'feminism', + 'strong woman', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'maman', + 'mommy', + 'mama', + 'mother', + 'silver hair' + ], + modifiable: true), + Emoji( + name: 'old man', + char: '\u{1F474}', + shortName: 'older_man', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'man', + 'old', + 'uc6', + 'old people', + 'diversity', + 'men', + 'vintage', + 'human', + 'cane', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'grey hair', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'silver hair', + 'balding' + ]), + Emoji( + name: 'old man: light skin tone', + char: '\u{1F474}\u{1F3FB}', + shortName: 'older_man_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'light skin tone', + 'man', + 'old', + 'uc8', + 'old people', + 'diversity', + 'men', + 'vintage', + 'human', + 'cane', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'grey hair', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'silver hair', + 'balding' + ], + modifiable: true), + Emoji( + name: 'old man: medium-light skin tone', + char: '\u{1F474}\u{1F3FC}', + shortName: 'older_man_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'man', + 'medium-light skin tone', + 'old', + 'uc8', + 'old people', + 'diversity', + 'men', + 'vintage', + 'human', + 'cane', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'grey hair', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'silver hair', + 'balding' + ], + modifiable: true), + Emoji( + name: 'old man: medium skin tone', + char: '\u{1F474}\u{1F3FD}', + shortName: 'older_man_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'man', + 'medium skin tone', + 'old', + 'uc8', + 'old people', + 'diversity', + 'men', + 'vintage', + 'human', + 'cane', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'grey hair', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'silver hair', + 'balding' + ], + modifiable: true), + Emoji( + name: 'old man: medium-dark skin tone', + char: '\u{1F474}\u{1F3FE}', + shortName: 'older_man_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'man', + 'medium-dark skin tone', + 'old', + 'uc8', + 'old people', + 'diversity', + 'men', + 'vintage', + 'human', + 'cane', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'grey hair', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'silver hair', + 'balding' + ], + modifiable: true), + Emoji( + name: 'old man: dark skin tone', + char: '\u{1F474}\u{1F3FF}', + shortName: 'older_man_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.person, + keywords: [ + 'dark skin tone', + 'man', + 'old', + 'uc8', + 'old people', + 'diversity', + 'men', + 'vintage', + 'human', + 'cane', + 'daddy', + 'parent', + 'handsome', + 'husband', + 'grey hair', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'stud', + 'silver hair', + 'balding' + ], + modifiable: true), + Emoji( + name: 'person with skullcap', + char: '\u{1F472}', + shortName: 'man_with_chinese_cap', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'gua pi mao', + 'hat', + 'man', + 'uc6', + 'diversity', + 'men', + 'human', + 'chinese', + 'parent', + 'handsome', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'chinois', + 'asian', + 'chine', + 'parents', + 'adult', + 'stud' + ]), + Emoji( + name: 'person with skullcap: light skin tone', + char: '\u{1F472}\u{1F3FB}', + shortName: 'man_with_chinese_cap_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'gua pi mao', + 'hat', + 'light skin tone', + 'man', + 'uc8', + 'diversity', + 'men', + 'human', + 'chinese', + 'parent', + 'handsome', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'chinois', + 'asian', + 'chine', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'person with skullcap: medium-light skin tone', + char: '\u{1F472}\u{1F3FC}', + shortName: 'man_with_chinese_cap_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'gua pi mao', + 'hat', + 'man', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'men', + 'human', + 'chinese', + 'parent', + 'handsome', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'chinois', + 'asian', + 'chine', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'person with skullcap: medium skin tone', + char: '\u{1F472}\u{1F3FD}', + shortName: 'man_with_chinese_cap_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'gua pi mao', + 'hat', + 'man', + 'medium skin tone', + 'uc8', + 'diversity', + 'men', + 'human', + 'chinese', + 'parent', + 'handsome', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'chinois', + 'asian', + 'chine', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'person with skullcap: medium-dark skin tone', + char: '\u{1F472}\u{1F3FE}', + shortName: 'man_with_chinese_cap_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'gua pi mao', + 'hat', + 'man', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'men', + 'human', + 'chinese', + 'parent', + 'handsome', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'chinois', + 'asian', + 'chine', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'person with skullcap: dark skin tone', + char: '\u{1F472}\u{1F3FF}', + shortName: 'man_with_chinese_cap_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'gua pi mao', + 'hat', + 'man', + 'uc8', + 'diversity', + 'men', + 'human', + 'chinese', + 'parent', + 'handsome', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'chinois', + 'asian', + 'chine', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'person wearing turban', + char: '\u{1F473}', + shortName: 'person_wearing_turban', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'turban', + 'uc6', + 'diversity', + 'men', + 'human', + 'disney', + 'daddy', + 'islam', + 'parent', + 'wife', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'cartoon', + 'dad', + 'papa', + 'pere', + 'father', + 'muslim', + 'arab', + 'parents', + 'adult' + ]), + Emoji( + name: 'person wearing turban: light skin tone', + char: '\u{1F473}\u{1F3FB}', + shortName: 'person_wearing_turban_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'light skin tone', + 'turban', + 'uc8', + 'diversity', + 'men', + 'human', + 'disney', + 'daddy', + 'islam', + 'parent', + 'wife', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'cartoon', + 'dad', + 'papa', + 'pere', + 'father', + 'muslim', + 'arab', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'person wearing turban: medium-light skin tone', + char: '\u{1F473}\u{1F3FC}', + shortName: 'person_wearing_turban_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium-light skin tone', + 'turban', + 'uc8', + 'diversity', + 'men', + 'human', + 'disney', + 'daddy', + 'islam', + 'parent', + 'wife', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'cartoon', + 'dad', + 'papa', + 'pere', + 'father', + 'muslim', + 'arab', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'person wearing turban: medium skin tone', + char: '\u{1F473}\u{1F3FD}', + shortName: 'person_wearing_turban_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium skin tone', + 'turban', + 'uc8', + 'diversity', + 'men', + 'human', + 'disney', + 'daddy', + 'islam', + 'parent', + 'wife', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'cartoon', + 'dad', + 'papa', + 'pere', + 'father', + 'muslim', + 'arab', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'person wearing turban: medium-dark skin tone', + char: '\u{1F473}\u{1F3FE}', + shortName: 'person_wearing_turban_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium-dark skin tone', + 'turban', + 'uc8', + 'diversity', + 'men', + 'human', + 'disney', + 'daddy', + 'islam', + 'parent', + 'wife', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'cartoon', + 'dad', + 'papa', + 'pere', + 'father', + 'muslim', + 'arab', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'person wearing turban: dark skin tone', + char: '\u{1F473}\u{1F3FF}', + shortName: 'person_wearing_turban_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'turban', + 'uc8', + 'diversity', + 'men', + 'human', + 'disney', + 'daddy', + 'islam', + 'parent', + 'wife', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'cartoon', + 'dad', + 'papa', + 'pere', + 'father', + 'muslim', + 'arab', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'woman wearing turban', + char: '\u{1F473}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_wearing_turban', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'turban', + 'woman', + 'uc6', + 'diversity', + 'women', + 'human', + 'islam', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'gender', + 'people', + 'muslim', + 'arab', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman wearing turban: light skin tone', + char: '\u{1F473}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_wearing_turban_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'light skin tone', + 'turban', + 'woman', + 'uc8', + 'diversity', + 'women', + 'human', + 'islam', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'gender', + 'people', + 'muslim', + 'arab', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman wearing turban: medium-light skin tone', + char: '\u{1F473}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_wearing_turban_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium-light skin tone', + 'turban', + 'woman', + 'uc8', + 'diversity', + 'women', + 'human', + 'islam', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'gender', + 'people', + 'muslim', + 'arab', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman wearing turban: medium skin tone', + char: '\u{1F473}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_wearing_turban_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium skin tone', + 'turban', + 'woman', + 'uc8', + 'diversity', + 'women', + 'human', + 'islam', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'gender', + 'people', + 'muslim', + 'arab', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman wearing turban: medium-dark skin tone', + char: '\u{1F473}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_wearing_turban_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium-dark skin tone', + 'turban', + 'woman', + 'uc8', + 'diversity', + 'women', + 'human', + 'islam', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'gender', + 'people', + 'muslim', + 'arab', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman wearing turban: dark skin tone', + char: '\u{1F473}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_wearing_turban_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'turban', + 'woman', + 'uc8', + 'diversity', + 'women', + 'human', + 'islam', + 'parent', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'gender', + 'people', + 'muslim', + 'arab', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man wearing turban', + char: '\u{1F473}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_wearing_turban', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'man', + 'turban', + 'uc6', + 'diversity', + 'men', + 'mustache', + 'human', + 'daddy', + 'islam', + 'parent', + 'handsome', + 'husband', + 'beard', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'muslim', + 'arab', + 'parents', + 'adult', + 'stud' + ]), + Emoji( + name: 'man wearing turban: light skin tone', + char: '\u{1F473}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_wearing_turban_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'light skin tone', + 'man', + 'turban', + 'uc8', + 'diversity', + 'men', + 'mustache', + 'human', + 'daddy', + 'islam', + 'parent', + 'handsome', + 'husband', + 'beard', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'muslim', + 'arab', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man wearing turban: medium-light skin tone', + char: '\u{1F473}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_wearing_turban_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'man', + 'medium-light skin tone', + 'turban', + 'uc8', + 'diversity', + 'men', + 'mustache', + 'human', + 'daddy', + 'islam', + 'parent', + 'handsome', + 'husband', + 'beard', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'muslim', + 'arab', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man wearing turban: medium skin tone', + char: '\u{1F473}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_wearing_turban_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'man', + 'medium skin tone', + 'turban', + 'uc8', + 'diversity', + 'men', + 'mustache', + 'human', + 'daddy', + 'islam', + 'parent', + 'handsome', + 'husband', + 'beard', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'muslim', + 'arab', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man wearing turban: medium-dark skin tone', + char: '\u{1F473}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_wearing_turban_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'man', + 'medium-dark skin tone', + 'turban', + 'uc8', + 'diversity', + 'men', + 'mustache', + 'human', + 'daddy', + 'islam', + 'parent', + 'handsome', + 'husband', + 'beard', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'muslim', + 'arab', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man wearing turban: dark skin tone', + char: '\u{1F473}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_wearing_turban_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'man', + 'turban', + 'uc8', + 'diversity', + 'men', + 'mustache', + 'human', + 'daddy', + 'islam', + 'parent', + 'handsome', + 'husband', + 'beard', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'muslim', + 'arab', + 'parents', + 'adult', + 'stud' + ], + modifiable: true), + Emoji( + name: 'woman with headscarf', + char: '\u{1F9D5}', + shortName: 'woman_with_headscarf', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc10', + 'diversity', + 'women', + 'girls night', + 'human', + 'parent', + 'hijab', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman with headscarf: light skin tone', + char: '\u{1F9D5}\u{1F3FB}', + shortName: 'woman_with_headscarf_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'headscarf', + 'hijab', + 'light skin tone', + 'mantilla', + 'tichel', + 'uc10', + 'diversity', + 'women', + 'girls night', + 'human', + 'parent', + 'hijab', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman with headscarf: medium-light skin tone', + char: '\u{1F9D5}\u{1F3FC}', + shortName: 'woman_with_headscarf_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'headscarf', + 'hijab', + 'mantilla', + 'medium-light skin tone', + 'tichel', + 'uc10', + 'diversity', + 'women', + 'girls night', + 'human', + 'parent', + 'hijab', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman with headscarf: medium skin tone', + char: '\u{1F9D5}\u{1F3FD}', + shortName: 'woman_with_headscarf_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'headscarf', + 'hijab', + 'mantilla', + 'medium skin tone', + 'tichel', + 'uc10', + 'diversity', + 'women', + 'girls night', + 'human', + 'parent', + 'hijab', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman with headscarf: medium-dark skin tone', + char: '\u{1F9D5}\u{1F3FE}', + shortName: 'woman_with_headscarf_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'headscarf', + 'hijab', + 'mantilla', + 'medium-dark skin tone', + 'tichel', + 'uc10', + 'diversity', + 'women', + 'girls night', + 'human', + 'parent', + 'hijab', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman with headscarf: dark skin tone', + char: '\u{1F9D5}\u{1F3FF}', + shortName: 'woman_with_headscarf_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'headscarf', + 'hijab', + 'mantilla', + 'tichel', + 'uc10', + 'diversity', + 'women', + 'girls night', + 'human', + 'parent', + 'hijab', + 'wife', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'police officer', + char: '\u{1F46E}', + shortName: 'police_officer', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'officer', + 'police', + 'uc6', + 'diversity', + 'job', + 'police', + '911', + 'mustache', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ]), + Emoji( + name: 'police officer: light skin tone', + char: '\u{1F46E}\u{1F3FB}', + shortName: 'police_officer_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'light skin tone', + 'officer', + 'police', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'mustache', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'police officer: medium-light skin tone', + char: '\u{1F46E}\u{1F3FC}', + shortName: 'police_officer_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'medium-light skin tone', + 'officer', + 'police', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'mustache', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'police officer: medium skin tone', + char: '\u{1F46E}\u{1F3FD}', + shortName: 'police_officer_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'medium skin tone', + 'officer', + 'police', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'mustache', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'police officer: medium-dark skin tone', + char: '\u{1F46E}\u{1F3FE}', + shortName: 'police_officer_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'medium-dark skin tone', + 'officer', + 'police', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'mustache', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'police officer: dark skin tone', + char: '\u{1F46E}\u{1F3FF}', + shortName: 'police_officer_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'dark skin tone', + 'officer', + 'police', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'mustache', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'woman police officer', + char: '\u{1F46E}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_police_officer', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'officer', + 'police', + 'woman', + 'uc6', + 'diversity', + 'job', + 'police', + '911', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ]), + Emoji( + name: 'woman police officer: light skin tone', + char: '\u{1F46E}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_police_officer_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'light skin tone', + 'officer', + 'police', + 'woman', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'woman police officer: medium-light skin tone', + char: '\u{1F46E}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_police_officer_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'medium-light skin tone', + 'officer', + 'police', + 'woman', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'woman police officer: medium skin tone', + char: '\u{1F46E}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_police_officer_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'medium skin tone', + 'officer', + 'police', + 'woman', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'woman police officer: medium-dark skin tone', + char: '\u{1F46E}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_police_officer_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'medium-dark skin tone', + 'officer', + 'police', + 'woman', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'woman police officer: dark skin tone', + char: '\u{1F46E}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_police_officer_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'dark skin tone', + 'officer', + 'police', + 'woman', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man police officer', + char: '\u{1F46E}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_police_officer', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'man', + 'officer', + 'police', + 'uc6', + 'diversity', + 'job', + 'police', + '911', + 'mustache', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ]), + Emoji( + name: 'man police officer: light skin tone', + char: '\u{1F46E}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_police_officer_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'light skin tone', + 'man', + 'officer', + 'police', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'mustache', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man police officer: medium-light skin tone', + char: '\u{1F46E}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_police_officer_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'man', + 'medium-light skin tone', + 'officer', + 'police', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'mustache', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man police officer: medium skin tone', + char: '\u{1F46E}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_police_officer_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'man', + 'medium skin tone', + 'officer', + 'police', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'mustache', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man police officer: medium-dark skin tone', + char: '\u{1F46E}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_police_officer_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'man', + 'medium-dark skin tone', + 'officer', + 'police', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'mustache', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man police officer: dark skin tone', + char: '\u{1F46E}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_police_officer_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'cop', + 'dark skin tone', + 'man', + 'officer', + 'police', + 'uc8', + 'diversity', + 'job', + 'police', + '911', + 'mustache', + 'power', + 'pig', + 'help', + 'private', + 'mystery', + 'court', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'pork', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'construction worker', + char: '\u{1F477}', + shortName: 'construction_worker', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'hat', + 'worker', + 'uc6', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'construction worker: light skin tone', + char: '\u{1F477}\u{1F3FB}', + shortName: 'construction_worker_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'hat', + 'light skin tone', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'construction worker: medium-light skin tone', + char: '\u{1F477}\u{1F3FC}', + shortName: 'construction_worker_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'hat', + 'medium-light skin tone', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'construction worker: medium skin tone', + char: '\u{1F477}\u{1F3FD}', + shortName: 'construction_worker_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'hat', + 'medium skin tone', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'construction worker: medium-dark skin tone', + char: '\u{1F477}\u{1F3FE}', + shortName: 'construction_worker_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'hat', + 'medium-dark skin tone', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'construction worker: dark skin tone', + char: '\u{1F477}\u{1F3FF}', + shortName: 'construction_worker_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'dark skin tone', + 'hat', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman construction worker', + char: '\u{1F477}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_construction_worker', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'woman', + 'worker', + 'uc6', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'woman construction worker: light skin tone', + char: '\u{1F477}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_construction_worker_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'light skin tone', + 'woman', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman construction worker: medium-light skin tone', + char: '\u{1F477}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_construction_worker_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'medium-light skin tone', + 'woman', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman construction worker: medium skin tone', + char: '\u{1F477}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_construction_worker_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'medium skin tone', + 'woman', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman construction worker: medium-dark skin tone', + char: '\u{1F477}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_construction_worker_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'medium-dark skin tone', + 'woman', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman construction worker: dark skin tone', + char: '\u{1F477}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_construction_worker_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'dark skin tone', + 'woman', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man construction worker', + char: '\u{1F477}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_construction_worker', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'man', + 'worker', + 'uc6', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'man construction worker: light skin tone', + char: '\u{1F477}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_construction_worker_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'light skin tone', + 'man', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man construction worker: medium-light skin tone', + char: '\u{1F477}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_construction_worker_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'man', + 'medium-light skin tone', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man construction worker: medium skin tone', + char: '\u{1F477}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_construction_worker_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'man', + 'medium skin tone', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man construction worker: medium-dark skin tone', + char: '\u{1F477}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_construction_worker_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'man', + 'medium-dark skin tone', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man construction worker: dark skin tone', + char: '\u{1F477}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_construction_worker_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'construction', + 'dark skin tone', + 'man', + 'worker', + 'uc8', + 'diversity', + 'job', + 'build', + 'construction', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'guard', + char: '\u{1F482}', + shortName: 'guard', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'uc6', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ]), + Emoji( + name: 'guard: light skin tone', + char: '\u{1F482}\u{1F3FB}', + shortName: 'guard_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'light skin tone', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'guard: medium-light skin tone', + char: '\u{1F482}\u{1F3FC}', + shortName: 'guard_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'guard: medium skin tone', + char: '\u{1F482}\u{1F3FD}', + shortName: 'guard_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'medium skin tone', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'guard: medium-dark skin tone', + char: '\u{1F482}\u{1F3FE}', + shortName: 'guard_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'guard: dark skin tone', + char: '\u{1F482}\u{1F3FF}', + shortName: 'guard_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'guard', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'woman guard', + char: '\u{1F482}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_guard', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'woman', + 'uc6', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ]), + Emoji( + name: 'woman guard: light skin tone', + char: '\u{1F482}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_guard_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'light skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'woman guard: medium-light skin tone', + char: '\u{1F482}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_guard_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'medium-light skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'woman guard: medium skin tone', + char: '\u{1F482}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_guard_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'medium skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'woman guard: medium-dark skin tone', + char: '\u{1F482}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_guard_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'medium-dark skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'woman guard: dark skin tone', + char: '\u{1F482}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_guard_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'guard', + 'woman', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man guard', + char: '\u{1F482}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_guard', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'man', + 'uc6', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ]), + Emoji( + name: 'man guard: light skin tone', + char: '\u{1F482}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_guard_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'light skin tone', + 'man', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man guard: medium-light skin tone', + char: '\u{1F482}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_guard_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'man', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man guard: medium skin tone', + char: '\u{1F482}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_guard_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'man', + 'medium skin tone', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man guard: medium-dark skin tone', + char: '\u{1F482}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_guard_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'guard', + 'man', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man guard: dark skin tone', + char: '\u{1F482}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_guard_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'guard', + 'man', + 'uc8', + 'diversity', + 'job', + 'queen', + 'england', + 'private', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'detective', + char: '\u{1F575}', + shortName: 'detective', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'sleuth', + 'spy', + 'uc7', + 'diversity', + 'glasses', + 'halloween', + 'job', + 'google', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'eyeglasses', + 'eye glasses', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ]), + Emoji( + name: 'detective: light skin tone', + char: '\u{1F575}\u{1F3FB}', + shortName: 'detective_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'light skin tone', + 'sleuth', + 'spy', + 'uc8', + 'diversity', + 'glasses', + 'halloween', + 'job', + 'google', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'eyeglasses', + 'eye glasses', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'detective: medium-light skin tone', + char: '\u{1F575}\u{1F3FC}', + shortName: 'detective_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'medium-light skin tone', + 'sleuth', + 'spy', + 'uc8', + 'diversity', + 'glasses', + 'halloween', + 'job', + 'google', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'eyeglasses', + 'eye glasses', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'detective: medium skin tone', + char: '\u{1F575}\u{1F3FD}', + shortName: 'detective_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'medium skin tone', + 'sleuth', + 'spy', + 'uc8', + 'diversity', + 'glasses', + 'halloween', + 'job', + 'google', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'eyeglasses', + 'eye glasses', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'detective: medium-dark skin tone', + char: '\u{1F575}\u{1F3FE}', + shortName: 'detective_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'medium-dark skin tone', + 'sleuth', + 'spy', + 'uc8', + 'diversity', + 'glasses', + 'halloween', + 'job', + 'google', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'eyeglasses', + 'eye glasses', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'detective: dark skin tone', + char: '\u{1F575}\u{1F3FF}', + shortName: 'detective_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'detective', + 'sleuth', + 'spy', + 'uc8', + 'diversity', + 'glasses', + 'halloween', + 'job', + 'google', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'eyeglasses', + 'eye glasses', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'woman detective', + char: '\u{1F575}\u{FE0F}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_detective', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'sleuth', + 'spy', + 'woman', + 'uc7', + 'diversity', + 'halloween', + 'job', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ]), + Emoji( + name: 'woman detective: light skin tone', + char: '\u{1F575}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_detective_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'light skin tone', + 'sleuth', + 'spy', + 'woman', + 'uc8', + 'diversity', + 'halloween', + 'job', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'woman detective: medium-light skin tone', + char: '\u{1F575}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_detective_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'medium-light skin tone', + 'sleuth', + 'spy', + 'woman', + 'uc8', + 'diversity', + 'halloween', + 'job', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'woman detective: medium skin tone', + char: '\u{1F575}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_detective_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'medium skin tone', + 'sleuth', + 'spy', + 'woman', + 'uc8', + 'diversity', + 'halloween', + 'job', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'woman detective: medium-dark skin tone', + char: '\u{1F575}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_detective_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'medium-dark skin tone', + 'sleuth', + 'spy', + 'woman', + 'uc8', + 'diversity', + 'halloween', + 'job', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'woman detective: dark skin tone', + char: '\u{1F575}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_detective_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'detective', + 'sleuth', + 'spy', + 'woman', + 'uc8', + 'diversity', + 'halloween', + 'job', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'man detective', + char: '\u{1F575}\u{FE0F}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_detective', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'man', + 'sleuth', + 'spy', + 'uc7', + 'diversity', + 'halloween', + 'job', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ]), + Emoji( + name: 'man detective: light skin tone', + char: '\u{1F575}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_detective_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'light skin tone', + 'man', + 'sleuth', + 'spy', + 'uc8', + 'diversity', + 'halloween', + 'job', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'man detective: medium-light skin tone', + char: '\u{1F575}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_detective_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'man', + 'medium-light skin tone', + 'sleuth', + 'spy', + 'uc8', + 'diversity', + 'halloween', + 'job', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'man detective: medium skin tone', + char: '\u{1F575}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_detective_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'man', + 'medium skin tone', + 'sleuth', + 'spy', + 'uc8', + 'diversity', + 'halloween', + 'job', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'man detective: medium-dark skin tone', + char: '\u{1F575}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_detective_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'detective', + 'man', + 'medium-dark skin tone', + 'sleuth', + 'spy', + 'uc8', + 'diversity', + 'halloween', + 'job', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'man detective: dark skin tone', + char: '\u{1F575}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_detective_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'detective', + 'man', + 'sleuth', + 'spy', + 'uc8', + 'diversity', + 'halloween', + 'job', + 'search', + 'detective', + 'super hero', + 'private', + 'mystery', + 'clever', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'profession', + 'boss', + 'career', + 'look', + 'find', + 'looking', + 'see', + 'superhero', + 'superman', + 'batman', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'witty' + ], + modifiable: true), + Emoji( + name: 'health worker', + char: '\u{1F9D1}\u{200D}\u{2695}\u{FE0F}', + shortName: 'health_worker', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'health worker: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{2695}\u{FE0F}', + shortName: 'health_worker_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'health worker: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{2695}\u{FE0F}', + shortName: 'health_worker_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'health worker: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{2695}\u{FE0F}', + shortName: 'health_worker_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'health worker: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{2695}\u{FE0F}', + shortName: 'health_worker_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'health worker: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{2695}\u{FE0F}', + shortName: 'health_worker_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman health worker', + char: '\u{1F469}\u{200D}\u{2695}\u{FE0F}', + shortName: 'woman_health_worker', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'doctor', + 'healthcare', + 'nurse', + 'therapist', + 'woman', + 'uc6', + 'diversity', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'woman health worker: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{2695}\u{FE0F}', + shortName: 'woman_health_worker_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'doctor', + 'healthcare', + 'light skin tone', + 'nurse', + 'therapist', + 'woman', + 'uc8', + 'diversity', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman health worker: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{2695}\u{FE0F}', + shortName: 'woman_health_worker_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'doctor', + 'healthcare', + 'medium-light skin tone', + 'nurse', + 'therapist', + 'woman', + 'uc8', + 'diversity', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman health worker: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{2695}\u{FE0F}', + shortName: 'woman_health_worker_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'doctor', + 'healthcare', + 'medium skin tone', + 'nurse', + 'therapist', + 'woman', + 'uc8', + 'diversity', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman health worker: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{2695}\u{FE0F}', + shortName: 'woman_health_worker_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'doctor', + 'healthcare', + 'medium-dark skin tone', + 'nurse', + 'therapist', + 'woman', + 'uc8', + 'diversity', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman health worker: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{2695}\u{FE0F}', + shortName: 'woman_health_worker_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'doctor', + 'healthcare', + 'nurse', + 'therapist', + 'woman', + 'uc8', + 'diversity', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man health worker', + char: '\u{1F468}\u{200D}\u{2695}\u{FE0F}', + shortName: 'man_health_worker', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'doctor', + 'healthcare', + 'man', + 'nurse', + 'therapist', + 'uc6', + 'diversity', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'man health worker: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{2695}\u{FE0F}', + shortName: 'man_health_worker_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'doctor', + 'healthcare', + 'light skin tone', + 'man', + 'nurse', + 'therapist', + 'uc8', + 'diversity', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man health worker: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{2695}\u{FE0F}', + shortName: 'man_health_worker_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'doctor', + 'healthcare', + 'man', + 'medium-light skin tone', + 'nurse', + 'therapist', + 'uc8', + 'diversity', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man health worker: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{2695}\u{FE0F}', + shortName: 'man_health_worker_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'doctor', + 'healthcare', + 'man', + 'medium skin tone', + 'nurse', + 'therapist', + 'uc8', + 'diversity', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man health worker: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{2695}\u{FE0F}', + shortName: 'man_health_worker_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'doctor', + 'healthcare', + 'man', + 'medium-dark skin tone', + 'nurse', + 'therapist', + 'uc8', + 'diversity', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man health worker: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{2695}\u{FE0F}', + shortName: 'man_health_worker_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'doctor', + 'healthcare', + 'man', + 'nurse', + 'therapist', + 'uc8', + 'diversity', + 'health', + 'sick', + 'job', + '911', + 'nerd', + 'nurse', + 'help', + 'disguise', + 'medical', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'medicine', + 'doctor', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'farmer', + char: '\u{1F9D1}\u{200D}\u{1F33E}', + shortName: 'farmer', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'farm', + 'disguise', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'farmer: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F33E}', + shortName: 'farmer_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'farm', + 'disguise', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'farmer: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F33E}', + shortName: 'farmer_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'farm', + 'disguise', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'farmer: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F33E}', + shortName: 'farmer_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'farm', + 'disguise', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'farmer: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F33E}', + shortName: 'farmer_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'farm', + 'disguise', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'farmer: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F33E}', + shortName: 'farmer_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'farm', + 'disguise', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman farmer', + char: '\u{1F469}\u{200D}\u{1F33E}', + shortName: 'woman_farmer', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'farmer', + 'gardener', + 'rancher', + 'woman', + 'uc6', + 'diversity', + 'job', + 'farm', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'woman farmer: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F33E}', + shortName: 'woman_farmer_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'farmer', + 'gardener', + 'light skin tone', + 'rancher', + 'woman', + 'uc8', + 'diversity', + 'job', + 'farm', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman farmer: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F33E}', + shortName: 'woman_farmer_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'farmer', + 'gardener', + 'medium-light skin tone', + 'rancher', + 'woman', + 'uc8', + 'diversity', + 'job', + 'farm', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman farmer: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F33E}', + shortName: 'woman_farmer_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'farmer', + 'gardener', + 'medium skin tone', + 'rancher', + 'woman', + 'uc8', + 'diversity', + 'job', + 'farm', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman farmer: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F33E}', + shortName: 'woman_farmer_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'farmer', + 'gardener', + 'medium-dark skin tone', + 'rancher', + 'woman', + 'uc8', + 'diversity', + 'job', + 'farm', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman farmer: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F33E}', + shortName: 'woman_farmer_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'farmer', + 'gardener', + 'rancher', + 'woman', + 'uc8', + 'diversity', + 'job', + 'farm', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man farmer', + char: '\u{1F468}\u{200D}\u{1F33E}', + shortName: 'man_farmer', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'farmer', + 'gardener', + 'man', + 'rancher', + 'uc6', + 'diversity', + 'job', + 'farm', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'man farmer: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F33E}', + shortName: 'man_farmer_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'farmer', + 'gardener', + 'light skin tone', + 'man', + 'rancher', + 'uc8', + 'diversity', + 'job', + 'farm', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man farmer: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F33E}', + shortName: 'man_farmer_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'farmer', + 'gardener', + 'man', + 'medium-light skin tone', + 'rancher', + 'uc8', + 'diversity', + 'job', + 'farm', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man farmer: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F33E}', + shortName: 'man_farmer_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'farmer', + 'gardener', + 'man', + 'medium skin tone', + 'rancher', + 'uc8', + 'diversity', + 'job', + 'farm', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man farmer: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F33E}', + shortName: 'man_farmer_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'farmer', + 'gardener', + 'man', + 'medium-dark skin tone', + 'rancher', + 'uc8', + 'diversity', + 'job', + 'farm', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man farmer: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F33E}', + shortName: 'man_farmer_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'farmer', + 'gardener', + 'man', + 'rancher', + 'uc8', + 'diversity', + 'job', + 'farm', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'cook', + char: '\u{1F9D1}\u{200D}\u{1F373}', + shortName: 'cook', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'chef', + 'dinner', + 'disguise', + 'profession', + 'boss', + 'career', + 'cuisinière', + 'cuisinier', + 'lunch' + ]), + Emoji( + name: 'cook: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F373}', + shortName: 'cook_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'chef', + 'dinner', + 'disguise', + 'profession', + 'boss', + 'career', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'cook: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F373}', + shortName: 'cook_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'chef', + 'dinner', + 'disguise', + 'profession', + 'boss', + 'career', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'cook: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F373}', + shortName: 'cook_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'chef', + 'dinner', + 'disguise', + 'profession', + 'boss', + 'career', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'cook: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F373}', + shortName: 'cook_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'chef', + 'dinner', + 'disguise', + 'profession', + 'boss', + 'career', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'cook: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F373}', + shortName: 'cook_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'chef', + 'dinner', + 'disguise', + 'profession', + 'boss', + 'career', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'woman cook', + char: '\u{1F469}\u{200D}\u{1F373}', + shortName: 'woman_cook', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'chef', + 'cook', + 'woman', + 'uc6', + 'diversity', + 'job', + 'bake', + 'chef', + 'dinner', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'baking', + 'cuisinière', + 'cuisinier', + 'lunch' + ]), + Emoji( + name: 'woman cook: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F373}', + shortName: 'woman_cook_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'chef', + 'cook', + 'light skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + 'bake', + 'chef', + 'dinner', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'baking', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'woman cook: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F373}', + shortName: 'woman_cook_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'chef', + 'cook', + 'medium-light skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + 'bake', + 'chef', + 'dinner', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'baking', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'woman cook: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F373}', + shortName: 'woman_cook_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'chef', + 'cook', + 'medium skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + 'bake', + 'chef', + 'dinner', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'baking', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'woman cook: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F373}', + shortName: 'woman_cook_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'chef', + 'cook', + 'medium-dark skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + 'bake', + 'chef', + 'dinner', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'baking', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'woman cook: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F373}', + shortName: 'woman_cook_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'chef', + 'cook', + 'dark skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + 'bake', + 'chef', + 'dinner', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'baking', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'man cook', + char: '\u{1F468}\u{200D}\u{1F373}', + shortName: 'man_cook', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'chef', + 'cook', + 'man', + 'uc6', + 'diversity', + 'job', + 'bake', + 'chef', + 'dinner', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'baking', + 'cuisinière', + 'cuisinier', + 'lunch' + ]), + Emoji( + name: 'man cook: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F373}', + shortName: 'man_cook_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'chef', + 'cook', + 'light skin tone', + 'man', + 'uc8', + 'diversity', + 'job', + 'bake', + 'chef', + 'dinner', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'baking', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'man cook: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F373}', + shortName: 'man_cook_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'chef', + 'cook', + 'man', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'job', + 'bake', + 'chef', + 'dinner', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'baking', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'man cook: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F373}', + shortName: 'man_cook_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'chef', + 'cook', + 'man', + 'medium skin tone', + 'uc8', + 'diversity', + 'job', + 'bake', + 'chef', + 'dinner', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'baking', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'man cook: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F373}', + shortName: 'man_cook_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'chef', + 'cook', + 'man', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'job', + 'bake', + 'chef', + 'dinner', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'baking', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'man cook: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F373}', + shortName: 'man_cook_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'chef', + 'cook', + 'dark skin tone', + 'man', + 'uc8', + 'diversity', + 'job', + 'bake', + 'chef', + 'dinner', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'baking', + 'cuisinière', + 'cuisinier', + 'lunch' + ], + modifiable: true), + Emoji( + name: 'student', + char: '\u{1F9D1}\u{200D}\u{1F393}', + shortName: 'student', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'nerd', + 'graduate', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'student: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F393}', + shortName: 'student_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'nerd', + 'graduate', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'student: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F393}', + shortName: 'student_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'nerd', + 'graduate', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'student: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F393}', + shortName: 'student_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'nerd', + 'graduate', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'student: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F393}', + shortName: 'student_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'nerd', + 'graduate', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'student: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F393}', + shortName: 'student_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'nerd', + 'graduate', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman student', + char: '\u{1F469}\u{200D}\u{1F393}', + shortName: 'woman_student', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'graduate', + 'student', + 'woman', + 'uc6', + 'diversity', + 'classroom', + 'nerd', + 'graduate', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'woman student: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F393}', + shortName: 'woman_student_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'graduate', + 'light skin tone', + 'student', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'nerd', + 'graduate', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman student: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F393}', + shortName: 'woman_student_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'graduate', + 'medium-light skin tone', + 'student', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'nerd', + 'graduate', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman student: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F393}', + shortName: 'woman_student_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'graduate', + 'medium skin tone', + 'student', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'nerd', + 'graduate', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman student: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F393}', + shortName: 'woman_student_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'graduate', + 'medium-dark skin tone', + 'student', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'nerd', + 'graduate', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman student: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F393}', + shortName: 'woman_student_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'graduate', + 'student', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'nerd', + 'graduate', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man student', + char: '\u{1F468}\u{200D}\u{1F393}', + shortName: 'man_student', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'graduate', + 'man', + 'student', + 'uc6', + 'diversity', + 'classroom', + 'nerd', + 'graduate', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'man student: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F393}', + shortName: 'man_student_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'graduate', + 'light skin tone', + 'man', + 'student', + 'uc8', + 'diversity', + 'classroom', + 'nerd', + 'graduate', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man student: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F393}', + shortName: 'man_student_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'graduate', + 'man', + 'medium-light skin tone', + 'student', + 'uc8', + 'diversity', + 'classroom', + 'nerd', + 'graduate', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man student: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F393}', + shortName: 'man_student_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'graduate', + 'man', + 'medium skin tone', + 'student', + 'uc8', + 'diversity', + 'classroom', + 'nerd', + 'graduate', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man student: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F393}', + shortName: 'man_student_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'graduate', + 'man', + 'medium-dark skin tone', + 'student', + 'uc8', + 'diversity', + 'classroom', + 'nerd', + 'graduate', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man student: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F393}', + shortName: 'man_student_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'graduate', + 'man', + 'student', + 'uc8', + 'diversity', + 'classroom', + 'nerd', + 'graduate', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'singer', + char: '\u{1F9D1}\u{200D}\u{1F3A4}', + shortName: 'singer', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'singer: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F3A4}', + shortName: 'singer_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'singer: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F3A4}', + shortName: 'singer_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'singer: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F3A4}', + shortName: 'singer_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'singer: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F3A4}', + shortName: 'singer_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'singer: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F3A4}', + shortName: 'singer_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman singer', + char: '\u{1F469}\u{200D}\u{1F3A4}', + shortName: 'woman_singer', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'actor', + 'entertainer', + 'rock', + 'singer', + 'star', + 'woman', + 'uc6', + 'instruments', + 'diversity', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'woman singer: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F3A4}', + shortName: 'woman_singer_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'actor', + 'entertainer', + 'light skin tone', + 'rock', + 'singer', + 'star', + 'woman', + 'uc8', + 'instruments', + 'diversity', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman singer: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F3A4}', + shortName: 'woman_singer_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'actor', + 'entertainer', + 'medium-light skin tone', + 'rock', + 'singer', + 'star', + 'woman', + 'uc8', + 'instruments', + 'diversity', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman singer: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F3A4}', + shortName: 'woman_singer_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'actor', + 'entertainer', + 'medium skin tone', + 'rock', + 'singer', + 'star', + 'woman', + 'uc8', + 'instruments', + 'diversity', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman singer: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F3A4}', + shortName: 'woman_singer_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'actor', + 'entertainer', + 'medium-dark skin tone', + 'rock', + 'singer', + 'star', + 'woman', + 'uc8', + 'instruments', + 'diversity', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman singer: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F3A4}', + shortName: 'woman_singer_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'actor', + 'dark skin tone', + 'entertainer', + 'rock', + 'singer', + 'star', + 'woman', + 'uc8', + 'instruments', + 'diversity', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man singer', + char: '\u{1F468}\u{200D}\u{1F3A4}', + shortName: 'man_singer', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'actor', + 'entertainer', + 'man', + 'rock', + 'singer', + 'star', + 'uc6', + 'instruments', + 'diversity', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'man singer: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F3A4}', + shortName: 'man_singer_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'actor', + 'entertainer', + 'light skin tone', + 'man', + 'rock', + 'singer', + 'star', + 'uc8', + 'instruments', + 'diversity', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man singer: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F3A4}', + shortName: 'man_singer_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'actor', + 'entertainer', + 'man', + 'medium-light skin tone', + 'rock', + 'singer', + 'star', + 'uc8', + 'instruments', + 'diversity', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man singer: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F3A4}', + shortName: 'man_singer_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'actor', + 'entertainer', + 'man', + 'medium skin tone', + 'rock', + 'singer', + 'star', + 'uc8', + 'instruments', + 'diversity', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man singer: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F3A4}', + shortName: 'man_singer_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'actor', + 'entertainer', + 'man', + 'medium-dark skin tone', + 'rock', + 'singer', + 'star', + 'uc8', + 'instruments', + 'diversity', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man singer: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F3A4}', + shortName: 'man_singer_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'actor', + 'dark skin tone', + 'entertainer', + 'man', + 'rock', + 'singer', + 'star', + 'uc8', + 'instruments', + 'diversity', + 'job', + 'rock and roll', + 'fame', + 'artist', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'teacher', + char: '\u{1F9D1}\u{200D}\u{1F3EB}', + shortName: 'teacher', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'job', + 'nerd', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'teacher: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F3EB}', + shortName: 'teacher_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'job', + 'nerd', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'teacher: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F3EB}', + shortName: 'teacher_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'job', + 'nerd', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'teacher: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F3EB}', + shortName: 'teacher_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'job', + 'nerd', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'teacher: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F3EB}', + shortName: 'teacher_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'job', + 'nerd', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'teacher: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F3EB}', + shortName: 'teacher_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'job', + 'nerd', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman teacher', + char: '\u{1F469}\u{200D}\u{1F3EB}', + shortName: 'woman_teacher', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'instructor', + 'professor', + 'teacher', + 'woman', + 'uc6', + 'diversity', + 'classroom', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'woman teacher: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F3EB}', + shortName: 'woman_teacher_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'instructor', + 'light skin tone', + 'professor', + 'teacher', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman teacher: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F3EB}', + shortName: 'woman_teacher_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'instructor', + 'medium-light skin tone', + 'professor', + 'teacher', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman teacher: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F3EB}', + shortName: 'woman_teacher_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'instructor', + 'medium skin tone', + 'professor', + 'teacher', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman teacher: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F3EB}', + shortName: 'woman_teacher_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'instructor', + 'medium-dark skin tone', + 'professor', + 'teacher', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman teacher: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F3EB}', + shortName: 'woman_teacher_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'instructor', + 'professor', + 'teacher', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man teacher', + char: '\u{1F468}\u{200D}\u{1F3EB}', + shortName: 'man_teacher', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'instructor', + 'man', + 'professor', + 'teacher', + 'uc6', + 'diversity', + 'classroom', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'man teacher: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F3EB}', + shortName: 'man_teacher_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'instructor', + 'light skin tone', + 'man', + 'professor', + 'teacher', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man teacher: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F3EB}', + shortName: 'man_teacher_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'instructor', + 'man', + 'medium-light skin tone', + 'professor', + 'teacher', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man teacher: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F3EB}', + shortName: 'man_teacher_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'instructor', + 'man', + 'medium skin tone', + 'professor', + 'teacher', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man teacher: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F3EB}', + shortName: 'man_teacher_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'instructor', + 'man', + 'medium-dark skin tone', + 'professor', + 'teacher', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man teacher: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F3EB}', + shortName: 'man_teacher_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'instructor', + 'man', + 'professor', + 'teacher', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'factory worker', + char: '\u{1F9D1}\u{200D}\u{1F3ED}', + shortName: 'factory_worker', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'mask', + 'build', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'factory worker: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F3ED}', + shortName: 'factory_worker_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'mask', + 'build', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'factory worker: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F3ED}', + shortName: 'factory_worker_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'mask', + 'build', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'factory worker: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F3ED}', + shortName: 'factory_worker_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'mask', + 'build', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'factory worker: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F3ED}', + shortName: 'factory_worker_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'mask', + 'build', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'factory worker: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F3ED}', + shortName: 'factory_worker_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'mask', + 'build', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman factory worker', + char: '\u{1F469}\u{200D}\u{1F3ED}', + shortName: 'woman_factory_worker', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'assembly', + 'factory', + 'industrial', + 'woman', + 'worker', + 'uc6', + 'diversity', + 'job', + 'mask', + 'build', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'woman factory worker: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F3ED}', + shortName: 'woman_factory_worker_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'assembly', + 'factory', + 'industrial', + 'light skin tone', + 'woman', + 'worker', + 'uc8', + 'diversity', + 'job', + 'mask', + 'build', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman factory worker: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F3ED}', + shortName: 'woman_factory_worker_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'assembly', + 'factory', + 'industrial', + 'medium-light skin tone', + 'woman', + 'worker', + 'uc8', + 'diversity', + 'job', + 'mask', + 'build', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman factory worker: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F3ED}', + shortName: 'woman_factory_worker_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'assembly', + 'factory', + 'industrial', + 'medium skin tone', + 'woman', + 'worker', + 'uc8', + 'diversity', + 'job', + 'mask', + 'build', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman factory worker: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F3ED}', + shortName: 'woman_factory_worker_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'assembly', + 'factory', + 'industrial', + 'medium-dark skin tone', + 'woman', + 'worker', + 'uc8', + 'diversity', + 'job', + 'mask', + 'build', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman factory worker: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F3ED}', + shortName: 'woman_factory_worker_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'assembly', + 'dark skin tone', + 'factory', + 'industrial', + 'woman', + 'worker', + 'uc8', + 'diversity', + 'job', + 'mask', + 'build', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man factory worker', + char: '\u{1F468}\u{200D}\u{1F3ED}', + shortName: 'man_factory_worker', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'assembly', + 'factory', + 'industrial', + 'man', + 'worker', + 'uc6', + 'diversity', + 'job', + 'mask', + 'build', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'man factory worker: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F3ED}', + shortName: 'man_factory_worker_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'assembly', + 'factory', + 'industrial', + 'light skin tone', + 'man', + 'worker', + 'uc8', + 'diversity', + 'job', + 'mask', + 'build', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man factory worker: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F3ED}', + shortName: 'man_factory_worker_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'assembly', + 'factory', + 'industrial', + 'man', + 'medium-light skin tone', + 'worker', + 'uc8', + 'diversity', + 'job', + 'mask', + 'build', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man factory worker: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F3ED}', + shortName: 'man_factory_worker_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'assembly', + 'factory', + 'industrial', + 'man', + 'medium skin tone', + 'worker', + 'uc8', + 'diversity', + 'job', + 'mask', + 'build', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man factory worker: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F3ED}', + shortName: 'man_factory_worker_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'assembly', + 'factory', + 'industrial', + 'man', + 'medium-dark skin tone', + 'worker', + 'uc8', + 'diversity', + 'job', + 'mask', + 'build', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man factory worker: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F3ED}', + shortName: 'man_factory_worker_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'assembly', + 'dark skin tone', + 'factory', + 'industrial', + 'man', + 'worker', + 'uc8', + 'diversity', + 'job', + 'mask', + 'build', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'technologist', + char: '\u{1F9D1}\u{200D}\u{1F4BB}', + shortName: 'technologist', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'job', + 'business', + 'nerd', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ]), + Emoji( + name: 'technologist: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F4BB}', + shortName: 'technologist_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'job', + 'business', + 'nerd', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'technologist: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F4BB}', + shortName: 'technologist_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'job', + 'business', + 'nerd', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'technologist: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F4BB}', + shortName: 'technologist_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'job', + 'business', + 'nerd', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'technologist: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F4BB}', + shortName: 'technologist_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'job', + 'business', + 'nerd', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'technologist: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F4BB}', + shortName: 'technologist_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'classroom', + 'job', + 'business', + 'nerd', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'woman technologist', + char: '\u{1F469}\u{200D}\u{1F4BB}', + shortName: 'woman_technologist', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'coder', + 'developer', + 'inventor', + 'software', + 'technologist', + 'woman', + 'uc6', + 'diversity', + 'classroom', + 'job', + 'business', + 'nerd', + 'code', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'coding', + 'office' + ]), + Emoji( + name: 'woman technologist: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F4BB}', + shortName: 'woman_technologist_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'coder', + 'developer', + 'inventor', + 'light skin tone', + 'software', + 'technologist', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'business', + 'nerd', + 'code', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'coding', + 'office' + ], + modifiable: true), + Emoji( + name: 'woman technologist: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F4BB}', + shortName: 'woman_technologist_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'coder', + 'developer', + 'inventor', + 'medium-light skin tone', + 'software', + 'technologist', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'business', + 'nerd', + 'code', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'coding', + 'office' + ], + modifiable: true), + Emoji( + name: 'woman technologist: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F4BB}', + shortName: 'woman_technologist_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'coder', + 'developer', + 'inventor', + 'medium skin tone', + 'software', + 'technologist', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'business', + 'nerd', + 'code', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'coding', + 'office' + ], + modifiable: true), + Emoji( + name: 'woman technologist: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F4BB}', + shortName: 'woman_technologist_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'coder', + 'developer', + 'inventor', + 'medium-dark skin tone', + 'software', + 'technologist', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'business', + 'nerd', + 'code', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'coding', + 'office' + ], + modifiable: true), + Emoji( + name: 'woman technologist: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F4BB}', + shortName: 'woman_technologist_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'coder', + 'dark skin tone', + 'developer', + 'inventor', + 'software', + 'technologist', + 'woman', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'business', + 'nerd', + 'code', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'coding', + 'office' + ], + modifiable: true), + Emoji( + name: 'man technologist', + char: '\u{1F468}\u{200D}\u{1F4BB}', + shortName: 'man_technologist', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'coder', + 'developer', + 'inventor', + 'man', + 'software', + 'technologist', + 'uc6', + 'diversity', + 'classroom', + 'job', + 'business', + 'nerd', + 'code', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'coding', + 'office' + ]), + Emoji( + name: 'man technologist: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F4BB}', + shortName: 'man_technologist_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'coder', + 'developer', + 'inventor', + 'light skin tone', + 'man', + 'software', + 'technologist', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'business', + 'nerd', + 'code', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'coding', + 'office' + ], + modifiable: true), + Emoji( + name: 'man technologist: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F4BB}', + shortName: 'man_technologist_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'coder', + 'developer', + 'inventor', + 'man', + 'medium-light skin tone', + 'software', + 'technologist', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'business', + 'nerd', + 'code', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'coding', + 'office' + ], + modifiable: true), + Emoji( + name: 'man technologist: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F4BB}', + shortName: 'man_technologist_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'coder', + 'developer', + 'inventor', + 'man', + 'medium skin tone', + 'software', + 'technologist', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'business', + 'nerd', + 'code', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'coding', + 'office' + ], + modifiable: true), + Emoji( + name: 'man technologist: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F4BB}', + shortName: 'man_technologist_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'coder', + 'developer', + 'inventor', + 'man', + 'medium-dark skin tone', + 'software', + 'technologist', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'business', + 'nerd', + 'code', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'coding', + 'office' + ], + modifiable: true), + Emoji( + name: 'man technologist: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F4BB}', + shortName: 'man_technologist_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'coder', + 'dark skin tone', + 'developer', + 'inventor', + 'man', + 'software', + 'technologist', + 'uc8', + 'diversity', + 'classroom', + 'job', + 'business', + 'nerd', + 'code', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'coding', + 'office' + ], + modifiable: true), + Emoji( + name: 'office worker', + char: '\u{1F9D1}\u{200D}\u{1F4BC}', + shortName: 'office_worker', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'men', + 'job', + 'business', + 'nerd', + 'work', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ]), + Emoji( + name: 'office worker: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F4BC}', + shortName: 'office_worker_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'men', + 'job', + 'business', + 'nerd', + 'work', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'office worker: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F4BC}', + shortName: 'office_worker_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'men', + 'job', + 'business', + 'nerd', + 'work', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'office worker: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F4BC}', + shortName: 'office_worker_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'men', + 'job', + 'business', + 'nerd', + 'work', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'office worker: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F4BC}', + shortName: 'office_worker_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'men', + 'job', + 'business', + 'nerd', + 'work', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'office worker: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F4BC}', + shortName: 'office_worker_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'men', + 'job', + 'business', + 'nerd', + 'work', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'woman office worker', + char: '\u{1F469}\u{200D}\u{1F4BC}', + shortName: 'woman_office_worker', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'architect', + 'business', + 'manager', + 'office', + 'white-collar', + 'woman', + 'uc6', + 'diversity', + 'women', + 'job', + 'business', + 'nerd', + 'costume', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ]), + Emoji( + name: 'woman office worker: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F4BC}', + shortName: 'woman_office_worker_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'architect', + 'business', + 'light skin tone', + 'manager', + 'office', + 'white-collar', + 'woman', + 'uc8', + 'diversity', + 'women', + 'job', + 'business', + 'nerd', + 'costume', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'woman office worker: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F4BC}', + shortName: 'woman_office_worker_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'architect', + 'business', + 'manager', + 'medium-light skin tone', + 'office', + 'white-collar', + 'woman', + 'uc8', + 'diversity', + 'women', + 'job', + 'business', + 'nerd', + 'costume', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'woman office worker: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F4BC}', + shortName: 'woman_office_worker_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'architect', + 'business', + 'manager', + 'medium skin tone', + 'office', + 'white-collar', + 'woman', + 'uc8', + 'diversity', + 'women', + 'job', + 'business', + 'nerd', + 'costume', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'woman office worker: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F4BC}', + shortName: 'woman_office_worker_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'architect', + 'business', + 'manager', + 'medium-dark skin tone', + 'office', + 'white-collar', + 'woman', + 'uc8', + 'diversity', + 'women', + 'job', + 'business', + 'nerd', + 'costume', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'woman office worker: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F4BC}', + shortName: 'woman_office_worker_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'architect', + 'business', + 'dark skin tone', + 'manager', + 'office', + 'white-collar', + 'woman', + 'uc8', + 'diversity', + 'women', + 'job', + 'business', + 'nerd', + 'costume', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'man office worker', + char: '\u{1F468}\u{200D}\u{1F4BC}', + shortName: 'man_office_worker', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'architect', + 'business', + 'man', + 'manager', + 'office', + 'white-collar', + 'uc6', + 'diversity', + 'men', + 'job', + 'business', + 'nerd', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ]), + Emoji( + name: 'man office worker: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F4BC}', + shortName: 'man_office_worker_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'architect', + 'business', + 'light skin tone', + 'man', + 'manager', + 'office', + 'white-collar', + 'uc8', + 'diversity', + 'men', + 'job', + 'business', + 'nerd', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'man office worker: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F4BC}', + shortName: 'man_office_worker_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'architect', + 'business', + 'man', + 'manager', + 'medium-light skin tone', + 'office', + 'white-collar', + 'uc8', + 'diversity', + 'men', + 'job', + 'business', + 'nerd', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'man office worker: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F4BC}', + shortName: 'man_office_worker_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'architect', + 'business', + 'man', + 'manager', + 'medium skin tone', + 'office', + 'white-collar', + 'uc8', + 'diversity', + 'men', + 'job', + 'business', + 'nerd', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'man office worker: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F4BC}', + shortName: 'man_office_worker_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'architect', + 'business', + 'man', + 'manager', + 'medium-dark skin tone', + 'office', + 'white-collar', + 'uc8', + 'diversity', + 'men', + 'job', + 'business', + 'nerd', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'man office worker: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F4BC}', + shortName: 'man_office_worker_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'architect', + 'business', + 'dark skin tone', + 'man', + 'manager', + 'office', + 'white-collar', + 'uc8', + 'diversity', + 'men', + 'job', + 'business', + 'nerd', + 'work', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious', + 'office' + ], + modifiable: true), + Emoji( + name: 'mechanic', + char: '\u{1F9D1}\u{200D}\u{1F527}', + shortName: 'mechanic', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: ['uc12', 'job', 'profession', 'boss', 'career']), + Emoji( + name: 'mechanic: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F527}', + shortName: 'mechanic_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: ['uc12', 'job', 'profession', 'boss', 'career'], + modifiable: true), + Emoji( + name: 'mechanic: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F527}', + shortName: 'mechanic_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: ['uc12', 'job', 'profession', 'boss', 'career'], + modifiable: true), + Emoji( + name: 'mechanic: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F527}', + shortName: 'mechanic_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: ['uc12', 'job', 'profession', 'boss', 'career'], + modifiable: true), + Emoji( + name: 'mechanic: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F527}', + shortName: 'mechanic_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: ['uc12', 'job', 'profession', 'boss', 'career'], + modifiable: true), + Emoji( + name: 'mechanic: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F527}', + shortName: 'mechanic_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: ['uc12', 'job', 'profession', 'boss', 'career'], + modifiable: true), + Emoji( + name: 'woman mechanic', + char: '\u{1F469}\u{200D}\u{1F527}', + shortName: 'woman_mechanic', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'electrician', + 'mechanic', + 'plumber', + 'tradesperson', + 'woman', + 'uc6', + 'diversity', + 'job', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'woman mechanic: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F527}', + shortName: 'woman_mechanic_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'electrician', + 'light skin tone', + 'mechanic', + 'plumber', + 'tradesperson', + 'woman', + 'uc8', + 'diversity', + 'job', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman mechanic: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F527}', + shortName: 'woman_mechanic_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'electrician', + 'mechanic', + 'medium-light skin tone', + 'plumber', + 'tradesperson', + 'woman', + 'uc8', + 'diversity', + 'job', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman mechanic: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F527}', + shortName: 'woman_mechanic_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'electrician', + 'mechanic', + 'medium skin tone', + 'plumber', + 'tradesperson', + 'woman', + 'uc8', + 'diversity', + 'job', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman mechanic: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F527}', + shortName: 'woman_mechanic_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'electrician', + 'mechanic', + 'medium-dark skin tone', + 'plumber', + 'tradesperson', + 'woman', + 'uc8', + 'diversity', + 'job', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman mechanic: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F527}', + shortName: 'woman_mechanic_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'electrician', + 'mechanic', + 'plumber', + 'tradesperson', + 'woman', + 'uc8', + 'diversity', + 'job', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man mechanic', + char: '\u{1F468}\u{200D}\u{1F527}', + shortName: 'man_mechanic', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'electrician', + 'man', + 'mechanic', + 'plumber', + 'tradesperson', + 'uc6', + 'diversity', + 'job', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'man mechanic: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F527}', + shortName: 'man_mechanic_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'electrician', + 'light skin tone', + 'man', + 'mechanic', + 'plumber', + 'tradesperson', + 'uc8', + 'diversity', + 'job', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man mechanic: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F527}', + shortName: 'man_mechanic_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'electrician', + 'man', + 'mechanic', + 'medium-light skin tone', + 'plumber', + 'tradesperson', + 'uc8', + 'diversity', + 'job', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man mechanic: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F527}', + shortName: 'man_mechanic_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'electrician', + 'man', + 'mechanic', + 'medium skin tone', + 'plumber', + 'tradesperson', + 'uc8', + 'diversity', + 'job', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man mechanic: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F527}', + shortName: 'man_mechanic_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'electrician', + 'man', + 'mechanic', + 'medium-dark skin tone', + 'plumber', + 'tradesperson', + 'uc8', + 'diversity', + 'job', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man mechanic: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F527}', + shortName: 'man_mechanic_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'electrician', + 'man', + 'mechanic', + 'plumber', + 'tradesperson', + 'uc8', + 'diversity', + 'job', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'scientist', + char: '\u{1F9D1}\u{200D}\u{1F52C}', + shortName: 'scientist', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'science', + 'job', + 'nerd', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'scientist: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F52C}', + shortName: 'scientist_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'science', + 'job', + 'nerd', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'scientist: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F52C}', + shortName: 'scientist_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'science', + 'job', + 'nerd', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'scientist: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F52C}', + shortName: 'scientist_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'science', + 'job', + 'nerd', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'scientist: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F52C}', + shortName: 'scientist_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'science', + 'job', + 'nerd', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'scientist: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F52C}', + shortName: 'scientist_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'science', + 'job', + 'nerd', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman scientist', + char: '\u{1F469}\u{200D}\u{1F52C}', + shortName: 'woman_scientist', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'biologist', + 'chemist', + 'engineer', + 'mathematician', + 'physicist', + 'scientist', + 'woman', + 'uc6', + 'diversity', + 'science', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'woman scientist: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F52C}', + shortName: 'woman_scientist_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'biologist', + 'chemist', + 'engineer', + 'light skin tone', + 'mathematician', + 'physicist', + 'scientist', + 'woman', + 'uc8', + 'diversity', + 'science', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman scientist: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F52C}', + shortName: 'woman_scientist_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'biologist', + 'chemist', + 'engineer', + 'mathematician', + 'medium-light skin tone', + 'physicist', + 'scientist', + 'woman', + 'uc8', + 'diversity', + 'science', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman scientist: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F52C}', + shortName: 'woman_scientist_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'biologist', + 'chemist', + 'engineer', + 'mathematician', + 'medium skin tone', + 'physicist', + 'scientist', + 'woman', + 'uc8', + 'diversity', + 'science', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman scientist: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F52C}', + shortName: 'woman_scientist_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'biologist', + 'chemist', + 'engineer', + 'mathematician', + 'medium-dark skin tone', + 'physicist', + 'scientist', + 'woman', + 'uc8', + 'diversity', + 'science', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman scientist: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F52C}', + shortName: 'woman_scientist_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'biologist', + 'chemist', + 'dark skin tone', + 'engineer', + 'mathematician', + 'physicist', + 'scientist', + 'woman', + 'uc8', + 'diversity', + 'science', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man scientist', + char: '\u{1F468}\u{200D}\u{1F52C}', + shortName: 'man_scientist', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'biologist', + 'chemist', + 'engineer', + 'man', + 'mathematician', + 'physicist', + 'scientist', + 'uc6', + 'diversity', + 'science', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'man scientist: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F52C}', + shortName: 'man_scientist_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'biologist', + 'chemist', + 'engineer', + 'light skin tone', + 'man', + 'mathematician', + 'physicist', + 'scientist', + 'uc8', + 'diversity', + 'science', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man scientist: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F52C}', + shortName: 'man_scientist_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'biologist', + 'chemist', + 'engineer', + 'man', + 'mathematician', + 'medium-light skin tone', + 'physicist', + 'scientist', + 'uc8', + 'diversity', + 'science', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man scientist: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F52C}', + shortName: 'man_scientist_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'biologist', + 'chemist', + 'engineer', + 'man', + 'mathematician', + 'medium skin tone', + 'physicist', + 'scientist', + 'uc8', + 'diversity', + 'science', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man scientist: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F52C}', + shortName: 'man_scientist_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'biologist', + 'chemist', + 'engineer', + 'man', + 'mathematician', + 'medium-dark skin tone', + 'physicist', + 'scientist', + 'uc8', + 'diversity', + 'science', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man scientist: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F52C}', + shortName: 'man_scientist_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'biologist', + 'chemist', + 'dark skin tone', + 'engineer', + 'man', + 'mathematician', + 'physicist', + 'scientist', + 'uc8', + 'diversity', + 'science', + 'job', + 'nerd', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'lab', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'artist', + char: '\u{1F9D1}\u{200D}\u{1F3A8}', + shortName: 'artist', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'painting', + 'artist', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ]), + Emoji( + name: 'artist: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F3A8}', + shortName: 'artist_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'painting', + 'artist', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'artist: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F3A8}', + shortName: 'artist_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'painting', + 'artist', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'artist: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F3A8}', + shortName: 'artist_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'painting', + 'artist', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'artist: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F3A8}', + shortName: 'artist_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'painting', + 'artist', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'artist: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F3A8}', + shortName: 'artist_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'painting', + 'artist', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'woman artist', + char: '\u{1F469}\u{200D}\u{1F3A8}', + shortName: 'woman_artist', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'artist', + 'palette', + 'woman', + 'uc6', + 'diversity', + 'job', + 'painting', + 'artist', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ]), + Emoji( + name: 'woman artist: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F3A8}', + shortName: 'woman_artist_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'artist', + 'light skin tone', + 'palette', + 'woman', + 'uc8', + 'diversity', + 'job', + 'painting', + 'artist', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'woman artist: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F3A8}', + shortName: 'woman_artist_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'artist', + 'medium-light skin tone', + 'palette', + 'woman', + 'uc8', + 'diversity', + 'job', + 'painting', + 'artist', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'woman artist: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F3A8}', + shortName: 'woman_artist_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'artist', + 'medium skin tone', + 'palette', + 'woman', + 'uc8', + 'diversity', + 'job', + 'painting', + 'artist', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'woman artist: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F3A8}', + shortName: 'woman_artist_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'artist', + 'medium-dark skin tone', + 'palette', + 'woman', + 'uc8', + 'diversity', + 'job', + 'painting', + 'artist', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'woman artist: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F3A8}', + shortName: 'woman_artist_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'artist', + 'dark skin tone', + 'palette', + 'woman', + 'uc8', + 'diversity', + 'job', + 'painting', + 'artist', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'man artist', + char: '\u{1F468}\u{200D}\u{1F3A8}', + shortName: 'man_artist', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'artist', + 'man', + 'palette', + 'uc6', + 'diversity', + 'job', + 'painting', + 'artist', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ]), + Emoji( + name: 'man artist: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F3A8}', + shortName: 'man_artist_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'artist', + 'light skin tone', + 'man', + 'palette', + 'uc8', + 'diversity', + 'job', + 'painting', + 'artist', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'man artist: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F3A8}', + shortName: 'man_artist_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'artist', + 'man', + 'medium-light skin tone', + 'palette', + 'uc8', + 'diversity', + 'job', + 'painting', + 'artist', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'man artist: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F3A8}', + shortName: 'man_artist_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'artist', + 'man', + 'medium skin tone', + 'palette', + 'uc8', + 'diversity', + 'job', + 'painting', + 'artist', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'man artist: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F3A8}', + shortName: 'man_artist_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'artist', + 'man', + 'medium-dark skin tone', + 'palette', + 'uc8', + 'diversity', + 'job', + 'painting', + 'artist', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'man artist: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F3A8}', + shortName: 'man_artist_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'artist', + 'dark skin tone', + 'man', + 'palette', + 'uc8', + 'diversity', + 'job', + 'painting', + 'artist', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'painter', + 'arts' + ], + modifiable: true), + Emoji( + name: 'firefighter', + char: '\u{1F9D1}\u{200D}\u{1F692}', + shortName: 'firefighter', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + '911', + 'help', + 'handsome', + 'fires', + 'helmet', + 'disguise', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'stud' + ]), + Emoji( + name: 'firefighter: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F692}', + shortName: 'firefighter_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + '911', + 'help', + 'handsome', + 'fires', + 'helmet', + 'disguise', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'stud' + ], + modifiable: true), + Emoji( + name: 'firefighter: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F692}', + shortName: 'firefighter_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + '911', + 'help', + 'handsome', + 'fires', + 'helmet', + 'disguise', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'stud' + ], + modifiable: true), + Emoji( + name: 'firefighter: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F692}', + shortName: 'firefighter_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + '911', + 'help', + 'handsome', + 'fires', + 'helmet', + 'disguise', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'stud' + ], + modifiable: true), + Emoji( + name: 'firefighter: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F692}', + shortName: 'firefighter_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + '911', + 'help', + 'handsome', + 'fires', + 'helmet', + 'disguise', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'stud' + ], + modifiable: true), + Emoji( + name: 'firefighter: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F692}', + shortName: 'firefighter_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + '911', + 'help', + 'handsome', + 'fires', + 'helmet', + 'disguise', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'stud' + ], + modifiable: true), + Emoji( + name: 'woman firefighter', + char: '\u{1F469}\u{200D}\u{1F692}', + shortName: 'woman_firefighter', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'firefighter', + 'firetruck', + 'woman', + 'uc6', + 'diversity', + 'job', + '911', + 'help', + 'fires', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury' + ]), + Emoji( + name: 'woman firefighter: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F692}', + shortName: 'woman_firefighter_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'firefighter', + 'firetruck', + 'light skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + '911', + 'help', + 'fires', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury' + ], + modifiable: true), + Emoji( + name: 'woman firefighter: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F692}', + shortName: 'woman_firefighter_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'firefighter', + 'firetruck', + 'medium-light skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + '911', + 'help', + 'fires', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury' + ], + modifiable: true), + Emoji( + name: 'woman firefighter: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F692}', + shortName: 'woman_firefighter_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'firefighter', + 'firetruck', + 'medium skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + '911', + 'help', + 'fires', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury' + ], + modifiable: true), + Emoji( + name: 'woman firefighter: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F692}', + shortName: 'woman_firefighter_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'firefighter', + 'firetruck', + 'medium-dark skin tone', + 'woman', + 'uc8', + 'diversity', + 'job', + '911', + 'help', + 'fires', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury' + ], + modifiable: true), + Emoji( + name: 'woman firefighter: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F692}', + shortName: 'woman_firefighter_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'firefighter', + 'firetruck', + 'woman', + 'uc8', + 'diversity', + 'job', + '911', + 'help', + 'fires', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury' + ], + modifiable: true), + Emoji( + name: 'man firefighter', + char: '\u{1F468}\u{200D}\u{1F692}', + shortName: 'man_firefighter', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'firefighter', + 'firetruck', + 'man', + 'uc6', + 'diversity', + 'job', + '911', + 'help', + 'handsome', + 'fires', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'stud' + ]), + Emoji( + name: 'man firefighter: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F692}', + shortName: 'man_firefighter_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'firefighter', + 'firetruck', + 'light skin tone', + 'man', + 'uc8', + 'diversity', + 'job', + '911', + 'help', + 'handsome', + 'fires', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man firefighter: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F692}', + shortName: 'man_firefighter_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'firefighter', + 'firetruck', + 'man', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'job', + '911', + 'help', + 'handsome', + 'fires', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man firefighter: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F692}', + shortName: 'man_firefighter_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'firefighter', + 'firetruck', + 'man', + 'medium skin tone', + 'uc8', + 'diversity', + 'job', + '911', + 'help', + 'handsome', + 'fires', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man firefighter: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F692}', + shortName: 'man_firefighter_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'firefighter', + 'firetruck', + 'man', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'job', + '911', + 'help', + 'handsome', + 'fires', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man firefighter: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F692}', + shortName: 'man_firefighter_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'firefighter', + 'firetruck', + 'man', + 'uc8', + 'diversity', + 'job', + '911', + 'help', + 'handsome', + 'fires', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury', + 'stud' + ], + modifiable: true), + Emoji( + name: 'pilot', + char: '\u{1F9D1}\u{200D}\u{2708}\u{FE0F}', + shortName: 'pilot', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'fly', + 'job', + 'airplane', + 'handsome', + 'disguise', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'stud' + ]), + Emoji( + name: 'pilot: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{2708}\u{FE0F}', + shortName: 'pilot_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'fly', + 'job', + 'airplane', + 'handsome', + 'disguise', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'stud' + ], + modifiable: true), + Emoji( + name: 'pilot: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{2708}\u{FE0F}', + shortName: 'pilot_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'fly', + 'job', + 'airplane', + 'handsome', + 'disguise', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'stud' + ], + modifiable: true), + Emoji( + name: 'pilot: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{2708}\u{FE0F}', + shortName: 'pilot_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'fly', + 'job', + 'airplane', + 'handsome', + 'disguise', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'stud' + ], + modifiable: true), + Emoji( + name: 'pilot: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{2708}\u{FE0F}', + shortName: 'pilot_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'fly', + 'job', + 'airplane', + 'handsome', + 'disguise', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'stud' + ], + modifiable: true), + Emoji( + name: 'pilot: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{2708}\u{FE0F}', + shortName: 'pilot_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'fly', + 'job', + 'airplane', + 'handsome', + 'disguise', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'stud' + ], + modifiable: true), + Emoji( + name: 'woman pilot', + char: '\u{1F469}\u{200D}\u{2708}\u{FE0F}', + shortName: 'woman_pilot', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'pilot', + 'plane', + 'woman', + 'uc6', + 'diversity', + 'fly', + 'job', + 'airplane', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport' + ]), + Emoji( + name: 'woman pilot: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{2708}\u{FE0F}', + shortName: 'woman_pilot_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'light skin tone', + 'pilot', + 'plane', + 'woman', + 'uc8', + 'diversity', + 'fly', + 'job', + 'airplane', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport' + ], + modifiable: true), + Emoji( + name: 'woman pilot: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{2708}\u{FE0F}', + shortName: 'woman_pilot_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium-light skin tone', + 'pilot', + 'plane', + 'woman', + 'uc8', + 'diversity', + 'fly', + 'job', + 'airplane', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport' + ], + modifiable: true), + Emoji( + name: 'woman pilot: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{2708}\u{FE0F}', + shortName: 'woman_pilot_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium skin tone', + 'pilot', + 'plane', + 'woman', + 'uc8', + 'diversity', + 'fly', + 'job', + 'airplane', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport' + ], + modifiable: true), + Emoji( + name: 'woman pilot: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{2708}\u{FE0F}', + shortName: 'woman_pilot_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium-dark skin tone', + 'pilot', + 'plane', + 'woman', + 'uc8', + 'diversity', + 'fly', + 'job', + 'airplane', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport' + ], + modifiable: true), + Emoji( + name: 'woman pilot: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{2708}\u{FE0F}', + shortName: 'woman_pilot_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'pilot', + 'plane', + 'woman', + 'uc8', + 'diversity', + 'fly', + 'job', + 'airplane', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport' + ], + modifiable: true), + Emoji( + name: 'man pilot', + char: '\u{1F468}\u{200D}\u{2708}\u{FE0F}', + shortName: 'man_pilot', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'man', + 'pilot', + 'plane', + 'uc6', + 'diversity', + 'fly', + 'job', + 'airplane', + 'handsome', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'stud' + ]), + Emoji( + name: 'man pilot: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{2708}\u{FE0F}', + shortName: 'man_pilot_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'light skin tone', + 'man', + 'pilot', + 'plane', + 'uc8', + 'diversity', + 'fly', + 'job', + 'airplane', + 'handsome', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man pilot: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{2708}\u{FE0F}', + shortName: 'man_pilot_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'man', + 'medium-light skin tone', + 'pilot', + 'plane', + 'uc8', + 'diversity', + 'fly', + 'job', + 'airplane', + 'handsome', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man pilot: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{2708}\u{FE0F}', + shortName: 'man_pilot_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'man', + 'medium skin tone', + 'pilot', + 'plane', + 'uc8', + 'diversity', + 'fly', + 'job', + 'airplane', + 'handsome', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man pilot: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{2708}\u{FE0F}', + shortName: 'man_pilot_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'man', + 'medium-dark skin tone', + 'pilot', + 'plane', + 'uc8', + 'diversity', + 'fly', + 'job', + 'airplane', + 'handsome', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man pilot: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{2708}\u{FE0F}', + shortName: 'man_pilot_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'man', + 'pilot', + 'plane', + 'uc8', + 'diversity', + 'fly', + 'job', + 'airplane', + 'handsome', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'flight', + 'flying', + 'flights', + 'avion', + 'profession', + 'boss', + 'career', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'stud' + ], + modifiable: true), + Emoji( + name: 'astronaut', + char: '\u{1F9D1}\u{200D}\u{1F680}', + shortName: 'astronaut', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'space', + 'job', + 'helmet', + 'disguise', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'astronaut: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F680}', + shortName: 'astronaut_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'space', + 'job', + 'helmet', + 'disguise', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'astronaut: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F680}', + shortName: 'astronaut_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'space', + 'job', + 'helmet', + 'disguise', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'astronaut: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F680}', + shortName: 'astronaut_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'space', + 'job', + 'helmet', + 'disguise', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'astronaut: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F680}', + shortName: 'astronaut_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'space', + 'job', + 'helmet', + 'disguise', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'astronaut: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F680}', + shortName: 'astronaut_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'space', + 'job', + 'helmet', + 'disguise', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman astronaut', + char: '\u{1F469}\u{200D}\u{1F680}', + shortName: 'woman_astronaut', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'astronaut', + 'rocket', + 'woman', + 'uc6', + 'diversity', + 'space', + 'job', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'woman astronaut: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F680}', + shortName: 'woman_astronaut_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'astronaut', + 'light skin tone', + 'rocket', + 'woman', + 'uc8', + 'diversity', + 'space', + 'job', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman astronaut: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F680}', + shortName: 'woman_astronaut_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'astronaut', + 'medium-light skin tone', + 'rocket', + 'woman', + 'uc8', + 'diversity', + 'space', + 'job', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman astronaut: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F680}', + shortName: 'woman_astronaut_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'astronaut', + 'medium skin tone', + 'rocket', + 'woman', + 'uc8', + 'diversity', + 'space', + 'job', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman astronaut: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F680}', + shortName: 'woman_astronaut_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'astronaut', + 'medium-dark skin tone', + 'rocket', + 'woman', + 'uc8', + 'diversity', + 'space', + 'job', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'woman astronaut: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F680}', + shortName: 'woman_astronaut_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'astronaut', + 'dark skin tone', + 'rocket', + 'woman', + 'uc8', + 'diversity', + 'space', + 'job', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man astronaut', + char: '\u{1F468}\u{200D}\u{1F680}', + shortName: 'man_astronaut', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'astronaut', + 'man', + 'rocket', + 'uc6', + 'diversity', + 'space', + 'job', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ]), + Emoji( + name: 'man astronaut: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F680}', + shortName: 'man_astronaut_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'astronaut', + 'light skin tone', + 'man', + 'rocket', + 'uc8', + 'diversity', + 'space', + 'job', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man astronaut: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F680}', + shortName: 'man_astronaut_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'astronaut', + 'man', + 'medium-light skin tone', + 'rocket', + 'uc8', + 'diversity', + 'space', + 'job', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man astronaut: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F680}', + shortName: 'man_astronaut_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'astronaut', + 'man', + 'medium skin tone', + 'rocket', + 'uc8', + 'diversity', + 'space', + 'job', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man astronaut: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F680}', + shortName: 'man_astronaut_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'astronaut', + 'man', + 'medium-dark skin tone', + 'rocket', + 'uc8', + 'diversity', + 'space', + 'job', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'man astronaut: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F680}', + shortName: 'man_astronaut_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'astronaut', + 'dark skin tone', + 'man', + 'rocket', + 'uc8', + 'diversity', + 'space', + 'job', + 'helmet', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'profession', + 'boss', + 'career' + ], + modifiable: true), + Emoji( + name: 'judge', + char: '\u{1F9D1}\u{200D}\u{2696}\u{FE0F}', + shortName: 'judge', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'nerd', + 'court', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'judge: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{2696}\u{FE0F}', + shortName: 'judge_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'nerd', + 'court', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'judge: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{2696}\u{FE0F}', + shortName: 'judge_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'nerd', + 'court', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'judge: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{2696}\u{FE0F}', + shortName: 'judge_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'nerd', + 'court', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'judge: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{2696}\u{FE0F}', + shortName: 'judge_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'nerd', + 'court', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'judge: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{2696}\u{FE0F}', + shortName: 'judge_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc12', + 'job', + 'nerd', + 'court', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman judge', + char: '\u{1F469}\u{200D}\u{2696}\u{FE0F}', + shortName: 'woman_judge', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'judge', + 'scales', + 'woman', + 'uc6', + 'diversity', + 'job', + 'nerd', + 'court', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'woman judge: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{2696}\u{FE0F}', + shortName: 'woman_judge_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'judge', + 'light skin tone', + 'scales', + 'woman', + 'uc8', + 'diversity', + 'job', + 'nerd', + 'court', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman judge: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{2696}\u{FE0F}', + shortName: 'woman_judge_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'judge', + 'medium-light skin tone', + 'scales', + 'woman', + 'uc8', + 'diversity', + 'job', + 'nerd', + 'court', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman judge: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{2696}\u{FE0F}', + shortName: 'woman_judge_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'judge', + 'medium skin tone', + 'scales', + 'woman', + 'uc8', + 'diversity', + 'job', + 'nerd', + 'court', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman judge: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{2696}\u{FE0F}', + shortName: 'woman_judge_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'judge', + 'medium-dark skin tone', + 'scales', + 'woman', + 'uc8', + 'diversity', + 'job', + 'nerd', + 'court', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'woman judge: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{2696}\u{FE0F}', + shortName: 'woman_judge_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'judge', + 'scales', + 'woman', + 'uc8', + 'diversity', + 'job', + 'nerd', + 'court', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man judge', + char: '\u{1F468}\u{200D}\u{2696}\u{FE0F}', + shortName: 'man_judge', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'justice', + 'man', + 'scales', + 'uc6', + 'diversity', + 'job', + 'nerd', + 'court', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ]), + Emoji( + name: 'man judge: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{2696}\u{FE0F}', + shortName: 'man_judge_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'justice', + 'light skin tone', + 'man', + 'scales', + 'uc8', + 'diversity', + 'job', + 'nerd', + 'court', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man judge: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{2696}\u{FE0F}', + shortName: 'man_judge_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'justice', + 'man', + 'medium-light skin tone', + 'scales', + 'uc8', + 'diversity', + 'job', + 'nerd', + 'court', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man judge: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{2696}\u{FE0F}', + shortName: 'man_judge_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'justice', + 'man', + 'medium skin tone', + 'scales', + 'uc8', + 'diversity', + 'job', + 'nerd', + 'court', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man judge: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{2696}\u{FE0F}', + shortName: 'man_judge_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'justice', + 'man', + 'medium-dark skin tone', + 'scales', + 'uc8', + 'diversity', + 'job', + 'nerd', + 'court', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'man judge: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{2696}\u{FE0F}', + shortName: 'man_judge_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'justice', + 'man', + 'scales', + 'uc8', + 'diversity', + 'job', + 'nerd', + 'court', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'profession', + 'boss', + 'career', + 'smart', + 'geek', + 'serious' + ], + modifiable: true), + Emoji( + name: 'person with veil', + char: '\u{1F470}', + shortName: 'person_with_veil', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'person', + 'veil', + 'wedding', + 'uc6', + 'diversity', + 'wedding', + 'women', + 'beautiful', + 'las vegas', + 'wife', + 'dress', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'vegas' + ]), + Emoji( + name: 'person with veil: light skin tone', + char: '\u{1F470}\u{1F3FB}', + shortName: 'person_with_veil_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'person', + 'light skin tone', + 'veil', + 'wedding', + 'uc8', + 'diversity', + 'wedding', + 'women', + 'beautiful', + 'las vegas', + 'wife', + 'dress', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'person with veil: medium-light skin tone', + char: '\u{1F470}\u{1F3FC}', + shortName: 'person_with_veil_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'person', + 'medium-light skin tone', + 'veil', + 'wedding', + 'uc8', + 'diversity', + 'wedding', + 'women', + 'beautiful', + 'las vegas', + 'wife', + 'dress', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'person with veil: medium skin tone', + char: '\u{1F470}\u{1F3FD}', + shortName: 'person_with_veil_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'person', + 'medium skin tone', + 'veil', + 'wedding', + 'uc8', + 'diversity', + 'wedding', + 'women', + 'beautiful', + 'las vegas', + 'wife', + 'dress', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'person with veil: medium-dark skin tone', + char: '\u{1F470}\u{1F3FE}', + shortName: 'person_with_veil_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'person', + 'medium-dark skin tone', + 'veil', + 'wedding', + 'uc8', + 'diversity', + 'wedding', + 'women', + 'beautiful', + 'las vegas', + 'wife', + 'dress', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'person with veil: dark skin tone', + char: '\u{1F470}\u{1F3FF}', + shortName: 'person_with_veil_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'person', + 'dark skin tone', + 'veil', + 'wedding', + 'uc8', + 'diversity', + 'wedding', + 'women', + 'beautiful', + 'las vegas', + 'wife', + 'dress', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'woman with veil', + char: '\u{1F470}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_with_veil', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'beautiful', + 'wife', + 'dress', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ]), + Emoji( + name: 'woman with veil: light skin tone', + char: '\u{1F470}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_with_veil_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'beautiful', + 'wife', + 'dress', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ], + modifiable: true), + Emoji( + name: 'woman with veil: medium-light skin tone', + char: '\u{1F470}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_with_veil_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'beautiful', + 'wife', + 'dress', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ], + modifiable: true), + Emoji( + name: 'woman with veil: medium skin tone', + char: '\u{1F470}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_with_veil_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'beautiful', + 'wife', + 'dress', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ], + modifiable: true), + Emoji( + name: 'woman with veil: medium-dark skin tone', + char: '\u{1F470}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_with_veil_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'beautiful', + 'wife', + 'dress', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ], + modifiable: true), + Emoji( + name: 'woman with veil: dark skin tone', + char: '\u{1F470}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_with_veil_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'beautiful', + 'wife', + 'dress', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ], + modifiable: true), + Emoji( + name: 'man with veil', + char: '\u{1F470}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_with_veil', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'wife', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry' + ]), + Emoji( + name: 'man with veil: light skin tone', + char: '\u{1F470}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_with_veil_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'wife', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry' + ], + modifiable: true), + Emoji( + name: 'man with veil: medium-light skin tone', + char: '\u{1F470}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_with_veil_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'wife', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry' + ], + modifiable: true), + Emoji( + name: 'man with veil: medium skin tone', + char: '\u{1F470}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_with_veil_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'wife', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry' + ], + modifiable: true), + Emoji( + name: 'man with veil: medium-dark skin tone', + char: '\u{1F470}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_with_veil_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'wife', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry' + ], + modifiable: true), + Emoji( + name: 'man with veil: dark skin tone', + char: '\u{1F470}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_with_veil_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'wife', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry' + ], + modifiable: true), + Emoji( + name: 'person in tuxedo', + char: '\u{1F935}', + shortName: 'person_in_tuxedo', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'person', + 'tuxedo', + 'uc9', + 'diversity', + 'wedding', + 'men', + 'boys night', + 'donald trump', + 'fame', + 'vampire', + 'las vegas', + 'rich', + 'jacket', + 'handsome', + 'costume', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'trump', + 'famous', + 'celebrity', + 'dracula', + 'vegas', + 'grand', + 'expensive', + 'fancy', + 'veste', + 'stud' + ]), + Emoji( + name: 'person in tuxedo: light skin tone', + char: '\u{1F935}\u{1F3FB}', + shortName: 'person_in_tuxedo_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'light skin tone', + 'person', + 'tuxedo', + 'uc9', + 'diversity', + 'wedding', + 'men', + 'boys night', + 'donald trump', + 'fame', + 'vampire', + 'las vegas', + 'rich', + 'jacket', + 'handsome', + 'costume', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'trump', + 'famous', + 'celebrity', + 'dracula', + 'vegas', + 'grand', + 'expensive', + 'fancy', + 'veste', + 'stud' + ], + modifiable: true), + Emoji( + name: 'person in tuxedo: medium-light skin tone', + char: '\u{1F935}\u{1F3FC}', + shortName: 'person_in_tuxedo_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'person', + 'medium-light skin tone', + 'tuxedo', + 'uc9', + 'diversity', + 'wedding', + 'men', + 'boys night', + 'donald trump', + 'fame', + 'vampire', + 'las vegas', + 'rich', + 'jacket', + 'handsome', + 'costume', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'trump', + 'famous', + 'celebrity', + 'dracula', + 'vegas', + 'grand', + 'expensive', + 'fancy', + 'veste', + 'stud' + ], + modifiable: true), + Emoji( + name: 'person in tuxedo: medium skin tone', + char: '\u{1F935}\u{1F3FD}', + shortName: 'person_in_tuxedo_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'person', + 'medium skin tone', + 'tuxedo', + 'uc9', + 'diversity', + 'wedding', + 'men', + 'boys night', + 'donald trump', + 'fame', + 'vampire', + 'las vegas', + 'rich', + 'jacket', + 'handsome', + 'costume', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'trump', + 'famous', + 'celebrity', + 'dracula', + 'vegas', + 'grand', + 'expensive', + 'fancy', + 'veste', + 'stud' + ], + modifiable: true), + Emoji( + name: 'person in tuxedo: medium-dark skin tone', + char: '\u{1F935}\u{1F3FE}', + shortName: 'person_in_tuxedo_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'person', + 'medium-dark skin tone', + 'tuxedo', + 'uc9', + 'diversity', + 'wedding', + 'men', + 'boys night', + 'donald trump', + 'fame', + 'vampire', + 'las vegas', + 'rich', + 'jacket', + 'handsome', + 'costume', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'trump', + 'famous', + 'celebrity', + 'dracula', + 'vegas', + 'grand', + 'expensive', + 'fancy', + 'veste', + 'stud' + ], + modifiable: true), + Emoji( + name: 'person in tuxedo: dark skin tone', + char: '\u{1F935}\u{1F3FF}', + shortName: 'person_in_tuxedo_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'person', + 'tuxedo', + 'uc9', + 'diversity', + 'wedding', + 'men', + 'boys night', + 'donald trump', + 'fame', + 'vampire', + 'las vegas', + 'rich', + 'jacket', + 'handsome', + 'costume', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'trump', + 'famous', + 'celebrity', + 'dracula', + 'vegas', + 'grand', + 'expensive', + 'fancy', + 'veste', + 'stud' + ], + modifiable: true), + Emoji( + name: 'woman in tuxedo', + char: '\u{1F935}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_tuxedo', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'fame', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'woman in tuxedo: light skin tone', + char: '\u{1F935}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_tuxedo_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'fame', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman in tuxedo: medium-light skin tone', + char: '\u{1F935}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_tuxedo_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'fame', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman in tuxedo: medium skin tone', + char: '\u{1F935}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_tuxedo_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'fame', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman in tuxedo: medium-dark skin tone', + char: '\u{1F935}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_tuxedo_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'fame', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman in tuxedo: dark skin tone', + char: '\u{1F935}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_tuxedo_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'fame', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man in tuxedo', + char: '\u{1F935}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_tuxedo', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'men', + 'boys night', + 'fame', + 'jacket', + 'handsome', + 'costume', + 'husband', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'famous', + 'celebrity', + 'veste', + 'stud' + ]), + Emoji( + name: 'man in tuxedo: light skin tone', + char: '\u{1F935}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_tuxedo_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'men', + 'boys night', + 'fame', + 'jacket', + 'handsome', + 'costume', + 'husband', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'famous', + 'celebrity', + 'veste', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man in tuxedo: medium-light skin tone', + char: '\u{1F935}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_tuxedo_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'men', + 'boys night', + 'fame', + 'jacket', + 'handsome', + 'costume', + 'husband', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'famous', + 'celebrity', + 'veste', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man in tuxedo: medium skin tone', + char: '\u{1F935}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_tuxedo_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'men', + 'boys night', + 'fame', + 'jacket', + 'handsome', + 'costume', + 'husband', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'famous', + 'celebrity', + 'veste', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man in tuxedo: medium-dark skin tone', + char: '\u{1F935}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_tuxedo_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'men', + 'boys night', + 'fame', + 'jacket', + 'handsome', + 'costume', + 'husband', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'famous', + 'celebrity', + 'veste', + 'stud' + ], + modifiable: true), + Emoji( + name: 'man in tuxedo: dark skin tone', + char: '\u{1F935}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_tuxedo_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'wedding', + 'men', + 'boys night', + 'fame', + 'jacket', + 'handsome', + 'costume', + 'husband', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'famous', + 'celebrity', + 'veste', + 'stud' + ], + modifiable: true), + Emoji( + name: 'princess', + char: '\u{1F478}', + shortName: 'princess', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'fairy tale', + 'fantasy', + 'uc6', + 'diversity', + 'wedding', + 'women', + 'halloween', + 'beautiful', + 'girls night', + 'power', + 'queen', + 'disney', + 'bling', + 'fame', + 'crown', + 'rich', + 'dress', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'woman', + 'female', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'king', + 'prince', + 'princess', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'princess: light skin tone', + char: '\u{1F478}\u{1F3FB}', + shortName: 'princess_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'fairy tale', + 'fantasy', + 'light skin tone', + 'uc8', + 'diversity', + 'wedding', + 'women', + 'halloween', + 'beautiful', + 'girls night', + 'power', + 'queen', + 'disney', + 'bling', + 'fame', + 'crown', + 'rich', + 'dress', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'woman', + 'female', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'king', + 'prince', + 'princess', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy' + ], + modifiable: true), + Emoji( + name: 'princess: medium-light skin tone', + char: '\u{1F478}\u{1F3FC}', + shortName: 'princess_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'fairy tale', + 'fantasy', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'wedding', + 'women', + 'halloween', + 'beautiful', + 'girls night', + 'power', + 'queen', + 'disney', + 'bling', + 'fame', + 'crown', + 'rich', + 'dress', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'woman', + 'female', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'king', + 'prince', + 'princess', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy' + ], + modifiable: true), + Emoji( + name: 'princess: medium skin tone', + char: '\u{1F478}\u{1F3FD}', + shortName: 'princess_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'fairy tale', + 'fantasy', + 'medium skin tone', + 'uc8', + 'diversity', + 'wedding', + 'women', + 'halloween', + 'beautiful', + 'girls night', + 'power', + 'queen', + 'disney', + 'bling', + 'fame', + 'crown', + 'rich', + 'dress', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'woman', + 'female', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'king', + 'prince', + 'princess', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy' + ], + modifiable: true), + Emoji( + name: 'princess: medium-dark skin tone', + char: '\u{1F478}\u{1F3FE}', + shortName: 'princess_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'fairy tale', + 'fantasy', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'wedding', + 'women', + 'halloween', + 'beautiful', + 'girls night', + 'power', + 'queen', + 'disney', + 'bling', + 'fame', + 'crown', + 'rich', + 'dress', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'woman', + 'female', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'king', + 'prince', + 'princess', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy' + ], + modifiable: true), + Emoji( + name: 'princess: dark skin tone', + char: '\u{1F478}\u{1F3FF}', + shortName: 'princess_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'fairy tale', + 'fantasy', + 'uc8', + 'diversity', + 'wedding', + 'women', + 'halloween', + 'beautiful', + 'girls night', + 'power', + 'queen', + 'disney', + 'bling', + 'fame', + 'crown', + 'rich', + 'dress', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'woman', + 'female', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'king', + 'prince', + 'princess', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy' + ], + modifiable: true), + Emoji( + name: 'prince', + char: '\u{1F934}', + shortName: 'prince', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'prince', + 'uc9', + 'diversity', + 'men', + 'power', + 'queen', + 'disney', + 'bling', + 'fame', + 'crown', + 'rich', + 'handsome', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'king', + 'prince', + 'princess', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy', + 'stud' + ]), + Emoji( + name: 'prince: light skin tone', + char: '\u{1F934}\u{1F3FB}', + shortName: 'prince_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'light skin tone', + 'prince', + 'uc9', + 'diversity', + 'men', + 'power', + 'queen', + 'disney', + 'bling', + 'fame', + 'crown', + 'rich', + 'handsome', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'king', + 'prince', + 'princess', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy', + 'stud' + ], + modifiable: true), + Emoji( + name: 'prince: medium-light skin tone', + char: '\u{1F934}\u{1F3FC}', + shortName: 'prince_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium-light skin tone', + 'prince', + 'uc9', + 'diversity', + 'men', + 'power', + 'queen', + 'disney', + 'bling', + 'fame', + 'crown', + 'rich', + 'handsome', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'king', + 'prince', + 'princess', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy', + 'stud' + ], + modifiable: true), + Emoji( + name: 'prince: medium skin tone', + char: '\u{1F934}\u{1F3FD}', + shortName: 'prince_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium skin tone', + 'prince', + 'uc9', + 'diversity', + 'men', + 'power', + 'queen', + 'disney', + 'bling', + 'fame', + 'crown', + 'rich', + 'handsome', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'king', + 'prince', + 'princess', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy', + 'stud' + ], + modifiable: true), + Emoji( + name: 'prince: medium-dark skin tone', + char: '\u{1F934}\u{1F3FE}', + shortName: 'prince_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium-dark skin tone', + 'prince', + 'uc9', + 'diversity', + 'men', + 'power', + 'queen', + 'disney', + 'bling', + 'fame', + 'crown', + 'rich', + 'handsome', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'king', + 'prince', + 'princess', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy', + 'stud' + ], + modifiable: true), + Emoji( + name: 'prince: dark skin tone', + char: '\u{1F934}\u{1F3FF}', + shortName: 'prince_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'prince', + 'uc9', + 'diversity', + 'men', + 'power', + 'queen', + 'disney', + 'bling', + 'fame', + 'crown', + 'rich', + 'handsome', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'king', + 'prince', + 'princess', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy', + 'stud' + ], + modifiable: true), + Emoji( + name: 'superhero', + char: '\u{1F9B8}', + shortName: 'superhero', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + 'men', + '911', + 'power', + 'daddy', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'emergency', + 'injury', + 'dad', + 'papa', + 'pere', + 'father', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman' + ]), + Emoji( + name: 'superhero: light skin tone', + char: '\u{1F9B8}\u{1F3FB}', + shortName: 'superhero_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + 'men', + '911', + 'power', + 'daddy', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'emergency', + 'injury', + 'dad', + 'papa', + 'pere', + 'father', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman' + ], + modifiable: true), + Emoji( + name: 'superhero: medium-light skin tone', + char: '\u{1F9B8}\u{1F3FC}', + shortName: 'superhero_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + 'men', + '911', + 'power', + 'daddy', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'emergency', + 'injury', + 'dad', + 'papa', + 'pere', + 'father', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman' + ], + modifiable: true), + Emoji( + name: 'superhero: medium skin tone', + char: '\u{1F9B8}\u{1F3FD}', + shortName: 'superhero_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + 'men', + '911', + 'power', + 'daddy', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'emergency', + 'injury', + 'dad', + 'papa', + 'pere', + 'father', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman' + ], + modifiable: true), + Emoji( + name: 'superhero: medium-dark skin tone', + char: '\u{1F9B8}\u{1F3FE}', + shortName: 'superhero_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + 'men', + '911', + 'power', + 'daddy', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'emergency', + 'injury', + 'dad', + 'papa', + 'pere', + 'father', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman' + ], + modifiable: true), + Emoji( + name: 'superhero: dark skin tone', + char: '\u{1F9B8}\u{1F3FF}', + shortName: 'superhero_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + 'men', + '911', + 'power', + 'daddy', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'emergency', + 'injury', + 'dad', + 'papa', + 'pere', + 'father', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman' + ], + modifiable: true), + Emoji( + name: 'woman superhero', + char: '\u{1F9B8}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_superhero', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + '911', + 'power', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'mom', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'emergency', + 'injury', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman superhero: light skin tone', + char: '\u{1F9B8}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_superhero_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + '911', + 'power', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'mom', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'emergency', + 'injury', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman superhero: medium-light skin tone', + char: '\u{1F9B8}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_superhero_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + '911', + 'power', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'mom', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'emergency', + 'injury', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman superhero: medium skin tone', + char: '\u{1F9B8}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_superhero_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + '911', + 'power', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'mom', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'emergency', + 'injury', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman superhero: medium-dark skin tone', + char: '\u{1F9B8}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_superhero_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + '911', + 'power', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'mom', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'emergency', + 'injury', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman superhero: dark skin tone', + char: '\u{1F9B8}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_superhero_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + '911', + 'power', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'mom', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'emergency', + 'injury', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man superhero', + char: '\u{1F9B8}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_superhero', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + 'men', + '911', + 'power', + 'daddy', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'emergency', + 'injury', + 'dad', + 'papa', + 'pere', + 'father', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman' + ]), + Emoji( + name: 'man superhero: light skin tone', + char: '\u{1F9B8}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_superhero_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + 'men', + '911', + 'power', + 'daddy', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'emergency', + 'injury', + 'dad', + 'papa', + 'pere', + 'father', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman' + ], + modifiable: true), + Emoji( + name: 'man superhero: medium-light skin tone', + char: '\u{1F9B8}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_superhero_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + 'men', + '911', + 'power', + 'daddy', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'emergency', + 'injury', + 'dad', + 'papa', + 'pere', + 'father', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman' + ], + modifiable: true), + Emoji( + name: 'man superhero: medium skin tone', + char: '\u{1F9B8}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_superhero_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + 'men', + '911', + 'power', + 'daddy', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'emergency', + 'injury', + 'dad', + 'papa', + 'pere', + 'father', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman' + ], + modifiable: true), + Emoji( + name: 'man superhero: medium-dark skin tone', + char: '\u{1F9B8}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_superhero_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + 'men', + '911', + 'power', + 'daddy', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'emergency', + 'injury', + 'dad', + 'papa', + 'pere', + 'father', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman' + ], + modifiable: true), + Emoji( + name: 'man superhero: dark skin tone', + char: '\u{1F9B8}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_superhero_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'peace', + 'halloween', + 'men', + '911', + 'power', + 'daddy', + 'fame', + 'help', + 'super hero', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'peace out', + 'peace sign', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'emergency', + 'injury', + 'dad', + 'papa', + 'pere', + 'father', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman' + ], + modifiable: true), + Emoji( + name: 'supervillain', + char: '\u{1F9B9}', + shortName: 'supervillain', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'men', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ]), + Emoji( + name: 'supervillain: light skin tone', + char: '\u{1F9B9}\u{1F3FB}', + shortName: 'supervillain_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'men', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'supervillain: medium-light skin tone', + char: '\u{1F9B9}\u{1F3FC}', + shortName: 'supervillain_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'men', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'supervillain: medium skin tone', + char: '\u{1F9B9}\u{1F3FD}', + shortName: 'supervillain_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'men', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'supervillain: medium-dark skin tone', + char: '\u{1F9B9}\u{1F3FE}', + shortName: 'supervillain_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'men', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'supervillain: dark skin tone', + char: '\u{1F9B9}\u{1F3FF}', + shortName: 'supervillain_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'men', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'woman supervillain: light skin tone', + char: '\u{1F9B9}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_supervillain_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'woman supervillain', + char: '\u{1F9B9}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_supervillain', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ]), + Emoji( + name: 'woman supervillain: medium-light skin tone', + char: '\u{1F9B9}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_supervillain_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'woman supervillain: medium skin tone', + char: '\u{1F9B9}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_supervillain_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'woman supervillain: medium-dark skin tone', + char: '\u{1F9B9}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_supervillain_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'woman supervillain: dark skin tone', + char: '\u{1F9B9}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_supervillain_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'man supervillain', + char: '\u{1F9B9}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_supervillain', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'men', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ]), + Emoji( + name: 'man supervillain: light skin tone', + char: '\u{1F9B9}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_supervillain_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'men', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'man supervillain: medium-light skin tone', + char: '\u{1F9B9}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_supervillain_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'men', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'man supervillain: medium skin tone', + char: '\u{1F9B9}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_supervillain_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'men', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'man supervillain: medium-dark skin tone', + char: '\u{1F9B9}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_supervillain_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'men', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'man supervillain: dark skin tone', + char: '\u{1F9B9}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_supervillain_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc11', + 'diversity', + 'halloween', + 'men', + 'power', + 'evil', + 'fame', + 'guilty', + 'super hero', + 'killer', + 'costume', + 'mask', + 'fantasy', + 'proud', + 'greed', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'famous', + 'celebrity', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'selfish' + ], + modifiable: true), + Emoji( + name: 'ninja', + char: '\u{1F977}', + shortName: 'ninja', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'japan', + 'evil', + 'chinese', + 'super hero', + 'killer', + 'mask', + 'disguise', + 'shinobi', + 'japanese', + 'ninja', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'chinois', + 'asian', + 'chine', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'samurai' + ]), + Emoji( + name: 'ninja: light skin tone', + char: '\u{1F977}\u{1F3FB}', + shortName: 'ninja_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'japan', + 'evil', + 'chinese', + 'super hero', + 'killer', + 'mask', + 'disguise', + 'shinobi', + 'japanese', + 'ninja', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'chinois', + 'asian', + 'chine', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'samurai' + ], + modifiable: true), + Emoji( + name: 'ninja: medium-light skin tone', + char: '\u{1F977}\u{1F3FC}', + shortName: 'ninja_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'japan', + 'evil', + 'chinese', + 'super hero', + 'killer', + 'mask', + 'disguise', + 'shinobi', + 'japanese', + 'ninja', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'chinois', + 'asian', + 'chine', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'samurai' + ], + modifiable: true), + Emoji( + name: 'ninja: medium skin tone', + char: '\u{1F977}\u{1F3FD}', + shortName: 'ninja_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'japan', + 'evil', + 'chinese', + 'super hero', + 'killer', + 'mask', + 'disguise', + 'shinobi', + 'japanese', + 'ninja', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'chinois', + 'asian', + 'chine', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'samurai' + ], + modifiable: true), + Emoji( + name: 'ninja: medium-dark skin tone', + char: '\u{1F977}\u{1F3FE}', + shortName: 'ninja_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'japan', + 'evil', + 'chinese', + 'super hero', + 'killer', + 'mask', + 'disguise', + 'shinobi', + 'japanese', + 'ninja', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'chinois', + 'asian', + 'chine', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'samurai' + ], + modifiable: true), + Emoji( + name: 'ninja: dark skin tone', + char: '\u{1F977}\u{1F3FF}', + shortName: 'ninja_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'japan', + 'evil', + 'chinese', + 'super hero', + 'killer', + 'mask', + 'disguise', + 'shinobi', + 'japanese', + 'ninja', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'chinois', + 'asian', + 'chine', + 'superhero', + 'superman', + 'batman', + 'savage', + 'scary clown', + 'samurai' + ], + modifiable: true), + Emoji( + name: 'mx claus', + char: '\u{1F9D1}\u{200D}\u{1F384}', + shortName: 'mx_claus', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc13', + 'holidays', + 'winter', + 'christmas', + 'advent', + 'fantasy', + 'disguise', + 'holiday', + 'navidad', + 'xmas', + 'noel', + 'merry christmas' + ]), + Emoji( + name: 'mx claus: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F384}', + shortName: 'mx_claus_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc13', + 'holidays', + 'winter', + 'christmas', + 'advent', + 'fantasy', + 'disguise', + 'holiday', + 'navidad', + 'xmas', + 'noel', + 'merry christmas' + ], + modifiable: true), + Emoji( + name: 'mx claus: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F384}', + shortName: 'mx_claus_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc13', + 'holidays', + 'winter', + 'christmas', + 'advent', + 'fantasy', + 'disguise', + 'holiday', + 'navidad', + 'xmas', + 'noel', + 'merry christmas' + ], + modifiable: true), + Emoji( + name: 'mx claus: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F384}', + shortName: 'mx_claus_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc13', + 'holidays', + 'winter', + 'christmas', + 'advent', + 'fantasy', + 'disguise', + 'holiday', + 'navidad', + 'xmas', + 'noel', + 'merry christmas' + ], + modifiable: true), + Emoji( + name: 'mx claus: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F384}', + shortName: 'mx_claus_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc13', + 'holidays', + 'winter', + 'christmas', + 'advent', + 'fantasy', + 'disguise', + 'holiday', + 'navidad', + 'xmas', + 'noel', + 'merry christmas' + ], + modifiable: true), + Emoji( + name: 'mx claus: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F384}', + shortName: 'mx_claus_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc13', + 'holidays', + 'winter', + 'christmas', + 'advent', + 'fantasy', + 'disguise', + 'holiday', + 'navidad', + 'xmas', + 'noel', + 'merry christmas' + ], + modifiable: true), + Emoji( + name: 'Mrs. Claus', + char: '\u{1F936}', + shortName: 'mrs_claus', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Christmas', + 'Mrs.', + 'celebration', + 'claus', + 'mother', + 'uc9', + 'holidays', + 'diversity', + 'winter', + 'christmas', + 'santa', + 'advent', + 'fantasy', + 'disguise', + 'holiday', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus' + ]), + Emoji( + name: 'Mrs. Claus: light skin tone', + char: '\u{1F936}\u{1F3FB}', + shortName: 'mrs_claus_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Christmas', + 'Mrs.', + 'celebration', + 'claus', + 'light skin tone', + 'mother', + 'uc9', + 'holidays', + 'diversity', + 'winter', + 'christmas', + 'santa', + 'advent', + 'fantasy', + 'disguise', + 'holiday', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus' + ], + modifiable: true), + Emoji( + name: 'Mrs. Claus: medium-light skin tone', + char: '\u{1F936}\u{1F3FC}', + shortName: 'mrs_claus_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Christmas', + 'Mrs.', + 'celebration', + 'claus', + 'medium-light skin tone', + 'mother', + 'uc9', + 'holidays', + 'diversity', + 'winter', + 'christmas', + 'santa', + 'advent', + 'fantasy', + 'disguise', + 'holiday', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus' + ], + modifiable: true), + Emoji( + name: 'Mrs. Claus: medium skin tone', + char: '\u{1F936}\u{1F3FD}', + shortName: 'mrs_claus_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Christmas', + 'Mrs.', + 'celebration', + 'claus', + 'medium skin tone', + 'mother', + 'uc9', + 'holidays', + 'diversity', + 'winter', + 'christmas', + 'santa', + 'advent', + 'fantasy', + 'disguise', + 'holiday', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus' + ], + modifiable: true), + Emoji( + name: 'Mrs. Claus: medium-dark skin tone', + char: '\u{1F936}\u{1F3FE}', + shortName: 'mrs_claus_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Christmas', + 'Mrs.', + 'celebration', + 'claus', + 'medium-dark skin tone', + 'mother', + 'uc9', + 'holidays', + 'diversity', + 'winter', + 'christmas', + 'santa', + 'advent', + 'fantasy', + 'disguise', + 'holiday', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus' + ], + modifiable: true), + Emoji( + name: 'Mrs. Claus: dark skin tone', + char: '\u{1F936}\u{1F3FF}', + shortName: 'mrs_claus_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Christmas', + 'Mrs.', + 'celebration', + 'claus', + 'dark skin tone', + 'mother', + 'uc9', + 'holidays', + 'diversity', + 'winter', + 'christmas', + 'santa', + 'advent', + 'fantasy', + 'disguise', + 'holiday', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus' + ], + modifiable: true), + Emoji( + name: 'Santa Claus', + char: '\u{1F385}', + shortName: 'santa', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Christmas', + 'celebration', + 'claus', + 'father', + 'santa', + 'uc6', + 'holidays', + 'diversity', + 'winter', + 'christmas', + 'santa', + 'mustache', + 'advent', + 'beard', + 'fantasy', + 'disguise', + 'holiday', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus' + ]), + Emoji( + name: 'Santa Claus: light skin tone', + char: '\u{1F385}\u{1F3FB}', + shortName: 'santa_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Christmas', + 'celebration', + 'claus', + 'father', + 'light skin tone', + 'santa', + 'uc8', + 'holidays', + 'diversity', + 'winter', + 'christmas', + 'santa', + 'mustache', + 'advent', + 'beard', + 'fantasy', + 'disguise', + 'holiday', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus' + ], + modifiable: true), + Emoji( + name: 'Santa Claus: medium-light skin tone', + char: '\u{1F385}\u{1F3FC}', + shortName: 'santa_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Christmas', + 'celebration', + 'claus', + 'father', + 'medium-light skin tone', + 'santa', + 'uc8', + 'holidays', + 'diversity', + 'winter', + 'christmas', + 'santa', + 'mustache', + 'advent', + 'beard', + 'fantasy', + 'disguise', + 'holiday', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus' + ], + modifiable: true), + Emoji( + name: 'Santa Claus: medium skin tone', + char: '\u{1F385}\u{1F3FD}', + shortName: 'santa_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Christmas', + 'celebration', + 'claus', + 'father', + 'medium skin tone', + 'santa', + 'uc8', + 'holidays', + 'diversity', + 'winter', + 'christmas', + 'santa', + 'mustache', + 'advent', + 'beard', + 'fantasy', + 'disguise', + 'holiday', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus' + ], + modifiable: true), + Emoji( + name: 'Santa Claus: medium-dark skin tone', + char: '\u{1F385}\u{1F3FE}', + shortName: 'santa_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Christmas', + 'celebration', + 'claus', + 'father', + 'medium-dark skin tone', + 'santa', + 'uc8', + 'holidays', + 'diversity', + 'winter', + 'christmas', + 'santa', + 'mustache', + 'advent', + 'beard', + 'fantasy', + 'disguise', + 'holiday', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus' + ], + modifiable: true), + Emoji( + name: 'Santa Claus: dark skin tone', + char: '\u{1F385}\u{1F3FF}', + shortName: 'santa_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Christmas', + 'celebration', + 'claus', + 'dark skin tone', + 'father', + 'santa', + 'uc8', + 'holidays', + 'diversity', + 'winter', + 'christmas', + 'santa', + 'mustache', + 'advent', + 'beard', + 'fantasy', + 'disguise', + 'holiday', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus' + ], + modifiable: true), + Emoji( + name: 'mage', + char: '\u{1F9D9}', + shortName: 'mage', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'sorcerer', + 'sorceress', + 'witch', + 'wizard', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'wizard', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ]), + Emoji( + name: 'mage: light skin tone', + char: '\u{1F9D9}\u{1F3FB}', + shortName: 'mage_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'light skin tone', + 'sorcerer', + 'sorceress', + 'witch', + 'wizard', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'wizard', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'mage: medium-light skin tone', + char: '\u{1F9D9}\u{1F3FC}', + shortName: 'mage_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium-light skin tone', + 'sorcerer', + 'sorceress', + 'witch', + 'wizard', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'wizard', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'mage: medium skin tone', + char: '\u{1F9D9}\u{1F3FD}', + shortName: 'mage_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium skin tone', + 'sorcerer', + 'sorceress', + 'witch', + 'wizard', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'wizard', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'mage: medium-dark skin tone', + char: '\u{1F9D9}\u{1F3FE}', + shortName: 'mage_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium-dark skin tone', + 'sorcerer', + 'sorceress', + 'witch', + 'wizard', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'wizard', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'mage: dark skin tone', + char: '\u{1F9D9}\u{1F3FF}', + shortName: 'mage_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'dark skin tone', + 'sorcerer', + 'sorceress', + 'witch', + 'wizard', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'wizard', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'woman mage', + char: '\u{1F9D9}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_mage', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'sorceress', + 'witch', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'wizard', + 'fantasy', + 'disguise', + 'snow white', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ]), + Emoji( + name: 'woman mage: light skin tone', + char: '\u{1F9D9}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_mage_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'light skin tone', + 'sorceress', + 'witch', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'wizard', + 'fantasy', + 'disguise', + 'snow white', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'woman mage: medium-light skin tone', + char: '\u{1F9D9}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_mage_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium-light skin tone', + 'sorceress', + 'witch', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'wizard', + 'fantasy', + 'disguise', + 'snow white', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'woman mage: medium skin tone', + char: '\u{1F9D9}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_mage_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium skin tone', + 'sorceress', + 'witch', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'wizard', + 'fantasy', + 'disguise', + 'snow white', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'woman mage: medium-dark skin tone', + char: '\u{1F9D9}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_mage_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium-dark skin tone', + 'sorceress', + 'witch', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'wizard', + 'fantasy', + 'disguise', + 'snow white', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'woman mage: dark skin tone', + char: '\u{1F9D9}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_mage_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'dark skin tone', + 'sorceress', + 'witch', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'wizard', + 'fantasy', + 'disguise', + 'snow white', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'man mage', + char: '\u{1F9D9}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_mage', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'sorcerer', + 'wizard', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'beard', + 'wizard', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ]), + Emoji( + name: 'man mage: light skin tone', + char: '\u{1F9D9}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_mage_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'light skin tone', + 'sorcerer', + 'wizard', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'beard', + 'wizard', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'man mage: medium-light skin tone', + char: '\u{1F9D9}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_mage_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium-light skin tone', + 'sorcerer', + 'wizard', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'beard', + 'wizard', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'man mage: medium skin tone', + char: '\u{1F9D9}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_mage_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium skin tone', + 'sorcerer', + 'wizard', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'beard', + 'wizard', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'man mage: medium-dark skin tone', + char: '\u{1F9D9}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_mage_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium-dark skin tone', + 'sorcerer', + 'wizard', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'beard', + 'wizard', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'man mage: dark skin tone', + char: '\u{1F9D9}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_mage_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'dark skin tone', + 'sorcerer', + 'wizard', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'beard', + 'wizard', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical', + 'Sorcerer', + 'Sorceress', + 'witch' + ], + modifiable: true), + Emoji( + name: 'elf', + char: '\u{1F9DD}', + shortName: 'elf', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ]), + Emoji( + name: 'elf: light skin tone', + char: '\u{1F9DD}\u{1F3FB}', + shortName: 'elf_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'light skin tone', + 'magical', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'elf: medium-light skin tone', + char: '\u{1F9DD}\u{1F3FC}', + shortName: 'elf_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'magical', + 'medium-light skin tone', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'elf: medium skin tone', + char: '\u{1F9DD}\u{1F3FD}', + shortName: 'elf_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'magical', + 'medium skin tone', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'elf: medium-dark skin tone', + char: '\u{1F9DD}\u{1F3FE}', + shortName: 'elf_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'magical', + 'medium-dark skin tone', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'elf: dark skin tone', + char: '\u{1F9DD}\u{1F3FF}', + shortName: 'elf_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'dark skin tone', + 'magical', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'woman elf', + char: '\u{1F9DD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_elf', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'magical', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ]), + Emoji( + name: 'woman elf: light skin tone', + char: '\u{1F9DD}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_elf_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'light skin tone', + 'magical', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'woman elf: medium-light skin tone', + char: '\u{1F9DD}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_elf_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'magical', + 'medium-light skin tone', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'woman elf: medium skin tone', + char: '\u{1F9DD}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_elf_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'magical', + 'medium skin tone', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'woman elf: medium-dark skin tone', + char: '\u{1F9DD}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_elf_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'magical', + 'medium-dark skin tone', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'woman elf: dark skin tone', + char: '\u{1F9DD}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_elf_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'dark skin tone', + 'magical', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'man elf', + char: '\u{1F9DD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_elf', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'magical', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ]), + Emoji( + name: 'man elf: light skin tone', + char: '\u{1F9DD}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_elf_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'light skin tone', + 'magical', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'man elf: medium-light skin tone', + char: '\u{1F9DD}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_elf_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'magical', + 'medium-light skin tone', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'man elf: medium skin tone', + char: '\u{1F9DD}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_elf_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'magical', + 'medium skin tone', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'man elf: medium-dark skin tone', + char: '\u{1F9DD}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_elf_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'magical', + 'medium-dark skin tone', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'man elf: dark skin tone', + char: '\u{1F9DD}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_elf_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'dark skin tone', + 'magical', + 'uc10', + 'diversity', + 'halloween', + 'christmas', + 'magic', + 'legolas', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'vampire', + char: '\u{1F9DB}', + shortName: 'vampire', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Dracula', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ]), + Emoji( + name: 'vampire: light skin tone', + char: '\u{1F9DB}\u{1F3FB}', + shortName: 'vampire_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Dracula', + 'light skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'vampire: medium-light skin tone', + char: '\u{1F9DB}\u{1F3FC}', + shortName: 'vampire_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Dracula', + 'medium-light skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'vampire: medium skin tone', + char: '\u{1F9DB}\u{1F3FD}', + shortName: 'vampire_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Dracula', + 'medium skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'vampire: medium-dark skin tone', + char: '\u{1F9DB}\u{1F3FE}', + shortName: 'vampire_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Dracula', + 'medium-dark skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'vampire: dark skin tone', + char: '\u{1F9DB}\u{1F3FF}', + shortName: 'vampire_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Dracula', + 'dark skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'woman vampire', + char: '\u{1F9DB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_vampire', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ]), + Emoji( + name: 'woman vampire: light skin tone', + char: '\u{1F9DB}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_vampire_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'light skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'woman vampire: medium-light skin tone', + char: '\u{1F9DB}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_vampire_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium-light skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'woman vampire: medium skin tone', + char: '\u{1F9DB}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_vampire_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'woman vampire: medium-dark skin tone', + char: '\u{1F9DB}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_vampire_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium-dark skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'woman vampire: dark skin tone', + char: '\u{1F9DB}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_vampire_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'dark skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'man vampire', + char: '\u{1F9DB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_vampire', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Dracula', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ]), + Emoji( + name: 'man vampire: light skin tone', + char: '\u{1F9DB}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_vampire_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Dracula', + 'light skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'man vampire: medium-light skin tone', + char: '\u{1F9DB}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_vampire_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Dracula', + 'medium-light skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'man vampire: medium skin tone', + char: '\u{1F9DB}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_vampire_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Dracula', + 'medium skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'man vampire: medium-dark skin tone', + char: '\u{1F9DB}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_vampire_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Dracula', + 'medium-dark skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'man vampire: dark skin tone', + char: '\u{1F9DB}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_vampire_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Dracula', + 'dark skin tone', + 'undead', + 'uc10', + 'diversity', + 'halloween', + 'vampire', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'dracula' + ], + modifiable: true), + Emoji( + name: 'zombie', + char: '\u{1F9DF}', + shortName: 'zombie', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc10', + 'halloween', + 'monster', + 'disguise', + 'samhain', + 'monsters', + 'beast' + ]), + Emoji( + name: 'woman zombie', + char: '\u{1F9DF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_zombie', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'undead', + 'walking dead', + 'uc10', + 'halloween', + 'monster', + 'fantasy', + 'disguise', + 'samhain', + 'monsters', + 'beast' + ]), + Emoji( + name: 'man zombie', + char: '\u{1F9DF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_zombie', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'undead', + 'walking dead', + 'uc10', + 'halloween', + 'monster', + 'fantasy', + 'disguise', + 'samhain', + 'monsters', + 'beast' + ]), + Emoji( + name: 'genie', + char: '\u{1F9DE}', + shortName: 'genie', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'uc10', + 'halloween', + 'magic', + 'djinni', + 'fantasy', + 'disguise', + 'samhain', + 'spell', + 'genie', + 'magical', + 'jinni' + ]), + Emoji( + name: 'woman genie', + char: '\u{1F9DE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_genie', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'djinn', + 'uc10', + 'halloween', + 'magic', + 'djinni', + 'fantasy', + 'disguise', + 'samhain', + 'spell', + 'genie', + 'magical', + 'jinni' + ]), + Emoji( + name: 'man genie', + char: '\u{1F9DE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_genie', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'djinn', + 'uc10', + 'halloween', + 'magic', + 'disney', + 'djinni', + 'fantasy', + 'disguise', + 'samhain', + 'spell', + 'genie', + 'magical', + 'cartoon', + 'jinni' + ]), + Emoji( + name: 'merperson', + char: '\u{1F9DC}', + shortName: 'merperson', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'mermaid', + 'merman', + 'merwoman', + 'uc10', + 'diversity', + 'halloween', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ]), + Emoji( + name: 'merperson: light skin tone', + char: '\u{1F9DC}\u{1F3FB}', + shortName: 'merperson_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'light skin tone', + 'mermaid', + 'merman', + 'merwoman', + 'uc10', + 'diversity', + 'halloween', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'merperson: medium-light skin tone', + char: '\u{1F9DC}\u{1F3FC}', + shortName: 'merperson_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium-light skin tone', + 'mermaid', + 'merman', + 'merwoman', + 'uc10', + 'diversity', + 'halloween', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'merperson: medium skin tone', + char: '\u{1F9DC}\u{1F3FD}', + shortName: 'merperson_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium skin tone', + 'mermaid', + 'merman', + 'merwoman', + 'uc10', + 'diversity', + 'halloween', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'merperson: medium-dark skin tone', + char: '\u{1F9DC}\u{1F3FE}', + shortName: 'merperson_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium-dark skin tone', + 'mermaid', + 'merman', + 'merwoman', + 'uc10', + 'diversity', + 'halloween', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'merperson: dark skin tone', + char: '\u{1F9DC}\u{1F3FF}', + shortName: 'merperson_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'dark skin tone', + 'mermaid', + 'merman', + 'merwoman', + 'uc10', + 'diversity', + 'halloween', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'mermaid', + char: '\u{1F9DC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'mermaid', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'merwoman', + 'uc10', + 'diversity', + 'halloween', + 'disney', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cartoon', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ]), + Emoji( + name: 'mermaid: light skin tone', + char: '\u{1F9DC}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'mermaid_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'light skin tone', + 'merwoman', + 'uc10', + 'diversity', + 'halloween', + 'disney', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cartoon', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'mermaid: medium-light skin tone', + char: '\u{1F9DC}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'mermaid_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium-light skin tone', + 'merwoman', + 'uc10', + 'diversity', + 'halloween', + 'disney', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cartoon', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'mermaid: medium skin tone', + char: '\u{1F9DC}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'mermaid_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium skin tone', + 'merwoman', + 'uc10', + 'diversity', + 'halloween', + 'disney', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cartoon', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'mermaid: medium-dark skin tone', + char: '\u{1F9DC}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'mermaid_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'medium-dark skin tone', + 'merwoman', + 'uc10', + 'diversity', + 'halloween', + 'disney', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cartoon', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'mermaid: dark skin tone', + char: '\u{1F9DC}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'mermaid_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'dark skin tone', + 'merwoman', + 'uc10', + 'diversity', + 'halloween', + 'disney', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cartoon', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'merman', + char: '\u{1F9DC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'merman', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Triton', + 'uc10', + 'diversity', + 'halloween', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ]), + Emoji( + name: 'merman: light skin tone', + char: '\u{1F9DC}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'merman_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Triton', + 'light skin tone', + 'uc10', + 'diversity', + 'halloween', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'merman: medium-light skin tone', + char: '\u{1F9DC}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'merman_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Triton', + 'medium-light skin tone', + 'uc10', + 'diversity', + 'halloween', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'merman: medium skin tone', + char: '\u{1F9DC}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'merman_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Triton', + 'medium skin tone', + 'uc10', + 'diversity', + 'halloween', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'merman: medium-dark skin tone', + char: '\u{1F9DC}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'merman_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Triton', + 'medium-dark skin tone', + 'uc10', + 'diversity', + 'halloween', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'merman: dark skin tone', + char: '\u{1F9DC}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'merman_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Triton', + 'dark skin tone', + 'uc10', + 'diversity', + 'halloween', + 'mermaid', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren' + ], + modifiable: true), + Emoji( + name: 'fairy', + char: '\u{1F9DA}', + shortName: 'fairy', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Oberon', + 'Puck', + 'Titania', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical' + ]), + Emoji( + name: 'fairy: light skin tone', + char: '\u{1F9DA}\u{1F3FB}', + shortName: 'fairy_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Oberon', + 'Puck', + 'Titania', + 'light skin tone', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'fairy: medium-light skin tone', + char: '\u{1F9DA}\u{1F3FC}', + shortName: 'fairy_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Oberon', + 'Puck', + 'Titania', + 'medium-light skin tone', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'fairy: medium skin tone', + char: '\u{1F9DA}\u{1F3FD}', + shortName: 'fairy_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Oberon', + 'Puck', + 'Titania', + 'medium skin tone', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'fairy: medium-dark skin tone', + char: '\u{1F9DA}\u{1F3FE}', + shortName: 'fairy_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Oberon', + 'Puck', + 'Titania', + 'medium-dark skin tone', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'fairy: dark skin tone', + char: '\u{1F9DA}\u{1F3FF}', + shortName: 'fairy_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Oberon', + 'Puck', + 'Titania', + 'dark skin tone', + 'uc10', + 'diversity', + 'halloween', + 'magic', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'woman fairy', + char: '\u{1F9DA}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_fairy', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Titania', + 'uc10', + 'diversity', + 'halloween', + 'beautiful', + 'magic', + 'disney', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'spell', + 'genie', + 'magical', + 'cartoon' + ]), + Emoji( + name: 'woman fairy: light skin tone', + char: '\u{1F9DA}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_fairy_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Titania', + 'light skin tone', + 'uc10', + 'diversity', + 'halloween', + 'beautiful', + 'magic', + 'disney', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'spell', + 'genie', + 'magical', + 'cartoon' + ], + modifiable: true), + Emoji( + name: 'woman fairy: medium-light skin tone', + char: '\u{1F9DA}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_fairy_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Titania', + 'medium-light skin tone', + 'uc10', + 'diversity', + 'halloween', + 'beautiful', + 'magic', + 'disney', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'spell', + 'genie', + 'magical', + 'cartoon' + ], + modifiable: true), + Emoji( + name: 'woman fairy: medium skin tone', + char: '\u{1F9DA}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_fairy_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Titania', + 'medium skin tone', + 'uc10', + 'diversity', + 'halloween', + 'beautiful', + 'magic', + 'disney', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'spell', + 'genie', + 'magical', + 'cartoon' + ], + modifiable: true), + Emoji( + name: 'woman fairy: medium-dark skin tone', + char: '\u{1F9DA}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_fairy_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Titania', + 'medium-dark skin tone', + 'uc10', + 'diversity', + 'halloween', + 'beautiful', + 'magic', + 'disney', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'spell', + 'genie', + 'magical', + 'cartoon' + ], + modifiable: true), + Emoji( + name: 'woman fairy: dark skin tone', + char: '\u{1F9DA}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_fairy_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Titania', + 'dark skin tone', + 'uc10', + 'diversity', + 'halloween', + 'beautiful', + 'magic', + 'disney', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'spell', + 'genie', + 'magical', + 'cartoon' + ], + modifiable: true), + Emoji( + name: 'man fairy', + char: '\u{1F9DA}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_fairy', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Oberon', + 'Puck', + 'uc10', + 'diversity', + 'halloween', + 'beautiful', + 'magic', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'spell', + 'genie', + 'magical' + ]), + Emoji( + name: 'man fairy: light skin tone', + char: '\u{1F9DA}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_fairy_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Oberon', + 'Puck', + 'light skin tone', + 'uc10', + 'diversity', + 'halloween', + 'beautiful', + 'magic', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'man fairy: medium-light skin tone', + char: '\u{1F9DA}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_fairy_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Oberon', + 'Puck', + 'medium-light skin tone', + 'uc10', + 'diversity', + 'halloween', + 'beautiful', + 'magic', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'man fairy: medium skin tone', + char: '\u{1F9DA}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_fairy_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Oberon', + 'Puck', + 'medium skin tone', + 'uc10', + 'diversity', + 'halloween', + 'beautiful', + 'magic', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'man fairy: medium-dark skin tone', + char: '\u{1F9DA}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_fairy_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Oberon', + 'Puck', + 'medium-dark skin tone', + 'uc10', + 'diversity', + 'halloween', + 'beautiful', + 'magic', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'man fairy: dark skin tone', + char: '\u{1F9DA}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_fairy_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'Oberon', + 'Puck', + 'dark skin tone', + 'uc10', + 'diversity', + 'halloween', + 'beautiful', + 'magic', + 'fantasy', + 'disguise', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'spell', + 'genie', + 'magical' + ], + modifiable: true), + Emoji( + name: 'baby angel', + char: '\u{1F47C}', + shortName: 'angel', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'angel', + 'baby', + 'face', + 'fairy tale', + 'fantasy', + 'uc6', + 'diversity', + 'halloween', + 'baby', + 'christmas', + 'pray', + 'omg', + 'jesus', + 'fantasy', + 'child', + 'soul', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'omfg', + 'oh my god', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'baby angel: light skin tone', + char: '\u{1F47C}\u{1F3FB}', + shortName: 'angel_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'angel', + 'baby', + 'face', + 'fairy tale', + 'fantasy', + 'light skin tone', + 'uc8', + 'diversity', + 'halloween', + 'baby', + 'christmas', + 'pray', + 'omg', + 'jesus', + 'fantasy', + 'child', + 'soul', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'omfg', + 'oh my god', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'baby angel: medium-light skin tone', + char: '\u{1F47C}\u{1F3FC}', + shortName: 'angel_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'angel', + 'baby', + 'face', + 'fairy tale', + 'fantasy', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'halloween', + 'baby', + 'christmas', + 'pray', + 'omg', + 'jesus', + 'fantasy', + 'child', + 'soul', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'omfg', + 'oh my god', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'baby angel: medium skin tone', + char: '\u{1F47C}\u{1F3FD}', + shortName: 'angel_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'angel', + 'baby', + 'face', + 'fairy tale', + 'fantasy', + 'medium skin tone', + 'uc8', + 'diversity', + 'halloween', + 'baby', + 'christmas', + 'pray', + 'omg', + 'jesus', + 'fantasy', + 'child', + 'soul', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'omfg', + 'oh my god', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'baby angel: medium-dark skin tone', + char: '\u{1F47C}\u{1F3FE}', + shortName: 'angel_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'angel', + 'baby', + 'face', + 'fairy tale', + 'fantasy', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'halloween', + 'baby', + 'christmas', + 'pray', + 'omg', + 'jesus', + 'fantasy', + 'child', + 'soul', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'omfg', + 'oh my god', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'baby angel: dark skin tone', + char: '\u{1F47C}\u{1F3FF}', + shortName: 'angel_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personFantasy, + keywords: [ + 'angel', + 'baby', + 'dark skin tone', + 'face', + 'fairy tale', + 'fantasy', + 'uc8', + 'diversity', + 'halloween', + 'baby', + 'christmas', + 'pray', + 'omg', + 'jesus', + 'fantasy', + 'child', + 'soul', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'samhain', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'omfg', + 'oh my god', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'pregnant woman', + char: '\u{1F930}', + shortName: 'pregnant_woman', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'pregnant', + 'woman', + 'uc9', + 'diversity', + 'women', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'pregnant woman: light skin tone', + char: '\u{1F930}\u{1F3FB}', + shortName: 'pregnant_woman_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'light skin tone', + 'pregnant', + 'woman', + 'uc9', + 'diversity', + 'women', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'pregnant woman: medium-light skin tone', + char: '\u{1F930}\u{1F3FC}', + shortName: 'pregnant_woman_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium-light skin tone', + 'pregnant', + 'woman', + 'uc9', + 'diversity', + 'women', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'pregnant woman: medium skin tone', + char: '\u{1F930}\u{1F3FD}', + shortName: 'pregnant_woman_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium skin tone', + 'pregnant', + 'woman', + 'uc9', + 'diversity', + 'women', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'pregnant woman: medium-dark skin tone', + char: '\u{1F930}\u{1F3FE}', + shortName: 'pregnant_woman_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'medium-dark skin tone', + 'pregnant', + 'woman', + 'uc9', + 'diversity', + 'women', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'pregnant woman: dark skin tone', + char: '\u{1F930}\u{1F3FF}', + shortName: 'pregnant_woman_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'dark skin tone', + 'pregnant', + 'woman', + 'uc9', + 'diversity', + 'women', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'breast-feeding', + char: '\u{1F931}', + shortName: 'breast_feeding', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'baby', + 'breast', + 'nursing', + 'uc10', + 'food', + 'diversity', + 'boobs', + 'women', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boob', + 'tits', + 'tit', + 'breast', + 'woman', + 'female', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'breast-feeding: light skin tone', + char: '\u{1F931}\u{1F3FB}', + shortName: 'breast_feeding_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'baby', + 'breast', + 'light skin tone', + 'nursing', + 'uc10', + 'food', + 'diversity', + 'boobs', + 'women', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boob', + 'tits', + 'tit', + 'breast', + 'woman', + 'female', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'breast-feeding: medium-light skin tone', + char: '\u{1F931}\u{1F3FC}', + shortName: 'breast_feeding_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'baby', + 'breast', + 'medium-light skin tone', + 'nursing', + 'uc10', + 'food', + 'diversity', + 'boobs', + 'women', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boob', + 'tits', + 'tit', + 'breast', + 'woman', + 'female', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'breast-feeding: medium skin tone', + char: '\u{1F931}\u{1F3FD}', + shortName: 'breast_feeding_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'baby', + 'breast', + 'medium skin tone', + 'nursing', + 'uc10', + 'food', + 'diversity', + 'boobs', + 'women', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boob', + 'tits', + 'tit', + 'breast', + 'woman', + 'female', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'breast-feeding: medium-dark skin tone', + char: '\u{1F931}\u{1F3FE}', + shortName: 'breast_feeding_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'baby', + 'breast', + 'medium-dark skin tone', + 'nursing', + 'uc10', + 'food', + 'diversity', + 'boobs', + 'women', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boob', + 'tits', + 'tit', + 'breast', + 'woman', + 'female', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'breast-feeding: dark skin tone', + char: '\u{1F931}\u{1F3FF}', + shortName: 'breast_feeding_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'baby', + 'breast', + 'dark skin tone', + 'nursing', + 'uc10', + 'food', + 'diversity', + 'boobs', + 'women', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boob', + 'tits', + 'tit', + 'breast', + 'woman', + 'female', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person feeding baby', + char: '\u{1F9D1}\u{200D}\u{1F37C}', + shortName: 'person_feeding_baby', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'daddy', + 'parent', + 'wife', + 'child', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'person feeding baby: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F37C}', + shortName: 'person_feeding_baby_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'daddy', + 'parent', + 'wife', + 'child', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'person feeding baby: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F37C}', + shortName: 'person_feeding_baby_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'daddy', + 'parent', + 'wife', + 'child', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'person feeding baby: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F37C}', + shortName: 'person_feeding_baby_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'daddy', + 'parent', + 'wife', + 'child', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'person feeding baby: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F37C}', + shortName: 'person_feeding_baby_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'daddy', + 'parent', + 'wife', + 'child', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'person feeding baby: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F37C}', + shortName: 'person_feeding_baby_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'daddy', + 'parent', + 'wife', + 'child', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'woman feeding baby', + char: '\u{1F469}\u{200D}\u{1F37C}', + shortName: 'woman_feeding_baby', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman feeding baby: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F37C}', + shortName: 'woman_feeding_baby_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman feeding baby: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F37C}', + shortName: 'woman_feeding_baby_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman feeding baby: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F37C}', + shortName: 'woman_feeding_baby_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman feeding baby: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F37C}', + shortName: 'woman_feeding_baby_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman feeding baby: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F37C}', + shortName: 'woman_feeding_baby_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'parent', + 'wife', + 'child', + 'mom', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man feeding baby', + char: '\u{1F468}\u{200D}\u{1F37C}', + shortName: 'man_feeding_baby', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'daddy', + 'parent', + 'child', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'man feeding baby: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F37C}', + shortName: 'man_feeding_baby_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'daddy', + 'parent', + 'child', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'man feeding baby: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F37C}', + shortName: 'man_feeding_baby_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'daddy', + 'parent', + 'child', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'man feeding baby: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F37C}', + shortName: 'man_feeding_baby_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'daddy', + 'parent', + 'child', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'man feeding baby: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F37C}', + shortName: 'man_feeding_baby_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'daddy', + 'parent', + 'child', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'man feeding baby: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F37C}', + shortName: 'man_feeding_baby_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personRole, + keywords: [ + 'uc13', + 'food', + 'baby', + 'daddy', + 'parent', + 'child', + 'formula', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ], + modifiable: true), + Emoji( + name: 'person bowing', + char: '\u{1F647}', + shortName: 'person_bowing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bow', + 'gesture', + 'sorry', + 'uc6', + 'diversity', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'begging', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ]), + Emoji( + name: 'person bowing: light skin tone', + char: '\u{1F647}\u{1F3FB}', + shortName: 'person_bowing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bow', + 'gesture', + 'light skin tone', + 'sorry', + 'uc8', + 'diversity', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'begging', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'person bowing: medium-light skin tone', + char: '\u{1F647}\u{1F3FC}', + shortName: 'person_bowing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bow', + 'gesture', + 'medium-light skin tone', + 'sorry', + 'uc8', + 'diversity', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'begging', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'person bowing: medium skin tone', + char: '\u{1F647}\u{1F3FD}', + shortName: 'person_bowing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bow', + 'gesture', + 'medium skin tone', + 'sorry', + 'uc8', + 'diversity', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'begging', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'person bowing: medium-dark skin tone', + char: '\u{1F647}\u{1F3FE}', + shortName: 'person_bowing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bow', + 'gesture', + 'medium-dark skin tone', + 'sorry', + 'uc8', + 'diversity', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'begging', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'person bowing: dark skin tone', + char: '\u{1F647}\u{1F3FF}', + shortName: 'person_bowing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bow', + 'dark skin tone', + 'gesture', + 'sorry', + 'uc8', + 'diversity', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'begging', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'woman bowing', + char: '\u{1F647}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_bowing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bowing', + 'favor', + 'gesture', + 'sorry', + 'woman', + 'uc6', + 'diversity', + 'women', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ]), + Emoji( + name: 'woman bowing: light skin tone', + char: '\u{1F647}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_bowing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bowing', + 'favor', + 'gesture', + 'light skin tone', + 'sorry', + 'woman', + 'uc8', + 'diversity', + 'women', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'woman bowing: medium-light skin tone', + char: '\u{1F647}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_bowing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bowing', + 'favor', + 'gesture', + 'medium-light skin tone', + 'sorry', + 'woman', + 'uc8', + 'diversity', + 'women', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'woman bowing: medium skin tone', + char: '\u{1F647}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_bowing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bowing', + 'favor', + 'gesture', + 'medium skin tone', + 'sorry', + 'woman', + 'uc8', + 'diversity', + 'women', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'woman bowing: medium-dark skin tone', + char: '\u{1F647}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_bowing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bowing', + 'favor', + 'gesture', + 'medium-dark skin tone', + 'sorry', + 'woman', + 'uc8', + 'diversity', + 'women', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'woman bowing: dark skin tone', + char: '\u{1F647}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_bowing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bowing', + 'dark skin tone', + 'favor', + 'gesture', + 'sorry', + 'woman', + 'uc8', + 'diversity', + 'women', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'man bowing', + char: '\u{1F647}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_bowing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bowing', + 'favor', + 'gesture', + 'man', + 'sorry', + 'uc6', + 'diversity', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ]), + Emoji( + name: 'man bowing: light skin tone', + char: '\u{1F647}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_bowing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bowing', + 'favor', + 'gesture', + 'light skin tone', + 'man', + 'sorry', + 'uc8', + 'diversity', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'man bowing: medium-light skin tone', + char: '\u{1F647}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_bowing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bowing', + 'favor', + 'gesture', + 'man', + 'medium-light skin tone', + 'sorry', + 'uc8', + 'diversity', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'man bowing: medium skin tone', + char: '\u{1F647}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_bowing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bowing', + 'favor', + 'gesture', + 'man', + 'medium skin tone', + 'sorry', + 'uc8', + 'diversity', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'man bowing: medium-dark skin tone', + char: '\u{1F647}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_bowing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bowing', + 'favor', + 'gesture', + 'man', + 'medium-dark skin tone', + 'sorry', + 'uc8', + 'diversity', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'man bowing: dark skin tone', + char: '\u{1F647}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_bowing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'apology', + 'bowing', + 'dark skin tone', + 'favor', + 'gesture', + 'man', + 'sorry', + 'uc8', + 'diversity', + 'thank you', + 'pray', + 'jesus', + 'yoga', + 'fame', + 'idea', + 'hope', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'famous', + 'celebrity', + 'swear', + 'promise' + ], + modifiable: true), + Emoji( + name: 'person tipping hand', + char: '\u{1F481}', + shortName: 'person_tipping_hand', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'hand', + 'help', + 'information', + 'sassy', + 'tipping', + 'uc6', + 'diversity', + 'men', + 'lipstick', + 'help', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'mouth', + 'mouths', + 'makeup', + 'lip', + 'lips', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person tipping hand: light skin tone', + char: '\u{1F481}\u{1F3FB}', + shortName: 'person_tipping_hand_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'hand', + 'help', + 'information', + 'light skin tone', + 'sassy', + 'tipping', + 'uc8', + 'diversity', + 'men', + 'lipstick', + 'help', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'mouth', + 'mouths', + 'makeup', + 'lip', + 'lips', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person tipping hand: medium-light skin tone', + char: '\u{1F481}\u{1F3FC}', + shortName: 'person_tipping_hand_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'hand', + 'help', + 'information', + 'medium-light skin tone', + 'sassy', + 'tipping', + 'uc8', + 'diversity', + 'men', + 'lipstick', + 'help', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'mouth', + 'mouths', + 'makeup', + 'lip', + 'lips', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person tipping hand: medium skin tone', + char: '\u{1F481}\u{1F3FD}', + shortName: 'person_tipping_hand_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'hand', + 'help', + 'information', + 'medium skin tone', + 'sassy', + 'tipping', + 'uc8', + 'diversity', + 'men', + 'lipstick', + 'help', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'mouth', + 'mouths', + 'makeup', + 'lip', + 'lips', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person tipping hand: medium-dark skin tone', + char: '\u{1F481}\u{1F3FE}', + shortName: 'person_tipping_hand_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'hand', + 'help', + 'information', + 'medium-dark skin tone', + 'sassy', + 'tipping', + 'uc8', + 'diversity', + 'men', + 'lipstick', + 'help', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'mouth', + 'mouths', + 'makeup', + 'lip', + 'lips', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person tipping hand: dark skin tone', + char: '\u{1F481}\u{1F3FF}', + shortName: 'person_tipping_hand_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'hand', + 'help', + 'information', + 'sassy', + 'tipping', + 'uc8', + 'diversity', + 'men', + 'lipstick', + 'help', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'mouth', + 'mouths', + 'makeup', + 'lip', + 'lips', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman tipping hand', + char: '\u{1F481}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_tipping_hand', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'sassy', + 'tipping hand', + 'woman', + 'uc6', + 'diversity', + 'women', + 'help', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman tipping hand: light skin tone', + char: '\u{1F481}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_tipping_hand_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'light skin tone', + 'sassy', + 'tipping hand', + 'woman', + 'uc8', + 'diversity', + 'women', + 'help', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman tipping hand: medium-light skin tone', + char: '\u{1F481}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_tipping_hand_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'medium-light skin tone', + 'sassy', + 'tipping hand', + 'woman', + 'uc8', + 'diversity', + 'women', + 'help', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman tipping hand: medium skin tone', + char: '\u{1F481}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_tipping_hand_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'medium skin tone', + 'sassy', + 'tipping hand', + 'woman', + 'uc8', + 'diversity', + 'women', + 'help', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman tipping hand: medium-dark skin tone', + char: '\u{1F481}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_tipping_hand_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'medium-dark skin tone', + 'sassy', + 'tipping hand', + 'woman', + 'uc8', + 'diversity', + 'women', + 'help', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman tipping hand: dark skin tone', + char: '\u{1F481}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_tipping_hand_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'sassy', + 'tipping hand', + 'woman', + 'uc8', + 'diversity', + 'women', + 'help', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man tipping hand', + char: '\u{1F481}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_tipping_hand', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'man', + 'sassy', + 'tipping hand', + 'uc6', + 'diversity', + 'men', + 'help', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ]), + Emoji( + name: 'man tipping hand: light skin tone', + char: '\u{1F481}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_tipping_hand_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'light skin tone', + 'man', + 'sassy', + 'tipping hand', + 'uc8', + 'diversity', + 'men', + 'help', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'man tipping hand: medium-light skin tone', + char: '\u{1F481}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_tipping_hand_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'man', + 'medium-light skin tone', + 'sassy', + 'tipping hand', + 'uc8', + 'diversity', + 'men', + 'help', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'man tipping hand: medium skin tone', + char: '\u{1F481}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_tipping_hand_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'man', + 'medium skin tone', + 'sassy', + 'tipping hand', + 'uc8', + 'diversity', + 'men', + 'help', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'man tipping hand: medium-dark skin tone', + char: '\u{1F481}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_tipping_hand_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'man', + 'medium-dark skin tone', + 'sassy', + 'tipping hand', + 'uc8', + 'diversity', + 'men', + 'help', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'man tipping hand: dark skin tone', + char: '\u{1F481}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_tipping_hand_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'man', + 'sassy', + 'tipping hand', + 'uc8', + 'diversity', + 'men', + 'help', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'person gesturing NO', + char: '\u{1F645}', + shortName: 'person_gesturing_no', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'no', + 'not', + 'prohibited', + 'uc6', + 'diversity', + 'men', + 'angry', + 'girls night', + 'hate', + 'danger', + 'bitch', + 'daddy', + 'crazy', + 'private', + 'mom', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'ladies night', + 'girls only', + 'girlfriend', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person gesturing NO: light skin tone', + char: '\u{1F645}\u{1F3FB}', + shortName: 'person_gesturing_no_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'light skin tone', + 'no', + 'not', + 'prohibited', + 'uc8', + 'diversity', + 'men', + 'angry', + 'girls night', + 'hate', + 'danger', + 'bitch', + 'daddy', + 'crazy', + 'private', + 'mom', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'ladies night', + 'girls only', + 'girlfriend', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person gesturing NO: medium-light skin tone', + char: '\u{1F645}\u{1F3FC}', + shortName: 'person_gesturing_no_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'medium-light skin tone', + 'no', + 'not', + 'prohibited', + 'uc8', + 'diversity', + 'men', + 'angry', + 'girls night', + 'hate', + 'danger', + 'bitch', + 'daddy', + 'crazy', + 'private', + 'mom', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'ladies night', + 'girls only', + 'girlfriend', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person gesturing NO: medium skin tone', + char: '\u{1F645}\u{1F3FD}', + shortName: 'person_gesturing_no_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'medium skin tone', + 'no', + 'not', + 'prohibited', + 'uc8', + 'diversity', + 'men', + 'angry', + 'girls night', + 'hate', + 'danger', + 'bitch', + 'daddy', + 'crazy', + 'private', + 'mom', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'ladies night', + 'girls only', + 'girlfriend', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person gesturing NO: medium-dark skin tone', + char: '\u{1F645}\u{1F3FE}', + shortName: 'person_gesturing_no_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'medium-dark skin tone', + 'no', + 'not', + 'prohibited', + 'uc8', + 'diversity', + 'men', + 'angry', + 'girls night', + 'hate', + 'danger', + 'bitch', + 'daddy', + 'crazy', + 'private', + 'mom', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'ladies night', + 'girls only', + 'girlfriend', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person gesturing NO: dark skin tone', + char: '\u{1F645}\u{1F3FF}', + shortName: 'person_gesturing_no_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'forbidden', + 'gesture', + 'hand', + 'no', + 'not', + 'prohibited', + 'uc8', + 'diversity', + 'men', + 'angry', + 'girls night', + 'hate', + 'danger', + 'bitch', + 'daddy', + 'crazy', + 'private', + 'mom', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'ladies night', + 'girls only', + 'girlfriend', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman gesturing NO', + char: '\u{1F645}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_gesturing_no', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'no', + 'prohibited', + 'woman', + 'uc6', + 'diversity', + 'women', + 'girls night', + 'danger', + 'bitch', + 'crazy', + 'private', + 'mom', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'puta', + 'pute', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman gesturing NO: light skin tone', + char: '\u{1F645}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_gesturing_no_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'light skin tone', + 'no', + 'prohibited', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'danger', + 'bitch', + 'crazy', + 'private', + 'mom', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'puta', + 'pute', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman gesturing NO: medium-light skin tone', + char: '\u{1F645}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_gesturing_no_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'medium-light skin tone', + 'no', + 'prohibited', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'danger', + 'bitch', + 'crazy', + 'private', + 'mom', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'puta', + 'pute', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman gesturing NO: medium skin tone', + char: '\u{1F645}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_gesturing_no_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'medium skin tone', + 'no', + 'prohibited', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'danger', + 'bitch', + 'crazy', + 'private', + 'mom', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'puta', + 'pute', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman gesturing NO: medium-dark skin tone', + char: '\u{1F645}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_gesturing_no_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'medium-dark skin tone', + 'no', + 'prohibited', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'danger', + 'bitch', + 'crazy', + 'private', + 'mom', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'puta', + 'pute', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman gesturing NO: dark skin tone', + char: '\u{1F645}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_gesturing_no_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'forbidden', + 'gesture', + 'hand', + 'no', + 'prohibited', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'danger', + 'bitch', + 'crazy', + 'private', + 'mom', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'puta', + 'pute', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man gesturing NO', + char: '\u{1F645}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_gesturing_no', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'man', + 'no', + 'prohibited', + 'uc6', + 'diversity', + 'men', + 'angry', + 'hate', + 'danger', + 'daddy', + 'crazy', + 'private', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ]), + Emoji( + name: 'man gesturing NO: light skin tone', + char: '\u{1F645}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_gesturing_no_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'light skin tone', + 'man', + 'no', + 'prohibited', + 'uc8', + 'diversity', + 'men', + 'angry', + 'hate', + 'danger', + 'daddy', + 'crazy', + 'private', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man gesturing NO: medium-light skin tone', + char: '\u{1F645}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_gesturing_no_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'man', + 'medium-light skin tone', + 'no', + 'prohibited', + 'uc8', + 'diversity', + 'men', + 'angry', + 'hate', + 'danger', + 'daddy', + 'crazy', + 'private', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man gesturing NO: medium skin tone', + char: '\u{1F645}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_gesturing_no_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'man', + 'medium skin tone', + 'no', + 'prohibited', + 'uc8', + 'diversity', + 'men', + 'angry', + 'hate', + 'danger', + 'daddy', + 'crazy', + 'private', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man gesturing NO: medium-dark skin tone', + char: '\u{1F645}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_gesturing_no_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'forbidden', + 'gesture', + 'hand', + 'man', + 'medium-dark skin tone', + 'no', + 'prohibited', + 'uc8', + 'diversity', + 'men', + 'angry', + 'hate', + 'danger', + 'daddy', + 'crazy', + 'private', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'man gesturing NO: dark skin tone', + char: '\u{1F645}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_gesturing_no_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'forbidden', + 'gesture', + 'hand', + 'man', + 'no', + 'prohibited', + 'uc8', + 'diversity', + 'men', + 'angry', + 'hate', + 'danger', + 'daddy', + 'crazy', + 'private', + 'never', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ], + modifiable: true), + Emoji( + name: 'person gesturing OK', + char: '\u{1F646}', + shortName: 'person_gesturing_ok', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'uc6', + 'diversity', + 'men', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild', + '*\\0/*', + '\\0/', + '*\\O/*', + '\\O/' + ]), + Emoji( + name: 'person gesturing OK: light skin tone', + char: '\u{1F646}\u{1F3FB}', + shortName: 'person_gesturing_ok_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'light skin tone', + 'uc8', + 'diversity', + 'men', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'person gesturing OK: medium-light skin tone', + char: '\u{1F646}\u{1F3FC}', + shortName: 'person_gesturing_ok_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'men', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'person gesturing OK: medium skin tone', + char: '\u{1F646}\u{1F3FD}', + shortName: 'person_gesturing_ok_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'medium skin tone', + 'uc8', + 'diversity', + 'men', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'person gesturing OK: medium-dark skin tone', + char: '\u{1F646}\u{1F3FE}', + shortName: 'person_gesturing_ok_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'men', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'person gesturing OK: dark skin tone', + char: '\u{1F646}\u{1F3FF}', + shortName: 'person_gesturing_ok_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'dark skin tone', + 'gesture', + 'hand', + 'uc8', + 'diversity', + 'men', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'woman gesturing OK', + char: '\u{1F646}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_gesturing_ok', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'woman', + 'uc6', + 'diversity', + 'women', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ]), + Emoji( + name: 'woman gesturing OK: light skin tone', + char: '\u{1F646}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_gesturing_ok_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'light skin tone', + 'woman', + 'uc8', + 'diversity', + 'women', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'woman gesturing OK: medium-light skin tone', + char: '\u{1F646}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_gesturing_ok_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'medium-light skin tone', + 'woman', + 'uc8', + 'diversity', + 'women', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'woman gesturing OK: medium skin tone', + char: '\u{1F646}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_gesturing_ok_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'medium skin tone', + 'woman', + 'uc8', + 'diversity', + 'women', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'woman gesturing OK: medium-dark skin tone', + char: '\u{1F646}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_gesturing_ok_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'medium-dark skin tone', + 'woman', + 'uc8', + 'diversity', + 'women', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'woman gesturing OK: dark skin tone', + char: '\u{1F646}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_gesturing_ok_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'dark skin tone', + 'gesture', + 'hand', + 'woman', + 'uc8', + 'diversity', + 'women', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man gesturing OK', + char: '\u{1F646}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_gesturing_ok', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'man', + 'uc6', + 'diversity', + 'men', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ]), + Emoji( + name: 'man gesturing OK: light skin tone', + char: '\u{1F646}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_gesturing_ok_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'light skin tone', + 'man', + 'uc8', + 'diversity', + 'men', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man gesturing OK: medium-light skin tone', + char: '\u{1F646}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_gesturing_ok_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'man', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'men', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man gesturing OK: medium skin tone', + char: '\u{1F646}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_gesturing_ok_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'man', + 'medium skin tone', + 'uc8', + 'diversity', + 'men', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man gesturing OK: medium-dark skin tone', + char: '\u{1F646}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_gesturing_ok_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'gesture', + 'hand', + 'man', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'men', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man gesturing OK: dark skin tone', + char: '\u{1F646}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_gesturing_ok_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'OK', + 'dark skin tone', + 'gesture', + 'hand', + 'man', + 'uc8', + 'diversity', + 'men', + 'thank you', + 'awesome', + 'yoga', + 'crazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'person raising hand', + char: '\u{1F64B}', + shortName: 'person_raising_hand', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'hand', + 'happy', + 'raised', + 'uc6', + 'diversity', + 'men', + 'hi', + 'girls night', + 'boys night', + 'celebrate', + 'daddy', + 'help', + 'crazy', + 'mom', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person raising hand: light skin tone', + char: '\u{1F64B}\u{1F3FB}', + shortName: 'person_raising_hand_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'hand', + 'happy', + 'light skin tone', + 'raised', + 'uc8', + 'diversity', + 'men', + 'hi', + 'girls night', + 'boys night', + 'celebrate', + 'daddy', + 'help', + 'crazy', + 'mom', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person raising hand: medium-light skin tone', + char: '\u{1F64B}\u{1F3FC}', + shortName: 'person_raising_hand_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'hand', + 'happy', + 'medium-light skin tone', + 'raised', + 'uc8', + 'diversity', + 'men', + 'hi', + 'girls night', + 'boys night', + 'celebrate', + 'daddy', + 'help', + 'crazy', + 'mom', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person raising hand: medium skin tone', + char: '\u{1F64B}\u{1F3FD}', + shortName: 'person_raising_hand_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'hand', + 'happy', + 'medium skin tone', + 'raised', + 'uc8', + 'diversity', + 'men', + 'hi', + 'girls night', + 'boys night', + 'celebrate', + 'daddy', + 'help', + 'crazy', + 'mom', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person raising hand: medium-dark skin tone', + char: '\u{1F64B}\u{1F3FE}', + shortName: 'person_raising_hand_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'hand', + 'happy', + 'medium-dark skin tone', + 'raised', + 'uc8', + 'diversity', + 'men', + 'hi', + 'girls night', + 'boys night', + 'celebrate', + 'daddy', + 'help', + 'crazy', + 'mom', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person raising hand: dark skin tone', + char: '\u{1F64B}\u{1F3FF}', + shortName: 'person_raising_hand_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'gesture', + 'hand', + 'happy', + 'raised', + 'uc8', + 'diversity', + 'men', + 'hi', + 'girls night', + 'boys night', + 'celebrate', + 'daddy', + 'help', + 'crazy', + 'mom', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman raising hand', + char: '\u{1F64B}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_raising_hand', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'raising hand', + 'woman', + 'uc6', + 'diversity', + 'women', + 'hi', + 'girls night', + 'celebrate', + 'help', + 'crazy', + 'mom', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'weird', + 'awkward', + 'insane', + 'wild', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman raising hand: light skin tone', + char: '\u{1F64B}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_raising_hand_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'light skin tone', + 'raising hand', + 'woman', + 'uc8', + 'diversity', + 'women', + 'hi', + 'girls night', + 'celebrate', + 'help', + 'crazy', + 'mom', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'weird', + 'awkward', + 'insane', + 'wild', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman raising hand: medium-light skin tone', + char: '\u{1F64B}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_raising_hand_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'medium-light skin tone', + 'raising hand', + 'woman', + 'uc8', + 'diversity', + 'women', + 'hi', + 'girls night', + 'celebrate', + 'help', + 'crazy', + 'mom', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'weird', + 'awkward', + 'insane', + 'wild', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman raising hand: medium skin tone', + char: '\u{1F64B}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_raising_hand_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'medium skin tone', + 'raising hand', + 'woman', + 'uc8', + 'diversity', + 'women', + 'hi', + 'girls night', + 'celebrate', + 'help', + 'crazy', + 'mom', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'weird', + 'awkward', + 'insane', + 'wild', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman raising hand: medium-dark skin tone', + char: '\u{1F64B}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_raising_hand_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'medium-dark skin tone', + 'raising hand', + 'woman', + 'uc8', + 'diversity', + 'women', + 'hi', + 'girls night', + 'celebrate', + 'help', + 'crazy', + 'mom', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'weird', + 'awkward', + 'insane', + 'wild', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman raising hand: dark skin tone', + char: '\u{1F64B}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_raising_hand_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'gesture', + 'raising hand', + 'woman', + 'uc8', + 'diversity', + 'women', + 'hi', + 'girls night', + 'celebrate', + 'help', + 'crazy', + 'mom', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'ladies night', + 'girls only', + 'girlfriend', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'weird', + 'awkward', + 'insane', + 'wild', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man raising hand', + char: '\u{1F64B}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_raising_hand', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'man', + 'raising hand', + 'uc6', + 'diversity', + 'men', + 'hi', + 'boys night', + 'celebrate', + 'daddy', + 'help', + 'crazy', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild' + ]), + Emoji( + name: 'man raising hand: light skin tone', + char: '\u{1F64B}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_raising_hand_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'light skin tone', + 'man', + 'raising hand', + 'uc8', + 'diversity', + 'men', + 'hi', + 'boys night', + 'celebrate', + 'daddy', + 'help', + 'crazy', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man raising hand: medium-light skin tone', + char: '\u{1F64B}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_raising_hand_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'man', + 'medium-light skin tone', + 'raising hand', + 'uc8', + 'diversity', + 'men', + 'hi', + 'boys night', + 'celebrate', + 'daddy', + 'help', + 'crazy', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man raising hand: medium skin tone', + char: '\u{1F64B}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_raising_hand_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'man', + 'medium skin tone', + 'raising hand', + 'uc8', + 'diversity', + 'men', + 'hi', + 'boys night', + 'celebrate', + 'daddy', + 'help', + 'crazy', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man raising hand: medium-dark skin tone', + char: '\u{1F64B}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_raising_hand_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'man', + 'medium-dark skin tone', + 'raising hand', + 'uc8', + 'diversity', + 'men', + 'hi', + 'boys night', + 'celebrate', + 'daddy', + 'help', + 'crazy', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man raising hand: dark skin tone', + char: '\u{1F64B}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_raising_hand_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'gesture', + 'man', + 'raising hand', + 'uc8', + 'diversity', + 'men', + 'hi', + 'boys night', + 'celebrate', + 'daddy', + 'help', + 'crazy', + 'proud', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'hello', + 'greeting', + 'bonjour', + 'bye', + 'ciao', + 'adios', + 'goodbye', + 'hey', + 'holla', + 'my name is', + 'salut', + 'welcome', + 'ПРИВЕĐĸ', + 'tu tapelle', + 'guys night', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'deaf person', + char: '\u{1F9CF}', + shortName: 'deaf_person', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ]), + Emoji( + name: 'deaf person: light skin tone', + char: '\u{1F9CF}\u{1F3FB}', + shortName: 'deaf_person_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf person: medium-light skin tone', + char: '\u{1F9CF}\u{1F3FC}', + shortName: 'deaf_person_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf person: medium skin tone', + char: '\u{1F9CF}\u{1F3FD}', + shortName: 'deaf_person_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf person: medium-dark skin tone', + char: '\u{1F9CF}\u{1F3FE}', + shortName: 'deaf_person_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf person: dark skin tone', + char: '\u{1F9CF}\u{1F3FF}', + shortName: 'deaf_person_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf woman', + char: '\u{1F9CF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'deaf_woman', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'women', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ]), + Emoji( + name: 'deaf woman: light skin tone', + char: '\u{1F9CF}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'deaf_woman_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'women', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf woman: medium-light skin tone', + char: '\u{1F9CF}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'deaf_woman_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'women', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf woman: medium skin tone', + char: '\u{1F9CF}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'deaf_woman_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'women', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf woman: medium-dark skin tone', + char: '\u{1F9CF}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'deaf_woman_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'women', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf woman: dark skin tone', + char: '\u{1F9CF}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'deaf_woman_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'women', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf man', + char: '\u{1F9CF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'deaf_man', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ]), + Emoji( + name: 'deaf man: light skin tone', + char: '\u{1F9CF}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'deaf_man_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf man: medium-light skin tone', + char: '\u{1F9CF}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'deaf_man_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf man: medium skin tone', + char: '\u{1F9CF}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'deaf_man_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf man: medium-dark skin tone', + char: '\u{1F9CF}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'deaf_man_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'deaf man: dark skin tone', + char: '\u{1F9CF}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'deaf_man_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'quiet', + 'sound', + 'deaf', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear', + 'hard of hearing' + ], + modifiable: true), + Emoji( + name: 'person facepalming', + char: '\u{1F926}', + shortName: 'person_facepalming', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'face', + 'palm', + 'uc9', + 'diversity', + 'men', + 'stressed', + 'boys night', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ]), + Emoji( + name: 'person facepalming: light skin tone', + char: '\u{1F926}\u{1F3FB}', + shortName: 'person_facepalming_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'face', + 'light skin tone', + 'palm', + 'uc9', + 'diversity', + 'men', + 'stressed', + 'boys night', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'person facepalming: medium-light skin tone', + char: '\u{1F926}\u{1F3FC}', + shortName: 'person_facepalming_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'face', + 'medium-light skin tone', + 'palm', + 'uc9', + 'diversity', + 'men', + 'stressed', + 'boys night', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'person facepalming: medium skin tone', + char: '\u{1F926}\u{1F3FD}', + shortName: 'person_facepalming_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'face', + 'medium skin tone', + 'palm', + 'uc9', + 'diversity', + 'men', + 'stressed', + 'boys night', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'person facepalming: medium-dark skin tone', + char: '\u{1F926}\u{1F3FE}', + shortName: 'person_facepalming_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'face', + 'medium-dark skin tone', + 'palm', + 'uc9', + 'diversity', + 'men', + 'stressed', + 'boys night', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'person facepalming: dark skin tone', + char: '\u{1F926}\u{1F3FF}', + shortName: 'person_facepalming_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'disbelief', + 'exasperation', + 'face', + 'palm', + 'uc9', + 'diversity', + 'men', + 'stressed', + 'boys night', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'woman facepalming', + char: '\u{1F926}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_facepalming', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'facepalm', + 'woman', + 'uc9', + 'diversity', + 'women', + 'stressed', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ]), + Emoji( + name: 'woman facepalming: light skin tone', + char: '\u{1F926}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_facepalming_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'facepalm', + 'light skin tone', + 'woman', + 'uc9', + 'diversity', + 'women', + 'stressed', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'woman facepalming: medium-light skin tone', + char: '\u{1F926}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_facepalming_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'facepalm', + 'medium-light skin tone', + 'woman', + 'uc9', + 'diversity', + 'women', + 'stressed', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'woman facepalming: medium skin tone', + char: '\u{1F926}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_facepalming_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'facepalm', + 'medium skin tone', + 'woman', + 'uc9', + 'diversity', + 'women', + 'stressed', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'woman facepalming: medium-dark skin tone', + char: '\u{1F926}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_facepalming_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'facepalm', + 'medium-dark skin tone', + 'woman', + 'uc9', + 'diversity', + 'women', + 'stressed', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'woman facepalming: dark skin tone', + char: '\u{1F926}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_facepalming_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'disbelief', + 'exasperation', + 'facepalm', + 'woman', + 'uc9', + 'diversity', + 'women', + 'stressed', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man facepalming', + char: '\u{1F926}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_facepalming', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'facepalm', + 'man', + 'uc9', + 'diversity', + 'men', + 'stressed', + 'boys night', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ]), + Emoji( + name: 'man facepalming: light skin tone', + char: '\u{1F926}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_facepalming_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'facepalm', + 'light skin tone', + 'man', + 'uc9', + 'diversity', + 'men', + 'stressed', + 'boys night', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man facepalming: medium-light skin tone', + char: '\u{1F926}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_facepalming_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'facepalm', + 'man', + 'medium-light skin tone', + 'uc9', + 'diversity', + 'men', + 'stressed', + 'boys night', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man facepalming: medium skin tone', + char: '\u{1F926}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_facepalming_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'facepalm', + 'man', + 'medium skin tone', + 'uc9', + 'diversity', + 'men', + 'stressed', + 'boys night', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man facepalming: medium-dark skin tone', + char: '\u{1F926}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_facepalming_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'disbelief', + 'exasperation', + 'facepalm', + 'man', + 'medium-dark skin tone', + 'uc9', + 'diversity', + 'men', + 'stressed', + 'boys night', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'man facepalming: dark skin tone', + char: '\u{1F926}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_facepalming_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'disbelief', + 'exasperation', + 'facepalm', + 'man', + 'uc9', + 'diversity', + 'men', + 'stressed', + 'boys night', + 'facepalm', + 'dumb', + 'las vegas', + 'crazy', + 'wrong', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'whoops', + 'oops', + 'mistake', + 'idiot', + 'ignorant', + 'stupid', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild' + ], + modifiable: true), + Emoji( + name: 'person shrugging', + char: '\u{1F937}', + shortName: 'person_shrugging', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'shrug', + 'uc9', + 'diversity', + 'men', + 'shrug', + 'neutral', + 'bitch', + 'daddy', + 'doubt', + 'dumb', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ]), + Emoji( + name: 'person shrugging: light skin tone', + char: '\u{1F937}\u{1F3FB}', + shortName: 'person_shrugging_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'light skin tone', + 'shrug', + 'uc9', + 'diversity', + 'men', + 'shrug', + 'neutral', + 'bitch', + 'daddy', + 'doubt', + 'dumb', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'person shrugging: medium-light skin tone', + char: '\u{1F937}\u{1F3FC}', + shortName: 'person_shrugging_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'medium-light skin tone', + 'shrug', + 'uc9', + 'diversity', + 'men', + 'shrug', + 'neutral', + 'bitch', + 'daddy', + 'doubt', + 'dumb', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'person shrugging: medium skin tone', + char: '\u{1F937}\u{1F3FD}', + shortName: 'person_shrugging_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'medium skin tone', + 'shrug', + 'uc9', + 'diversity', + 'men', + 'shrug', + 'neutral', + 'bitch', + 'daddy', + 'doubt', + 'dumb', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'person shrugging: medium-dark skin tone', + char: '\u{1F937}\u{1F3FE}', + shortName: 'person_shrugging_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'medium-dark skin tone', + 'shrug', + 'uc9', + 'diversity', + 'men', + 'shrug', + 'neutral', + 'bitch', + 'daddy', + 'doubt', + 'dumb', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'person shrugging: dark skin tone', + char: '\u{1F937}\u{1F3FF}', + shortName: 'person_shrugging_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'doubt', + 'ignorance', + 'indifference', + 'shrug', + 'uc9', + 'diversity', + 'men', + 'shrug', + 'neutral', + 'bitch', + 'daddy', + 'doubt', + 'dumb', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'woman shrugging', + char: '\u{1F937}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_shrugging', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'shrug', + 'woman', + 'uc9', + 'diversity', + 'women', + 'shrug', + 'neutral', + 'bitch', + 'doubt', + 'dumb', + 'guilty', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'puta', + 'pute', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ]), + Emoji( + name: 'woman shrugging: light skin tone', + char: '\u{1F937}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_shrugging_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'light skin tone', + 'shrug', + 'woman', + 'uc9', + 'diversity', + 'women', + 'shrug', + 'neutral', + 'bitch', + 'doubt', + 'dumb', + 'guilty', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'puta', + 'pute', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'woman shrugging: medium-light skin tone', + char: '\u{1F937}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_shrugging_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'medium-light skin tone', + 'shrug', + 'woman', + 'uc9', + 'diversity', + 'women', + 'shrug', + 'neutral', + 'bitch', + 'doubt', + 'dumb', + 'guilty', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'puta', + 'pute', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'woman shrugging: medium skin tone', + char: '\u{1F937}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_shrugging_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'medium skin tone', + 'shrug', + 'woman', + 'uc9', + 'diversity', + 'women', + 'shrug', + 'neutral', + 'bitch', + 'doubt', + 'dumb', + 'guilty', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'puta', + 'pute', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'woman shrugging: medium-dark skin tone', + char: '\u{1F937}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_shrugging_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'medium-dark skin tone', + 'shrug', + 'woman', + 'uc9', + 'diversity', + 'women', + 'shrug', + 'neutral', + 'bitch', + 'doubt', + 'dumb', + 'guilty', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'puta', + 'pute', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'woman shrugging: dark skin tone', + char: '\u{1F937}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_shrugging_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'doubt', + 'ignorance', + 'indifference', + 'shrug', + 'woman', + 'uc9', + 'diversity', + 'women', + 'shrug', + 'neutral', + 'bitch', + 'doubt', + 'dumb', + 'guilty', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'puta', + 'pute', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'man shrugging', + char: '\u{1F937}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_shrugging', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'man', + 'shrug', + 'uc9', + 'diversity', + 'men', + 'shrug', + 'neutral', + 'daddy', + 'doubt', + 'dumb', + 'guilty', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ]), + Emoji( + name: 'man shrugging: light skin tone', + char: '\u{1F937}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_shrugging_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'light skin tone', + 'man', + 'shrug', + 'uc9', + 'diversity', + 'men', + 'shrug', + 'neutral', + 'daddy', + 'doubt', + 'dumb', + 'guilty', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'man shrugging: medium-light skin tone', + char: '\u{1F937}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_shrugging_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'man', + 'medium-light skin tone', + 'shrug', + 'uc9', + 'diversity', + 'men', + 'shrug', + 'neutral', + 'daddy', + 'doubt', + 'dumb', + 'guilty', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'man shrugging: medium skin tone', + char: '\u{1F937}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_shrugging_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'man', + 'medium skin tone', + 'shrug', + 'uc9', + 'diversity', + 'men', + 'shrug', + 'neutral', + 'daddy', + 'doubt', + 'dumb', + 'guilty', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'man shrugging: medium-dark skin tone', + char: '\u{1F937}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_shrugging_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'doubt', + 'ignorance', + 'indifference', + 'man', + 'medium-dark skin tone', + 'shrug', + 'uc9', + 'diversity', + 'men', + 'shrug', + 'neutral', + 'daddy', + 'doubt', + 'dumb', + 'guilty', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'man shrugging: dark skin tone', + char: '\u{1F937}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_shrugging_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'doubt', + 'ignorance', + 'indifference', + 'man', + 'shrug', + 'uc9', + 'diversity', + 'men', + 'shrug', + 'neutral', + 'daddy', + 'doubt', + 'dumb', + 'guilty', + 'confused', + 'what', + 'crazy', + 'mystery', + 'question', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father', + 'unsure', + 'thinking', + 'wonder', + 'curious', + 'worry', + 'pensive', + 'remember', + 'skeptical', + 'idiot', + 'ignorant', + 'stupid', + 'perplexed', + 'weird', + 'awkward', + 'insane', + 'wild', + 'quiz', + 'puzzled' + ], + modifiable: true), + Emoji( + name: 'person pouting', + char: '\u{1F64E}', + shortName: 'person_pouting', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'pouting', + 'uc6', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'bitch', + 'daddy', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person pouting: light skin tone', + char: '\u{1F64E}\u{1F3FB}', + shortName: 'person_pouting_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'light skin tone', + 'pouting', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'bitch', + 'daddy', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person pouting: medium-light skin tone', + char: '\u{1F64E}\u{1F3FC}', + shortName: 'person_pouting_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'medium-light skin tone', + 'pouting', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'bitch', + 'daddy', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person pouting: medium skin tone', + char: '\u{1F64E}\u{1F3FD}', + shortName: 'person_pouting_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'medium skin tone', + 'pouting', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'bitch', + 'daddy', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person pouting: medium-dark skin tone', + char: '\u{1F64E}\u{1F3FE}', + shortName: 'person_pouting_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'medium-dark skin tone', + 'pouting', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'bitch', + 'daddy', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person pouting: dark skin tone', + char: '\u{1F64E}\u{1F3FF}', + shortName: 'person_pouting_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'gesture', + 'pouting', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'bitch', + 'daddy', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman pouting', + char: '\u{1F64E}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_pouting', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'pouting', + 'woman', + 'uc6', + 'diversity', + 'sad', + 'women', + 'stressed', + 'bitch', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'woman', + 'female', + 'puta', + 'pute', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman pouting: light skin tone', + char: '\u{1F64E}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_pouting_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'light skin tone', + 'pouting', + 'woman', + 'uc8', + 'diversity', + 'sad', + 'women', + 'stressed', + 'bitch', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'woman', + 'female', + 'puta', + 'pute', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman pouting: medium-light skin tone', + char: '\u{1F64E}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_pouting_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'medium-light skin tone', + 'pouting', + 'woman', + 'uc8', + 'diversity', + 'sad', + 'women', + 'stressed', + 'bitch', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'woman', + 'female', + 'puta', + 'pute', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman pouting: medium skin tone', + char: '\u{1F64E}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_pouting_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'medium skin tone', + 'pouting', + 'woman', + 'uc8', + 'diversity', + 'sad', + 'women', + 'stressed', + 'bitch', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'woman', + 'female', + 'puta', + 'pute', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman pouting: medium-dark skin tone', + char: '\u{1F64E}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_pouting_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'medium-dark skin tone', + 'pouting', + 'woman', + 'uc8', + 'diversity', + 'sad', + 'women', + 'stressed', + 'bitch', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'woman', + 'female', + 'puta', + 'pute', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman pouting: dark skin tone', + char: '\u{1F64E}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_pouting_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'gesture', + 'pouting', + 'woman', + 'uc8', + 'diversity', + 'sad', + 'women', + 'stressed', + 'bitch', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'woman', + 'female', + 'puta', + 'pute', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man pouting', + char: '\u{1F64E}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_pouting', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'man', + 'pouting', + 'uc6', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'daddy', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'dad', + 'papa', + 'pere', + 'father' + ]), + Emoji( + name: 'man pouting: light skin tone', + char: '\u{1F64E}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_pouting_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'light skin tone', + 'man', + 'pouting', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'daddy', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'man pouting: medium-light skin tone', + char: '\u{1F64E}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_pouting_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'man', + 'medium-light skin tone', + 'pouting', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'daddy', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'man pouting: medium skin tone', + char: '\u{1F64E}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_pouting_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'man', + 'medium skin tone', + 'pouting', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'daddy', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'man pouting: medium-dark skin tone', + char: '\u{1F64E}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_pouting_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'gesture', + 'man', + 'medium-dark skin tone', + 'pouting', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'daddy', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'man pouting: dark skin tone', + char: '\u{1F64E}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_pouting_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'gesture', + 'man', + 'pouting', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'daddy', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'person frowning', + char: '\u{1F64D}', + shortName: 'person_frowning', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frown', + 'gesture', + 'uc6', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'bitch', + 'daddy', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person frowning: light skin tone', + char: '\u{1F64D}\u{1F3FB}', + shortName: 'person_frowning_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frown', + 'gesture', + 'light skin tone', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'bitch', + 'daddy', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person frowning: medium-light skin tone', + char: '\u{1F64D}\u{1F3FC}', + shortName: 'person_frowning_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frown', + 'gesture', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'bitch', + 'daddy', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person frowning: medium skin tone', + char: '\u{1F64D}\u{1F3FD}', + shortName: 'person_frowning_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frown', + 'gesture', + 'medium skin tone', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'bitch', + 'daddy', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person frowning: medium-dark skin tone', + char: '\u{1F64D}\u{1F3FE}', + shortName: 'person_frowning_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frown', + 'gesture', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'bitch', + 'daddy', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person frowning: dark skin tone', + char: '\u{1F64D}\u{1F3FF}', + shortName: 'person_frowning_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'frown', + 'gesture', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'bitch', + 'daddy', + 'husband', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'puta', + 'pute', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman frowning', + char: '\u{1F64D}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_frowning', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frowning', + 'gesture', + 'woman', + 'uc6', + 'diversity', + 'sad', + 'women', + 'stressed', + 'bitch', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'woman', + 'female', + 'puta', + 'pute', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman frowning: light skin tone', + char: '\u{1F64D}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_frowning_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frowning', + 'gesture', + 'light skin tone', + 'woman', + 'uc8', + 'diversity', + 'sad', + 'women', + 'stressed', + 'bitch', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'woman', + 'female', + 'puta', + 'pute', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman frowning: medium-light skin tone', + char: '\u{1F64D}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_frowning_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frowning', + 'gesture', + 'medium-light skin tone', + 'woman', + 'uc8', + 'diversity', + 'sad', + 'women', + 'stressed', + 'bitch', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'woman', + 'female', + 'puta', + 'pute', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman frowning: medium skin tone', + char: '\u{1F64D}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_frowning_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frowning', + 'gesture', + 'medium skin tone', + 'woman', + 'uc8', + 'diversity', + 'sad', + 'women', + 'stressed', + 'bitch', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'woman', + 'female', + 'puta', + 'pute', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman frowning: medium-dark skin tone', + char: '\u{1F64D}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_frowning_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frowning', + 'gesture', + 'medium-dark skin tone', + 'woman', + 'uc8', + 'diversity', + 'sad', + 'women', + 'stressed', + 'bitch', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'woman', + 'female', + 'puta', + 'pute', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman frowning: dark skin tone', + char: '\u{1F64D}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_frowning_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'frowning', + 'gesture', + 'woman', + 'uc8', + 'diversity', + 'sad', + 'women', + 'stressed', + 'bitch', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'woman', + 'female', + 'puta', + 'pute', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man frowning', + char: '\u{1F64D}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_frowning', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frowning', + 'gesture', + 'man', + 'uc6', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'daddy', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'dad', + 'papa', + 'pere', + 'father' + ]), + Emoji( + name: 'man frowning: light skin tone', + char: '\u{1F64D}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_frowning_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frowning', + 'gesture', + 'light skin tone', + 'man', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'daddy', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'man frowning: medium-light skin tone', + char: '\u{1F64D}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_frowning_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frowning', + 'gesture', + 'man', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'daddy', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'man frowning: medium skin tone', + char: '\u{1F64D}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_frowning_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frowning', + 'gesture', + 'man', + 'medium skin tone', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'daddy', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'man frowning: medium-dark skin tone', + char: '\u{1F64D}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_frowning_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'frowning', + 'gesture', + 'man', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'daddy', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'man frowning: dark skin tone', + char: '\u{1F64D}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_frowning_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personGesture, + keywords: [ + 'dark skin tone', + 'frowning', + 'gesture', + 'man', + 'uc8', + 'diversity', + 'sad', + 'men', + 'angry', + 'stressed', + 'hate', + 'daddy', + 'husband', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'triste', + 'depression', + 'negative', + 'sadness', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'person getting haircut', + char: '\u{1F487}', + shortName: 'person_getting_haircut', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'barber', + 'beauty', + 'haircut', + 'parlor', + 'uc6', + 'diversity', + 'men', + 'daddy', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person getting haircut: light skin tone', + char: '\u{1F487}\u{1F3FB}', + shortName: 'person_getting_haircut_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'barber', + 'beauty', + 'haircut', + 'light skin tone', + 'parlor', + 'uc8', + 'diversity', + 'men', + 'daddy', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person getting haircut: medium-light skin tone', + char: '\u{1F487}\u{1F3FC}', + shortName: 'person_getting_haircut_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'barber', + 'beauty', + 'haircut', + 'medium-light skin tone', + 'parlor', + 'uc8', + 'diversity', + 'men', + 'daddy', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person getting haircut: medium skin tone', + char: '\u{1F487}\u{1F3FD}', + shortName: 'person_getting_haircut_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'barber', + 'beauty', + 'haircut', + 'medium skin tone', + 'parlor', + 'uc8', + 'diversity', + 'men', + 'daddy', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person getting haircut: medium-dark skin tone', + char: '\u{1F487}\u{1F3FE}', + shortName: 'person_getting_haircut_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'barber', + 'beauty', + 'haircut', + 'medium-dark skin tone', + 'parlor', + 'uc8', + 'diversity', + 'men', + 'daddy', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person getting haircut: dark skin tone', + char: '\u{1F487}\u{1F3FF}', + shortName: 'person_getting_haircut_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'barber', + 'beauty', + 'dark skin tone', + 'haircut', + 'parlor', + 'uc8', + 'diversity', + 'men', + 'daddy', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman getting haircut', + char: '\u{1F487}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_getting_haircut', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'haircut', + 'woman', + 'uc6', + 'diversity', + 'women', + 'girls night', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman getting haircut: light skin tone', + char: '\u{1F487}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_getting_haircut_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'haircut', + 'light skin tone', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman getting haircut: medium-light skin tone', + char: '\u{1F487}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_getting_haircut_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'haircut', + 'medium-light skin tone', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman getting haircut: medium skin tone', + char: '\u{1F487}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_getting_haircut_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'haircut', + 'medium skin tone', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman getting haircut: medium-dark skin tone', + char: '\u{1F487}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_getting_haircut_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'haircut', + 'medium-dark skin tone', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman getting haircut: dark skin tone', + char: '\u{1F487}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_getting_haircut_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'haircut', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man getting haircut', + char: '\u{1F487}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_getting_haircut', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'haircut', + 'man', + 'uc6', + 'diversity', + 'men', + 'daddy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father' + ]), + Emoji( + name: 'man getting haircut: light skin tone', + char: '\u{1F487}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_getting_haircut_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'haircut', + 'light skin tone', + 'man', + 'uc8', + 'diversity', + 'men', + 'daddy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'man getting haircut: medium-light skin tone', + char: '\u{1F487}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_getting_haircut_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'haircut', + 'man', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'men', + 'daddy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'man getting haircut: medium skin tone', + char: '\u{1F487}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_getting_haircut_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'haircut', + 'man', + 'medium skin tone', + 'uc8', + 'diversity', + 'men', + 'daddy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'man getting haircut: medium-dark skin tone', + char: '\u{1F487}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_getting_haircut_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'haircut', + 'man', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'men', + 'daddy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'man getting haircut: dark skin tone', + char: '\u{1F487}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_getting_haircut_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'haircut', + 'man', + 'uc8', + 'diversity', + 'men', + 'daddy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'dad', + 'papa', + 'pere', + 'father' + ], + modifiable: true), + Emoji( + name: 'person getting massage', + char: '\u{1F486}', + shortName: 'person_getting_massage', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'massage', + 'salon', + 'uc6', + 'diversity', + 'men', + 'girls night', + 'pleased', + 'yoga', + 'calm', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'ladies night', + 'girls only', + 'girlfriend', + 'please', + 'chill', + 'confident', + 'content', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person getting massage: light skin tone', + char: '\u{1F486}\u{1F3FB}', + shortName: 'person_getting_massage_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'light skin tone', + 'massage', + 'salon', + 'uc8', + 'diversity', + 'men', + 'girls night', + 'pleased', + 'yoga', + 'calm', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'ladies night', + 'girls only', + 'girlfriend', + 'please', + 'chill', + 'confident', + 'content', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person getting massage: medium-light skin tone', + char: '\u{1F486}\u{1F3FC}', + shortName: 'person_getting_massage_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'massage', + 'medium-light skin tone', + 'salon', + 'uc8', + 'diversity', + 'men', + 'girls night', + 'pleased', + 'yoga', + 'calm', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'ladies night', + 'girls only', + 'girlfriend', + 'please', + 'chill', + 'confident', + 'content', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person getting massage: medium skin tone', + char: '\u{1F486}\u{1F3FD}', + shortName: 'person_getting_massage_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'massage', + 'medium skin tone', + 'salon', + 'uc8', + 'diversity', + 'men', + 'girls night', + 'pleased', + 'yoga', + 'calm', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'ladies night', + 'girls only', + 'girlfriend', + 'please', + 'chill', + 'confident', + 'content', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person getting massage: medium-dark skin tone', + char: '\u{1F486}\u{1F3FE}', + shortName: 'person_getting_massage_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'massage', + 'medium-dark skin tone', + 'salon', + 'uc8', + 'diversity', + 'men', + 'girls night', + 'pleased', + 'yoga', + 'calm', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'ladies night', + 'girls only', + 'girlfriend', + 'please', + 'chill', + 'confident', + 'content', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person getting massage: dark skin tone', + char: '\u{1F486}\u{1F3FF}', + shortName: 'person_getting_massage_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'face', + 'massage', + 'salon', + 'uc8', + 'diversity', + 'men', + 'girls night', + 'pleased', + 'yoga', + 'calm', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'ladies night', + 'girls only', + 'girlfriend', + 'please', + 'chill', + 'confident', + 'content', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman getting massage', + char: '\u{1F486}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_getting_face_massage', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'massage', + 'woman', + 'uc6', + 'diversity', + 'women', + 'girls night', + 'yoga', + 'calm', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman getting massage: light skin tone', + char: '\u{1F486}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_getting_face_massage_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'light skin tone', + 'massage', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'yoga', + 'calm', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman getting massage: medium-light skin tone', + char: '\u{1F486}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_getting_face_massage_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'massage', + 'medium-light skin tone', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'yoga', + 'calm', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman getting massage: medium skin tone', + char: '\u{1F486}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_getting_face_massage_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'massage', + 'medium skin tone', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'yoga', + 'calm', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman getting massage: medium-dark skin tone', + char: '\u{1F486}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_getting_face_massage_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'massage', + 'medium-dark skin tone', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'yoga', + 'calm', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman getting massage: dark skin tone', + char: '\u{1F486}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_getting_face_massage_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'face', + 'massage', + 'woman', + 'uc8', + 'diversity', + 'women', + 'girls night', + 'yoga', + 'calm', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'ladies night', + 'girls only', + 'girlfriend', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man getting massage', + char: '\u{1F486}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_getting_face_massage', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'man', + 'massage', + 'uc6', + 'diversity', + 'men', + 'yoga', + 'calm', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna' + ]), + Emoji( + name: 'man getting massage: light skin tone', + char: '\u{1F486}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_getting_face_massage_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'light skin tone', + 'man', + 'massage', + 'uc8', + 'diversity', + 'men', + 'yoga', + 'calm', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'man getting massage: medium-light skin tone', + char: '\u{1F486}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_getting_face_massage_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'man', + 'massage', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'men', + 'yoga', + 'calm', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'man getting massage: medium skin tone', + char: '\u{1F486}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_getting_face_massage_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'man', + 'massage', + 'medium skin tone', + 'uc8', + 'diversity', + 'men', + 'yoga', + 'calm', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'man getting massage: medium-dark skin tone', + char: '\u{1F486}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_getting_face_massage_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'face', + 'man', + 'massage', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'men', + 'yoga', + 'calm', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'man getting massage: dark skin tone', + char: '\u{1F486}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_getting_face_massage_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'face', + 'man', + 'massage', + 'uc8', + 'diversity', + 'men', + 'yoga', + 'calm', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'person in steamy room', + char: '\u{1F9D6}', + shortName: 'person_in_steamy_room', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc10', + 'diversity', + 'hot', + 'steam', + 'girls night', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'ladies night', + 'girls only', + 'girlfriend', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person in steamy room: light skin tone', + char: '\u{1F9D6}\u{1F3FB}', + shortName: 'person_in_steamy_room_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'light skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'hot', + 'steam', + 'girls night', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'ladies night', + 'girls only', + 'girlfriend', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person in steamy room: medium-light skin tone', + char: '\u{1F9D6}\u{1F3FC}', + shortName: 'person_in_steamy_room_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'medium-light skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'hot', + 'steam', + 'girls night', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'ladies night', + 'girls only', + 'girlfriend', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person in steamy room: medium skin tone', + char: '\u{1F9D6}\u{1F3FD}', + shortName: 'person_in_steamy_room_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'medium skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'hot', + 'steam', + 'girls night', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'ladies night', + 'girls only', + 'girlfriend', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person in steamy room: medium-dark skin tone', + char: '\u{1F9D6}\u{1F3FE}', + shortName: 'person_in_steamy_room_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'medium-dark skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'hot', + 'steam', + 'girls night', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'ladies night', + 'girls only', + 'girlfriend', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person in steamy room: dark skin tone', + char: '\u{1F9D6}\u{1F3FF}', + shortName: 'person_in_steamy_room_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'hot', + 'steam', + 'girls night', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'ladies night', + 'girls only', + 'girlfriend', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman in steamy room', + char: '\u{1F9D6}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_steamy_room', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'women', + 'hot', + 'steam', + 'girls night', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'ladies night', + 'girls only', + 'girlfriend', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman in steamy room: light skin tone', + char: '\u{1F9D6}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_steamy_room_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'light skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'women', + 'hot', + 'steam', + 'girls night', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'ladies night', + 'girls only', + 'girlfriend', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman in steamy room: medium-light skin tone', + char: '\u{1F9D6}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_steamy_room_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'medium-light skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'women', + 'hot', + 'steam', + 'girls night', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'ladies night', + 'girls only', + 'girlfriend', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman in steamy room: medium skin tone', + char: '\u{1F9D6}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_steamy_room_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'medium skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'women', + 'hot', + 'steam', + 'girls night', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'ladies night', + 'girls only', + 'girlfriend', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman in steamy room: medium-dark skin tone', + char: '\u{1F9D6}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_steamy_room_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'medium-dark skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'women', + 'hot', + 'steam', + 'girls night', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'ladies night', + 'girls only', + 'girlfriend', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman in steamy room: dark skin tone', + char: '\u{1F9D6}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_steamy_room_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'women', + 'hot', + 'steam', + 'girls night', + 'spa', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'ladies night', + 'girls only', + 'girlfriend', + 'relax', + 'sauna', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man in steamy room', + char: '\u{1F9D6}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_steamy_room', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'hot', + 'steam', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'relax', + 'sauna' + ]), + Emoji( + name: 'man in steamy room: light skin tone', + char: '\u{1F9D6}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_steamy_room_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'light skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'hot', + 'steam', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'man in steamy room: medium-light skin tone', + char: '\u{1F9D6}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_steamy_room_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'medium-light skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'hot', + 'steam', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'man in steamy room: medium skin tone', + char: '\u{1F9D6}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_steamy_room_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'medium skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'hot', + 'steam', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'man in steamy room: medium-dark skin tone', + char: '\u{1F9D6}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_steamy_room_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'medium-dark skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'hot', + 'steam', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'man in steamy room: dark skin tone', + char: '\u{1F9D6}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_steamy_room_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'sauna', + 'steam room', + 'uc10', + 'diversity', + 'hot', + 'steam', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'steaming', + 'piping', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'nail polish', + char: '\u{1F485}', + shortName: 'nail_care', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'care', + 'cosmetics', + 'manicure', + 'nail', + 'polish', + 'uc6', + 'diversity', + 'women', + 'body', + 'hands', + 'nailpolish', + 'beautiful', + 'girls night', + 'painting', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'nails', + 'fingernails', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'painter', + 'arts', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'nail polish: light skin tone', + char: '\u{1F485}\u{1F3FB}', + shortName: 'nail_care_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'care', + 'cosmetics', + 'light skin tone', + 'manicure', + 'nail', + 'polish', + 'uc8', + 'diversity', + 'women', + 'body', + 'hands', + 'nailpolish', + 'beautiful', + 'girls night', + 'painting', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'nails', + 'fingernails', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'painter', + 'arts', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'nail polish: medium-light skin tone', + char: '\u{1F485}\u{1F3FC}', + shortName: 'nail_care_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'care', + 'cosmetics', + 'manicure', + 'medium-light skin tone', + 'nail', + 'polish', + 'uc8', + 'diversity', + 'women', + 'body', + 'hands', + 'nailpolish', + 'beautiful', + 'girls night', + 'painting', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'nails', + 'fingernails', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'painter', + 'arts', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'nail polish: medium skin tone', + char: '\u{1F485}\u{1F3FD}', + shortName: 'nail_care_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'care', + 'cosmetics', + 'manicure', + 'medium skin tone', + 'nail', + 'polish', + 'uc8', + 'diversity', + 'women', + 'body', + 'hands', + 'nailpolish', + 'beautiful', + 'girls night', + 'painting', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'nails', + 'fingernails', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'painter', + 'arts', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'nail polish: medium-dark skin tone', + char: '\u{1F485}\u{1F3FE}', + shortName: 'nail_care_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'care', + 'cosmetics', + 'manicure', + 'medium-dark skin tone', + 'nail', + 'polish', + 'uc8', + 'diversity', + 'women', + 'body', + 'hands', + 'nailpolish', + 'beautiful', + 'girls night', + 'painting', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'nails', + 'fingernails', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'painter', + 'arts', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'nail polish: dark skin tone', + char: '\u{1F485}\u{1F3FF}', + shortName: 'nail_care_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'care', + 'cosmetics', + 'dark skin tone', + 'manicure', + 'nail', + 'polish', + 'uc8', + 'diversity', + 'women', + 'body', + 'hands', + 'nailpolish', + 'beautiful', + 'girls night', + 'painting', + 'mom', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'body part', + 'anatomy', + 'hand', + 'finger', + 'fingers', + 'nails', + 'fingernails', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'painter', + 'arts', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'selfie', + char: '\u{1F933}', + shortName: 'selfie', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'camera', + 'phone', + 'selfie', + 'uc9', + 'diversity', + 'selfie', + 'fame', + 'instagram', + 'fun', + 'youtube', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'famous', + 'celebrity', + 'vlog' + ]), + Emoji( + name: 'selfie: light skin tone', + char: '\u{1F933}\u{1F3FB}', + shortName: 'selfie_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'camera', + 'light skin tone', + 'phone', + 'selfie', + 'uc9', + 'diversity', + 'selfie', + 'fame', + 'instagram', + 'fun', + 'youtube', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'famous', + 'celebrity', + 'vlog' + ], + modifiable: true), + Emoji( + name: 'selfie: medium-light skin tone', + char: '\u{1F933}\u{1F3FC}', + shortName: 'selfie_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'camera', + 'medium-light skin tone', + 'phone', + 'selfie', + 'uc9', + 'diversity', + 'selfie', + 'fame', + 'instagram', + 'fun', + 'youtube', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'famous', + 'celebrity', + 'vlog' + ], + modifiable: true), + Emoji( + name: 'selfie: medium skin tone', + char: '\u{1F933}\u{1F3FD}', + shortName: 'selfie_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'camera', + 'medium skin tone', + 'phone', + 'selfie', + 'uc9', + 'diversity', + 'selfie', + 'fame', + 'instagram', + 'fun', + 'youtube', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'famous', + 'celebrity', + 'vlog' + ], + modifiable: true), + Emoji( + name: 'selfie: medium-dark skin tone', + char: '\u{1F933}\u{1F3FE}', + shortName: 'selfie_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'camera', + 'medium-dark skin tone', + 'phone', + 'selfie', + 'uc9', + 'diversity', + 'selfie', + 'fame', + 'instagram', + 'fun', + 'youtube', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'famous', + 'celebrity', + 'vlog' + ], + modifiable: true), + Emoji( + name: 'selfie: dark skin tone', + char: '\u{1F933}\u{1F3FF}', + shortName: 'selfie_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.handProp, + keywords: [ + 'camera', + 'dark skin tone', + 'phone', + 'selfie', + 'uc9', + 'diversity', + 'selfie', + 'fame', + 'instagram', + 'fun', + 'youtube', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'famous', + 'celebrity', + 'vlog' + ], + modifiable: true), + Emoji( + name: 'woman dancing', + char: '\u{1F483}', + shortName: 'dancer', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dancing', + 'woman', + 'uc6', + 'instruments', + 'diversity', + 'women', + 'mexican', + 'sexy', + 'circus', + 'beautiful', + 'girls night', + 'dance', + 'hawaii', + 'celebrate', + 'disco', + 'las vegas', + 'fun', + 'activity', + 'dress', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'mexico', + 'cinco de mayo', + 'espaÃąol', + 'circus tent', + 'clown', + 'clowns', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'vegas' + ]), + Emoji( + name: 'woman dancing: light skin tone', + char: '\u{1F483}\u{1F3FB}', + shortName: 'dancer_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dancing', + 'light skin tone', + 'woman', + 'uc8', + 'instruments', + 'diversity', + 'women', + 'mexican', + 'sexy', + 'circus', + 'beautiful', + 'girls night', + 'dance', + 'hawaii', + 'celebrate', + 'disco', + 'las vegas', + 'fun', + 'activity', + 'dress', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'mexico', + 'cinco de mayo', + 'espaÃąol', + 'circus tent', + 'clown', + 'clowns', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'woman dancing: medium-light skin tone', + char: '\u{1F483}\u{1F3FC}', + shortName: 'dancer_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dancing', + 'medium-light skin tone', + 'woman', + 'uc8', + 'instruments', + 'diversity', + 'women', + 'mexican', + 'sexy', + 'circus', + 'beautiful', + 'girls night', + 'dance', + 'hawaii', + 'celebrate', + 'disco', + 'las vegas', + 'fun', + 'activity', + 'dress', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'mexico', + 'cinco de mayo', + 'espaÃąol', + 'circus tent', + 'clown', + 'clowns', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'woman dancing: medium skin tone', + char: '\u{1F483}\u{1F3FD}', + shortName: 'dancer_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dancing', + 'medium skin tone', + 'woman', + 'uc8', + 'instruments', + 'diversity', + 'women', + 'mexican', + 'sexy', + 'circus', + 'beautiful', + 'girls night', + 'dance', + 'hawaii', + 'celebrate', + 'disco', + 'las vegas', + 'fun', + 'activity', + 'dress', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'mexico', + 'cinco de mayo', + 'espaÃąol', + 'circus tent', + 'clown', + 'clowns', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'woman dancing: medium-dark skin tone', + char: '\u{1F483}\u{1F3FE}', + shortName: 'dancer_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dancing', + 'medium-dark skin tone', + 'woman', + 'uc8', + 'instruments', + 'diversity', + 'women', + 'mexican', + 'sexy', + 'circus', + 'beautiful', + 'girls night', + 'dance', + 'hawaii', + 'celebrate', + 'disco', + 'las vegas', + 'fun', + 'activity', + 'dress', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'mexico', + 'cinco de mayo', + 'espaÃąol', + 'circus tent', + 'clown', + 'clowns', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'woman dancing: dark skin tone', + char: '\u{1F483}\u{1F3FF}', + shortName: 'dancer_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dancing', + 'dark skin tone', + 'woman', + 'uc8', + 'instruments', + 'diversity', + 'women', + 'mexican', + 'sexy', + 'circus', + 'beautiful', + 'girls night', + 'dance', + 'hawaii', + 'celebrate', + 'disco', + 'las vegas', + 'fun', + 'activity', + 'dress', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'mexico', + 'cinco de mayo', + 'espaÃąol', + 'circus tent', + 'clown', + 'clowns', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'man dancing', + char: '\u{1F57A}', + shortName: 'man_dancing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dance', + 'man', + 'uc9', + 'instruments', + 'diversity', + 'men', + 'boys night', + 'dance', + 'celebrate', + 'disco', + 'daddy', + 'las vegas', + 'fun', + 'activity', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'vegas' + ]), + Emoji( + name: 'man dancing: light skin tone', + char: '\u{1F57A}\u{1F3FB}', + shortName: 'man_dancing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dance', + 'light skin tone', + 'man', + 'uc9', + 'instruments', + 'diversity', + 'men', + 'boys night', + 'dance', + 'celebrate', + 'disco', + 'daddy', + 'las vegas', + 'fun', + 'activity', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'man dancing: medium-light skin tone', + char: '\u{1F57A}\u{1F3FC}', + shortName: 'man_dancing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dance', + 'man', + 'medium-light skin tone', + 'uc9', + 'instruments', + 'diversity', + 'men', + 'boys night', + 'dance', + 'celebrate', + 'disco', + 'daddy', + 'las vegas', + 'fun', + 'activity', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'man dancing: medium skin tone', + char: '\u{1F57A}\u{1F3FD}', + shortName: 'man_dancing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dance', + 'man', + 'medium skin tone', + 'uc9', + 'instruments', + 'diversity', + 'men', + 'boys night', + 'dance', + 'celebrate', + 'disco', + 'daddy', + 'las vegas', + 'fun', + 'activity', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'man dancing: dark skin tone', + char: '\u{1F57A}\u{1F3FF}', + shortName: 'man_dancing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dance', + 'dark skin tone', + 'man', + 'uc9', + 'instruments', + 'diversity', + 'men', + 'boys night', + 'dance', + 'celebrate', + 'disco', + 'daddy', + 'las vegas', + 'fun', + 'activity', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'man dancing: medium-dark skin tone', + char: '\u{1F57A}\u{1F3FE}', + shortName: 'man_dancing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dance', + 'man', + 'medium-dark skin tone', + 'uc9', + 'instruments', + 'diversity', + 'men', + 'boys night', + 'dance', + 'celebrate', + 'disco', + 'daddy', + 'las vegas', + 'fun', + 'activity', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'dad', + 'papa', + 'pere', + 'father', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'people with bunny ears', + char: '\u{1F46F}', + shortName: 'people_with_bunny_ears_partying', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'bunny ear', + 'dancer', + 'partying', + 'uc6', + 'instruments', + 'silly', + 'halloween', + 'men', + 'japan', + 'sexy', + 'girls night', + 'boys night', + 'dance', + 'easter', + 'porn', + 'las vegas', + 'fun', + 'crazy', + 'playboy', + 'activity', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'funny', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'japanese', + 'ninja', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild', + 'play boy' + ]), + Emoji( + name: 'women with bunny ears', + char: '\u{1F46F}\u{200D}\u{2640}\u{FE0F}', + shortName: 'women_with_bunny_ears_partying', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'bunny ear', + 'dancer', + 'partying', + 'women', + 'uc6', + 'instruments', + 'silly', + 'women', + 'halloween', + 'japan', + 'girls night', + 'boys night', + 'dance', + 'easter', + 'las vegas', + 'fun', + 'crazy', + 'playboy', + 'activity', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'funny', + 'woman', + 'female', + 'samhain', + 'japanese', + 'ninja', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild', + 'play boy' + ]), + Emoji( + name: 'men with bunny ears', + char: '\u{1F46F}\u{200D}\u{2642}\u{FE0F}', + shortName: 'men_with_bunny_ears_partying', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'bunny ear', + 'dancer', + 'men', + 'partying', + 'uc6', + 'instruments', + 'silly', + 'halloween', + 'men', + 'japan', + 'girls night', + 'boys night', + 'dance', + 'queen', + 'easter', + 'las vegas', + 'fun', + 'crazy', + 'playboy', + 'activity', + 'disguise', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'funny', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'japanese', + 'ninja', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa', + 'king', + 'prince', + 'princess', + 'vegas', + 'weird', + 'awkward', + 'insane', + 'wild', + 'play boy' + ]), + Emoji( + name: 'person in suit levitating', + char: '\u{1F574}\u{FE0F}', + shortName: 'levitate', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'business', + 'man', + 'suit', + 'uc7', + 'halloween', + 'men', + 'job', + 'business', + 'sunglasses', + 'google', + 'detective', + 'fame', + 'gangster', + 'super hero', + 'vampire', + 'las vegas', + 'mystery', + 'disguise', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'shades', + 'lunettes de soleil', + 'sun glasses', + 'famous', + 'celebrity', + 'thug', + 'superhero', + 'superman', + 'batman', + 'dracula', + 'vegas' + ]), + Emoji( + name: 'person in suit levitating: light skin tone', + char: '\u{1F574}\u{1F3FB}', + shortName: 'levitate_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'business', + 'light skin tone', + 'man', + 'suit', + 'uc8', + 'halloween', + 'men', + 'job', + 'business', + 'sunglasses', + 'google', + 'detective', + 'fame', + 'gangster', + 'super hero', + 'vampire', + 'las vegas', + 'mystery', + 'disguise', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'shades', + 'lunettes de soleil', + 'sun glasses', + 'famous', + 'celebrity', + 'thug', + 'superhero', + 'superman', + 'batman', + 'dracula', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'person in suit levitating: medium-light skin tone', + char: '\u{1F574}\u{1F3FC}', + shortName: 'levitate_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'business', + 'man', + 'medium-light skin tone', + 'suit', + 'uc8', + 'halloween', + 'men', + 'job', + 'business', + 'sunglasses', + 'google', + 'detective', + 'fame', + 'gangster', + 'super hero', + 'vampire', + 'las vegas', + 'mystery', + 'disguise', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'shades', + 'lunettes de soleil', + 'sun glasses', + 'famous', + 'celebrity', + 'thug', + 'superhero', + 'superman', + 'batman', + 'dracula', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'person in suit levitating: medium skin tone', + char: '\u{1F574}\u{1F3FD}', + shortName: 'levitate_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'business', + 'man', + 'medium skin tone', + 'suit', + 'uc8', + 'halloween', + 'men', + 'job', + 'business', + 'sunglasses', + 'google', + 'detective', + 'fame', + 'gangster', + 'super hero', + 'vampire', + 'las vegas', + 'mystery', + 'disguise', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'shades', + 'lunettes de soleil', + 'sun glasses', + 'famous', + 'celebrity', + 'thug', + 'superhero', + 'superman', + 'batman', + 'dracula', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'person in suit levitating: medium-dark skin tone', + char: '\u{1F574}\u{1F3FE}', + shortName: 'levitate_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'business', + 'man', + 'medium-dark skin tone', + 'suit', + 'uc8', + 'halloween', + 'men', + 'job', + 'business', + 'sunglasses', + 'google', + 'detective', + 'fame', + 'gangster', + 'super hero', + 'vampire', + 'las vegas', + 'mystery', + 'disguise', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'shades', + 'lunettes de soleil', + 'sun glasses', + 'famous', + 'celebrity', + 'thug', + 'superhero', + 'superman', + 'batman', + 'dracula', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'person in suit levitating: dark skin tone', + char: '\u{1F574}\u{1F3FF}', + shortName: 'levitate_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'business', + 'dark skin tone', + 'man', + 'suit', + 'uc8', + 'halloween', + 'men', + 'job', + 'business', + 'sunglasses', + 'google', + 'detective', + 'fame', + 'gangster', + 'super hero', + 'vampire', + 'las vegas', + 'mystery', + 'disguise', + 'samhain', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'profession', + 'boss', + 'career', + 'shades', + 'lunettes de soleil', + 'sun glasses', + 'famous', + 'celebrity', + 'thug', + 'superhero', + 'superman', + 'batman', + 'dracula', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'person in manual wheelchair', + char: '\u{1F9D1}\u{200D}\u{1F9BD}', + shortName: 'person_in_manual_wheelchair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'disabled', + 'disability' + ]), + Emoji( + name: 'person in manual wheelchair: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F9BD}', + shortName: 'person_in_manual_wheelchair_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'person in manual wheelchair: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F9BD}', + shortName: 'person_in_manual_wheelchair_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'person in manual wheelchair: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F9BD}', + shortName: 'person_in_manual_wheelchair_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'person in manual wheelchair: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F9BD}', + shortName: 'person_in_manual_wheelchair_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'person in manual wheelchair: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F9BD}', + shortName: 'person_in_manual_wheelchair_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'woman in manual wheelchair', + char: '\u{1F469}\u{200D}\u{1F9BD}', + shortName: 'woman_in_manual_wheelchair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ]), + Emoji( + name: 'woman in manual wheelchair: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F9BD}', + shortName: 'woman_in_manual_wheelchair_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'woman in manual wheelchair: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F9BD}', + shortName: 'woman_in_manual_wheelchair_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'woman in manual wheelchair: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F9BD}', + shortName: 'woman_in_manual_wheelchair_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'woman in manual wheelchair: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F9BD}', + shortName: 'woman_in_manual_wheelchair_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'woman in manual wheelchair: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F9BD}', + shortName: 'woman_in_manual_wheelchair_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'man in manual wheelchair', + char: '\u{1F468}\u{200D}\u{1F9BD}', + shortName: 'man_in_manual_wheelchair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ]), + Emoji( + name: 'man in manual wheelchair: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F9BD}', + shortName: 'man_in_manual_wheelchair_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'man in manual wheelchair: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F9BD}', + shortName: 'man_in_manual_wheelchair_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'man in manual wheelchair: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F9BD}', + shortName: 'man_in_manual_wheelchair_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'man in manual wheelchair: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F9BD}', + shortName: 'man_in_manual_wheelchair_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'man in manual wheelchair: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F9BD}', + shortName: 'man_in_manual_wheelchair_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'person in motorized wheelchair', + char: '\u{1F9D1}\u{200D}\u{1F9BC}', + shortName: 'person_in_motorized_wheelchair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'disabled', + 'disability' + ]), + Emoji( + name: 'person in motorized wheelchair: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F9BC}', + shortName: 'person_in_motorized_wheelchair_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'person in motorized wheelchair: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F9BC}', + shortName: 'person_in_motorized_wheelchair_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'person in motorized wheelchair: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F9BC}', + shortName: 'person_in_motorized_wheelchair_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'person in motorized wheelchair: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F9BC}', + shortName: 'person_in_motorized_wheelchair_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'person in motorized wheelchair: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F9BC}', + shortName: 'person_in_motorized_wheelchair_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'woman in motorized wheelchair', + char: '\u{1F469}\u{200D}\u{1F9BC}', + shortName: 'woman_in_motorized_wheelchair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ]), + Emoji( + name: 'woman in motorized wheelchair: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F9BC}', + shortName: 'woman_in_motorized_wheelchair_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'woman in motorized wheelchair: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F9BC}', + shortName: 'woman_in_motorized_wheelchair_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'woman in motorized wheelchair: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F9BC}', + shortName: 'woman_in_motorized_wheelchair_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'woman in motorized wheelchair: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F9BC}', + shortName: 'woman_in_motorized_wheelchair_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'woman in motorized wheelchair: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F9BC}', + shortName: 'woman_in_motorized_wheelchair_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'man in motorized wheelchair', + char: '\u{1F468}\u{200D}\u{1F9BC}', + shortName: 'man_in_motorized_wheelchair', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ]), + Emoji( + name: 'man in motorized wheelchair: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F9BC}', + shortName: 'man_in_motorized_wheelchair_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'man in motorized wheelchair: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F9BC}', + shortName: 'man_in_motorized_wheelchair_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'man in motorized wheelchair: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F9BC}', + shortName: 'man_in_motorized_wheelchair_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'man in motorized wheelchair: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F9BC}', + shortName: 'man_in_motorized_wheelchair_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'man in motorized wheelchair: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F9BC}', + shortName: 'man_in_motorized_wheelchair_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'old people', + 'diversity', + 'handicap', + 'accessibility', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability' + ], + modifiable: true), + Emoji( + name: 'person walking', + char: '\u{1F6B6}', + shortName: 'person_walking', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'walk', + 'walking', + 'uc6', + 'sport', + 'diversity', + 'men', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ]), + Emoji( + name: 'person walking: light skin tone', + char: '\u{1F6B6}\u{1F3FB}', + shortName: 'person_walking_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'light skin tone', + 'walk', + 'walking', + 'uc8', + 'sport', + 'diversity', + 'men', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'person walking: medium-light skin tone', + char: '\u{1F6B6}\u{1F3FC}', + shortName: 'person_walking_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'medium-light skin tone', + 'walk', + 'walking', + 'uc8', + 'sport', + 'diversity', + 'men', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'person walking: medium skin tone', + char: '\u{1F6B6}\u{1F3FD}', + shortName: 'person_walking_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'medium skin tone', + 'walk', + 'walking', + 'uc8', + 'sport', + 'diversity', + 'men', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'person walking: medium-dark skin tone', + char: '\u{1F6B6}\u{1F3FE}', + shortName: 'person_walking_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'medium-dark skin tone', + 'walk', + 'walking', + 'uc8', + 'sport', + 'diversity', + 'men', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'person walking: dark skin tone', + char: '\u{1F6B6}\u{1F3FF}', + shortName: 'person_walking_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'hike', + 'walk', + 'walking', + 'uc8', + 'sport', + 'diversity', + 'men', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'woman walking', + char: '\u{1F6B6}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_walking', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'walk', + 'woman', + 'uc6', + 'sport', + 'diversity', + 'women', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female' + ]), + Emoji( + name: 'woman walking: light skin tone', + char: '\u{1F6B6}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_walking_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'light skin tone', + 'walk', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'women', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female' + ], + modifiable: true), + Emoji( + name: 'woman walking: medium-light skin tone', + char: '\u{1F6B6}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_walking_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'medium-light skin tone', + 'walk', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'women', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female' + ], + modifiable: true), + Emoji( + name: 'woman walking: medium skin tone', + char: '\u{1F6B6}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_walking_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'medium skin tone', + 'walk', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'women', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female' + ], + modifiable: true), + Emoji( + name: 'woman walking: medium-dark skin tone', + char: '\u{1F6B6}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_walking_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'medium-dark skin tone', + 'walk', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'women', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female' + ], + modifiable: true), + Emoji( + name: 'woman walking: dark skin tone', + char: '\u{1F6B6}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_walking_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'hike', + 'walk', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'women', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female' + ], + modifiable: true), + Emoji( + name: 'man walking', + char: '\u{1F6B6}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_walking', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'man', + 'walk', + 'uc6', + 'sport', + 'diversity', + 'men', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ]), + Emoji( + name: 'man walking: light skin tone', + char: '\u{1F6B6}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_walking_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'light skin tone', + 'man', + 'walk', + 'uc8', + 'sport', + 'diversity', + 'men', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'man walking: medium-light skin tone', + char: '\u{1F6B6}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_walking_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'man', + 'medium-light skin tone', + 'walk', + 'uc8', + 'sport', + 'diversity', + 'men', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'man walking: medium skin tone', + char: '\u{1F6B6}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_walking_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'man', + 'medium skin tone', + 'walk', + 'uc8', + 'sport', + 'diversity', + 'men', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'man walking: medium-dark skin tone', + char: '\u{1F6B6}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_walking_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'hike', + 'man', + 'medium-dark skin tone', + 'walk', + 'uc8', + 'sport', + 'diversity', + 'men', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'man walking: dark skin tone', + char: '\u{1F6B6}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_walking_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'hike', + 'man', + 'walk', + 'uc8', + 'sport', + 'diversity', + 'men', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ], + modifiable: true), + Emoji( + name: 'person with white cane: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F9AF}', + shortName: 'person_with_probing_cane_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'accessibility', + 'cane', + 'handicap', + 'blind', + 'probe', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'person with white cane', + char: '\u{1F9D1}\u{200D}\u{1F9AF}', + shortName: 'person_with_probing_cane', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'cane', + 'handicap', + 'blind', + 'probe', + 'disabled', + 'disability', + 'white cane' + ]), + Emoji( + name: 'person with white cane: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F9AF}', + shortName: 'person_with_probing_cane_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'cane', + 'handicap', + 'blind', + 'probe', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'person with white cane: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F9AF}', + shortName: 'person_with_probing_cane_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'cane', + 'handicap', + 'blind', + 'probe', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'person with white cane: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F9AF}', + shortName: 'person_with_probing_cane_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'cane', + 'handicap', + 'blind', + 'probe', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'person with white cane: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F9AF}', + shortName: 'person_with_probing_cane_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'cane', + 'handicap', + 'blind', + 'probe', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'woman with white cane', + char: '\u{1F469}\u{200D}\u{1F9AF}', + shortName: 'woman_with_probing_cane', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'white cane' + ]), + Emoji( + name: 'woman with white cane: light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F9AF}', + shortName: 'woman_with_probing_cane_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'cane', + 'diversity', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'woman with white cane: medium-light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F9AF}', + shortName: 'woman_with_probing_cane_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'woman with white cane: medium skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F9AF}', + shortName: 'woman_with_probing_cane_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'woman with white cane: medium-dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F9AF}', + shortName: 'woman_with_probing_cane_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'woman with white cane: dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F9AF}', + shortName: 'woman_with_probing_cane_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'man with white cane', + char: '\u{1F468}\u{200D}\u{1F9AF}', + shortName: 'man_with_probing_cane', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'cane', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'white cane' + ]), + Emoji( + name: 'man with white cane: light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F9AF}', + shortName: 'man_with_probing_cane_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'cane', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'man with white cane: medium skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F9AF}', + shortName: 'man_with_probing_cane_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'cane', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'man with white cane: medium-light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F9AF}', + shortName: 'man_with_probing_cane_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'cane', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'man with white cane: medium-dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F9AF}', + shortName: 'man_with_probing_cane_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'cane', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'man with white cane: dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F9AF}', + shortName: 'man_with_probing_cane_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'cane', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'disabled', + 'disability', + 'white cane' + ], + modifiable: true), + Emoji( + name: 'person kneeling', + char: '\u{1F9CE}', + shortName: 'person_kneeling', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ]), + Emoji( + name: 'person kneeling: light skin tone', + char: '\u{1F9CE}\u{1F3FB}', + shortName: 'person_kneeling_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'person kneeling: medium-light skin tone', + char: '\u{1F9CE}\u{1F3FC}', + shortName: 'person_kneeling_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'person kneeling: medium skin tone', + char: '\u{1F9CE}\u{1F3FD}', + shortName: 'person_kneeling_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'person kneeling: medium-dark skin tone', + char: '\u{1F9CE}\u{1F3FE}', + shortName: 'person_kneeling_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'person kneeling: dark skin tone', + char: '\u{1F9CE}\u{1F3FF}', + shortName: 'person_kneeling_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'woman kneeling', + char: '\u{1F9CE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_kneeling', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ]), + Emoji( + name: 'woman kneeling: light skin tone', + char: '\u{1F9CE}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_kneeling_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'woman kneeling: medium-light skin tone', + char: '\u{1F9CE}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_kneeling_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'woman kneeling: medium skin tone', + char: '\u{1F9CE}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_kneeling_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'woman kneeling: medium-dark skin tone', + char: '\u{1F9CE}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_kneeling_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'woman kneeling: dark skin tone', + char: '\u{1F9CE}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_kneeling_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'man kneeling', + char: '\u{1F9CE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_kneeling', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ]), + Emoji( + name: 'man kneeling: light skin tone', + char: '\u{1F9CE}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_kneeling_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'man kneeling: medium-light skin tone', + char: '\u{1F9CE}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_kneeling_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'man kneeling: medium skin tone', + char: '\u{1F9CE}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_kneeling_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'man kneeling: medium-dark skin tone', + char: '\u{1F9CE}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_kneeling_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'man kneeling: dark skin tone', + char: '\u{1F9CE}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_kneeling_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'sit', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'person running', + char: '\u{1F3C3}', + shortName: 'person_running', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'marathon', + 'running', + 'uc6', + 'sport', + 'diversity', + 'men', + 'boys night', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'running', + 'jog', + 'runner' + ]), + Emoji( + name: 'person running: light skin tone', + char: '\u{1F3C3}\u{1F3FB}', + shortName: 'person_running_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'light skin tone', + 'marathon', + 'running', + 'uc8', + 'sport', + 'diversity', + 'men', + 'boys night', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'person running: medium-light skin tone', + char: '\u{1F3C3}\u{1F3FC}', + shortName: 'person_running_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'marathon', + 'medium-light skin tone', + 'running', + 'uc8', + 'sport', + 'diversity', + 'men', + 'boys night', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'person running: medium skin tone', + char: '\u{1F3C3}\u{1F3FD}', + shortName: 'person_running_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'marathon', + 'medium skin tone', + 'running', + 'uc8', + 'sport', + 'diversity', + 'men', + 'boys night', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'person running: medium-dark skin tone', + char: '\u{1F3C3}\u{1F3FE}', + shortName: 'person_running_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'marathon', + 'medium-dark skin tone', + 'running', + 'uc8', + 'sport', + 'diversity', + 'men', + 'boys night', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'person running: dark skin tone', + char: '\u{1F3C3}\u{1F3FF}', + shortName: 'person_running_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'marathon', + 'running', + 'uc8', + 'sport', + 'diversity', + 'men', + 'boys night', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'woman running', + char: '\u{1F3C3}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_running', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'marathon', + 'racing', + 'running', + 'woman', + 'uc6', + 'sport', + 'diversity', + 'women', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'running', + 'jog', + 'runner' + ]), + Emoji( + name: 'woman running: light skin tone', + char: '\u{1F3C3}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_running_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'light skin tone', + 'marathon', + 'racing', + 'running', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'women', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'woman running: medium-light skin tone', + char: '\u{1F3C3}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_running_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'marathon', + 'medium-light skin tone', + 'racing', + 'running', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'women', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'woman running: medium skin tone', + char: '\u{1F3C3}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_running_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'marathon', + 'medium skin tone', + 'racing', + 'running', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'women', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'woman running: medium-dark skin tone', + char: '\u{1F3C3}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_running_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'marathon', + 'medium-dark skin tone', + 'racing', + 'running', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'women', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'woman running: dark skin tone', + char: '\u{1F3C3}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_running_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'marathon', + 'racing', + 'running', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'women', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'man running', + char: '\u{1F3C3}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_running', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'man', + 'marathon', + 'racing', + 'running', + 'uc6', + 'sport', + 'men', + 'boys night', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'running', + 'jog', + 'runner' + ]), + Emoji( + name: 'man running: light skin tone', + char: '\u{1F3C3}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_running_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'light skin tone', + 'man', + 'marathon', + 'racing', + 'running', + 'uc8', + 'sport', + 'men', + 'boys night', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'man running: medium-light skin tone', + char: '\u{1F3C3}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_running_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'man', + 'marathon', + 'medium-light skin tone', + 'racing', + 'running', + 'uc8', + 'sport', + 'men', + 'boys night', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'man running: medium skin tone', + char: '\u{1F3C3}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_running_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'man', + 'marathon', + 'medium skin tone', + 'racing', + 'running', + 'uc8', + 'sport', + 'men', + 'boys night', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'man running: medium-dark skin tone', + char: '\u{1F3C3}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_running_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'man', + 'marathon', + 'medium-dark skin tone', + 'racing', + 'running', + 'uc8', + 'sport', + 'men', + 'boys night', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'man running: dark skin tone', + char: '\u{1F3C3}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_running_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'dark skin tone', + 'man', + 'marathon', + 'racing', + 'running', + 'uc8', + 'sport', + 'men', + 'boys night', + 'run', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'guys night', + 'running', + 'jog', + 'runner' + ], + modifiable: true), + Emoji( + name: 'person standing', + char: '\u{1F9CD}', + shortName: 'person_standing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'wife', + 'husband', + 'mom', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'person standing: light skin tone', + char: '\u{1F9CD}\u{1F3FB}', + shortName: 'person_standing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'wife', + 'husband', + 'mom', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person standing: medium-light skin tone', + char: '\u{1F9CD}\u{1F3FC}', + shortName: 'person_standing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'wife', + 'husband', + 'mom', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person standing: medium skin tone', + char: '\u{1F9CD}\u{1F3FD}', + shortName: 'person_standing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'wife', + 'husband', + 'mom', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person standing: medium-dark skin tone', + char: '\u{1F9CD}\u{1F3FE}', + shortName: 'person_standing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'wife', + 'husband', + 'mom', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'person standing: dark skin tone', + char: '\u{1F9CD}\u{1F3FF}', + shortName: 'person_standing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'wife', + 'husband', + 'mom', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman standing', + char: '\u{1F9CD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_standing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'human', + 'parent', + 'wife', + 'mom', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman standing: light skin tone', + char: '\u{1F9CD}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_standing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'human', + 'parent', + 'wife', + 'mom', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman standing: medium-light skin tone', + char: '\u{1F9CD}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_standing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'human', + 'parent', + 'wife', + 'mom', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman standing: medium skin tone', + char: '\u{1F9CD}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_standing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'human', + 'parent', + 'wife', + 'mom', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman standing: medium-dark skin tone', + char: '\u{1F9CD}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_standing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'human', + 'parent', + 'wife', + 'mom', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'woman standing: dark skin tone', + char: '\u{1F9CD}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_standing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'human', + 'parent', + 'wife', + 'mom', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'man standing', + char: '\u{1F9CD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_standing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'husband', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ]), + Emoji( + name: 'man standing: light skin tone', + char: '\u{1F9CD}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_standing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'husband', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'man standing: medium-light skin tone', + char: '\u{1F9CD}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_standing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'husband', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'man standing: medium skin tone', + char: '\u{1F9CD}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_standing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'husband', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'man standing: medium-dark skin tone', + char: '\u{1F9CD}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_standing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'husband', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'man standing: dark skin tone', + char: '\u{1F9CD}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_standing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'uc12', + 'diversity', + 'men', + 'human', + 'daddy', + 'parent', + 'husband', + 'stand', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands', + char: '\u{1F9D1}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}', + shortName: 'people_holding_hands', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ]), + Emoji( + name: 'people holding hands: light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FB}', + shortName: 'people_holding_hands_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: light skin tone, medium-light skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FC}', + shortName: 'people_holding_hands_tone1_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: light skin tone, medium skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FD}', + shortName: 'people_holding_hands_tone1_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: light skin tone, medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FE}', + shortName: 'people_holding_hands_tone1_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: light skin tone, dark skin tone', + char: '\u{1F9D1}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FF}', + shortName: 'people_holding_hands_tone1_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium-light skin tone, light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FB}', + shortName: 'people_holding_hands_tone2_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium-light skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FC}', + shortName: 'people_holding_hands_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium-light skin tone, medium skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FD}', + shortName: 'people_holding_hands_tone2_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'people holding hands: medium-light skin tone, medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FE}', + shortName: 'people_holding_hands_tone2_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium-light skin tone, dark skin tone', + char: '\u{1F9D1}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FF}', + shortName: 'people_holding_hands_tone2_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium skin tone, light skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FB}', + shortName: 'people_holding_hands_tone3_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium skin tone, medium-light skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FC}', + shortName: 'people_holding_hands_tone3_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FD}', + shortName: 'people_holding_hands_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium skin tone, medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FE}', + shortName: 'people_holding_hands_tone3_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium skin tone, dark skin tone', + char: '\u{1F9D1}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FF}', + shortName: 'people_holding_hands_tone3_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium-dark skin tone, light skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FB}', + shortName: 'people_holding_hands_tone4_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'people holding hands: medium-dark skin tone, medium-light skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FC}', + shortName: 'people_holding_hands_tone4_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium-dark skin tone, medium skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FD}', + shortName: 'people_holding_hands_tone4_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FE}', + shortName: 'people_holding_hands_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: medium-dark skin tone, dark skin tone', + char: '\u{1F9D1}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FF}', + shortName: 'people_holding_hands_tone4_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: dark skin tone, light skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FB}', + shortName: 'people_holding_hands_tone5_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: dark skin tone, medium-light skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FC}', + shortName: 'people_holding_hands_tone5_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: dark skin tone, medium skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FD}', + shortName: 'people_holding_hands_tone5_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: dark skin tone, medium-dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FE}', + shortName: 'people_holding_hands_tone5_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'people holding hands: dark skin tone', + char: '\u{1F9D1}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F9D1}\u{1F3FF}', + shortName: 'people_holding_hands_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'lgbt', + 'friend', + 'human', + 'daddy', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'woman and man holding hands', + char: '\u{1F46B}', + shortName: 'couple', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'couple', + 'hand', + 'hold', + 'man', + 'woman', + 'uc6', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ]), + Emoji( + name: 'woman and man holding hands: light skin tone', + char: '\u{1F46B}\u{1F3FB}', + shortName: 'woman_and_man_holding_hands_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: light skin tone, medium-light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FC}', + shortName: 'woman_and_man_holding_hands_tone1_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'woman and man holding hands: light skin tone, medium skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FD}', + shortName: 'woman_and_man_holding_hands_tone1_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: light skin tone, medium-dark skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FE}', + shortName: 'woman_and_man_holding_hands_tone1_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'woman and man holding hands: light skin tone, dark skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FF}', + shortName: 'woman_and_man_holding_hands_tone1_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: medium-light skin tone, light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FB}', + shortName: 'woman_and_man_holding_hands_tone2_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'woman and man holding hands: medium-light skin tone', + char: '\u{1F46B}\u{1F3FC}', + shortName: 'woman_and_man_holding_hands_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: medium-light skin tone, medium skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FD}', + shortName: 'woman_and_man_holding_hands_tone2_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: medium-light skin tone, medium-dark skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FE}', + shortName: 'woman_and_man_holding_hands_tone2_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: medium-light skin tone, dark skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FF}', + shortName: 'woman_and_man_holding_hands_tone2_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'woman and man holding hands: medium skin tone, light skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FB}', + shortName: 'woman_and_man_holding_hands_tone3_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: medium skin tone, medium-light skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FC}', + shortName: 'woman_and_man_holding_hands_tone3_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'woman and man holding hands: medium skin tone', + char: '\u{1F46B}\u{1F3FD}', + shortName: 'woman_and_man_holding_hands_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: medium skin tone, medium-dark skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FE}', + shortName: 'woman_and_man_holding_hands_tone3_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'woman and man holding hands: medium skin tone, dark skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FF}', + shortName: 'woman_and_man_holding_hands_tone3_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: medium-dark skin tone, light skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FB}', + shortName: 'woman_and_man_holding_hands_tone4_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: medium-dark skin tone, medium-light skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FC}', + shortName: 'woman_and_man_holding_hands_tone4_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: medium-dark skin tone, medium skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FD}', + shortName: 'woman_and_man_holding_hands_tone4_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'woman and man holding hands: medium-dark skin tone', + char: '\u{1F46B}\u{1F3FE}', + shortName: 'woman_and_man_holding_hands_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: medium-dark skin tone, dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FF}', + shortName: 'woman_and_man_holding_hands_tone4_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'woman and man holding hands: dark skin tone, light skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FB}', + shortName: 'woman_and_man_holding_hands_tone5_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: dark skin tone, medium-light skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FC}', + shortName: 'woman_and_man_holding_hands_tone5_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'woman and man holding hands: dark skin tone, medium skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FD}', + shortName: 'woman_and_man_holding_hands_tone5_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: + 'woman and man holding hands: dark skin tone, medium-dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FE}', + shortName: 'woman_and_man_holding_hands_tone5_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'woman and man holding hands: dark skin tone', + char: '\u{1F46B}\u{1F3FF}', + shortName: 'woman_and_man_holding_hands_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'creationism', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'adam & eve', + 'adam and eve', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'women holding hands', + char: '\u{1F46D}', + shortName: 'two_women_holding_hands', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'couple', + 'hand', + 'hold', + 'woman', + 'uc6', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'women holding hands: light skin tone', + char: '\u{1F46D}\u{1F3FB}', + shortName: 'women_holding_hands_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: light skin tone, medium-light skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FC}', + shortName: 'women_holding_hands_tone1_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: light skin tone, medium skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FD}', + shortName: 'women_holding_hands_tone1_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: light skin tone, medium-dark skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FE}', + shortName: 'women_holding_hands_tone1_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: light skin tone, dark skin tone', + char: '\u{1F469}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FF}', + shortName: 'women_holding_hands_tone1_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium-light skin tone, light skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FB}', + shortName: 'women_holding_hands_tone2_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium-light skin tone', + char: '\u{1F46D}\u{1F3FC}', + shortName: 'women_holding_hands_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium-light skin tone, medium skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FD}', + shortName: 'women_holding_hands_tone2_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: + 'women holding hands: medium-light skin tone, medium-dark skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FE}', + shortName: 'women_holding_hands_tone2_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium-light skin tone, dark skin tone', + char: '\u{1F469}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FF}', + shortName: 'women_holding_hands_tone2_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium skin tone, light skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FB}', + shortName: 'women_holding_hands_tone3_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium skin tone, medium-light skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FC}', + shortName: 'women_holding_hands_tone3_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium skin tone', + char: '\u{1F46D}\u{1F3FD}', + shortName: 'women_holding_hands_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium skin tone, medium-dark skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FE}', + shortName: 'women_holding_hands_tone3_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium skin tone, dark skin tone', + char: '\u{1F469}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FF}', + shortName: 'women_holding_hands_tone3_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium-dark skin tone, light skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FB}', + shortName: 'women_holding_hands_tone4_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: + 'women holding hands: medium-dark skin tone, medium-light skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FC}', + shortName: 'women_holding_hands_tone4_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium-dark skin tone, medium skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FD}', + shortName: 'women_holding_hands_tone4_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium-dark skin tone', + char: '\u{1F46D}\u{1F3FE}', + shortName: 'women_holding_hands_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: medium-dark skin tone, dark skin tone', + char: '\u{1F469}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FF}', + shortName: 'women_holding_hands_tone4_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: dark skin tone, light skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FB}', + shortName: 'women_holding_hands_tone5_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: dark skin tone, medium-light skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FC}', + shortName: 'women_holding_hands_tone5_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: dark skin tone, medium skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FD}', + shortName: 'women_holding_hands_tone5_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: dark skin tone, medium-dark skin tone', + char: '\u{1F469}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FE}', + shortName: 'women_holding_hands_tone5_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'women holding hands: dark skin tone', + char: '\u{1F46D}\u{1F3FF}', + shortName: 'women_holding_hands_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'girls night', + 'friend', + 'human', + 'porn', + 'parent', + 'wife', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'gender', + 'people', + 'parents', + 'adult', + 'maman', + 'mommy', + 'mama', + 'mother' + ], + modifiable: true), + Emoji( + name: 'men holding hands', + char: '\u{1F46C}', + shortName: 'two_men_holding_hands', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'Gemini', + 'couple', + 'hand', + 'hold', + 'man', + 'twins', + 'zodiac', + 'uc6', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ]), + Emoji( + name: 'men holding hands: light skin tone', + char: '\u{1F46C}\u{1F3FB}', + shortName: 'men_holding_hands_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: light skin tone, medium-light skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FC}', + shortName: 'men_holding_hands_tone1_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: light skin tone, medium skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FD}', + shortName: 'men_holding_hands_tone1_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: light skin tone, medium-dark skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FE}', + shortName: 'men_holding_hands_tone1_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: light skin tone, dark skin tone', + char: '\u{1F468}\u{1F3FB}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FF}', + shortName: 'men_holding_hands_tone1_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium-light skin tone, light skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FB}', + shortName: 'men_holding_hands_tone2_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium-light skin tone', + char: '\u{1F46C}\u{1F3FC}', + shortName: 'men_holding_hands_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium-light skin tone, medium skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FD}', + shortName: 'men_holding_hands_tone2_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium-light skin tone, medium-dark skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FE}', + shortName: 'men_holding_hands_tone2_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium-light skin tone, dark skin tone', + char: '\u{1F468}\u{1F3FC}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FF}', + shortName: 'men_holding_hands_tone2_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium skin tone, light skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FB}', + shortName: 'men_holding_hands_tone3_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium skin tone, medium-light skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FC}', + shortName: 'men_holding_hands_tone3_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium skin tone', + char: '\u{1F46C}\u{1F3FD}', + shortName: 'men_holding_hands_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium skin tone, medium-dark skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FE}', + shortName: 'men_holding_hands_tone3_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium skin tone, dark skin tone', + char: '\u{1F468}\u{1F3FD}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FF}', + shortName: 'men_holding_hands_tone3_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium-dark skin tone, light skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FB}', + shortName: 'men_holding_hands_tone4_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium-dark skin tone, medium-light skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FC}', + shortName: 'men_holding_hands_tone4_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium-dark skin tone, medium skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FD}', + shortName: 'men_holding_hands_tone4_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium-dark skin tone', + char: '\u{1F46C}\u{1F3FE}', + shortName: 'men_holding_hands_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: medium-dark skin tone, dark skin tone', + char: '\u{1F468}\u{1F3FE}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FF}', + shortName: 'men_holding_hands_tone4_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: dark skin tone, light skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FB}', + shortName: 'men_holding_hands_tone5_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: dark skin tone, medium-light skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FC}', + shortName: 'men_holding_hands_tone5_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: dark skin tone, medium skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FD}', + shortName: 'men_holding_hands_tone5_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: dark skin tone, medium-dark skin tone', + char: '\u{1F468}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F468}\u{1F3FE}', + shortName: 'men_holding_hands_tone5_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'men holding hands: dark skin tone', + char: '\u{1F46C}\u{1F3FF}', + shortName: 'men_holding_hands_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'uc12', + 'family', + 'diversity', + 'wedding', + 'gay', + 'men', + 'lgbt', + 'friend', + 'queen', + 'human', + 'daddy', + 'porn', + 'parent', + 'husband', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'king', + 'prince', + 'princess', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult' + ], + modifiable: true), + Emoji( + name: 'couple with heart', + char: '\u{1F491}', + shortName: 'couple_with_heart', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'couple', + 'love', + 'uc6', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'love', + 'sex', + 'lgbt', + 'pink', + 'husband', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'rose' + ]), + Emoji( + name: 'couple with heart: woman, man', + char: '\u{1F469}\u{200D}\u{2764}\u{FE0F}\u{200D}\u{1F468}', + shortName: 'couple_with_heart_woman_man', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'couple', + 'love', + 'man', + 'woman', + 'uc6', + 'wedding', + 'love', + 'sex', + 'husband', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'fuck', + 'fucking', + 'horny', + 'humping' + ]), + Emoji( + name: 'couple with heart: woman, woman', + char: '\u{1F469}\u{200D}\u{2764}\u{FE0F}\u{200D}\u{1F469}', + shortName: 'couple_ww', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'couple', + 'love', + 'woman', + 'uc6', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'love', + 'sex', + 'lgbt', + 'pink', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'rose' + ]), + Emoji( + name: 'couple with heart: man, man', + char: '\u{1F468}\u{200D}\u{2764}\u{FE0F}\u{200D}\u{1F468}', + shortName: 'couple_mm', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'couple', + 'love', + 'man', + 'uc6', + 'wedding', + 'gay', + 'men', + 'love', + 'sex', + 'lgbt', + 'pink', + 'husband', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'rose' + ]), + Emoji( + name: 'kiss', + char: '\u{1F48F}', + shortName: 'couplekiss', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'couple', + 'uc6', + 'wedding', + 'lesbian', + 'gay', + 'men', + 'love', + 'sex', + 'hug', + 'lgbt', + 'pink', + 'kisses', + 'husband', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'embrace', + 'hugs', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'rose', + 'bisous', + 'beijos', + 'besos', + 'bise', + 'blowing kisses', + 'kissy' + ]), + Emoji( + name: 'kiss: woman, man', + char: + '\u{1F469}\u{200D}\u{2764}\u{FE0F}\u{200D}\u{1F48B}\u{200D}\u{1F468}', + shortName: 'kiss_woman_man', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'couple', + 'man', + 'woman', + 'uc6', + 'wedding', + 'love', + 'sex', + 'hug', + 'kisses', + 'husband', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'embrace', + 'hugs', + 'bisous', + 'beijos', + 'besos', + 'bise', + 'blowing kisses', + 'kissy' + ]), + Emoji( + name: 'kiss: woman, woman', + char: + '\u{1F469}\u{200D}\u{2764}\u{FE0F}\u{200D}\u{1F48B}\u{200D}\u{1F469}', + shortName: 'kiss_ww', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'couple', + 'woman', + 'uc6', + 'wedding', + 'lesbian', + 'gay', + 'women', + 'love', + 'sex', + 'hug', + 'lgbt', + 'pink', + 'kisses', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'woman', + 'female', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'embrace', + 'hugs', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'rose', + 'bisous', + 'beijos', + 'besos', + 'bise', + 'blowing kisses', + 'kissy' + ]), + Emoji( + name: 'kiss: man, man', + char: + '\u{1F468}\u{200D}\u{2764}\u{FE0F}\u{200D}\u{1F48B}\u{200D}\u{1F468}', + shortName: 'kiss_mm', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'couple', + 'man', + 'uc6', + 'wedding', + 'gay', + 'men', + 'love', + 'sex', + 'hug', + 'lgbt', + 'pink', + 'kisses', + 'husband', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'embrace', + 'hugs', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'rose', + 'bisous', + 'beijos', + 'besos', + 'bise', + 'blowing kisses', + 'kissy' + ]), + Emoji( + name: 'family', + char: '\u{1F46A}', + shortName: 'family', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'family', + 'uc6', + 'family', + 'lesbian', + 'gay', + 'men', + 'christmas', + 'lgbt', + 'human', + 'daddy', + 'parent', + 'wife', + 'husband', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: man, woman, boy', + char: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}', + shortName: 'family_man_woman_boy', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'man', + 'woman', + 'uc6', + 'family', + 'human', + 'daddy', + 'parent', + 'wife', + 'husband', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: man, woman, girl', + char: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}', + shortName: 'family_mwg', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'family', + 'girl', + 'man', + 'woman', + 'uc6', + 'family', + 'human', + 'daddy', + 'parent', + 'wife', + 'husband', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: man, woman, girl, boy', + char: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}', + shortName: 'family_mwgb', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'girl', + 'man', + 'woman', + 'uc6', + 'family', + 'human', + 'daddy', + 'parent', + 'wife', + 'husband', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: man, woman, boy, boy', + char: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}\u{200D}\u{1F466}', + shortName: 'family_mwbb', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'man', + 'woman', + 'uc6', + 'family', + 'human', + 'daddy', + 'parent', + 'wife', + 'husband', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: man, woman, girl, girl', + char: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F467}', + shortName: 'family_mwgg', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'family', + 'girl', + 'man', + 'woman', + 'uc6', + 'family', + 'human', + 'daddy', + 'parent', + 'wife', + 'husband', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: woman, woman, boy', + char: '\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F466}', + shortName: 'family_wwb', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'woman', + 'uc6', + 'family', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'human', + 'parent', + 'wife', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'gender', + 'people', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: woman, woman, girl', + char: '\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}', + shortName: 'family_wwg', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'family', + 'girl', + 'woman', + 'uc6', + 'family', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'human', + 'parent', + 'wife', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'gender', + 'people', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: woman, woman, girl, boy', + char: '\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}', + shortName: 'family_wwgb', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'girl', + 'woman', + 'uc6', + 'family', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'human', + 'parent', + 'wife', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'gender', + 'people', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: woman, woman, boy, boy', + char: '\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F466}\u{200D}\u{1F466}', + shortName: 'family_wwbb', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'woman', + 'uc6', + 'family', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'human', + 'parent', + 'wife', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'gender', + 'people', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: woman, woman, girl, girl', + char: '\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F467}', + shortName: 'family_wwgg', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'family', + 'girl', + 'woman', + 'uc6', + 'family', + 'lesbian', + 'gay', + 'women', + 'lgbt', + 'human', + 'parent', + 'wife', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'twink', + 'woman', + 'female', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'gender', + 'people', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: man, man, boy', + char: '\u{1F468}\u{200D}\u{1F468}\u{200D}\u{1F466}', + shortName: 'family_mmb', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'man', + 'uc6', + 'family', + 'gay', + 'men', + 'lgbt', + 'human', + 'daddy', + 'parent', + 'husband', + 'child', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'family: man, man, girl', + char: '\u{1F468}\u{200D}\u{1F468}\u{200D}\u{1F467}', + shortName: 'family_mmg', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'family', + 'girl', + 'man', + 'uc6', + 'family', + 'gay', + 'men', + 'lgbt', + 'human', + 'daddy', + 'parent', + 'husband', + 'child', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'family: man, man, girl, boy', + char: '\u{1F468}\u{200D}\u{1F468}\u{200D}\u{1F467}\u{200D}\u{1F466}', + shortName: 'family_mmgb', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'girl', + 'man', + 'uc6', + 'family', + 'gay', + 'men', + 'lgbt', + 'human', + 'daddy', + 'parent', + 'husband', + 'child', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'family: man, man, boy, boy', + char: '\u{1F468}\u{200D}\u{1F468}\u{200D}\u{1F466}\u{200D}\u{1F466}', + shortName: 'family_mmbb', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'man', + 'uc6', + 'family', + 'gay', + 'men', + 'lgbt', + 'human', + 'daddy', + 'parent', + 'husband', + 'child', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'family: man, man, girl, girl', + char: '\u{1F468}\u{200D}\u{1F468}\u{200D}\u{1F467}\u{200D}\u{1F467}', + shortName: 'family_mmgg', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'family', + 'girl', + 'man', + 'uc6', + 'family', + 'gay', + 'men', + 'lgbt', + 'human', + 'daddy', + 'parent', + 'husband', + 'child', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'twink', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'family: woman, boy', + char: '\u{1F469}\u{200D}\u{1F466}', + shortName: 'family_woman_boy', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'woman', + 'uc6', + 'family', + 'women', + 'human', + 'parent', + 'wife', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'woman', + 'female', + 'gender', + 'people', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: woman, girl', + char: '\u{1F469}\u{200D}\u{1F467}', + shortName: 'family_woman_girl', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'family', + 'girl', + 'woman', + 'uc6', + 'family', + 'women', + 'human', + 'parent', + 'wife', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'woman', + 'female', + 'gender', + 'people', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: woman, girl, boy', + char: '\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}', + shortName: 'family_woman_girl_boy', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'girl', + 'woman', + 'uc6', + 'family', + 'women', + 'human', + 'parent', + 'wife', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'woman', + 'female', + 'gender', + 'people', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: woman, boy, boy', + char: '\u{1F469}\u{200D}\u{1F466}\u{200D}\u{1F466}', + shortName: 'family_woman_boy_boy', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'woman', + 'uc6', + 'family', + 'women', + 'human', + 'parent', + 'wife', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'woman', + 'female', + 'gender', + 'people', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: woman, girl, girl', + char: '\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F467}', + shortName: 'family_woman_girl_girl', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'family', + 'girl', + 'woman', + 'uc6', + 'family', + 'women', + 'human', + 'parent', + 'wife', + 'child', + 'mom', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'woman', + 'female', + 'gender', + 'people', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'family: man, boy', + char: '\u{1F468}\u{200D}\u{1F466}', + shortName: 'family_man_boy', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'man', + 'uc6', + 'family', + 'men', + 'human', + 'daddy', + 'parent', + 'husband', + 'child', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'family: man, girl', + char: '\u{1F468}\u{200D}\u{1F467}', + shortName: 'family_man_girl', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'family', + 'girl', + 'man', + 'uc6', + 'family', + 'men', + 'human', + 'daddy', + 'parent', + 'husband', + 'child', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'family: man, girl, boy', + char: '\u{1F468}\u{200D}\u{1F467}\u{200D}\u{1F466}', + shortName: 'family_man_girl_boy', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'girl', + 'man', + 'uc6', + 'family', + 'men', + 'human', + 'daddy', + 'parent', + 'husband', + 'child', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'family: man, boy, boy', + char: '\u{1F468}\u{200D}\u{1F466}\u{200D}\u{1F466}', + shortName: 'family_man_boy_boy', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'boy', + 'family', + 'man', + 'uc6', + 'family', + 'men', + 'human', + 'daddy', + 'parent', + 'husband', + 'child', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'family: man, girl, girl', + char: '\u{1F468}\u{200D}\u{1F467}\u{200D}\u{1F467}', + shortName: 'family_man_girl_girl', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.family, + keywords: [ + 'family', + 'girl', + 'man', + 'uc6', + 'family', + 'men', + 'human', + 'daddy', + 'parent', + 'husband', + 'child', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'gender', + 'people', + 'dad', + 'papa', + 'pere', + 'father', + 'parents', + 'adult', + 'children', + 'girl', + 'boy', + 'kids', + 'niÃąo', + 'enfant' + ]), + Emoji( + name: 'yarn', + char: '\u{1F9F6}', + shortName: 'yarn', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.artsCrafts, + keywords: [ + 'uc11', + 'cat', + 'household', + 'sew', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow', + 'knit', + 'embroider', + 'stitch', + 'repair', + 'crochet', + 'alter', + 'seamstress', + 'fix' + ]), + Emoji( + name: 'thread', + char: '\u{1F9F5}', + shortName: 'thread', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.artsCrafts, + keywords: [ + 'uc11', + 'household', + 'sew', + 'knit', + 'embroider', + 'stitch', + 'repair', + 'crochet', + 'alter', + 'seamstress', + 'fix' + ]), + Emoji( + name: 'coat', + char: '\u{1F9E5}', + shortName: 'coat', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'jacket', + 'uc10', + 'fashion', + 'winter', + 'cold', + 'jacket', + 'clothes', + 'clothing', + 'style', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'veste' + ]), + Emoji( + name: 'lab coat', + char: '\u{1F97C}', + shortName: 'lab_coat', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: ['uc11', 'science', 'jacket', 'medical', 'lab', 'veste']), + Emoji( + name: 'safety vest', + char: '\u{1F9BA}', + shortName: 'safety_vest', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'uc12', + '911', + 'jacket', + 'construction', + 'emergency', + 'injury', + 'veste' + ]), + Emoji( + name: 'woman’s clothes', + char: '\u{1F45A}', + shortName: 'womans_clothes', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'woman', + 'uc6', + 'fashion', + 'women', + 'pink', + 'mom', + 'clothes', + 'clothing', + 'style', + 'woman', + 'female', + 'rose', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 't-shirt', + char: '\u{1F455}', + shortName: 'shirt', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'shirt', + 'tshirt', + 'uc6', + 'fashion', + 'men', + 'clothes', + 'clothing', + 'style', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ]), + Emoji( + name: 'jeans', + char: '\u{1F456}', + shortName: 'jeans', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'pants', + 'trousers', + 'uc6', + 'fashion', + 'men', + 'pants', + 'clothes', + 'clothing', + 'style', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'pant', + 'levis', + 'slacks' + ]), + Emoji( + name: 'briefs', + char: '\u{1FA72}', + shortName: 'briefs', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'uc12', + 'fashion', + 'men', + 'underwear', + 'clothes', + 'clothing', + 'style', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'undergarments', + 'boxers', + 'panties', + 'boy shorts', + 'panty', + 'biancheria intima', + 'sous-vÃĒtements', + 'ropa interior', + 'speedos' + ]), + Emoji( + name: 'shorts', + char: '\u{1FA73}', + shortName: 'shorts', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'uc12', + 'fashion', + 'vacation', + 'swim', + 'beach', + 'scuba', + 'pantalones cortos', + 'clothes', + 'clothing', + 'style', + 'swimming', + 'swimmer', + 'snorkel', + 'kurze Hose', + 'pantaloncini' + ]), + Emoji( + name: 'necktie', + char: '\u{1F454}', + shortName: 'necktie', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'uc6', + 'fashion', + 'men', + 'accessories', + 'business', + 'clothes', + 'clothing', + 'style', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ]), + Emoji( + name: 'dress', + char: '\u{1F457}', + shortName: 'dress', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'uc6', + 'fashion', + 'women', + 'sexy', + 'beautiful', + 'girls night', + 'pink', + 'vintage', + 'florida', + 'mom', + 'dress', + 'clothes', + 'clothing', + 'style', + 'woman', + 'female', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'rose', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'bikini', + char: '\u{1F459}', + shortName: 'bikini', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'swim', + 'uc6', + 'fashion', + 'women', + 'sexy', + 'tropical', + 'vacation', + 'swim', + 'beach', + 'hawaii', + 'california', + 'florida', + 'las vegas', + 'summer', + 'binoculars', + 'clothes', + 'clothing', + 'style', + 'woman', + 'female', + 'swimming', + 'swimmer', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'vegas', + 'weekend' + ]), + Emoji( + name: 'one-piece swimsuit', + char: '\u{1FA71}', + shortName: 'one_piece_swimsuit', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'uc12', + 'fashion', + 'tropical', + 'vacation', + 'swim', + 'beach', + 'hawaii', + 'california', + 'florida', + 'scuba', + 'summer', + 'clothes', + 'clothing', + 'style', + 'swimming', + 'swimmer', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel', + 'weekend' + ]), + Emoji( + name: 'kimono', + char: '\u{1F458}', + shortName: 'kimono', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'uc6', + 'fashion', + 'japan', + 'pink', + 'dress', + 'clothes', + 'clothing', + 'style', + 'japanese', + 'ninja', + 'rose' + ]), + Emoji( + name: 'sari', + char: '\u{1F97B}', + shortName: 'sari', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'uc12', + 'fashion', + 'saree', + 'dress', + 'clothes', + 'clothing', + 'style', + 'shari', + 'nivi', + 'choli', + 'ravike', + 'cholo', + 'parkar', + 'ul-pavadai' + ]), + Emoji( + name: 'flat shoe', + char: '\u{1F97F}', + shortName: 'womans_flat_shoe', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'uc11', + 'fashion', + 'women', + 'shoe', + 'accessories', + 'pink', + 'mom', + 'clothes', + 'clothing', + 'style', + 'woman', + 'female', + 'shoes', + 'baskets', + 'loafers', + 'sandals', + 'pumps', + 'boots', + 'heels', + 'rose', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'high-heeled shoe', + char: '\u{1F460}', + shortName: 'high_heel', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'heel', + 'shoe', + 'woman', + 'uc6', + 'fashion', + 'women', + 'shoe', + 'sexy', + 'accessories', + 'girls night', + 'california', + 'las vegas', + 'rich', + 'mom', + 'clothes', + 'clothing', + 'style', + 'woman', + 'female', + 'shoes', + 'baskets', + 'loafers', + 'sandals', + 'pumps', + 'boots', + 'heels', + 'ladies night', + 'girls only', + 'girlfriend', + 'vegas', + 'grand', + 'expensive', + 'fancy', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman’s sandal', + char: '\u{1F461}', + shortName: 'sandal', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'sandal', + 'shoe', + 'woman', + 'uc6', + 'fashion', + 'women', + 'shoe', + 'accessories', + 'pink', + 'summer', + 'mom', + 'clothes', + 'clothing', + 'style', + 'woman', + 'female', + 'shoes', + 'baskets', + 'loafers', + 'sandals', + 'pumps', + 'boots', + 'heels', + 'rose', + 'weekend', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'woman’s boot', + char: '\u{1F462}', + shortName: 'boot', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'boot', + 'clothing', + 'shoe', + 'woman', + 'uc6', + 'fashion', + 'women', + 'shoe', + 'sexy', + 'accessories', + 'vintage', + 'rich', + 'clothes', + 'clothing', + 'style', + 'woman', + 'female', + 'shoes', + 'baskets', + 'loafers', + 'sandals', + 'pumps', + 'boots', + 'heels', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'man’s shoe', + char: '\u{1F45E}', + shortName: 'mans_shoe', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'man', + 'shoe', + 'uc6', + 'fashion', + 'shoe', + 'men', + 'accessories', + 'vintage', + 'clothes', + 'clothing', + 'style', + 'shoes', + 'baskets', + 'loafers', + 'sandals', + 'pumps', + 'boots', + 'heels', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male' + ]), + Emoji( + name: 'running shoe', + char: '\u{1F45F}', + shortName: 'athletic_shoe', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'athletic', + 'clothing', + 'shoe', + 'sneaker', + 'uc6', + 'sport', + 'fashion', + 'shoe', + 'accessories', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'clothes', + 'clothing', + 'style', + 'shoes', + 'baskets', + 'loafers', + 'sandals', + 'pumps', + 'boots', + 'heels' + ]), + Emoji( + name: 'hiking boot', + char: '\u{1F97E}', + shortName: 'hiking_boot', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'uc11', + 'shoe', + 'accessories', + 'mountain', + 'activity', + 'rock climbing', + 'shoes', + 'baskets', + 'loafers', + 'sandals', + 'pumps', + 'boots', + 'heels', + 'climber' + ]), + Emoji( + name: 'thong sandal', + char: '\u{1FA74}', + shortName: 'thong_sandal', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'uc13', + 'tropical', + 'beach', + 'hawaii', + 'flip flop', + 'california', + 'florida', + 'summer', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ]), + Emoji( + name: 'socks', + char: '\u{1F9E6}', + shortName: 'socks', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'stocking', + 'uc10', + 'fashion', + 'winter', + 'cold', + 'accessories', + 'clothes', + 'clothing', + 'style', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'gloves', + char: '\u{1F9E4}', + shortName: 'gloves', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'hand', + 'uc10', + 'fashion', + 'winter', + 'cold', + 'accessories', + 'gloves', + 'mittins', + 'clothes', + 'clothing', + 'style', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'muff' + ]), + Emoji( + name: 'scarf', + char: '\u{1F9E3}', + shortName: 'scarf', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'neck', + 'uc10', + 'fashion', + 'winter', + 'cold', + 'accessories', + 'clothes', + 'clothing', + 'style', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'top hat', + char: '\u{1F3A9}', + shortName: 'tophat', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'hat', + 'top', + 'tophat', + 'uc6', + 'fashion', + 'wedding', + 'hat', + 'men', + 'accessories', + 'magic', + 'vintage', + 'rich', + 'clothes', + 'clothing', + 'style', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'hats', + 'cap', + 'caps', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'spell', + 'genie', + 'magical', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'billed cap', + char: '\u{1F9E2}', + shortName: 'billed_cap', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'baseball cap', + 'uc10', + 'fashion', + 'hat', + 'accessories', + 'clothes', + 'clothing', + 'style', + 'hats', + 'cap', + 'caps' + ]), + Emoji( + name: 'woman’s hat', + char: '\u{1F452}', + shortName: 'womans_hat', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'hat', + 'woman', + 'uc6', + 'fashion', + 'hat', + 'women', + 'accessories', + 'pink', + 'vintage', + 'easter', + 'rich', + 'clothes', + 'clothing', + 'style', + 'hats', + 'cap', + 'caps', + 'woman', + 'female', + 'rose', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'graduation cap', + char: '\u{1F393}', + shortName: 'mortar_board', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'cap', + 'celebration', + 'clothing', + 'graduation', + 'hat', + 'uc6', + 'hat', + 'classroom', + 'accessories', + 'graduate', + 'hats', + 'cap', + 'caps', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning' + ]), + Emoji( + name: 'rescue worker’s helmet', + char: '\u{26D1}\u{FE0F}', + shortName: 'helmet_with_cross', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'aid', + 'cross', + 'face', + 'hat', + 'helmet', + 'uc5', + 'hat', + 'accessories', + 'job', + '911', + 'help', + 'helmet', + 'hats', + 'cap', + 'caps', + 'profession', + 'boss', + 'career', + 'emergency', + 'injury' + ]), + Emoji( + name: 'military helmet', + char: '\u{1FA96}', + shortName: 'military_helmet', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'uc13', + 'hat', + 'soldier', + 'helmet', + 'army', + 'hats', + 'cap', + 'caps' + ]), + Emoji( + name: 'crown', + char: '\u{1F451}', + shortName: 'crown', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'king', + 'queen', + 'uc6', + 'accessories', + 'power', + 'queen', + 'england', + 'bling', + 'fame', + 'crown', + 'rich', + 'king', + 'prince', + 'princess', + 'united kingdom', + 'london', + 'uk', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'tiara', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'ring', + char: '\u{1F48D}', + shortName: 'ring', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'diamond', + 'uc6', + 'wedding', + 'accessories', + 'girls night', + 'trap', + 'vintage', + 'bling', + 'diamond', + 'rich', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'ladies night', + 'girls only', + 'girlfriend', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'clutch bag', + char: '\u{1F45D}', + shortName: 'pouch', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'bag', + 'clothing', + 'pouch', + 'uc6', + 'fashion', + 'women', + 'bag', + 'accessories', + 'mom', + 'clothes', + 'clothing', + 'style', + 'woman', + 'female', + 'swag', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'purse', + char: '\u{1F45B}', + shortName: 'purse', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'coin', + 'uc6', + 'fashion', + 'women', + 'bag', + 'money', + 'accessories', + 'pink', + 'vintage', + 'mom', + 'clothes', + 'clothing', + 'style', + 'woman', + 'female', + 'swag', + 'cash', + 'dollars', + 'dollar', + 'bucks', + 'currency', + 'funds', + 'payment', + 'money face', + 'reward', + 'thief', + 'bank', + 'benjamins', + 'argent', + 'dinero', + 'i soldi', + 'Geld', + 'rose', + 'maman', + 'mommy', + 'mama', + 'mother' + ]), + Emoji( + name: 'handbag', + char: '\u{1F45C}', + shortName: 'handbag', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'bag', + 'clothing', + 'purse', + 'uc6', + 'fashion', + 'women', + 'bag', + 'vacation', + 'accessories', + 'rich', + 'mom', + 'work', + 'clothes', + 'clothing', + 'style', + 'woman', + 'female', + 'swag', + 'grand', + 'expensive', + 'fancy', + 'maman', + 'mommy', + 'mama', + 'mother', + 'office' + ]), + Emoji( + name: 'briefcase', + char: '\u{1F4BC}', + shortName: 'briefcase', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'briefcase', + 'uc6', + 'fashion', + 'bag', + 'men', + 'classroom', + 'accessories', + 'nutcase', + 'job', + 'business', + 'rich', + 'work', + 'clothes', + 'clothing', + 'style', + 'swag', + 'man', + 'guy', + 'guys', + 'gentleman', + 'male', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'profession', + 'boss', + 'career', + 'grand', + 'expensive', + 'fancy', + 'office' + ]), + Emoji( + name: 'backpack', + char: '\u{1F392}', + shortName: 'school_satchel', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'bag', + 'satchel', + 'school', + 'uc6', + 'fashion', + 'bag', + 'classroom', + 'vacation', + 'accessories', + 'backpack', + 'suitcase', + 'clothes', + 'clothing', + 'style', + 'swag', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'carry-on' + ]), + Emoji( + name: 'luggage', + char: '\u{1F9F3}', + shortName: 'luggage', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.hotel, + keywords: [ + 'uc11', + 'bag', + 'travel', + 'vacation', + 'suitcase', + 'household', + 'swag', + 'carry-on' + ]), + Emoji( + name: 'glasses', + char: '\u{1F453}', + shortName: 'eyeglasses', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'clothing', + 'eye', + 'eyeglasses', + 'eyewear', + 'uc6', + 'fashion', + 'glasses', + 'accessories', + 'harry potter', + 'detective', + 'clothes', + 'clothing', + 'style', + 'eyeglasses', + 'eye glasses' + ]), + Emoji( + name: 'sunglasses', + char: '\u{1F576}\u{FE0F}', + shortName: 'dark_sunglasses', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'dark', + 'eye', + 'eyewear', + 'glasses', + 'uc7', + 'fashion', + 'glasses', + 'accessories', + 'awesome', + 'beautiful', + 'sunglasses', + 'hawaii', + 'california', + 'florida', + 'las vegas', + 'summer', + 'clothes', + 'clothing', + 'style', + 'eyeglasses', + 'eye glasses', + 'okay', + 'got it', + 'cool', + 'ok', + 'will do', + 'like', + 'bien', + 'yep', + 'yup', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'shades', + 'lunettes de soleil', + 'sun glasses', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'vegas', + 'weekend' + ]), + Emoji( + name: 'goggles', + char: '\u{1F97D}', + shortName: 'goggles', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'uc11', + 'glasses', + 'science', + 'accessories', + 'medical', + 'eyeglasses', + 'eye glasses', + 'lab' + ]), + Emoji( + name: 'closed umbrella', + char: '\u{1F302}', + shortName: 'closed_umbrella', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'clothing', + 'rain', + 'umbrella', + 'uc6', + 'sky', + 'rain', + 'accessories', + 'umbrella', + 'england', + 'cane', + 'united kingdom', + 'london', + 'uk' + ]), + Emoji( + name: 'curly hair', + char: '\u{1F9B1}', + shortName: 'curly_haired', + emojiGroup: EmojiGroup.component, + emojiSubgroup: EmojiSubgroup.hairStyle, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'hair', + 'afro', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + "'fro", + 'curls', + 'frizzy', + 'perm' + ]), + Emoji( + name: 'red hair', + char: '\u{1F9B0}', + shortName: 'red_haired', + emojiGroup: EmojiGroup.component, + emojiSubgroup: EmojiSubgroup.hairStyle, + keywords: [ + 'uc11', + 'diversity', + 'body', + 'hair', + 'ginger', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy' + ]), + Emoji( + name: 'white hair', + char: '\u{1F9B3}', + shortName: 'white_haired', + emojiGroup: EmojiGroup.component, + emojiSubgroup: EmojiSubgroup.hairStyle, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'body', + 'hair', + 'grey hair', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'silver hair' + ]), + Emoji( + name: 'bald', + char: '\u{1F9B2}', + shortName: 'bald', + emojiGroup: EmojiGroup.component, + emojiSubgroup: EmojiSubgroup.hairStyle, + keywords: [ + 'uc11', + 'old people', + 'diversity', + 'body', + 'shaved head', + 'hairless', + 'grandparents', + 'elderly', + 'grandma', + 'grandpa', + 'grandmother', + 'grandfather', + 'mamie', + 'papy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'body part', + 'anatomy', + 'balding' + ]), + Emoji( + name: 'dog face', + char: '\u{1F436}', + shortName: 'dog', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'dog', + 'face', + 'pet', + 'uc6', + 'animal', + 'dog', + 'pug', + 'harry potter', + 'bingo', + 'bitch', + 'pets', + 'pokemon', + 'minecraft', + 'animals', + 'animal kingdom', + 'puppy', + 'doggy', + 'memes', + 'dogs', + 'perro', + 'puppies', + 'chien', + 'pugs', + 'puta', + 'pute' + ]), + Emoji( + name: 'cat face', + char: '\u{1F431}', + shortName: 'cat', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'cat', + 'face', + 'pet', + 'uc6', + 'animal', + 'halloween', + 'cat', + 'vagina', + 'pussy', + 'glitter', + 'pets', + 'pokemon', + 'porn', + 'animals', + 'animal kingdom', + 'samhain', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow', + 'condom' + ]), + Emoji( + name: 'mouse face', + char: '\u{1F42D}', + shortName: 'mouse', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'face', + 'mouse', + 'uc6', + 'animal', + 'mickey', + 'disney', + 'pokemon', + 'rodent', + 'animals', + 'animal kingdom', + 'cartoon' + ]), + Emoji( + name: 'hamster', + char: '\u{1F439}', + shortName: 'hamster', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'face', + 'hamster', + 'pet', + 'uc6', + 'animal', + 'pets', + 'rodent', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'rabbit face', + char: '\u{1F430}', + shortName: 'rabbit', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'bunny', + 'face', + 'pet', + 'rabbit', + 'uc6', + 'animal', + 'wildlife', + 'magic', + 'easter', + 'pets', + 'pokemon', + 'playboy', + 'animals', + 'animal kingdom', + 'spell', + 'genie', + 'magical', + 'play boy' + ]), + Emoji( + name: 'fox', + char: '\u{1F98A}', + shortName: 'fox', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'face', + 'fox', + 'uc9', + 'animal', + 'wildlife', + 'forest', + 'animals', + 'animal kingdom', + 'rainforest' + ]), + Emoji( + name: 'bear', + char: '\u{1F43B}', + shortName: 'bear', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'bear', + 'face', + 'uc6', + 'animal', + 'wildlife', + 'roar', + 'gummy', + 'california', + 'forest', + 'animals', + 'animal kingdom', + 'rawr', + 'rainforest' + ]), + Emoji( + name: 'panda', + char: '\u{1F43C}', + shortName: 'panda_face', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'face', + 'panda', + 'uc6', + 'animal', + 'wildlife', + 'roar', + 'chinese', + 'forest', + 'animals', + 'animal kingdom', + 'rawr', + 'chinois', + 'asian', + 'chine', + 'rainforest' + ]), + Emoji( + name: 'polar bear', + char: '\u{1F43B}\u{200D}\u{2744}\u{FE0F}', + shortName: 'polar_bear', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: ['uc13', 'animal', 'polar bear', 'animals', 'animal kingdom']), + Emoji( + name: 'koala', + char: '\u{1F428}', + shortName: 'koala', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'bear', + 'uc6', + 'animal', + 'wildlife', + 'australia', + 'forest', + 'marsupial', + 'animals', + 'animal kingdom', + 'rainforest', + 'marsupials' + ]), + Emoji( + name: 'tiger face', + char: '\u{1F42F}', + shortName: 'tiger', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'face', + 'tiger', + 'uc6', + 'animal', + 'wildlife', + 'roar', + 'cat', + 'forest', + 'animals', + 'animal kingdom', + 'rawr', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow', + 'rainforest' + ]), + Emoji( + name: 'lion', + char: '\u{1F981}', + shortName: 'lion_face', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'Leo', + 'face', + 'lion', + 'zodiac', + 'uc8', + 'animal', + 'wildlife', + 'roar', + 'cat', + 'england', + 'forest', + 'animals', + 'animal kingdom', + 'rawr', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow', + 'united kingdom', + 'london', + 'uk', + 'rainforest' + ]), + Emoji( + name: 'cow face', + char: '\u{1F42E}', + shortName: 'cow', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'cow', + 'face', + 'uc6', + 'animal', + 'farm', + 'texas', + 'minecraft', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'pig face', + char: '\u{1F437}', + shortName: 'pig', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'face', + 'pig', + 'uc6', + 'animal', + 'pig', + 'farm', + 'guinea pig', + 'pets', + 'minecraft', + 'animals', + 'animal kingdom', + 'pork' + ]), + Emoji( + name: 'pig nose', + char: '\u{1F43D}', + shortName: 'pig_nose', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'face', + 'nose', + 'pig', + 'uc6', + 'animal', + 'pig', + 'guinea pig', + 'pets', + 'animals', + 'animal kingdom', + 'pork' + ]), + Emoji( + name: 'frog', + char: '\u{1F438}', + shortName: 'frog', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalAmphibian, + keywords: [ + 'face', + 'frog', + 'uc6', + 'animal', + 'wildlife', + 'forest', + 'pets', + 'pokemon', + 'river', + 'animals', + 'animal kingdom', + 'rainforest' + ]), + Emoji( + name: 'monkey face', + char: '\u{1F435}', + shortName: 'monkey_face', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'face', + 'monkey', + 'uc6', + 'animal', + 'monkey', + 'animals', + 'animal kingdom', + 'progi', + 'ape', + 'primate' + ]), + Emoji( + name: 'see-no-evil monkey', + char: '\u{1F648}', + shortName: 'see_no_evil', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.monkeyFace, + keywords: [ + 'evil', + 'face', + 'forbidden', + 'gesture', + 'monkey', + 'no', + 'not', + 'prohibited', + 'see', + 'uc6', + 'animal', + 'monkey', + 'porn', + 'shame', + 'animals', + 'animal kingdom', + 'progi', + 'ape', + 'primate' + ]), + Emoji( + name: 'hear-no-evil monkey', + char: '\u{1F649}', + shortName: 'hear_no_evil', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.monkeyFace, + keywords: [ + 'evil', + 'face', + 'forbidden', + 'gesture', + 'hear', + 'monkey', + 'no', + 'not', + 'prohibited', + 'uc6', + 'animal', + 'monkey', + 'animals', + 'animal kingdom', + 'progi', + 'ape', + 'primate' + ]), + Emoji( + name: 'speak-no-evil monkey', + char: '\u{1F64A}', + shortName: 'speak_no_evil', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.monkeyFace, + keywords: [ + 'evil', + 'face', + 'forbidden', + 'gesture', + 'monkey', + 'no', + 'not', + 'prohibited', + 'speak', + 'uc6', + 'animal', + 'monkey', + 'facebook', + 'quiet', + 'animals', + 'animal kingdom', + 'progi', + 'ape', + 'primate', + 'shut up', + 'hushed', + 'silence', + 'silent', + 'shush', + 'shh' + ]), + Emoji( + name: 'monkey', + char: '\u{1F412}', + shortName: 'monkey', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'monkey', + 'uc6', + 'animal', + 'wildlife', + 'monkey', + 'forest', + 'pokemon', + 'animals', + 'animal kingdom', + 'progi', + 'ape', + 'primate', + 'rainforest' + ]), + Emoji( + name: 'chicken', + char: '\u{1F414}', + shortName: 'chicken', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'bird', + 'chicken', + 'uc6', + 'animal', + 'birds', + 'farm', + 'jewish', + 'pets', + 'minecraft', + 'animals', + 'animal kingdom', + 'goose', + 'hannukah', + 'hanukkah', + 'israel' + ]), + Emoji( + name: 'penguin', + char: '\u{1F427}', + shortName: 'penguin', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'bird', + 'penguin', + 'uc6', + 'animal', + 'wildlife', + 'birds', + 'ocean', + 'animals', + 'animal kingdom', + 'goose', + 'sea' + ]), + Emoji( + name: 'bird', + char: '\u{1F426}', + shortName: 'bird', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'bird', + 'uc6', + 'animal', + 'wildlife', + 'twitter', + 'birds', + 'forest', + 'pets', + 'pokemon', + 'animals', + 'animal kingdom', + 'goose', + 'rainforest' + ]), + Emoji( + name: 'baby chick', + char: '\u{1F424}', + shortName: 'baby_chick', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'baby', + 'bird', + 'chick', + 'uc6', + 'animal', + 'easter', + 'birds', + 'pets', + 'animals', + 'animal kingdom', + 'goose' + ]), + Emoji( + name: 'hatching chick', + char: '\u{1F423}', + shortName: 'hatching_chick', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'baby', + 'bird', + 'chick', + 'hatching', + 'uc6', + 'animal', + 'easter', + 'birds', + 'eggs', + 'pets', + 'animals', + 'animal kingdom', + 'goose' + ]), + Emoji( + name: 'front-facing baby chick', + char: '\u{1F425}', + shortName: 'hatched_chick', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'baby', + 'bird', + 'chick', + 'uc6', + 'animal', + 'easter', + 'birds', + 'pets', + 'pokemon', + 'minecraft', + 'animals', + 'animal kingdom', + 'goose' + ]), + Emoji( + name: 'duck', + char: '\u{1F986}', + shortName: 'duck', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'bird', + 'duck', + 'uc9', + 'animal', + 'wildlife', + 'quack', + 'birds', + 'hunt', + 'animals', + 'animal kingdom', + 'goose' + ]), + Emoji( + name: 'dodo', + char: '\u{1F9A4}', + shortName: 'dodo', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'uc13', + 'animal', + 'birds', + 'animals', + 'animal kingdom', + 'goose' + ]), + Emoji( + name: 'eagle', + char: '\u{1F985}', + shortName: 'eagle', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'bird', + 'eagle', + 'uc9', + 'animal', + 'wildlife', + 'america', + 'birds', + 'forest', + 'pokemon', + 'independence day', + 'animals', + 'animal kingdom', + 'usa', + 'united states', + 'united states of america', + 'american', + 'goose', + 'rainforest', + '4th of july' + ]), + Emoji( + name: 'owl', + char: '\u{1F989}', + shortName: 'owl', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'bird', + 'owl', + 'wise', + 'uc9', + 'animal', + 'wildlife', + 'halloween', + 'birds', + 'forest', + 'animals', + 'animal kingdom', + 'samhain', + 'goose', + 'rainforest' + ]), + Emoji( + name: 'bat', + char: '\u{1F987}', + shortName: 'bat', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'bat', + 'vampire', + 'uc9', + 'animal', + 'wildlife', + 'halloween', + 'harry potter', + 'forest', + 'pokemon', + 'super hero', + 'vampire', + 'animals', + 'animal kingdom', + 'samhain', + 'rainforest', + 'superhero', + 'superman', + 'batman', + 'dracula' + ]), + Emoji( + name: 'wolf', + char: '\u{1F43A}', + shortName: 'wolf', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'face', + 'wolf', + 'uc6', + 'animal', + 'wildlife', + 'roar', + 'forest', + 'pokemon', + 'minecraft', + 'animals', + 'animal kingdom', + 'rawr', + 'rainforest' + ]), + Emoji( + name: 'boar', + char: '\u{1F417}', + shortName: 'boar', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'pig', + 'uc6', + 'animal', + 'wildlife', + 'pig', + 'farm', + 'forest', + 'guinea pig', + 'hunt', + 'animals', + 'animal kingdom', + 'pork', + 'rainforest' + ]), + Emoji( + name: 'horse face', + char: '\u{1F434}', + shortName: 'horse', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'face', + 'horse', + 'uc6', + 'animal', + 'wildlife', + 'horse racing', + 'donkey', + 'farm', + 'pets', + 'pokemon', + 'texas', + 'minecraft', + 'horse', + 'animals', + 'animal kingdom', + 'horseback riding', + 'horse and rider', + 'horses', + 'horseshoe', + 'pony', + 'poney' + ]), + Emoji( + name: 'unicorn', + char: '\u{1F984}', + shortName: 'unicorn', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'face', + 'unicorn', + 'uc8', + 'animal', + 'halloween', + 'emojione', + 'hug', + 'lgbt', + 'unicorn', + 'pink', + 'facebook', + 'dream', + 'pokemon', + 'fantasy', + 'animals', + 'animal kingdom', + 'samhain', + 'emoji one', + 'embrace', + 'hugs', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'unicorns', + 'onesie', + 'rose', + 'dreams' + ]), + Emoji( + name: 'honeybee', + char: '\u{1F41D}', + shortName: 'bee', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'bee', + 'insect', + 'uc6', + 'animal', + 'wildlife', + 'insects', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug' + ]), + Emoji( + name: 'bug', + char: '\u{1F41B}', + shortName: 'bug', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'insect', + 'uc6', + 'animal', + 'wildlife', + 'insects', + 'gummy', + 'forest', + 'pokemon', + 'worm', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug', + 'rainforest', + 'caterpillar' + ]), + Emoji( + name: 'butterfly', + char: '\u{1F98B}', + shortName: 'butterfly', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'butterfly', + 'insect', + 'pretty', + 'uc9', + 'animal', + 'wildlife', + 'insects', + 'forest', + 'pokemon', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug', + 'rainforest' + ]), + Emoji( + name: 'snail', + char: '\u{1F40C}', + shortName: 'snail', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'snail', + 'uc6', + 'animal', + 'wildlife', + 'insects', + 'emojione', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug', + 'emoji one' + ]), + Emoji( + name: 'worm', + char: '\u{1FAB1}', + shortName: 'worm', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: ['uc13', 'animal', 'wildlife', 'animals', 'animal kingdom']), + Emoji( + name: 'lady beetle', + char: '\u{1F41E}', + shortName: 'lady_beetle', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'beetle', + 'insect', + 'ladybird', + 'ladybug', + 'uc6', + 'animal', + 'wildlife', + 'insects', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug' + ]), + Emoji( + name: 'ant', + char: '\u{1F41C}', + shortName: 'ant', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'insect', + 'uc6', + 'animal', + 'wildlife', + 'insects', + 'pokemon', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug' + ]), + Emoji( + name: 'fly', + char: '\u{1FAB0}', + shortName: 'fly', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'uc13', + 'animal', + 'wildlife', + 'insects', + 'shit', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug', + 'poop', + 'turd', + 'feces', + 'pile', + 'merde', + 'butthole', + 'caca', + 'crap', + 'dirty', + 'pooo', + 'mess', + 'brown', + 'poopoo' + ]), + Emoji( + name: 'mosquito', + char: '\u{1F99F}', + shortName: 'mosquito', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'uc11', + 'animal', + 'wildlife', + 'insects', + 'bite', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug' + ]), + Emoji( + name: 'cockroach', + char: '\u{1FAB3}', + shortName: 'cockroach', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'uc13', + 'animal', + 'wildlife', + 'insects', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug' + ]), + Emoji( + name: 'beetle', + char: '\u{1FAB2}', + shortName: 'beetle', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'uc13', + 'animal', + 'wildlife', + 'insects', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug' + ]), + Emoji( + name: 'cricket', + char: '\u{1F997}', + shortName: 'cricket', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'uc10', + 'animal', + 'wildlife', + 'insects', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug' + ]), + Emoji( + name: 'spider', + char: '\u{1F577}\u{FE0F}', + shortName: 'spider', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'insect', + 'uc7', + 'animal', + 'wildlife', + 'insects', + 'halloween', + 'australia', + 'harry potter', + 'forest', + 'pets', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug', + 'samhain', + 'rainforest' + ]), + Emoji( + name: 'spider web', + char: '\u{1F578}\u{FE0F}', + shortName: 'spider_web', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'spider', + 'web', + 'uc7', + 'halloween', + 'forest', + 'samhain', + 'rainforest' + ]), + Emoji( + name: 'scorpion', + char: '\u{1F982}', + shortName: 'scorpion', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'Scorpius', + 'scorpio', + 'zodiac', + 'uc8', + 'animal', + 'wildlife', + 'insects', + 'reptile', + 'animals', + 'animal kingdom', + 'insect', + 'bugs', + 'bug', + 'reptiles' + ]), + Emoji( + name: 'turtle', + char: '\u{1F422}', + shortName: 'turtle', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalReptile, + keywords: [ + 'terrapin', + 'tortoise', + 'turtle', + 'uc6', + 'animal', + 'wildlife', + 'reptile', + 'pets', + 'pokemon', + 'river', + 'ocean', + 'animals', + 'animal kingdom', + 'reptiles', + 'sea' + ]), + Emoji( + name: 'snake', + char: '\u{1F40D}', + shortName: 'snake', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalReptile, + keywords: [ + 'Ophiuchus', + 'bearer', + 'serpent', + 'zodiac', + 'uc6', + 'animal', + 'wildlife', + 'reptile', + 'creationism', + 'australia', + 'harry potter', + 'forest', + 'pets', + 'pokemon', + 'ocean', + 'animals', + 'animal kingdom', + 'reptiles', + 'adam & eve', + 'adam and eve', + 'rainforest', + 'sea' + ]), + Emoji( + name: 'lizard', + char: '\u{1F98E}', + shortName: 'lizard', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalReptile, + keywords: [ + 'lizard', + 'reptile', + 'uc9', + 'animal', + 'wildlife', + 'reptile', + 'forest', + 'pets', + 'pokemon', + 'animals', + 'animal kingdom', + 'reptiles', + 'rainforest' + ]), + Emoji( + name: 'T-Rex', + char: '\u{1F996}', + shortName: 't_rex', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalReptile, + keywords: [ + 'Tyrannosaurus Rex', + 'uc10', + 'animal', + 'dinosaur', + 'Tyrannosaurus Rex', + 'animals', + 'animal kingdom', + 'trex', + 't rex' + ]), + Emoji( + name: 'sauropod', + char: '\u{1F995}', + shortName: 'sauropod', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalReptile, + keywords: [ + 'brachiosaurus', + 'brontosaurus', + 'diplodocus', + 'uc10', + 'animal', + 'dinosaur', + 'Brontosaurus', + 'animals', + 'animal kingdom', + 'Diplodocus', + 'Brachiosaurus' + ]), + Emoji( + name: 'octopus', + char: '\u{1F419}', + shortName: 'octopus', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMarine, + keywords: [ + 'octopus', + 'uc6', + 'animal', + 'wildlife', + 'pussy', + 'pokemon', + 'porn', + 'scuba', + 'seafood', + 'ocean', + 'animals', + 'animal kingdom', + 'condom', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'squid', + char: '\u{1F991}', + shortName: 'squid', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodMarine, + keywords: [ + 'food', + 'molusc', + 'squid', + 'uc9', + 'animal', + 'wildlife', + 'scuba', + 'seafood', + 'ocean', + 'animals', + 'animal kingdom', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'shrimp', + char: '\u{1F990}', + shortName: 'shrimp', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodMarine, + keywords: [ + 'food', + 'shellfish', + 'shrimp', + 'small', + 'uc9', + 'animal', + 'wildlife', + 'prawn', + 'scuba', + 'seafood', + 'ocean', + 'crustacean', + 'animals', + 'animal kingdom', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'lobster', + char: '\u{1F99E}', + shortName: 'lobster', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodMarine, + keywords: [ + 'uc11', + 'animal', + 'food', + 'wildlife', + 'seafood', + 'ocean', + 'crustacean', + 'animals', + 'animal kingdom', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'sea' + ]), + Emoji( + name: 'crab', + char: '\u{1F980}', + shortName: 'crab', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodMarine, + keywords: [ + 'Cancer', + 'zodiac', + 'uc8', + 'animal', + 'wildlife', + 'tropical', + 'pokemon', + 'scuba', + 'seafood', + 'ocean', + 'crustacean', + 'animals', + 'animal kingdom', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'blowfish', + char: '\u{1F421}', + shortName: 'blowfish', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMarine, + keywords: [ + 'fish', + 'uc6', + 'animal', + 'wildlife', + 'japan', + 'scuba', + 'ocean', + 'animals', + 'animal kingdom', + 'japanese', + 'ninja', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'tropical fish', + char: '\u{1F420}', + shortName: 'tropical_fish', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMarine, + keywords: [ + 'fish', + 'tropical', + 'uc6', + 'animal', + 'wildlife', + 'tropical', + 'pets', + 'scuba', + 'ocean', + 'animals', + 'animal kingdom', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'fish', + char: '\u{1F41F}', + shortName: 'fish', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMarine, + keywords: [ + 'Pisces', + 'zodiac', + 'uc6', + 'animal', + 'wildlife', + 'tropical', + 'pets', + 'scuba', + 'seafood', + 'river', + 'ocean', + 'animals', + 'animal kingdom', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'seal', + char: '\u{1F9AD}', + shortName: 'seal', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMarine, + keywords: [ + 'uc13', + 'animal', + 'wildlife', + 'ocean', + 'animals', + 'animal kingdom', + 'sea' + ]), + Emoji( + name: 'dolphin', + char: '\u{1F42C}', + shortName: 'dolphin', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMarine, + keywords: [ + 'flipper', + 'uc6', + 'animal', + 'wildlife', + 'tropical', + 'florida', + 'scuba', + 'ocean', + 'animals', + 'animal kingdom', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'spouting whale', + char: '\u{1F433}', + shortName: 'whale', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMarine, + keywords: [ + 'face', + 'spouting', + 'whale', + 'uc6', + 'animal', + 'wildlife', + 'tropical', + 'whales', + 'scuba', + 'ocean', + 'animals', + 'animal kingdom', + 'whale', + 'moby', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'whale', + char: '\u{1F40B}', + shortName: 'whale2', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMarine, + keywords: [ + 'whale', + 'uc6', + 'animal', + 'wildlife', + 'tropical', + 'whales', + 'scuba', + 'ocean', + 'animals', + 'animal kingdom', + 'whale', + 'moby', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'shark', + char: '\u{1F988}', + shortName: 'shark', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMarine, + keywords: [ + 'fish', + 'shark', + 'uc9', + 'animal', + 'wildlife', + 'florida', + 'scuba', + 'ocean', + 'animals', + 'animal kingdom', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'crocodile', + char: '\u{1F40A}', + shortName: 'crocodile', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalReptile, + keywords: [ + 'crocodile', + 'uc6', + 'animal', + 'wildlife', + 'reptile', + 'florida', + 'river', + 'alligator', + 'animals', + 'animal kingdom', + 'reptiles' + ]), + Emoji( + name: 'tiger', + char: '\u{1F405}', + shortName: 'tiger2', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'tiger', + 'uc6', + 'animal', + 'wildlife', + 'roar', + 'cat', + 'circus', + 'forest', + 'pokemon', + 'animals', + 'animal kingdom', + 'rawr', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow', + 'circus tent', + 'clown', + 'clowns', + 'rainforest' + ]), + Emoji( + name: 'leopard', + char: '\u{1F406}', + shortName: 'leopard', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'leopard', + 'uc6', + 'animal', + 'wildlife', + 'roar', + 'cat', + 'forest', + 'animals', + 'animal kingdom', + 'rawr', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow', + 'rainforest' + ]), + Emoji( + name: 'zebra', + char: '\u{1F993}', + shortName: 'zebra', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'stripe', + 'uc10', + 'animal', + 'wildlife', + 'horse', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'gorilla', + char: '\u{1F98D}', + shortName: 'gorilla', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'gorilla', + 'uc9', + 'animal', + 'wildlife', + 'forest', + 'pokemon', + 'animals', + 'animal kingdom', + 'rainforest' + ]), + Emoji( + name: 'orangutan', + char: '\u{1F9A7}', + shortName: 'orangutan', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'uc12', + 'animal', + 'wildlife', + 'monkey', + 'animals', + 'animal kingdom', + 'progi', + 'ape', + 'primate' + ]), + Emoji( + name: 'elephant', + char: '\u{1F418}', + shortName: 'elephant', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'elephant', + 'uc6', + 'animal', + 'wildlife', + 'circus', + 'elephant', + 'animals', + 'animal kingdom', + 'circus tent', + 'clown', + 'clowns' + ]), + Emoji( + name: 'mammoth', + char: '\u{1F9A3}', + shortName: 'mammoth', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'uc13', + 'animal', + 'wildlife', + 'mammuthus', + 'elephant', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'bison', + char: '\u{1F9AC}', + shortName: 'bison', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: ['uc13', 'animal', 'wildlife', 'animals', 'animal kingdom']), + Emoji( + name: 'hippopotamus', + char: '\u{1F99B}', + shortName: 'hippopotamus', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: ['uc11', 'animal', 'wildlife', 'animals', 'animal kingdom']), + Emoji( + name: 'rhinoceros', + char: '\u{1F98F}', + shortName: 'rhino', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'rhinoceros', + 'uc9', + 'animal', + 'wildlife', + 'forest', + 'pokemon', + 'animals', + 'animal kingdom', + 'rainforest' + ]), + Emoji( + name: 'camel', + char: '\u{1F42A}', + shortName: 'dromedary_camel', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'dromedary', + 'hump', + 'uc6', + 'animal', + 'wildlife', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'two-hump camel', + char: '\u{1F42B}', + shortName: 'camel', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'bactrian', + 'camel', + 'hump', + 'uc6', + 'animal', + 'wildlife', + 'hump day', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'giraffe', + char: '\u{1F992}', + shortName: 'giraffe', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'spots', + 'uc10', + 'animal', + 'wildlife', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'kangaroo', + char: '\u{1F998}', + shortName: 'kangaroo', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'uc11', + 'animal', + 'wildlife', + 'australia', + 'marsupial', + 'animals', + 'animal kingdom', + 'marsupials' + ]), + Emoji( + name: 'water buffalo', + char: '\u{1F403}', + shortName: 'water_buffalo', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'buffalo', + 'water', + 'uc6', + 'animal', + 'wildlife', + 'scotland', + 'animals', + 'animal kingdom', + 'scottish' + ]), + Emoji( + name: 'ox', + char: '\u{1F402}', + shortName: 'ox', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'Taurus', + 'bull', + 'zodiac', + 'uc6', + 'animal', + 'farm', + 'texas', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'cow', + char: '\u{1F404}', + shortName: 'cow2', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'cow', + 'uc6', + 'animal', + 'farm', + 'texas', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'horse', + char: '\u{1F40E}', + shortName: 'racehorse', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'equestrian', + 'racehorse', + 'racing', + 'uc6', + 'animal', + 'wildlife', + 'horse racing', + 'donkey', + 'farm', + 'pets', + 'pokemon', + 'texas', + 'viking', + 'horse', + 'animals', + 'animal kingdom', + 'horseback riding', + 'horse and rider', + 'horses', + 'horseshoe', + 'pony', + 'poney', + 'knight' + ]), + Emoji( + name: 'pig', + char: '\u{1F416}', + shortName: 'pig2', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'sow', + 'uc6', + 'animal', + 'pink', + 'pig', + 'farm', + 'guinea pig', + 'pets', + 'minecraft', + 'animals', + 'animal kingdom', + 'rose', + 'pork' + ]), + Emoji( + name: 'ram', + char: '\u{1F40F}', + shortName: 'ram', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'Aries', + 'male', + 'sheep', + 'zodiac', + 'uc6', + 'animal', + 'wildlife', + 'farm', + 'sheep', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'ewe', + char: '\u{1F411}', + shortName: 'sheep', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'female', + 'sheep', + 'uc6', + 'animal', + 'farm', + 'lamb', + 'sheep', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'llama', + char: '\u{1F999}', + shortName: 'llama', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: ['uc11', 'animal', 'farm', 'animals', 'animal kingdom']), + Emoji( + name: 'goat', + char: '\u{1F410}', + shortName: 'goat', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'Capricorn', + 'zodiac', + 'uc6', + 'animal', + 'farm', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'deer', + char: '\u{1F98C}', + shortName: 'deer', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'deer', + 'uc9', + 'animal', + 'wildlife', + 'christmas', + 'raindeer', + 'forest', + 'hunt', + 'animals', + 'animal kingdom', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'moose', + 'rudolph', + 'rainforest' + ]), + Emoji( + name: 'dog', + char: '\u{1F415}', + shortName: 'dog2', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'pet', + 'uc6', + 'animal', + 'dog', + 'japan', + 'pug', + 'bingo', + 'bitch', + 'farm', + 'pets', + 'pokemon', + 'minecraft', + 'hunt', + 'animals', + 'animal kingdom', + 'puppy', + 'doggy', + 'memes', + 'dogs', + 'perro', + 'puppies', + 'chien', + 'japanese', + 'ninja', + 'pugs', + 'puta', + 'pute' + ]), + Emoji( + name: 'poodle', + char: '\u{1F429}', + shortName: 'poodle', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'dog', + 'uc6', + 'animal', + 'dog', + 'pink', + 'paris', + 'bitch', + 'pets', + 'animals', + 'animal kingdom', + 'puppy', + 'doggy', + 'memes', + 'dogs', + 'perro', + 'puppies', + 'chien', + 'rose', + 'french', + 'france', + 'puta', + 'pute' + ]), + Emoji( + name: 'guide dog', + char: '\u{1F9AE}', + shortName: 'guide_dog', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'uc12', + 'animal', + 'dog', + 'handicap', + 'pets', + 'blind', + 'animals', + 'animal kingdom', + 'puppy', + 'doggy', + 'memes', + 'dogs', + 'perro', + 'puppies', + 'chien', + 'disabled', + 'disability' + ]), + Emoji( + name: 'service dog', + char: '\u{1F415}\u{200D}\u{1F9BA}', + shortName: 'service_dog', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'uc12', + 'animal', + 'dog', + 'handicap', + 'pets', + 'blind', + 'animals', + 'animal kingdom', + 'puppy', + 'doggy', + 'memes', + 'dogs', + 'perro', + 'puppies', + 'chien', + 'disabled', + 'disability' + ]), + Emoji( + name: 'cat', + char: '\u{1F408}', + shortName: 'cat2', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'pet', + 'uc6', + 'animal', + 'cat', + 'pussy', + 'grass', + 'farm', + 'pets', + 'pokemon', + 'porn', + 'animals', + 'animal kingdom', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow', + 'condom' + ]), + Emoji( + name: 'black cat', + char: '\u{1F408}\u{200D}\u{2B1B}', + shortName: 'black_cat', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'uc13', + 'animal', + 'halloween', + 'cat', + 'luck', + 'sol', + 'farm', + 'animals', + 'animal kingdom', + 'samhain', + 'kitty', + 'kitten', + 'cats', + 'kittens', + 'kitties', + 'feline', + 'felines', + 'cat face', + 'gato', + 'meow', + 'good luck', + 'lucky', + 'shit outta luck', + 'shit out of luck', + 'bad luck' + ]), + Emoji( + name: 'rooster', + char: '\u{1F413}', + shortName: 'rooster', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'bird', + 'rooster', + 'uc6', + 'animal', + 'wildlife', + 'birds', + 'farm', + 'animals', + 'animal kingdom', + 'goose' + ]), + Emoji( + name: 'turkey', + char: '\u{1F983}', + shortName: 'turkey', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'bird', + 'turkey', + 'uc8', + 'animal', + 'wildlife', + 'birds', + 'farm', + 'thanksgiving', + 'animals', + 'animal kingdom', + 'goose' + ]), + Emoji( + name: 'peacock', + char: '\u{1F99A}', + shortName: 'peacock', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'uc11', + 'animal', + 'wildlife', + 'birds', + 'farm', + 'animals', + 'animal kingdom', + 'goose' + ]), + Emoji( + name: 'parrot', + char: '\u{1F99C}', + shortName: 'parrot', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'uc11', + 'animal', + 'wildlife', + 'tropical', + 'pirate', + 'birds', + 'animals', + 'animal kingdom', + 'goose' + ]), + Emoji( + name: 'swan', + char: '\u{1F9A2}', + shortName: 'swan', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'uc11', + 'animal', + 'wildlife', + 'birds', + 'animals', + 'animal kingdom', + 'goose' + ]), + Emoji( + name: 'flamingo', + char: '\u{1F9A9}', + shortName: 'flamingo', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'uc12', + 'animal', + 'wildlife', + 'pink', + 'birds', + 'animals', + 'animal kingdom', + 'rose', + 'goose' + ]), + Emoji( + name: 'dove', + char: '\u{1F54A}\u{FE0F}', + shortName: 'dove', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'bird', + 'fly', + 'peace', + 'uc7', + 'animal', + 'wildlife', + 'wedding', + 'religion', + 'peace', + 'pray', + 'birds', + 'animals', + 'animal kingdom', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'peace out', + 'peace sign', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'goose' + ]), + Emoji( + name: 'rabbit', + char: '\u{1F407}', + shortName: 'rabbit2', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'bunny', + 'pet', + 'uc6', + 'animal', + 'wildlife', + 'magic', + 'easter', + 'pets', + 'pokemon', + 'hunt', + 'animals', + 'animal kingdom', + 'spell', + 'genie', + 'magical' + ]), + Emoji( + name: 'raccoon', + char: '\u{1F99D}', + shortName: 'raccoon', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'uc11', + 'animal', + 'wildlife', + 'forest', + 'animals', + 'animal kingdom', + 'rainforest' + ]), + Emoji( + name: 'skunk', + char: '\u{1F9A8}', + shortName: 'skunk', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'uc12', + 'animal', + 'wildlife', + 'stinky', + 'forest', + 'animals', + 'animal kingdom', + 'smell', + 'stink', + 'odor', + 'rainforest' + ]), + Emoji( + name: 'badger', + char: '\u{1F9A1}', + shortName: 'badger', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'uc11', + 'animal', + 'wildlife', + 'forest', + 'animals', + 'animal kingdom', + 'rainforest' + ]), + Emoji( + name: 'beaver', + char: '\u{1F9AB}', + shortName: 'beaver', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'uc13', + 'animal', + 'wildlife', + 'forest', + 'rodent', + 'beaver', + 'animals', + 'animal kingdom', + 'rainforest' + ]), + Emoji( + name: 'otter', + char: '\u{1F9A6}', + shortName: 'otter', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'uc12', + 'animal', + 'wildlife', + 'forest', + 'ocean', + 'otor', + 'animals', + 'animal kingdom', + 'rainforest', + 'sea', + 'oter', + 'wÃŗdrĖĨ' + ]), + Emoji( + name: 'sloth', + char: '\u{1F9A5}', + shortName: 'sloth', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'uc12', + 'animal', + 'wildlife', + 'forest', + 'lazy', + 'animals', + 'animal kingdom', + 'rainforest' + ]), + Emoji( + name: 'mouse', + char: '\u{1F401}', + shortName: 'mouse2', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'mouse', + 'uc6', + 'animal', + 'wildlife', + 'mickey', + 'disney', + 'pokemon', + 'rodent', + 'animals', + 'animal kingdom', + 'cartoon' + ]), + Emoji( + name: 'rat', + char: '\u{1F400}', + shortName: 'rat', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'rat', + 'uc6', + 'animal', + 'wildlife', + 'harry potter', + 'pokemon', + 'rodent', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'chipmunk', + char: '\u{1F43F}\u{FE0F}', + shortName: 'chipmunk', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'chipmunk', + 'uc7', + 'animal', + 'wildlife', + 'squirrel', + 'forest', + 'pokemon', + 'rodent', + 'animals', + 'animal kingdom', + 'rainforest' + ]), + Emoji( + name: 'hedgehog', + char: '\u{1F994}', + shortName: 'hedgehog', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'spiny', + 'uc10', + 'animal', + 'wildlife', + 'erinaceinae', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'paw prints', + char: '\u{1F43E}', + shortName: 'feet', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMammal, + keywords: [ + 'feet', + 'paw', + 'print', + 'uc6', + 'animal', + 'paws', + 'animals', + 'animal kingdom' + ]), + Emoji( + name: 'dragon', + char: '\u{1F409}', + shortName: 'dragon', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalReptile, + keywords: [ + 'fairy tale', + 'uc6', + 'animal', + 'roar', + 'reptile', + 'harry potter', + 'pokemon', + 'minecraft', + 'fantasy', + 'animals', + 'animal kingdom', + 'rawr', + 'reptiles' + ]), + Emoji( + name: 'dragon face', + char: '\u{1F432}', + shortName: 'dragon_face', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalReptile, + keywords: [ + 'dragon', + 'face', + 'fairy tale', + 'uc6', + 'animal', + 'roar', + 'monster', + 'reptile', + 'pokemon', + 'minecraft', + 'fantasy', + 'animals', + 'animal kingdom', + 'rawr', + 'monsters', + 'beast', + 'reptiles' + ]), + Emoji( + name: 'cactus', + char: '\u{1F335}', + shortName: 'cactus', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: [ + 'plant', + 'uc6', + 'nature', + 'plant', + 'trees', + 'plants', + 'tree', + 'branch', + 'wood' + ]), + Emoji( + name: 'Christmas tree', + char: '\u{1F384}', + shortName: 'christmas_tree', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'Christmas', + 'celebration', + 'tree', + 'uc6', + 'holidays', + 'plant', + 'christmas', + 'santa', + 'trees', + 'advent', + 'holiday', + 'plants', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'santa clause', + 'santa claus', + 'tree', + 'branch', + 'wood' + ]), + Emoji( + name: 'evergreen tree', + char: '\u{1F332}', + shortName: 'evergreen_tree', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: [ + 'tree', + 'uc6', + 'nature', + 'plant', + 'camp', + 'trees', + 'forest', + 'parks', + 'plants', + 'camping', + 'tent', + 'camper', + 'outdoor', + 'outside', + 'tree', + 'branch', + 'wood', + 'rainforest', + 'regional park', + 'nature park', + 'natural park' + ]), + Emoji( + name: 'deciduous tree', + char: '\u{1F333}', + shortName: 'deciduous_tree', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: [ + 'deciduous', + 'shedding', + 'tree', + 'uc6', + 'nature', + 'plant', + 'camp', + 'trees', + 'farm', + 'forest', + 'parks', + 'plants', + 'camping', + 'tent', + 'camper', + 'outdoor', + 'outside', + 'tree', + 'branch', + 'wood', + 'rainforest', + 'regional park', + 'nature park', + 'natural park' + ]), + Emoji( + name: 'palm tree', + char: '\u{1F334}', + shortName: 'palm_tree', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: [ + 'palm', + 'tree', + 'uc6', + 'nature', + 'plant', + 'tropical', + 'trees', + 'california', + 'coconut', + 'florida', + 'palma', + 'plants', + 'tree', + 'branch', + 'wood', + 'palmas' + ]), + Emoji( + name: 'seedling', + char: '\u{1F331}', + shortName: 'seedling', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: [ + 'young', + 'uc6', + 'nature', + 'plant', + 'trees', + 'leaf', + 'grass', + 'weed', + 'farm', + 'irish', + 'pokemon', + 'plants', + 'tree', + 'branch', + 'wood', + 'leaves', + 'saint patricks day', + 'st patricks day', + 'leprechaun' + ]), + Emoji( + name: 'herb', + char: '\u{1F33F}', + shortName: 'herb', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: [ + 'leaf', + 'uc6', + 'nature', + 'plant', + 'leaf', + 'grass', + 'weed', + 'farm', + 'plants', + 'leaves' + ]), + Emoji( + name: 'shamrock', + char: '\u{2618}\u{FE0F}', + shortName: 'shamrock', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: [ + 'plant', + 'uc4', + 'nature', + 'plant', + 'luck', + 'leaf', + 'grass', + 'irish', + 'plants', + 'good luck', + 'lucky', + 'leaves', + 'saint patricks day', + 'st patricks day', + 'leprechaun' + ]), + Emoji( + name: 'four leaf clover', + char: '\u{1F340}', + shortName: 'four_leaf_clover', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: [ + '4', + 'clover', + 'four', + 'leaf', + 'uc6', + 'nature', + 'plant', + 'luck', + 'leaf', + 'sol', + 'grass', + 'bingo', + 'irish', + 'plants', + 'good luck', + 'lucky', + 'leaves', + 'shit outta luck', + 'shit out of luck', + 'bad luck', + 'saint patricks day', + 'st patricks day', + 'leprechaun' + ]), + Emoji( + name: 'pine decoration', + char: '\u{1F38D}', + shortName: 'bamboo', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'Japanese', + 'bamboo', + 'celebration', + 'pine', + 'uc6', + 'nature', + 'plant', + 'japan', + 'plants', + 'japanese', + 'ninja' + ]), + Emoji( + name: 'tanabata tree', + char: '\u{1F38B}', + shortName: 'tanabata_tree', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'Japanese', + 'banner', + 'celebration', + 'tree', + 'uc6', + 'nature', + 'plant', + 'japan', + 'trees', + 'plants', + 'japanese', + 'ninja', + 'tree', + 'branch', + 'wood' + ]), + Emoji( + name: 'leaf fluttering in wind', + char: '\u{1F343}', + shortName: 'leaves', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: [ + 'blow', + 'flutter', + 'leaf', + 'wind', + 'uc6', + 'weather', + 'nature', + 'plant', + 'trees', + 'leaf', + 'storm', + 'autumn', + 'plants', + 'tree', + 'branch', + 'wood', + 'leaves', + 'fall' + ]), + Emoji( + name: 'fallen leaf', + char: '\u{1F342}', + shortName: 'fallen_leaf', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: [ + 'falling', + 'leaf', + 'uc6', + 'nature', + 'plant', + 'trees', + 'leaf', + 'autumn', + 'plants', + 'tree', + 'branch', + 'wood', + 'leaves', + 'fall' + ]), + Emoji( + name: 'maple leaf', + char: '\u{1F341}', + shortName: 'maple_leaf', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: [ + 'falling', + 'leaf', + 'maple', + 'uc6', + 'nature', + 'plant', + 'trees', + 'leaf', + 'autumn', + 'marijuana', + 'plants', + 'tree', + 'branch', + 'wood', + 'leaves', + 'fall' + ]), + Emoji( + name: 'feather', + char: '\u{1FAB6}', + shortName: 'feather', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBird, + keywords: [ + 'uc13', + 'animal', + 'nature', + 'lgbt', + 'birds', + 'plume', + 'animals', + 'animal kingdom', + 'homosexual', + 'bisex', + 'transgender', + 'non binary', + 'pansexuality', + 'intersex', + 'goose', + 'quill', + 'plumage', + 'feathering', + 'pluma', + 'piuma', + 'feder' + ]), + Emoji( + name: 'mushroom', + char: '\u{1F344}', + shortName: 'mushroom', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'toadstool', + 'uc6', + 'food', + 'nature', + 'vegetables', + 'drugs', + 'plant', + 'disney', + 'poison', + 'pokemon', + 'mushroom', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume', + 'drug', + 'narcotics', + 'plants', + 'cartoon', + 'toxic', + 'toxins' + ]), + Emoji( + name: 'spiral shell', + char: '\u{1F41A}', + shortName: 'shell', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalMarine, + keywords: [ + 'shell', + 'spiral', + 'uc6', + 'nature', + 'tropical', + 'scuba', + 'ocean', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'rock', + char: '\u{1FAA8}', + shortName: 'rock', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'uc13', + 'nature', + 'parks', + 'climb', + 'boulder', + 'regional park', + 'nature park', + 'natural park', + 'pebble' + ]), + Emoji( + name: 'wood', + char: '\u{1FAB5}', + shortName: 'wood', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'uc13', + 'nature', + 'beaver', + 'parks', + 'regional park', + 'nature park', + 'natural park' + ]), + Emoji( + name: 'sheaf of rice', + char: '\u{1F33E}', + shortName: 'ear_of_rice', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: [ + 'ear', + 'grain', + 'rice', + 'uc6', + 'nature', + 'plant', + 'leaf', + 'grass', + 'farm', + 'plants', + 'leaves' + ]), + Emoji( + name: 'potted plant', + char: '\u{1FAB4}', + shortName: 'potted_plant', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantOther, + keywords: ['uc13', 'nature', 'plant', 'leaf', 'plants', 'leaves']), + Emoji( + name: 'bouquet', + char: '\u{1F490}', + shortName: 'bouquet', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantFlower, + keywords: [ + 'flower', + 'uc6', + 'nature', + 'wedding', + 'flower', + 'plant', + 'love', + 'rip', + 'condolence', + 'beautiful', + 'roses', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'flowers', + 'plants', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'rest in peace', + 'compassion', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ]), + Emoji( + name: 'tulip', + char: '\u{1F337}', + shortName: 'tulip', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantFlower, + keywords: [ + 'flower', + 'uc6', + 'nature', + 'flower', + 'plant', + 'vagina', + 'beautiful', + 'girls night', + 'pink', + 'easter', + 'flowers', + 'plants', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'ladies night', + 'girls only', + 'girlfriend', + 'rose' + ]), + Emoji( + name: 'rose', + char: '\u{1F339}', + shortName: 'rose', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantFlower, + keywords: [ + 'flower', + 'uc6', + 'nature', + 'flower', + 'plant', + 'love', + 'rip', + 'condolence', + 'beautiful', + 'flowers', + 'plants', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'rest in peace', + 'compassion', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ]), + Emoji( + name: 'wilted flower', + char: '\u{1F940}', + shortName: 'wilted_rose', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantFlower, + keywords: [ + 'flower', + 'wilted', + 'uc9', + 'flower', + 'halloween', + 'plant', + 'dead', + 'flowers', + 'samhain', + 'plants', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died' + ]), + Emoji( + name: 'hibiscus', + char: '\u{1F33A}', + shortName: 'hibiscus', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantFlower, + keywords: [ + 'flower', + 'uc6', + 'nature', + 'flower', + 'plant', + 'tropical', + 'beautiful', + 'pink', + 'flowers', + 'plants', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'rose' + ]), + Emoji( + name: 'cherry blossom', + char: '\u{1F338}', + shortName: 'cherry_blossom', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantFlower, + keywords: [ + 'blossom', + 'cherry', + 'flower', + 'uc6', + 'nature', + 'flower', + 'plant', + 'japan', + 'tropical', + 'beautiful', + 'hawaii', + 'pink', + 'sakura', + 'flowers', + 'plants', + 'japanese', + 'ninja', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'rose' + ]), + Emoji( + name: 'blossom', + char: '\u{1F33C}', + shortName: 'blossom', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantFlower, + keywords: [ + 'flower', + 'uc6', + 'nature', + 'flower', + 'plant', + 'vagina', + 'beautiful', + 'flowers', + 'plants', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ]), + Emoji( + name: 'sunflower', + char: '\u{1F33B}', + shortName: 'sunflower', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantFlower, + keywords: [ + 'flower', + 'sun', + 'uc6', + 'nature', + 'flower', + 'plant', + 'beautiful', + 'farm', + 'flowers', + 'plants', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely' + ]), + Emoji( + name: 'sun with face', + char: '\u{1F31E}', + shortName: 'sun_with_face', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'bright', + 'face', + 'sun', + 'uc6', + 'sun', + 'sky', + 'day', + 'hump day', + 'morning', + 'sunglasses', + 'california', + 'pokemon', + 'las vegas', + 'summer', + 'sunshine', + 'sunny', + 'eclipse', + 'solar', + 'solareclipse', + 'shiny', + 'good morning', + 'shades', + 'lunettes de soleil', + 'sun glasses', + 'vegas', + 'weekend' + ]), + Emoji( + name: 'full moon face', + char: '\u{1F31D}', + shortName: 'full_moon_with_face', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'bright', + 'face', + 'full', + 'moon', + 'uc6', + 'halloween', + 'space', + 'sky', + 'moon', + 'goodnight', + 'samhain', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'first quarter moon face', + char: '\u{1F31B}', + shortName: 'first_quarter_moon_with_face', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'face', + 'moon', + 'quarter', + 'uc6', + 'space', + 'sky', + 'moon', + 'goodnight', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'last quarter moon face', + char: '\u{1F31C}', + shortName: 'last_quarter_moon_with_face', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'face', + 'moon', + 'quarter', + 'uc6', + 'space', + 'sky', + 'moon', + 'goodnight', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'new moon face', + char: '\u{1F31A}', + shortName: 'new_moon_with_face', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'face', + 'moon', + 'uc6', + 'space', + 'sky', + 'moon', + 'goodnight', + 'pokemon', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'full moon', + char: '\u{1F315}', + shortName: 'full_moon', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'full', + 'moon', + 'uc6', + 'halloween', + 'space', + 'sky', + 'moon', + 'goodnight', + 'samhain', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'waning gibbous moon', + char: '\u{1F316}', + shortName: 'waning_gibbous_moon', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'gibbous', + 'moon', + 'waning', + 'uc6', + 'space', + 'sky', + 'moon', + 'goodnight', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'last quarter moon', + char: '\u{1F317}', + shortName: 'last_quarter_moon', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'moon', + 'quarter', + 'uc6', + 'space', + 'sky', + 'moon', + 'goodnight', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'waning crescent moon', + char: '\u{1F318}', + shortName: 'waning_crescent_moon', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'crescent', + 'moon', + 'waning', + 'uc6', + 'space', + 'sky', + 'moon', + 'goodnight', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'new moon', + char: '\u{1F311}', + shortName: 'new_moon', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'dark', + 'moon', + 'uc6', + 'halloween', + 'space', + 'sky', + 'moon', + 'goodnight', + 'samhain', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'waxing crescent moon', + char: '\u{1F312}', + shortName: 'waxing_crescent_moon', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'crescent', + 'moon', + 'waxing', + 'uc6', + 'space', + 'sky', + 'moon', + 'goodnight', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'first quarter moon', + char: '\u{1F313}', + shortName: 'first_quarter_moon', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'moon', + 'quarter', + 'uc6', + 'space', + 'sky', + 'moon', + 'goodnight', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'waxing gibbous moon', + char: '\u{1F314}', + shortName: 'waxing_gibbous_moon', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'gibbous', + 'moon', + 'waxing', + 'uc6', + 'space', + 'sky', + 'moon', + 'goodnight', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'crescent moon', + char: '\u{1F319}', + shortName: 'crescent_moon', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'crescent', + 'moon', + 'uc6', + 'space', + 'sky', + 'moon', + 'goodnight', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'night', + 'moons', + 'lunar', + 'lunareclipse', + 'lunar eclipse' + ]), + Emoji( + name: 'globe showing Americas', + char: '\u{1F30E}', + shortName: 'earth_americas', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeMap, + keywords: [ + 'Americas', + 'earth', + 'globe', + 'world', + 'uc6', + 'weather', + 'america', + 'space', + 'map', + 'vacation', + 'globe', + 'history', + 'world', + 'usa', + 'united states', + 'united states of america', + 'american', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'maps', + 'location', + 'locate', + 'local', + 'lost', + 'globes', + 'planet', + 'earth', + 'earthquake', + 'ancient', + 'old' + ]), + Emoji( + name: 'globe showing Europe-Africa', + char: '\u{1F30D}', + shortName: 'earth_africa', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeMap, + keywords: [ + 'Africa', + 'Europe', + 'earth', + 'globe', + 'world', + 'uc6', + 'space', + 'map', + 'vacation', + 'globe', + 'history', + 'world', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'maps', + 'location', + 'locate', + 'local', + 'lost', + 'globes', + 'planet', + 'earth', + 'earthquake', + 'ancient', + 'old' + ]), + Emoji( + name: 'globe showing Asia-Australia', + char: '\u{1F30F}', + shortName: 'earth_asia', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeMap, + keywords: [ + 'Asia', + 'Australia', + 'earth', + 'globe', + 'world', + 'uc6', + 'space', + 'map', + 'vacation', + 'globe', + 'history', + 'world', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'maps', + 'location', + 'locate', + 'local', + 'lost', + 'globes', + 'planet', + 'earth', + 'earthquake', + 'ancient', + 'old' + ]), + Emoji( + name: 'ringed planet', + char: '\u{1FA90}', + shortName: 'ringed_planet', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'uc12', + 'space', + 'saturn', + 'world', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'saturnine' + ]), + Emoji( + name: 'dizzy', + char: '\u{1F4AB}', + shortName: 'dizzy', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'comic', + 'star', + 'uc6', + 'star', + 'star wars', + 'drunk', + 'hit', + 'anime', + 'stars', + 'flustered', + 'dizzy', + 'punch', + 'pow', + 'bam', + 'manga' + ]), + Emoji( + name: 'star', + char: '\u{2B50}', + shortName: 'star', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'star', + 'uc5', + 'space', + 'sky', + 'star', + 'star wars', + 'fame', + 'texas', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'stars', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'glowing star', + char: '\u{1F31F}', + shortName: 'star2', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'glittery', + 'glow', + 'shining', + 'sparkle', + 'star', + 'uc6', + 'space', + 'sky', + 'star', + 'christmas', + 'star wars', + 'fame', + 'sparkle', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'stars', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'famous', + 'celebrity', + 'bright', + 'shine', + 'twinkle' + ]), + Emoji( + name: 'sparkles', + char: '\u{2728}', + shortName: 'sparkles', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'sparkle', + 'star', + 'uc6', + 'star', + 'birthday', + 'girls night', + 'magic', + 'glitter', + 'bling', + 'fame', + 'sparkle', + 'stars', + 'birth', + 'cumpleaÃąos', + 'anniversaire', + 'bday', + 'ladies night', + 'girls only', + 'girlfriend', + 'spell', + 'genie', + 'magical', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'famous', + 'celebrity', + 'bright', + 'shine', + 'twinkle' + ]), + Emoji( + name: 'high voltage', + char: '\u{26A1}', + shortName: 'zap', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'danger', + 'electric', + 'electricity', + 'lightning', + 'voltage', + 'zap', + 'uc4', + 'weather', + 'halloween', + 'sky', + 'diarrhea', + 'lightning', + 'electric', + 'harry potter', + 'magic', + 'power', + 'storm', + 'bling', + 'pokemon', + 'samhain', + 'shits', + 'the shits', + 'spell', + 'genie', + 'magical', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure' + ]), + Emoji( + name: 'comet', + char: '\u{2604}\u{FE0F}', + shortName: 'comet', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'space', + 'uc1', + 'space', + 'sky', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship' + ]), + Emoji( + name: 'collision', + char: '\u{1F4A5}', + shortName: 'boom', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'boom', + 'comic', + 'uc6', + 'blast', + 'explosion', + 'power', + 'flame', + 'anime', + 'sparkle', + 'boom', + 'explode', + 'burn', + 'match', + 'flames', + 'manga', + 'bright', + 'shine', + 'twinkle' + ]), + Emoji( + name: 'fire', + char: '\u{1F525}', + shortName: 'fire', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'flame', + 'tool', + 'uc6', + 'love', + 'christmas', + 'wth', + 'hot', + 'harry potter', + 'flame', + 'jewish', + 'porn', + 'independence day', + 'fires', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'what the hell', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'burn', + 'match', + 'flames', + 'hannukah', + 'hanukkah', + 'israel', + '4th of july' + ]), + Emoji( + name: 'tornado', + char: '\u{1F32A}\u{FE0F}', + shortName: 'cloud_tornado', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cloud', + 'whirlwind', + 'uc7', + 'weather', + 'sky', + 'power', + 'storm', + 'clean', + 'texas' + ]), + Emoji( + name: 'rainbow', + char: '\u{1F308}', + shortName: 'rainbow', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'rain', + 'uc6', + 'weather', + 'gay', + 'sky', + 'rain', + 'rainbow', + 'gay pride', + 'hawaii', + 'color', + 'glitter', + 'easter', + 'irish', + 'mirror', + 'twink', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch', + 'saint patricks day', + 'st patricks day', + 'leprechaun' + ]), + Emoji( + name: 'sun', + char: '\u{2600}\u{FE0F}', + shortName: 'sunny', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'bright', + 'rays', + 'sunny', + 'uc1', + 'weather', + 'sun', + 'space', + 'sky', + 'day', + 'hot', + 'morning', + 'sunglasses', + 'power', + 'california', + 'las vegas', + 'summer', + 'independence day', + 'sunshine', + 'sunny', + 'eclipse', + 'solar', + 'solareclipse', + 'shiny', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'good morning', + 'shades', + 'lunettes de soleil', + 'sun glasses', + 'vegas', + 'weekend', + '4th of july' + ]), + Emoji( + name: 'sun behind small cloud', + char: '\u{1F324}\u{FE0F}', + shortName: 'white_sun_small_cloud', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cloud', + 'sun', + 'uc7', + 'weather', + 'sun', + 'sky', + 'cloud', + 'sunshine', + 'sunny', + 'eclipse', + 'solar', + 'solareclipse', + 'shiny', + 'clouds', + 'nuage' + ]), + Emoji( + name: 'sun behind cloud', + char: '\u{26C5}', + shortName: 'partly_sunny', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cloud', + 'sun', + 'uc5', + 'weather', + 'sun', + 'sky', + 'cloud', + 'sunshine', + 'sunny', + 'eclipse', + 'solar', + 'solareclipse', + 'shiny', + 'clouds', + 'nuage' + ]), + Emoji( + name: 'sun behind large cloud', + char: '\u{1F325}\u{FE0F}', + shortName: 'white_sun_cloud', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cloud', + 'sun', + 'uc7', + 'weather', + 'sun', + 'sky', + 'cloud', + 'cold', + 'sunshine', + 'sunny', + 'eclipse', + 'solar', + 'solareclipse', + 'shiny', + 'clouds', + 'nuage', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'cloud', + char: '\u{2601}\u{FE0F}', + shortName: 'cloud', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'weather', + 'uc1', + 'weather', + 'sky', + 'cloud', + 'cold', + 'dream', + 'clouds', + 'nuage', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'dreams' + ]), + Emoji( + name: 'sun behind rain cloud', + char: '\u{1F326}\u{FE0F}', + shortName: 'white_sun_rain_cloud', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cloud', + 'rain', + 'sun', + 'uc7', + 'weather', + 'sun', + 'sky', + 'cloud', + 'rain', + 'cold', + 'sunshine', + 'sunny', + 'eclipse', + 'solar', + 'solareclipse', + 'shiny', + 'clouds', + 'nuage', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'cloud with rain', + char: '\u{1F327}\u{FE0F}', + shortName: 'cloud_rain', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cloud', + 'rain', + 'uc7', + 'weather', + 'winter', + 'sky', + 'cloud', + 'rain', + 'cold', + 'clouds', + 'nuage', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'cloud with lightning and rain', + char: '\u{26C8}\u{FE0F}', + shortName: 'thunder_cloud_rain', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cloud', + 'rain', + 'thunder', + 'uc5', + 'weather', + 'sky', + 'cloud', + 'rain', + 'cold', + 'lightning', + 'storm', + 'clouds', + 'nuage', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'cloud with lightning', + char: '\u{1F329}\u{FE0F}', + shortName: 'cloud_lightning', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cloud', + 'lightning', + 'uc7', + 'weather', + 'halloween', + 'sky', + 'cloud', + 'rain', + 'cold', + 'lightning', + 'storm', + 'samhain', + 'clouds', + 'nuage', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'cloud with snow', + char: '\u{1F328}\u{FE0F}', + shortName: 'cloud_snow', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cloud', + 'cold', + 'snow', + 'uc7', + 'weather', + 'winter', + 'sky', + 'cloud', + 'snow', + 'cold', + 'clouds', + 'nuage', + 'freeze', + 'frozen', + 'frost', + 'ice cube', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'snowflake', + char: '\u{2744}\u{FE0F}', + shortName: 'snowflake', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cold', + 'snow', + 'uc1', + 'weather', + 'winter', + 'sky', + 'snow', + 'christmas', + 'cold', + 'freeze', + 'frozen', + 'frost', + 'ice cube', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'snowman', + char: '\u{2603}\u{FE0F}', + shortName: 'snowman2', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cold', + 'snow', + 'uc1', + 'weather', + 'holidays', + 'winter', + 'snow', + 'christmas', + 'cold', + 'holiday', + 'freeze', + 'frozen', + 'frost', + 'ice cube', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'snowman without snow', + char: '\u{26C4}', + shortName: 'snowman', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cold', + 'snow', + 'snowman', + 'uc5', + 'weather', + 'winter', + 'snow', + 'christmas', + 'cold', + 'freeze', + 'frozen', + 'frost', + 'ice cube', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'wind face', + char: '\u{1F32C}\u{FE0F}', + shortName: 'wind_blowing_face', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'blow', + 'cloud', + 'face', + 'wind', + 'uc7', + 'weather', + 'winter', + 'smoking', + 'cold', + 'power', + 'dream', + 'autumn', + 'breathe', + 'smoke', + 'cigarette', + 'puff', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'dreams', + 'fall', + 'sigh', + 'inhale' + ]), + Emoji( + name: 'dashing away', + char: '\u{1F4A8}', + shortName: 'dash', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'comic', + 'dash', + 'running', + 'uc6', + 'cloud', + 'smoking', + 'cold', + 'clouds', + 'nuage', + 'smoke', + 'cigarette', + 'puff', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'droplet', + char: '\u{1F4A7}', + shortName: 'droplet', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cold', + 'comic', + 'drop', + 'sweat', + 'uc6', + 'weather', + 'sky', + 'rain', + 'sweat', + 'drip', + 'anime', + 'water', + 'manga', + 'water drop' + ]), + Emoji( + name: 'sweat droplets', + char: '\u{1F4A6}', + shortName: 'sweat_drops', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'comic', + 'splashing', + 'sweat', + 'uc6', + 'rain', + 'stressed', + 'sweat', + 'clean', + 'drip', + 'porn', + 'anime', + 'water', + 'manga', + 'water drop' + ]), + Emoji( + name: 'umbrella with rain drops', + char: '\u{2614}', + shortName: 'umbrella', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'clothing', + 'drop', + 'rain', + 'umbrella', + 'uc4', + 'weather', + 'sky', + 'rain', + 'cold', + 'umbrella', + 'england', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'united kingdom', + 'london', + 'uk' + ]), + Emoji( + name: 'umbrella', + char: '\u{2602}\u{FE0F}', + shortName: 'umbrella2', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'clothing', + 'rain', + 'uc1', + 'weather', + 'sky', + 'umbrella', + 'summer', + 'weekend' + ]), + Emoji( + name: 'water wave', + char: '\u{1F30A}', + shortName: 'ocean', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'ocean', + 'water', + 'wave', + 'uc6', + 'weather', + 'boat', + 'tropical', + 'swim', + 'hawaii', + 'storm', + 'mermaid', + 'california', + 'florida', + 'scuba', + 'waves', + 'ocean', + 'boats', + 'boating', + 'swimming', + 'swimmer', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'merboy', + 'mergirl', + 'merman', + 'merperson', + 'selkie', + 'undine', + 'atargatis', + 'siren', + 'snorkel', + 'sea' + ]), + Emoji( + name: 'fog', + char: '\u{1F32B}\u{FE0F}', + shortName: 'fog', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'cloud', + 'uc7', + 'weather', + 'sky', + 'cold', + 'steam', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'steaming', + 'piping' + ]), + Emoji( + name: 'green apple', + char: '\u{1F34F}', + shortName: 'green_apple', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'apple', + 'fruit', + 'green', + 'uc6', + 'food', + 'fruit', + 'classroom', + 'apples', + 'diet', + 'snacks', + 'picnic', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'snack' + ]), + Emoji( + name: 'red apple', + char: '\u{1F34E}', + shortName: 'apple', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'apple', + 'fruit', + 'red', + 'uc6', + 'food', + 'fruit', + 'classroom', + 'creationism', + 'new york', + 'apples', + 'diet', + 'snacks', + 'picnic', + 'vegetarian', + 'snow white', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'adam & eve', + 'adam and eve', + 'snack' + ]), + Emoji( + name: 'pear', + char: '\u{1F350}', + shortName: 'pear', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'fruit', + 'uc6', + 'food', + 'fruit', + 'diet', + 'picnic', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger' + ]), + Emoji( + name: 'tangerine', + char: '\u{1F34A}', + shortName: 'tangerine', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'fruit', + 'orange', + 'uc6', + 'food', + 'fruit', + 'breakfast', + 'diet', + 'donald trump', + 'florida', + 'citrus', + 'picnic', + 'orange', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner', + 'trump', + 'juice', + 'lime' + ]), + Emoji( + name: 'lemon', + char: '\u{1F34B}', + shortName: 'lemon', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'citrus', + 'fruit', + 'uc6', + 'food', + 'fruit', + 'diet', + 'citrus', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'juice', + 'lime' + ]), + Emoji( + name: 'banana', + char: '\u{1F34C}', + shortName: 'banana', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'fruit', + 'uc6', + 'food', + 'fruit', + 'penis', + 'breakfast', + 'monkey', + 'diet', + 'snacks', + 'picnic', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'dick', + 'petit dejeuner', + 'progi', + 'ape', + 'primate', + 'snack' + ]), + Emoji( + name: 'watermelon', + char: '\u{1F349}', + shortName: 'watermelon', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'fruit', + 'uc6', + 'food', + 'fruit', + 'diet', + 'summer', + 'picnic', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'weekend' + ]), + Emoji( + name: 'grapes', + char: '\u{1F347}', + shortName: 'grapes', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'fruit', + 'grape', + 'uc6', + 'food', + 'fruit', + 'paris', + 'diet', + 'snacks', + 'picnic', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'french', + 'france', + 'snack' + ]), + Emoji( + name: 'blueberries', + char: '\u{1FAD0}', + shortName: 'blueberries', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'uc13', + 'food', + 'fruit', + 'breakfast', + 'diet', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner' + ]), + Emoji( + name: 'strawberry', + char: '\u{1F353}', + shortName: 'strawberry', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'berry', + 'fruit', + 'uc6', + 'food', + 'fruit', + 'diet', + 'picnic', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger' + ]), + Emoji( + name: 'melon', + char: '\u{1F348}', + shortName: 'melon', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'fruit', + 'uc6', + 'food', + 'fruit', + 'boobs', + 'diet', + 'porn', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'boob', + 'tits', + 'tit', + 'breast' + ]), + Emoji( + name: 'cherries', + char: '\u{1F352}', + shortName: 'cherries', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'cherry', + 'fruit', + 'uc6', + 'food', + 'fruit', + 'sex', + 'vagina', + 'pussy', + 'diet', + 'porn', + 'picnic', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'condom' + ]), + Emoji( + name: 'peach', + char: '\u{1F351}', + shortName: 'peach', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'fruit', + 'uc6', + 'food', + 'fruit', + 'butt', + 'sex', + 'vagina', + 'pussy', + 'diet', + 'porn', + 'picnic', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'ass', + 'booty', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'condom' + ]), + Emoji( + name: 'mango', + char: '\u{1F96D}', + shortName: 'mango', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'uc11', + 'food', + 'fruit', + 'tropical', + 'thai', + 'diet', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'pattaya' + ]), + Emoji( + name: 'pineapple', + char: '\u{1F34D}', + shortName: 'pineapple', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'fruit', + 'uc6', + 'food', + 'fruit', + 'tropical', + 'hawaii', + 'diet', + 'pineapple', + 'picnic', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'pinapple' + ]), + Emoji( + name: 'coconut', + char: '\u{1F965}', + shortName: 'coconut', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'palm', + 'piÃąa colada', + 'uc10', + 'food', + 'fruit', + 'thai', + 'coconut', + 'diet', + 'vegetarian', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'pattaya' + ]), + Emoji( + name: 'kiwi fruit', + char: '\u{1F95D}', + shortName: 'kiwi', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'food', + 'fruit', + 'kiwi', + 'uc9', + 'food', + 'fruit', + 'breakfast', + 'diet', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner' + ]), + Emoji( + name: 'tomato', + char: '\u{1F345}', + shortName: 'tomato', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'fruit', + 'vegetable', + 'uc6', + 'food', + 'fruit', + 'vegetables', + 'diet', + 'picnic', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume' + ]), + Emoji( + name: 'eggplant', + char: '\u{1F346}', + shortName: 'eggplant', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'aubergine', + 'vegetable', + 'uc6', + 'food', + 'vegetables', + 'penis', + 'sex', + 'diet', + 'porn', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume', + 'dick', + 'fuck', + 'fucking', + 'horny', + 'humping' + ]), + Emoji( + name: 'avocado', + char: '\u{1F951}', + shortName: 'avocado', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'avocado', + 'food', + 'fruit', + 'uc9', + 'food', + 'fruit', + 'vegetables', + 'california', + 'diet', + 'avocado', + 'picnic', + 'vegetarian', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume', + 'avacado' + ]), + Emoji( + name: 'olive', + char: '\u{1FAD2}', + shortName: 'olive', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodFruit, + keywords: [ + 'uc13', + 'food', + 'italian', + 'vegetarian', + 'appetizer', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'italy', + 'italie', + 'apÊro', + 'entrÊe' + ]), + Emoji( + name: 'broccoli', + char: '\u{1F966}', + shortName: 'broccoli', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'wild cabbage', + 'uc10', + 'food', + 'vegetables', + 'diet', + 'vegetarian', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume' + ]), + Emoji( + name: 'leafy green', + char: '\u{1F96C}', + shortName: 'leafy_green', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'uc11', + 'food', + 'vegetables', + 'diet', + 'lettuce', + 'vegetarian', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume' + ]), + Emoji( + name: 'bell pepper', + char: '\u{1FAD1}', + shortName: 'bell_pepper', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'uc13', + 'food', + 'diet', + 'vegetarian', + 'appetizer', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'apÊro', + 'entrÊe' + ]), + Emoji( + name: 'cucumber', + char: '\u{1F952}', + shortName: 'cucumber', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'cucumber', + 'food', + 'pickle', + 'vegetable', + 'uc9', + 'food', + 'fruit', + 'vegetables', + 'penis', + 'diet', + 'pickle', + 'picnic', + 'vegetarian', + 'appetizer', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume', + 'dick', + 'apÊro', + 'entrÊe' + ]), + Emoji( + name: 'hot pepper', + char: '\u{1F336}\u{FE0F}', + shortName: 'hot_pepper', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'hot', + 'pepper', + 'uc7', + 'food', + 'vegetables', + 'mexican', + 'hot', + 'chili', + 'diet', + 'texas', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume', + 'mexico', + 'cinco de mayo', + 'espaÃąol', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß' + ]), + Emoji( + name: 'ear of corn', + char: '\u{1F33D}', + shortName: 'corn', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'corn', + 'ear', + 'maize', + 'maze', + 'uc6', + 'food', + 'vegetables', + 'diet', + 'farm', + 'picnic', + 'independence day', + 'thanksgiving', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume', + '4th of july' + ]), + Emoji( + name: 'carrot', + char: '\u{1F955}', + shortName: 'carrot', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'carrot', + 'food', + 'vegetable', + 'uc9', + 'food', + 'vegetables', + 'penis', + 'diet', + 'vegetarian', + 'appetizer', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume', + 'dick', + 'apÊro', + 'entrÊe' + ]), + Emoji( + name: 'garlic', + char: '\u{1F9C4}', + shortName: 'garlic', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'uc12', + 'food', + 'vegetables', + 'diet', + 'vampire', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume', + 'dracula' + ]), + Emoji( + name: 'onion', + char: '\u{1F9C5}', + shortName: 'onion', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'uc12', + 'food', + 'cry', + 'vegetables', + 'diet', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'crying', + 'weeping', + 'weep', + 'sob', + 'sobbing', + 'tear', + 'tears', + 'bawling', + 'vegetable', + 'veggie', + 'legume' + ]), + Emoji( + name: 'potato', + char: '\u{1F954}', + shortName: 'potato', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'food', + 'potato', + 'vegetable', + 'uc9', + 'food', + 'vegetables', + 'carbs', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume', + 'carbohydrates' + ]), + Emoji( + name: 'roasted sweet potato', + char: '\u{1F360}', + shortName: 'sweet_potato', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'potato', + 'roasted', + 'sweet', + 'uc6', + 'food', + 'vegetables', + 'diet', + 'yam', + 'carbs', + 'thanksgiving', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume', + 'carbohydrates' + ]), + Emoji( + name: 'croissant', + char: '\u{1F950}', + shortName: 'croissant', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'bread', + 'crescent roll', + 'croissant', + 'food', + 'french', + 'uc9', + 'food', + 'breakfast', + 'paris', + 'bake', + 'picnic', + 'carbs', + 'pastry', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner', + 'french', + 'france', + 'baking', + 'carbohydrates', + 'pastries', + 'pÃĸtisserie' + ]), + Emoji( + name: 'bagel', + char: '\u{1F96F}', + shortName: 'bagel', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'uc11', + 'food', + 'new york', + 'breakfast', + 'bake', + 'carbs', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner', + 'baking', + 'carbohydrates' + ]), + Emoji( + name: 'bread', + char: '\u{1F35E}', + shortName: 'bread', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'loaf', + 'uc6', + 'food', + 'sandwich', + 'breakfast', + 'bake', + 'toast', + 'carbs', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'sanwiches', + 'petit dejeuner', + 'baking', + 'carbohydrates' + ]), + Emoji( + name: 'baguette bread', + char: '\u{1F956}', + shortName: 'french_bread', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'baguette', + 'bread', + 'food', + 'french', + 'uc9', + 'food', + 'penis', + 'paris', + 'bake', + 'picnic', + 'carbs', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'dick', + 'french', + 'france', + 'baking', + 'carbohydrates' + ]), + Emoji( + name: 'flatbread', + char: '\u{1FAD3}', + shortName: 'flatbread', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'uc13', + 'food', + 'carbs', + 'vegetarian', + 'pita', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'carbohydrates', + 'naan', + 'tortilla', + 'chepati', + 'focaccia', + 'fry bread', + 'lavash', + 'matzah', + 'roti' + ]), + Emoji( + name: 'pretzel', + char: '\u{1F968}', + shortName: 'pretzel', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'uc10', + 'food', + 'bake', + 'snacks', + 'carbs', + 'german', + 'vegetarian', + 'appetizer', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'baking', + 'snack', + 'carbohydrates', + 'oktoberfest', + 'octoberfest', + 'bratwurst', + 'apÊro', + 'entrÊe' + ]), + Emoji( + name: 'cheese wedge', + char: '\u{1F9C0}', + shortName: 'cheese', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'cheese', + 'uc8', + 'food', + 'paris', + 'picnic', + 'cheese', + 'vegetarian', + 'keto', + 'appetizer', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'french', + 'france', + 'apÊro', + 'entrÊe' + ]), + Emoji( + name: 'egg', + char: '\u{1F95A}', + shortName: 'egg', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'egg', + 'food', + 'uc9', + 'food', + 'breakfast', + 'easter', + 'diet', + 'eggs', + 'vegetarian', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner' + ]), + Emoji( + name: 'cooking', + char: '\u{1F373}', + shortName: 'cooking', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'egg', + 'frying', + 'pan', + 'uc6', + 'food', + 'breakfast', + 'eggs', + 'restaurant', + 'vegetarian', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner' + ]), + Emoji( + name: 'butter', + char: '\u{1F9C8}', + shortName: 'butter', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'uc12', + 'food', + 'breakfast', + 'condiment', + 'butter', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner', + 'condiments', + 'seasoning', + 'topping', + 'margarine', + 'ghee' + ]), + Emoji( + name: 'pancakes', + char: '\u{1F95E}', + shortName: 'pancakes', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'crÃĒpe', + 'food', + 'hotcake', + 'pancake', + 'uc9', + 'food', + 'breakfast', + 'carbs', + 'restaurant', + 'vegetarian', + 'pancake', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner', + 'carbohydrates', + 'pannenkoeken', + 'maple syrup', + 'pfannkuchen', + 'panqueques', + 'crÃĒpes' + ]), + Emoji( + name: 'waffle', + char: '\u{1F9C7}', + shortName: 'waffle', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'uc12', + 'food', + 'breakfast', + 'carbs', + 'waffles', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner', + 'carbohydrates', + 'eggo', + 'gaufre', + 'gofre', + 'waffel', + 'wafel' + ]), + Emoji( + name: 'bacon', + char: '\u{1F953}', + shortName: 'bacon', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'bacon', + 'food', + 'meat', + 'uc9', + 'food', + 'breakfast', + 'pig', + 'meat', + 'restaurant', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner', + 'pork' + ]), + Emoji( + name: 'cut of meat', + char: '\u{1F969}', + shortName: 'cut_of_meat', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'chop', + 'lambchop', + 'porkchop', + 'steak', + 'uc10', + 'food', + 'texas', + 'dinner', + 'steak', + 'meat', + 'restaurant', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'lunch' + ]), + Emoji( + name: 'poultry leg', + char: '\u{1F357}', + shortName: 'poultry_leg', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'bone', + 'chicken', + 'leg', + 'poultry', + 'uc6', + 'food', + 'chicken leg', + 'disney', + 'viking', + 'dinner', + 'meat', + 'thanksgiving', + 'restaurant', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'cartoon', + 'knight', + 'lunch' + ]), + Emoji( + name: 'meat on bone', + char: '\u{1F356}', + shortName: 'meat_on_bone', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'bone', + 'meat', + 'uc6', + 'food', + 'beef', + 'brazil', + 'viking', + 'dinner', + 'meat', + 'thanksgiving', + 'restaurant', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'brasil', + 'bresil', + 'knight', + 'lunch' + ]), + Emoji( + name: 'hot dog', + char: '\u{1F32D}', + shortName: 'hotdog', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'frankfurter', + 'hotdog', + 'sausage', + 'uc8', + 'food', + 'america', + 'new york', + 'sandwich', + 'dinner', + 'franks', + 'independence day', + 'german', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'usa', + 'united states', + 'united states of america', + 'american', + 'sanwiches', + 'lunch', + 'sausage', + 'hot dog', + '4th of july', + 'oktoberfest', + 'octoberfest', + 'bratwurst' + ]), + Emoji( + name: 'hamburger', + char: '\u{1F354}', + shortName: 'hamburger', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'burger', + 'uc6', + 'food', + 'america', + 'boys night', + 'sandwich', + 'beef', + 'mcdonalds', + 'dinner', + 'burger', + 'cheese', + 'independence day', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'usa', + 'united states', + 'united states of america', + 'american', + 'guys night', + 'sanwiches', + 'ronald mcdonald', + 'macdo', + 'lunch', + 'cheeseburger', + 'cheese burger', + '4th of july' + ]), + Emoji( + name: 'french fries', + char: '\u{1F35F}', + shortName: 'fries', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'french', + 'fries', + 'uc6', + 'food', + 'america', + 'chips', + 'mcdonalds', + 'dinner', + 'carbs', + 'restaurant', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'usa', + 'united states', + 'united states of america', + 'american', + 'ronald mcdonald', + 'macdo', + 'lunch', + 'carbohydrates' + ]), + Emoji( + name: 'pizza', + char: '\u{1F355}', + shortName: 'pizza', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'cheese', + 'slice', + 'uc6', + 'food', + 'italian', + 'boys night', + 'new york', + 'dinner', + 'cheese', + 'carbs', + 'restaurant', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'italy', + 'italie', + 'guys night', + 'lunch', + 'carbohydrates' + ]), + Emoji( + name: 'sandwich', + char: '\u{1F96A}', + shortName: 'sandwich', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'bread', + 'uc10', + 'food', + 'sandwich', + 'dinner', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'sanwiches', + 'lunch' + ]), + Emoji( + name: 'stuffed flatbread', + char: '\u{1F959}', + shortName: 'stuffed_flatbread', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'falafel', + 'flatbread', + 'food', + 'gyro', + 'kebab', + 'stuffed', + 'uc9', + 'food', + 'sandwich', + 'dinner', + 'german', + 'restaurant', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'sanwiches', + 'lunch', + 'oktoberfest', + 'octoberfest', + 'bratwurst' + ]), + Emoji( + name: 'falafel', + char: '\u{1F9C6}', + shortName: 'falafel', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'uc12', + 'food', + 'dinner', + 'meatball', + 'felafel', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'lunch', + 'chickpeas', + 'fava beans', + 'levantine', + 'meze' + ]), + Emoji( + name: 'taco', + char: '\u{1F32E}', + shortName: 'taco', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'mexican', + 'uc8', + 'food', + 'mexican', + 'vagina', + 'tacos', + 'hola', + 'pussy', + 'porn', + 'texas', + 'dinner', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'mexico', + 'cinco de mayo', + 'espaÃąol', + 'condom', + 'lunch' + ]), + Emoji( + name: 'burrito', + char: '\u{1F32F}', + shortName: 'burrito', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'mexican', + 'wrap', + 'uc8', + 'food', + 'mexican', + 'hola', + 'texas', + 'dinner', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'mexico', + 'cinco de mayo', + 'espaÃąol', + 'lunch' + ]), + Emoji( + name: 'tamale', + char: '\u{1FAD4}', + shortName: 'tamale', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'uc13', + 'food', + 'dinner', + 'tamal', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'lunch', + 'chuchito', + 'pastelle', + 'pasteles', + 'hallaca', + 'zacahuil', + 'corunda', + 'bollo', + 'humita', + 'binaki', + 'masa', + 'dukunu', + 'paches' + ]), + Emoji( + name: 'green salad', + char: '\u{1F957}', + shortName: 'salad', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'food', + 'green', + 'salad', + 'uc9', + 'food', + 'vegetables', + 'diet', + 'dinner', + 'lettuce', + 'picnic', + 'restaurant', + 'vegetarian', + 'keto', + 'appetizer', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume', + 'lunch', + 'apÊro', + 'entrÊe' + ]), + Emoji( + name: 'shallow pan of food', + char: '\u{1F958}', + shortName: 'shallow_pan_of_food', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'casserole', + 'food', + 'paella', + 'pan', + 'shallow', + 'uc9', + 'food', + 'mexican', + 'barcelona', + 'beef', + 'brazil', + 'dinner', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'mexico', + 'cinco de mayo', + 'espaÃąol', + 'espaÃąa', + 'spanish', + 'brasil', + 'bresil', + 'lunch' + ]), + Emoji( + name: 'fondue', + char: '\u{1FAD5}', + shortName: 'fondue', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'uc13', + 'food', + 'dinner', + 'cheese', + 'vegetarian', + 'shabu', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'lunch', + 'hot pot' + ]), + Emoji( + name: 'canned food', + char: '\u{1F96B}', + shortName: 'canned_food', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'can', + 'uc10', + 'food', + 'soup', + 'dinner', + 'restaurant', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'lunch' + ]), + Emoji( + name: 'spaghetti', + char: '\u{1F35D}', + shortName: 'spaghetti', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'pasta', + 'uc6', + 'food', + 'noodles', + 'pasta', + 'italian', + 'dinner', + 'meatball', + 'restaurant', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'noodle', + 'pÃĸtes', + 'italy', + 'italie', + 'lunch' + ]), + Emoji( + name: 'steaming bowl', + char: '\u{1F35C}', + shortName: 'ramen', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'bowl', + 'noodle', + 'ramen', + 'steaming', + 'uc6', + 'food', + 'noodles', + 'ramen', + 'pasta', + 'japan', + 'steam', + 'thai', + 'chinese', + 'soup', + 'dinner', + 'restaurant', + 'vegetarian', + 'bone broth', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'noodle', + 'pÃĸtes', + 'japanese', + 'ninja', + 'steaming', + 'piping', + 'pattaya', + 'chinois', + 'asian', + 'chine', + 'lunch' + ]), + Emoji( + name: 'pot of food', + char: '\u{1F372}', + shortName: 'stew', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'pot', + 'stew', + 'uc6', + 'food', + 'steam', + 'thai', + 'brazil', + 'soup', + 'dinner', + 'stew', + 'thanksgiving', + 'restaurant', + 'bone broth', + 'shabu', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'steaming', + 'piping', + 'pattaya', + 'brasil', + 'bresil', + 'lunch', + 'braise', + 'hot pot' + ]), + Emoji( + name: 'curry rice', + char: '\u{1F35B}', + shortName: 'curry', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'curry', + 'rice', + 'uc6', + 'food', + 'japan', + 'thai', + 'dinner', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'japanese', + 'ninja', + 'pattaya', + 'lunch' + ]), + Emoji( + name: 'sushi', + char: '\u{1F363}', + shortName: 'sushi', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'sushi', + 'uc6', + 'food', + 'sushi', + 'japan', + 'california', + 'diet', + 'seafood', + 'dinner', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'japanese', + 'ninja', + 'lunch' + ]), + Emoji( + name: 'bento box', + char: '\u{1F371}', + shortName: 'bento', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'bento', + 'box', + 'uc6', + 'food', + 'sushi', + 'japan', + 'dinner', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'japanese', + 'ninja', + 'lunch' + ]), + Emoji( + name: 'dumpling', + char: '\u{1F95F}', + shortName: 'dumpling', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'empanada', + 'gyōza', + 'jiaozi', + 'pierogi', + 'potsticker', + 'uc10', + 'food', + 'chinese', + 'dinner', + 'dumpling', + 'pastry', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'chinois', + 'asian', + 'chine', + 'lunch', + 'Empanada', + 'Gyōza', + 'Pierogi', + 'pastries', + 'pÃĸtisserie' + ]), + Emoji( + name: 'oyster', + char: '\u{1F9AA}', + shortName: 'oyster', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodMarine, + keywords: [ + 'uc12', + 'animal', + 'food', + 'seafood', + 'dinner', + 'ocean', + 'crustacean', + 'half shell', + 'animals', + 'animal kingdom', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'lunch', + 'sea', + 'pearl' + ]), + Emoji( + name: 'fried shrimp', + char: '\u{1F364}', + shortName: 'fried_shrimp', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'fried', + 'prawn', + 'shrimp', + 'tempura', + 'uc6', + 'food', + 'japan', + 'prawn', + 'seafood', + 'dinner', + 'crustacean', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'japanese', + 'ninja', + 'lunch' + ]), + Emoji( + name: 'rice ball', + char: '\u{1F359}', + shortName: 'rice_ball', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'Japanese', + 'ball', + 'rice', + 'uc6', + 'food', + 'sushi', + 'japan', + 'snacks', + 'dinner', + 'restaurant', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'japanese', + 'ninja', + 'snack', + 'lunch' + ]), + Emoji( + name: 'cooked rice', + char: '\u{1F35A}', + shortName: 'rice', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'cooked', + 'rice', + 'uc6', + 'food', + 'sushi', + 'japan', + 'thai', + 'chinese', + 'brazil', + 'dinner', + 'carbs', + 'restaurant', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'japanese', + 'ninja', + 'pattaya', + 'chinois', + 'asian', + 'chine', + 'brasil', + 'bresil', + 'lunch', + 'carbohydrates' + ]), + Emoji( + name: 'rice cracker', + char: '\u{1F358}', + shortName: 'rice_cracker', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'cracker', + 'rice', + 'uc6', + 'food', + 'sushi', + 'chinese', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'chinois', + 'asian', + 'chine' + ]), + Emoji( + name: 'fish cake with swirl', + char: '\u{1F365}', + shortName: 'fish_cake', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'cake', + 'fish', + 'pastry', + 'swirl', + 'uc6', + 'food', + 'sushi', + 'japan', + 'seafood', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'japanese', + 'ninja' + ]), + Emoji( + name: 'fortune cookie', + char: '\u{1F960}', + shortName: 'fortune_cookie', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'prophecy', + 'uc10', + 'food', + 'luck', + 'sugar', + 'cookie', + 'chinese', + 'bake', + 'carbs', + 'pastry', + 'restaurant', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'good luck', + 'lucky', + 'junk food', + 'dessert', + 'sweets', + 'cookies', + 'chinois', + 'asian', + 'chine', + 'baking', + 'carbohydrates', + 'pastries', + 'pÃĸtisserie' + ]), + Emoji( + name: 'moon cake', + char: '\u{1F96E}', + shortName: 'moon_cake', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'uc11', + 'food', + 'cake', + 'celebrate', + 'sugar', + 'chinese', + 'bake', + 'carbs', + 'pastry', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'cupcake', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'junk food', + 'dessert', + 'sweets', + 'chinois', + 'asian', + 'chine', + 'baking', + 'carbohydrates', + 'pastries', + 'pÃĸtisserie' + ]), + Emoji( + name: 'oden', + char: '\u{1F362}', + shortName: 'oden', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'kebab', + 'seafood', + 'skewer', + 'stick', + 'uc6', + 'food', + 'japan', + 'dinner', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'japanese', + 'ninja', + 'lunch' + ]), + Emoji( + name: 'dango', + char: '\u{1F361}', + shortName: 'dango', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'Japanese', + 'dessert', + 'skewer', + 'stick', + 'sweet', + 'uc6', + 'food', + 'japan', + 'sugar', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'japanese', + 'ninja', + 'junk food', + 'dessert', + 'sweets' + ]), + Emoji( + name: 'shaved ice', + char: '\u{1F367}', + shortName: 'shaved_ice', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'dessert', + 'ice', + 'shaved', + 'sweet', + 'uc6', + 'food', + 'ice cream', + 'hawaii', + 'sugar', + 'disney', + 'summer', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'junk food', + 'dessert', + 'sweets', + 'cartoon', + 'weekend' + ]), + Emoji( + name: 'ice cream', + char: '\u{1F368}', + shortName: 'ice_cream', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'cream', + 'dessert', + 'ice', + 'sweet', + 'uc6', + 'food', + 'ice cream', + 'sugar', + 'summer', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'junk food', + 'dessert', + 'sweets', + 'weekend' + ]), + Emoji( + name: 'soft ice cream', + char: '\u{1F366}', + shortName: 'icecream', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'cream', + 'dessert', + 'ice', + 'icecream', + 'soft', + 'sweet', + 'uc6', + 'food', + 'italian', + 'ice cream', + 'sugar', + 'summer', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'italy', + 'italie', + 'junk food', + 'dessert', + 'sweets', + 'weekend' + ]), + Emoji( + name: 'pie', + char: '\u{1F967}', + shortName: 'pie', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'uc10', + 'food', + 'sugar', + 'bake', + 'carbs', + 'pastry', + 'quiche', + 'thanksgiving', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'junk food', + 'dessert', + 'sweets', + 'baking', + 'carbohydrates', + 'pastries', + 'pÃĸtisserie', + 'tart' + ]), + Emoji( + name: 'cupcake', + char: '\u{1F9C1}', + shortName: 'cupcake', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'uc11', + 'food', + 'birthday', + 'happy birthday', + 'cake', + 'pink', + 'celebrate', + 'sugar', + 'bake', + 'carbs', + 'pastry', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'birth', + 'cumpleaÃąos', + 'anniversaire', + 'bday', + 'Bon anniversaire', + 'joyeux anniversaire', + 'buon compleanno', + 'feliz cumpleaÃąos', + 'alles Gute zum Geburtstag', + 'feliz AniversÃĄrio', + 'Gratulerer med dagen', + 'cupcake', + 'rose', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'junk food', + 'dessert', + 'sweets', + 'baking', + 'carbohydrates', + 'pastries', + 'pÃĸtisserie' + ]), + Emoji( + name: 'shortcake', + char: '\u{1F370}', + shortName: 'cake', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'cake', + 'dessert', + 'pastry', + 'slice', + 'sweet', + 'uc6', + 'food', + 'cake', + 'sugar', + 'bake', + 'carbs', + 'pastry', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'cupcake', + 'junk food', + 'dessert', + 'sweets', + 'baking', + 'carbohydrates', + 'pastries', + 'pÃĸtisserie' + ]), + Emoji( + name: 'birthday cake', + char: '\u{1F382}', + shortName: 'birthday', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'birthday', + 'cake', + 'celebration', + 'dessert', + 'pastry', + 'sweet', + 'uc6', + 'food', + 'holidays', + 'birthday', + 'happy birthday', + 'cake', + 'celebrate', + 'sugar', + 'facebook', + 'bake', + 'pastry', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'holiday', + 'birth', + 'cumpleaÃąos', + 'anniversaire', + 'bday', + 'Bon anniversaire', + 'joyeux anniversaire', + 'buon compleanno', + 'feliz cumpleaÃąos', + 'alles Gute zum Geburtstag', + 'feliz AniversÃĄrio', + 'Gratulerer med dagen', + 'cupcake', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'junk food', + 'dessert', + 'sweets', + 'baking', + 'pastries', + 'pÃĸtisserie' + ]), + Emoji( + name: 'custard', + char: '\u{1F36E}', + shortName: 'custard', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'dessert', + 'pudding', + 'sweet', + 'uc6', + 'food', + 'sugar', + 'bake', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'junk food', + 'dessert', + 'sweets', + 'baking' + ]), + Emoji( + name: 'lollipop', + char: '\u{1F36D}', + shortName: 'lollipop', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'candy', + 'dessert', + 'sweet', + 'uc6', + 'food', + 'halloween', + 'candy', + 'sugar', + 'disney', + 'snacks', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'samhain', + 'candy cane', + 'candycane', + 'lolipop', + 'bonbon', + 'junk food', + 'dessert', + 'sweets', + 'cartoon', + 'snack' + ]), + Emoji( + name: 'candy', + char: '\u{1F36C}', + shortName: 'candy', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'dessert', + 'sweet', + 'uc6', + 'food', + 'halloween', + 'candy', + 'sugar', + 'snacks', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'samhain', + 'candy cane', + 'candycane', + 'lolipop', + 'bonbon', + 'junk food', + 'dessert', + 'sweets', + 'snack' + ]), + Emoji( + name: 'chocolate bar', + char: '\u{1F36B}', + shortName: 'chocolate_bar', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'bar', + 'chocolate', + 'dessert', + 'sweet', + 'uc6', + 'food', + 'halloween', + 'love', + 'girls night', + 'candy', + 'sugar', + 'easter', + 'cocoa', + 'snacks', + 'rich', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'samhain', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'ladies night', + 'girls only', + 'girlfriend', + 'candy cane', + 'candycane', + 'lolipop', + 'bonbon', + 'junk food', + 'dessert', + 'sweets', + 'hot chocolate', + 'snack', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'popcorn', + char: '\u{1F37F}', + shortName: 'popcorn', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'popcorn', + 'uc8', + 'food', + 'celebrate', + 'snacks', + 'carbs', + 'vegetarian', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'snack', + 'carbohydrates' + ]), + Emoji( + name: 'doughnut', + char: '\u{1F369}', + shortName: 'doughnut', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'dessert', + 'donut', + 'sweet', + 'uc6', + 'food', + 'sex', + 'vagina', + 'breakfast', + 'doughnut', + 'sugar', + 'bake', + 'carbs', + 'pastry', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'fuck', + 'fucking', + 'horny', + 'humping', + 'petit dejeuner', + 'donut', + 'junk food', + 'dessert', + 'sweets', + 'baking', + 'carbohydrates', + 'pastries', + 'pÃĸtisserie' + ]), + Emoji( + name: 'cookie', + char: '\u{1F36A}', + shortName: 'cookie', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'dessert', + 'sweet', + 'uc6', + 'food', + 'christmas', + 'vagina', + 'sugar', + 'cookie', + 'bake', + 'snacks', + 'carbs', + 'pastry', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'junk food', + 'dessert', + 'sweets', + 'cookies', + 'baking', + 'snack', + 'carbohydrates', + 'pastries', + 'pÃĸtisserie' + ]), + Emoji( + name: 'chestnut', + char: '\u{1F330}', + shortName: 'chestnut', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'plant', + 'uc6', + 'food', + 'nature', + 'plant', + 'christmas', + 'nut', + 'keto', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'plants', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'nuts' + ]), + Emoji( + name: 'peanuts', + char: '\u{1F95C}', + shortName: 'peanuts', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodVegetable, + keywords: [ + 'food', + 'nut', + 'peanut', + 'vegetable', + 'uc9', + 'food', + 'vegetables', + 'squirrel', + 'nut', + 'snacks', + 'picnic', + 'appetizer', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'vegetable', + 'veggie', + 'legume', + 'nuts', + 'snack', + 'apÊro', + 'entrÊe' + ]), + Emoji( + name: 'honey pot', + char: '\u{1F36F}', + shortName: 'honey_pot', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodSweet, + keywords: [ + 'honey', + 'honeypot', + 'pot', + 'sweet', + 'uc6', + 'food', + 'vagina', + 'breakfast', + 'sugar', + 'condiment', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner', + 'junk food', + 'dessert', + 'sweets', + 'condiments', + 'seasoning', + 'topping' + ]), + Emoji( + name: 'glass of milk', + char: '\u{1F95B}', + shortName: 'milk', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'drink', + 'glass', + 'milk', + 'uc9', + 'drink', + 'christmas', + 'dinner', + 'restaurant', + 'drinks', + 'beverage', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'lunch' + ]), + Emoji( + name: 'baby bottle', + char: '\u{1F37C}', + shortName: 'baby_bottle', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'baby', + 'bottle', + 'drink', + 'milk', + 'uc6', + 'food', + 'drink', + 'baby', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'drinks', + 'beverage', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino' + ]), + Emoji( + name: 'hot beverage', + char: '\u{2615}', + shortName: 'coffee', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'beverage', + 'coffee', + 'drink', + 'hot', + 'steaming', + 'tea', + 'uc4', + 'drink', + 'caffeine', + 'steam', + 'morning', + 'coffee', + 'breakfast', + 'cocoa', + 'diet', + 'restaurant', + 'keto', + 'drinks', + 'beverage', + 'decaffeinated', + 'decaf', + 'steaming', + 'piping', + 'good morning', + 'starbucks', + 'petit dejeuner', + 'hot chocolate' + ]), + Emoji( + name: 'teacup without handle', + char: '\u{1F375}', + shortName: 'tea', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'beverage', + 'cup', + 'drink', + 'tea', + 'teacup', + 'uc6', + 'drink', + 'japan', + 'caffeine', + 'steam', + 'morning', + 'tea', + 'breakfast', + 'england', + 'chinese', + 'diet', + 'restaurant', + 'keto', + 'drinks', + 'beverage', + 'japanese', + 'ninja', + 'decaffeinated', + 'decaf', + 'steaming', + 'piping', + 'good morning', + 'iced tea', + 'petit dejeuner', + 'united kingdom', + 'london', + 'uk', + 'chinois', + 'asian', + 'chine' + ]), + Emoji( + name: 'teapot', + char: '\u{1FAD6}', + shortName: 'teapot', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'uc13', + 'japan', + 'breakfast', + 'chinese', + 'kettle', + 'japanese', + 'ninja', + 'petit dejeuner', + 'chinois', + 'asian', + 'chine', + 'teakettle', + 'caldron', + 'boiler', + 'thÊière', + 'teiera', + 'tetera', + 'infuser', + 'kyÅĢsu', + 'tetsubin', + 'yixing' + ]), + Emoji( + name: 'mate', + char: '\u{1F9C9}', + shortName: 'mate', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'uc12', + 'drink', + 'caffeine', + 'tea', + 'breakfast', + 'yerba', + 'drinks', + 'beverage', + 'decaffeinated', + 'decaf', + 'iced tea', + 'petit dejeuner', + 'chimarrÃŖo', + 'cimarrÃŗn', + 'matÊ' + ]), + Emoji( + name: 'bubble tea', + char: '\u{1F9CB}', + shortName: 'bubble_tea', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'uc13', + 'drink', + 'japan', + 'thai', + 'chinese', + 'drinks', + 'beverage', + 'japanese', + 'ninja', + 'pattaya', + 'chinois', + 'asian', + 'chine' + ]), + Emoji( + name: 'beverage box', + char: '\u{1F9C3}', + shortName: 'beverage_box', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'uc12', + 'drink', + 'apples', + 'citrus', + 'snacks', + 'drinks', + 'beverage', + 'juice', + 'lime', + 'snack' + ]), + Emoji( + name: 'cup with straw', + char: '\u{1F964}', + shortName: 'cup_with_straw', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'uc10', + 'drink', + 'dinner', + 'soda', + 'restaurant', + 'keto', + 'drinks', + 'beverage', + 'lunch', + 'cola', + 'milkshake', + 'soft drink', + 'sippy cup', + 'coke', + 'pepsi' + ]), + Emoji( + name: 'sake', + char: '\u{1F376}', + shortName: 'sake', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'bar', + 'beverage', + 'bottle', + 'cup', + 'drink', + 'uc6', + 'drink', + 'japan', + 'alcohol', + 'sake', + 'girls night', + 'chinese', + 'dinner', + 'restaurant', + 'drinks', + 'beverage', + 'japanese', + 'ninja', + 'liquor', + 'booze', + 'ladies night', + 'girls only', + 'girlfriend', + 'chinois', + 'asian', + 'chine', + 'lunch' + ]), + Emoji( + name: 'beer mug', + char: '\u{1F37A}', + shortName: 'beer', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'bar', + 'beer', + 'drink', + 'mug', + 'uc6', + 'drink', + 'japan', + 'alcohol', + 'beer', + 'cocktail', + 'friend', + 'irish', + 'dinner', + 'german', + 'restaurant', + 'drinks', + 'beverage', + 'japanese', + 'ninja', + 'liquor', + 'booze', + 'martini', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'saint patricks day', + 'st patricks day', + 'leprechaun', + 'lunch', + 'oktoberfest', + 'octoberfest', + 'bratwurst' + ]), + Emoji( + name: 'clinking beer mugs', + char: '\u{1F37B}', + shortName: 'beers', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'bar', + 'beer', + 'clink', + 'drink', + 'mug', + 'uc6', + 'drink', + 'alcohol', + 'cheers', + 'beer', + 'cocktail', + 'thank you', + 'girls night', + 'boys night', + 'harry potter', + 'friend', + 'celebrate', + 'irish', + 'toast', + 'german', + 'restaurant', + 'drinks', + 'beverage', + 'liquor', + 'booze', + 'gān bēi', + 'Na zdravi', + 'Proost', + 'Prost', + 'SlÃĄinte', + 'Cin cin', + 'Kanpai', + 'Na zdrowie', + 'SaÃēde', + 'На ĐˇĐ´ĐžŅ€ĐžĐ˛ŅŒĐĩ', + 'Salud', + 'SkÃĨl', + 'Sei gesund', + 'santÊ', + 'martini', + 'thanks', + 'thankful', + 'praise', + 'gracias', + 'merci', + 'thankyou', + 'acceptable', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'saint patricks day', + 'st patricks day', + 'leprechaun', + 'oktoberfest', + 'octoberfest', + 'bratwurst' + ]), + Emoji( + name: 'clinking glasses', + char: '\u{1F942}', + shortName: 'champagne_glass', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'celebrate', + 'clink', + 'drink', + 'glass', + 'uc9', + 'drink', + 'alcohol', + 'cheers', + 'girls night', + 'friend', + 'celebrate', + 'toast', + 'restaurant', + 'drinks', + 'beverage', + 'liquor', + 'booze', + 'gān bēi', + 'Na zdravi', + 'Proost', + 'Prost', + 'SlÃĄinte', + 'Cin cin', + 'Kanpai', + 'Na zdrowie', + 'SaÃēde', + 'На ĐˇĐ´ĐžŅ€ĐžĐ˛ŅŒĐĩ', + 'Salud', + 'SkÃĨl', + 'Sei gesund', + 'santÊ', + 'ladies night', + 'girls only', + 'girlfriend', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar' + ]), + Emoji( + name: 'wine glass', + char: '\u{1F377}', + shortName: 'wine_glass', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'bar', + 'beverage', + 'drink', + 'glass', + 'wine', + 'uc6', + 'drink', + 'italian', + 'christmas', + 'alcohol', + 'cocktail', + 'girls night', + 'australia', + 'paris', + 'rich', + 'dinner', + 'picnic', + 'thanksgiving', + 'restaurant', + 'drinks', + 'beverage', + 'italy', + 'italie', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'liquor', + 'booze', + 'martini', + 'ladies night', + 'girls only', + 'girlfriend', + 'french', + 'france', + 'grand', + 'expensive', + 'fancy', + 'lunch' + ]), + Emoji( + name: 'tumbler glass', + char: '\u{1F943}', + shortName: 'tumbler_glass', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'glass', + 'liquor', + 'shot', + 'tumbler', + 'whisky', + 'uc9', + 'drink', + 'japan', + 'alcohol', + 'cocktail', + 'boys night', + 'whisky', + 'irish', + 'scotland', + 'las vegas', + 'dinner', + 'shot', + 'restaurant', + 'keto', + 'drinks', + 'beverage', + 'japanese', + 'ninja', + 'liquor', + 'booze', + 'martini', + 'guys night', + 'whiskey', + 'scotch', + 'saint patricks day', + 'st patricks day', + 'leprechaun', + 'scottish', + 'vegas', + 'lunch' + ]), + Emoji( + name: 'cocktail glass', + char: '\u{1F378}', + shortName: 'cocktail', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'bar', + 'cocktail', + 'drink', + 'glass', + 'uc6', + 'drink', + 'alcohol', + 'cocktail', + 'girls night', + 'las vegas', + 'dinner', + 'restaurant', + 'drinks', + 'beverage', + 'liquor', + 'booze', + 'martini', + 'ladies night', + 'girls only', + 'girlfriend', + 'vegas', + 'lunch' + ]), + Emoji( + name: 'tropical drink', + char: '\u{1F379}', + shortName: 'tropical_drink', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'bar', + 'drink', + 'tropical', + 'uc6', + 'drink', + 'alcohol', + 'tropical', + 'cocktail', + 'tea', + 'citrus', + 'summer', + 'dinner', + 'restaurant', + 'drinks', + 'beverage', + 'liquor', + 'booze', + 'martini', + 'iced tea', + 'juice', + 'lime', + 'weekend', + 'lunch' + ]), + Emoji( + name: 'bottle with popping cork', + char: '\u{1F37E}', + shortName: 'champagne', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'bar', + 'bottle', + 'cork', + 'drink', + 'popping', + 'uc8', + 'drink', + 'holidays', + 'alcohol', + 'cheers', + 'celebrate', + 'paris', + 'rich', + 'toast', + 'picnic', + 'restaurant', + 'drinks', + 'beverage', + 'holiday', + 'liquor', + 'booze', + 'gān bēi', + 'Na zdravi', + 'Proost', + 'Prost', + 'SlÃĄinte', + 'Cin cin', + 'Kanpai', + 'Na zdrowie', + 'SaÃēde', + 'На ĐˇĐ´ĐžŅ€ĐžĐ˛ŅŒĐĩ', + 'Salud', + 'SkÃĨl', + 'Sei gesund', + 'santÊ', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'french', + 'france', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'ice', + char: '\u{1F9CA}', + shortName: 'ice_cube', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.drink, + keywords: [ + 'uc12', + 'snow', + 'cold', + 'igloo', + 'cubo de hielo', + 'freeze', + 'frozen', + 'frost', + 'ice cube', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'glaçon', + 'cubetto di ghiaccio' + ]), + Emoji( + name: 'spoon', + char: '\u{1F944}', + shortName: 'spoon', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.dishware, + keywords: [ + 'spoon', + 'tableware', + 'uc9', + 'food', + 'cutlery', + 'steel', + 'utensils', + 'restaurant', + 'dishes', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'dish', + 'metal' + ]), + Emoji( + name: 'fork and knife', + char: '\u{1F374}', + shortName: 'fork_and_knife', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.dishware, + keywords: [ + 'cooking', + 'fork', + 'knife', + 'uc6', + 'food', + 'christmas', + 'cutlery', + 'dinner', + 'steel', + 'picnic', + 'independence day', + 'utensils', + 'restaurant', + 'dishes', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'dish', + 'lunch', + 'metal', + '4th of july' + ]), + Emoji( + name: 'fork and knife with plate', + char: '\u{1F37D}\u{FE0F}', + shortName: 'fork_knife_plate', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.dishware, + keywords: [ + 'cooking', + 'fork', + 'knife', + 'plate', + 'uc7', + 'food', + 'cutlery', + 'diet', + 'dinner', + 'picnic', + 'utensils', + 'restaurant', + 'dishes', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'dish', + 'lunch' + ]), + Emoji( + name: 'bowl with spoon', + char: '\u{1F963}', + shortName: 'bowl_with_spoon', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'uc10', + 'food', + 'breakfast', + 'soup', + 'dinner', + 'cereal', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'petit dejeuner', + 'lunch' + ]), + Emoji( + name: 'takeout box', + char: '\u{1F961}', + shortName: 'takeout_box', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodAsian, + keywords: [ + 'oyster pail', + 'uc10', + 'food', + 'chinese', + 'dinner', + 'oyster pail', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'chinois', + 'asian', + 'chine', + 'lunch' + ]), + Emoji( + name: 'chopsticks', + char: '\u{1F962}', + shortName: 'chopsticks', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.dishware, + keywords: [ + 'uc10', + 'food', + 'sushi', + 'japan', + 'chinese', + 'utensils', + 'restaurant', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'japanese', + 'ninja', + 'chinois', + 'asian', + 'chine' + ]), + Emoji( + name: 'salt', + char: '\u{1F9C2}', + shortName: 'salt', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.foodPrepared, + keywords: [ + 'uc11', + 'condiment', + 'restaurant', + 'condiments', + 'seasoning', + 'topping' + ]), + Emoji( + name: 'soccer ball', + char: '\u{26BD}', + shortName: 'soccer', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'ball', + 'football', + 'soccer', + 'uc5', + 'sport', + 'game', + 'ball', + 'football', + 'soccer', + 'play', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon', + 'soccer ball', + 'world cup' + ]), + Emoji( + name: 'basketball', + char: '\u{1F3C0}', + shortName: 'basketball', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'ball', + 'hoop', + 'uc6', + 'sport', + 'game', + 'ball', + 'basketball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon' + ]), + Emoji( + name: 'american football', + char: '\u{1F3C8}', + shortName: 'football', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'american', + 'ball', + 'football', + 'uc6', + 'sport', + 'america', + 'game', + 'ball', + 'football', + 'play', + 'texas', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'usa', + 'united states', + 'united states of america', + 'american', + 'games', + 'gaming', + 'balls', + 'ballon' + ]), + Emoji( + name: 'baseball', + char: '\u{26BE}', + shortName: 'baseball', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'ball', + 'uc5', + 'sport', + 'game', + 'ball', + 'play', + 'throw', + 'activity', + 'independence day', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon', + '4th of july' + ]), + Emoji( + name: 'softball', + char: '\u{1F94E}', + shortName: 'softball', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'uc11', + 'sport', + 'game', + 'ball', + 'play', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon' + ]), + Emoji( + name: 'tennis', + char: '\u{1F3BE}', + shortName: 'tennis', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'ball', + 'racquet', + 'uc6', + 'sport', + 'game', + 'ball', + 'tennis', + 'play', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon', + 'tennis ball', + 'tennis racquet' + ]), + Emoji( + name: 'volleyball', + char: '\u{1F3D0}', + shortName: 'volleyball', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'ball', + 'game', + 'uc8', + 'sport', + 'game', + 'ball', + 'volley ball', + 'play', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon' + ]), + Emoji( + name: 'rugby football', + char: '\u{1F3C9}', + shortName: 'rugby_football', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'ball', + 'football', + 'rugby', + 'uc6', + 'sport', + 'game', + 'ball', + 'football', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon' + ]), + Emoji( + name: 'flying disc', + char: '\u{1F94F}', + shortName: 'flying_disc', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'uc11', + 'sport', + 'game', + 'play', + 'fun', + 'throw', + 'activity', + 'frisbee', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'disque-volant', + 'boomerang' + ]), + Emoji( + name: 'boomerang', + char: '\u{1FA83}', + shortName: 'boomerang', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'uc13', + 'sport', + 'game', + 'play', + 'fun', + 'throw', + 'hunt', + 'activity', + 'boumerang', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'airfoil', + 'aerofoil' + ]), + Emoji( + name: 'pool 8 ball', + char: '\u{1F3B1}', + shortName: '8ball', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + '8', + '8 ball', + 'ball', + 'billiard', + 'eight', + 'game', + 'uc6', + 'sport', + 'game', + 'ball', + 'billiards', + 'luck', + 'boys night', + 'play', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon', + 'billiards ball', + '8 ball', + '8ball', + 'pool', + 'eight ball', + 'good luck', + 'lucky', + 'guys night' + ]), + Emoji( + name: 'yo-yo', + char: '\u{1FA80}', + shortName: 'yo_yo', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'uc12', + 'game', + 'play', + 'activity', + 'yoyo', + 'toy', + 'stringed', + 'games', + 'gaming', + 'fluctuate' + ]), + Emoji( + name: 'ping pong', + char: '\u{1F3D3}', + shortName: 'ping_pong', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'ball', + 'bat', + 'game', + 'paddle', + 'ping pong', + 'table tennis', + 'uc8', + 'sport', + 'game', + 'ball', + 'ping pong', + 'play', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon', + 'table tennis', + 'ping pong ball', + 'ping pong paddle', + 'paddle' + ]), + Emoji( + name: 'badminton', + char: '\u{1F3F8}', + shortName: 'badminton', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'birdie', + 'game', + 'racquet', + 'shuttlecock', + 'uc8', + 'sport', + 'game', + 'play', + 'fun', + 'activity', + 'stringed', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming' + ]), + Emoji( + name: 'ice hockey', + char: '\u{1F3D2}', + shortName: 'hockey', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'game', + 'hockey', + 'ice', + 'puck', + 'stick', + 'uc8', + 'sport', + 'game', + 'hockey', + 'play', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'field hockey' + ]), + Emoji( + name: 'field hockey', + char: '\u{1F3D1}', + shortName: 'field_hockey', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'ball', + 'field', + 'game', + 'hockey', + 'stick', + 'uc8', + 'sport', + 'game', + 'ball', + 'hockey', + 'play', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon', + 'field hockey' + ]), + Emoji( + name: 'lacrosse', + char: '\u{1F94D}', + shortName: 'lacrosse', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'uc11', + 'sport', + 'game', + 'ball', + 'play', + 'fun', + 'throw', + 'activity', + 'stringed', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon' + ]), + Emoji( + name: 'cricket game', + char: '\u{1F3CF}', + shortName: 'cricket_game', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'ball', + 'bat', + 'game', + 'uc8', + 'sport', + 'game', + 'ball', + 'cricket', + 'play', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon', + 'cricket bat', + 'cricket ball' + ]), + Emoji( + name: 'goal net', + char: '\u{1F945}', + shortName: 'goal', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'goal', + 'net', + 'uc9', + 'sport', + 'football', + 'soccer', + 'play', + 'fun', + 'activity', + 'stringed', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'soccer ball', + 'world cup' + ]), + Emoji( + name: 'flag in hole', + char: '\u{26F3}', + shortName: 'golf', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'golf', + 'hole', + 'uc5', + 'sport', + 'game', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ]), + Emoji( + name: 'kite', + char: '\u{1FA81}', + shortName: 'kite', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'uc12', + 'sport', + 'fly', + 'vacation', + 'fun', + 'activity', + 'toy', + 'kite', + 'stringed', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'flight', + 'flying', + 'flights', + 'avion' + ]), + Emoji( + name: 'bow and arrow', + char: '\u{1F3F9}', + shortName: 'bow_and_arrow', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'Sagittarius', + 'archer', + 'archery', + 'arrow', + 'bow', + 'tool', + 'weapon', + 'zodiac', + 'uc8', + 'sport', + 'weapon', + 'arrow', + 'game', + 'play', + 'target', + 'minecraft', + 'activity', + 'stringed', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'weapons', + 'arrows', + 'games', + 'gaming' + ]), + Emoji( + name: 'fishing pole', + char: '\u{1F3A3}', + shortName: 'fishing_pole_and_fish', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'fish', + 'pole', + 'uc6', + 'sport', + 'vacation', + 'fishing', + 'florida', + 'fun', + 'activity', + 'stringed', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'fish', + 'fishing pole', + 'fishing rod' + ]), + Emoji( + name: 'diving mask', + char: '\u{1F93F}', + shortName: 'diving_mask', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'uc12', + 'sport', + 'glasses', + 'vacation', + 'swim', + 'scuba', + 'fun', + 'activity', + 'mask', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'eyeglasses', + 'eye glasses', + 'swimming', + 'swimmer', + 'snorkel' + ]), + Emoji( + name: 'boxing glove', + char: '\u{1F94A}', + shortName: 'boxing_glove', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'boxing', + 'glove', + 'uc9', + 'sport', + 'fight', + 'gloves', + 'hit', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'punch', + 'pow', + 'bam' + ]), + Emoji( + name: 'martial arts uniform', + char: '\u{1F94B}', + shortName: 'martial_arts_uniform', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'judo', + 'karate', + 'martial arts', + 'taekwondo', + 'uniform', + 'uc9', + 'sport', + 'fight', + 'karate', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout' + ]), + Emoji( + name: 'running shirt', + char: '\u{1F3BD}', + shortName: 'running_shirt_with_sash', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'athletics', + 'running', + 'sash', + 'shirt', + 'uc6', + 'sport', + 'award', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero' + ]), + Emoji( + name: 'skateboard', + char: '\u{1F6F9}', + shortName: 'skateboard', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'uc11', + 'sport', + 'fun', + 'activity', + 'boosted', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'boosted board' + ]), + Emoji( + name: 'roller skate', + char: '\u{1F6FC}', + shortName: 'roller_skate', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'uc13', + 'sport', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout' + ]), + Emoji( + name: 'sled', + char: '\u{1F6F7}', + shortName: 'sled', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'uc10', + 'sport', + 'winter', + 'christmas', + 'fun', + 'activity', + 'sleigh', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'sledge', + 'toboggan' + ]), + Emoji( + name: 'ice skate', + char: '\u{26F8}\u{FE0F}', + shortName: 'ice_skate', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'ice', + 'skate', + 'uc5', + 'sport', + 'winter', + 'cold', + 'ice skating', + 'disney', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'cartoon' + ]), + Emoji( + name: 'curling stone', + char: '\u{1F94C}', + shortName: 'curling_stone', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'game', + 'rock', + 'uc10', + 'sport', + 'winter', + 'game', + 'play', + 'activity', + 'iron', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming' + ]), + Emoji( + name: 'skis', + char: '\u{1F3BF}', + shortName: 'ski', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'ski', + 'snow', + 'uc6', + 'sport', + 'winter', + 'cold', + 'skiing', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'ski', + 'snow skiing', + 'ski boot' + ]), + Emoji( + name: 'skier', + char: '\u{26F7}\u{FE0F}', + shortName: 'skier', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ski', + 'snow', + 'uc5', + 'sport', + 'winter', + 'vacation', + 'cold', + 'skiing', + 'paris', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'ski', + 'snow skiing', + 'ski boot', + 'french', + 'france' + ]), + Emoji( + name: 'snowboarder', + char: '\u{1F3C2}', + shortName: 'snowboarder', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ski', + 'snow', + 'snowboard', + 'uc6', + 'sport', + 'diversity', + 'winter', + 'vacation', + 'cold', + 'snowboarding', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'snowboarder' + ]), + Emoji( + name: 'snowboarder: light skin tone', + char: '\u{1F3C2}\u{1F3FB}', + shortName: 'snowboarder_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'light skin tone', + 'ski', + 'snow', + 'snowboard', + 'uc8', + 'sport', + 'diversity', + 'winter', + 'vacation', + 'cold', + 'snowboarding', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'snowboarder' + ], + modifiable: true), + Emoji( + name: 'snowboarder: medium-light skin tone', + char: '\u{1F3C2}\u{1F3FC}', + shortName: 'snowboarder_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-light skin tone', + 'ski', + 'snow', + 'snowboard', + 'uc8', + 'sport', + 'diversity', + 'winter', + 'vacation', + 'cold', + 'snowboarding', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'snowboarder' + ], + modifiable: true), + Emoji( + name: 'snowboarder: medium skin tone', + char: '\u{1F3C2}\u{1F3FD}', + shortName: 'snowboarder_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium skin tone', + 'ski', + 'snow', + 'snowboard', + 'uc8', + 'sport', + 'diversity', + 'winter', + 'vacation', + 'cold', + 'snowboarding', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'snowboarder' + ], + modifiable: true), + Emoji( + name: 'snowboarder: medium-dark skin tone', + char: '\u{1F3C2}\u{1F3FE}', + shortName: 'snowboarder_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-dark skin tone', + 'ski', + 'snow', + 'snowboard', + 'uc8', + 'sport', + 'diversity', + 'winter', + 'vacation', + 'cold', + 'snowboarding', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'snowboarder' + ], + modifiable: true), + Emoji( + name: 'snowboarder: dark skin tone', + char: '\u{1F3C2}\u{1F3FF}', + shortName: 'snowboarder_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'ski', + 'snow', + 'snowboard', + 'uc8', + 'sport', + 'diversity', + 'winter', + 'vacation', + 'cold', + 'snowboarding', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'snowboarder' + ], + modifiable: true), + Emoji( + name: 'parachute', + char: '\u{1FA82}', + shortName: 'parachute', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'uc12', + 'sport', + 'fly', + 'vacation', + 'airplane', + 'fun', + 'activity', + 'kite', + 'skydive', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'flight', + 'flying', + 'flights', + 'avion', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'hang-glide', + 'parasail' + ]), + Emoji( + name: 'person lifting weights', + char: '\u{1F3CB}', + shortName: 'person_lifting_weights', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'lifter', + 'weight', + 'uc7', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ]), + Emoji( + name: 'person lifting weights: light skin tone', + char: '\u{1F3CB}\u{1F3FB}', + shortName: 'person_lifting_weights_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'lifter', + 'light skin tone', + 'weight', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'person lifting weights: medium-light skin tone', + char: '\u{1F3CB}\u{1F3FC}', + shortName: 'person_lifting_weights_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'lifter', + 'medium-light skin tone', + 'weight', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'person lifting weights: medium skin tone', + char: '\u{1F3CB}\u{1F3FD}', + shortName: 'person_lifting_weights_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'lifter', + 'medium skin tone', + 'weight', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'person lifting weights: medium-dark skin tone', + char: '\u{1F3CB}\u{1F3FE}', + shortName: 'person_lifting_weights_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'lifter', + 'medium-dark skin tone', + 'weight', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'person lifting weights: dark skin tone', + char: '\u{1F3CB}\u{1F3FF}', + shortName: 'person_lifting_weights_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'lifter', + 'weight', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'woman lifting weights', + char: '\u{1F3CB}\u{FE0F}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_lifting_weights', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'weight lifter', + 'woman', + 'uc7', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ]), + Emoji( + name: 'woman lifting weights: light skin tone', + char: '\u{1F3CB}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_lifting_weights_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'light skin tone', + 'weight lifter', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'woman lifting weights: medium-light skin tone', + char: '\u{1F3CB}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_lifting_weights_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-light skin tone', + 'weight lifter', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'woman lifting weights: medium skin tone', + char: '\u{1F3CB}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_lifting_weights_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium skin tone', + 'weight lifter', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'woman lifting weights: medium-dark skin tone', + char: '\u{1F3CB}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_lifting_weights_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-dark skin tone', + 'weight lifter', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'woman lifting weights: dark skin tone', + char: '\u{1F3CB}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_lifting_weights_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'weight lifter', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'man lifting weights', + char: '\u{1F3CB}\u{FE0F}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_lifting_weights', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'weight lifter', + 'uc7', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ]), + Emoji( + name: 'man lifting weights: light skin tone', + char: '\u{1F3CB}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_lifting_weights_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'light skin tone', + 'man', + 'weight lifter', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'man lifting weights: medium-light skin tone', + char: '\u{1F3CB}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_lifting_weights_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'medium-light skin tone', + 'weight lifter', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'man lifting weights: medium skin tone', + char: '\u{1F3CB}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_lifting_weights_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'medium skin tone', + 'weight lifter', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'man lifting weights: medium-dark skin tone', + char: '\u{1F3CB}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_lifting_weights_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'medium-dark skin tone', + 'weight lifter', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'man lifting weights: dark skin tone', + char: '\u{1F3CB}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_lifting_weights_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'man', + 'weight lifter', + 'uc8', + 'sport', + 'diversity', + 'flex', + 'weight lifting', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'strong', + 'weight lifter' + ], + modifiable: true), + Emoji( + name: 'people wrestling', + char: '\u{1F93C}', + shortName: 'people_wrestling', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'wrestle', + 'wrestler', + 'uc9', + 'sport', + 'fight', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout' + ]), + Emoji( + name: 'women wrestling', + char: '\u{1F93C}\u{200D}\u{2640}\u{FE0F}', + shortName: 'women_wrestling', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'women', + 'wrestle', + 'uc9', + 'sport', + 'fight', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout' + ]), + Emoji( + name: 'men wrestling', + char: '\u{1F93C}\u{200D}\u{2642}\u{FE0F}', + shortName: 'men_wrestling', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'men', + 'wrestle', + 'uc9', + 'sport', + 'fight', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout' + ]), + Emoji( + name: 'person cartwheeling', + char: '\u{1F938}', + shortName: 'person_doing_cartwheel', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'uc9', + 'sport', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ]), + Emoji( + name: 'person cartwheeling: light skin tone', + char: '\u{1F938}\u{1F3FB}', + shortName: 'person_doing_cartwheel_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'light skin tone', + 'uc9', + 'sport', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'person cartwheeling: medium-light skin tone', + char: '\u{1F938}\u{1F3FC}', + shortName: 'person_doing_cartwheel_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'medium-light skin tone', + 'uc9', + 'sport', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'person cartwheeling: medium skin tone', + char: '\u{1F938}\u{1F3FD}', + shortName: 'person_doing_cartwheel_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'medium skin tone', + 'uc9', + 'sport', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'person cartwheeling: medium-dark skin tone', + char: '\u{1F938}\u{1F3FE}', + shortName: 'person_doing_cartwheel_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'medium-dark skin tone', + 'uc9', + 'sport', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'person cartwheeling: dark skin tone', + char: '\u{1F938}\u{1F3FF}', + shortName: 'person_doing_cartwheel_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'dark skin tone', + 'gymnastics', + 'uc9', + 'sport', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'woman cartwheeling', + char: '\u{1F938}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_cartwheeling', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ]), + Emoji( + name: 'woman cartwheeling: light skin tone', + char: '\u{1F938}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_cartwheeling_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'light skin tone', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'woman cartwheeling: medium-light skin tone', + char: '\u{1F938}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_cartwheeling_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'medium-light skin tone', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'woman cartwheeling: medium skin tone', + char: '\u{1F938}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_cartwheeling_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'medium skin tone', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'woman cartwheeling: medium-dark skin tone', + char: '\u{1F938}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_cartwheeling_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'medium-dark skin tone', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'woman cartwheeling: dark skin tone', + char: '\u{1F938}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_cartwheeling_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'dark skin tone', + 'gymnastics', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'man cartwheeling', + char: '\u{1F938}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_cartwheeling', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'man', + 'uc9', + 'sport', + 'diversity', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ]), + Emoji( + name: 'man cartwheeling: light skin tone', + char: '\u{1F938}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_cartwheeling_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'light skin tone', + 'man', + 'uc9', + 'sport', + 'diversity', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'man cartwheeling: medium-light skin tone', + char: '\u{1F938}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_cartwheeling_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'man', + 'medium-light skin tone', + 'uc9', + 'sport', + 'diversity', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'man cartwheeling: medium skin tone', + char: '\u{1F938}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_cartwheeling_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'man', + 'medium skin tone', + 'uc9', + 'sport', + 'diversity', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'man cartwheeling: medium-dark skin tone', + char: '\u{1F938}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_cartwheeling_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'gymnastics', + 'man', + 'medium-dark skin tone', + 'uc9', + 'sport', + 'diversity', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'man cartwheeling: dark skin tone', + char: '\u{1F938}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_cartwheeling_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'cartwheel', + 'dark skin tone', + 'gymnastics', + 'man', + 'uc9', + 'sport', + 'diversity', + 'circus', + 'gymnast', + 'yoga', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'circus tent', + 'clown', + 'clowns', + 'gymnastics', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum' + ], + modifiable: true), + Emoji( + name: 'person bouncing ball', + char: '\u{26F9}', + shortName: 'person_bouncing_ball', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'uc5', + 'sport', + 'diversity', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'person bouncing ball: light skin tone', + char: '\u{26F9}\u{1F3FB}', + shortName: 'person_bouncing_ball_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'light skin tone', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'person bouncing ball: medium-light skin tone', + char: '\u{26F9}\u{1F3FC}', + shortName: 'person_bouncing_ball_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'medium-light skin tone', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'person bouncing ball: medium skin tone', + char: '\u{26F9}\u{1F3FD}', + shortName: 'person_bouncing_ball_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'medium skin tone', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'person bouncing ball: medium-dark skin tone', + char: '\u{26F9}\u{1F3FE}', + shortName: 'person_bouncing_ball_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'medium-dark skin tone', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'person bouncing ball: dark skin tone', + char: '\u{26F9}\u{1F3FF}', + shortName: 'person_bouncing_ball_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'dark skin tone', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman bouncing ball', + char: '\u{26F9}\u{FE0F}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_bouncing_ball', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'woman', + 'uc5', + 'sport', + 'diversity', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'woman bouncing ball: light skin tone', + char: '\u{26F9}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_bouncing_ball_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'light skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman bouncing ball: medium-light skin tone', + char: '\u{26F9}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_bouncing_ball_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'medium-light skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman bouncing ball: medium skin tone', + char: '\u{26F9}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_bouncing_ball_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'medium skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman bouncing ball: medium-dark skin tone', + char: '\u{26F9}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_bouncing_ball_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'medium-dark skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman bouncing ball: dark skin tone', + char: '\u{26F9}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_bouncing_ball_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'dark skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man bouncing ball', + char: '\u{26F9}\u{FE0F}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_bouncing_ball', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'man', + 'uc5', + 'sport', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'man bouncing ball: light skin tone', + char: '\u{26F9}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_bouncing_ball_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'light skin tone', + 'man', + 'uc8', + 'sport', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man bouncing ball: medium-light skin tone', + char: '\u{26F9}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_bouncing_ball_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'man', + 'medium-light skin tone', + 'uc8', + 'sport', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man bouncing ball: medium skin tone', + char: '\u{26F9}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_bouncing_ball_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'man', + 'medium skin tone', + 'uc8', + 'sport', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man bouncing ball: medium-dark skin tone', + char: '\u{26F9}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_bouncing_ball_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'man', + 'medium-dark skin tone', + 'uc8', + 'sport', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man bouncing ball: dark skin tone', + char: '\u{26F9}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_bouncing_ball_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'dark skin tone', + 'man', + 'uc8', + 'sport', + 'ball', + 'basketball', + 'play', + 'fame', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'balls', + 'ballon', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'person fencing', + char: '\u{1F93A}', + shortName: 'person_fencing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'fencer', + 'fencing', + 'sword', + 'uc9', + 'sport', + 'fight', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout' + ]), + Emoji( + name: 'person playing handball', + char: '\u{1F93E}', + shortName: 'person_playing_handball', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'handball', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ]), + Emoji( + name: 'person playing handball: light skin tone', + char: '\u{1F93E}\u{1F3FB}', + shortName: 'person_playing_handball_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'handball', + 'light skin tone', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'person playing handball: medium-light skin tone', + char: '\u{1F93E}\u{1F3FC}', + shortName: 'person_playing_handball_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'handball', + 'medium-light skin tone', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'person playing handball: medium skin tone', + char: '\u{1F93E}\u{1F3FD}', + shortName: 'person_playing_handball_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'handball', + 'medium skin tone', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'person playing handball: medium-dark skin tone', + char: '\u{1F93E}\u{1F3FE}', + shortName: 'person_playing_handball_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'handball', + 'medium-dark skin tone', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'person playing handball: dark skin tone', + char: '\u{1F93E}\u{1F3FF}', + shortName: 'person_playing_handball_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'dark skin tone', + 'handball', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'woman playing handball', + char: '\u{1F93E}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_playing_handball', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'handball', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ]), + Emoji( + name: 'woman playing handball: light skin tone', + char: '\u{1F93E}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_playing_handball_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'handball', + 'light skin tone', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'woman playing handball: medium-light skin tone', + char: '\u{1F93E}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_playing_handball_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'handball', + 'medium-light skin tone', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'woman playing handball: medium skin tone', + char: '\u{1F93E}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_playing_handball_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'handball', + 'medium skin tone', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'woman playing handball: medium-dark skin tone', + char: '\u{1F93E}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_playing_handball_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'handball', + 'medium-dark skin tone', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'woman playing handball: dark skin tone', + char: '\u{1F93E}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_playing_handball_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'handball', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'man playing handball', + char: '\u{1F93E}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_playing_handball', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'handball', + 'man', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ]), + Emoji( + name: 'man playing handball: light skin tone', + char: '\u{1F93E}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_playing_handball_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'handball', + 'light skin tone', + 'man', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'man playing handball: medium-light skin tone', + char: '\u{1F93E}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_playing_handball_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'handball', + 'man', + 'medium-light skin tone', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'man playing handball: medium skin tone', + char: '\u{1F93E}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_playing_handball_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'handball', + 'man', + 'medium skin tone', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'man playing handball: medium-dark skin tone', + char: '\u{1F93E}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_playing_handball_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'handball', + 'man', + 'medium-dark skin tone', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'man playing handball: dark skin tone', + char: '\u{1F93E}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_playing_handball_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'handball', + 'man', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'volley ball', + 'play', + 'throw', + 'jump', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'person golfing', + char: '\u{1F3CC}', + shortName: 'person_golfing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'golf', + 'uc7', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ]), + Emoji( + name: 'person golfing: light skin tone', + char: '\u{1F3CC}\u{1F3FB}', + shortName: 'person_golfing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'golf', + 'light skin tone', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'person golfing: medium-light skin tone', + char: '\u{1F3CC}\u{1F3FC}', + shortName: 'person_golfing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'golf', + 'medium-light skin tone', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'person golfing: medium skin tone', + char: '\u{1F3CC}\u{1F3FD}', + shortName: 'person_golfing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'golf', + 'medium skin tone', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'person golfing: medium-dark skin tone', + char: '\u{1F3CC}\u{1F3FE}', + shortName: 'person_golfing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'golf', + 'medium-dark skin tone', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'person golfing: dark skin tone', + char: '\u{1F3CC}\u{1F3FF}', + shortName: 'person_golfing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'ball', + 'dark skin tone', + 'golf', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'woman golfing', + char: '\u{1F3CC}\u{FE0F}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_golfing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'golf', + 'woman', + 'uc7', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ]), + Emoji( + name: 'woman golfing: light skin tone', + char: '\u{1F3CC}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_golfing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'golf', + 'light skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'woman golfing: medium-light skin tone', + char: '\u{1F3CC}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_golfing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'golf', + 'medium-light skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'woman golfing: medium skin tone', + char: '\u{1F3CC}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_golfing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'golf', + 'medium skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'woman golfing: medium-dark skin tone', + char: '\u{1F3CC}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_golfing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'golf', + 'medium-dark skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'woman golfing: dark skin tone', + char: '\u{1F3CC}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_golfing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'golf', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'man golfing', + char: '\u{1F3CC}\u{FE0F}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_golfing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'golf', + 'man', + 'uc7', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ]), + Emoji( + name: 'man golfing: light skin tone', + char: '\u{1F3CC}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_golfing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'golf', + 'light skin tone', + 'man', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'man golfing: medium-light skin tone', + char: '\u{1F3CC}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_golfing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'golf', + 'man', + 'medium-light skin tone', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'man golfing: medium skin tone', + char: '\u{1F3CC}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_golfing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'golf', + 'man', + 'medium skin tone', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'man golfing: medium-dark skin tone', + char: '\u{1F3CC}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_golfing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'golf', + 'man', + 'medium-dark skin tone', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'man golfing: dark skin tone', + char: '\u{1F3CC}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_golfing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'golf', + 'man', + 'uc8', + 'sport', + 'diversity', + 'ball', + 'vacation', + 'golf', + 'play', + 'florida', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'golfing', + 'golfer' + ], + modifiable: true), + Emoji( + name: 'horse racing', + char: '\u{1F3C7}', + shortName: 'horse_racing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'horse', + 'jockey', + 'racehorse', + 'racing', + 'uc6', + 'sport', + 'diversity', + 'horse racing', + 'las vegas', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'horseback riding', + 'horse and rider', + 'horses', + 'horseshoe', + 'pony', + 'vegas' + ]), + Emoji( + name: 'horse racing: light skin tone', + char: '\u{1F3C7}\u{1F3FB}', + shortName: 'horse_racing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'horse', + 'jockey', + 'light skin tone', + 'racehorse', + 'racing', + 'uc8', + 'sport', + 'diversity', + 'horse racing', + 'las vegas', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'horseback riding', + 'horse and rider', + 'horses', + 'horseshoe', + 'pony', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'horse racing: medium-light skin tone', + char: '\u{1F3C7}\u{1F3FC}', + shortName: 'horse_racing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'horse', + 'jockey', + 'medium-light skin tone', + 'racehorse', + 'racing', + 'uc8', + 'sport', + 'diversity', + 'horse racing', + 'las vegas', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'horseback riding', + 'horse and rider', + 'horses', + 'horseshoe', + 'pony', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'horse racing: medium skin tone', + char: '\u{1F3C7}\u{1F3FD}', + shortName: 'horse_racing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'horse', + 'jockey', + 'medium skin tone', + 'racehorse', + 'racing', + 'uc8', + 'sport', + 'diversity', + 'horse racing', + 'las vegas', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'horseback riding', + 'horse and rider', + 'horses', + 'horseshoe', + 'pony', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'horse racing: medium-dark skin tone', + char: '\u{1F3C7}\u{1F3FE}', + shortName: 'horse_racing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'horse', + 'jockey', + 'medium-dark skin tone', + 'racehorse', + 'racing', + 'uc8', + 'sport', + 'diversity', + 'horse racing', + 'las vegas', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'horseback riding', + 'horse and rider', + 'horses', + 'horseshoe', + 'pony', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'horse racing: dark skin tone', + char: '\u{1F3C7}\u{1F3FF}', + shortName: 'horse_racing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'horse', + 'jockey', + 'racehorse', + 'racing', + 'uc8', + 'sport', + 'diversity', + 'horse racing', + 'las vegas', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'horseback riding', + 'horse and rider', + 'horses', + 'horseshoe', + 'pony', + 'vegas' + ], + modifiable: true), + Emoji( + name: 'person in lotus position', + char: '\u{1F9D8}', + shortName: 'person_in_lotus_position', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'uc10', + 'sport', + 'diversity', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ]), + Emoji( + name: 'person in lotus position: light skin tone', + char: '\u{1F9D8}\u{1F3FB}', + shortName: 'person_in_lotus_position_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'light skin tone', + 'meditation', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'person in lotus position: medium-light skin tone', + char: '\u{1F9D8}\u{1F3FC}', + shortName: 'person_in_lotus_position_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'meditation', + 'medium-light skin tone', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'person in lotus position: medium skin tone', + char: '\u{1F9D8}\u{1F3FD}', + shortName: 'person_in_lotus_position_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'meditation', + 'medium skin tone', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'person in lotus position: medium-dark skin tone', + char: '\u{1F9D8}\u{1F3FE}', + shortName: 'person_in_lotus_position_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'meditation', + 'medium-dark skin tone', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'person in lotus position: dark skin tone', + char: '\u{1F9D8}\u{1F3FF}', + shortName: 'person_in_lotus_position_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'dark skin tone', + 'meditation', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'woman in lotus position', + char: '\u{1F9D8}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_lotus_position', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'meditation', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'women', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ]), + Emoji( + name: 'woman in lotus position: light skin tone', + char: '\u{1F9D8}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_lotus_position_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'light skin tone', + 'meditation', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'women', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'woman in lotus position: medium-light skin tone', + char: '\u{1F9D8}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_lotus_position_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'meditation', + 'medium-light skin tone', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'women', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'woman in lotus position: medium skin tone', + char: '\u{1F9D8}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_lotus_position_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'meditation', + 'medium skin tone', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'women', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'woman in lotus position: medium-dark skin tone', + char: '\u{1F9D8}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_lotus_position_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'meditation', + 'medium-dark skin tone', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'women', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'woman in lotus position: dark skin tone', + char: '\u{1F9D8}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_in_lotus_position_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'dark skin tone', + 'meditation', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'women', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'woman', + 'female', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'man in lotus position', + char: '\u{1F9D8}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_lotus_position', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'meditation', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ]), + Emoji( + name: 'man in lotus position: light skin tone', + char: '\u{1F9D8}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_lotus_position_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'light skin tone', + 'meditation', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'man in lotus position: medium-light skin tone', + char: '\u{1F9D8}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_lotus_position_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'meditation', + 'medium-light skin tone', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'man in lotus position: medium skin tone', + char: '\u{1F9D8}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_lotus_position_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'meditation', + 'medium skin tone', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'man in lotus position: medium-dark skin tone', + char: '\u{1F9D8}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_lotus_position_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'meditation', + 'medium-dark skin tone', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'man in lotus position: dark skin tone', + char: '\u{1F9D8}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_in_lotus_position_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'dark skin tone', + 'meditation', + 'yoga', + 'uc10', + 'sport', + 'diversity', + 'vacation', + 'yoga', + 'california', + 'activity', + 'spa', + 'sit', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'meditation', + 'meditate', + 'zen', + 'om', + 'aum', + 'relax', + 'sauna', + 'sitting', + 'kneel', + 'kneeling' + ], + modifiable: true), + Emoji( + name: 'person surfing', + char: '\u{1F3C4}', + shortName: 'person_surfing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'surfing', + 'uc6', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ]), + Emoji( + name: 'person surfing: light skin tone', + char: '\u{1F3C4}\u{1F3FB}', + shortName: 'person_surfing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'light skin tone', + 'surfing', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'person surfing: medium-light skin tone', + char: '\u{1F3C4}\u{1F3FC}', + shortName: 'person_surfing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-light skin tone', + 'surfing', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'person surfing: medium skin tone', + char: '\u{1F3C4}\u{1F3FD}', + shortName: 'person_surfing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium skin tone', + 'surfing', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'person surfing: medium-dark skin tone', + char: '\u{1F3C4}\u{1F3FE}', + shortName: 'person_surfing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-dark skin tone', + 'surfing', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'person surfing: dark skin tone', + char: '\u{1F3C4}\u{1F3FF}', + shortName: 'person_surfing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'surfing', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'woman surfing', + char: '\u{1F3C4}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_surfing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'surfing', + 'woman', + 'uc6', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ]), + Emoji( + name: 'woman surfing: light skin tone', + char: '\u{1F3C4}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_surfing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'light skin tone', + 'surfing', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'woman surfing: medium-light skin tone', + char: '\u{1F3C4}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_surfing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-light skin tone', + 'surfing', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'woman surfing: medium skin tone', + char: '\u{1F3C4}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_surfing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium skin tone', + 'surfing', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'woman surfing: medium-dark skin tone', + char: '\u{1F3C4}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_surfing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-dark skin tone', + 'surfing', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'woman surfing: dark skin tone', + char: '\u{1F3C4}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_surfing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'surfing', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'man surfing', + char: '\u{1F3C4}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_surfing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'surfing', + 'uc6', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ]), + Emoji( + name: 'man surfing: light skin tone', + char: '\u{1F3C4}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_surfing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'light skin tone', + 'man', + 'surfing', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'man surfing: medium-light skin tone', + char: '\u{1F3C4}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_surfing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'medium-light skin tone', + 'surfing', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'man surfing: medium skin tone', + char: '\u{1F3C4}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_surfing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'medium skin tone', + 'surfing', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'man surfing: medium-dark skin tone', + char: '\u{1F3C4}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_surfing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'medium-dark skin tone', + 'surfing', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'man surfing: dark skin tone', + char: '\u{1F3C4}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_surfing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'man', + 'surfing', + 'uc8', + 'sport', + 'diversity', + 'tropical', + 'vacation', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'person swimming', + char: '\u{1F3CA}', + shortName: 'person_swimming', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'swim', + 'uc6', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ]), + Emoji( + name: 'person swimming: light skin tone', + char: '\u{1F3CA}\u{1F3FB}', + shortName: 'person_swimming_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'light skin tone', + 'swim', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'person swimming: medium-light skin tone', + char: '\u{1F3CA}\u{1F3FC}', + shortName: 'person_swimming_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-light skin tone', + 'swim', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'person swimming: medium skin tone', + char: '\u{1F3CA}\u{1F3FD}', + shortName: 'person_swimming_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium skin tone', + 'swim', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'person swimming: medium-dark skin tone', + char: '\u{1F3CA}\u{1F3FE}', + shortName: 'person_swimming_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-dark skin tone', + 'swim', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'person swimming: dark skin tone', + char: '\u{1F3CA}\u{1F3FF}', + shortName: 'person_swimming_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'swim', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'woman swimming', + char: '\u{1F3CA}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_swimming', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'swim', + 'woman', + 'uc6', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ]), + Emoji( + name: 'woman swimming: light skin tone', + char: '\u{1F3CA}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_swimming_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'light skin tone', + 'swim', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'woman swimming: medium-light skin tone', + char: '\u{1F3CA}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_swimming_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-light skin tone', + 'swim', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'woman swimming: medium skin tone', + char: '\u{1F3CA}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_swimming_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium skin tone', + 'swim', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'woman swimming: medium-dark skin tone', + char: '\u{1F3CA}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_swimming_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-dark skin tone', + 'swim', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'woman swimming: dark skin tone', + char: '\u{1F3CA}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_swimming_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'swim', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'man swimming', + char: '\u{1F3CA}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_swimming', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'swim', + 'uc6', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ]), + Emoji( + name: 'man swimming: light skin tone', + char: '\u{1F3CA}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_swimming_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'light skin tone', + 'man', + 'swim', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'man swimming: medium-light skin tone', + char: '\u{1F3CA}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_swimming_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'medium-light skin tone', + 'swim', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'man swimming: medium skin tone', + char: '\u{1F3CA}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_swimming_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'medium skin tone', + 'swim', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'man swimming: medium-dark skin tone', + char: '\u{1F3CA}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_swimming_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'medium-dark skin tone', + 'swim', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'man swimming: dark skin tone', + char: '\u{1F3CA}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_swimming_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'man', + 'swim', + 'uc8', + 'sport', + 'diversity', + 'vacation', + 'swim', + 'scuba', + 'summer', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'swimming', + 'swimmer', + 'snorkel', + 'weekend' + ], + modifiable: true), + Emoji( + name: 'person playing water polo', + char: '\u{1F93D}', + shortName: 'person_playing_water_polo', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'polo', + 'water', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ]), + Emoji( + name: 'person playing water polo: light skin tone', + char: '\u{1F93D}\u{1F3FB}', + shortName: 'person_playing_water_polo_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'light skin tone', + 'polo', + 'water', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'person playing water polo: medium-light skin tone', + char: '\u{1F93D}\u{1F3FC}', + shortName: 'person_playing_water_polo_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-light skin tone', + 'polo', + 'water', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'person playing water polo: medium skin tone', + char: '\u{1F93D}\u{1F3FD}', + shortName: 'person_playing_water_polo_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium skin tone', + 'polo', + 'water', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'person playing water polo: medium-dark skin tone', + char: '\u{1F93D}\u{1F3FE}', + shortName: 'person_playing_water_polo_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-dark skin tone', + 'polo', + 'water', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'person playing water polo: dark skin tone', + char: '\u{1F93D}\u{1F3FF}', + shortName: 'person_playing_water_polo_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'polo', + 'water', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'woman playing water polo', + char: '\u{1F93D}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_playing_water_polo', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'water polo', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ]), + Emoji( + name: 'woman playing water polo: light skin tone', + char: '\u{1F93D}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_playing_water_polo_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'light skin tone', + 'water polo', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'woman playing water polo: medium-light skin tone', + char: '\u{1F93D}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_playing_water_polo_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-light skin tone', + 'water polo', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'woman playing water polo: medium skin tone', + char: '\u{1F93D}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_playing_water_polo_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium skin tone', + 'water polo', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'woman playing water polo: medium-dark skin tone', + char: '\u{1F93D}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_playing_water_polo_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'medium-dark skin tone', + 'water polo', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'woman playing water polo: dark skin tone', + char: '\u{1F93D}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_playing_water_polo_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'water polo', + 'woman', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'man playing water polo', + char: '\u{1F93D}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_playing_water_polo', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'water polo', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ]), + Emoji( + name: 'man playing water polo: light skin tone', + char: '\u{1F93D}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_playing_water_polo_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'light skin tone', + 'man', + 'water polo', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'man playing water polo: medium-light skin tone', + char: '\u{1F93D}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_playing_water_polo_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'medium-light skin tone', + 'water polo', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'man playing water polo: medium skin tone', + char: '\u{1F93D}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_playing_water_polo_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'medium skin tone', + 'water polo', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'man playing water polo: medium-dark skin tone', + char: '\u{1F93D}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_playing_water_polo_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'man', + 'medium-dark skin tone', + 'water polo', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'man playing water polo: dark skin tone', + char: '\u{1F93D}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_playing_water_polo_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'man', + 'water polo', + 'uc9', + 'sport', + 'diversity', + 'ball', + 'play', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon' + ], + modifiable: true), + Emoji( + name: 'person rowing boat', + char: '\u{1F6A3}', + shortName: 'person_rowing_boat', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'rowboat', + 'uc6', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ]), + Emoji( + name: 'person rowing boat: light skin tone', + char: '\u{1F6A3}\u{1F3FB}', + shortName: 'person_rowing_boat_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'light skin tone', + 'rowboat', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'person rowing boat: medium-light skin tone', + char: '\u{1F6A3}\u{1F3FC}', + shortName: 'person_rowing_boat_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'medium-light skin tone', + 'rowboat', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'person rowing boat: medium skin tone', + char: '\u{1F6A3}\u{1F3FD}', + shortName: 'person_rowing_boat_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'medium skin tone', + 'rowboat', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'person rowing boat: medium-dark skin tone', + char: '\u{1F6A3}\u{1F3FE}', + shortName: 'person_rowing_boat_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'medium-dark skin tone', + 'rowboat', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'person rowing boat: dark skin tone', + char: '\u{1F6A3}\u{1F3FF}', + shortName: 'person_rowing_boat_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'dark skin tone', + 'rowboat', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'woman rowing boat', + char: '\u{1F6A3}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_rowing_boat', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'rowboat', + 'woman', + 'uc6', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ]), + Emoji( + name: 'woman rowing boat: light skin tone', + char: '\u{1F6A3}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_rowing_boat_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'light skin tone', + 'rowboat', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'woman rowing boat: medium-light skin tone', + char: '\u{1F6A3}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_rowing_boat_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'medium-light skin tone', + 'rowboat', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'woman rowing boat: medium skin tone', + char: '\u{1F6A3}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_rowing_boat_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'medium skin tone', + 'rowboat', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'woman rowing boat: medium-dark skin tone', + char: '\u{1F6A3}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_rowing_boat_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'medium-dark skin tone', + 'rowboat', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'woman rowing boat: dark skin tone', + char: '\u{1F6A3}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_rowing_boat_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'dark skin tone', + 'rowboat', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'man rowing boat', + char: '\u{1F6A3}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_rowing_boat', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'man', + 'rowboat', + 'uc6', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ]), + Emoji( + name: 'man rowing boat: light skin tone', + char: '\u{1F6A3}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_rowing_boat_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'light skin tone', + 'man', + 'rowboat', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'man rowing boat: medium-light skin tone', + char: '\u{1F6A3}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_rowing_boat_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'man', + 'medium-light skin tone', + 'rowboat', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'man rowing boat: medium skin tone', + char: '\u{1F6A3}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_rowing_boat_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'man', + 'medium skin tone', + 'rowboat', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'man rowing boat: medium-dark skin tone', + char: '\u{1F6A3}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_rowing_boat_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'man', + 'medium-dark skin tone', + 'rowboat', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'man rowing boat: dark skin tone', + char: '\u{1F6A3}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_rowing_boat_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'boat', + 'dark skin tone', + 'man', + 'rowboat', + 'uc8', + 'sport', + 'diversity', + 'boat', + 'rowing', + 'hawaii', + 'scuba', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'boats', + 'boating', + 'rowboat', + 'canoe', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel' + ], + modifiable: true), + Emoji( + name: 'person climbing', + char: '\u{1F9D7}', + shortName: 'person_climbing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ]), + Emoji( + name: 'person climbing: light skin tone', + char: '\u{1F9D7}\u{1F3FB}', + shortName: 'person_climbing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'light skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'person climbing: medium-light skin tone', + char: '\u{1F9D7}\u{1F3FC}', + shortName: 'person_climbing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'medium-light skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'person climbing: medium skin tone', + char: '\u{1F9D7}\u{1F3FD}', + shortName: 'person_climbing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'medium skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'person climbing: medium-dark skin tone', + char: '\u{1F9D7}\u{1F3FE}', + shortName: 'person_climbing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'medium-dark skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'person climbing: dark skin tone', + char: '\u{1F9D7}\u{1F3FF}', + shortName: 'person_climbing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'dark skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'woman climbing', + char: '\u{1F9D7}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_climbing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ]), + Emoji( + name: 'woman climbing: light skin tone', + char: '\u{1F9D7}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_climbing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'light skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'woman climbing: medium-light skin tone', + char: '\u{1F9D7}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_climbing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'medium-light skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'woman climbing: medium skin tone', + char: '\u{1F9D7}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_climbing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'medium skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'woman climbing: medium-dark skin tone', + char: '\u{1F9D7}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_climbing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'medium-dark skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'woman climbing: dark skin tone', + char: '\u{1F9D7}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_climbing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'dark skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'man climbing', + char: '\u{1F9D7}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_climbing', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ]), + Emoji( + name: 'man climbing: light skin tone', + char: '\u{1F9D7}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_climbing_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'light skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'man climbing: medium-light skin tone', + char: '\u{1F9D7}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_climbing_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'medium-light skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'man climbing: medium skin tone', + char: '\u{1F9D7}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_climbing_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'medium skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'man climbing: medium-dark skin tone', + char: '\u{1F9D7}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_climbing_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'medium-dark skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'man climbing: dark skin tone', + char: '\u{1F9D7}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_climbing_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personActivity, + keywords: [ + 'climber', + 'dark skin tone', + 'uc10', + 'sport', + 'diversity', + 'fun', + 'activity', + 'rock climbing', + 'climb', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'climber' + ], + modifiable: true), + Emoji( + name: 'person mountain biking', + char: '\u{1F6B5}', + shortName: 'person_mountain_biking', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bicyclist', + 'bike', + 'cyclist', + 'mountain', + 'uc6', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ]), + Emoji( + name: 'person mountain biking: light skin tone', + char: '\u{1F6B5}\u{1F3FB}', + shortName: 'person_mountain_biking_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bicyclist', + 'bike', + 'cyclist', + 'light skin tone', + 'mountain', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'person mountain biking: medium-light skin tone', + char: '\u{1F6B5}\u{1F3FC}', + shortName: 'person_mountain_biking_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bicyclist', + 'bike', + 'cyclist', + 'medium-light skin tone', + 'mountain', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'person mountain biking: medium skin tone', + char: '\u{1F6B5}\u{1F3FD}', + shortName: 'person_mountain_biking_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bicyclist', + 'bike', + 'cyclist', + 'medium skin tone', + 'mountain', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'person mountain biking: medium-dark skin tone', + char: '\u{1F6B5}\u{1F3FE}', + shortName: 'person_mountain_biking_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bicyclist', + 'bike', + 'cyclist', + 'medium-dark skin tone', + 'mountain', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'person mountain biking: dark skin tone', + char: '\u{1F6B5}\u{1F3FF}', + shortName: 'person_mountain_biking_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bicyclist', + 'bike', + 'cyclist', + 'dark skin tone', + 'mountain', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'woman mountain biking', + char: '\u{1F6B5}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_mountain_biking', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bike', + 'biking', + 'cyclist', + 'mountain', + 'woman', + 'uc6', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ]), + Emoji( + name: 'woman mountain biking: light skin tone', + char: '\u{1F6B5}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_mountain_biking_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bike', + 'biking', + 'cyclist', + 'light skin tone', + 'mountain', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'woman mountain biking: medium-light skin tone', + char: '\u{1F6B5}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_mountain_biking_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bike', + 'biking', + 'cyclist', + 'medium-light skin tone', + 'mountain', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'woman mountain biking: medium skin tone', + char: '\u{1F6B5}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_mountain_biking_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bike', + 'biking', + 'cyclist', + 'medium skin tone', + 'mountain', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'woman mountain biking: medium-dark skin tone', + char: '\u{1F6B5}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_mountain_biking_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bike', + 'biking', + 'cyclist', + 'medium-dark skin tone', + 'mountain', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'woman mountain biking: dark skin tone', + char: '\u{1F6B5}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_mountain_biking_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bike', + 'biking', + 'cyclist', + 'dark skin tone', + 'mountain', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'man mountain biking', + char: '\u{1F6B5}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_mountain_biking', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bike', + 'cyclist', + 'man', + 'mountain', + 'uc6', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ]), + Emoji( + name: 'man mountain biking: light skin tone', + char: '\u{1F6B5}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_mountain_biking_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bike', + 'cyclist', + 'light skin tone', + 'man', + 'mountain', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'man mountain biking: medium-light skin tone', + char: '\u{1F6B5}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_mountain_biking_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bike', + 'cyclist', + 'man', + 'medium-light skin tone', + 'mountain', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'man mountain biking: medium skin tone', + char: '\u{1F6B5}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_mountain_biking_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bike', + 'cyclist', + 'man', + 'medium skin tone', + 'mountain', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'man mountain biking: medium-dark skin tone', + char: '\u{1F6B5}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_mountain_biking_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bike', + 'cyclist', + 'man', + 'medium-dark skin tone', + 'mountain', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'man mountain biking: dark skin tone', + char: '\u{1F6B5}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_mountain_biking_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'bike', + 'cyclist', + 'dark skin tone', + 'man', + 'mountain', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'person biking', + char: '\u{1F6B4}', + shortName: 'person_biking', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'uc6', + 'sport', + 'diversity', + 'bike', + 'fame', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'person biking: light skin tone', + char: '\u{1F6B4}\u{1F3FB}', + shortName: 'person_biking_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'light skin tone', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fame', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'person biking: medium-light skin tone', + char: '\u{1F6B4}\u{1F3FC}', + shortName: 'person_biking_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'medium-light skin tone', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fame', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'person biking: medium skin tone', + char: '\u{1F6B4}\u{1F3FD}', + shortName: 'person_biking_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'medium skin tone', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fame', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'person biking: medium-dark skin tone', + char: '\u{1F6B4}\u{1F3FE}', + shortName: 'person_biking_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'medium-dark skin tone', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fame', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'person biking: dark skin tone', + char: '\u{1F6B4}\u{1F3FF}', + shortName: 'person_biking_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'dark skin tone', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fame', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'woman biking', + char: '\u{1F6B4}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_biking', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'woman', + 'uc6', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ]), + Emoji( + name: 'woman biking: light skin tone', + char: '\u{1F6B4}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_biking_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'light skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'woman biking: medium-light skin tone', + char: '\u{1F6B4}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_biking_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'medium-light skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'woman biking: medium skin tone', + char: '\u{1F6B4}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_biking_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'medium skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'woman biking: medium-dark skin tone', + char: '\u{1F6B4}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_biking_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'medium-dark skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'woman biking: dark skin tone', + char: '\u{1F6B4}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_biking_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'dark skin tone', + 'woman', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling' + ], + modifiable: true), + Emoji( + name: 'man biking', + char: '\u{1F6B4}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_biking', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'man', + 'uc6', + 'sport', + 'diversity', + 'bike', + 'fame', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'man biking: light skin tone', + char: '\u{1F6B4}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_biking_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'light skin tone', + 'man', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fame', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man biking: medium-light skin tone', + char: '\u{1F6B4}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_biking_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'man', + 'medium-light skin tone', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fame', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man biking: medium skin tone', + char: '\u{1F6B4}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_biking_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'man', + 'medium skin tone', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fame', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man biking: medium-dark skin tone', + char: '\u{1F6B4}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_biking_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'man', + 'medium-dark skin tone', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fame', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'man biking: dark skin tone', + char: '\u{1F6B4}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_biking_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'bicycle', + 'biking', + 'cyclist', + 'dark skin tone', + 'man', + 'uc8', + 'sport', + 'diversity', + 'bike', + 'fame', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'bikes', + 'bicycle', + 'bicycling', + 'famous', + 'celebrity' + ], + modifiable: true), + Emoji( + name: 'trophy', + char: '\u{1F3C6}', + shortName: 'trophy', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.awardMedal, + keywords: [ + 'prize', + 'uc6', + 'sport', + 'game', + 'award', + 'football', + 'soccer', + 'perfect', + 'win', + 'harry potter', + 'nerd', + 'fame', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'soccer ball', + 'world cup', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'smart', + 'geek', + 'serious', + 'famous', + 'celebrity' + ]), + Emoji( + name: '1st place medal', + char: '\u{1F947}', + shortName: 'first_place', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.awardMedal, + keywords: [ + 'first', + 'gold', + 'medal', + 'uc9', + 'sport', + 'award', + 'win', + 'medal', + 'gymnast', + 'fame', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'medals', + 'gold medal', + 'silver medal', + 'bronze medal', + 'gymnastics', + 'famous', + 'celebrity' + ]), + Emoji( + name: '2nd place medal', + char: '\u{1F948}', + shortName: 'second_place', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.awardMedal, + keywords: [ + 'medal', + 'second', + 'silver', + 'uc9', + 'sport', + 'award', + 'win', + 'medal', + 'gymnast', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'medals', + 'gold medal', + 'silver medal', + 'bronze medal', + 'gymnastics' + ]), + Emoji( + name: '3rd place medal', + char: '\u{1F949}', + shortName: 'third_place', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.awardMedal, + keywords: [ + 'bronze', + 'medal', + 'third', + 'uc9', + 'sport', + 'award', + 'win', + 'medal', + 'gymnast', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'medals', + 'gold medal', + 'silver medal', + 'bronze medal', + 'gymnastics' + ]), + Emoji( + name: 'sports medal', + char: '\u{1F3C5}', + shortName: 'medal', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.awardMedal, + keywords: [ + 'medal', + 'uc7', + 'sport', + 'award', + 'perfect', + 'win', + 'medal', + 'gymnast', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'perfecto', + 'perfection', + 'superb', + 'flawless', + 'excellent', + 'supreme', + 'super', + 'great', + 'winning', + 'killing it', + 'crushing it', + 'victory', + 'victorious', + 'success', + 'successful', + 'winner', + 'medals', + 'gold medal', + 'silver medal', + 'bronze medal', + 'gymnastics' + ]), + Emoji( + name: 'military medal', + char: '\u{1F396}\u{FE0F}', + shortName: 'military_medal', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.awardMedal, + keywords: [ + 'celebration', + 'medal', + 'military', + 'uc7', + 'award', + 'medal', + 'gymnast', + 'activity', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'medals', + 'gold medal', + 'silver medal', + 'bronze medal', + 'gymnastics' + ]), + Emoji( + name: 'rosette', + char: '\u{1F3F5}\u{FE0F}', + shortName: 'rosette', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.plantFlower, + keywords: ['plant', 'uc7', 'tropical', 'activity']), + Emoji( + name: 'reminder ribbon', + char: '\u{1F397}\u{FE0F}', + shortName: 'reminder_ribbon', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'celebration', + 'reminder', + 'ribbon', + 'uc7', + 'award', + 'hope', + 'activity', + 'important', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'swear', + 'promise' + ]), + Emoji( + name: 'ticket', + char: '\u{1F3AB}', + shortName: 'ticket', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'admission', + 'uc6', + 'theatre', + 'instruments', + 'movie', + 'amusement park', + 'circus', + 'pink', + 'disney', + 'discount', + 'las vegas', + 'activity', + 'opera', + 'theater', + 'craft', + 'drama', + 'monet', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'movies', + 'cinema', + 'film', + 'films', + 'video', + 'videos', + 'theme park', + 'circus tent', + 'clown', + 'clowns', + 'rose', + 'cartoon', + 'sale', + 'bargain', + 'vegas' + ]), + Emoji( + name: 'admission tickets', + char: '\u{1F39F}\u{FE0F}', + shortName: 'tickets', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'admission', + 'ticket', + 'uc7', + 'theatre', + 'instruments', + 'movie', + 'amusement park', + 'circus', + 'disney', + 'activity', + 'theater', + 'craft', + 'drama', + 'monet', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'movies', + 'cinema', + 'film', + 'films', + 'video', + 'videos', + 'theme park', + 'circus tent', + 'clown', + 'clowns', + 'cartoon' + ]), + Emoji( + name: 'circus tent', + char: '\u{1F3AA}', + shortName: 'circus_tent', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'circus', + 'tent', + 'uc6', + 'amusement park', + 'circus', + 'magic', + 'activity', + 'independence day', + 'opera', + 'theme park', + 'circus tent', + 'clown', + 'clowns', + 'spell', + 'genie', + 'magical', + '4th of july' + ]), + Emoji( + name: 'person juggling', + char: '\u{1F939}', + shortName: 'person_juggling', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'balance', + 'juggle', + 'multitask', + 'skill', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ]), + Emoji( + name: 'person juggling: light skin tone', + char: '\u{1F939}\u{1F3FB}', + shortName: 'person_juggling_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'balance', + 'juggle', + 'light skin tone', + 'multitask', + 'skill', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'person juggling: medium-light skin tone', + char: '\u{1F939}\u{1F3FC}', + shortName: 'person_juggling_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'balance', + 'juggle', + 'medium-light skin tone', + 'multitask', + 'skill', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'person juggling: medium skin tone', + char: '\u{1F939}\u{1F3FD}', + shortName: 'person_juggling_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'balance', + 'juggle', + 'medium skin tone', + 'multitask', + 'skill', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'person juggling: medium-dark skin tone', + char: '\u{1F939}\u{1F3FE}', + shortName: 'person_juggling_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'balance', + 'juggle', + 'medium-dark skin tone', + 'multitask', + 'skill', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'person juggling: dark skin tone', + char: '\u{1F939}\u{1F3FF}', + shortName: 'person_juggling_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'balance', + 'dark skin tone', + 'juggle', + 'multitask', + 'skill', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'woman juggling', + char: '\u{1F939}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_juggling', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'juggling', + 'multitask', + 'woman', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ]), + Emoji( + name: 'woman juggling: light skin tone', + char: '\u{1F939}\u{1F3FB}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_juggling_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'juggling', + 'light skin tone', + 'multitask', + 'woman', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'woman juggling: medium-light skin tone', + char: '\u{1F939}\u{1F3FC}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_juggling_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'juggling', + 'medium-light skin tone', + 'multitask', + 'woman', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'woman juggling: medium skin tone', + char: '\u{1F939}\u{1F3FD}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_juggling_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'juggling', + 'medium skin tone', + 'multitask', + 'woman', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'woman juggling: medium-dark skin tone', + char: '\u{1F939}\u{1F3FE}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_juggling_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'juggling', + 'medium-dark skin tone', + 'multitask', + 'woman', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'woman juggling: dark skin tone', + char: '\u{1F939}\u{1F3FF}\u{200D}\u{2640}\u{FE0F}', + shortName: 'woman_juggling_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'juggling', + 'multitask', + 'woman', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'man juggling', + char: '\u{1F939}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_juggling', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'juggling', + 'man', + 'multitask', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ]), + Emoji( + name: 'man juggling: light skin tone', + char: '\u{1F939}\u{1F3FB}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_juggling_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'juggling', + 'light skin tone', + 'man', + 'multitask', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'man juggling: medium-light skin tone', + char: '\u{1F939}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_juggling_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'juggling', + 'man', + 'medium-light skin tone', + 'multitask', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'man juggling: medium skin tone', + char: '\u{1F939}\u{1F3FD}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_juggling_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'juggling', + 'man', + 'medium skin tone', + 'multitask', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'man juggling: medium-dark skin tone', + char: '\u{1F939}\u{1F3FE}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_juggling_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'juggling', + 'man', + 'medium-dark skin tone', + 'multitask', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'man juggling: dark skin tone', + char: '\u{1F939}\u{1F3FF}\u{200D}\u{2642}\u{FE0F}', + shortName: 'man_juggling_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personSport, + keywords: [ + 'dark skin tone', + 'juggling', + 'man', + 'multitask', + 'uc9', + 'diversity', + 'ball', + 'circus', + 'throw', + 'activity', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'balls', + 'ballon', + 'circus tent', + 'clown', + 'clowns' + ], + modifiable: true), + Emoji( + name: 'performing arts', + char: '\u{1F3AD}', + shortName: 'performing_arts', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.artsCrafts, + keywords: [ + 'art', + 'mask', + 'performing', + 'theater', + 'theatre', + 'uc6', + 'theatre', + 'halloween', + 'movie', + 'circus', + 'girls night', + 'play', + 'fame', + 'las vegas', + 'fun', + 'activity', + 'mask', + 'fantasy', + 'opera', + 'theater', + 'craft', + 'drama', + 'monet', + 'samhain', + 'movies', + 'cinema', + 'film', + 'films', + 'video', + 'videos', + 'circus tent', + 'clown', + 'clowns', + 'ladies night', + 'girls only', + 'girlfriend', + 'famous', + 'celebrity', + 'vegas' + ]), + Emoji( + name: 'ballet shoes', + char: '\u{1FA70}', + shortName: 'ballet_shoes', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'uc12', + 'shoe', + 'dance', + 'vintage', + 'activity', + 'opera', + 'shoes', + 'baskets', + 'loafers', + 'sandals', + 'pumps', + 'boots', + 'heels', + 'dancers', + 'dancing', + 'ballet', + 'ballerina', + 'dabbing', + 'salsa' + ]), + Emoji( + name: 'artist palette', + char: '\u{1F3A8}', + shortName: 'art', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.artsCrafts, + keywords: [ + 'art', + 'museum', + 'painting', + 'palette', + 'uc6', + 'theatre', + 'painting', + 'color', + 'instagram', + 'fun', + 'activity', + 'theater', + 'craft', + 'drama', + 'monet', + 'painter', + 'arts', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch' + ]), + Emoji( + name: 'clapper board', + char: '\u{1F3AC}', + shortName: 'clapper', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'clapper', + 'movie', + 'uc6', + 'movie', + 'disney', + 'california', + 'fame', + 'activity', + 'movies', + 'cinema', + 'film', + 'films', + 'video', + 'videos', + 'cartoon', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'microphone', + char: '\u{1F3A4}', + shortName: 'microphone', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.music, + keywords: [ + 'karaoke', + 'mic', + 'uc6', + 'instruments', + 'rock and roll', + 'disco', + 'fame', + 'activity', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'headphone', + char: '\u{1F3A7}', + shortName: 'headphones', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.music, + keywords: [ + 'earbud', + 'uc6', + 'instruments', + 'headphones', + 'rock and roll', + 'earphone', + 'activity', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'headphone', + 'head phones', + 'casque', + 'earbud' + ]), + Emoji( + name: 'musical score', + char: '\u{1F3BC}', + shortName: 'musical_score', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.music, + keywords: [ + 'music', + 'score', + 'uc6', + 'instruments', + 'piano', + 'rock and roll', + 'disco', + 'activity', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique' + ]), + Emoji( + name: 'musical keyboard', + char: '\u{1F3B9}', + shortName: 'musical_keyboard', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.musicalInstrument, + keywords: [ + 'instrument', + 'keyboard', + 'music', + 'piano', + 'uc6', + 'instruments', + 'play', + 'keyboard', + 'piano', + 'rock and roll', + 'activity', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'keyboards' + ]), + Emoji( + name: 'drum', + char: '\u{1F941}', + shortName: 'drum', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.musicalInstrument, + keywords: [ + 'drum', + 'drumsticks', + 'music', + 'uc9', + 'instruments', + 'play', + 'rock and roll', + 'activity', + 'toy', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique' + ]), + Emoji( + name: 'long drum', + char: '\u{1FA98}', + shortName: 'long_drum', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.musicalInstrument, + keywords: [ + 'uc13', + 'instruments', + 'play', + 'activity', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique' + ]), + Emoji( + name: 'saxophone', + char: '\u{1F3B7}', + shortName: 'saxophone', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.musicalInstrument, + keywords: [ + 'instrument', + 'music', + 'sax', + 'uc6', + 'instruments', + 'play', + 'activity', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique' + ]), + Emoji( + name: 'trumpet', + char: '\u{1F3BA}', + shortName: 'trumpet', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.musicalInstrument, + keywords: [ + 'instrument', + 'music', + 'uc6', + 'instruments', + 'play', + 'activity', + 'independence day', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + '4th of july' + ]), + Emoji( + name: 'guitar', + char: '\u{1F3B8}', + shortName: 'guitar', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.musicalInstrument, + keywords: [ + 'instrument', + 'music', + 'uc6', + 'instruments', + 'mexican', + 'play', + 'rock and roll', + 'activity', + 'guitarra', + 'stringed', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'mexico', + 'cinco de mayo', + 'espaÃąol', + 'guitare', + 'gitarre', + 'chitarra', + 'bangio' + ]), + Emoji( + name: 'banjo', + char: '\u{1FA95}', + shortName: 'banjo', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.musicalInstrument, + keywords: [ + 'uc12', + 'instruments', + 'play', + 'activity', + 'toy', + 'guitarra', + 'stringed', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'guitare', + 'gitarre', + 'chitarra', + 'bangio' + ]), + Emoji( + name: 'violin', + char: '\u{1F3BB}', + shortName: 'violin', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.musicalInstrument, + keywords: [ + 'instrument', + 'music', + 'uc6', + 'instruments', + 'sarcastic', + 'play', + 'activity', + 'stringed', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'sarcasm' + ]), + Emoji( + name: 'accordion', + char: '\u{1FA97}', + shortName: 'accordion', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.musicalInstrument, + keywords: [ + 'uc13', + 'instruments', + 'play', + 'keyboard', + 'activity', + 'squeezebox', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'keyboards', + 'akkordeon', + 'bellows', + 'aerophone' + ]), + Emoji( + name: 'game die', + char: '\u{1F3B2}', + shortName: 'game_die', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'dice', + 'die', + 'game', + 'uc6', + 'game', + 'boys night', + 'play', + 'bingo', + 'las vegas', + 'dice', + 'fun', + 'activity', + 'toy', + 'games', + 'gaming', + 'guys night', + 'vegas' + ]), + Emoji( + name: 'chess pawn', + char: '\u{265F}\u{FE0F}', + shortName: 'chess_pawn', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: ['uc1', 'game', 'play', 'fun', 'activity', 'games', 'gaming']), + Emoji( + name: 'direct hit', + char: '\u{1F3AF}', + shortName: 'dart', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'bull', + 'bullseye', + 'dart', + 'eye', + 'game', + 'hit', + 'target', + 'uc6', + 'sport', + 'game', + 'boys night', + 'play', + 'target', + 'fun', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'guys night' + ]), + Emoji( + name: 'bowling', + char: '\u{1F3B3}', + shortName: 'bowling', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.sport, + keywords: [ + 'ball', + 'game', + 'uc6', + 'sport', + 'game', + 'ball', + 'boys night', + 'play', + 'fun', + 'throw', + 'activity', + 'sports', + 'exercise', + 'athlete', + 'athletes', + 'athletic', + 'team', + 'fitness', + 'work out', + 'workout', + 'games', + 'gaming', + 'balls', + 'ballon', + 'guys night' + ]), + Emoji( + name: 'video game', + char: '\u{1F3AE}', + shortName: 'video_game', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'controller', + 'game', + 'uc6', + 'electronics', + 'game', + 'boys night', + 'play', + 'controller', + 'fun', + 'activity', + 'games', + 'gaming', + 'guys night', + 'remote' + ]), + Emoji( + name: 'slot machine', + char: '\u{1F3B0}', + shortName: 'slot_machine', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'game', + 'slot', + 'uc6', + 'game', + 'boys night', + 'play', + 'bingo', + 'las vegas', + 'fun', + 'activity', + 'games', + 'gaming', + 'guys night', + 'vegas' + ]), + Emoji( + name: 'puzzle piece', + char: '\u{1F9E9}', + shortName: 'jigsaw', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'uc11', + 'game', + 'play', + 'fun', + 'puzzle', + 'activity', + 'household', + 'toy', + 'question', + 'games', + 'gaming', + 'quiz', + 'puzzled' + ]), + Emoji( + name: 'automobile', + char: '\u{1F697}', + shortName: 'red_car', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'car', + 'uc6', + 'transportation', + 'car', + 'travel', + 'toy', + 'cars', + 'vehicle', + 'fast car', + 'drive', + 'driving', + 'auto' + ]), + Emoji( + name: 'taxi', + char: '\u{1F695}', + shortName: 'taxi', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'vehicle', + 'uc6', + 'transportation', + 'car', + 'travel', + 'cars', + 'vehicle', + 'fast car', + 'drive', + 'driving', + 'auto' + ]), + Emoji( + name: 'sport utility vehicle', + char: '\u{1F699}', + shortName: 'blue_car', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'recreational', + 'sport utility', + 'uc6', + 'transportation', + 'car', + 'travel', + 'cars', + 'vehicle', + 'fast car', + 'drive', + 'driving', + 'auto' + ]), + Emoji( + name: 'pickup truck', + char: '\u{1F6FB}', + shortName: 'pickup_truck', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'uc13', + 'transportation', + 'car', + 'truck', + 'travel', + 'cars', + 'vehicle', + 'fast car', + 'drive', + 'driving', + 'auto', + 'trucks' + ]), + Emoji( + name: 'bus', + char: '\u{1F68C}', + shortName: 'bus', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'vehicle', + 'uc6', + 'transportation', + 'bus', + 'classroom', + 'travel', + 'vacation', + 'buses', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning' + ]), + Emoji( + name: 'trolleybus', + char: '\u{1F68E}', + shortName: 'trolleybus', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'bus', + 'tram', + 'trolley', + 'uc6', + 'transportation', + 'bus', + 'travel', + 'buses' + ]), + Emoji( + name: 'racing car', + char: '\u{1F3CE}\u{FE0F}', + shortName: 'race_car', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'car', + 'racing', + 'uc7', + 'transportation', + 'car', + 'disney', + 'fun', + 'rich', + 'cars', + 'vehicle', + 'fast car', + 'drive', + 'driving', + 'auto', + 'cartoon', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'police car', + char: '\u{1F693}', + shortName: 'police_car', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'car', + 'patrol', + 'police', + 'uc6', + 'transportation', + 'car', + 'police', + '911', + 'sirens', + 'help', + 'cars', + 'vehicle', + 'fast car', + 'drive', + 'driving', + 'auto', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'switch' + ]), + Emoji( + name: 'ambulance', + char: '\u{1F691}', + shortName: 'ambulance', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'vehicle', + 'uc6', + 'transportation', + '911', + 'sirens', + 'help', + 'poison', + 'covid', + 'emergency', + 'injury', + 'switch', + 'toxic', + 'toxins' + ]), + Emoji( + name: 'fire engine', + char: '\u{1F692}', + shortName: 'fire_engine', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'engine', + 'fire', + 'truck', + 'uc6', + 'transportation', + 'truck', + '911', + 'sirens', + 'help', + 'fires', + 'trucks', + 'emergency', + 'injury', + 'switch' + ]), + Emoji( + name: 'minibus', + char: '\u{1F690}', + shortName: 'minibus', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'bus', + 'uc6', + 'transportation', + 'bus', + 'travel', + 'camp', + 'vacation', + 'buses', + 'camping', + 'tent', + 'camper', + 'outdoor', + 'outside' + ]), + Emoji( + name: 'delivery truck', + char: '\u{1F69A}', + shortName: 'truck', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'delivery', + 'truck', + 'uc6', + 'transportation', + 'truck', + 'moving', + 'trucks' + ]), + Emoji( + name: 'articulated lorry', + char: '\u{1F69B}', + shortName: 'articulated_lorry', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'lorry', + 'semi', + 'truck', + 'uc6', + 'transportation', + 'truck', + 'moving', + 'trucks' + ]), + Emoji( + name: 'tractor', + char: '\u{1F69C}', + shortName: 'tractor', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: ['vehicle', 'uc6', 'transportation', 'farm']), + Emoji( + name: 'white cane', + char: '\u{1F9AF}', + shortName: 'probing_cane', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'uc12', + 'transportation', + 'cane', + 'handicap', + 'navigate', + 'blind', + 'probe', + 'accessibility', + 'disabled', + 'disability', + 'white cane' + ]), + Emoji( + name: 'manual wheelchair', + char: '\u{1F9BD}', + shortName: 'manual_wheelchair', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'uc12', + 'transportation', + 'handicap', + 'accessibility', + 'disabled', + 'disability' + ]), + Emoji( + name: 'motorized wheelchair', + char: '\u{1F9BC}', + shortName: 'motorized_wheelchair', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'uc12', + 'transportation', + 'handicap', + 'accessibility', + 'disabled', + 'disability' + ]), + Emoji( + name: 'kick scooter', + char: '\u{1F6F4}', + shortName: 'scooter', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: ['kick', 'scooter', 'uc9', 'transportation']), + Emoji( + name: 'bicycle', + char: '\u{1F6B2}', + shortName: 'bike', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'bike', + 'uc6', + 'transportation', + 'bike', + 'travel', + 'bikes', + 'bicycle', + 'bicycling' + ]), + Emoji( + name: 'motor scooter', + char: '\u{1F6F5}', + shortName: 'motor_scooter', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'motor', + 'scooter', + 'uc9', + 'transportation', + 'travel', + 'thai', + 'pattaya' + ]), + Emoji( + name: 'motorcycle', + char: '\u{1F3CD}\u{FE0F}', + shortName: 'motorcycle', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'racing', + 'uc7', + 'transportation', + 'bike', + 'travel', + 'super hero', + 'fun', + 'bikes', + 'bicycle', + 'bicycling', + 'superhero', + 'superman', + 'batman' + ]), + Emoji( + name: 'auto rickshaw', + char: '\u{1F6FA}', + shortName: 'auto_rickshaw', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'uc12', + 'transportation', + 'car', + 'travel', + 'vacation', + 'thai', + 'chinese', + 'cart', + 'tuk tuk', + 'cars', + 'vehicle', + 'fast car', + 'drive', + 'driving', + 'auto', + 'pattaya', + 'chinois', + 'asian', + 'chine', + 'pedicab', + 'trishaw', + 'jinrikisha' + ]), + Emoji( + name: 'police car light', + char: '\u{1F6A8}', + shortName: 'rotating_light', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'beacon', + 'car', + 'light', + 'police', + 'revolving', + 'uc6', + 'transportation', + 'police', + '911', + 'sirens', + 'help', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'switch' + ]), + Emoji( + name: 'oncoming police car', + char: '\u{1F694}', + shortName: 'oncoming_police_car', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'car', + 'oncoming', + 'police', + 'uc6', + 'transportation', + 'car', + 'police', + '911', + 'sirens', + 'help', + 'cars', + 'vehicle', + 'fast car', + 'drive', + 'driving', + 'auto', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'emergency', + 'injury', + 'switch' + ]), + Emoji( + name: 'oncoming bus', + char: '\u{1F68D}', + shortName: 'oncoming_bus', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'bus', + 'oncoming', + 'uc6', + 'transportation', + 'bus', + 'travel', + 'buses' + ]), + Emoji( + name: 'oncoming automobile', + char: '\u{1F698}', + shortName: 'oncoming_automobile', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'automobile', + 'car', + 'oncoming', + 'uc6', + 'transportation', + 'car', + 'travel', + 'cars', + 'vehicle', + 'fast car', + 'drive', + 'driving', + 'auto' + ]), + Emoji( + name: 'oncoming taxi', + char: '\u{1F696}', + shortName: 'oncoming_taxi', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'oncoming', + 'taxi', + 'uc6', + 'transportation', + 'car', + 'travel', + 'cars', + 'vehicle', + 'fast car', + 'drive', + 'driving', + 'auto' + ]), + Emoji( + name: 'aerial tramway', + char: '\u{1F6A1}', + shortName: 'aerial_tramway', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'aerial', + 'cable', + 'car', + 'gondola', + 'tramway', + 'uc6', + 'transportation', + 'train', + 'travel', + 'disney', + 'trains', + 'cartoon' + ]), + Emoji( + name: 'mountain cableway', + char: '\u{1F6A0}', + shortName: 'mountain_cableway', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'cable', + 'gondola', + 'mountain', + 'uc6', + 'transportation', + 'train', + 'travel', + 'skiing', + 'snowboarding', + 'trains', + 'ski', + 'snow skiing', + 'ski boot', + 'snowboarder' + ]), + Emoji( + name: 'suspension railway', + char: '\u{1F69F}', + shortName: 'suspension_railway', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'railway', + 'suspension', + 'uc6', + 'transportation', + 'train', + 'travel', + 'trains' + ]), + Emoji( + name: 'railway car', + char: '\u{1F683}', + shortName: 'railway_car', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'car', + 'electric', + 'railway', + 'train', + 'tram', + 'trolleybus', + 'uc6', + 'transportation', + 'train', + 'travel', + 'trains' + ]), + Emoji( + name: 'tram car', + char: '\u{1F68B}', + shortName: 'train', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'car', + 'tram', + 'trolleybus', + 'uc6', + 'transportation', + 'train', + 'travel', + 'trains' + ]), + Emoji( + name: 'mountain railway', + char: '\u{1F69E}', + shortName: 'mountain_railway', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'car', + 'mountain', + 'railway', + 'uc6', + 'transportation', + 'train', + 'travel', + 'vacation', + 'mountain', + 'trains' + ]), + Emoji( + name: 'monorail', + char: '\u{1F69D}', + shortName: 'monorail', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'vehicle', + 'uc6', + 'transportation', + 'train', + 'travel', + 'vacation', + 'disney', + 'trains', + 'cartoon' + ]), + Emoji( + name: 'high-speed train', + char: '\u{1F684}', + shortName: 'bullettrain_side', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'railway', + 'shinkansen', + 'speed', + 'train', + 'uc6', + 'transportation', + 'train', + 'travel', + 'vacation', + 'trains' + ]), + Emoji( + name: 'bullet train', + char: '\u{1F685}', + shortName: 'bullettrain_front', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'bullet', + 'railway', + 'shinkansen', + 'speed', + 'train', + 'uc6', + 'transportation', + 'train', + 'travel', + 'vacation', + 'trains' + ]), + Emoji( + name: 'light rail', + char: '\u{1F688}', + shortName: 'light_rail', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'railway', + 'uc6', + 'transportation', + 'train', + 'travel', + 'trains' + ]), + Emoji( + name: 'locomotive', + char: '\u{1F682}', + shortName: 'steam_locomotive', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'engine', + 'railway', + 'steam', + 'train', + 'uc6', + 'transportation', + 'train', + 'travel', + 'steam', + 'disney', + 'trains', + 'steaming', + 'piping', + 'cartoon' + ]), + Emoji( + name: 'train', + char: '\u{1F686}', + shortName: 'train2', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'railway', + 'uc6', + 'transportation', + 'train', + 'travel', + 'trains' + ]), + Emoji( + name: 'metro', + char: '\u{1F687}', + shortName: 'metro', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'subway', + 'uc6', + 'transportation', + 'train', + 'travel', + 'trains' + ]), + Emoji( + name: 'tram', + char: '\u{1F68A}', + shortName: 'tram', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'trolleybus', + 'uc6', + 'transportation', + 'train', + 'travel', + 'trains' + ]), + Emoji( + name: 'station', + char: '\u{1F689}', + shortName: 'station', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'railway', + 'train', + 'uc6', + 'transportation', + 'train', + 'travel', + 'vacation', + 'trains' + ]), + Emoji( + name: 'airplane', + char: '\u{2708}\u{FE0F}', + shortName: 'airplane', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'aeroplane', + 'airplane', + 'uc1', + 'transportation', + 'plane', + 'fly', + 'travel', + 'vacation', + 'airplane', + 'planes', + 'flight', + 'flying', + 'flights', + 'avion', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport' + ]), + Emoji( + name: 'airplane departure', + char: '\u{1F6EB}', + shortName: 'airplane_departure', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'aeroplane', + 'airplane', + 'check-in', + 'departure', + 'departures', + 'uc7', + 'transportation', + 'plane', + 'fly', + 'travel', + 'vacation', + 'airplane', + 'planes', + 'flight', + 'flying', + 'flights', + 'avion', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport' + ]), + Emoji( + name: 'airplane arrival', + char: '\u{1F6EC}', + shortName: 'airplane_arriving', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'aeroplane', + 'airplane', + 'arrivals', + 'arriving', + 'landing', + 'uc7', + 'transportation', + 'plane', + 'fly', + 'travel', + 'vacation', + 'airplane', + 'planes', + 'flight', + 'flying', + 'flights', + 'avion', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport' + ]), + Emoji( + name: 'small airplane', + char: '\u{1F6E9}\u{FE0F}', + shortName: 'airplane_small', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'aeroplane', + 'airplane', + 'uc7', + 'transportation', + 'plane', + 'fly', + 'travel', + 'vacation', + 'airplane', + 'rich', + 'planes', + 'flight', + 'flying', + 'flights', + 'avion', + 'airline', + 'aircraft', + 'airforce', + 'air force', + 'airport', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'seat', + char: '\u{1F4BA}', + shortName: 'seat', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'chair', + 'uc6', + 'transportation', + 'fly', + 'travel', + 'vacation', + 'seat', + 'flight', + 'flying', + 'flights', + 'avion', + 'bench', + 'sedia', + 'Stuhl', + 'chaise', + 'silla', + 'armchair' + ]), + Emoji( + name: 'satellite', + char: '\u{1F6F0}\u{FE0F}', + shortName: 'satellite_orbital', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'space', + 'uc7', + 'space', + 'drone', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship' + ]), + Emoji( + name: 'rocket', + char: '\u{1F680}', + shortName: 'rocket', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'space', + 'uc6', + 'transportation', + 'fly', + 'space', + 'travel', + 'blast', + 'star wars', + 'flight', + 'flying', + 'flights', + 'avion', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'boom' + ]), + Emoji( + name: 'flying saucer', + char: '\u{1F6F8}', + shortName: 'flying_saucer', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'UFO', + 'uc10', + 'transportation', + 'space', + 'travel', + 'alien', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'ufo' + ]), + Emoji( + name: 'helicopter', + char: '\u{1F681}', + shortName: 'helicopter', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportAir, + keywords: [ + 'vehicle', + 'uc6', + 'transportation', + 'plane', + 'fly', + 'travel', + 'vacation', + 'rich', + 'planes', + 'flight', + 'flying', + 'flights', + 'avion', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'canoe', + char: '\u{1F6F6}', + shortName: 'canoe', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportWater, + keywords: [ + 'boat', + 'canoe', + 'uc9', + 'transportation', + 'travel', + 'rowing', + 'rowboat', + 'canoe' + ]), + Emoji( + name: 'sailboat', + char: '\u{26F5}', + shortName: 'sailboat', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportWater, + keywords: [ + 'boat', + 'resort', + 'sea', + 'yacht', + 'uc5', + 'transportation', + 'travel', + 'boat', + 'vacation', + 'pirate', + 'rich', + 'ocean', + 'boats', + 'boating', + 'grand', + 'expensive', + 'fancy', + 'sea' + ]), + Emoji( + name: 'speedboat', + char: '\u{1F6A4}', + shortName: 'speedboat', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportWater, + keywords: [ + 'boat', + 'uc6', + 'transportation', + 'travel', + 'boat', + 'tropical', + 'vacation', + 'florida', + 'scuba', + 'boats', + 'boating', + 'snorkel' + ]), + Emoji( + name: 'motor boat', + char: '\u{1F6E5}\u{FE0F}', + shortName: 'motorboat', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportWater, + keywords: [ + 'boat', + 'motorboat', + 'uc7', + 'transportation', + 'travel', + 'boat', + 'scuba', + 'rich', + 'boats', + 'boating', + 'snorkel', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'passenger ship', + char: '\u{1F6F3}\u{FE0F}', + shortName: 'cruise_ship', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportWater, + keywords: [ + 'passenger', + 'ship', + 'uc7', + 'transportation', + 'travel', + 'boat', + 'vacation', + 'disney', + 'florida', + 'fun', + 'ocean', + 'boats', + 'boating', + 'cartoon', + 'sea' + ]), + Emoji( + name: 'ferry', + char: '\u{26F4}\u{FE0F}', + shortName: 'ferry', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportWater, + keywords: [ + 'boat', + 'passenger', + 'uc5', + 'transportation', + 'travel', + 'boat', + 'vacation', + 'ocean', + 'boats', + 'boating', + 'sea' + ]), + Emoji( + name: 'ship', + char: '\u{1F6A2}', + shortName: 'ship', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportWater, + keywords: [ + 'boat', + 'passenger', + 'uc6', + 'transportation', + 'travel', + 'boat', + 'smoking', + 'vacation', + 'moving', + 'ocean', + 'boats', + 'boating', + 'smoke', + 'cigarette', + 'puff', + 'sea' + ]), + Emoji( + name: 'anchor', + char: '\u{2693}', + shortName: 'anchor', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportWater, + keywords: [ + 'ship', + 'tool', + 'uc4', + 'boat', + 'vacation', + 'pirate', + 'boats', + 'boating' + ]), + Emoji( + name: 'fuel pump', + char: '\u{26FD}', + shortName: 'fuelpump', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'fuel', + 'fuelpump', + 'gas', + 'pump', + 'station', + 'uc5', + 'travel', + 'gas pump', + 'petrol' + ]), + Emoji( + name: 'construction', + char: '\u{1F6A7}', + shortName: 'construction', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: ['barrier', 'uc6', 'construction']), + Emoji( + name: 'vertical traffic light', + char: '\u{1F6A6}', + shortName: 'vertical_traffic_light', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: ['light', 'signal', 'traffic', 'uc6', 'stop light']), + Emoji( + name: 'horizontal traffic light', + char: '\u{1F6A5}', + shortName: 'traffic_light', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: ['light', 'signal', 'traffic', 'uc6', 'stop light']), + Emoji( + name: 'bus stop', + char: '\u{1F68F}', + shortName: 'busstop', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: ['bus', 'busstop', 'stop', 'uc6']), + Emoji( + name: 'world map', + char: '\u{1F5FA}\u{FE0F}', + shortName: 'map', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeMap, + keywords: [ + 'map', + 'world', + 'uc7', + 'places', + 'travel', + 'map', + 'vacation', + 'pirate', + 'history', + 'minecraft', + 'navigate', + 'direction', + 'world', + 'maps', + 'location', + 'locate', + 'local', + 'lost', + 'ancient', + 'old' + ]), + Emoji( + name: 'moai', + char: '\u{1F5FF}', + shortName: 'moyai', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.otherObject, + keywords: [ + 'face', + 'moyai', + 'statue', + 'uc6', + 'places', + 'travel', + 'japan', + 'vacation', + 'memorial', + 'japanese', + 'ninja' + ]), + Emoji( + name: 'Statue of Liberty', + char: '\u{1F5FD}', + shortName: 'statue_of_liberty', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'liberty', + 'statue', + 'uc6', + 'places', + 'america', + 'travel', + 'vacation', + 'statue of liberty', + 'free speech', + 'new york', + 'independence day', + 'memorial', + 'usa', + 'united states', + 'united states of america', + 'american', + 'statueofliberty', + 'freedom of speech', + '4th of july' + ]), + Emoji( + name: 'Tokyo tower', + char: '\u{1F5FC}', + shortName: 'tokyo_tower', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'Tokyo', + 'tower', + 'uc6', + 'building', + 'places', + 'travel', + 'japan', + 'vacation', + 'memorial', + 'buildings', + 'japanese', + 'ninja' + ]), + Emoji( + name: 'castle', + char: '\u{1F3F0}', + shortName: 'european_castle', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'European', + 'uc6', + 'building', + 'places', + 'travel', + 'vacation', + 'paris', + 'history', + 'irish', + 'scotland', + 'viking', + 'minecraft', + 'buildings', + 'french', + 'france', + 'ancient', + 'old', + 'saint patricks day', + 'st patricks day', + 'leprechaun', + 'scottish', + 'knight' + ]), + Emoji( + name: 'Japanese castle', + char: '\u{1F3EF}', + shortName: 'japanese_castle', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'Japanese', + 'castle', + 'uc6', + 'building', + 'places', + 'travel', + 'japan', + 'vacation', + 'buildings', + 'japanese', + 'ninja' + ]), + Emoji( + name: 'stadium', + char: '\u{1F3DF}\u{FE0F}', + shortName: 'stadium', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'stadium', + 'uc7', + 'building', + 'instruments', + 'places', + 'travel', + 'game', + 'vacation', + 'boys night', + 'buildings', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'games', + 'gaming', + 'guys night' + ]), + Emoji( + name: 'ferris wheel', + char: '\u{1F3A1}', + shortName: 'ferris_wheel', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'amusement park', + 'ferris', + 'wheel', + 'uc6', + 'places', + 'travel', + 'vacation', + 'amusement park', + 'circus', + 'ferris wheel', + 'england', + 'disney', + 'fun', + 'summer', + 'independence day', + 'theme park', + 'circus tent', + 'clown', + 'clowns', + 'united kingdom', + 'london', + 'uk', + 'cartoon', + 'weekend', + '4th of july' + ]), + Emoji( + name: 'roller coaster', + char: '\u{1F3A2}', + shortName: 'roller_coaster', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'amusement park', + 'coaster', + 'roller', + 'uc6', + 'places', + 'travel', + 'vacation', + 'amusement park', + 'disney', + 'fun', + 'summer', + 'theme park', + 'cartoon', + 'weekend' + ]), + Emoji( + name: 'carousel horse', + char: '\u{1F3A0}', + shortName: 'carousel_horse', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'carousel', + 'horse', + 'uc6', + 'places', + 'vacation', + 'amusement park', + 'carousel', + 'donkey', + 'disney', + 'fun', + 'independence day', + 'theme park', + 'carousel horse', + 'poney', + 'cartoon', + '4th of july' + ]), + Emoji( + name: 'fountain', + char: '\u{26F2}', + shortName: 'fountain', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'fountain', + 'uc5', + 'places', + 'travel', + 'vacation', + 'rich', + 'memorial', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'umbrella on ground', + char: '\u{26F1}\u{FE0F}', + shortName: 'beach_umbrella', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'rain', + 'sun', + 'umbrella', + 'uc5', + 'travel', + 'tropical', + 'vacation', + 'umbrella', + 'hawaii', + 'california', + 'florida', + 'summer', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ]), + Emoji( + name: 'beach with umbrella', + char: '\u{1F3D6}\u{FE0F}', + shortName: 'beach', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeGeographic, + keywords: [ + 'beach', + 'umbrella', + 'uc7', + 'places', + 'travel', + 'tropical', + 'vacation', + 'swim', + 'beach', + 'australia', + 'umbrella', + 'hawaii', + 'california', + 'florida', + 'fun', + 'summer', + 'swimming', + 'swimmer', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ]), + Emoji( + name: 'desert island', + char: '\u{1F3DD}\u{FE0F}', + shortName: 'island', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeGeographic, + keywords: [ + 'desert', + 'island', + 'uc7', + 'places', + 'travel', + 'tropical', + 'vacation', + 'swim', + 'beach', + 'hawaii', + 'florida', + 'summer', + 'swimming', + 'swimmer', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'weekend' + ]), + Emoji( + name: 'desert', + char: '\u{1F3DC}\u{FE0F}', + shortName: 'desert', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeGeographic, + keywords: [ + 'desert', + 'uc7', + 'places', + 'travel', + 'vacation', + 'hot', + 'australia', + 'california', + 'las vegas', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'vegas' + ]), + Emoji( + name: 'volcano', + char: '\u{1F30B}', + shortName: 'volcano', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeGeographic, + keywords: [ + 'eruption', + 'mountain', + 'uc6', + 'places', + 'travel', + 'japan', + 'smoking', + 'tropical', + 'mountain', + 'explosion', + 'hawaii', + 'minecraft', + 'japanese', + 'ninja', + 'smoke', + 'cigarette', + 'puff', + 'explode', + 'aloha', + 'kawaii', + 'maui', + 'moana' + ]), + Emoji( + name: 'mountain', + char: '\u{26F0}\u{FE0F}', + shortName: 'mountain', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeGeographic, + keywords: [ + 'mountain', + 'uc5', + 'places', + 'travel', + 'camp', + 'vacation', + 'mountain', + 'climb', + 'camping', + 'tent', + 'camper', + 'outdoor', + 'outside' + ]), + Emoji( + name: 'snow-capped mountain', + char: '\u{1F3D4}\u{FE0F}', + shortName: 'mountain_snow', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeGeographic, + keywords: [ + 'cold', + 'mountain', + 'snow', + 'uc7', + 'places', + 'winter', + 'travel', + 'snow', + 'camp', + 'vacation', + 'cold', + 'snowboarding', + 'mountain', + 'paris', + 'polar bear', + 'freeze', + 'frozen', + 'frost', + 'ice cube', + 'camping', + 'tent', + 'camper', + 'outdoor', + 'outside', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles', + 'snowboarder', + 'french', + 'france' + ]), + Emoji( + name: 'mount fuji', + char: '\u{1F5FB}', + shortName: 'mount_fuji', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeGeographic, + keywords: [ + 'fuji', + 'mountain', + 'uc6', + 'places', + 'travel', + 'japan', + 'camp', + 'vacation', + 'cold', + 'mountain', + 'japanese', + 'ninja', + 'camping', + 'tent', + 'camper', + 'outdoor', + 'outside', + 'chilly', + 'chilled', + 'brisk', + 'freezing', + 'frostbite', + 'icicles' + ]), + Emoji( + name: 'camping', + char: '\u{1F3D5}\u{FE0F}', + shortName: 'camping', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeGeographic, + keywords: [ + 'camping', + 'uc7', + 'places', + 'travel', + 'camp', + 'vacation', + 'mountain', + 'fun', + 'parks', + 'camping', + 'tent', + 'camper', + 'outdoor', + 'outside', + 'regional park', + 'nature park', + 'natural park' + ]), + Emoji( + name: 'tent', + char: '\u{26FA}', + shortName: 'tent', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'camping', + 'uc5', + 'places', + 'travel', + 'camp', + 'vacation', + 'summer', + 'camping', + 'tent', + 'camper', + 'outdoor', + 'outside', + 'weekend' + ]), + Emoji( + name: 'house', + char: '\u{1F3E0}', + shortName: 'house', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'home', + 'house', + 'uc6', + 'building', + 'places', + 'house', + 'covid', + 'buildings', + 'houses', + 'apartment', + 'apartments', + 'casa', + 'maison', + 'home' + ]), + Emoji( + name: 'house with garden', + char: '\u{1F3E1}', + shortName: 'house_with_garden', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'garden', + 'home', + 'house', + 'uc6', + 'building', + 'places', + 'house', + 'buildings', + 'houses', + 'apartment', + 'apartments', + 'casa', + 'maison', + 'home' + ]), + Emoji( + name: 'houses', + char: '\u{1F3D8}\u{FE0F}', + shortName: 'homes', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'houses', + 'uc7', + 'building', + 'places', + 'house', + 'buildings', + 'houses', + 'apartment', + 'apartments', + 'casa', + 'maison', + 'home' + ]), + Emoji( + name: 'derelict house', + char: '\u{1F3DA}\u{FE0F}', + shortName: 'house_abandoned', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'derelict', + 'house', + 'uc7', + 'building', + 'places', + 'house', + 'halloween', + 'buildings', + 'houses', + 'apartment', + 'apartments', + 'casa', + 'maison', + 'home', + 'samhain' + ]), + Emoji( + name: 'hut', + char: '\u{1F6D6}', + shortName: 'hut', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: ['uc13', 'places', 'star wars']), + Emoji( + name: 'building construction', + char: '\u{1F3D7}\u{FE0F}', + shortName: 'construction_site', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'construction', + 'uc7', + 'building', + 'crane', + 'build', + 'construction', + 'buildings' + ]), + Emoji( + name: 'factory', + char: '\u{1F3ED}', + shortName: 'factory', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'building', + 'uc6', + 'building', + 'places', + 'travel', + 'steam', + 'power', + 'poison', + 'buildings', + 'steaming', + 'piping', + 'toxic', + 'toxins' + ]), + Emoji( + name: 'office building', + char: '\u{1F3E2}', + shortName: 'office', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'building', + 'uc6', + 'building', + 'places', + 'classroom', + 'business', + 'work', + 'buildings', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'office' + ]), + Emoji( + name: 'department store', + char: '\u{1F3EC}', + shortName: 'department_store', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'department', + 'store', + 'uc6', + 'building', + 'places', + 'disney', + 'buildings', + 'cartoon' + ]), + Emoji( + name: 'Japanese post office', + char: '\u{1F3E3}', + shortName: 'post_office', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'Japanese', + 'post', + 'uc6', + 'building', + 'places', + 'japan', + 'mail', + 'buildings', + 'japanese', + 'ninja', + 'email', + 'post', + 'post office' + ]), + Emoji( + name: 'post office', + char: '\u{1F3E4}', + shortName: 'european_post_office', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'European', + 'post', + 'uc6', + 'building', + 'places', + 'mail', + 'buildings', + 'email', + 'post', + 'post office' + ]), + Emoji( + name: 'hospital', + char: '\u{1F3E5}', + shortName: 'hospital', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'doctor', + 'medicine', + 'uc6', + 'building', + 'places', + 'health', + '911', + 'nurse', + 'covid', + 'buildings', + 'medicine', + 'doctor', + 'emergency', + 'injury' + ]), + Emoji( + name: 'bank', + char: '\u{1F3E6}', + shortName: 'bank', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'building', + 'uc6', + 'building', + 'places', + 'money', + 'rich', + 'buildings', + 'cash', + 'dollars', + 'dollar', + 'bucks', + 'currency', + 'funds', + 'payment', + 'money face', + 'reward', + 'thief', + 'bank', + 'benjamins', + 'argent', + 'dinero', + 'i soldi', + 'Geld', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'hotel', + char: '\u{1F3E8}', + shortName: 'hotel', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'building', + 'uc6', + 'building', + 'places', + 'vacation', + 'las vegas', + 'hotel', + 'buildings', + 'vegas', + 'vacancy', + 'no vacancy' + ]), + Emoji( + name: 'convenience store', + char: '\u{1F3EA}', + shortName: 'convenience_store', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'convenience', + 'store', + 'uc6', + 'building', + 'places', + 'las vegas', + 'buildings', + 'vegas' + ]), + Emoji( + name: 'school', + char: '\u{1F3EB}', + shortName: 'school', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'building', + 'uc6', + 'building', + 'places', + 'classroom', + 'buildings', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning' + ]), + Emoji( + name: 'love hotel', + char: '\u{1F3E9}', + shortName: 'love_hotel', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'hotel', + 'love', + 'uc6', + 'building', + 'places', + 'love', + 'japan', + 'vacation', + 'pink', + 'porn', + 'hotel', + 'buildings', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'japanese', + 'ninja', + 'rose', + 'vacancy', + 'no vacancy' + ]), + Emoji( + name: 'wedding', + char: '\u{1F492}', + shortName: 'wedding', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'chapel', + 'romance', + 'uc6', + 'building', + 'places', + 'wedding', + 'love', + 'pink', + 'buildings', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'rose' + ]), + Emoji( + name: 'classical building', + char: '\u{1F3DB}\u{FE0F}', + shortName: 'classical_building', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'classical', + 'uc7', + 'building', + 'places', + 'travel', + 'vacation', + 'police', + 'history', + 'court', + 'memorial', + 'buildings', + 'cop', + 'policeman', + 'popo', + 'prison', + 'handcuff', + 'jail', + 'justice', + 'ancient', + 'old' + ]), + Emoji( + name: 'church', + char: '\u{26EA}', + shortName: 'church', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeReligious, + keywords: [ + 'Christian', + 'cross', + 'religion', + 'uc5', + 'building', + 'places', + 'wedding', + 'religion', + 'travel', + 'christmas', + 'pray', + 'condolence', + 'jesus', + 'easter', + 'bible', + 'advent', + 'buildings', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'compassion' + ]), + Emoji( + name: 'mosque', + char: '\u{1F54C}', + shortName: 'mosque', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeReligious, + keywords: [ + 'Muslim', + 'islam', + 'religion', + 'uc8', + 'building', + 'places', + 'religion', + 'vacation', + 'pray', + 'condolence', + 'islam', + 'buildings', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'compassion', + 'muslim', + 'arab' + ]), + Emoji( + name: 'synagogue', + char: '\u{1F54D}', + shortName: 'synagogue', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeReligious, + keywords: [ + 'Jew', + 'Jewish', + 'religion', + 'temple', + 'uc8', + 'building', + 'places', + 'wedding', + 'religion', + 'vacation', + 'pray', + 'condolence', + 'jewish', + 'buildings', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'compassion', + 'hannukah', + 'hanukkah', + 'israel' + ]), + Emoji( + name: 'hindu temple', + char: '\u{1F6D5}', + shortName: 'hindu_temple', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeReligious, + keywords: [ + 'uc12', + 'building', + 'places', + 'travel', + 'pray', + 'dharmachakra', + 'buildings', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'jainism', + 'buddhism', + 'hinduism', + 'nirvana', + 'maintain', + 'keep', + 'law', + 'bueno', + 'dharma', + 'kama', + 'artha', + 'moksa', + 'karma' + ]), + Emoji( + name: 'kaaba', + char: '\u{1F54B}', + shortName: 'kaaba', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeReligious, + keywords: [ + 'Muslim', + 'islam', + 'religion', + 'uc8', + 'building', + 'places', + 'religion', + 'pray', + 'condolence', + 'islam', + 'buildings', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering', + 'compassion', + 'muslim', + 'arab' + ]), + Emoji( + name: 'shinto shrine', + char: '\u{26E9}\u{FE0F}', + shortName: 'shinto_shrine', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeReligious, + keywords: [ + 'religion', + 'shinto', + 'shrine', + 'uc5', + 'building', + 'places', + 'travel', + 'japan', + 'vacation', + 'buildings', + 'japanese', + 'ninja' + ]), + Emoji( + name: 'railway track', + char: '\u{1F6E4}\u{FE0F}', + shortName: 'railway_track', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'railway', + 'train', + 'uc7', + 'train', + 'travel', + 'vacation', + 'trains' + ]), + Emoji( + name: 'motorway', + char: '\u{1F6E3}\u{FE0F}', + shortName: 'motorway', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'highway', + 'road', + 'uc7', + 'travel', + 'camp', + 'vacation', + 'road', + 'camping', + 'tent', + 'camper', + 'outdoor', + 'outside', + 'route', + 'highway', + 'street' + ]), + Emoji( + name: 'map of Japan', + char: '\u{1F5FE}', + shortName: 'japan', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeMap, + keywords: [ + 'Japan', + 'map', + 'uc6', + 'places', + 'travel', + 'japan', + 'map', + 'vacation', + 'japanese', + 'ninja', + 'maps', + 'location', + 'locate', + 'local', + 'lost' + ]), + Emoji( + name: 'moon viewing ceremony', + char: '\u{1F391}', + shortName: 'rice_scene', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'celebration', + 'ceremony', + 'moon', + 'uc6', + 'places', + 'space', + 'sky', + 'travel', + 'japan', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'japanese', + 'ninja' + ]), + Emoji( + name: 'national park', + char: '\u{1F3DE}\u{FE0F}', + shortName: 'park', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeGeographic, + keywords: [ + 'park', + 'uc7', + 'places', + 'travel', + 'camp', + 'vacation', + 'summer', + 'river', + 'memorial', + 'parks', + 'camping', + 'tent', + 'camper', + 'outdoor', + 'outside', + 'weekend', + 'regional park', + 'nature park', + 'natural park' + ]), + Emoji( + name: 'sunrise', + char: '\u{1F305}', + shortName: 'sunrise', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'morning', + 'sun', + 'uc6', + 'places', + 'sun', + 'sky', + 'travel', + 'tropical', + 'vacation', + 'day', + 'hump day', + 'morning', + 'hawaii', + 'california', + 'florida', + 'scuba', + 'summer', + 'sunshine', + 'sunny', + 'eclipse', + 'solar', + 'solareclipse', + 'shiny', + 'good morning', + 'aloha', + 'kawaii', + 'maui', + 'moana', + 'snorkel', + 'weekend' + ]), + Emoji( + name: 'sunrise over mountains', + char: '\u{1F304}', + shortName: 'sunrise_over_mountains', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'morning', + 'mountain', + 'sun', + 'sunrise', + 'uc6', + 'places', + 'sun', + 'sky', + 'travel', + 'camp', + 'vacation', + 'day', + 'morning', + 'mountain', + 'california', + 'sunshine', + 'sunny', + 'eclipse', + 'solar', + 'solareclipse', + 'shiny', + 'camping', + 'tent', + 'camper', + 'outdoor', + 'outside', + 'good morning' + ]), + Emoji( + name: 'shooting star', + char: '\u{1F320}', + shortName: 'stars', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'falling', + 'shooting', + 'star', + 'uc6', + 'space', + 'star wars', + 'fame', + 'sparkle', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'famous', + 'celebrity', + 'bright', + 'shine', + 'twinkle' + ]), + Emoji( + name: 'sparkler', + char: '\u{1F387}', + shortName: 'sparkler', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'celebration', + 'fireworks', + 'sparkle', + 'uc6', + 'holidays', + 'happy birthday', + 'firework', + 'explosion', + 'celebrate', + 'glitter', + 'disney', + 'sparkle', + 'holiday', + 'Bon anniversaire', + 'joyeux anniversaire', + 'buon compleanno', + 'feliz cumpleaÃąos', + 'alles Gute zum Geburtstag', + 'feliz AniversÃĄrio', + 'Gratulerer med dagen', + 'fireworks', + 'firecracker', + 'explode', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'cartoon', + 'bright', + 'shine', + 'twinkle' + ]), + Emoji( + name: 'fireworks', + char: '\u{1F386}', + shortName: 'fireworks', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'celebration', + 'uc6', + 'holidays', + 'firework', + 'explosion', + 'celebrate', + 'glitter', + 'disney', + 'sparkle', + 'independence day', + 'holiday', + 'fireworks', + 'firecracker', + 'explode', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'cartoon', + 'bright', + 'shine', + 'twinkle', + '4th of july' + ]), + Emoji( + name: 'sunset', + char: '\u{1F307}', + shortName: 'city_sunset', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'dusk', + 'sun', + 'uc6', + 'building', + 'places', + 'sky', + 'vacation', + 'buildings' + ]), + Emoji( + name: 'cityscape at dusk', + char: '\u{1F306}', + shortName: 'city_dusk', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'city', + 'dusk', + 'evening', + 'landscape', + 'sun', + 'sunset', + 'uc6', + 'building', + 'places', + 'buildings' + ]), + Emoji( + name: 'cityscape', + char: '\u{1F3D9}\u{FE0F}', + shortName: 'cityscape', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'city', + 'uc7', + 'building', + 'places', + 'vacation', + 'new york', + 'england', + 'donald trump', + 'rich', + 'buildings', + 'united kingdom', + 'london', + 'uk', + 'trump', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'night with stars', + char: '\u{1F303}', + shortName: 'night_with_stars', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'night', + 'star', + 'uc6', + 'building', + 'places', + 'halloween', + 'sky', + 'vacation', + 'goodnight', + 'buildings', + 'samhain' + ]), + Emoji( + name: 'milky way', + char: '\u{1F30C}', + shortName: 'milky_way', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'space', + 'uc6', + 'places', + 'space', + 'sky', + 'travel', + 'star', + 'vacation', + 'goodnight', + 'star wars', + 'dream', + 'fantasy', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'stars', + 'dreams' + ]), + Emoji( + name: 'bridge at night', + char: '\u{1F309}', + shortName: 'bridge_at_night', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'bridge', + 'night', + 'uc6', + 'places', + 'travel', + 'vacation', + 'goodnight', + 'england', + 'california', + 'united kingdom', + 'london', + 'uk' + ]), + Emoji( + name: 'foggy', + char: '\u{1F301}', + shortName: 'foggy', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: [ + 'fog', + 'uc6', + 'building', + 'places', + 'sky', + 'travel', + 'vacation', + 'england', + 'buildings', + 'united kingdom', + 'london', + 'uk' + ]), + Emoji( + name: 'watch', + char: '\u{231A}', + shortName: 'watch', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.time, + keywords: [ + 'clock', + 'uc1', + 'electronics', + 'time', + 'accessories', + 'bling', + 'wait', + 'clocks', + 'clock', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'hours' + ]), + Emoji( + name: 'mobile phone', + char: '\u{1F4F1}', + shortName: 'mobile_phone', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.phone, + keywords: [ + 'cell', + 'mobile', + 'phone', + 'telephone', + 'uc6', + 'electronics', + 'phone', + 'talk', + 'selfie', + 'technology', + 'laptop', + 'instagram', + 'telephone', + 'iphone', + 'smartphone', + 'text', + 'talking', + 'speech', + 'social', + 'chat', + 'voice', + 'speechless', + 'speak', + 'computer', + 'online', + 'wifi', + 'website', + 'zoom', + 'ipad', + 'tablet' + ]), + Emoji( + name: 'mobile phone with arrow', + char: '\u{1F4F2}', + shortName: 'calling', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.phone, + keywords: [ + 'arrow', + 'call', + 'cell', + 'mobile', + 'phone', + 'receive', + 'telephone', + 'uc6', + 'electronics', + 'phone', + 'selfie', + 'technology', + 'download', + 'telephone', + 'iphone', + 'smartphone', + 'text' + ]), + Emoji( + name: 'laptop', + char: '\u{1F4BB}', + shortName: 'computer', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: [ + 'computer', + 'pc', + 'personal', + 'uc6', + 'electronics', + 'classroom', + 'internet', + 'technology', + 'laptop', + 'download', + 'instagram', + 'youtube', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'computer', + 'online', + 'wifi', + 'website', + 'zoom', + 'ipad', + 'tablet', + 'vlog', + 'office' + ]), + Emoji( + name: 'keyboard', + char: '\u{2328}\u{FE0F}', + shortName: 'keyboard', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: [ + 'computer', + 'uc1', + 'electronics', + 'classroom', + 'technology', + 'laptop', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'computer', + 'online', + 'wifi', + 'website', + 'zoom', + 'ipad', + 'tablet', + 'office' + ]), + Emoji( + name: 'desktop computer', + char: '\u{1F5A5}\u{FE0F}', + shortName: 'desktop', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: [ + 'computer', + 'desktop', + 'uc7', + 'electronics', + 'classroom', + 'internet', + 'technology', + 'laptop', + 'download', + 'instagram', + 'youtube', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'computer', + 'online', + 'wifi', + 'website', + 'zoom', + 'ipad', + 'tablet', + 'vlog', + 'office' + ]), + Emoji( + name: 'printer', + char: '\u{1F5A8}\u{FE0F}', + shortName: 'printer', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: [ + 'computer', + 'uc7', + 'electronics', + 'classroom', + 'technology', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'office' + ]), + Emoji( + name: 'computer mouse', + char: '\u{1F5B1}\u{FE0F}', + shortName: 'mouse_three_button', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: [ + 'computer', + 'uc7', + 'electronics', + 'classroom', + 'game', + 'technology', + 'laptop', + 'click', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'games', + 'gaming', + 'computer', + 'online', + 'wifi', + 'website', + 'zoom', + 'ipad', + 'tablet', + 'office' + ]), + Emoji( + name: 'trackball', + char: '\u{1F5B2}\u{FE0F}', + shortName: 'trackball', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: [ + 'computer', + 'uc7', + 'electronics', + 'classroom', + 'game', + 'technology', + 'laptop', + 'click', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'games', + 'gaming', + 'computer', + 'online', + 'wifi', + 'website', + 'zoom', + 'ipad', + 'tablet', + 'office' + ]), + Emoji( + name: 'joystick', + char: '\u{1F579}\u{FE0F}', + shortName: 'joystick', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'game', + 'video game', + 'uc7', + 'electronics', + 'game', + 'boys night', + 'technology', + 'controller', + 'pacman', + 'games', + 'gaming', + 'guys night', + 'remote', + 'pac man' + ]), + Emoji( + name: 'clamp', + char: '\u{1F5DC}\u{FE0F}', + shortName: 'compression', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'compress', + 'tool', + 'vice', + 'uc7', + 'tool', + 'download', + 'steel', + 'tools', + 'metal' + ]), + Emoji( + name: 'computer disk', + char: '\u{1F4BD}', + shortName: 'minidisc', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: [ + 'computer', + 'disk', + 'minidisk', + 'optical', + 'uc6', + 'instruments', + 'electronics', + 'classroom', + 'laptop', + 'download', + 'work', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'computer', + 'online', + 'wifi', + 'website', + 'zoom', + 'ipad', + 'tablet', + 'office' + ]), + Emoji( + name: 'floppy disk', + char: '\u{1F4BE}', + shortName: 'floppy_disk', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: [ + 'computer', + 'disk', + 'floppy', + 'uc6', + 'electronics', + 'classroom', + 'laptop', + 'download', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'computer', + 'online', + 'wifi', + 'website', + 'zoom', + 'ipad', + 'tablet', + 'office' + ]), + Emoji( + name: 'optical disk', + char: '\u{1F4BF}', + shortName: 'cd', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: [ + 'cd', + 'computer', + 'disk', + 'optical', + 'uc6', + 'instruments', + 'electronics', + 'download', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique' + ]), + Emoji( + name: 'dvd', + char: '\u{1F4C0}', + shortName: 'dvd', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: [ + 'blu-ray', + 'computer', + 'disk', + 'dvd', + 'optical', + 'uc6', + 'electronics' + ]), + Emoji( + name: 'videocassette', + char: '\u{1F4FC}', + shortName: 'vhs', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'tape', + 'vhs', + 'video', + 'uc6', + 'electronics', + 'history', + 'ancient', + 'old' + ]), + Emoji( + name: 'camera', + char: '\u{1F4F7}', + shortName: 'camera', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'video', + 'uc6', + 'electronics', + 'selfie', + 'technology', + 'detective', + 'instagram', + 'youtube', + 'vlog' + ]), + Emoji( + name: 'camera with flash', + char: '\u{1F4F8}', + shortName: 'camera_with_flash', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'camera', + 'flash', + 'video', + 'uc7', + 'electronics', + 'technology', + 'instagram' + ]), + Emoji( + name: 'video camera', + char: '\u{1F4F9}', + shortName: 'video_camera', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'camera', + 'video', + 'uc6', + 'electronics', + 'movie', + 'technology', + 'porn', + 'youtube', + 'movies', + 'cinema', + 'film', + 'films', + 'video', + 'videos', + 'vlog' + ]), + Emoji( + name: 'movie camera', + char: '\u{1F3A5}', + shortName: 'movie_camera', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'camera', + 'cinema', + 'movie', + 'uc6', + 'movie', + 'movies', + 'cinema', + 'film', + 'films', + 'video', + 'videos' + ]), + Emoji( + name: 'film projector', + char: '\u{1F4FD}\u{FE0F}', + shortName: 'projector', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'cinema', + 'film', + 'movie', + 'projector', + 'video', + 'uc7', + 'movie', + 'disney', + 'california', + 'fame', + 'movies', + 'cinema', + 'film', + 'films', + 'video', + 'videos', + 'cartoon', + 'famous', + 'celebrity' + ]), + Emoji( + name: 'film frames', + char: '\u{1F39E}\u{FE0F}', + shortName: 'film_frames', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'cinema', + 'film', + 'frames', + 'movie', + 'uc7', + 'movie', + 'disney', + 'california', + 'movies', + 'cinema', + 'film', + 'films', + 'video', + 'videos', + 'cartoon' + ]), + Emoji( + name: 'telephone receiver', + char: '\u{1F4DE}', + shortName: 'telephone_receiver', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.phone, + keywords: [ + 'phone', + 'receiver', + 'telephone', + 'uc6', + 'electronics', + 'phone', + 'talk', + 'telephone', + 'iphone', + 'smartphone', + 'text', + 'talking', + 'speech', + 'social', + 'chat', + 'voice', + 'speechless', + 'speak' + ]), + Emoji( + name: 'telephone', + char: '\u{260E}\u{FE0F}', + shortName: 'telephone', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.phone, + keywords: [ + 'phone', + 'uc1', + 'electronics', + 'phone', + 'talk', + 'history', + 'hotel', + 'telephone', + 'iphone', + 'smartphone', + 'text', + 'talking', + 'speech', + 'social', + 'chat', + 'voice', + 'speechless', + 'speak', + 'ancient', + 'old', + 'vacancy', + 'no vacancy' + ]), + Emoji( + name: 'pager', + char: '\u{1F4DF}', + shortName: 'pager', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.phone, + keywords: [ + 'pager', + 'uc6', + 'electronics', + 'technology', + 'history', + 'work', + 'ancient', + 'old', + 'office' + ]), + Emoji( + name: 'fax machine', + char: '\u{1F4E0}', + shortName: 'fax', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.phone, + keywords: [ + 'fax', + 'uc6', + 'electronics', + 'classroom', + 'technology', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'office' + ]), + Emoji( + name: 'television', + char: '\u{1F4FA}', + shortName: 'tv', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'tv', + 'video', + 'uc6', + 'electronics', + 'classroom', + 'technology', + 'news', + 'fame', + 'history', + 'youtube', + 'household', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'article', + 'famous', + 'celebrity', + 'ancient', + 'old', + 'vlog' + ]), + Emoji( + name: 'radio', + char: '\u{1F4FB}', + shortName: 'radio', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.music, + keywords: [ + 'video', + 'uc6', + 'instruments', + 'electronics', + 'news', + 'history', + 'sound', + 'household', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'article', + 'ancient', + 'old', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear' + ]), + Emoji( + name: 'studio microphone', + char: '\u{1F399}\u{FE0F}', + shortName: 'microphone2', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.music, + keywords: [ + 'mic', + 'microphone', + 'music', + 'studio', + 'uc7', + 'instruments', + 'electronics', + 'news', + 'sound', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'article', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear' + ]), + Emoji( + name: 'level slider', + char: '\u{1F39A}\u{FE0F}', + shortName: 'level_slider', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.music, + keywords: [ + 'level', + 'music', + 'slider', + 'uc7', + 'instruments', + 'electronics', + 'sound', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear' + ]), + Emoji( + name: 'control knobs', + char: '\u{1F39B}\u{FE0F}', + shortName: 'control_knobs', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.music, + keywords: [ + 'control', + 'knobs', + 'music', + 'uc7', + 'instruments', + 'electronics', + 'power', + 'bake', + 'sound', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique', + 'baking', + 'volume', + 'speaker', + 'loud', + 'mic', + 'audio', + 'hear' + ]), + Emoji( + name: 'compass', + char: '\u{1F9ED}', + shortName: 'compass', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeMap, + keywords: [ + 'uc11', + 'travel', + 'camp', + 'science', + 'map', + 'globe', + 'navigate', + 'direction', + 'camping', + 'tent', + 'camper', + 'outdoor', + 'outside', + 'lab', + 'maps', + 'location', + 'locate', + 'local', + 'lost', + 'globes', + 'planet', + 'earth', + 'earthquake' + ]), + Emoji( + name: 'stopwatch', + char: '\u{23F1}\u{FE0F}', + shortName: 'stopwatch', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.time, + keywords: [ + 'clock', + 'uc6', + 'electronics', + 'time', + 'wait', + 'clocks', + 'clock', + 'hours' + ]), + Emoji( + name: 'timer clock', + char: '\u{23F2}\u{FE0F}', + shortName: 'timer', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.time, + keywords: [ + 'clock', + 'timer', + 'uc6', + 'time', + 'bake', + 'wait', + 'measure', + 'clocks', + 'clock', + 'baking', + 'hours' + ]), + Emoji( + name: 'alarm clock', + char: '\u{23F0}', + shortName: 'alarm_clock', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.time, + keywords: [ + 'alarm', + 'clock', + 'uc6', + 'time', + 'alarm', + 'wait', + 'clocks', + 'clock', + 'alarms', + 'announce', + 'hours' + ]), + Emoji( + name: 'mantelpiece clock', + char: '\u{1F570}\u{FE0F}', + shortName: 'clock', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.time, + keywords: [ + 'clock', + 'uc7', + 'time', + 'vintage', + 'wait', + 'household', + 'clocks', + 'clock', + 'hours' + ]), + Emoji( + name: 'hourglass done', + char: '\u{231B}', + shortName: 'hourglass', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.time, + keywords: [ + 'sand', + 'timer', + 'uc1', + 'time', + 'empty', + 'percent', + 'wait', + 'measure', + 'clocks', + 'clock', + 'hours' + ]), + Emoji( + name: 'hourglass not done', + char: '\u{23F3}', + shortName: 'hourglass_flowing_sand', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.time, + keywords: [ + 'hourglass', + 'sand', + 'timer', + 'uc6', + 'time', + 'infinity', + 'history', + 'percent', + 'wait', + 'measure', + 'clocks', + 'clock', + 'infini', + 'forever', + 'ancient', + 'old', + 'hours' + ]), + Emoji( + name: 'satellite antenna', + char: '\u{1F4E1}', + shortName: 'satellite', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.science, + keywords: ['antenna', 'dish', 'satellite', 'uc6', 'technology', 'power']), + Emoji( + name: 'battery', + char: '\u{1F50B}', + shortName: 'battery', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: ['battery', 'uc6', 'science', 'power', 'energy', 'lab']), + Emoji( + name: 'electric plug', + char: '\u{1F50C}', + shortName: 'electric_plug', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: [ + 'electric', + 'electricity', + 'plug', + 'uc6', + 'electronics', + 'electric', + 'power', + 'household', + 'energy' + ]), + Emoji( + name: 'light bulb', + char: '\u{1F4A1}', + shortName: 'bulb', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'bulb', + 'comic', + 'electric', + 'idea', + 'light', + 'uc6', + 'science', + 'electric', + 'light', + 'power', + 'idea', + 'sparkle', + 'innovate', + 'household', + 'energy', + 'lab', + 'lamp', + 'light bulb', + 'flashlight', + 'spotlight', + 'illuminate', + 'lightbulb', + 'lighting', + 'luce', + 'licht', + 'lumière', + 'luz', + 'bright', + 'shine', + 'twinkle', + 'innovation', + 'inquire' + ]), + Emoji( + name: 'flashlight', + char: '\u{1F526}', + shortName: 'flashlight', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'electric', + 'light', + 'tool', + 'torch', + 'uc6', + 'electronics', + 'tool', + 'light', + 'star wars', + 'search', + 'detective', + 'sparkle', + 'household', + 'energy', + 'tools', + 'lamp', + 'light bulb', + 'flashlight', + 'spotlight', + 'illuminate', + 'lightbulb', + 'lighting', + 'luce', + 'licht', + 'lumière', + 'luz', + 'look', + 'find', + 'looking', + 'see', + 'bright', + 'shine', + 'twinkle' + ]), + Emoji( + name: 'candle', + char: '\u{1F56F}\u{FE0F}', + shortName: 'candle', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'light', + 'uc7', + 'religion', + 'halloween', + 'birthday', + 'christmas', + 'light', + 'jewish', + 'samhain', + 'birth', + 'cumpleaÃąos', + 'anniversaire', + 'bday', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'lamp', + 'light bulb', + 'flashlight', + 'spotlight', + 'illuminate', + 'lightbulb', + 'lighting', + 'luce', + 'licht', + 'lumière', + 'luz', + 'hannukah', + 'hanukkah', + 'israel' + ]), + Emoji( + name: 'diya lamp', + char: '\u{1FA94}', + shortName: 'diya_lamp', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'uc12', + 'light', + 'jealous', + 'dharmachakra', + 'diya', + 'greed', + 'soul', + 'lamp', + 'light bulb', + 'flashlight', + 'spotlight', + 'illuminate', + 'lightbulb', + 'lighting', + 'luce', + 'licht', + 'lumière', + 'luz', + 'jainism', + 'buddhism', + 'hinduism', + 'nirvana', + 'maintain', + 'keep', + 'law', + 'bueno', + 'dharma', + 'kama', + 'artha', + 'moksa', + 'karma', + 'diyo', + 'deya', + 'divaa', + 'deepa', + 'deepam', + 'deepak', + 'diwali', + 'oil lamp', + 'selfish' + ]), + Emoji( + name: 'fire extinguisher', + char: '\u{1F9EF}', + shortName: 'fire_extinguisher', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'uc11', + 'alarm', + 'science', + 'danger', + 'household', + 'fires', + 'alarms', + 'announce', + 'lab', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous' + ]), + Emoji( + name: 'oil drum', + char: '\u{1F6E2}\u{FE0F}', + shortName: 'oil', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.transportGround, + keywords: [ + 'drum', + 'oil', + 'uc7', + 'jewish', + 'hannukah', + 'hanukkah', + 'israel' + ]), + Emoji( + name: 'money with wings', + char: '\u{1F4B8}', + shortName: 'money_with_wings', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.money, + keywords: [ + 'bank', + 'banknote', + 'bill', + 'dollar', + 'fly', + 'money', + 'note', + 'wings', + 'uc6', + 'money', + 'vacation', + 'boys night', + 'coins', + 'las vegas', + 'rich', + 'purchase', + 'cash', + 'dollars', + 'dollar', + 'bucks', + 'currency', + 'funds', + 'payment', + 'money face', + 'reward', + 'thief', + 'bank', + 'benjamins', + 'argent', + 'dinero', + 'i soldi', + 'Geld', + 'guys night', + 'vegas', + 'grand', + 'expensive', + 'fancy', + 'buy', + 'shop', + 'spend' + ]), + Emoji( + name: 'dollar banknote', + char: '\u{1F4B5}', + shortName: 'dollar', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.money, + keywords: [ + 'bank', + 'banknote', + 'bill', + 'currency', + 'dollar', + 'money', + 'note', + 'uc6', + 'money', + 'coins', + 'rich', + 'purchase', + 'cash', + 'dollars', + 'dollar', + 'bucks', + 'currency', + 'funds', + 'payment', + 'money face', + 'reward', + 'thief', + 'bank', + 'benjamins', + 'argent', + 'dinero', + 'i soldi', + 'Geld', + 'grand', + 'expensive', + 'fancy', + 'buy', + 'shop', + 'spend' + ]), + Emoji( + name: 'yen banknote', + char: '\u{1F4B4}', + shortName: 'yen', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.money, + keywords: [ + 'bank', + 'banknote', + 'bill', + 'currency', + 'money', + 'note', + 'yen', + 'uc6', + 'money', + 'coins', + 'rich', + 'purchase', + 'cash', + 'dollars', + 'dollar', + 'bucks', + 'currency', + 'funds', + 'payment', + 'money face', + 'reward', + 'thief', + 'bank', + 'benjamins', + 'argent', + 'dinero', + 'i soldi', + 'Geld', + 'grand', + 'expensive', + 'fancy', + 'buy', + 'shop', + 'spend' + ]), + Emoji( + name: 'euro banknote', + char: '\u{1F4B6}', + shortName: 'euro', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.money, + keywords: [ + 'bank', + 'banknote', + 'bill', + 'currency', + 'euro', + 'money', + 'note', + 'uc6', + 'money', + 'coins', + 'rich', + 'purchase', + 'cash', + 'dollars', + 'dollar', + 'bucks', + 'currency', + 'funds', + 'payment', + 'money face', + 'reward', + 'thief', + 'bank', + 'benjamins', + 'argent', + 'dinero', + 'i soldi', + 'Geld', + 'grand', + 'expensive', + 'fancy', + 'buy', + 'shop', + 'spend' + ]), + Emoji( + name: 'pound banknote', + char: '\u{1F4B7}', + shortName: 'pound', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.money, + keywords: [ + 'bank', + 'banknote', + 'bill', + 'currency', + 'money', + 'note', + 'pound', + 'uc6', + 'money', + 'coins', + 'rich', + 'purchase', + 'cash', + 'dollars', + 'dollar', + 'bucks', + 'currency', + 'funds', + 'payment', + 'money face', + 'reward', + 'thief', + 'bank', + 'benjamins', + 'argent', + 'dinero', + 'i soldi', + 'Geld', + 'grand', + 'expensive', + 'fancy', + 'buy', + 'shop', + 'spend' + ]), + Emoji( + name: 'coin', + char: '\u{1FA99}', + shortName: 'coin', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.money, + keywords: [ + 'uc13', + 'money', + 'coins', + 'purchase', + 'cash', + 'dollars', + 'dollar', + 'bucks', + 'currency', + 'funds', + 'payment', + 'money face', + 'reward', + 'thief', + 'bank', + 'benjamins', + 'argent', + 'dinero', + 'i soldi', + 'Geld', + 'buy', + 'shop', + 'spend' + ]), + Emoji( + name: 'money bag', + char: '\u{1F4B0}', + shortName: 'moneybag', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.money, + keywords: [ + 'bag', + 'dollar', + 'money', + 'moneybag', + 'uc6', + 'wedding', + 'bag', + 'money', + 'award', + 'pirate', + 'bling', + 'coins', + 'donald trump', + 'rich', + 'weddings', + 'marriage', + 'newlywed', + 'bride', + 'groome', + 'groom', + 'married', + 'marry', + 'swag', + 'cash', + 'dollars', + 'dollar', + 'bucks', + 'currency', + 'funds', + 'payment', + 'money face', + 'reward', + 'thief', + 'bank', + 'benjamins', + 'argent', + 'dinero', + 'i soldi', + 'Geld', + 'awards', + 'prize', + 'prizes', + 'trophy', + 'trophies', + 'spot', + 'best', + 'champion', + 'hero', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'trump', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'credit card', + char: '\u{1F4B3}', + shortName: 'credit_card', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.money, + keywords: [ + 'bank', + 'card', + 'credit', + 'money', + 'uc6', + 'money', + 'vacation', + 'boys night', + 'rich', + 'purchase', + 'cash', + 'dollars', + 'dollar', + 'bucks', + 'currency', + 'funds', + 'payment', + 'money face', + 'reward', + 'thief', + 'bank', + 'benjamins', + 'argent', + 'dinero', + 'i soldi', + 'Geld', + 'guys night', + 'grand', + 'expensive', + 'fancy', + 'buy', + 'shop', + 'spend' + ]), + Emoji( + name: 'gem stone', + char: '\u{1F48E}', + shortName: 'gem', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'diamond', + 'gem', + 'jewel', + 'uc6', + 'bling', + 'minecraft', + 'diamond', + 'rich', + 'sparkle', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'grand', + 'expensive', + 'fancy', + 'bright', + 'shine', + 'twinkle' + ]), + Emoji( + name: 'balance scale', + char: '\u{2696}\u{FE0F}', + shortName: 'scales', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'Libra', + 'balance', + 'justice', + 'scales', + 'tool', + 'weight', + 'zodiac', + 'uc4', + 'tool', + 'science', + 'poison', + 'measure', + 'tools', + 'lab', + 'toxic', + 'toxins' + ]), + Emoji( + name: 'ladder', + char: '\u{1FA9C}', + shortName: 'ladder', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: ['uc13', 'tool', 'household', 'climb', 'tools']), + Emoji( + name: 'toolbox', + char: '\u{1F9F0}', + shortName: 'toolbox', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: ['uc11', 'tool', 'household', 'build', 'tools']), + Emoji( + name: 'screwdriver', + char: '\u{1FA9B}', + shortName: 'screwdriver', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'uc13', + 'tool', + 'household', + 'build', + 'phillips', + 'tools', + 'flat tip', + 'flat head', + 'spiral ratchet', + 'ratchet', + 'slot head', + 'torx', + 'star head', + 'hex key' + ]), + Emoji( + name: 'wrench', + char: '\u{1F527}', + shortName: 'wrench', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'spanner', + 'tool', + 'wrench', + 'uc6', + 'tool', + 'steel', + 'build', + 'tools', + 'metal' + ]), + Emoji( + name: 'hammer', + char: '\u{1F528}', + shortName: 'hammer', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'tool', + 'uc6', + 'tool', + 'weapon', + 'steel', + 'household', + 'build', + 'tools', + 'weapons', + 'metal' + ]), + Emoji( + name: 'hammer and pick', + char: '\u{2692}\u{FE0F}', + shortName: 'hammer_pick', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'hammer', + 'pick', + 'tool', + 'uc4', + 'tool', + 'weapon', + 'minecraft', + 'steel', + 'build', + 'chop', + 'tools', + 'weapons', + 'metal' + ]), + Emoji( + name: 'hammer and wrench', + char: '\u{1F6E0}\u{FE0F}', + shortName: 'tools', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'hammer', + 'spanner', + 'tool', + 'wrench', + 'uc7', + 'tool', + 'steel', + 'build', + 'tools', + 'metal' + ]), + Emoji( + name: 'pick', + char: '\u{26CF}\u{FE0F}', + shortName: 'pick', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'mining', + 'tool', + 'uc5', + 'tool', + 'weapon', + 'farm', + 'viking', + 'minecraft', + 'killer', + 'steel', + 'build', + 'chop', + 'shinobi', + 'tools', + 'weapons', + 'knight', + 'savage', + 'scary clown', + 'metal', + 'samurai' + ]), + Emoji( + name: 'nut and bolt', + char: '\u{1F529}', + shortName: 'nut_and_bolt', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'bolt', + 'nut', + 'tool', + 'uc6', + 'tool', + 'nutcase', + 'steel', + 'build', + 'tools', + 'metal' + ]), + Emoji( + name: 'gear', + char: '\u{2699}\u{FE0F}', + shortName: 'gear', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: ['tool', 'uc4', 'tool', 'steel', 'tools', 'metal']), + Emoji( + name: 'brick', + char: '\u{1F9F1}', + shortName: 'bricks', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeBuilding, + keywords: [ + 'uc11', + 'house', + 'trap', + 'donald trump', + 'private', + 'household', + 'build', + 'block', + 'houses', + 'apartment', + 'apartments', + 'casa', + 'maison', + 'home', + 'trump', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ]), + Emoji( + name: 'chains', + char: '\u{26D3}\u{FE0F}', + shortName: 'chains', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'chain', + 'uc5', + 'tool', + 'halloween', + 'steel', + 'shinobi', + 'tools', + 'samhain', + 'metal', + 'samurai' + ]), + Emoji( + name: 'hook', + char: '\u{1FA9D}', + shortName: 'hook', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: ['uc13', 'tool', 'steel', 'tools', 'metal']), + Emoji( + name: 'knot', + char: '\u{1FAA2}', + shortName: 'knot', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.artsCrafts, + keywords: [ + 'uc13', + 'boat', + 'rock climbing', + 'build', + 'rope', + 'tie', + 'boats', + 'boating', + 'climber', + 'cordage', + 'hitches', + 'bends', + 'splices', + 'loop' + ]), + Emoji( + name: 'magnet', + char: '\u{1F9F2}', + shortName: 'magnet', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: ['uc11', 'science', 'magnet', 'household', 'lab']), + Emoji( + name: 'pistol', + char: '\u{1F52B}', + shortName: 'gun', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'gun', + 'handgun', + 'revolver', + 'tool', + 'weapon', + 'uc6', + 'weapon', + 'angry', + 'dead', + 'gun', + 'sarcastic', + 'deadpool', + 'danger', + 'soldier', + 'texas', + 'summer', + 'killer', + 'hunt', + 'shot', + 'war', + 'independence day', + 'weapons', + 'upset', + 'pissed', + 'pissed off', + 'unhappy', + 'frustrated', + 'anger', + 'rage', + 'frustration', + 'furious', + 'mad', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'guns', + 'trigger', + 'sarcasm', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'weekend', + 'savage', + 'scary clown', + '4th of july' + ]), + Emoji( + name: 'bomb', + char: '\u{1F4A3}', + shortName: 'bomb', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'comic', + 'uc6', + 'weapon', + 'dead', + 'blast', + 'explosion', + 'deadpool', + 'power', + 'danger', + 'minecraft', + 'throw', + 'killer', + 'war', + 'weapons', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'boom', + 'explode', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'savage', + 'scary clown' + ]), + Emoji( + name: 'firecracker', + char: '\u{1F9E8}', + shortName: 'firecracker', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'uc11', + 'weapon', + 'blast', + 'danger', + 'chinese', + 'throw', + 'war', + 'independence day', + 'shinobi', + 'weapons', + 'boom', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'chinois', + 'asian', + 'chine', + '4th of july', + 'samurai' + ]), + Emoji( + name: 'axe', + char: '\u{1FA93}', + shortName: 'axe', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'uc12', + 'tool', + 'weapon', + 'halloween', + 'danger', + 'killer', + 'steel', + 'chopper', + 'knives', + 'chop', + 'tools', + 'weapons', + 'samhain', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'savage', + 'scary clown', + 'metal', + 'hatchet', + 'adz', + 'tomahawk' + ]), + Emoji( + name: 'carpentry saw', + char: '\u{1FA9A}', + shortName: 'carpentry_saw', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'uc13', + 'tool', + 'weapon', + 'steel', + 'chop', + 'tools', + 'weapons', + 'metal' + ]), + Emoji( + name: 'kitchen knife', + char: '\u{1F52A}', + shortName: 'knife', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.dishware, + keywords: [ + 'cooking', + 'hocho', + 'knife', + 'tool', + 'weapon', + 'uc6', + 'tool', + 'weapon', + 'blood', + 'danger', + 'cutlery', + 'minecraft', + 'crazy', + 'killer', + 'hunt', + 'steel', + 'utensils', + 'knives', + 'chop', + 'tools', + 'weapons', + 'sangre', + 'sang', + 'blut', + 'sangue', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'dish', + 'weird', + 'awkward', + 'insane', + 'wild', + 'savage', + 'scary clown', + 'metal' + ]), + Emoji( + name: 'dagger', + char: '\u{1F5E1}\u{FE0F}', + shortName: 'dagger', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'knife', + 'weapon', + 'uc7', + 'weapon', + 'halloween', + 'blood', + 'viking', + 'killer', + 'hunt', + 'steel', + 'knives', + 'weapons', + 'samhain', + 'sangre', + 'sang', + 'blut', + 'sangue', + 'knight', + 'savage', + 'scary clown', + 'metal' + ]), + Emoji( + name: 'crossed swords', + char: '\u{2694}\u{FE0F}', + shortName: 'crossed_swords', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'crossed', + 'swords', + 'weapon', + 'uc4', + 'weapon', + 'japan', + 'dead', + 'deadpool', + 'danger', + 'viking', + 'minecraft', + 'killer', + 'steel', + 'war', + 'knives', + 'shinobi', + 'weapons', + 'japanese', + 'ninja', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'knight', + 'savage', + 'scary clown', + 'metal', + 'samurai' + ]), + Emoji( + name: 'shield', + char: '\u{1F6E1}\u{FE0F}', + shortName: 'shield', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'weapon', + 'uc7', + 'harry potter', + 'viking', + 'minecraft', + 'knight' + ]), + Emoji( + name: 'cigarette', + char: '\u{1F6AC}', + shortName: 'smoking', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.otherObject, + keywords: [ + 'smoking', + 'uc6', + 'drugs', + 'smoking', + 'danger', + 'poison', + 'killer', + 'drug', + 'narcotics', + 'smoke', + 'cigarette', + 'puff', + 'warn', + 'attention', + 'caution', + 'alert', + 'error', + 'panic', + 'restricted', + "don't", + 'dont', + 'dangerous', + 'toxic', + 'toxins', + 'savage', + 'scary clown' + ]), + Emoji( + name: 'coffin', + char: '\u{26B0}\u{FE0F}', + shortName: 'coffin', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.otherObject, + keywords: [ + 'death', + 'uc4', + 'halloween', + 'dead', + 'rip', + 'condolence', + 'killer', + 'war', + 'covid', + 'samhain', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'rest in peace', + 'compassion', + 'savage', + 'scary clown' + ]), + Emoji( + name: 'headstone', + char: '\u{1FAA6}', + shortName: 'headstone', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.otherObject, + keywords: [ + 'uc13', + 'halloween', + 'dead', + 'killer', + 'war', + 'memorial', + 'covid', + 'tombstone', + 'samhain', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'savage', + 'scary clown' + ]), + Emoji( + name: 'funeral urn', + char: '\u{26B1}\u{FE0F}', + shortName: 'urn', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.otherObject, + keywords: [ + 'ashes', + 'death', + 'funeral', + 'urn', + 'uc4', + 'halloween', + 'dead', + 'rip', + 'condolence', + 'covid', + 'samhain', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'rest in peace', + 'compassion' + ]), + Emoji( + name: 'amphora', + char: '\u{1F3FA}', + shortName: 'amphora', + emojiGroup: EmojiGroup.foodDrink, + emojiSubgroup: EmojiSubgroup.dishware, + keywords: [ + 'Aquarius', + 'cooking', + 'drink', + 'jug', + 'tool', + 'weapon', + 'zodiac', + 'uc8', + 'bling', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure' + ]), + Emoji( + name: 'magic wand', + char: '\u{1FA84}', + shortName: 'magic_wand', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'uc13', + 'harry potter', + 'disney', + 'wizard', + 'cartoon', + 'Sorcerer', + 'Sorceress', + 'witch' + ]), + Emoji( + name: 'crystal ball', + char: '\u{1F52E}', + shortName: 'crystal_ball', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'ball', + 'crystal', + 'fairy tale', + 'fantasy', + 'fortune', + 'tool', + 'uc6', + 'halloween', + 'ball', + 'harry potter', + 'magic', + 'disney', + 'bling', + 'mirror', + 'future', + 'mystery', + 'wizard', + 'fantasy', + 'energy', + 'snow white', + 'samhain', + 'balls', + 'ballon', + 'spell', + 'genie', + 'magical', + 'cartoon', + 'jewels', + 'gems', + 'jewel', + 'jewelry', + 'treasure', + 'Sorcerer', + 'Sorceress', + 'witch' + ]), + Emoji( + name: 'prayer beads', + char: '\u{1F4FF}', + shortName: 'prayer_beads', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'beads', + 'clothing', + 'necklace', + 'prayer', + 'religion', + 'uc8', + 'religion', + 'rosary', + 'pray', + 'jesus', + 'bible', + 'prayer', + 'praying', + 'prayers', + 'grateful', + 'sorry', + 'heaven', + 'bless', + 'faith', + 'holy', + 'spirit', + 'hopeful', + 'blessed', + 'preach', + 'offering' + ]), + Emoji( + name: 'nazar amulet', + char: '\u{1F9FF}', + shortName: 'nazar_amulet', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'uc11', + 'game', + 'eyes', + 'luck', + 'magic', + 'evil', + 'fantasy', + 'eye bead', + 'games', + 'gaming', + 'eye', + 'eyebrow', + 'good luck', + 'lucky', + 'spell', + 'genie', + 'magical', + 'imp', + 'demon', + 'devil', + 'naughty', + 'devilish', + 'diablo', + 'diable', + 'satan', + 'Nazar Boncuğu', + 'Munçuk', + 'turkish' + ]), + Emoji( + name: 'barber pole', + char: '\u{1F488}', + shortName: 'barber', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.placeOther, + keywords: ['barber', 'haircut', 'pole', 'uc6']), + Emoji( + name: 'alembic', + char: '\u{2697}\u{FE0F}', + shortName: 'alembic', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.science, + keywords: [ + 'chemistry', + 'tool', + 'uc4', + 'classroom', + 'science', + 'poison', + 'minecraft', + 'measure', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'lab', + 'toxic', + 'toxins' + ]), + Emoji( + name: 'telescope', + char: '\u{1F52D}', + shortName: 'telescope', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.science, + keywords: [ + 'science', + 'tool', + 'uc6', + 'space', + 'star', + 'science', + 'search', + 'outer space', + 'galaxy', + 'universe', + 'nasa', + 'spaceship', + 'stars', + 'lab', + 'look', + 'find', + 'looking', + 'see' + ]), + Emoji( + name: 'microscope', + char: '\u{1F52C}', + shortName: 'microscope', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.science, + keywords: [ + 'science', + 'tool', + 'uc6', + 'classroom', + 'science', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'lab' + ]), + Emoji( + name: 'hole', + char: '\u{1F573}\u{FE0F}', + shortName: 'hole', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: ['hole', 'uc7', 'trap']), + Emoji( + name: 'window', + char: '\u{1FA9F}', + shortName: 'window', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'uc13', + 'house', + 'sky', + 'day', + 'household', + 'daydream', + 'houses', + 'apartment', + 'apartments', + 'casa', + 'maison', + 'home' + ]), + Emoji( + name: 'adhesive bandage', + char: '\u{1FA79}', + shortName: 'adhesive_bandage', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.medical, + keywords: [ + 'uc12', + 'health', + '911', + 'nurse', + 'bandaid', + 'medical', + 'medicine', + 'doctor', + 'emergency', + 'injury' + ]), + Emoji( + name: 'stethoscope', + char: '\u{1FA7A}', + shortName: 'stethoscope', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.medical, + keywords: [ + 'uc12', + 'health', + '911', + 'nurse', + 'heart', + 'auscultation', + 'covid', + 'medical', + 'medicine', + 'doctor', + 'emergency', + 'injury', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur' + ]), + Emoji( + name: 'pill', + char: '\u{1F48A}', + shortName: 'pill', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.medical, + keywords: [ + 'doctor', + 'medicine', + 'sick', + 'uc6', + 'drugs', + 'health', + 'nurse', + 'poison', + 'killer', + 'medical', + 'drug', + 'narcotics', + 'medicine', + 'doctor', + 'toxic', + 'toxins', + 'savage', + 'scary clown' + ]), + Emoji( + name: 'syringe', + char: '\u{1F489}', + shortName: 'syringe', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.medical, + keywords: [ + 'doctor', + 'medicine', + 'needle', + 'shot', + 'sick', + 'tool', + 'uc6', + 'weapon', + 'drugs', + 'dead', + 'health', + '911', + 'blood', + 'nurse', + 'poison', + 'killer', + 'shot', + 'needle', + 'bleed', + 'covid', + 'medical', + 'weapons', + 'drug', + 'narcotics', + 'death', + 'die', + 'dying', + 'fart', + 'goth', + 'grave', + 'headstone', + 'horror', + 'hurt', + 'kill', + 'murder', + 'tomb', + 'toot', + 'died', + 'medicine', + 'doctor', + 'emergency', + 'injury', + 'sangre', + 'sang', + 'blut', + 'sangue', + 'toxic', + 'toxins', + 'savage', + 'scary clown', + 'donation', + 'menstruation' + ]), + Emoji( + name: 'drop of blood', + char: '\u{1FA78}', + shortName: 'drop_of_blood', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.medical, + keywords: [ + 'uc12', + 'body', + 'science', + 'health', + '911', + 'blood', + 'vampire', + 'shot', + 'bleed', + 'medical', + 'body part', + 'anatomy', + 'lab', + 'medicine', + 'doctor', + 'emergency', + 'injury', + 'sangre', + 'sang', + 'blut', + 'sangue', + 'dracula', + 'donation', + 'menstruation' + ]), + Emoji( + name: 'dna', + char: '\u{1F9EC}', + shortName: 'dna', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.science, + keywords: [ + 'uc11', + 'family', + 'body', + 'science', + 'blood', + 'history', + 'future', + 'deoxyribonucleic acid', + 'medical', + 'families', + 'group', + 'brother', + 'sister', + 'daughter', + 'son', + 'together', + 'sibling', + 'twins', + 'brothers', + 'sisters', + 'body part', + 'anatomy', + 'lab', + 'sangre', + 'sang', + 'blut', + 'sangue', + 'ancient', + 'old', + 'gene', + 'genetic code', + 'RNA', + 'chromosome', + 'heredity', + 'nucleic acid' + ]), + Emoji( + name: 'microbe', + char: '\u{1F9A0}', + shortName: 'microbe', + emojiGroup: EmojiGroup.animalsNature, + emojiSubgroup: EmojiSubgroup.animalBug, + keywords: [ + 'uc11', + 'body', + 'science', + 'stinky', + 'bacteria', + 'booger', + 'virus', + 'covid', + 'medical', + 'body part', + 'anatomy', + 'lab', + 'smell', + 'stink', + 'odor', + 'microorganism', + 'bacterium', + 'corona' + ]), + Emoji( + name: 'petri dish', + char: '\u{1F9EB}', + shortName: 'petri_dish', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.science, + keywords: [ + 'uc11', + 'classroom', + 'science', + 'bacteria', + 'petrie dish', + 'mushroom', + 'virus', + 'covid', + 'medical', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'lab', + 'microorganism', + 'bacterium', + 'petri plate', + 'cell culture dish', + 'moss', + 'corona' + ]), + Emoji( + name: 'test tube', + char: '\u{1F9EA}', + shortName: 'test_tube', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.science, + keywords: [ + 'uc11', + 'science', + 'poison', + 'test-tube', + 'measure', + 'medical', + 'lab', + 'toxic', + 'toxins', + 'culture tube', + 'sample tube' + ]), + Emoji( + name: 'thermometer', + char: '\u{1F321}\u{FE0F}', + shortName: 'thermometer', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.skyWeather, + keywords: [ + 'weather', + 'uc7', + 'science', + 'health', + 'hot', + 'virus', + 'measure', + 'medical', + 'lab', + 'medicine', + 'doctor', + 'heat', + 'warm', + 'caliente', + 'chaud', + 'heiß', + 'corona' + ]), + Emoji( + name: 'mouse trap', + char: '\u{1FAA4}', + shortName: 'mouse_trap', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: ['uc13', 'trap', 'household', 'rodent']), + Emoji( + name: 'broom', + char: '\u{1F9F9}', + shortName: 'broom', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: ['uc11', 'clean', 'sweep', 'household', 'dust', 'mop']), + Emoji( + name: 'basket', + char: '\u{1F9FA}', + shortName: 'basket', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'uc11', + 'household', + 'sew', + 'knit', + 'embroider', + 'stitch', + 'repair', + 'crochet', + 'alter', + 'seamstress', + 'fix' + ]), + Emoji( + name: 'sewing needle', + char: '\u{1FAA1}', + shortName: 'sewing_needle', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.artsCrafts, + keywords: [ + 'uc13', + 'bathroom', + 'needle', + 'household', + 'sew', + 'knit', + 'embroider', + 'stitch', + 'repair', + 'crochet', + 'alter', + 'seamstress', + 'fix' + ]), + Emoji( + name: 'roll of paper', + char: '\u{1F9FB}', + shortName: 'roll_of_paper', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'uc11', + 'bathroom', + 'diarrhea', + 'shit', + 'clean', + 'household', + 'shits', + 'the shits', + 'poop', + 'turd', + 'feces', + 'pile', + 'merde', + 'butthole', + 'caca', + 'crap', + 'dirty', + 'pooo', + 'mess', + 'brown', + 'poopoo' + ]), + Emoji( + name: 'toilet', + char: '\u{1F6BD}', + shortName: 'toilet', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'toilet', + 'uc6', + 'bathroom', + 'sick', + 'diarrhea', + 'shit', + 'private', + 'household', + 'throne', + 'barf', + 'vomit', + 'throw up', + 'puke', + 'get well', + 'cough', + 'puking', + 'barfing', + 'malade', + 'spew', + 'shits', + 'the shits', + 'poop', + 'turd', + 'feces', + 'pile', + 'merde', + 'butthole', + 'caca', + 'crap', + 'dirty', + 'pooo', + 'mess', + 'brown', + 'poopoo', + 'ĐŋŅ€Đ¸Đ˛', + 'privÊ', + 'privado', + 'reserved' + ]), + Emoji( + name: 'plunger', + char: '\u{1FAA0}', + shortName: 'plunger', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'uc13', + 'bathroom', + 'shit', + 'household', + 'poop', + 'turd', + 'feces', + 'pile', + 'merde', + 'butthole', + 'caca', + 'crap', + 'dirty', + 'pooo', + 'mess', + 'brown', + 'poopoo' + ]), + Emoji( + name: 'bucket', + char: '\u{1FAA3}', + shortName: 'bucket', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: ['uc13', 'household', 'pail', 'vessel']), + Emoji( + name: 'potable water', + char: '\u{1F6B0}', + shortName: 'potable_water', + emojiGroup: EmojiGroup.symbols, + emojiSubgroup: EmojiSubgroup.transportSign, + keywords: [ + 'drinking', + 'potable', + 'water', + 'uc6', + 'drip', + 'water', + 'household', + 'water drop' + ]), + Emoji( + name: 'shower', + char: '\u{1F6BF}', + shortName: 'shower', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'water', + 'uc6', + 'bathroom', + 'clean', + 'wash', + 'shower', + 'bathe', + 'bathing', + 'washing' + ]), + Emoji( + name: 'bathtub', + char: '\u{1F6C1}', + shortName: 'bathtub', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'bath', + 'uc6', + 'bathroom', + 'steam', + 'clean', + 'wash', + 'steaming', + 'piping', + 'shower', + 'bathe', + 'bathing', + 'washing' + ]), + Emoji( + name: 'person taking bath', + char: '\u{1F6C0}', + shortName: 'bath', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'bath', + 'bathtub', + 'uc6', + 'diversity', + 'bathroom', + 'steam', + 'clean', + 'wash', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'steaming', + 'piping', + 'shower', + 'bathe', + 'bathing', + 'washing', + 'relax', + 'sauna' + ]), + Emoji( + name: 'person taking bath: light skin tone', + char: '\u{1F6C0}\u{1F3FB}', + shortName: 'bath_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'bath', + 'bathtub', + 'light skin tone', + 'uc8', + 'diversity', + 'bathroom', + 'steam', + 'clean', + 'wash', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'steaming', + 'piping', + 'shower', + 'bathe', + 'bathing', + 'washing', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'person taking bath: medium-light skin tone', + char: '\u{1F6C0}\u{1F3FC}', + shortName: 'bath_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'bath', + 'bathtub', + 'medium-light skin tone', + 'uc8', + 'diversity', + 'bathroom', + 'steam', + 'clean', + 'wash', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'steaming', + 'piping', + 'shower', + 'bathe', + 'bathing', + 'washing', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'person taking bath: medium skin tone', + char: '\u{1F6C0}\u{1F3FD}', + shortName: 'bath_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'bath', + 'bathtub', + 'medium skin tone', + 'uc8', + 'diversity', + 'bathroom', + 'steam', + 'clean', + 'wash', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'steaming', + 'piping', + 'shower', + 'bathe', + 'bathing', + 'washing', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'person taking bath: medium-dark skin tone', + char: '\u{1F6C0}\u{1F3FE}', + shortName: 'bath_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'bath', + 'bathtub', + 'medium-dark skin tone', + 'uc8', + 'diversity', + 'bathroom', + 'steam', + 'clean', + 'wash', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'steaming', + 'piping', + 'shower', + 'bathe', + 'bathing', + 'washing', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'person taking bath: dark skin tone', + char: '\u{1F6C0}\u{1F3FF}', + shortName: 'bath_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'bath', + 'bathtub', + 'dark skin tone', + 'uc8', + 'diversity', + 'bathroom', + 'steam', + 'clean', + 'wash', + 'spa', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'steaming', + 'piping', + 'shower', + 'bathe', + 'bathing', + 'washing', + 'relax', + 'sauna' + ], + modifiable: true), + Emoji( + name: 'toothbrush', + char: '\u{1FAA5}', + shortName: 'toothbrush', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: ['uc13', 'bathroom']), + Emoji( + name: 'soap', + char: '\u{1F9FC}', + shortName: 'soap', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'uc11', + 'bathroom', + 'health', + 'pink', + 'clean', + 'wash', + 'household', + 'dishes', + 'savon', + 'covid', + 'medicine', + 'doctor', + 'rose', + 'shower', + 'bathe', + 'bathing', + 'washing' + ]), + Emoji( + name: 'razor', + char: '\u{1FA92}', + shortName: 'razor', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'uc12', + 'weapon', + 'mustache', + 'beard', + 'blade', + 'weapons', + 'shave', + 'trim' + ]), + Emoji( + name: 'sponge', + char: '\u{1F9FD}', + shortName: 'sponge', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'uc11', + 'bathroom', + 'clean', + 'wash', + 'household', + 'dishes', + 'shower', + 'bathe', + 'bathing', + 'washing' + ]), + Emoji( + name: 'lotion bottle', + char: '\u{1F9F4}', + shortName: 'squeeze_bottle', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'uc11', + 'bathroom', + 'beach', + 'clean', + 'wash', + 'picnic', + 'household', + 'dishes', + 'savon', + 'covid', + 'shower', + 'bathe', + 'bathing', + 'washing' + ]), + Emoji( + name: 'bellhop bell', + char: '\u{1F6CE}\u{FE0F}', + shortName: 'bellhop', + emojiGroup: EmojiGroup.travelPlaces, + emojiSubgroup: EmojiSubgroup.hotel, + keywords: [ + 'bell', + 'bellhop', + 'hotel', + 'uc7', + 'vacation', + 'help', + 'suitcase', + 'hotel', + 'carry-on', + 'vacancy', + 'no vacancy' + ]), + Emoji( + name: 'key', + char: '\u{1F511}', + shortName: 'key', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lock, + keywords: [ + 'lock', + 'password', + 'uc6', + 'lock', + 'household', + 'locks', + 'key', + 'keys' + ]), + Emoji( + name: 'old key', + char: '\u{1F5DD}\u{FE0F}', + shortName: 'key2', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lock, + keywords: [ + 'clue', + 'key', + 'lock', + 'old', + 'uc7', + 'lock', + 'harry potter', + 'household', + 'locks', + 'key', + 'keys' + ]), + Emoji( + name: 'door', + char: '\u{1F6AA}', + shortName: 'door', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'door', + 'uc6', + 'minecraft', + 'hotel', + 'household', + 'vacancy', + 'no vacancy' + ]), + Emoji( + name: 'chair', + char: '\u{1FA91}', + shortName: 'chair', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'uc12', + 'household', + 'sit', + 'seat', + 'throne', + 'sitting', + 'kneel', + 'kneeling', + 'bench', + 'sedia', + 'Stuhl', + 'chaise', + 'silla', + 'armchair' + ]), + Emoji( + name: 'mirror', + char: '\u{1FA9E}', + shortName: 'mirror', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'uc13', + 'bathroom', + 'beautiful', + 'disney', + 'mirror', + 'household', + 'snow white', + 'cute', + 'pretty', + 'adorable', + 'adore', + 'beauty', + 'cutie', + 'babe', + 'lovely', + 'cartoon' + ]), + Emoji( + name: 'couch and lamp', + char: '\u{1F6CB}\u{FE0F}', + shortName: 'couch', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'couch', + 'hotel', + 'lamp', + 'uc7', + 'tired', + 'light', + 'sofa', + 'hotel', + 'household', + 'seat', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted', + 'lamp', + 'light bulb', + 'flashlight', + 'spotlight', + 'illuminate', + 'lightbulb', + 'lighting', + 'luce', + 'licht', + 'lumière', + 'luz', + 'vacancy', + 'no vacancy', + 'bench', + 'sedia', + 'Stuhl', + 'chaise', + 'silla', + 'armchair' + ]), + Emoji( + name: 'bed', + char: '\u{1F6CF}\u{FE0F}', + shortName: 'bed', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'hotel', + 'sleep', + 'uc7', + 'tired', + 'hotel', + 'household', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted', + 'vacancy', + 'no vacancy' + ]), + Emoji( + name: 'person in bed', + char: '\u{1F6CC}', + shortName: 'sleeping_accommodation', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'hotel', + 'sleep', + 'uc7', + 'diversity', + 'tired', + 'lazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted' + ]), + Emoji( + name: 'person in bed: light skin tone', + char: '\u{1F6CC}\u{1F3FB}', + shortName: 'person_in_bed_tone1', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'hotel', + 'light skin tone', + 'sleep', + 'uc8', + 'diversity', + 'tired', + 'lazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted' + ], + modifiable: true), + Emoji( + name: 'person in bed: medium-light skin tone', + char: '\u{1F6CC}\u{1F3FC}', + shortName: 'person_in_bed_tone2', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'hotel', + 'medium-light skin tone', + 'sleep', + 'uc8', + 'diversity', + 'tired', + 'lazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted' + ], + modifiable: true), + Emoji( + name: 'person in bed: medium skin tone', + char: '\u{1F6CC}\u{1F3FD}', + shortName: 'person_in_bed_tone3', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'hotel', + 'medium skin tone', + 'sleep', + 'uc8', + 'diversity', + 'tired', + 'lazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted' + ], + modifiable: true), + Emoji( + name: 'person in bed: medium-dark skin tone', + char: '\u{1F6CC}\u{1F3FE}', + shortName: 'person_in_bed_tone4', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'hotel', + 'medium-dark skin tone', + 'sleep', + 'uc8', + 'diversity', + 'tired', + 'lazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted' + ], + modifiable: true), + Emoji( + name: 'person in bed: dark skin tone', + char: '\u{1F6CC}\u{1F3FF}', + shortName: 'person_in_bed_tone5', + emojiGroup: EmojiGroup.peopleBody, + emojiSubgroup: EmojiSubgroup.personResting, + keywords: [ + 'dark skin tone', + 'hotel', + 'sleep', + 'uc8', + 'diversity', + 'tired', + 'lazy', + 'diverse', + 'modifier', + 'modifiers', + 'equality', + 'sleepy', + 'sleep', + 'dormi', + 'pillow', + 'blanket', + 'exhausted' + ], + modifiable: true), + Emoji( + name: 'teddy bear', + char: '\u{1F9F8}', + shortName: 'teddy_bear', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'uc11', + 'animal', + 'baby', + 'play', + 'gummy', + 'household', + 'stuffed animal', + 'toy', + 'animals', + 'animal kingdom', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'doudou' + ]), + Emoji( + name: 'framed picture', + char: '\u{1F5BC}\u{FE0F}', + shortName: 'frame_photo', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.artsCrafts, + keywords: [ + 'art', + 'frame', + 'museum', + 'painting', + 'picture', + 'uc7', + 'theatre', + 'travel', + 'vacation', + 'painting', + 'image', + 'instagram', + 'household', + 'theater', + 'craft', + 'drama', + 'monet', + 'painter', + 'arts' + ]), + Emoji( + name: 'shopping bags', + char: '\u{1F6CD}\u{FE0F}', + shortName: 'shopping_bags', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.clothing, + keywords: [ + 'bag', + 'hotel', + 'shopping', + 'uc7', + 'bag', + 'gift', + 'birthday', + 'happy birthday', + 'celebrate', + 'rich', + 'purchase', + 'swag', + 'present', + 'cadeau', + 'bows', + 'presents', + 'birth', + 'cumpleaÃąos', + 'anniversaire', + 'bday', + 'Bon anniversaire', + 'joyeux anniversaire', + 'buon compleanno', + 'feliz cumpleaÃąos', + 'alles Gute zum Geburtstag', + 'feliz AniversÃĄrio', + 'Gratulerer med dagen', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'grand', + 'expensive', + 'fancy', + 'buy', + 'shop', + 'spend' + ]), + Emoji( + name: 'shopping cart', + char: '\u{1F6D2}', + shortName: 'shopping_cart', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'cart', + 'shopping', + 'trolley', + 'uc9', + 'food', + 'purchase', + 'foods', + 'eat', + 'meal', + 'comida', + 'nourriture', + 'eats', + 'groceries', + 'grocery', + 'hungry', + 'tasty', + 'mmm', + 'yummy', + 'feed', + 'hunger', + 'buy', + 'shop', + 'spend' + ]), + Emoji( + name: 'wrapped gift', + char: '\u{1F381}', + shortName: 'gift', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'box', + 'celebration', + 'gift', + 'present', + 'wrapped', + 'uc6', + 'holidays', + 'love', + 'gift', + 'birthday', + 'christmas', + 'happy birthday', + 'celebrate', + 'holiday', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'present', + 'cadeau', + 'bows', + 'presents', + 'birth', + 'cumpleaÃąos', + 'anniversaire', + 'bday', + 'navidad', + 'xmas', + 'noel', + 'merry christmas', + 'Bon anniversaire', + 'joyeux anniversaire', + 'buon compleanno', + 'feliz cumpleaÃąos', + 'alles Gute zum Geburtstag', + 'feliz AniversÃĄrio', + 'Gratulerer med dagen', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar' + ]), + Emoji( + name: 'balloon', + char: '\u{1F388}', + shortName: 'balloon', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'celebration', + 'uc6', + 'holidays', + 'baby', + 'birthday', + 'good', + 'balloons', + 'happy birthday', + 'celebrate', + 'independence day', + 'sperm', + 'toy', + 'holiday', + 'kid', + 'babies', + 'infant', + 'infants', + 'crying kid', + 'bebe', + 'little', + 'petite', + 'bambino', + 'birth', + 'cumpleaÃąos', + 'anniversaire', + 'bday', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'Bon anniversaire', + 'joyeux anniversaire', + 'buon compleanno', + 'feliz cumpleaÃąos', + 'alles Gute zum Geburtstag', + 'feliz AniversÃĄrio', + 'Gratulerer med dagen', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + '4th of july' + ]), + Emoji( + name: 'carp streamer', + char: '\u{1F38F}', + shortName: 'flags', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'carp', + 'celebration', + 'streamer', + 'uc6', + 'japan', + 'japanese', + 'ninja' + ]), + Emoji( + name: 'ribbon', + char: '\u{1F380}', + shortName: 'ribbon', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'celebration', + 'uc6', + 'holidays', + 'love', + 'gift', + 'birthday', + 'accessories', + 'happy birthday', + 'celebrate', + 'rich', + 'holiday', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'present', + 'cadeau', + 'bows', + 'presents', + 'birth', + 'cumpleaÃąos', + 'anniversaire', + 'bday', + 'Bon anniversaire', + 'joyeux anniversaire', + 'buon compleanno', + 'feliz cumpleaÃąos', + 'alles Gute zum Geburtstag', + 'feliz AniversÃĄrio', + 'Gratulerer med dagen', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'grand', + 'expensive', + 'fancy' + ]), + Emoji( + name: 'confetti ball', + char: '\u{1F38A}', + shortName: 'confetti_ball', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'ball', + 'celebration', + 'confetti', + 'uc6', + 'happy', + 'birthday', + 'cheers', + 'girls night', + 'boys night', + 'happy birthday', + 'confetti', + 'celebrate', + 'glitter', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'birth', + 'cumpleaÃąos', + 'anniversaire', + 'bday', + 'gān bēi', + 'Na zdravi', + 'Proost', + 'Prost', + 'SlÃĄinte', + 'Cin cin', + 'Kanpai', + 'Na zdrowie', + 'SaÃēde', + 'На ĐˇĐ´ĐžŅ€ĐžĐ˛ŅŒĐĩ', + 'Salud', + 'SkÃĨl', + 'Sei gesund', + 'santÊ', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'Bon anniversaire', + 'joyeux anniversaire', + 'buon compleanno', + 'feliz cumpleaÃąos', + 'alles Gute zum Geburtstag', + 'feliz AniversÃĄrio', + 'Gratulerer med dagen', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar' + ]), + Emoji( + name: 'party popper', + char: '\u{1F389}', + shortName: 'tada', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'celebration', + 'party', + 'popper', + 'tada', + 'uc6', + 'holidays', + 'happy', + 'birthday', + 'cheers', + 'good', + 'girls night', + 'boys night', + 'happy birthday', + 'confetti', + 'celebrate', + 'glitter', + 'bingo', + 'fame', + 'fun', + 'independence day', + 'holiday', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'birth', + 'cumpleaÃąos', + 'anniversaire', + 'bday', + 'gān bēi', + 'Na zdravi', + 'Proost', + 'Prost', + 'SlÃĄinte', + 'Cin cin', + 'Kanpai', + 'Na zdrowie', + 'SaÃēde', + 'На ĐˇĐ´ĐžŅ€ĐžĐ˛ŅŒĐĩ', + 'Salud', + 'SkÃĨl', + 'Sei gesund', + 'santÊ', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'ladies night', + 'girls only', + 'girlfriend', + 'guys night', + 'Bon anniversaire', + 'joyeux anniversaire', + 'buon compleanno', + 'feliz cumpleaÃąos', + 'alles Gute zum Geburtstag', + 'feliz AniversÃĄrio', + 'Gratulerer med dagen', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'famous', + 'celebrity', + '4th of july' + ]), + Emoji( + name: 'piÃąata', + char: '\u{1FA85}', + shortName: 'piÃąata', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'uc13', + 'mexican', + 'birthday', + 'happy birthday', + 'celebrate', + 'pinata', + 'mexico', + 'cinco de mayo', + 'espaÃąol', + 'birth', + 'cumpleaÃąos', + 'anniversaire', + 'bday', + 'Bon anniversaire', + 'joyeux anniversaire', + 'buon compleanno', + 'feliz cumpleaÃąos', + 'alles Gute zum Geburtstag', + 'feliz AniversÃĄrio', + 'Gratulerer med dagen', + 'celebration', + 'event', + 'celebrating', + 'festa', + 'parties', + 'events', + 'new years', + 'new year', + 'fiesta', + 'fete', + 'newyear', + 'party', + 'festive', + 'festival', + 'yolo', + 'festejar', + 'papier-mÃĸchÊ', + 'paper mache', + 'pignatta', + 'dahi handi', + 'fer cagar el tiÃŗ', + 'suikawari', + 'pukpok-palayok', + 'cartonería' + ]), + Emoji( + name: 'nesting dolls', + char: '\u{1FA86}', + shortName: 'nesting_dolls', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.game, + keywords: [ + 'uc13', + 'russian', + 'toy', + 'matryoshka dolls', + 'babushka dolls', + 'stacking dolls', + 'russian dolls' + ]), + Emoji( + name: 'Japanese dolls', + char: '\u{1F38E}', + shortName: 'dolls', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'Japanese', + 'celebration', + 'doll', + 'festival', + 'uc6', + 'japan', + 'japanese', + 'ninja' + ]), + Emoji( + name: 'red paper lantern', + char: '\u{1F3EE}', + shortName: 'izakaya_lantern', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'bar', + 'lantern', + 'light', + 'red', + 'uc6', + 'japan', + 'japanese', + 'ninja' + ]), + Emoji( + name: 'wind chime', + char: '\u{1F390}', + shortName: 'wind_chime', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'bell', + 'celebration', + 'chime', + 'wind', + 'uc6', + 'japan', + 'japanese', + 'ninja' + ]), + Emoji( + name: 'red envelope', + char: '\u{1F9E7}', + shortName: 'red_envelope', + emojiGroup: EmojiGroup.activities, + emojiSubgroup: EmojiSubgroup.event, + keywords: [ + 'uc11', + 'gift', + 'chinese', + 'present', + 'cadeau', + 'bows', + 'presents', + 'chinois', + 'asian', + 'chine' + ]), + Emoji( + name: 'envelope', + char: '\u{2709}\u{FE0F}', + shortName: 'envelope', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'email', + 'letter', + 'uc1', + 'write', + 'mail', + 'envelope', + 'work', + 'writing', + 'email', + 'post', + 'post office', + 'letter', + 'message', + 'offer', + 'office' + ]), + Emoji( + name: 'envelope with arrow', + char: '\u{1F4E9}', + shortName: 'envelope_with_arrow', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'arrow', + 'down', + 'e-mail', + 'email', + 'envelope', + 'letter', + 'mail', + 'outgoing', + 'sent', + 'uc6', + 'mail', + 'envelope', + 'download', + 'work', + 'email', + 'post', + 'post office', + 'letter', + 'message', + 'offer', + 'office' + ]), + Emoji( + name: 'incoming envelope', + char: '\u{1F4E8}', + shortName: 'incoming_envelope', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'e-mail', + 'email', + 'envelope', + 'incoming', + 'letter', + 'mail', + 'receive', + 'uc6', + 'mail', + 'envelope', + 'work', + 'email', + 'post', + 'post office', + 'letter', + 'message', + 'offer', + 'office' + ]), + Emoji( + name: 'e-mail', + char: '\u{1F4E7}', + shortName: 'e-mail', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'email', + 'letter', + 'mail', + 'uc6', + 'classroom', + 'mail', + 'business', + 'envelope', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'email', + 'post', + 'post office', + 'letter', + 'message', + 'offer', + 'office' + ]), + Emoji( + name: 'love letter', + char: '\u{1F48C}', + shortName: 'love_letter', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'heart', + 'letter', + 'love', + 'mail', + 'uc6', + 'love', + 'mail', + 'envelope', + 'pink', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'email', + 'post', + 'post office', + 'letter', + 'message', + 'offer', + 'rose' + ]), + Emoji( + name: 'inbox tray', + char: '\u{1F4E5}', + shortName: 'inbox_tray', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'box', + 'inbox', + 'letter', + 'mail', + 'receive', + 'tray', + 'uc6', + 'business', + 'envelope', + 'work', + 'letter', + 'message', + 'offer', + 'office' + ]), + Emoji( + name: 'outbox tray', + char: '\u{1F4E4}', + shortName: 'outbox_tray', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'box', + 'letter', + 'mail', + 'outbox', + 'sent', + 'tray', + 'uc6', + 'business', + 'work', + 'office' + ]), + Emoji( + name: 'package', + char: '\u{1F4E6}', + shortName: 'package', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'box', + 'parcel', + 'uc6', + 'classroom', + 'gift', + 'mail', + 'moving', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'present', + 'cadeau', + 'bows', + 'presents', + 'email', + 'post', + 'post office', + 'office' + ]), + Emoji( + name: 'label', + char: '\u{1F3F7}\u{FE0F}', + shortName: 'label', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: ['label', 'uc7', 'discount', 'price', 'sale', 'bargain']), + Emoji( + name: 'closed mailbox with lowered flag', + char: '\u{1F4EA}', + shortName: 'mailbox_closed', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'closed', + 'lowered', + 'mail', + 'mailbox', + 'postbox', + 'uc6', + 'mail', + 'envelope', + 'household', + 'email', + 'post', + 'post office', + 'letter', + 'message', + 'offer' + ]), + Emoji( + name: 'closed mailbox with raised flag', + char: '\u{1F4EB}', + shortName: 'mailbox', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'closed', + 'mail', + 'mailbox', + 'postbox', + 'uc6', + 'mail', + 'envelope', + 'email', + 'post', + 'post office', + 'letter', + 'message', + 'offer' + ]), + Emoji( + name: 'open mailbox with raised flag', + char: '\u{1F4EC}', + shortName: 'mailbox_with_mail', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'mail', + 'mailbox', + 'open', + 'postbox', + 'uc6', + 'mail', + 'envelope', + 'household', + 'email', + 'post', + 'post office', + 'letter', + 'message', + 'offer' + ]), + Emoji( + name: 'open mailbox with lowered flag', + char: '\u{1F4ED}', + shortName: 'mailbox_with_no_mail', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'lowered', + 'mail', + 'mailbox', + 'open', + 'postbox', + 'uc6', + 'mail', + 'envelope', + 'empty', + 'email', + 'post', + 'post office', + 'letter', + 'message', + 'offer' + ]), + Emoji( + name: 'postbox', + char: '\u{1F4EE}', + shortName: 'postbox', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'mail', + 'mailbox', + 'uc6', + 'mail', + 'envelope', + 'email', + 'post', + 'post office', + 'letter', + 'message', + 'offer' + ]), + Emoji( + name: 'postal horn', + char: '\u{1F4EF}', + shortName: 'postal_horn', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.sound, + keywords: [ + 'horn', + 'post', + 'postal', + 'uc6', + 'instruments', + 'music', + 'instrument', + 'singing', + 'concert', + 'jaz', + 'listen', + 'singer', + 'song', + 'musique' + ]), + Emoji( + name: 'placard', + char: '\u{1FAA7}', + shortName: 'placard', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.otherObject, + keywords: [ + 'uc13', + 'peace', + 'protest', + 'peace out', + 'peace sign', + 'blm', + 'demonstration' + ]), + Emoji( + name: 'scroll', + char: '\u{1F4DC}', + shortName: 'scroll', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'paper', + 'uc6', + 'classroom', + 'harry potter', + 'document', + 'scroll', + 'history', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'documents', + 'ancient', + 'old', + 'office' + ]), + Emoji( + name: 'page with curl', + char: '\u{1F4C3}', + shortName: 'page_with_curl', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'curl', + 'document', + 'page', + 'uc6', + 'classroom', + 'write', + 'document', + 'envelope', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'documents', + 'letter', + 'message', + 'offer', + 'office' + ]), + Emoji( + name: 'page facing up', + char: '\u{1F4C4}', + shortName: 'page_facing_up', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'document', + 'page', + 'uc6', + 'classroom', + 'write', + 'document', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'documents', + 'office' + ]), + Emoji( + name: 'bookmark tabs', + char: '\u{1F4D1}', + shortName: 'bookmark_tabs', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'bookmark', + 'mark', + 'marker', + 'tabs', + 'uc6', + 'classroom', + 'write', + 'document', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'documents', + 'office' + ]), + Emoji( + name: 'receipt', + char: '\u{1F9FE}', + shortName: 'receipt', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.money, + keywords: [ + 'uc11', + 'document', + 'business', + 'discount', + 'history', + 'price', + 'rich', + 'purchase', + 'household', + 'restaurant', + 'invoice', + 'documents', + 'sale', + 'bargain', + 'ancient', + 'old', + 'grand', + 'expensive', + 'fancy', + 'buy', + 'shop', + 'spend' + ]), + Emoji( + name: 'bar chart', + char: '\u{1F4CA}', + shortName: 'bar_chart', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'bar', + 'chart', + 'graph', + 'uc6', + 'classroom', + 'business', + 'data', + 'measure', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'graph', + 'office' + ]), + Emoji( + name: 'chart increasing', + char: '\u{1F4C8}', + shortName: 'chart_with_upwards_trend', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'chart', + 'graph', + 'growth', + 'trend', + 'upward', + 'uc6', + 'classroom', + 'business', + 'data', + 'measure', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'graph', + 'office' + ]), + Emoji( + name: 'chart decreasing', + char: '\u{1F4C9}', + shortName: 'chart_with_downwards_trend', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'chart', + 'down', + 'graph', + 'trend', + 'uc6', + 'classroom', + 'business', + 'data', + 'measure', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'graph', + 'office' + ]), + Emoji( + name: 'spiral notepad', + char: '\u{1F5D2}\u{FE0F}', + shortName: 'notepad_spiral', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'note', + 'pad', + 'spiral', + 'uc7', + 'classroom', + 'write', + 'business', + 'envelope', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'letter', + 'message', + 'offer', + 'office' + ]), + Emoji( + name: 'spiral calendar', + char: '\u{1F5D3}\u{FE0F}', + shortName: 'calendar_spiral', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'calendar', + 'pad', + 'spiral', + 'uc7', + 'classroom', + 'calendar', + 'advent', + 'schedule', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'date', + 'sunday', + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday', + 'month', + 'agenda', + 'mois', + 'year', + 'today', + 'jour', + 'week', + 'when', + 'office' + ]), + Emoji( + name: 'tear-off calendar', + char: '\u{1F4C6}', + shortName: 'calendar', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'calendar', + 'uc6', + 'classroom', + 'day', + 'calendar', + 'business', + 'schedule', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'date', + 'sunday', + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday', + 'month', + 'agenda', + 'mois', + 'year', + 'today', + 'jour', + 'week', + 'when', + 'office' + ]), + Emoji( + name: 'calendar', + char: '\u{1F4C5}', + shortName: 'date', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'date', + 'uc6', + 'classroom', + 'calendar', + 'advent', + 'schedule', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'date', + 'sunday', + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday', + 'month', + 'agenda', + 'mois', + 'year', + 'today', + 'jour', + 'week', + 'when', + 'office' + ]), + Emoji( + name: 'wastebasket', + char: '\u{1F5D1}\u{FE0F}', + shortName: 'wastebasket', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'wastebasket', + 'uc7', + 'classroom', + 'business', + 'trash', + 'clean', + 'empty', + 'delete', + 'household', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'litter', + 'trash can', + 'garbage', + 'rubbish', + 'poubelle', + 'basura', + 'office' + ]), + Emoji( + name: 'card index', + char: '\u{1F4C7}', + shortName: 'card_index', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'card', + 'index', + 'rolodex', + 'uc6', + 'classroom', + 'business', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'office' + ]), + Emoji( + name: 'card file box', + char: '\u{1F5C3}\u{FE0F}', + shortName: 'card_box', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'box', + 'card', + 'file', + 'uc7', + 'classroom', + 'business', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'office' + ]), + Emoji( + name: 'ballot box with ballot', + char: '\u{1F5F3}\u{FE0F}', + shortName: 'ballot_box', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.mail, + keywords: [ + 'ballot', + 'box', + 'uc7', + 'classroom', + 'vote', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning' + ]), + Emoji( + name: 'file cabinet', + char: '\u{1F5C4}\u{FE0F}', + shortName: 'file_cabinet', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'cabinet', + 'file', + 'filing', + 'uc7', + 'classroom', + 'business', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'office' + ]), + Emoji( + name: 'clipboard', + char: '\u{1F4CB}', + shortName: 'clipboard', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'clipboard', + 'uc6', + 'classroom', + 'write', + 'business', + 'data', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'graph', + 'office' + ]), + Emoji( + name: 'file folder', + char: '\u{1F4C1}', + shortName: 'file_folder', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'file', + 'folder', + 'uc6', + 'classroom', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'office' + ]), + Emoji( + name: 'open file folder', + char: '\u{1F4C2}', + shortName: 'open_file_folder', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'file', + 'folder', + 'open', + 'uc6', + 'classroom', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'office' + ]), + Emoji( + name: 'card index dividers', + char: '\u{1F5C2}\u{FE0F}', + shortName: 'dividers', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'card', + 'dividers', + 'index', + 'uc7', + 'classroom', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'office' + ]), + Emoji( + name: 'rolled-up newspaper', + char: '\u{1F5DE}\u{FE0F}', + shortName: 'newspaper2', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'news', + 'newspaper', + 'paper', + 'rolled', + 'uc7', + 'classroom', + 'write', + 'news', + 'history', + 'household', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'article', + 'ancient', + 'old', + 'office' + ]), + Emoji( + name: 'newspaper', + char: '\u{1F4F0}', + shortName: 'newspaper', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'news', + 'paper', + 'uc6', + 'classroom', + 'write', + 'news', + 'history', + 'household', + 'work', + 'covid', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'article', + 'ancient', + 'old', + 'office' + ]), + Emoji( + name: 'notebook', + char: '\u{1F4D3}', + shortName: 'notebook', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'notebook', + 'uc6', + 'book', + 'classroom', + 'write', + 'work', + 'journal', + 'books', + 'read', + 'reading', + 'cahier', + 'livre', + 'cuaderno', + 'diary', + 'dictionary', + 'encyclopedia', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'office' + ]), + Emoji( + name: 'notebook with decorative cover', + char: '\u{1F4D4}', + shortName: 'notebook_with_decorative_cover', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'book', + 'cover', + 'decorated', + 'notebook', + 'uc6', + 'book', + 'classroom', + 'write', + 'work', + 'journal', + 'books', + 'read', + 'reading', + 'cahier', + 'livre', + 'cuaderno', + 'diary', + 'dictionary', + 'encyclopedia', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'office' + ]), + Emoji( + name: 'ledger', + char: '\u{1F4D2}', + shortName: 'ledger', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'notebook', + 'uc6', + 'classroom', + 'write', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'office' + ]), + Emoji( + name: 'closed book', + char: '\u{1F4D5}', + shortName: 'closed_book', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'book', + 'closed', + 'uc6', + 'book', + 'classroom', + 'write', + 'bible', + 'history', + 'work', + 'books', + 'read', + 'reading', + 'cahier', + 'livre', + 'cuaderno', + 'diary', + 'dictionary', + 'encyclopedia', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'ancient', + 'old', + 'office' + ]), + Emoji( + name: 'green book', + char: '\u{1F4D7}', + shortName: 'green_book', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'book', + 'green', + 'uc6', + 'book', + 'classroom', + 'bible', + 'history', + 'work', + 'books', + 'read', + 'reading', + 'cahier', + 'livre', + 'cuaderno', + 'diary', + 'dictionary', + 'encyclopedia', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'ancient', + 'old', + 'office' + ]), + Emoji( + name: 'blue book', + char: '\u{1F4D8}', + shortName: 'blue_book', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'blue', + 'book', + 'uc6', + 'book', + 'classroom', + 'write', + 'bible', + 'history', + 'work', + 'books', + 'read', + 'reading', + 'cahier', + 'livre', + 'cuaderno', + 'diary', + 'dictionary', + 'encyclopedia', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'ancient', + 'old', + 'office' + ]), + Emoji( + name: 'orange book', + char: '\u{1F4D9}', + shortName: 'orange_book', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'book', + 'orange', + 'uc6', + 'book', + 'classroom', + 'write', + 'bible', + 'history', + 'work', + 'books', + 'read', + 'reading', + 'cahier', + 'livre', + 'cuaderno', + 'diary', + 'dictionary', + 'encyclopedia', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'ancient', + 'old', + 'office' + ]), + Emoji( + name: 'books', + char: '\u{1F4DA}', + shortName: 'books', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'book', + 'uc6', + 'book', + 'classroom', + 'write', + 'harry potter', + 'nerd', + 'history', + 'work', + 'books', + 'read', + 'reading', + 'cahier', + 'livre', + 'cuaderno', + 'diary', + 'dictionary', + 'encyclopedia', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'smart', + 'geek', + 'serious', + 'ancient', + 'old', + 'office' + ]), + Emoji( + name: 'open book', + char: '\u{1F4D6}', + shortName: 'book', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'book', + 'open', + 'uc6', + 'book', + 'classroom', + 'write', + 'harry potter', + 'history', + 'schedule', + 'work', + 'journal', + 'books', + 'read', + 'reading', + 'cahier', + 'livre', + 'cuaderno', + 'diary', + 'dictionary', + 'encyclopedia', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'ancient', + 'old', + 'office' + ]), + Emoji( + name: 'bookmark', + char: '\u{1F516}', + shortName: 'bookmark', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.bookPaper, + keywords: [ + 'mark', + 'uc6', + 'book', + 'discount', + 'price', + 'household', + 'books', + 'read', + 'reading', + 'cahier', + 'livre', + 'cuaderno', + 'diary', + 'dictionary', + 'encyclopedia', + 'sale', + 'bargain' + ]), + Emoji( + name: 'safety pin', + char: '\u{1F9F7}', + shortName: 'safety_pin', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.household, + keywords: [ + 'uc11', + 'household', + 'sew', + 'work', + 'knit', + 'embroider', + 'stitch', + 'repair', + 'crochet', + 'alter', + 'seamstress', + 'fix', + 'office' + ]), + Emoji( + name: 'link', + char: '\u{1F517}', + shortName: 'link', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.tool, + keywords: [ + 'link', + 'uc6', + 'classroom', + 'steel', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'metal' + ]), + Emoji( + name: 'paperclip', + char: '\u{1F4CE}', + shortName: 'paperclip', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'paperclip', + 'uc6', + 'classroom', + 'business', + 'household', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'office' + ]), + Emoji( + name: 'linked paperclips', + char: '\u{1F587}\u{FE0F}', + shortName: 'paperclips', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'link', + 'paperclip', + 'uc7', + 'classroom', + 'business', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'office' + ]), + Emoji( + name: 'triangular ruler', + char: '\u{1F4D0}', + shortName: 'triangular_ruler', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'ruler', + 'set', + 'triangle', + 'uc6', + 'tool', + 'classroom', + 'triangle', + 'measure', + 'work', + 'tools', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'triangles', + 'office' + ]), + Emoji( + name: 'straight ruler', + char: '\u{1F4CF}', + shortName: 'straight_ruler', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'ruler', + 'straight edge', + 'uc6', + 'theatre', + 'tool', + 'classroom', + 'household', + 'measure', + 'work', + 'theater', + 'craft', + 'drama', + 'monet', + 'tools', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'office' + ]), + Emoji( + name: 'abacus', + char: '\u{1F9EE}', + shortName: 'abacus', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.computer, + keywords: [ + 'uc11', + 'math', + 'science', + 'vintage', + 'history', + 'calculator', + 'toy', + 'work', + 'decimal', + 'percentage', + 'fraction', + 'lab', + 'ancient', + 'old', + 'count', + 'add', + 'office' + ]), + Emoji( + name: 'pushpin', + char: '\u{1F4CC}', + shortName: 'pushpin', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'pin', + 'uc6', + 'classroom', + 'map', + 'business', + 'household', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'maps', + 'location', + 'locate', + 'local', + 'lost', + 'office' + ]), + Emoji( + name: 'round pushpin', + char: '\u{1F4CD}', + shortName: 'round_pushpin', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'pin', + 'pushpin', + 'uc6', + 'classroom', + 'map', + 'business', + 'household', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'maps', + 'location', + 'locate', + 'local', + 'lost', + 'office' + ]), + Emoji( + name: 'scissors', + char: '\u{2702}\u{FE0F}', + shortName: 'scissors', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.office, + keywords: [ + 'cutting', + 'tool', + 'uc1', + 'theatre', + 'tool', + 'weapon', + 'classroom', + 'steel', + 'household', + 'sew', + 'work', + 'theater', + 'craft', + 'drama', + 'monet', + 'tools', + 'weapons', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'metal', + 'knit', + 'embroider', + 'stitch', + 'repair', + 'crochet', + 'alter', + 'seamstress', + 'fix', + 'office' + ]), + Emoji( + name: 'pen', + char: '\u{1F58A}\u{FE0F}', + shortName: 'pen_ballpoint', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.writing, + keywords: [ + 'ballpoint', + 'uc7', + 'tool', + 'classroom', + 'write', + 'business', + 'color', + 'correct', + 'detective', + 'household', + 'work', + 'journal', + 'tools', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch', + 'passing grade', + 'office' + ]), + Emoji( + name: 'fountain pen', + char: '\u{1F58B}\u{FE0F}', + shortName: 'pen_fountain', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.writing, + keywords: [ + 'fountain', + 'pen', + 'uc7', + 'tool', + 'classroom', + 'write', + 'color', + 'correct', + 'work', + 'tools', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch', + 'passing grade', + 'office' + ]), + Emoji( + name: 'black nib', + char: '\u{2712}\u{FE0F}', + shortName: 'black_nib', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.writing, + keywords: [ + 'nib', + 'pen', + 'uc1', + 'classroom', + 'write', + 'correct', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'passing grade', + 'office' + ]), + Emoji( + name: 'paintbrush', + char: '\u{1F58C}\u{FE0F}', + shortName: 'paintbrush', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.writing, + keywords: [ + 'painting', + 'uc7', + 'theatre', + 'classroom', + 'write', + 'painting', + 'color', + 'theater', + 'craft', + 'drama', + 'monet', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'painter', + 'arts', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch' + ]), + Emoji( + name: 'crayon', + char: '\u{1F58D}\u{FE0F}', + shortName: 'crayon', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.writing, + keywords: [ + 'crayon', + 'uc7', + 'theatre', + 'classroom', + 'write', + 'color', + 'household', + 'theater', + 'craft', + 'drama', + 'monet', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch' + ]), + Emoji( + name: 'memo', + char: '\u{1F4DD}', + shortName: 'pencil', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.writing, + keywords: [ + 'pencil', + 'uc6', + 'classroom', + 'write', + 'document', + 'envelope', + 'color', + 'correct', + 'work', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'documents', + 'letter', + 'message', + 'offer', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch', + 'passing grade', + 'office' + ]), + Emoji( + name: 'pencil', + char: '\u{270F}\u{FE0F}', + shortName: 'pencil2', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.writing, + keywords: [ + 'pencil', + 'uc1', + 'theatre', + 'tool', + 'classroom', + 'write', + 'color', + 'correct', + 'household', + 'work', + 'theater', + 'craft', + 'drama', + 'monet', + 'tools', + 'school', + 'teach', + 'learn', + 'study', + 'college', + 'degree', + 'education', + 'homework', + 'student', + 'teacher', + 'university', + 'test', + 'learning', + 'writing', + 'colour', + 'coloring', + 'colouring', + 'drawing', + 'marker', + 'sketch', + 'passing grade', + 'office' + ]), + Emoji( + name: 'magnifying glass tilted left', + char: '\u{1F50D}', + shortName: 'mag', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'glass', + 'magnifying', + 'search', + 'tool', + 'uc6', + 'google', + 'search', + 'detective', + 'household', + 'look', + 'find', + 'looking', + 'see' + ]), + Emoji( + name: 'magnifying glass tilted right', + char: '\u{1F50E}', + shortName: 'mag_right', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lightVideo, + keywords: [ + 'glass', + 'magnifying', + 'search', + 'tool', + 'uc6', + 'google', + 'search', + 'detective', + 'look', + 'find', + 'looking', + 'see' + ]), + Emoji( + name: 'locked with pen', + char: '\u{1F50F}', + shortName: 'lock_with_ink_pen', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lock, + keywords: [ + 'ink', + 'lock', + 'nib', + 'pen', + 'privacy', + 'uc6', + 'lock', + 'locks', + 'key', + 'keys' + ]), + Emoji( + name: 'locked with key', + char: '\u{1F510}', + shortName: 'closed_lock_with_key', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lock, + keywords: [ + 'closed', + 'key', + 'lock', + 'secure', + 'uc6', + 'lock', + 'household', + 'locks', + 'key', + 'keys' + ]), + Emoji( + name: 'locked', + char: '\u{1F512}', + shortName: 'lock', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lock, + keywords: ['closed', 'uc6', 'lock', 'locks', 'key', 'keys']), + Emoji( + name: 'unlocked', + char: '\u{1F513}', + shortName: 'unlock', + emojiGroup: EmojiGroup.objects, + emojiSubgroup: EmojiSubgroup.lock, + keywords: [ + 'lock', + 'open', + 'unlock', + 'uc6', + 'lock', + 'locks', + 'key', + 'keys' + ]), + Emoji( + name: 'red heart', + char: '\u{2764}\u{FE0F}', + shortName: 'heart', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'heart', + 'uc1', + 'shapes', + 'love', + 'rainbow', + 'red heart', + 'heart', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur', + '<3' + ]), + Emoji( + name: 'orange heart', + char: '\u{1F9E1}', + shortName: 'orange_heart', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'orange', + 'uc10', + 'shapes', + 'love', + 'rainbow', + 'orange', + 'heart', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur' + ]), + Emoji( + name: 'yellow heart', + char: '\u{1F49B}', + shortName: 'yellow_heart', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'yellow', + 'uc6', + 'shapes', + 'love', + 'rainbow', + 'friend', + 'heart', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur' + ]), + Emoji( + name: 'green heart', + char: '\u{1F49A}', + shortName: 'green_heart', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'green', + 'uc6', + 'shapes', + 'halloween', + 'love', + 'rainbow', + 'irish', + 'jealous', + 'heart', + 'samhain', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'saint patricks day', + 'st patricks day', + 'leprechaun', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur' + ]), + Emoji( + name: 'blue heart', + char: '\u{1F499}', + shortName: 'blue_heart', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'blue', + 'uc6', + 'shapes', + 'love', + 'rainbow', + 'friend', + 'heart', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'friends', + 'friendship', + 'best friends', + 'bestfriends', + 'ami', + 'amiga', + 'amigo', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur' + ]), + Emoji( + name: 'purple heart', + char: '\u{1F49C}', + shortName: 'purple_heart', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'purple', + 'uc6', + 'shapes', + 'love', + 'rainbow', + 'pink', + 'heart', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'rose', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur' + ]), + Emoji( + name: 'black heart', + char: '\u{1F5A4}', + shortName: 'black_heart', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'black', + 'evil', + 'wicked', + 'uc9', + 'shapes', + 'halloween', + 'love', + 'heartbreak', + 'rainbow', + 'hate', + 'killer', + 'heart', + 'samhain', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'broken heart', + 'heartbroken', + 'i hate', + 'disgust', + 'stump', + 'shout', + 'dislike', + 'rude', + 'annoy', + 'grinch', + 'gross', + 'grumpy', + 'mean', + 'problem', + 'suck', + 'jerk', + 'asshole', + 'no', + 'savage', + 'scary clown', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur' + ]), + Emoji( + name: 'brown heart', + char: '\u{1F90E}', + shortName: 'brown_heart', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'uc12', + 'shapes', + 'heart', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur' + ]), + Emoji( + name: 'white heart', + char: '\u{1F90D}', + shortName: 'white_heart', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'uc12', + 'shapes', + 'heart', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur' + ]), + Emoji( + name: 'broken heart', + char: '\u{1F494}', + shortName: 'broken_heart', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.emotion, + keywords: [ + 'break', + 'broken', + 'uc6', + 'love', + 'heartbreak', + 'red heart', + 'heart', + 'i love you', + 'te amo', + "je t'aime", + 'anniversary', + 'lovin', + 'amour', + 'aimer', + 'amor', + 'valentines day', + 'enamour', + 'lovey', + 'broken heart', + 'heartbroken', + 'hearts', + 'serce', + 'corazÃŗn', + 'coraÃ§ÃŖo', + 'coeur', + ' keywords; + List? _runes; + + /// Emoji class. + /// [name] of emoji. [char] and character of emoji. [shortName] and a digest name of emoji, [emojiGroup] is emoji's group and [emojiSubgroup] is emoji's subgroup. [keywords] list of keywords for emoji. [modifiable] `true` if emoji has skin. + Emoji( + {this.name, + this.char, + this.shortName, + this.emojiGroup, + this.emojiSubgroup, + this.keywords = const [], + this.modifiable = false}); + + /// Runes of Emoji Character + List get charRunes { + return _runes ??= char!.runes.toList(); + } + + /// Returns current Emoji with New requested [skinTone] if modifiable, else Returns current Emoji + Emoji? newSkin(fitzpatrick skinTone) { + if (modifiable) { + switch (skinTone) { + case fitzpatrick.light: + return Emoji( + name: this.name! + ', tone1', + char: modify(this.char, skinTone), + shortName: this.shortName! + '_tone1', + emojiGroup: this.emojiGroup, + emojiSubgroup: this.emojiSubgroup, + keywords: this.keywords, + modifiable: true); + case fitzpatrick.mediumLight: + return Emoji( + name: this.name! + ', tone2', + char: modify(this.char, skinTone), + shortName: this.shortName! + '_tone2', + emojiGroup: this.emojiGroup, + emojiSubgroup: this.emojiSubgroup, + keywords: this.keywords, + modifiable: true); + case fitzpatrick.medium: + return Emoji( + name: this.name! + ', tone3', + char: modify(this.char, skinTone), + shortName: this.shortName! + '_tone3', + emojiGroup: this.emojiGroup, + emojiSubgroup: this.emojiSubgroup, + keywords: this.keywords, + modifiable: true); + case fitzpatrick.mediumDark: + return Emoji( + name: this.name! + ', tone4', + char: modify(this.char, skinTone), + shortName: this.shortName! + '_tone4', + emojiGroup: this.emojiGroup, + emojiSubgroup: this.emojiSubgroup, + keywords: this.keywords, + modifiable: true); + case fitzpatrick.dark: + return Emoji( + name: this.name! + ', tone5', + char: modify(this.char, skinTone), + shortName: this.shortName! + '_tone5', + emojiGroup: this.emojiGroup, + emojiSubgroup: this.emojiSubgroup, + keywords: this.keywords, + modifiable: true); + case fitzpatrick.None: + return Emoji.byChar(stabilize(this.char)); + } + } + return this; + } + + /// Get all Emojis + static List all() => List.unmodifiable(_emojis); + + /// Returns Emoji by [char] and character + static Emoji? byChar(String char) { + return _emojis.firstWhereOrNull((Emoji emoji) => emoji.char == char); + } + + /// Returns Emoji by [name] + static Emoji? byName(String name) { + name = name.toLowerCase(); // todo: searchable name + return _emojis.firstWhereOrNull((Emoji emoji) => emoji.name == name); + } + + /// Returns Emoji by [name] as short name. + static Emoji? byShortName(String name) { + return _emojis.firstWhereOrNull((Emoji emoji) => emoji.char == name); + } + + /// Returns list of Emojis in a same [group] + static Iterable byGroup(EmojiGroup group) { + return _emojis.where((Emoji emoji) => emoji.emojiGroup == group); + } + + /// Returns list of Emojis in a same [subgroup] + static Iterable bySubgroup(EmojiSubgroup subgroup) { + return _emojis.where((Emoji emoji) => emoji.emojiSubgroup == subgroup); + } + + /// Returns List of Emojis with Specific [keyword] + static Iterable byKeyword(String keyword) { + keyword = keyword.toLowerCase(); + return _emojis.where((Emoji emoji) => emoji.keywords.contains(keyword)); + } + + /// disassemble [emoji] to list of emojis, without skin tones if [noSkin] be `true`. + static List disassemble(String emoji, {bool noSkin = false}) { + List emojiRunes = emoji.runes.toList(); + emojiRunes.removeWhere((codeChar) => + ZeroWidthCharCodes.contains(codeChar) || + (noSkin && _isFitzpatrickCode(codeChar))); + return emojiRunes.map((char) => String.fromCharCode(char)).toList(); + // return emoji.runes.toList()..removeWhere((codeChar) => ZeroWidthCharCodes.contains(codeChar) || (noSkin && _isFitzpatrickCode(codeChar))).map((char) => String.fromCharCode(char)).toList() + } + + /// assemble emojis with [emojiChars] codes. + static String assemble(List emojiChars) { + List codeCharPoints = []; + + for (var i = 0; i < emojiChars.length; i++) { + if (i != 0 && !isFitzpatrick(emojiChars[i - 1])) { + codeCharPoints.add(ZWJ); + } + final emojiRunes = emojiChars[i].runes.toList(); + codeCharPoints.addAll(emojiRunes); + } + codeCharPoints.add(variationSelector16); + return String.fromCharCodes(codeCharPoints); + } + + /// Modify skin tone of [emoji] by requested [skinTone] + static String modify(String? emoji, fitzpatrick skinTone) { + int? skinToneCharCode; + switch (skinTone) { + case fitzpatrick.light: + skinToneCharCode = 127995; + break; + case fitzpatrick.mediumLight: + skinToneCharCode = 127996; + break; + case fitzpatrick.medium: + skinToneCharCode = 127997; + break; + case fitzpatrick.mediumDark: + skinToneCharCode = 127998; + break; + case fitzpatrick.dark: + skinToneCharCode = 127999; + break; + case fitzpatrick.None: + return stabilize(emoji); + } + + final emojiRunes = emoji!.runes.toList(); + List finalCharCodes = []; + for (final charCode in emojiRunes) { + if (!_isFitzpatrickCode(charCode)) { + finalCharCodes.add(charCode); + if (_isModifiable(charCode)) { + finalCharCodes.add(skinToneCharCode); + } + } + } + return String.fromCharCodes(finalCharCodes as Iterable); + } + + // todo: support unspecified gender for "... holding hands", "kiss", "couple with heart" and "family". + /// stabilize [skin] and [gender] of [emoji], if `true`. + static String stabilize(String? emoji, + {bool skin = true, bool gender = false}) { + if (gender) { + emoji = emoji! + .replaceAll( + '\u{200D}\u{2642}\u{FE0F}', '') // remove ZWJ man from emoji + .replaceAll( + '\u{200D}\u{2640}\u{FE0F}', '') // remove ZWJ woman from emoji + .replaceAll('\u{1F468}', '\u{1F9D1}') // replace man with person + .replaceAll('\u{1F469}', '\u{1F9D1}') // replace woman with person + .replaceAll( + '\u{1F474}', '\u{1F9D3}') // replace old man with old person + .replaceAll( + '\u{1F475}', '\u{1F9D3}'); // replace old woman with old person + } + + final List emojiRunes = emoji!.runes.toList(); + + if (skin) { + emojiRunes.removeWhere((codeChar) => _isFitzpatrickCode(codeChar)); + } + return String.fromCharCodes(emojiRunes); + } + + /// returns `true` if [emojiCode] is code of Emoji with skin!. + static _isModifiable(int emojiCode) { + return _modifiableCharCodes.contains(emojiCode); + } + + /// returns `true` if [emoji] is a Fitzpatrick Emoji. + static bool isFitzpatrick(String emoji) { + return skinToneEmojiChars.contains(emoji); + } + + /// returns `true` if [emojiCode] is code of Fitzpatrick Emoji. + static bool _isFitzpatrickCode(int emojiCode) { + return _skinToneCharCodes.contains(emojiCode); + } + + @override + toString() => char!; +} diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index ef73d9ad..11ff3253 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -1,7 +1,7 @@ import 'package:characters/characters.dart'; -import 'package:emojis/emoji.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; final _emojis = Emoji.all(); diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 9d944567..667848c4 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:emojis/emoji.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -12,6 +11,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:shimmer/shimmer.dart'; +import 'package:stream_chat_flutter/src/emoji/emoji.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/stream_chat_theme.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 942df52d..ea208647 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -24,8 +24,6 @@ dependencies: shimmer: ^2.0.0-nullsafety.0 flutter_markdown: ^0.6.1 url_launcher: ^6.0.3 - emojis: - git: git@github.com:Parkar99/emojis.git video_player: ^2.1.1 chewie: ^1.0.0 file_picker: ^3.0.1 From 6a1185445727d061e596cee5c3d795ab3459a14d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 30 Apr 2021 16:42:20 +0200 Subject: [PATCH 170/599] fix? --- packages/stream_chat_flutter_core/example/pubspec.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/stream_chat_flutter_core/example/pubspec.yaml b/packages/stream_chat_flutter_core/example/pubspec.yaml index e03eba6c..889ad518 100644 --- a/packages/stream_chat_flutter_core/example/pubspec.yaml +++ b/packages/stream_chat_flutter_core/example/pubspec.yaml @@ -30,10 +30,6 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.0 -dependency_overrides: - stream_chat: - path: ../../stream_chat - dev_dependencies: flutter_test: sdk: flutter From 727f2689872e27360e62a2421c602765ed8ff469 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 30 Apr 2021 17:00:50 +0200 Subject: [PATCH 171/599] fix --- packages/stream_chat_flutter/example/pubspec.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index 106517fb..a0f2b7db 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -31,14 +31,6 @@ dependencies: cupertino_icons: ^1.0.2 collection: ^1.15.0 -dependency_overrides: - stream_chat: - path: ../../stream_chat - stream_chat_flutter_core: - path: ../../stream_chat_flutter_core - stream_chat_persistence: - path: ../../stream_chat_persistence - dev_dependencies: flutter_test: sdk: flutter From c04af01e772bd1716ec32f56d5103d6527dca349 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 30 Apr 2021 17:10:16 +0200 Subject: [PATCH 172/599] verbose melos --- .github/workflows/stream_flutter_workflow.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 7b5df2ba..95f48ee9 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -27,7 +27,7 @@ jobs: ./.github/workflows/scripts/install-tools.sh flutter pub global activate tuneup - name: 'Bootstrap Workspace' - run: melos bootstrap + run: melos bootstrap --verbose - name: 'Dart Analyze' run: | melos exec -c 3 --ignore="*example*" -- \ @@ -50,7 +50,7 @@ jobs: run: | ./.github/workflows/scripts/install-tools.sh - name: 'Bootstrap Workspace' - run: melos bootstrap + run: melos bootstrap --verbose - name: 'Dart' run: | melos exec -c 1 -- \ @@ -72,7 +72,7 @@ jobs: flutter pub global activate coverage flutter pub global activate remove_from_coverage - name: 'Bootstrap Workspace' - run: melos bootstrap + run: melos bootstrap --verbose - name: 'Dart Test' run: | cd packages/stream_chat From 0c9805cf06b979773201067a9fc4620831f00d4f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 May 2021 10:13:42 +0200 Subject: [PATCH 173/599] update melos config --- melos.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/melos.yaml b/melos.yaml index 6a6c74dc..3fcad03f 100644 --- a/melos.yaml +++ b/melos.yaml @@ -59,5 +59,4 @@ dev_dependencies: pedantic: 1.9.2 environment: - sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.22.4 <2.0.0" \ No newline at end of file + sdk: ">=2.12.0 <3.0.0" \ No newline at end of file From bde1212da125cabef366df7ef9d18b68da66f48c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 May 2021 10:46:31 +0200 Subject: [PATCH 174/599] update deps --- packages/stream_chat_flutter/pubspec.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 9d7c4ad5..f43e8a1f 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -34,8 +34,7 @@ dependencies: http_parser: ^4.0.0 meta: ^1.3.0 lottie: ^1.0.1 - substring_highlight: - git: git@github.com:ArturAntin/substring_highlight.git + substring_highlight: ^1.0.26 flutter_slidable: ^0.6.0-nullsafety.0 image_gallery_saver: ^1.6.9 share_plus: ^2.0.0 From 2e52b0f5aa2f100b17256f2891bf3072443764e3 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 May 2021 10:48:00 +0200 Subject: [PATCH 175/599] update deps --- packages/stream_chat_flutter/pubspec.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index f43e8a1f..74cca71d 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -14,31 +14,31 @@ dependencies: flutter: sdk: flutter stream_chat_flutter_core: ^1.5.2 - photo_view: ^0.11.0 + photo_view: ^0.11.1 rxdart: ^0.26.0 scrollable_positioned_list: ^0.2.0-nullsafety.0 jiffy: ^4.1.0 - flutter_svg: ^0.21.0+1 + flutter_svg: ^0.22.0 flutter_portal: ^0.4.0 cached_network_image: ^3.0.0 - shimmer: ^2.0.0-nullsafety.0 + shimmer: ^2.0.0 flutter_markdown: ^0.6.1 url_launcher: ^6.0.3 video_player: ^2.1.1 chewie: ^1.0.0 file_picker: ^3.0.1 image_picker: ^0.7.4 - flutter_keyboard_visibility: ^5.0.0 + flutter_keyboard_visibility: ^5.0.1 video_compress: ^3.0.0 visibility_detector: ^0.2.0 http_parser: ^4.0.0 meta: ^1.3.0 lottie: ^1.0.1 substring_highlight: ^1.0.26 - flutter_slidable: ^0.6.0-nullsafety.0 + flutter_slidable: ^0.6.0 image_gallery_saver: ^1.6.9 - share_plus: ^2.0.0 - photo_manager: ^1.1.0 + share_plus: ^2.0.3 + photo_manager: ^1.1.4 ezanimation: ^0.5.0 synchronized: ^3.0.0 dio: ^4.0.0 From 37dcca75a5eacacc3b43d1c92d4cdb84b8a8c36f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 May 2021 10:51:50 +0200 Subject: [PATCH 176/599] fix analysis --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 2 +- .../stream_chat_persistence/lib/src/dao/channel_query_dao.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 163f3df1..01c067da 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -238,7 +238,7 @@ class MessageListView extends StatefulWidget { /// Customize the MessageWidget textBuilder final void Function(BuildContext context, Message message)? textBuilder; - final void Function(String link) onLinkTap; + final void Function(String link)? onLinkTap; @override _MessageListViewState createState() => _MessageListViewState(); diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart index 17587cce..9100e76d 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart @@ -99,7 +99,7 @@ class ChannelQueryDao extends DatabaseAccessor if (sort != null && sort.isNotEmpty) { chainedComparator = (a, b) { int result; - for (final comparator in sort!.map((it) => it.comparator)) { + for (final comparator in sort.map((it) => it.comparator)) { try { result = comparator!(a, b); } catch (e) { From ca69d12be49214709f74c266a9be81d59b641471 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 May 2021 11:22:58 +0200 Subject: [PATCH 177/599] fix example --- packages/stream_chat_flutter/example/pubspec.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index a0f2b7db..3feec144 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -25,6 +25,8 @@ dependencies: sdk: flutter stream_chat_flutter: path: ../ + stream_chat_persistence: + path: ../../stream_chat_persistence # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. From 699771c1049bcb1f6e2682540fa8bd56438bed4b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 May 2021 12:50:20 +0200 Subject: [PATCH 178/599] fix messageinput --- packages/stream_chat_flutter/lib/src/message_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 667848c4..aa14882e 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -2095,7 +2095,7 @@ class MessageInputState extends State { } return sendingFuture.then((resp) { - if (resp.message?._type == 'error') { + if (resp.message?.type == 'error') { _parseExistingMessage(message); } if (widget.onMessageSent != null) { From c41e9e8a2eacbb5d9d5f4d7d07edf03f1e7d3564 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 May 2021 15:16:11 +0200 Subject: [PATCH 179/599] update changelogs and pubspecs --- packages/stream_chat/CHANGELOG.md | 5 +++++ packages/stream_chat/lib/version.dart | 2 +- packages/stream_chat/pubspec.yaml | 6 +++--- packages/stream_chat_flutter/CHANGELOG.md | 5 +++++ packages/stream_chat_flutter/pubspec.yaml | 2 +- packages/stream_chat_flutter_core/CHANGELOG.md | 5 +++++ packages/stream_chat_flutter_core/pubspec.yaml | 2 +- packages/stream_chat_persistence/CHANGELOG.md | 5 +++++ packages/stream_chat_persistence/pubspec.yaml | 4 ++-- 9 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index d6d08a3c..2e6c3a78 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0-nullsafety.0 + +- Migrate this package to null safety +- Added typed filters + ## 1.5.3 - fix: `StreamChatClient.connect` returns quicker when you're using the persistence package diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index a18852e1..c03f9c04 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -3,4 +3,4 @@ import 'package:stream_chat/src/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header // ignore: constant_identifier_names -const PACKAGE_VERSION = '1.5.3'; +const PACKAGE_VERSION = '2.0.0-nullsafety.0'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 14f8ca31..6001ecf0 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat homepage: https://getstream.io/ description: The official Dart client for Stream Chat, a service for building chat applications. -version: 1.5.3 +version: 2.0.0-nullsafety.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -24,8 +24,8 @@ dependencies: web_socket_channel: ^2.0.0 dev_dependencies: - build_runner: ^1.12.2 - freezed: ^0.14.1+2 + build_runner: ^2.0.1 + freezed: ^0.14.1+3 json_serializable: ^4.1.0 mocktail: ^0.1.1 test: ^1.16.8 diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index efbdfccc..414f9121 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0-nullsafety.0 + +- Migrate this package to null safety +- Updated `stream_chat_core` dependency + ## 1.5.3 - Updated `stream_chat_core` dependency diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 74cca71d..6d30567b 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 1.5.3 +version: 2.0.0-nullsafety.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 68d01d52..d23fd7d9 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0-nullsafety.0 + +* Migrate this package to null safety +* Update llc dependency + ## 1.5.2 * Update llc dependency diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index ed8ff80f..70bac70a 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 1.5.2 +version: 2.0.0-nullsafety.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index e4971975..1b5c1dc2 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0-nullsafety.0 + +* Migrate this package to null safety +* Update llc dependency + ## 1.5.2 * Fix sorting by last_updated diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index e637ed8e..ec3eace2 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_persistence homepage: https://github.com/GetStream/stream-chat-flutter description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. -version: 1.5.2 +version: 2.0.0-nullsafety.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -25,7 +25,7 @@ dependency_overrides: path: ../stream_chat dev_dependencies: - build_runner: ^1.12.2 + build_runner: ^2.0.1 mocktail: ^0.1.1 moor_generator: ^4.2.1 pedantic: ^1.11.0 From 21792b355c83ae0a47e5c92902ac30794b7de589 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 May 2021 16:21:48 +0200 Subject: [PATCH 180/599] remove final from _path in ws --- packages/stream_chat/lib/src/api/websocket.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/api/websocket.dart b/packages/stream_chat/lib/src/api/websocket.dart index 8b670f7f..b5bbbb69 100644 --- a/packages/stream_chat/lib/src/api/websocket.dart +++ b/packages/stream_chat/lib/src/api/websocket.dart @@ -113,7 +113,7 @@ class WebSocket { Stream get connectionStatusStream => _connectionStatusController.stream; - late final String _path; + late String _path; int _retryAttempt = 1; late WebSocketChannel _channel; Timer? _healthCheck, _reconnectionMonitor; From 7bc1f96d7fba6d96c7e1e83161334858f1ad7b2e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 May 2021 20:21:06 +0200 Subject: [PATCH 181/599] remove override --- packages/stream_chat_flutter/pubspec.yaml | 8 -------- packages/stream_chat_flutter_core/pubspec.yaml | 4 ---- packages/stream_chat_persistence/pubspec.yaml | 4 ---- 3 files changed, 16 deletions(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 6d30567b..c8c4c469 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -5,8 +5,6 @@ version: 2.0.0-nullsafety.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues -publish_to: none - environment: sdk: '>=2.12.0 <3.0.0' @@ -47,12 +45,6 @@ dependencies: video_thumbnail: ^0.3.3 collection: ^1.15.0 -dependency_overrides: - stream_chat: - path: ../stream_chat - stream_chat_flutter_core: - path: ../stream_chat_flutter_core - flutter: assets: - images/ diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 70bac70a..031d8711 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -17,10 +17,6 @@ dependencies: rxdart: ^0.26.0 stream_chat: ^1.5.3 -dependency_overrides: - stream_chat: - path: ../stream_chat - dev_dependencies: fake_async: ^1.2.0 flutter_test: diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index ec3eace2..f3185581 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -20,10 +20,6 @@ dependencies: sqlite3_flutter_libs: ^0.4.1 stream_chat: ^1.5.3 -dependency_overrides: - stream_chat: - path: ../stream_chat - dev_dependencies: build_runner: ^2.0.1 mocktail: ^0.1.1 From ad8b8f359c8030830f359c3205b46d6afcc991d0 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 May 2021 21:22:35 +0200 Subject: [PATCH 182/599] fix deps --- packages/stream_chat_flutter/CHANGELOG.md | 2 +- packages/stream_chat_flutter/pubspec.yaml | 4 ++-- packages/stream_chat_persistence/CHANGELOG.md | 2 +- packages/stream_chat_persistence/pubspec.yaml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 414f9121..fd5d7d32 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.0-nullsafety.0 +## 2.0.0-nullsafety.1 - Migrate this package to null safety - Updated `stream_chat_core` dependency diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index c8c4c469..7a2725ed 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.0 +version: 2.0.0-nullsafety.1 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - stream_chat_flutter_core: ^1.5.2 + stream_chat_flutter_core: ^2.0.0-nullsafety.0 photo_view: ^0.11.1 rxdart: ^0.26.0 scrollable_positioned_list: ^0.2.0-nullsafety.0 diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index 1b5c1dc2..961e3de0 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.0-nullsafety.0 +## 2.0.0-nullsafety.1 * Migrate this package to null safety * Update llc dependency diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index f3185581..5d1927c3 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_persistence homepage: https://github.com/GetStream/stream-chat-flutter description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.0 +version: 2.0.0-nullsafety.1 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -18,7 +18,7 @@ dependencies: path: ^1.8.0 path_provider: ^2.0.1 sqlite3_flutter_libs: ^0.4.1 - stream_chat: ^1.5.3 + stream_chat: ^2.0.0-nullsafety.0 dev_dependencies: build_runner: ^2.0.1 From 1ac667fb3f64f8a3f63bdfc1d66c1552019aba9a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 May 2021 21:47:44 +0200 Subject: [PATCH 183/599] fix deps --- packages/stream_chat_flutter_core/CHANGELOG.md | 2 +- packages/stream_chat_flutter_core/pubspec.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index d23fd7d9..e3436ad3 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.0-nullsafety.0 +## 2.0.0-nullsafety.1 * Migrate this package to null safety * Update llc dependency diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 031d8711..059d927e 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.0 +version: 2.0.0-nullsafety.1 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -15,7 +15,7 @@ dependencies: sdk: flutter meta: ^1.3.0 rxdart: ^0.26.0 - stream_chat: ^1.5.3 + stream_chat: ^2.0.0-nullsafety.1 dev_dependencies: fake_async: ^1.2.0 From 88b8818e45a7de0c6c8bd61e354ea8f147a6a79b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 4 May 2021 09:50:19 +0200 Subject: [PATCH 184/599] fix deps --- packages/stream_chat/CHANGELOG.md | 2 +- packages/stream_chat/lib/version.dart | 2 +- packages/stream_chat/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 2e6c3a78..7fc05197 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.0-nullsafety.0 +## 2.0.0-nullsafety.1 - Migrate this package to null safety - Added typed filters diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index c03f9c04..ef903a2a 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -3,4 +3,4 @@ import 'package:stream_chat/src/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header // ignore: constant_identifier_names -const PACKAGE_VERSION = '2.0.0-nullsafety.0'; +const PACKAGE_VERSION = '2.0.0-nullsafety.1'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 6001ecf0..6b8ee0aa 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat homepage: https://getstream.io/ description: The official Dart client for Stream Chat, a service for building chat applications. -version: 2.0.0-nullsafety.0 +version: 2.0.0-nullsafety.1 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues From eb57c6eb9f8f03feb741f4b95699fba528c990ae Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 4 May 2021 16:11:28 +0530 Subject: [PATCH 185/599] added lint changes, started migrating --- .../stream_chat_flutter/analysis_options.yaml | 162 ++++++++--- .../lib/src/attachment/attachment.dart | 6 +- .../lib/src/attachment/attachment_title.dart | 82 +++--- .../attachment_upload_state_builder.dart | 7 +- .../lib/src/attachment/file_attachment.dart | 39 ++- .../lib/src/attachment/giphy_attachment.dart | 30 +-- .../lib/src/attachment/image_attachment.dart | 252 +++++++++--------- .../lib/src/attachment/video_attachment.dart | 171 ++++++------ .../lib/src/attachment_actions_modal.dart | 30 +-- .../lib/src/back_button.dart | 3 +- .../lib/src/channel_bottom_sheet.dart | 106 ++++---- .../lib/src/channel_header.dart | 3 +- .../lib/src/channel_image.dart | 47 ++-- .../lib/src/channel_info.dart | 125 +++++---- .../lib/src/channel_list_header.dart | 4 +- .../lib/src/channel_list_view.dart | 22 +- .../lib/src/channel_preview.dart | 8 +- .../lib/src/deleted_message.dart | 2 +- .../lib/src/full_screen_media.dart | 2 +- .../lib/src/group_image.dart | 4 +- .../lib/src/image_footer.dart | 102 ++++--- .../lib/src/image_group.dart | 12 +- .../lib/src/image_header.dart | 3 +- .../lib/src/info_tile.dart | 4 +- .../lib/src/media_list_view.dart | 10 +- .../lib/src/mention_tile.dart | 17 +- .../lib/src/message_actions_modal.dart | 63 +++-- .../lib/src/message_input.dart | 169 ++++++------ .../lib/src/message_list_view.dart | 25 +- .../lib/src/message_reactions_modal.dart | 13 +- .../lib/src/message_search_item.dart | 6 +- .../lib/src/message_search_list_view.dart | 7 +- .../lib/src/message_widget.dart | 40 ++- .../lib/src/option_list_tile.dart | 10 +- .../lib/src/quoted_message_widget.dart | 4 +- .../lib/src/reaction_bubble.dart | 21 +- .../lib/src/reaction_picker.dart | 11 +- .../lib/src/stream_chat.dart | 2 +- .../lib/src/stream_chat_theme.dart | 26 +- .../lib/src/stream_neumorphic_button.dart | 7 +- .../lib/src/stream_svg_icon.dart | 1 - .../lib/src/swipeable.dart | 22 +- .../lib/src/thread_header.dart | 2 - .../lib/src/unread_indicator.dart | 4 +- .../lib/src/upload_progress_indicator.dart | 2 +- .../lib/src/url_attachment.dart | 20 +- .../lib/src/user_avatar.dart | 3 +- .../lib/src/user_item.dart | 1 - .../lib/src/user_list_view.dart | 11 +- .../lib/src/user_reaction_display.dart | 2 +- .../stream_chat_flutter/lib/src/utils.dart | 36 +-- .../src/attachment_actions_modal_test.dart | 2 +- .../test/src/attachment_widgets_test.dart | 4 +- .../test/src/channel_header_test.dart | 1 - .../test/src/reaction_bubble_test.dart | 1 - 55 files changed, 884 insertions(+), 885 deletions(-) diff --git a/packages/stream_chat_flutter/analysis_options.yaml b/packages/stream_chat_flutter/analysis_options.yaml index 370654dc..3c78646a 100644 --- a/packages/stream_chat_flutter/analysis_options.yaml +++ b/packages/stream_chat_flutter/analysis_options.yaml @@ -13,52 +13,138 @@ linter: # these rules are documented on and in the same order as # the Dart Lint rules page to make maintenance easier # https://github.com/dart-lang/linter/blob/master/example/all.yaml - # - always_declare_return_types - # - always_specify_types - # - annotate_overrides - # - avoid_as + - always_use_package_imports - avoid_empty_else - - avoid_init_to_null - - avoid_return_types_on_setters - - avoid_web_libraries_in_flutter - - await_only_futures - - camel_case_types + - avoid_relative_lib_imports + - avoid_slow_async_io + - avoid_types_as_parameter_names - cancel_subscriptions - close_sinks - # - comment_references # we do not presume as to what people want to reference in their dartdocs - # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 - control_flow_in_finally - - empty_constructor_bodies - empty_statements - hash_and_equals - - implementation_imports - # - invariant_booleans - # - iterable_contains_unrelated_type - - library_names - # - library_prefixes - # - list_remove_unrelated_type - # - literal_only_boolean_expressions - - non_constant_identifier_names - # - one_member_abstracts - # - only_throw_errors - # - overridden_fields -# - package_api_docs - - package_names - - package_prefixed_library_names - - prefer_is_not_empty - # - prefer_mixin # https://github.com/dart-lang/language/issues/32 - # - public_member_api_docs - - slash_for_doc_comments - # - sort_constructors_first - # - sort_unnamed_constructors_first - # - super_goes_last # no longer needed w/ Dart 2 + - invariant_booleans + - iterable_contains_unrelated_type + - list_remove_unrelated_type + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - no_duplicate_case_values + - no_logic_in_create_state + - prefer_void_to_null - test_types_in_equals - throw_in_finally - # - type_annotate_public_apis # subset of always_specify_types - - type_init_formals - # - unawaited_futures - - unnecessary_brace_in_string_interps - - unnecessary_getters_setters - unnecessary_statements - unrelated_type_equality_checks + - omit_local_variable_types + - use_key_in_widget_constructors - valid_regexps + - always_declare_return_types + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catching_errors + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_private_typedef_functions + - avoid_redundant_argument_values + - avoid_return_types_on_setters + - avoid_returning_null_for_void + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_extensions + - camel_case_types + - cascade_invocations + + - constant_identifier_names + - curly_braces_in_flow_control_structures + - directives_ordering + - empty_catches + - empty_constructor_bodies + - exhaustive_cases + - file_names + - implementation_imports + - join_return_with_assignment + - leading_newlines_in_multiline_strings + - library_names + - library_prefixes + - lines_longer_than_80_chars + - missing_whitespace_between_adjacent_strings + - non_constant_identifier_names + - null_closures + - one_member_abstracts + - only_throw_errors + - package_api_docs + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_asserts_with_message + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_equal_for_default_values + - prefer_expression_function_bodies + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_int_literals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - provide_deprecation_message + - public_member_api_docs + - recursive_getters + - sized_box_for_whitespace + - slash_for_doc_comments + - sort_child_properties_last + - sort_constructors_first + - sort_unnamed_constructors_first + + - type_annotate_public_apis + - type_init_formals + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_parenthesis + - unnecessary_raw_strings + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - use_is_even_rather_than_modulo + - use_late_for_private_fields_and_variables + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_to_and_as_if_applicable + - package_names + - sort_pub_dependencies + + - cast_nullable_to_non_nullable + - unnecessary_null_checks + - tighten_type_of_initializing_formals + - null_check_on_nullable_type_parameter diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment.dart index 96ec9ed3..fb771317 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment.dart @@ -1,7 +1,7 @@ +export 'attachment_upload_state_builder.dart'; +export 'attachment_widget.dart' + show AttachmentError, AttachmentSource, AttachmentSourceX; export 'file_attachment.dart'; export 'giphy_attachment.dart'; export 'image_attachment.dart'; export 'video_attachment.dart'; -export 'attachment_upload_state_builder.dart'; -export 'attachment_widget.dart' - show AttachmentError, AttachmentSource, AttachmentSourceX; diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart index e2686638..ba369e5d 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart @@ -1,57 +1,59 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import '../stream_chat_theme.dart'; -import '../utils.dart'; - +/// Title for attachments class AttachmentTitle extends StatelessWidget { + /// Supply attachment and theme for constructing title const AttachmentTitle({ Key? key, required this.attachment, required this.messageTheme, }) : super(key: key); + /// Theme to apply to text final MessageTheme messageTheme; + + /// Attachment data to display final Attachment attachment; @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - if (attachment.titleLink != null) { - launchURL(context, attachment.titleLink!); - } - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (attachment.title != null) - Text( - attachment.title!, - overflow: TextOverflow.ellipsis, - style: messageTheme.messageText?.copyWith( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - fontWeight: FontWeight.bold, + Widget build(BuildContext context) => GestureDetector( + onTap: () { + if (attachment.titleLink != null) { + launchURL(context, attachment.titleLink); + } + }, + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (attachment.title != null) + Text( + attachment.title!, + overflow: TextOverflow.ellipsis, + style: messageTheme.messageText?.copyWith( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + fontWeight: FontWeight.bold, + ), ), - ), - if (attachment.titleLink != null || attachment.ogScrapeUrl != null) - Text( - Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl!) - .authority - .split('.') - .reversed - .take(2) - .toList() - .reversed - .join('.'), - style: messageTheme.messageText, - ), - ], + if (attachment.titleLink != null || + attachment.ogScrapeUrl != null) + Text( + Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl!) + .authority + .split('.') + .reversed + .take(2) + .toList() + .reversed + .join('.'), + style: messageTheme.messageText, + ), + ], + ), ), - ), - ); - } + ); } 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 2aa71516..e7abac74 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 @@ -2,9 +2,13 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/upload_progress_indicator.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +/// Widget to build in progress typedef InProgressBuilder = Widget Function(BuildContext, int, int); + +/// Widget to build on failure typedef FailedBuilder = Widget Function(BuildContext, String); +/// Widget to display attachment upload state class AttachmentUploadStateBuilder extends StatelessWidget { final Message message; final Attachment attachment; @@ -87,7 +91,6 @@ class _IconButton extends StatelessWidget { elevation: 0, highlightElevation: 0, focusElevation: 0, - disabledElevation: 0, hoverElevation: 0, onPressed: onPressed, fillColor: @@ -237,7 +240,7 @@ class _SuccessState extends StatelessWidget { alignment: Alignment.topRight, child: CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.overlayDark, - maxRadius: 12.0, + maxRadius: 12, child: StreamSvgIcon.check( color: StreamChatTheme.of(context).colorTheme.white, ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart index 90ef92b1..0ef78d85 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -42,7 +42,7 @@ class FileAttachment extends AttachmentWidget { onTap: onAttachmentTap, child: Container( width: size?.width ?? 100, - height: 56.0, + height: 56, decoration: BoxDecoration( color: colorTheme.white, borderRadius: BorderRadius.circular(12), @@ -54,12 +54,12 @@ class FileAttachment extends AttachmentWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - height: 40.0, + height: 40, width: 33.33, - margin: EdgeInsets.all(8.0), + margin: EdgeInsets.all(8), child: _getFileTypeImage(context), ), - SizedBox(width: 8.0), + SizedBox(width: 8), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -71,12 +71,12 @@ class FileAttachment extends AttachmentWidget { maxLines: 1, overflow: TextOverflow.ellipsis, ), - SizedBox(height: 3.0), + SizedBox(height: 3), _buildSubtitle(context), ], ), ), - SizedBox(width: 8.0), + SizedBox(width: 8), _buildTrailing(context), ], ), @@ -87,7 +87,7 @@ class FileAttachment extends AttachmentWidget { ShapeBorder _getDefaultShape(BuildContext context) { return RoundedRectangleBorder( - side: BorderSide(width: 0.0, color: Colors.transparent), + side: BorderSide(width: 0, color: Colors.transparent), borderRadius: BorderRadius.circular(8), ); } @@ -159,8 +159,8 @@ class FileAttachment extends AttachmentWidget { placeholderBuilder: (_) { return Center( child: Container( - width: 20.0, - height: 20.0, + width: 20, + height: 20, child: const CircularProgressIndicator(), ), ); @@ -171,8 +171,8 @@ class FileAttachment extends AttachmentWidget { placeholderBuilder: (_) { return Center( child: Container( - width: 20.0, - height: 20.0, + width: 20, + height: 20, child: const CircularProgressIndicator(), ), ); @@ -197,7 +197,6 @@ class FileAttachment extends AttachmentWidget { elevation: 0, highlightElevation: 0, focusElevation: 0, - disabledElevation: 0, hoverElevation: 0, onPressed: onPressed, fillColor: fillColor, @@ -214,7 +213,7 @@ class FileAttachment extends AttachmentWidget { var trailingWidget = trailing; trailingWidget ??= attachment.uploadState.when( preparing: () => Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: _buildButton( icon: StreamSvgIcon.close(color: theme.colorTheme.white), fillColor: theme.colorTheme.overlayDark, @@ -222,7 +221,7 @@ class FileAttachment extends AttachmentWidget { ), ), inProgress: (_, __) => Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: _buildButton( icon: StreamSvgIcon.close(color: theme.colorTheme.white), fillColor: theme.colorTheme.overlayDark, @@ -230,15 +229,15 @@ class FileAttachment extends AttachmentWidget { ), ), success: () => Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: CircleAvatar( backgroundColor: theme.colorTheme.accentBlue, - maxRadius: 12.0, + maxRadius: 12, child: StreamSvgIcon.check(color: theme.colorTheme.white), ), ), failed: (_) => Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: _buildButton( icon: StreamSvgIcon.retry(color: theme.colorTheme.white), fillColor: theme.colorTheme.overlayDark, @@ -251,7 +250,6 @@ class FileAttachment extends AttachmentWidget { ) ?? IconButton( icon: StreamSvgIcon.cloudDownload(color: theme.colorTheme.black), - padding: const EdgeInsets.all(8), visualDensity: VisualDensity.compact, splashRadius: 16, onPressed: () { @@ -262,7 +260,6 @@ class FileAttachment extends AttachmentWidget { if (message.status == MessageSendingStatus.sent) { trailingWidget = IconButton( icon: StreamSvgIcon.cloudDownload(color: theme.colorTheme.black), - padding: const EdgeInsets.all(8), visualDensity: VisualDensity.compact, splashRadius: 16, onPressed: () { @@ -304,9 +301,9 @@ class FileAttachment extends AttachmentWidget { progressIndicatorColor: theme.colorTheme.accentBlue, ); }, - success: () => Text('${fileSize(size, 2)}', style: textStyle), + success: () => Text(fileSize(size), style: textStyle), failed: (_) => Text('UPLOAD ERROR', style: textStyle), ) ?? - Text('${fileSize(size)}', style: textStyle); + Text(fileSize(size), style: textStyle); } } diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart index 8e2ede71..27e98f10 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -52,10 +52,10 @@ class GiphyAttachment extends AttachmentWidget { clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( - topRight: Radius.circular(16.0), - bottomRight: Radius.circular(0.0), - topLeft: Radius.circular(16.0), - bottomLeft: Radius.circular(16.0), + topRight: Radius.circular(16), + bottomRight: Radius.circular(0), + topLeft: Radius.circular(16), + bottomLeft: Radius.circular(16), ), ), child: Column( @@ -63,7 +63,7 @@ class GiphyAttachment extends AttachmentWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Row( children: [ StreamSvgIcon.giphyIcon(), @@ -91,7 +91,7 @@ class GiphyAttachment extends AttachmentWidget { ), ), Padding( - padding: const EdgeInsets.all(2.0), + padding: const EdgeInsets.all(2), child: GestureDetector( onTap: () => onAttachmentTap ?? _onImageTap(context), child: CachedNetworkImage( @@ -123,8 +123,6 @@ class GiphyAttachment extends AttachmentWidget { height: 0.5, ), Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: Container( @@ -156,7 +154,7 @@ class GiphyAttachment extends AttachmentWidget { .colorTheme .black .withOpacity(0.2), - height: 50.0, + height: 50, ), Expanded( child: Container( @@ -189,7 +187,7 @@ class GiphyAttachment extends AttachmentWidget { .colorTheme .black .withOpacity(0.2), - height: 50.0, + height: 50, ), Expanded( child: Container( @@ -217,11 +215,11 @@ class GiphyAttachment extends AttachmentWidget { ], ), ), - SizedBox(height: 4.0), + SizedBox(height: 4), Align( alignment: Alignment.centerRight, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -230,10 +228,10 @@ class GiphyAttachment extends AttachmentWidget { .colorTheme .black .withOpacity(0.5), - size: 16.0, + size: 16, ), SizedBox( - width: 8.0, + width: 8, ), Text( 'Only visible to you', @@ -332,8 +330,8 @@ class GiphyAttachment extends AttachmentWidget { ), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 4.0, + horizontal: 8, + vertical: 4, ), child: Row( children: [ diff --git a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart index 1ff9b8a9..652a6826 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart @@ -34,137 +34,131 @@ class ImageAttachment extends AttachmentWidget { ); @override - Widget build(BuildContext context) { - return source.when( - local: () { - if (attachment.localUri == null || attachment.file?.bytes == null) { - return AttachmentError(size: size); - } - return _buildImageAttachment( - context, - Image.memory( - attachment.file!.bytes!, - height: size?.height, - width: size?.width, - fit: BoxFit.cover, - errorBuilder: (context, _, __) { - return Image.asset( + Widget build(BuildContext context) => source.when( + local: () { + if (attachment.localUri == null || attachment.file?.bytes == null) { + return AttachmentError(size: size); + } + return _buildImageAttachment( + context, + Image.memory( + attachment.file!.bytes!, + height: size?.height, + width: size?.width, + fit: BoxFit.cover, + errorBuilder: (context, _, __) => Image.asset( 'images/placeholder.png', package: 'stream_chat_flutter', - ); - }, - ), - ); - }, - network: () { - var imageUrl = - attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl; - - if (imageUrl == null) { - return AttachmentError(size: size); - } - - var imageUri = Uri.parse(imageUrl); - if (imageUri.host == 'stream-io-cdn.com') { - imageUri = imageUri.replace(queryParameters: { - ...imageUri.queryParameters, - 'h': '500', - 'w': '500', - 'crop': 'center', - 'resize': 'crop', - }); - } else if (imageUri.host == 'stream-cloud-uploads.imgix.net') { - imageUri = imageUri.replace(queryParameters: { - ...imageUri.queryParameters, - 'height': '500', - 'width': '500', - 'fit': 'crop', - }); - } - imageUrl = imageUri.toString(); - - return _buildImageAttachment( - context, - CachedNetworkImage( - cacheKey: imageUri.path, - height: size?.height, - width: size?.width, - placeholder: (_, __) { - final image = Image.asset( - 'images/placeholder.png', - fit: BoxFit.cover, - package: 'stream_chat_flutter', - ); - final colorTheme = StreamChatTheme.of(context).colorTheme; - return Shimmer.fromColors( - baseColor: colorTheme.greyGainsboro, - highlightColor: colorTheme.whiteSmoke, - child: image, - ); - }, - imageUrl: imageUrl, - errorWidget: (context, url, error) { - return AttachmentError(size: size); - }, - fit: BoxFit.cover, - ), - ); - }, - ); - } - - Widget _buildImageAttachment(BuildContext context, Widget imageWidget) { - return ConstrainedBox( - constraints: BoxConstraints.loose(size!), - child: Column( - children: [ - Expanded( - child: Stack( - children: [ - GestureDetector( - onTap: onAttachmentTap ?? - () async { - final result = await Navigator.push( - context, - MaterialPageRoute( - builder: (_) { - final channel = StreamChannel.of(context).channel; - return StreamChannel( - channel: channel, - child: FullScreenMedia( - mediaAttachments: [attachment], - userName: message.user?.name, - message: message, - onShowMessage: onShowMessage, - ), - ); - }, - ), - ); - if (result != null) onReturnAction?.call(result); - }, - child: imageWidget, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: AttachmentUploadStateBuilder( - message: message, - attachment: attachment, - ), - ), - ], - ), - ), - if (showTitle && attachment.title != null) - Material( - color: messageTheme.messageBackgroundColor, - child: AttachmentTitle( - messageTheme: messageTheme, - attachment: attachment, ), ), - ], - ), - ); - } + ); + }, + network: () { + var imageUrl = + attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl; + + if (imageUrl == null) { + return AttachmentError(size: size); + } + + var imageUri = Uri.parse(imageUrl); + if (imageUri.host == 'stream-io-cdn.com') { + imageUri = imageUri.replace(queryParameters: { + ...imageUri.queryParameters, + 'h': '500', + 'w': '500', + 'crop': 'center', + 'resize': 'crop', + }); + } else if (imageUri.host == 'stream-cloud-uploads.imgix.net') { + imageUri = imageUri.replace(queryParameters: { + ...imageUri.queryParameters, + 'height': '500', + 'width': '500', + 'fit': 'crop', + }); + } + imageUrl = imageUri.toString(); + + return _buildImageAttachment( + context, + CachedNetworkImage( + cacheKey: imageUri.path, + height: size?.height, + width: size?.width, + placeholder: (_, __) { + final image = Image.asset( + 'images/placeholder.png', + fit: BoxFit.cover, + package: 'stream_chat_flutter', + ); + final colorTheme = StreamChatTheme.of(context).colorTheme; + return Shimmer.fromColors( + baseColor: colorTheme.greyGainsboro, + highlightColor: colorTheme.whiteSmoke, + child: image, + ); + }, + imageUrl: imageUrl, + errorWidget: (context, url, error) => AttachmentError(size: size), + fit: BoxFit.cover, + ), + ); + }, + ); + + Widget _buildImageAttachment(BuildContext context, Widget imageWidget) => + ConstrainedBox( + constraints: BoxConstraints.loose(size!), + child: Column( + children: [ + Expanded( + child: Stack( + children: [ + GestureDetector( + onTap: onAttachmentTap ?? + () async { + final result = await Navigator.push( + context, + MaterialPageRoute( + builder: (_) { + final channel = + StreamChannel.of(context).channel; + return StreamChannel( + channel: channel, + child: FullScreenMedia( + mediaAttachments: [attachment], + userName: message.user?.name, + message: message, + onShowMessage: onShowMessage, + ), + ); + }, + ), + ); + if (result != null) onReturnAction?.call(result); + }, + child: imageWidget, + ), + Padding( + padding: const EdgeInsets.all(8), + child: AttachmentUploadStateBuilder( + message: message, + attachment: attachment, + ), + ), + ], + ), + ), + if (showTitle && attachment.title != null) + Material( + color: messageTheme.messageBackgroundColor, + child: AttachmentTitle( + messageTheme: messageTheme, + attachment: attachment, + ), + ), + ], + ), + ); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart index 4ffbee1b..f74bba14 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart @@ -30,100 +30,97 @@ class VideoAttachment extends AttachmentWidget { ); @override - Widget build(BuildContext context) { - return source.when( - local: () { - if (attachment.file == null) { - return AttachmentError(size: size); - } - return _buildVideoAttachment( - context, - VideoThumbnailImage( - video: attachment.file!.path!, - height: size?.height, - width: size?.width, - fit: BoxFit.cover, - errorBuilder: (_, __) => AttachmentError(size: size), - ), - ); - }, - network: () { - if (attachment.assetUrl == null) { - return AttachmentError(size: size); - } - return _buildVideoAttachment( - context, - VideoThumbnailImage( - video: attachment.assetUrl!, - height: size?.height, - width: size?.width, - fit: BoxFit.cover, - errorBuilder: (_, __) => AttachmentError(size: size), - ), - ); - }, - ); - } + Widget build(BuildContext context) => source.when( + local: () { + if (attachment.file == null) { + return AttachmentError(size: size); + } + return _buildVideoAttachment( + context, + VideoThumbnailImage( + video: attachment.file!.path!, + height: size?.height, + width: size?.width, + fit: BoxFit.cover, + errorBuilder: (_, __) => AttachmentError(size: size), + ), + ); + }, + network: () { + if (attachment.assetUrl == null) { + return AttachmentError(size: size); + } + return _buildVideoAttachment( + context, + VideoThumbnailImage( + video: attachment.assetUrl!, + height: size?.height, + width: size?.width, + fit: BoxFit.cover, + errorBuilder: (_, __) => AttachmentError(size: size), + ), + ); + }, + ); - Widget _buildVideoAttachment(BuildContext context, Widget videoWidget) { - return ConstrainedBox( - constraints: BoxConstraints.loose(size ?? Size.infinite), - child: Column( - children: [ - Expanded( - child: GestureDetector( - onTap: onAttachmentTap ?? - () async { - final channel = StreamChannel.of(context).channel; - final res = await Navigator.push( - context, - MaterialPageRoute( - builder: (_) => StreamChannel( - channel: channel, - child: FullScreenMedia( - mediaAttachments: [attachment], - userName: message.user?.name, - message: message, - onShowMessage: onShowMessage, + Widget _buildVideoAttachment(BuildContext context, Widget videoWidget) => + ConstrainedBox( + constraints: BoxConstraints.loose(size ?? Size.infinite), + child: Column( + children: [ + Expanded( + child: GestureDetector( + onTap: onAttachmentTap ?? + () async { + final channel = StreamChannel.of(context).channel; + final res = await Navigator.push( + context, + MaterialPageRoute( + builder: (_) => StreamChannel( + channel: channel, + child: FullScreenMedia( + mediaAttachments: [attachment], + userName: message.user?.name, + message: message, + onShowMessage: onShowMessage, + ), ), ), - ), - ); - if (res != null) onReturnAction?.call(res); - }, - child: Stack( - children: [ - videoWidget, - Center( - child: Material( - shape: CircleBorder(), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Icon(Icons.play_arrow), + ); + if (res != null) onReturnAction?.call(res); + }, + child: Stack( + children: [ + videoWidget, + const Center( + child: Material( + shape: CircleBorder(), + child: Padding( + padding: EdgeInsets.all(16), + child: Icon(Icons.play_arrow), + ), ), ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: AttachmentUploadStateBuilder( - message: message, - attachment: attachment, + Padding( + padding: const EdgeInsets.all(8), + child: AttachmentUploadStateBuilder( + message: message, + attachment: attachment, + ), ), - ), - ], + ], + ), ), ), - ), - if (attachment.title != null) - Material( - color: messageTheme.messageBackgroundColor, - child: AttachmentTitle( - messageTheme: messageTheme, - attachment: attachment, + if (attachment.title != null) + Material( + color: messageTheme.messageBackgroundColor, + child: AttachmentTitle( + messageTheme: messageTheme, + attachment: attachment, + ), ), - ), - ], - ), - ); - } + ], + ), + ); } 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 a91c18df..98aa8d48 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart @@ -42,27 +42,25 @@ class AttachmentActionsModal extends StatelessWidget { }); @override - Widget build(BuildContext context) { - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () => Navigator.maybePop(context), - child: _buildPage(context), - ); - } + Widget build(BuildContext context) => GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => Navigator.maybePop(context), + child: _buildPage(context), + ); Widget _buildPage(context) { final theme = StreamChatTheme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - SizedBox(height: kToolbarHeight), + const SizedBox(height: kToolbarHeight), Padding( - padding: const EdgeInsets.only(right: 8.0), + 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.0), + borderRadius: BorderRadius.circular(16), ), child: Container( child: Column( @@ -73,7 +71,7 @@ class AttachmentActionsModal extends StatelessWidget { context, 'Reply', StreamSvgIcon.iconCurveLineLeftUp( - size: 24.0, + size: 24, color: theme.colorTheme.grey, ), () { @@ -84,7 +82,7 @@ class AttachmentActionsModal extends StatelessWidget { context, 'Show in Chat', StreamSvgIcon.eye( - size: 24.0, + size: 24, color: theme.colorTheme.black, ), onShowMessage, @@ -93,7 +91,7 @@ class AttachmentActionsModal extends StatelessWidget { context, 'Save ${message.attachments[currentIndex].type == 'video' ? 'Video' : 'Image'}', StreamSvgIcon.iconSave( - size: 24.0, + size: 24, color: theme.colorTheme.grey, ), () { @@ -144,7 +142,7 @@ class AttachmentActionsModal extends StatelessWidget { context, 'Delete', StreamSvgIcon.delete( - size: 24.0, + size: 24, color: theme.colorTheme.accentRed, ), () { @@ -201,7 +199,7 @@ class AttachmentActionsModal extends StatelessWidget { child: InkWell( onTap: onTap, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), child: Row( children: [ icon, @@ -271,7 +269,7 @@ class AttachmentActionsModal extends StatelessWidget { children: [ CircularProgressIndicator( value: progress.toProgressIndicatorValue, - strokeWidth: 8.0, + strokeWidth: 8, valueColor: AlwaysStoppedAnimation( theme.colorTheme.accentBlue, ), diff --git a/packages/stream_chat_flutter/lib/src/back_button.dart b/packages/stream_chat_flutter/lib/src/back_button.dart index 3d38364a..faf08fe8 100644 --- a/packages/stream_chat_flutter/lib/src/back_button.dart +++ b/packages/stream_chat_flutter/lib/src/back_button.dart @@ -30,7 +30,6 @@ class StreamBackButton extends StatelessWidget { elevation: 0, highlightElevation: 0, focusElevation: 0, - disabledElevation: 0, hoverElevation: 0, onPressed: () { if (onPressed != null) { @@ -39,7 +38,7 @@ class StreamBackButton extends StatelessWidget { Navigator.maybePop(context); } }, - padding: const EdgeInsets.all(14.0), + padding: const EdgeInsets.all(14), child: StreamSvgIcon.left( size: 24, color: StreamChatTheme.of(context).colorTheme.black, 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 c4218632..911b306b 100644 --- a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart @@ -7,7 +7,7 @@ import 'option_list_tile.dart'; class ChannelBottomSheet extends StatefulWidget { final VoidCallback? onViewInfoTap; - ChannelBottomSheet({this.onViewInfoTap}); + const ChannelBottomSheet({this.onViewInfoTap}); @override _ChannelBottomSheetState createState() => _ChannelBottomSheetState(); @@ -31,29 +31,29 @@ class _ChannelBottomSheetState extends State { clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), + topLeft: Radius.circular(16), + topRight: Radius.circular(16), ), ), child: !_showActions - ? SizedBox() + ? const SizedBox() : ListView( shrinkWrap: true, children: [ - SizedBox( - height: 24.0, + const SizedBox( + height: 24, ), Center( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16), child: ChannelName( textStyle: StreamChatTheme.of(context).textTheme.headlineBold, ), ), ), - SizedBox( - height: 5.0, + const SizedBox( + height: 5, ), Center( child: ChannelInfo( @@ -64,8 +64,8 @@ class _ChannelBottomSheetState extends State { .subtitle, ), ), - SizedBox( - height: 17.0, + const SizedBox( + height: 17, ), if (channel.isDistinct && channel.memberCount == 2) Column( @@ -75,16 +75,16 @@ class _ChannelBottomSheetState extends State { .firstWhere( (e) => e.user?.id != userAsMember.user?.id) .user!, - constraints: BoxConstraints( - maxHeight: 64.0, - maxWidth: 64.0, + constraints: const BoxConstraints( + maxHeight: 64, + maxWidth: 64, ), - borderRadius: BorderRadius.circular(32.0), + borderRadius: BorderRadius.circular(32), onlineIndicatorConstraints: - BoxConstraints.tight(Size(12.0, 12.0)), + BoxConstraints.tight(const Size(12, 12)), ), - SizedBox( - height: 6.0, + const SizedBox( + height: 6, ), Text( members @@ -102,50 +102,48 @@ class _ChannelBottomSheetState extends State { ), if (!(channel.isDistinct && channel.memberCount == 2)) Container( - height: 94.0, + height: 94, alignment: Alignment.center, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: members.length, shrinkWrap: true, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Column( - children: [ - UserAvatar( - user: members[index].user!, - constraints: BoxConstraints.tightFor( - height: 64.0, - width: 64.0, - ), - borderRadius: BorderRadius.circular(32.0), - onlineIndicatorConstraints: - BoxConstraints.tight(Size(12.0, 12.0)), + itemBuilder: (context, index) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Column( + children: [ + UserAvatar( + user: members[index].user!, + constraints: const BoxConstraints.tightFor( + height: 64, + width: 64, ), - SizedBox( - height: 6.0, - ), - Text( - members[index].user?.name ?? '', - style: StreamChatTheme.of(context) - .textTheme - .footnoteBold, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ); - }, + borderRadius: BorderRadius.circular(32), + onlineIndicatorConstraints: + BoxConstraints.tight(Size(12, 12)), + ), + const SizedBox( + height: 6, + ), + Text( + members[index].user?.name ?? '', + style: StreamChatTheme.of(context) + .textTheme + .footnoteBold, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), ), ), - SizedBox( - height: 24.0, + const SizedBox( + height: 24, ), OptionListTile( leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.user( color: StreamChatTheme.of(context).colorTheme.grey, ), @@ -156,7 +154,7 @@ class _ChannelBottomSheetState extends State { if (!channel.isDistinct) OptionListTile( leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.userRemove( color: StreamChatTheme.of(context).colorTheme.grey, ), @@ -175,7 +173,7 @@ class _ChannelBottomSheetState extends State { if (isOwner) OptionListTile( leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.delete( color: StreamChatTheme.of(context).colorTheme.accentRed, ), @@ -195,7 +193,7 @@ class _ChannelBottomSheetState extends State { ), OptionListTile( leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.closeSmall( color: StreamChatTheme.of(context).colorTheme.grey, ), diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index 82270e3b..1882c5b0 100644 --- a/packages/stream_chat_flutter/lib/src/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_header.dart @@ -144,7 +144,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { actions: actions ?? [ Padding( - padding: const EdgeInsets.only(right: 10.0), + padding: const EdgeInsets.only(right: 10), child: Center( child: ChannelImage( borderRadius: StreamChatTheme.of(context) @@ -169,7 +169,6 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { height: preferredSize.height, width: preferredSize.width, child: Column( - crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ title ?? diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index e24fe08b..74983505 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -158,30 +158,29 @@ class ChannelImage extends StatelessWidget { alignment: Alignment.center, fit: StackFit.expand, children: [ - image != null - ? CachedNetworkImage( - imageUrl: image, - errorWidget: (_, __, ___) { - return Center( - child: Text( - snapshot.data?.containsKey('name') ?? false - ? snapshot.data!['name'][0] - : '', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .white, - fontWeight: FontWeight.bold, - ), - ), - ); - }, - fit: BoxFit.cover, - ) - : StreamChatTheme.of(context).defaultChannelImage( - context, - channel, - ), + if (image != null) + CachedNetworkImage( + imageUrl: image, + errorWidget: (_, __, ___) { + return Center( + child: Text( + snapshot.data?.containsKey('name') ?? false + ? snapshot.data!['name'][0] + : '', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.white, + fontWeight: FontWeight.bold, + ), + ), + ); + }, + fit: BoxFit.cover, + ) + else + StreamChatTheme.of(context).defaultChannelImage( + context, + channel, + ), Material( color: Colors.transparent, child: InkWell( diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index dfcaa749..493729b9 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -27,28 +27,26 @@ class ChannelInfo extends StatelessWidget { return StreamBuilder>( stream: channel.state?.membersStream, initialData: channel.state?.members, - builder: (context, snapshot) { - return ConnectionStatusBuilder( - statusBuilder: (context, status) { - switch (status) { - case ConnectionStatus.connected: - return _buildConnectedTitleState(context, snapshot.data); - case ConnectionStatus.connecting: - return _buildConnectingTitleState(context); - case ConnectionStatus.disconnected: - return _buildDisconnectedTitleState(context, client); - default: - return Offstage(); - } - }, - ); - }, + builder: (context, snapshot) => ConnectionStatusBuilder( + statusBuilder: (context, status) { + switch (status) { + case ConnectionStatus.connected: + return _buildConnectedTitleState(context, snapshot.data); + case ConnectionStatus.connecting: + return _buildConnectingTitleState(context); + case ConnectionStatus.disconnected: + return _buildDisconnectedTitleState(context, client); + default: + return const Offstage(); + } + }, + ), ); } Widget _buildConnectedTitleState( BuildContext context, List? members) { - var alternativeWidget; + Widget? alternativeWidget; if (channel.memberCount != null && channel.memberCount! > 2) { var text = '${channel.memberCount} Members'; @@ -82,7 +80,7 @@ class ChannelInfo extends StatelessWidget { } if (!showTypingIndicator) { - return alternativeWidget ?? Offstage(); + return alternativeWidget ?? const Offstage(); } return TypingIndicator( @@ -92,58 +90,55 @@ class ChannelInfo extends StatelessWidget { ); } - Widget _buildConnectingTitleState(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - height: 16, - width: 16, - child: Center( - child: CircularProgressIndicator(), + Widget _buildConnectingTitleState(BuildContext context) => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 16, + width: 16, + child: const Center( + child: CircularProgressIndicator(), + ), ), - ), - SizedBox(width: 10), - Text( - 'Searching for Network', - style: textStyle, - ), - ], - ); - } + SizedBox(width: 10), + Text( + 'Searching for Network', + style: textStyle, + ), + ], + ); Widget _buildDisconnectedTitleState( BuildContext context, StreamChatClient client, - ) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Offline...', - style: textStyle, - ), - TextButton( - style: TextButton.styleFrom( - padding: const EdgeInsets.all(0), - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - visualDensity: VisualDensity( - horizontal: VisualDensity.minimumDensity, - vertical: VisualDensity.minimumDensity, + ) => + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Offline...', + style: textStyle, + ), + TextButton( + style: TextButton.styleFrom( + padding: const EdgeInsets.all(0), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: const VisualDensity( + horizontal: VisualDensity.minimumDensity, + vertical: VisualDensity.minimumDensity, + ), + ), + onPressed: () async { + await client.disconnect(); + await client.connect(); + }, + child: Text( + 'Try Again', + style: textStyle?.copyWith( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + ), ), ), - onPressed: () async { - await client.disconnect(); - await client.connect(); - }, - child: Text( - 'Try Again', - style: textStyle?.copyWith( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ), - ), - ), - ], - ); - } + ], + ); } diff --git a/packages/stream_chat_flutter/lib/src/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel_list_header.dart index f3889e22..bb103f7b 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -168,8 +168,8 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { return SvgPicture.asset( 'svgs/icon_pen_write.svg', package: 'stream_chat_flutter', - width: 24.0, - height: 24.0, + width: 24, + height: 24, color: color, ); }, 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 6d317d97..473007db 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -54,7 +54,7 @@ typedef ViewInfoCallback = void Function(Channel); /// Modify it to change the widget appearance. class ChannelListView extends StatefulWidget { /// Instantiate a new ChannelListView - ChannelListView({ + const ChannelListView({ Key? key, this.filter, this.options, @@ -248,7 +248,7 @@ class _ChannelListViewState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: StreamSvgIcon.message( size: 136, color: StreamChatTheme.of(context) @@ -257,7 +257,7 @@ class _ChannelListViewState extends State { ), ), Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Text( 'Let’s start chatting!', style: StreamChatTheme.of(context).textTheme.headline, @@ -265,7 +265,7 @@ class _ChannelListViewState extends State { ), Padding( padding: const EdgeInsets.symmetric( - vertical: 8.0, + vertical: 8, horizontal: 52, ), child: Text( @@ -340,7 +340,7 @@ class _ChannelListViewState extends State { highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke, child: Column( children: [ - SizedBox(height: 4.0), + SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -358,7 +358,7 @@ class _ChannelListViewState extends State { ], ), SizedBox( - height: 16.0, + height: 16, ), ], ), @@ -441,7 +441,7 @@ class _ChannelListViewState extends State { WidgetSpan( child: Padding( padding: const EdgeInsets.only( - right: 2.0, + right: 2, ), child: Icon(Icons.error_outline), ), @@ -477,7 +477,6 @@ class _ChannelListViewState extends State { enabled: widget.swipeToAction, actionPane: SlidableBehindActionPane(), actionExtentRatio: 0.12, - closeOnScroll: true, secondaryActions: [ IconSlideAction( color: backgroundColor, @@ -589,7 +588,6 @@ class _ChannelListViewState extends State { key: ValueKey('CHANNEL-${channel.id}'), child: Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, children: [ ChannelImage( channel: channel, @@ -634,7 +632,7 @@ class _ChannelListViewState extends State { .accentRed .withOpacity(.2), child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), + padding: const EdgeInsets.symmetric(vertical: 16), child: Center( child: Text('Error loading channels'), ), @@ -644,7 +642,7 @@ class _ChannelListViewState extends State { return snapshot.data! ? Center( child: Padding( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(16), child: const CircularProgressIndicator(), ), ) @@ -653,7 +651,7 @@ class _ChannelListViewState extends State { } Widget _separatorBuilder(context, i) { - var effect = StreamChatTheme.of(context).colorTheme.borderBottom; + final effect = StreamChatTheme.of(context).colorTheme.borderBottom; return Container( height: 1, diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index f28e9368..9c396065 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -47,7 +47,7 @@ class ChannelPreview extends StatelessWidget { /// Widget rendering the sending indicator, by default it uses the [SendingIndicator] widget final Widget? sendingIndicator; - ChannelPreview({ + const ChannelPreview({ required this.channel, Key? key, this.onTap, @@ -128,7 +128,7 @@ class ChannelPreview extends StatelessWidget { if (lastMessage?.user?.id == StreamChat.of(context).user?.id) { return Padding( - padding: const EdgeInsets.only(right: 4.0), + padding: const EdgeInsets.only(right: 4), child: SendingIndicator( message: lastMessage!, size: channelPreviewTheme.indicatorIconSize, @@ -167,7 +167,7 @@ class ChannelPreview extends StatelessWidget { String stringDate; final now = DateTime.now(); - var startOfDay = DateTime(now.year, now.month, now.day); + final startOfDay = DateTime(now.year, now.month, now.day); if (lastMessageAt.millisecondsSinceEpoch >= startOfDay.millisecondsSinceEpoch) { @@ -298,7 +298,7 @@ class ChannelPreview extends StatelessWidget { )); } else { resList.add(TextSpan( - text: e == textList.last ? '$e' : '$e ', + text: e == textList.last ? e : '$e ', style: normalTextStyle, )); } diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart index b3071904..31a9995e 100644 --- a/packages/stream_chat_flutter/lib/src/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart @@ -53,7 +53,7 @@ class DeletedMessage extends StatelessWidget { ), child: Padding( padding: const EdgeInsets.symmetric( - vertical: 8.0, + vertical: 8, horizontal: 16, ), child: Transform( 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 f960b5ee..4b56adc5 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -150,7 +150,7 @@ class _FullScreenMediaState extends State }, child: Padding( padding: const EdgeInsets.symmetric( - vertical: 50.0, + vertical: 50, ), child: Chewie( controller: controller.chewieController!, diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index f39abf59..3e56eeae 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -113,8 +113,8 @@ class GroupImage extends StatelessWidget { child: Container( color: selectionColor ?? StreamChatTheme.of(context).colorTheme.accentBlue, - height: 64.0, - width: 64.0, + height: 64, + width: 64, child: Padding( padding: EdgeInsets.all(selectionThickness), child: avatar, diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/image_footer.dart index 27c92674..f660bad7 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/image_footer.dart @@ -82,47 +82,45 @@ class _ImageFooterState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - !showShareButton - ? Container( - width: 48, - ) - : IconButton( - icon: StreamSvgIcon.iconShare( - size: 24.0, - color: StreamChatTheme.of(context).colorTheme.black, - ), - onPressed: () async { - final attachment = - widget.mediaAttachments[widget.currentPage]; - final url = attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl!; - final type = attachment.type == 'image' - ? 'jpg' - : url.split('?').first.split('.').last; - final request = - await HttpClient().getUrl(Uri.parse(url)); - final response = await request.close(); - final bytes = - await consolidateHttpClientResponseBytes(response); - final tmpPath = await getTemporaryDirectory(); - final filePath = - '${tmpPath.path}/${attachment.id}.$type'; - final file = File(filePath); - await file.writeAsBytes(bytes); - await Share.shareFiles( - [filePath], - mimeTypes: [ - 'image/$type', - ], - ); - }, - ), + if (!showShareButton) + Container( + width: 48, + ) + else + IconButton( + icon: StreamSvgIcon.iconShare( + size: 24, + color: StreamChatTheme.of(context).colorTheme.black, + ), + onPressed: () async { + final attachment = + widget.mediaAttachments[widget.currentPage]; + final url = attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl!; + final type = attachment.type == 'image' + ? 'jpg' + : url.split('?').first.split('.').last; + final request = await HttpClient().getUrl(Uri.parse(url)); + final response = await request.close(); + final bytes = + await consolidateHttpClientResponseBytes(response); + final tmpPath = await getTemporaryDirectory(); + final filePath = '${tmpPath.path}/${attachment.id}.$type'; + final file = File(filePath); + await file.writeAsBytes(bytes); + await Share.shareFiles( + [filePath], + mimeTypes: [ + 'image/$type', + ], + ); + }, + ), InkWell( onTap: widget.onTitleTap, child: Container( child: Column( - crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ @@ -156,12 +154,12 @@ class _ImageFooterState extends State { isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), + topLeft: Radius.circular(16), + topRight: Radius.circular(16), ), ), builder: (context) { - final crossAxisCount = 3; + const crossAxisCount = 3; final noOfRowToShowInitially = widget.mediaAttachments.length > crossAxisCount ? 2 : 1; final size = MediaQuery.of(context).size; @@ -181,7 +179,7 @@ class _ImageFooterState extends State { children: [ Center( child: Padding( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(16), child: Text( 'Photos', style: StreamChatTheme.of(context) @@ -209,8 +207,8 @@ class _ImageFooterState extends State { padding: const EdgeInsets.all(1), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, - mainAxisSpacing: 2.0, - crossAxisSpacing: 2.0, + mainAxisSpacing: 2, + crossAxisSpacing: 2, ), itemBuilder: (context, index) { Widget media; @@ -230,7 +228,7 @@ class _ImageFooterState extends State { media = InkWell( onTap: () => widget.mediaSelectedCallBack!(index), child: AspectRatio( - aspectRatio: 1.0, + aspectRatio: 1, child: CachedNetworkImage( imageUrl: attachment.imageUrl ?? attachment.assetUrl ?? @@ -246,7 +244,7 @@ class _ImageFooterState extends State { media, if (widget.message.user != null) Padding( - padding: EdgeInsets.all(8.0), + padding: EdgeInsets.all(8), child: Container( clipBehavior: Clip.antiAlias, decoration: BoxDecoration( @@ -254,7 +252,7 @@ class _ImageFooterState extends State { color: Colors.white.withOpacity(0.6), boxShadow: [ BoxShadow( - blurRadius: 8.0, + blurRadius: 8, color: StreamChatTheme.of(context) .colorTheme .black @@ -287,7 +285,7 @@ class _ImageFooterState extends State { /// Sends the current message Future sendMessage() async { - var text = _messageController.text.trim(); + final text = _messageController.text.trim(); final attachments = widget.message.attachments; @@ -311,10 +309,10 @@ class _ImageFooterState extends State { class IconClipper extends CustomClipper { @override Path getClip(Size size) { - var leftX = size.width / 5; - var rightX = 4 * size.width / 5; - var topY = size.height / 5; - var bottomY = 4 * size.height / 5; + final leftX = size.width / 5; + final rightX = 4 * size.width / 5; + final topY = size.height / 5; + final bottomY = 4 * size.height / 5; final path = Path(); path.moveTo(leftX, topY); @@ -322,7 +320,7 @@ class IconClipper extends CustomClipper { path.lineTo(rightX, bottomY); path.lineTo(rightX, topY); path.lineTo(leftX, topY); - path.lineTo(0.0, 0.0); + path.lineTo(0, 0); path.close(); return path; } diff --git a/packages/stream_chat_flutter/lib/src/image_group.dart b/packages/stream_chat_flutter/lib/src/image_group.dart index a08d13de..600b7867 100644 --- a/packages/stream_chat_flutter/lib/src/image_group.dart +++ b/packages/stream_chat_flutter/lib/src/image_group.dart @@ -27,22 +27,19 @@ class ImageGroup extends StatelessWidget { direction: Axis.vertical, children: [ Flexible( - flex: 1, fit: FlexFit.tight, child: Flex( crossAxisAlignment: CrossAxisAlignment.stretch, direction: Axis.horizontal, children: [ Flexible( - flex: 1, fit: FlexFit.tight, child: _buildImage(context, 0), ), Flexible( - flex: 1, fit: FlexFit.tight, child: Padding( - padding: const EdgeInsets.only(left: 2.0), + padding: const EdgeInsets.only(left: 2), child: _buildImage(context, 1), ), ), @@ -52,24 +49,21 @@ class ImageGroup extends StatelessWidget { if (images.length >= 3) Flexible( fit: FlexFit.tight, - flex: 1, child: Padding( - padding: const EdgeInsets.only(top: 2.0), + padding: const EdgeInsets.only(top: 2), child: Flex( direction: Axis.horizontal, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Flexible( fit: FlexFit.tight, - flex: 1, child: _buildImage(context, 2), ), if (images.length >= 4) Flexible( fit: FlexFit.tight, - flex: 1, child: Padding( - padding: const EdgeInsets.only(left: 2.0), + padding: const EdgeInsets.only(left: 2), child: Stack( fit: StackFit.expand, children: [ diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/image_header.dart index 6837722c..18c78770 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/image_header.dart @@ -53,7 +53,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { ? IconButton( icon: StreamSvgIcon.close( color: StreamChatTheme.of(context).colorTheme.black, - size: 24.0, + size: 24, ), onPressed: onBackPressed, ) @@ -79,7 +79,6 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { height: preferredSize.height, width: preferredSize.width, child: Column( - crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ diff --git a/packages/stream_chat_flutter/lib/src/info_tile.dart b/packages/stream_chat_flutter/lib/src/info_tile.dart index deb30877..53a9755f 100644 --- a/packages/stream_chat_flutter/lib/src/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/info_tile.dart @@ -11,7 +11,7 @@ class InfoTile extends StatelessWidget { final TextStyle? textStyle; final Color? backgroundColor; - InfoTile({ + const InfoTile({ required this.message, required this.child, required this.showMessage, @@ -28,7 +28,7 @@ class InfoTile extends StatelessWidget { portalAnchor: tileAnchor ?? Alignment.topCenter, childAnchor: childAnchor ?? Alignment.bottomCenter, portal: Container( - height: 25.0, + height: 25, color: backgroundColor ?? StreamChatTheme.of(context).colorTheme.grey.withOpacity(0.9), child: Center( 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 703fb092..a34dc8dd 100644 --- a/packages/stream_chat_flutter/lib/src/media_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/media_list_view.dart @@ -54,7 +54,7 @@ class _MediaListViewState extends State { ) { final media = _media.elementAt(position); return Padding( - padding: const EdgeInsets.symmetric(horizontal: 1.0, vertical: 1.0), + padding: const EdgeInsets.symmetric(horizontal: 1, vertical: 1), child: InkWell( onTap: () { if (widget.onSelect != null) { @@ -64,7 +64,7 @@ class _MediaListViewState extends State { child: Stack( children: [ AspectRatio( - aspectRatio: 1.0, + aspectRatio: 1, child: FadeInImage( fadeInDuration: Duration(milliseconds: 300), placeholder: AssetImage( @@ -144,9 +144,7 @@ class _MediaListViewState extends State { } void _getMedia() async { - final assetList = await PhotoManager.getAssetPathList( - hasAll: true, - ).then((value) { + final assetList = await PhotoManager.getAssetPathList().then((value) { if (value.isNotEmpty == true) { return value.singleWhere((element) => element.isAll); } @@ -178,7 +176,7 @@ class MediaThumbnailProvider extends ImageProvider { ImageStreamCompleter load(key, decode) { return MultiFrameImageStreamCompleter( codec: _loadAsync(key, decode), - scale: 1.0, + scale: 1, informationCollector: () sync* { yield ErrorDescription('Id: ${media.id}'); }, diff --git a/packages/stream_chat_flutter/lib/src/mention_tile.dart b/packages/stream_chat_flutter/lib/src/mention_tile.dart index 37e4e9ad..186e1f16 100644 --- a/packages/stream_chat_flutter/lib/src/mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/mention_tile.dart @@ -20,7 +20,7 @@ class MentionTile extends StatelessWidget { /// Widget at the end of tile final Widget? trailing; - MentionTile( + const MentionTile( this.member, { this.title, this.subtitle, @@ -31,12 +31,11 @@ class MentionTile extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - height: 56.0, + height: 56, child: Row( - crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( - width: 16.0, + width: 16, ), leading ?? UserAvatar( @@ -49,7 +48,7 @@ class MentionTile extends StatelessWidget { user: member.user!, ), SizedBox( - width: 8.0, + width: 8, ), Expanded( child: Align( @@ -60,13 +59,13 @@ class MentionTile extends StatelessWidget { children: [ title ?? Text( - '${member.user!.name}', + member.user!.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: StreamChatTheme.of(context).textTheme.bodyBold, ), SizedBox( - height: 2.0, + height: 2, ), subtitle ?? Text( @@ -88,8 +87,8 @@ class MentionTile extends StatelessWidget { trailing ?? Padding( padding: const EdgeInsets.only( - right: 18.0, - left: 8.0, + right: 18, + left: 8, ), child: StreamSvgIcon.mentions( color: StreamChatTheme.of(context).colorTheme.accentBlue, 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 b737bd09..ecff8799 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -119,7 +119,7 @@ class _MessageActionsModalState extends State { ), if (_showActions) TweenAnimationBuilder( - tween: Tween(begin: 0.0, end: 1.0), + tween: Tween(begin: 0, end: 1), duration: Duration(milliseconds: 300), curve: Curves.easeInOutBack, builder: (context, val, snapshot) { @@ -128,7 +128,7 @@ class _MessageActionsModalState extends State { child: Center( child: SingleChildScrollView( child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Column( crossAxisAlignment: widget.reverse ? CrossAxisAlignment.end @@ -146,7 +146,7 @@ class _MessageActionsModalState extends State { : (divFactor > 1.0 ? 0.0 : -(1.0 - divFactor)), - 0.0), + 0), child: ReactionPicker( message: widget.message, ), @@ -166,7 +166,6 @@ class _MessageActionsModalState extends State { messageTheme: widget.messageTheme, showReactions: false, showUsername: false, - showThreadReplyIndicator: false, showReplyMessage: false, showUserAvatar: widget.showUserAvatar, attachmentPadding: EdgeInsets.all( @@ -176,7 +175,7 @@ class _MessageActionsModalState extends State { translateUserAvatar: false, padding: const EdgeInsets.all(0), textPadding: EdgeInsets.symmetric( - vertical: 8.0, + vertical: 8, horizontal: widget.message.text!.isOnlyEmoji ? 0 : 16.0, @@ -185,7 +184,6 @@ class _MessageActionsModalState extends State { widget.showReactions && (widget.message.status == MessageSendingStatus.sent), - showInChannelIndicator: false, showSendingIndicator: false, shape: widget.messageShape, attachmentShape: widget.attachmentShape, @@ -269,7 +267,7 @@ class _MessageActionsModalState extends State { messageAction.onTap?.call(widget.message); }, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), child: Row( children: [ messageAction.leading ?? Offstage(), @@ -289,7 +287,7 @@ class _MessageActionsModalState extends State { title: 'Flag Message', icon: StreamSvgIcon.flag( color: StreamChatTheme.of(context).colorTheme.accentRed, - size: 24.0, + size: 24, ), question: 'Do you want to send a copy of this message to a\nmoderator for further investigation?', @@ -305,7 +303,7 @@ class _MessageActionsModalState extends State { context, icon: StreamSvgIcon.flag( color: theme.colorTheme.accentRed, - size: 24.0, + size: 24, ), details: 'The message has been reported to a moderator.', title: 'Message flagged', @@ -317,7 +315,7 @@ class _MessageActionsModalState extends State { context, icon: StreamSvgIcon.flag( color: theme.colorTheme.accentRed, - size: 24.0, + size: 24, ), details: 'The message has been reported to a moderator.', title: 'Message flagged', @@ -339,7 +337,7 @@ class _MessageActionsModalState extends State { title: 'Delete message', icon: StreamSvgIcon.flag( color: StreamChatTheme.of(context).colorTheme.accentRed, - size: 24.0, + size: 24, ), question: 'Are you sure you want to permanently delete this\nmessage?', okText: 'DELETE', @@ -365,7 +363,7 @@ class _MessageActionsModalState extends State { context, icon: StreamSvgIcon.error( color: StreamChatTheme.of(context).colorTheme.accentRed, - size: 24.0, + size: 24, ), details: 'The operation couldn\'t be completed.', title: 'Something went wrong', @@ -382,7 +380,7 @@ class _MessageActionsModalState extends State { } }, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), child: Row( children: [ StreamSvgIcon.reply( @@ -401,9 +399,9 @@ class _MessageActionsModalState extends State { Widget _buildFlagButton(BuildContext context) { return InkWell( - onTap: () => _showFlagDialog(), + onTap: _showFlagDialog, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), child: Row( children: [ StreamSvgIcon.iconFlag( @@ -424,9 +422,9 @@ class _MessageActionsModalState extends State { final isDeleteFailed = widget.message.status == MessageSendingStatus.failed_delete; return InkWell( - onTap: () => _showDeleteDialog(), + onTap: _showDeleteDialog, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), child: Row( children: [ StreamSvgIcon.delete( @@ -453,7 +451,7 @@ class _MessageActionsModalState extends State { Navigator.pop(context); }, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), child: Row( children: [ StreamSvgIcon.copy( @@ -478,7 +476,7 @@ class _MessageActionsModalState extends State { _showEditBottomSheet(context); }, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), child: Row( children: [ StreamSvgIcon.edit( @@ -509,7 +507,7 @@ class _MessageActionsModalState extends State { } }, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), child: Row( children: [ StreamSvgIcon.circleUp( @@ -555,7 +553,7 @@ class _MessageActionsModalState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: StreamSvgIcon.edit( color: StreamChatTheme.of(context) .colorTheme @@ -574,16 +572,17 @@ class _MessageActionsModalState extends State { ], ), ), - widget.editMessageInputBuilder != null - ? widget.editMessageInputBuilder!(context, widget.message) - : MessageInput( - editMessage: widget.message, - preMessageSending: (m) { - FocusScope.of(context).unfocus(); - Navigator.pop(context); - return m; - }, - ), + if (widget.editMessageInputBuilder != null) + widget.editMessageInputBuilder!(context, widget.message) + else + MessageInput( + editMessage: widget.message, + preMessageSending: (m) { + FocusScope.of(context).unfocus(); + Navigator.pop(context); + return m; + }, + ), ], ), ); @@ -600,7 +599,7 @@ class _MessageActionsModalState extends State { } }, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), child: Row( children: [ StreamSvgIcon.thread( diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index aa14882e..4c58f312 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -109,7 +109,7 @@ const _kMaxAttachmentSize = 20971520; // 20MB in Bytes /// Modify it to change the widget appearance. class MessageInput extends StatefulWidget { /// Instantiate a new MessageInput - MessageInput({ + const MessageInput({ Key? key, this.onMessageSent, this.preMessageSending, @@ -306,7 +306,7 @@ class MessageInputState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: StreamSvgIcon.reply( color: StreamChatTheme.of(context) .colorTheme @@ -326,15 +326,15 @@ class MessageInputState extends State { ), ), Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), + padding: const EdgeInsets.symmetric(vertical: 8), child: _buildTextField(context), ), if (widget.parentMessage != null && !widget.hideSendAsDm) Padding( padding: const EdgeInsets.only( - right: 12.0, - left: 12.0, - bottom: 12.0, + right: 12, + left: 12, + bottom: 12, ), child: _buildDmCheckbox(), ), @@ -356,7 +356,6 @@ class MessageInputState extends State { Flex _buildTextField(BuildContext context) { return Flex( direction: Axis.horizontal, - crossAxisAlignment: CrossAxisAlignment.center, children: [ if (!_commandEnabled && widget.actionsLocation == ActionsLocation.left) _buildExpandActionsButton(), @@ -371,7 +370,6 @@ class MessageInputState extends State { Widget _buildDmCheckbox() { return Row( - crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( height: 16, @@ -407,7 +405,7 @@ class MessageInputState extends State { ? CrossFadeState.showFirst : CrossFadeState.showSecond, firstChild: StreamSvgIcon.check( - size: 16.0, + size: 16, color: StreamChatTheme.of(context).colorTheme.white, ), secondChild: SizedBox( @@ -420,7 +418,7 @@ class MessageInputState extends State { ), ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), + padding: const EdgeInsets.symmetric(horizontal: 12), child: Text( 'Also send as direct message', style: StreamChatTheme.of(context).textTheme.footnote.copyWith( @@ -456,7 +454,7 @@ class MessageInputState extends State { Widget _buildExpandActionsButton() { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 8), child: AnimatedCrossFade( crossFadeState: _actionsShrunk ? CrossFadeState.showFirst @@ -464,7 +462,6 @@ class MessageInputState extends State { firstChild: IconButton( onPressed: () => setState(() => _actionsShrunk = false), icon: Transform.rotate( - alignment: Alignment.center, angle: (widget.actionsLocation == ActionsLocation.right || widget.actionsLocation == ActionsLocation.rightInside) ? pi @@ -484,7 +481,6 @@ class MessageInputState extends State { ), secondChild: FittedBox( child: Row( - crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ if (!widget.disableAttachments) _buildAttachmentButton(), @@ -510,10 +506,10 @@ class MessageInputState extends State { Expanded _buildTextInput(BuildContext context) { final theme = StreamChatTheme.of(context); final margin = (widget.sendButtonLocation == SendButtonLocation.inside - ? const EdgeInsets.only(right: 8.0) + ? const EdgeInsets.only(right: 8) : EdgeInsets.zero) + (widget.actionsLocation != ActionsLocation.left - ? const EdgeInsets.only(left: 8.0) + ? const EdgeInsets.only(left: 8) : EdgeInsets.zero); return Expanded( child: Center( @@ -545,7 +541,6 @@ class MessageInputState extends State { child: TextField( key: Key('messageInputText'), enabled: _inputEnabled, - minLines: null, maxLines: null, onSubmitted: (_) => sendMessage(), keyboardType: widget.keyboardType, @@ -607,7 +602,7 @@ class MessageInputState extends State { mainAxisSize: MainAxisSize.min, children: [ Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Container( constraints: BoxConstraints.tight(Size(64, 24)), decoration: BoxDecoration( @@ -620,7 +615,7 @@ class MessageInputState extends State { children: [ StreamSvgIcon.lightning( color: Colors.white, - size: 16.0, + size: 16, ), Text( _chosenCommand?.name.toUpperCase() ?? '', @@ -640,7 +635,6 @@ class MessageInputState extends State { : (widget.actionsLocation == ActionsLocation.leftInside ? Row( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, children: [ _buildExpandActionsButton(), ], @@ -650,11 +644,10 @@ class MessageInputState extends State { prefixIconConstraints: BoxConstraints.tightFor(height: 40), suffixIcon: Row( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, children: [ if (_commandEnabled) Padding( - padding: const EdgeInsets.only(right: 8.0), + padding: const EdgeInsets.only(right: 8), child: IconButton( icon: StreamSvgIcon.closeSmall(), splashRadius: 24, @@ -776,7 +769,7 @@ class MessageInputState extends State { void _checkCommands(String s, BuildContext context) { if (s.startsWith('/')) { - var matchedCommandsList = StreamChannel.of(context) + final matchedCommandsList = StreamChannel.of(context) .channel .config ?.commands @@ -825,19 +818,18 @@ class MessageInputState extends State { left: 0, right: 0, child: TweenAnimationBuilder( - tween: Tween(begin: 0.0, end: 1.0), + tween: Tween(begin: 0, end: 1), duration: Duration(milliseconds: 300), curve: Curves.easeInOutExpo, builder: (context, val, wid) { return Transform.scale( - alignment: Alignment.center, scale: val, child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Card( - elevation: 2.0, + elevation: 2, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), + borderRadius: BorderRadius.circular(8), ), color: StreamChatTheme.of(context).colorTheme.white, clipBehavior: Clip.antiAlias, @@ -845,20 +837,19 @@ class MessageInputState extends State { constraints: BoxConstraints.loose(Size.fromHeight(400)), decoration: BoxDecoration( color: StreamChatTheme.of(context).colorTheme.white, - borderRadius: BorderRadius.circular(8.0)), + borderRadius: BorderRadius.circular(8)), child: ListView( padding: const EdgeInsets.all(0), shrinkWrap: true, children: [ if (commands.isNotEmpty) Padding( - padding: - const EdgeInsets.only(left: 0.0, top: 8.0), + padding: const EdgeInsets.only(top: 8), child: Row( children: [ Padding( padding: const EdgeInsets.symmetric( - horizontal: 8.0, + horizontal: 8, ), child: StreamSvgIcon.lightning( color: StreamChatTheme.of(context) @@ -879,7 +870,7 @@ class MessageInputState extends State { ), ), SizedBox( - height: 10.0, + height: 10, ), ...commands .map( @@ -888,19 +879,19 @@ class MessageInputState extends State { _setCommand(c); }, child: Container( - height: 40.0, + height: 40, child: Row( children: [ SizedBox( - width: 16.0, + width: 16, ), _buildCommandIcon(c.name), SizedBox( - width: 8.0, + width: 8, ), Text.rich( TextSpan( - text: '${c.name.capitalize()}', + text: c.name.capitalize(), style: TextStyle( fontWeight: FontWeight.bold), children: [ @@ -987,10 +978,8 @@ class MessageInputState extends State { mainAxisSize: MainAxisSize.min, children: [ Row( - mainAxisAlignment: MainAxisAlignment.start, children: [ IconButton( - iconSize: 24, icon: StreamSvgIcon.pictures( color: _getIconColor(0), ), @@ -1010,11 +999,10 @@ class MessageInputState extends State { onPressed: !_attachmentContainsFile && _attachments.isNotEmpty ? null : () { - pickFile(DefaultAttachmentTypes.file, false); + pickFile(DefaultAttachmentTypes.file); }, ), IconButton( - iconSize: 24, icon: StreamSvgIcon.camera( color: _getIconColor(2), ), @@ -1026,7 +1014,6 @@ class MessageInputState extends State { ), IconButton( padding: const EdgeInsets.all(0), - iconSize: 24, icon: StreamSvgIcon.record( color: _getIconColor(3), ), @@ -1052,22 +1039,22 @@ class MessageInputState extends State { decoration: BoxDecoration( color: StreamChatTheme.of(context).colorTheme.white, borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), + topLeft: Radius.circular(16), + topRight: Radius.circular(16), ), ), child: Container( width: double.infinity, child: Center( child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Container( - width: 40.0, - height: 4.0, + width: 40, + height: 4, decoration: BoxDecoration( color: StreamChatTheme.of(context).colorTheme.whiteSmoke, - borderRadius: BorderRadius.circular(4.0), + borderRadius: BorderRadius.circular(4), ), ), ), @@ -1080,7 +1067,7 @@ class MessageInputState extends State { child: Container( decoration: BoxDecoration( color: StreamChatTheme.of(context).colorTheme.white, - borderRadius: BorderRadius.circular(8.0), + borderRadius: BorderRadius.circular(8), ), child: _PickerWidget( filePickerIndex: _filePickerIndex, @@ -1159,7 +1146,7 @@ class MessageInputState extends State { return CircleAvatar( radius: 12, child: StreamSvgIcon.giphyIcon( - size: 24.0, + size: 24, ), ); case 'ban': @@ -1167,7 +1154,7 @@ class MessageInputState extends State { backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.iconUserDelete( - size: 16.0, + size: 16, color: Colors.white, ), ); @@ -1176,7 +1163,7 @@ class MessageInputState extends State { backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.flag( - size: 14.0, + size: 14, color: Colors.white, ), ); @@ -1186,7 +1173,7 @@ class MessageInputState extends State { radius: 12, child: ClipOval( child: StreamSvgIcon.imgur( - size: 24.0, + size: 24, ), ), ); @@ -1195,7 +1182,7 @@ class MessageInputState extends State { backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.mute( - size: 16.0, + size: 16, color: Colors.white, ), ); @@ -1204,7 +1191,7 @@ class MessageInputState extends State { backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.userAdd( - size: 16.0, + size: 16, color: Colors.white, ), ); @@ -1213,7 +1200,7 @@ class MessageInputState extends State { backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.volumeUp( - size: 16.0, + size: 16, color: Colors.white, ), ); @@ -1222,7 +1209,7 @@ class MessageInputState extends State { backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.lightning( - size: 16.0, + size: 16, color: Colors.white, ), ); @@ -1263,18 +1250,18 @@ class MessageInputState extends State { left: 0, right: 0, child: TweenAnimationBuilder( - tween: Tween(begin: 0.0, end: 1.0), + tween: Tween(begin: 0, end: 1), duration: Duration(milliseconds: 300), curve: Curves.easeInOutExpo, builder: (context, val, wid) { return Transform.scale( scale: val, child: Card( - margin: EdgeInsets.all(8.0), - elevation: 2.0, + margin: EdgeInsets.all(8), + elevation: 2, color: StreamChatTheme.of(context).colorTheme.white, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), + borderRadius: BorderRadius.circular(8), ), clipBehavior: Clip.antiAlias, child: Container( @@ -1291,7 +1278,7 @@ class MessageInputState extends State { shrinkWrap: true, children: [ SizedBox( - height: 8.0, + height: 8, ), ...snapshot.data! .where((it) => it.user != null) @@ -1333,7 +1320,7 @@ class MessageInputState extends State { }, ).toList(), SizedBox( - height: 8.0, + height: 8, ), ], ); @@ -1361,7 +1348,7 @@ class MessageInputState extends State { final emojis = _emojiNames .where((e) => e.contains(query)) - .map((e) => Emoji.byName(e)) + .map(Emoji.byName) .where((e) => e != null); if (emojis.isEmpty) { @@ -1377,11 +1364,11 @@ class MessageInputState extends State { left: 0, right: 0, child: Card( - margin: EdgeInsets.all(8.0), - elevation: 2.0, + margin: EdgeInsets.all(8), + elevation: 2, color: StreamChatTheme.of(context).colorTheme.white, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), + borderRadius: BorderRadius.circular(8), ), clipBehavior: Clip.antiAlias, child: Container( @@ -1390,7 +1377,7 @@ class MessageInputState extends State { boxShadow: [ BoxShadow( spreadRadius: -8, - blurRadius: 5.0, + blurRadius: 5, offset: Offset(0, -4), ), ], @@ -1403,12 +1390,11 @@ class MessageInputState extends State { itemBuilder: (context, i) { if (i == 0) { return Padding( - padding: const EdgeInsets.only(left: 8.0, top: 8.0), + padding: const EdgeInsets.only(left: 8, top: 8), child: Row( children: [ Padding( - padding: - const EdgeInsets.symmetric(horizontal: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 8), child: StreamSvgIcon.smile( color: StreamChatTheme.of(context) .colorTheme @@ -1516,7 +1502,7 @@ class MessageInputState extends State { Padding( padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), child: LimitedBox( - maxHeight: 136.0, + maxHeight: 136, child: ListView( reverse: true, shrinkWrap: true, @@ -1524,16 +1510,15 @@ class MessageInputState extends State { .map( (e) => ClipRRect( borderRadius: BorderRadius.circular(10), - clipBehavior: Clip.antiAlias, child: FileAttachment( message: Message(), // dummy message attachment: e, size: Size( MediaQuery.of(context).size.width * 0.65, - 56.0, + 56, ), trailing: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: _buildRemoveButton(e), ), ), @@ -1547,18 +1532,17 @@ class MessageInputState extends State { Padding( padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), child: LimitedBox( - maxHeight: 104.0, + maxHeight: 104, child: ListView( scrollDirection: Axis.horizontal, children: remainingAttachments .map( (attachment) => ClipRRect( borderRadius: BorderRadius.circular(10), - clipBehavior: Clip.antiAlias, child: Stack( children: [ AspectRatio( - aspectRatio: 1.0, + aspectRatio: 1, child: Container( height: 104, width: 104, @@ -1593,7 +1577,6 @@ class MessageInputState extends State { elevation: 0, highlightElevation: 0, focusElevation: 0, - disabledElevation: 0, hoverElevation: 0, onPressed: () { setState(() => _attachments.remove(attachment.id)); @@ -1803,7 +1786,7 @@ class MessageInputState extends State { leading: Icon(Icons.image), title: Text('Upload a photo'), onTap: () { - pickFile(DefaultAttachmentTypes.image, false); + pickFile(DefaultAttachmentTypes.image); Navigator.pop(context); }, ), @@ -1811,7 +1794,7 @@ class MessageInputState extends State { leading: Icon(Icons.video_library), title: Text('Upload a video'), onTap: () { - pickFile(DefaultAttachmentTypes.video, false); + pickFile(DefaultAttachmentTypes.video); Navigator.pop(context); }, ), @@ -1837,7 +1820,7 @@ class MessageInputState extends State { leading: Icon(Icons.insert_drive_file), title: Text('Upload a file'), onTap: () { - pickFile(DefaultAttachmentTypes.file, false); + pickFile(DefaultAttachmentTypes.file); Navigator.pop(context); }, ), @@ -1967,7 +1950,7 @@ class MessageInputState extends State { Widget _buildIdleSendButton(BuildContext context) { return Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: StreamSvgIcon( assetName: _getIdleSendIcon(), color: @@ -1978,7 +1961,7 @@ class MessageInputState extends State { Widget _buildSendButton(BuildContext context) { return Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: IconButton( onPressed: sendMessage, padding: const EdgeInsets.all(0), @@ -2112,44 +2095,44 @@ class MessageInputState extends State { context: context, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), + topLeft: Radius.circular(16), + topRight: Radius.circular(16), )), builder: (context) { return Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( - height: 26.0, + height: 26, ), StreamSvgIcon.error( color: StreamChatTheme.of(context).colorTheme.accentRed, - size: 24.0, + size: 24, ), SizedBox( - height: 26.0, + height: 26, ), Text( 'Something went wrong', style: StreamChatTheme.of(context).textTheme.headlineBold, ), SizedBox( - height: 7.0, + height: 7, ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( description, textAlign: TextAlign.center, ), ), SizedBox( - height: 36.0, + height: 36, ), Container( color: StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), - height: 1.0, + height: 1, ), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -2345,7 +2328,7 @@ class __PickerWidgetState extends State<_PickerWidget> { color: StreamChatTheme.of(context).colorTheme.grey), textAlign: TextAlign.center, ), - SizedBox(height: 6.0), + SizedBox(height: 6), Center( child: Text( 'Allow access to your gallery', 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 01c067da..5b3b0359 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -111,7 +111,7 @@ class MessageDetails { /// Modify it to change the widget appearance. class MessageListView extends StatefulWidget { /// Instantiate a new MessageListView - MessageListView({ + const MessageListView({ Key? key, this.showScrollToBottom = true, this.messageBuilder, @@ -418,7 +418,6 @@ class _MessageListViewState extends State { child: ScrollablePositionedList.separated( key: ValueKey(initialIndex! + initialAlignment!), itemPositionsListener: _itemPositionListener, - addAutomaticKeepAlives: true, initialScrollIndex: initialIndex ?? 0, initialAlignment: initialAlignment ?? 0, physics: widget.scrollPhysics, @@ -437,7 +436,7 @@ class _MessageListViewState extends State { StreamChatTheme.of(context).colorTheme.bgGradient, ), child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Text( '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', textAlign: TextAlign.center, @@ -464,7 +463,7 @@ class _MessageListViewState extends State { dateTime: nextMessage.createdAt.toLocal(), ); return Padding( - padding: const EdgeInsets.symmetric(vertical: 12.0), + padding: const EdgeInsets.symmetric(vertical: 12), child: divider, ); } @@ -554,7 +553,7 @@ class _MessageListViewState extends State { ), if (widget.showScrollToBottom) _buildScrollToBottom(), Positioned( - top: 20.0, + top: 20, child: ValueListenableBuilder>( valueListenable: _itemPositionListener.itemPositions, builder: (context, values, _) { @@ -660,7 +659,7 @@ class _MessageListViewState extends State { top: -10, child: CircleAvatar( child: Padding( - padding: const EdgeInsets.all(3.0), + padding: const EdgeInsets.all(3), child: Text( '$unreadCount', style: TextStyle( @@ -712,7 +711,7 @@ class _MessageListViewState extends State { } return Center( child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: const CircularProgressIndicator(), ), ); @@ -799,8 +798,6 @@ class _MessageListViewState extends State { final isOnlyEmoji = message.text!.isOnlyEmoji; return MessageWidget( - showThreadReplyIndicator: false, - showInChannelIndicator: false, showReplyMessage: false, showResendMessage: false, showThreadReplyMessage: false, @@ -810,7 +807,7 @@ class _MessageListViewState extends State { message: message, reverse: isMyMessage, showUsername: !isMyMessage, - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), showSendingIndicator: false, onThreadTap: _onThreadTap as void Function(Message)?, borderRadiusGeometry: BorderRadius.only( @@ -820,7 +817,7 @@ class _MessageListViewState extends State { bottomRight: Radius.circular(16), ), textPadding: EdgeInsets.symmetric( - vertical: 8.0, + vertical: 8, horizontal: isOnlyEmoji ? 0 : 16.0, ), borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null, @@ -940,7 +937,7 @@ class _MessageListViewState extends State { message: message, reverse: isMyMessage, showReactions: !message.isDeleted, - padding: const EdgeInsets.symmetric(horizontal: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 8), showInChannelIndicator: showInChannelIndicator, showThreadReplyIndicator: showThreadReplyIndicator, showUsername: showUsername, @@ -997,7 +994,7 @@ class _MessageListViewState extends State { bottomRight: Radius.circular(16), ), textPadding: EdgeInsets.symmetric( - vertical: 8.0, + vertical: 8, horizontal: isOnlyEmoji ? 0 : 16.0, ), messageTheme: isMyMessage @@ -1069,7 +1066,7 @@ class _MessageListViewState extends State { ); }, child: Padding( - padding: const EdgeInsets.only(top: 4.0), + padding: const EdgeInsets.only(top: 4), child: child, ), ); 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 9fa35cb6..937cf681 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -59,7 +59,7 @@ class MessageReactionsModal extends StatelessWidget { : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); return TweenAnimationBuilder( - tween: Tween(begin: 0.0, end: 1.0), + tween: Tween(begin: 0, end: 1), duration: Duration(milliseconds: 300), curve: Curves.easeInOutBack, builder: (context, val, snapshot) { @@ -86,7 +86,7 @@ class MessageReactionsModal extends StatelessWidget { child: Center( child: SingleChildScrollView( child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -102,7 +102,7 @@ class MessageReactionsModal extends StatelessWidget { : (divFactor > 1.0 ? 0.0 : -(1.0 - divFactor)), - 0.0), + 0), child: ReactionPicker( message: message, ), @@ -121,7 +121,6 @@ class MessageReactionsModal extends StatelessWidget { showReactions: false, showUsername: false, showUserAvatar: showUserAvatar, - showThreadReplyIndicator: false, showTimestamp: false, translateUserAvatar: false, showSendingIndicator: false, @@ -133,9 +132,8 @@ class MessageReactionsModal extends StatelessWidget { attachmentPadding: EdgeInsets.all( hasFileAttachment ? 4 : 2, ), - showInChannelIndicator: false, textPadding: EdgeInsets.symmetric( - vertical: 8.0, + vertical: 8, horizontal: message.text!.isOnlyEmoji ? 0 : 16.0, ), @@ -184,7 +182,6 @@ class MessageReactionsModal extends StatelessWidget { child: Wrap( spacing: 16, runSpacing: 16, - alignment: WrapAlignment.start, children: message.latestReactions! .map((e) => _buildReaction( e, @@ -214,8 +211,6 @@ class MessageReactionsModal extends StatelessWidget { )), child: Column( mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, children: [ Stack( clipBehavior: Clip.none, 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 87b55aaa..7be8893b 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_item.dart @@ -147,8 +147,8 @@ class MessageSearchItem extends StatelessWidget { List attachments, TextStyle? normalTextStyle, TextStyle? mentionsTextStyle) { - var textList = text.split(' '); - var resList = []; + final textList = text.split(' '); + final resList = []; for (var e in textList) { if (mentions.isNotEmpty && mentions.any((element) => '@${element.name}' == e)) { @@ -166,7 +166,7 @@ class MessageSearchItem extends StatelessWidget { )); } else { resList.add(TextSpan( - text: e == textList.last ? '$e' : '$e ', + text: e == textList.last ? e : '$e ', style: normalTextStyle, )); } 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 358623e1..b88af62e 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 @@ -189,10 +189,7 @@ class _MessageSearchListViewState extends State { }, ); }, - childBuilder: widget.childBuilder ?? - (list) { - return _buildListView(list); - }, + childBuilder: widget.childBuilder ?? _buildListView, ); } @@ -228,7 +225,7 @@ class _MessageSearchListViewState extends State { .accentRed .withOpacity(.2), child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), + padding: const EdgeInsets.symmetric(vertical: 16), child: Center( child: Text('Error loading messages'), ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index b05762ba..32801cd1 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -201,8 +201,8 @@ class MessageWidget extends StatefulWidget { this.readList, this.padding, this.textPadding = const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, + horizontal: 16, + vertical: 8, ), this.attachmentPadding = EdgeInsets.zero, this.allRead = false, @@ -211,8 +211,7 @@ class MessageWidget extends StatefulWidget { this.onAttachmentTap, }) : attachmentBuilders = { 'image': (context, message, attachments) { - var border = RoundedRectangleBorder( - side: BorderSide.none, + final border = RoundedRectangleBorder( borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, ); @@ -267,8 +266,7 @@ class MessageWidget extends StatefulWidget { ); }, 'video': (context, message, attachments) { - var border = RoundedRectangleBorder( - side: BorderSide.none, + final border = RoundedRectangleBorder( borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, ); @@ -302,7 +300,6 @@ class MessageWidget extends StatefulWidget { }, 'giphy': (context, message, attachments) { final border = RoundedRectangleBorder( - side: BorderSide.none, borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, ); @@ -329,7 +326,7 @@ class MessageWidget extends StatefulWidget { ); }, 'file': (context, message, attachments) { - var border = RoundedRectangleBorder( + final border = RoundedRectangleBorder( side: attachmentBorderSide ?? BorderSide( color: StreamChatTheme.of(context).colorTheme.greyWhisper, @@ -426,7 +423,7 @@ class _MessageWidgetState extends State super.build(context); final avatarWidth = widget.messageTheme.avatarTheme?.constraints.maxWidth ?? 40; - var leftPadding = + final leftPadding = widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; return Material( @@ -460,7 +457,6 @@ class _MessageWidgetState extends State mainAxisSize: MainAxisSize.min, children: [ Row( - mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: [ @@ -481,8 +477,8 @@ class _MessageWidgetState extends State BoxConstraints(maxWidth: 22 * 6.0), child: _buildReactionIndicator(context), ), - portalAnchor: Alignment(-1.0, -1.0), - childAnchor: Alignment(1, -1.0), + portalAnchor: Alignment(-1, -1), + childAnchor: Alignment(1, -1), child: Stack( clipBehavior: Clip.none, children: [ @@ -526,7 +522,7 @@ class _MessageWidgetState extends State ) : Card( clipBehavior: Clip.antiAlias, - elevation: 0.0, + elevation: 0, margin: EdgeInsets.symmetric( horizontal: (isFailedState ? 15.0 @@ -650,9 +646,9 @@ class _MessageWidgetState extends State children: [ StreamSvgIcon.eye( color: StreamChatTheme.of(context).colorTheme.grey, - size: 16.0, + size: 16, ), - SizedBox(width: 8.0), + SizedBox(width: 8), Text( 'Only visible to you', style: StreamChatTheme.of(context) @@ -665,7 +661,7 @@ class _MessageWidgetState extends State ); } - var children = []; + final children = []; final threadParticipants = widget.message.threadParticipants?.take(2); final showThreadParticipants = threadParticipants?.isNotEmpty == true; @@ -757,18 +753,18 @@ class _MessageWidgetState extends State return mappedChild; }, ), - ].insertBetween(const SizedBox(width: 8.0)), + ].insertBetween(const SizedBox(width: 8)), ); } Widget _buildUrlAttachment() { - var urlAttachment = widget.message.attachments + final urlAttachment = widget.message.attachments .firstWhere((element) => element.ogScrapeUrl != null); - var host = Uri.parse(urlAttachment.ogScrapeUrl!).host; - var splitList = host.split('.'); - var hostName = splitList.length == 3 ? splitList[1] : splitList[0]; - var hostDisplayName = urlAttachment.authorName?.capitalize() ?? + final host = Uri.parse(urlAttachment.ogScrapeUrl!).host; + final splitList = host.split('.'); + final hostName = splitList.length == 3 ? splitList[1] : splitList[0]; + final hostDisplayName = urlAttachment.authorName?.capitalize() ?? getWebsiteName(hostName.toLowerCase()) ?? hostName.capitalize(); diff --git a/packages/stream_chat_flutter/lib/src/option_list_tile.dart b/packages/stream_chat_flutter/lib/src/option_list_tile.dart index 03af584b..a4463389 100644 --- a/packages/stream_chat_flutter/lib/src/option_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/option_list_tile.dart @@ -11,7 +11,7 @@ class OptionListTile extends StatelessWidget { final Color? separatorColor; final TextStyle? titleTextStyle; - OptionListTile({ + const OptionListTile({ this.title, this.leading, this.trailing, @@ -29,12 +29,12 @@ class OptionListTile extends StatelessWidget { Container( color: separatorColor ?? StreamChatTheme.of(context).colorTheme.greyGainsboro, - height: 1.0, + height: 1, ), Material( color: tileColor ?? StreamChatTheme.of(context).colorTheme.white, child: Container( - height: 63.0, + height: 63, child: InkWell( onTap: onTap, child: Row( @@ -42,7 +42,7 @@ class OptionListTile extends StatelessWidget { if (leading != null) Center(child: leading), if (leading == null) SizedBox( - width: 16.0, + width: 16, ), Expanded( flex: 4, @@ -62,7 +62,7 @@ class OptionListTile extends StatelessWidget { Expanded( flex: 2, child: Padding( - padding: const EdgeInsets.only(right: 16.0), + padding: const EdgeInsets.only(right: 16), child: Align( alignment: Alignment.centerRight, child: trailing ?? Container(), 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 43b198eb..9d489dab 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -87,7 +87,7 @@ class QuotedMessageWidget extends StatelessWidget { final GestureTapCallback? onTap; /// - QuotedMessageWidget({ + const QuotedMessageWidget({ Key? key, required this.message, required this.messageTheme, @@ -237,7 +237,7 @@ class QuotedMessageWidget extends StatelessWidget { ShapeBorder _getDefaultShape(BuildContext context) { return RoundedRectangleBorder( - side: BorderSide(width: 0.0, color: Colors.transparent), + side: BorderSide(width: 0, color: Colors.transparent), borderRadius: BorderRadius.circular(8), ); } diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index bcf3127c..d71c395a 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -40,7 +40,6 @@ class ReactionBubble extends StatelessWidget { alignment: Alignment.center, child: Stack( alignment: Alignment.center, - fit: StackFit.loose, children: [ Transform.translate( offset: Offset(reverse ? offset : -offset, 0), @@ -115,7 +114,7 @@ class ReactionBubble extends StatelessWidget { return Padding( padding: const EdgeInsets.symmetric( - horizontal: 4.0, + horizontal: 4, ), child: reactionIcon != null ? StreamSvgIcon( @@ -239,9 +238,9 @@ class ReactionBubblePainter extends CustomPainter { ..strokeWidth = 1 ..style = PaintingStyle.stroke; - final dy = -2.2; - final startAngle = 1.1; - final sweepAngle = 1.2; + const dy = -2.2; + const startAngle = 1.1; + const sweepAngle = 1.2; final path = Path(); path.addArc( Rect.fromCircle( @@ -259,9 +258,9 @@ class ReactionBubblePainter extends CustomPainter { ..color = color ..strokeWidth = 1; - final dy = -2.2; - final startAngle = 1; - final sweepAngle = 1.3; + const dy = -2.2; + const startAngle = 1; + const sweepAngle = 1.3; final path = Path(); path.addArc( Rect.fromCircle( @@ -280,9 +279,9 @@ class ReactionBubblePainter extends CustomPainter { ..strokeWidth = 1 ..style = PaintingStyle.fill; - final dy = -2.2; - final startAngle = 1.1; - final sweepAngle = 1.2; + const dy = -2.2; + const startAngle = 1.1; + const sweepAngle = 1.2; final path = Path(); path.addArc( Rect.fromCircle( diff --git a/packages/stream_chat_flutter/lib/src/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reaction_picker.dart index cd358b2a..aaa2c040 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_picker.dart @@ -49,7 +49,7 @@ class _ReactionPickerState extends State } return TweenAnimationBuilder( - tween: Tween(begin: 0.0, end: 1.0), + tween: Tween(begin: 0, end: 1), curve: Curves.easeInOutBack, duration: Duration(milliseconds: 500), builder: (context, val, wid) { @@ -61,8 +61,8 @@ class _ReactionPickerState extends State clipBehavior: Clip.hardEdge, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, + horizontal: 16, + vertical: 8, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -74,7 +74,7 @@ class _ReactionPickerState extends State ?.indexWhere((reaction) => reaction.type == reactionIcon.type) ?? -1; - var index = reactionIcons.indexOf(reactionIcon); + final index = reactionIcons.indexOf(reactionIcon); return ConstrainedBox( constraints: BoxConstraints.tightFor( @@ -83,8 +83,6 @@ class _ReactionPickerState extends State ), child: RawMaterialButton( elevation: 0, - padding: const EdgeInsets.all(0), - clipBehavior: Clip.none, shape: ContinuousRectangleBorder( borderRadius: BorderRadius.circular(16), ), @@ -110,7 +108,6 @@ class _ReactionPickerState extends State animation: animations[index], builder: (context, val) { return Transform.scale( - alignment: Alignment.center, scale: animations[index].value, child: StreamSvgIcon( assetName: reactionIcon.assetName, diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index f5461895..2c22dde3 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -43,7 +43,7 @@ class StreamChat extends StatefulWidget { /// upon the [Event.type] final EventHandler? onBackgroundEventReceived; - StreamChat({ + const StreamChat({ Key? key, required this.client, required this.child, 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 9b28d1f6..1e1eb089 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -13,7 +13,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class StreamChatTheme extends InheritedWidget { final StreamChatThemeData data; - StreamChatTheme({ + const StreamChatTheme({ Key? key, required this.data, required Widget child, @@ -237,7 +237,7 @@ class StreamChatThemeData { lastMessageAt: textTheme.footnote.copyWith( color: colorTheme.black.withOpacity(.5), ), - indicatorIconSize: 16.0), + indicatorIconSize: 16), channelListHeaderTheme: ChannelListHeaderTheme( avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), @@ -543,17 +543,13 @@ class ColorTheme { stops: [0, 1], ), this.borderTop = const Effect( - sigmaX: 0, - sigmaY: -1, - color: Color(0xff000000), - blur: 0.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.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.0), + 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.0), + sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8), }) : brightness = Brightness.light; ColorTheme.dark({ @@ -572,28 +568,28 @@ class ColorTheme { sigmaX: 0, sigmaY: -1, color: Color(0xff141924), - blur: 0.0, + blur: 0, ), this.borderBottom = const Effect( sigmaX: 0, sigmaY: 1, color: Color(0xff141924), - blur: 0.0, - alpha: 1.0, + blur: 0, + alpha: 1, ), this.shadowIconButton = const Effect( sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, - blur: 4.0, + blur: 4, ), this.modalShadow = const Effect( sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, - blur: 8.0, + blur: 8, ), this.highlight = const Color(0xff302d22), this.overlay = const Color.fromRGBO(0, 0, 0, 0.4), diff --git a/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart b/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart index 95b3c70e..157d2940 100644 --- a/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart +++ b/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart @@ -13,7 +13,7 @@ class StreamNeumorphicButton extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - margin: EdgeInsets.all(8.0), + margin: EdgeInsets.all(8), height: 40, width: 40, decoration: BoxDecoration( @@ -22,15 +22,12 @@ class StreamNeumorphicButton extends StatelessWidget { boxShadow: [ BoxShadow( color: Colors.grey.shade700, - offset: Offset(0, 1.0), + offset: Offset(0, 1), blurRadius: 0.5, - spreadRadius: 0, ), BoxShadow( color: Colors.white, - offset: Offset.zero, blurRadius: 0.5, - spreadRadius: 0, ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart index 8dc38ba2..f115617c 100644 --- a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart +++ b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart @@ -25,7 +25,6 @@ class StreamSvgIcon extends StatelessWidget { width: width, height: height, color: color, - alignment: Alignment.center, ); } diff --git a/packages/stream_chat_flutter/lib/src/swipeable.dart b/packages/stream_chat_flutter/lib/src/swipeable.dart index 90146e4c..3d195e4f 100644 --- a/packages/stream_chat_flutter/lib/src/swipeable.dart +++ b/packages/stream_chat_flutter/lib/src/swipeable.dart @@ -28,7 +28,7 @@ class Swipeable extends StatefulWidget { } class _SwipeableState extends State with TickerProviderStateMixin { - double _dragExtent = 0.0; + double _dragExtent = 0; late AnimationController _moveController; late AnimationController _iconMoveController; late Animation _moveAnimation; @@ -45,15 +45,15 @@ class _SwipeableState extends State with TickerProviderStateMixin { AnimationController(duration: _animationDuration, vsync: this); _iconMoveController = AnimationController(duration: _animationDuration, vsync: this); - _moveAnimation = Tween(begin: Offset.zero, end: Offset(1.0, 0.0)) + _moveAnimation = Tween(begin: Offset.zero, end: Offset(1, 0)) .animate(_moveController); _iconTransitionAnimation = - Tween(begin: Offset(-0.1, 0.0), end: Offset(0.4, 0.0)) + Tween(begin: Offset(-0.1, 0), end: Offset(0.4, 0)) .animate(_moveController); _iconFadeAnimation = - Tween(begin: 0.7, end: 1.0).animate(_iconMoveController); + Tween(begin: 0.7, end: 1).animate(_iconMoveController); - final controllerValue = 0.0; + const controllerValue = 0.0; _moveController.animateTo(controllerValue); _iconMoveController.animateTo(controllerValue); } @@ -77,19 +77,19 @@ class _SwipeableState extends State with TickerProviderStateMixin { if (_dragExtent.isNegative) return; - var movePastThresholdPixels = widget.threshold; + final movePastThresholdPixels = widget.threshold; var newPos = _dragExtent.abs() / context.size!.width; if (_dragExtent.abs() > movePastThresholdPixels) { // how many "thresholds" past the threshold we are. 1 = the threshold 2 // = two thresholds. - var n = _dragExtent.abs() / movePastThresholdPixels; + final n = _dragExtent.abs() / movePastThresholdPixels; // Take the number of thresholds past the threshold, and reduce this // number - var reducedThreshold = math.pow(n, 0.3); + final reducedThreshold = math.pow(n, 0.3); - var adjustedPixelPos = movePastThresholdPixels * reducedThreshold; + final adjustedPixelPos = movePastThresholdPixels * reducedThreshold; newPos = adjustedPixelPos / context.size!.width; if (_dragExtent > 0 && !_pastThreshold) { @@ -111,8 +111,8 @@ class _SwipeableState extends State with TickerProviderStateMixin { } void _handleDragEnd(DragEndDetails details) { - _moveController.animateTo(0.0, duration: _animationDuration); - _iconMoveController.animateTo(0.0, duration: _animationDuration); + _moveController.animateTo(0, duration: _animationDuration); + _iconMoveController.animateTo(0, duration: _animationDuration); _dragExtent = 0.0; if (_pastThreshold && widget.onSwipeEnd != null) { widget.onSwipeEnd!(); diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart index 9dc8f98c..0157791e 100644 --- a/packages/stream_chat_flutter/lib/src/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/thread_header.dart @@ -118,7 +118,6 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { child: Container( height: preferredSize.height, child: Column( - crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ title ?? @@ -133,7 +132,6 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { subtitle ?? Row( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( diff --git a/packages/stream_chat_flutter/lib/src/unread_indicator.dart b/packages/stream_chat_flutter/lib/src/unread_indicator.dart index d55cd4f1..8d2c996a 100644 --- a/packages/stream_chat_flutter/lib/src/unread_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/unread_indicator.dart @@ -33,8 +33,8 @@ class UnreadIndicator extends StatelessWidget { .unreadCounterColor, child: Padding( padding: const EdgeInsets.only( - left: 5.0, - right: 5.0, + left: 5, + right: 5, top: 2, bottom: 1, ), diff --git a/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart b/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart index e0e7e306..1175237e 100644 --- a/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart @@ -10,7 +10,7 @@ class UploadProgressIndicator extends StatelessWidget { final bool showBackground; final TextStyle? textStyle; - UploadProgressIndicator({ + const UploadProgressIndicator({ Key? key, required this.uploaded, required this.total, diff --git a/packages/stream_chat_flutter/lib/src/url_attachment.dart b/packages/stream_chat_flutter/lib/src/url_attachment.dart index e8e7b8a8..e924746d 100644 --- a/packages/stream_chat_flutter/lib/src/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/url_attachment.dart @@ -8,12 +8,12 @@ class UrlAttachment extends StatelessWidget { final String hostDisplayName; final EdgeInsets textPadding; - UrlAttachment({ + const UrlAttachment({ required this.urlAttachment, required this.hostDisplayName, this.textPadding = const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, + horizontal: 16, + vertical: 8, ), }); @@ -32,9 +32,9 @@ class UrlAttachment extends StatelessWidget { if (urlAttachment.imageUrl != null) Container( clipBehavior: Clip.antiAliasWithSaveLayer, - margin: EdgeInsets.symmetric(horizontal: 8.0), + margin: EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.0), + borderRadius: BorderRadius.circular(8), ), child: Stack( children: [ @@ -44,20 +44,20 @@ class UrlAttachment extends StatelessWidget { fit: BoxFit.cover, ), Positioned( - left: 0.0, + left: 0, bottom: -1, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.only( - topRight: Radius.circular(16.0), + topRight: Radius.circular(16), ), color: StreamChatTheme.of(context).colorTheme.blueAlice, ), child: Padding( padding: const EdgeInsets.only( - top: 8.0, - left: 8.0, - right: 8.0, + top: 8, + left: 8, + right: 8, ), child: Text( hostDisplayName, diff --git a/packages/stream_chat_flutter/lib/src/user_avatar.dart b/packages/stream_chat_flutter/lib/src/user_avatar.dart index 96753b99..0833b1a3 100644 --- a/packages/stream_chat_flutter/lib/src/user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/user_avatar.dart @@ -42,7 +42,6 @@ class UserAvatar extends StatelessWidget { Widget avatar = FittedBox( fit: BoxFit.cover, child: ClipRRect( - clipBehavior: Clip.antiAlias, borderRadius: borderRadius ?? streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius, child: Container( @@ -97,7 +96,7 @@ class UserAvatar extends StatelessWidget { type: MaterialType.circle, color: streamChatTheme.colorTheme.white, child: Container( - margin: const EdgeInsets.all(2.0), + margin: const EdgeInsets.all(2), constraints: onlineIndicatorConstraints ?? BoxConstraints.tightFor( width: 8, diff --git a/packages/stream_chat_flutter/lib/src/user_item.dart b/packages/stream_chat_flutter/lib/src/user_item.dart index 258855d8..ff51fea3 100644 --- a/packages/stream_chat_flutter/lib/src/user_item.dart +++ b/packages/stream_chat_flutter/lib/src/user_item.dart @@ -61,7 +61,6 @@ class UserItem extends StatelessWidget { }, leading: UserAvatar( user: user, - showOnlineStatus: true, onTap: (user) { if (onImageTap != null) { onImageTap!(user); 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 2f0e13b9..f1053e58 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -151,7 +151,7 @@ class _UserListViewState extends State @override Widget build(BuildContext context) { - var child = UserListCore( + final child = UserListCore( errorBuilder: widget.errorBuilder as Widget Function(Object)? ?? (err) { return _buildError(err as Error); @@ -225,7 +225,7 @@ class _UserListViewState extends State WidgetSpan( child: Padding( padding: const EdgeInsets.only( - right: 2.0, + right: 2, ), child: Icon(Icons.error_outline), ), @@ -237,7 +237,7 @@ class _UserListViewState extends State ), Padding( padding: const EdgeInsets.only( - top: 16.0, + top: 16, ), child: Text(message), ), @@ -313,7 +313,7 @@ class _UserListViewState extends State color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), child: Text( header, style: TextStyle( @@ -360,7 +360,6 @@ class _UserListViewState extends State ? widget.userItemBuilder!(context, user, selected) : Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, children: [ UserAvatar( user: user, @@ -414,7 +413,7 @@ class _UserListViewState extends State .accentRed .withOpacity(.2), child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), + padding: const EdgeInsets.symmetric(vertical: 16), child: Center( child: Text('Error loading users'), ), diff --git a/packages/stream_chat_flutter/lib/src/user_reaction_display.dart b/packages/stream_chat_flutter/lib/src/user_reaction_display.dart index 492baef5..fe61caad 100644 --- a/packages/stream_chat_flutter/lib/src/user_reaction_display.dart +++ b/packages/stream_chat_flutter/lib/src/user_reaction_display.dart @@ -23,7 +23,7 @@ class UserReactionDisplay extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: reactionToEmoji.keys.map((reactionType) { - var firstUserReaction = message.latestReactions! + final firstUserReaction = message.latestReactions! .firstWhere((element) => element.type == reactionType, orElse: () { return null; diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 79bd210e..4233dd2f 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -33,8 +33,8 @@ Future showConfirmationDialog( context: context, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), + topLeft: Radius.circular(16), + topRight: Radius.circular(16), )), builder: (context) { final effect = StreamChatTheme.of(context).colorTheme.borderTop; @@ -42,20 +42,20 @@ Future showConfirmationDialog( child: Column( mainAxisSize: MainAxisSize.min, children: [ - SizedBox(height: 26.0), + SizedBox(height: 26), if (icon != null) icon, - SizedBox(height: 26.0), + SizedBox(height: 26), Text( title, style: StreamChatTheme.of(context).textTheme.headlineBold, ), - SizedBox(height: 7.0), + SizedBox(height: 7), if (question != null) Text( question, textAlign: TextAlign.center, ), - SizedBox(height: 36.0), + SizedBox(height: 36), Container( color: effect.color!.withOpacity(effect.alpha ?? 1), height: 1, @@ -126,8 +126,8 @@ Future showInfoDialog( context: context, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), + topLeft: Radius.circular(16), + topRight: Radius.circular(16), )), builder: (context) { return SafeArea( @@ -135,11 +135,11 @@ Future showInfoDialog( mainAxisSize: MainAxisSize.min, children: [ SizedBox( - height: 26.0, + height: 26, ), if (icon != null) icon, SizedBox( - height: 26.0, + height: 26, ), Text( title, @@ -147,16 +147,16 @@ Future showInfoDialog( StreamChatTheme.of(context).textTheme.headlineBold, ), SizedBox( - height: 7.0, + height: 7, ), if (details != null) Text(details), SizedBox( - height: 36.0, + height: 36, ), Container( color: theme?.colorTheme.black.withOpacity(.08) ?? StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), - height: 1.0, + height: 1, ), Center( child: TextButton( @@ -236,7 +236,7 @@ String fileSize(dynamic size, [int round = 2]) { * the optional parameter [round] specifies the number * of digits after comma/point (default is 2) */ - final divider = 1024; + const divider = 1024; int _size; try { _size = int.parse(size.toString()); @@ -274,21 +274,21 @@ String fileSize(dynamic size, [int round = 2]) { if (_size < divider * divider * divider * divider * divider && _size % divider == 0) { - num r = _size / divider / divider / divider / divider; + final num r = _size / divider / divider / divider / divider; return '${r.toStringAsFixed(0)} TB'; } if (_size < divider * divider * divider * divider * divider) { - num r = _size / divider / divider / divider / divider; + final num r = _size / divider / divider / divider / divider; return '${r.toStringAsFixed(round)} TB'; } if (_size < divider * divider * divider * divider * divider * divider && _size % divider == 0) { - num r = _size / divider / divider / divider / divider / divider; + final num r = _size / divider / divider / divider / divider / divider; return '${r.toStringAsFixed(0)} PB'; } else { - num r = _size / divider / divider / divider / divider / divider; + final num r = _size / divider / divider / divider / divider / divider; return '${r.toStringAsFixed(round)} PB'; } } diff --git a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart index 762d4fe9..53516e9a 100644 --- a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart @@ -239,7 +239,7 @@ void main() { ), ); await tester.tap(find.text('Show in Chat')); - verify(() => onShowMessage.call()).called(1); + verify(onShowMessage.call).called(1); }, ); diff --git a/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart b/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart index b1eb70e2..e8dcdd53 100644 --- a/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart @@ -26,8 +26,8 @@ void main() { child: Container( child: FileAttachment( size: Size( - 300.0, - 300.0, + 300, + 300, ), message: Message(), attachment: Attachment( diff --git a/packages/stream_chat_flutter/test/src/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel_header_test.dart index c284cad5..4e99daf5 100644 --- a/packages/stream_chat_flutter/test/src/channel_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_header_test.dart @@ -318,7 +318,6 @@ void main() { body: ChannelHeader( showTypingIndicator: false, showBackButton: false, - showConnectionStateTile: false, ), ), ), diff --git a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart index fd5d31ce..10e0f45c 100644 --- a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart +++ b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart @@ -224,7 +224,6 @@ void main() { borderColor: Colors.red, backgroundColor: Colors.blue, maskColor: Colors.green, - highlightOwnReactions: true, reverse: true, flipTail: true, tailCirclesSpacing: 4, From f236d92840fbb6b1558bea34fd07df0602f8f4c5 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 4 May 2021 16:48:57 +0530 Subject: [PATCH 186/599] attachment lint --- .../attachment_upload_state_builder.dart | 136 +++++------ .../lib/src/attachment/attachment_widget.dart | 69 +++--- .../lib/src/attachment/file_attachment.dart | 146 ++++++------ .../lib/src/attachment/giphy_attachment.dart | 214 +++++++++--------- .../lib/src/attachment/image_attachment.dart | 31 ++- .../lib/src/attachment/video_attachment.dart | 25 +- .../lib/src/channel_info.dart | 2 +- 7 files changed, 324 insertions(+), 299 deletions(-) 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 e7abac74..825def58 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 @@ -10,13 +10,7 @@ typedef FailedBuilder = Widget Function(BuildContext, String); /// Widget to display attachment upload state class AttachmentUploadStateBuilder extends StatelessWidget { - final Message message; - final Attachment attachment; - final FailedBuilder? failedBuilder; - final WidgetBuilder? successBuilder; - final InProgressBuilder? inProgressBuilder; - final WidgetBuilder? preparingBuilder; - + /// Constructor for creating an [AttachmentUploadStateBuilder] widget const AttachmentUploadStateBuilder({ Key? key, required this.message, @@ -27,32 +21,46 @@ class AttachmentUploadStateBuilder extends StatelessWidget { this.preparingBuilder, }) : super(key: key); + /// Message which attachment is added to + final Message message; + + /// Attachment in concern + final Attachment attachment; + + /// Widget to display when failed + final FailedBuilder? failedBuilder; + + /// Widget to display when succeeded + final WidgetBuilder? successBuilder; + + /// Widget to display when in progress + final InProgressBuilder? inProgressBuilder; + + /// Widget to display when in prep + final WidgetBuilder? preparingBuilder; + @override Widget build(BuildContext context) { if (message.status == MessageSendingStatus.sent) { - return Offstage(); + return const Offstage(); } final messageId = message.id; final attachmentId = attachment.id; final inProgress = inProgressBuilder ?? - (context, int sent, int total) { - return _InProgressState( - sent: sent, - total: total, - attachmentId: attachmentId, - ); - }; + (context, int sent, int total) => _InProgressState( + sent: sent, + total: total, + attachmentId: attachmentId, + ); final failed = failedBuilder ?? - (context, error) { - return _FailedState( - error: error, - messageId: messageId, - attachmentId: attachmentId, - ); - }; + (context, error) => _FailedState( + error: error, + messageId: messageId, + attachmentId: attachmentId, + ); final success = successBuilder ?? (context) => _SuccessState(); @@ -69,11 +77,6 @@ class AttachmentUploadStateBuilder extends StatelessWidget { } class _IconButton extends StatelessWidget { - final Widget? icon; - final double iconSize; - final VoidCallback? onPressed; - final Color? fillColor; - const _IconButton({ Key? key, this.icon, @@ -82,36 +85,39 @@ class _IconButton extends StatelessWidget { this.fillColor, }) : super(key: key); + final Widget? icon; + final double iconSize; + final VoidCallback? onPressed; + final Color? fillColor; + @override - Widget build(BuildContext context) { - return Container( - height: iconSize, - width: iconSize, - child: RawMaterialButton( - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - hoverElevation: 0, - onPressed: onPressed, - fillColor: - fillColor ?? StreamChatTheme.of(context).colorTheme.overlayDark, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + Widget build(BuildContext context) => SizedBox( + height: iconSize, + width: iconSize, + child: RawMaterialButton( + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + hoverElevation: 0, + onPressed: onPressed, + fillColor: + fillColor ?? StreamChatTheme.of(context).colorTheme.overlayDark, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: icon, ), - child: icon, - ), - ); - } + ); } class _PreparingState extends StatelessWidget { - final String attachmentId; - const _PreparingState({ Key? key, required this.attachmentId, }) : super(key: key); + final String attachmentId; + @override Widget build(BuildContext context) { final channel = StreamChannel.of(context).channel; @@ -141,10 +147,6 @@ class _PreparingState extends StatelessWidget { } class _InProgressState extends StatelessWidget { - final int sent; - final int total; - final String attachmentId; - const _InProgressState({ Key? key, required this.sent, @@ -152,6 +154,10 @@ class _InProgressState extends StatelessWidget { required this.attachmentId, }) : super(key: key); + final int sent; + final int total; + final String attachmentId; + @override Widget build(BuildContext context) { final channel = StreamChannel.of(context).channel; @@ -181,10 +187,6 @@ class _InProgressState extends StatelessWidget { } class _FailedState extends StatelessWidget { - final String? error; - final String messageId; - final String attachmentId; - const _FailedState({ Key? key, this.error, @@ -192,6 +194,10 @@ class _FailedState extends StatelessWidget { required this.attachmentId, }) : super(key: key); + final String? error; + final String messageId; + final String attachmentId; + @override Widget build(BuildContext context) { final channel = StreamChannel.of(context).channel; @@ -235,16 +241,14 @@ class _FailedState extends StatelessWidget { class _SuccessState extends StatelessWidget { @override - Widget build(BuildContext context) { - return Align( - alignment: Alignment.topRight, - child: CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.overlayDark, - maxRadius: 12, - child: StreamSvgIcon.check( - color: StreamChatTheme.of(context).colorTheme.white, + Widget build(BuildContext context) => Align( + alignment: Alignment.topRight, + child: CircleAvatar( + backgroundColor: StreamChatTheme.of(context).colorTheme.overlayDark, + maxRadius: 12, + child: StreamSvgIcon.check( + color: StreamChatTheme.of(context).colorTheme.white, + ), ), - ), - ); - } + ); } 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 d80109c2..74769057 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart @@ -1,13 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import '../stream_chat_theme.dart'; - +/// Enum for identifying type of attachment enum AttachmentSource { + /// Attachment is attached local, + + /// Attachment is uploaded network, } +/// Extension for identifying type of attachment extension AttachmentSourceX on AttachmentSource { /// The [when] method is the equivalent to pattern matching. /// Its prototype depends on the AttachmentSource defined. @@ -25,18 +29,9 @@ extension AttachmentSourceX on AttachmentSource { } } +/// Abstract class for deriving attachment types abstract class AttachmentWidget extends StatelessWidget { - final Size? size; - final AttachmentSource? _source; - final Message message; - final Attachment attachment; - - AttachmentSource get source => - _source ?? - (attachment.file != null - ? AttachmentSource.local - : AttachmentSource.network); - + /// Constructor for creating attachment widget const AttachmentWidget({ Key? key, required this.message, @@ -45,30 +40,48 @@ abstract class AttachmentWidget extends StatelessWidget { AttachmentSource? source, }) : _source = source, super(key: key); + + /// Size of attachments + final Size? size; + final AttachmentSource? _source; + + /// Message which attachment is attached to + final Message message; + + /// Attachment to display + final Attachment attachment; + + /// Getter for source of attachment + AttachmentSource get source => + _source ?? + (attachment.file != null + ? AttachmentSource.local + : AttachmentSource.network); } +/// Widget for building in case of error class AttachmentError extends StatelessWidget { - final Size? size; - + /// Constructor for creating AttachmentError const AttachmentError({ Key? key, this.size, }) : super(key: key); + final Size? size; + @override - Widget build(BuildContext context) { - return Center( - child: Container( - width: size?.width, - height: size?.height, - color: StreamChatTheme.of(context).colorTheme.accentRed.withOpacity(.1), - child: Center( - child: Icon( - Icons.error_outline, - color: StreamChatTheme.of(context).colorTheme.black, + Widget build(BuildContext context) => Center( + child: Container( + width: size?.width, + height: size?.height, + color: + StreamChatTheme.of(context).colorTheme.accentRed.withOpacity(.1), + child: Center( + child: Icon( + Icons.error_outline, + color: StreamChatTheme.of(context).colorTheme.black, + ), ), ), - ), - ); - } + ); } 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 0ef78d85..c68d72f9 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -11,10 +11,7 @@ import '../upload_progress_indicator.dart'; import 'attachment_widget.dart'; class FileAttachment extends AttachmentWidget { - final Widget? title; - final Widget? trailing; - final VoidCallback? onAttachmentTap; - + /// Constructor for creating a widget when attachment is of type 'file' const FileAttachment({ Key? key, required Message message, @@ -30,8 +27,19 @@ class FileAttachment extends AttachmentWidget { size: size, ); + /// Title for attachment + final Widget? title; + + /// Widget for displaying at the end of attachment (such as a download button) + final Widget? trailing; + + /// Callback called when attachment widget is tapped + final VoidCallback? onAttachmentTap; + + /// Check if attachment is a video bool get isVideoAttachment => attachment.title?.mimeType?.type == 'video'; + /// Check if attachment is an image bool get isImageAttachment => attachment.title?.mimeType?.type == 'image'; @override @@ -56,10 +64,10 @@ class FileAttachment extends AttachmentWidget { Container( height: 40, width: 33.33, - margin: EdgeInsets.all(8), + margin: const EdgeInsets.all(8), child: _getFileTypeImage(context), ), - SizedBox(width: 8), + const SizedBox(width: 8), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -71,12 +79,12 @@ class FileAttachment extends AttachmentWidget { maxLines: 1, overflow: TextOverflow.ellipsis, ), - SizedBox(height: 3), + const SizedBox(height: 3), _buildSubtitle(context), ], ), ), - SizedBox(width: 8), + const SizedBox(width: 8), _buildTrailing(context), ], ), @@ -85,12 +93,10 @@ class FileAttachment extends AttachmentWidget { ); } - ShapeBorder _getDefaultShape(BuildContext context) { - return RoundedRectangleBorder( - side: BorderSide(width: 0, color: Colors.transparent), - borderRadius: BorderRadius.circular(8), - ); - } + ShapeBorder _getDefaultShape(BuildContext context) => RoundedRectangleBorder( + side: const BorderSide(width: 0, color: Colors.transparent), + borderRadius: BorderRadius.circular(8), + ); Widget _getFileTypeImage(BuildContext context) { if (isImageAttachment) { @@ -106,10 +112,8 @@ class FileAttachment extends AttachmentWidget { return Image.memory( attachment.file!.bytes!, fit: BoxFit.cover, - errorBuilder: (_, obj, trace) { - return getFileTypeImage( - attachment.extraData['other'] as String?); - }, + errorBuilder: (_, obj, trace) => + getFileTypeImage(attachment.extraData['other'] as String?), ); }, network: () { @@ -124,10 +128,8 @@ class FileAttachment extends AttachmentWidget { attachment.assetUrl ?? attachment.thumbUrl!, fit: BoxFit.cover, - errorWidget: (_, obj, trace) { - return getFileTypeImage( - attachment.extraData['other'] as String?); - }, + errorWidget: (_, obj, trace) => + getFileTypeImage(attachment.extraData['other'] as String?), placeholder: (_, __) { final image = Image.asset( 'images/placeholder.png', @@ -156,27 +158,23 @@ class FileAttachment extends AttachmentWidget { child: source.when( local: () => VideoThumbnailImage( video: attachment.file!.path!, - placeholderBuilder: (_) { - return Center( - child: Container( - width: 20, - height: 20, - child: const CircularProgressIndicator(), - ), - ); - }, + placeholderBuilder: (_) => const Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(), + ), + ), ), network: () => VideoThumbnailImage( video: attachment.assetUrl!, - placeholderBuilder: (_) { - return Center( - child: Container( - width: 20, - height: 20, - child: const CircularProgressIndicator(), - ), - ); - }, + placeholderBuilder: (_) => const Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(), + ), + ), ), ), ); @@ -189,22 +187,22 @@ class FileAttachment extends AttachmentWidget { double iconSize = 24.0, VoidCallback? onPressed, Color? fillColor, - }) { - return Container( - height: iconSize, - width: iconSize, - child: RawMaterialButton( - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - hoverElevation: 0, - onPressed: onPressed, - fillColor: fillColor, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - child: icon, - ), - ); - } + }) => + SizedBox( + height: iconSize, + width: iconSize, + child: RawMaterialButton( + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + hoverElevation: 0, + onPressed: onPressed, + fillColor: fillColor, + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: icon, + ), + ); Widget _buildTrailing(BuildContext context) { final theme = StreamChatTheme.of(context); @@ -281,26 +279,22 @@ class FileAttachment extends AttachmentWidget { color: theme.colorTheme.grey, ); return attachment.uploadState.when( - preparing: () { - return UploadProgressIndicator( - uploaded: 0, - total: double.maxFinite.toInt(), - showBackground: false, - padding: EdgeInsets.zero, - textStyle: textStyle, - progressIndicatorColor: theme.colorTheme.accentBlue, - ); - }, - inProgress: (sent, total) { - return UploadProgressIndicator( - uploaded: sent, - total: total, - showBackground: false, - padding: EdgeInsets.zero, - textStyle: textStyle, - progressIndicatorColor: theme.colorTheme.accentBlue, - ); - }, + preparing: () => UploadProgressIndicator( + uploaded: 0, + total: double.maxFinite.toInt(), + showBackground: false, + padding: EdgeInsets.zero, + textStyle: textStyle, + progressIndicatorColor: theme.colorTheme.accentBlue, + ), + inProgress: (sent, total) => UploadProgressIndicator( + uploaded: sent, + total: total, + showBackground: false, + padding: EdgeInsets.zero, + textStyle: textStyle, + progressIndicatorColor: theme.colorTheme.accentBlue, + ), success: () => Text(fileSize(size), style: textStyle), failed: (_) => Text('UPLOAD ERROR', style: textStyle), ) ?? diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart index 27e98f10..1bbbd54f 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -1,18 +1,13 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; +import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import '../full_screen_media.dart'; -import '../stream_chat_theme.dart'; -import '../stream_svg_icon.dart'; -import 'attachment_widget.dart'; - +/// Widget for showing a GIF attachment class GiphyAttachment extends AttachmentWidget { - final ShowMessageCallback? onShowMessage; - final ValueChanged? onReturnAction; - final VoidCallback? onAttachmentTap; - + /// Constructor for creating a [GiphyAttachment] widget const GiphyAttachment({ Key? key, required Message message, @@ -28,12 +23,21 @@ class GiphyAttachment extends AttachmentWidget { size: size, ); + /// Callback when show message is tapped + final ShowMessageCallback? onShowMessage; + + /// Callback when attachment is returned to from other screens + final ValueChanged? onReturnAction; + + /// Callback when attachment is tapped + final VoidCallback? onAttachmentTap; + @override Widget build(BuildContext context) { final imageUrl = attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl; if (imageUrl == null) { - return AttachmentError(); + return const AttachmentError(); } if (attachment.actions.isNotEmpty) { return _buildSendingAttachment(context, imageUrl); @@ -50,10 +54,9 @@ class GiphyAttachment extends AttachmentWidget { color: StreamChatTheme.of(context).colorTheme.white, elevation: 2, clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( + shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topRight: Radius.circular(16), - bottomRight: Radius.circular(0), topLeft: Radius.circular(16), bottomLeft: Radius.circular(16), ), @@ -67,12 +70,12 @@ class GiphyAttachment extends AttachmentWidget { child: Row( children: [ StreamSvgIcon.giphyIcon(), - SizedBox(width: 8), - Text( + const SizedBox(width: 8), + const Text( 'Giphy', style: TextStyle(fontWeight: FontWeight.bold), ), - SizedBox(width: 8), + const SizedBox(width: 8), if (attachment.title != null) Flexible( child: Text( @@ -97,19 +100,16 @@ class GiphyAttachment extends AttachmentWidget { child: CachedNetworkImage( height: size?.height, width: size?.width, - placeholder: (_, __) { - return Container( - width: size?.width, - height: size?.height, - child: Center( - child: CircularProgressIndicator(), - ), - ); - }, + placeholder: (_, __) => SizedBox( + width: size?.width, + height: size?.height, + child: const Center( + child: CircularProgressIndicator(), + ), + ), imageUrl: imageUrl, - errorWidget: (context, url, error) { - return AttachmentError(size: size); - }, + errorWidget: (context, url, error) => + AttachmentError(size: size), fit: BoxFit.cover, ), ), @@ -125,7 +125,7 @@ class GiphyAttachment extends AttachmentWidget { Row( children: [ Expanded( - child: Container( + child: SizedBox( height: 50, child: TextButton( onPressed: () { @@ -157,7 +157,7 @@ class GiphyAttachment extends AttachmentWidget { height: 50, ), Expanded( - child: Container( + child: SizedBox( height: 50, child: TextButton( onPressed: () { @@ -190,7 +190,7 @@ class GiphyAttachment extends AttachmentWidget { height: 50, ), Expanded( - child: Container( + child: SizedBox( height: 50, child: TextButton( onPressed: () { @@ -215,7 +215,7 @@ class GiphyAttachment extends AttachmentWidget { ], ), ), - SizedBox(height: 4), + const SizedBox(height: 4), Align( alignment: Alignment.centerRight, child: Padding( @@ -230,7 +230,7 @@ class GiphyAttachment extends AttachmentWidget { .withOpacity(0.5), size: 16, ), - SizedBox( + const SizedBox( width: 8, ), Text( @@ -273,88 +273,86 @@ class GiphyAttachment extends AttachmentWidget { if (res != null) onReturnAction?.call(res); } - Widget _buildSentAttachment(BuildContext context, String imageUrl) { - return Container( - child: GestureDetector( - onTap: () async { - final res = - await Navigator.push(context, MaterialPageRoute(builder: (_) { - final channel = StreamChannel.of(context).channel; - return StreamChannel( - channel: channel, - child: FullScreenMedia( - mediaAttachments: [attachment], - userName: message.user?.name, - message: message, - onShowMessage: onShowMessage, - ), - ); - })); - if (res != null) onReturnAction!(res); - }, - child: Stack( - children: [ - CachedNetworkImage( - height: size?.height, - width: size?.width, - placeholder: (_, __) { - final image = Image.asset( - 'images/placeholder.png', - fit: BoxFit.cover, - package: 'stream_chat_flutter', - ); + Widget _buildSentAttachment(BuildContext context, String imageUrl) => + SizedBox( + child: GestureDetector( + onTap: () async { + final res = + await Navigator.push(context, MaterialPageRoute(builder: (_) { + final channel = StreamChannel.of(context).channel; + return StreamChannel( + channel: channel, + child: FullScreenMedia( + mediaAttachments: [attachment], + userName: message.user?.name, + message: message, + onShowMessage: onShowMessage, + ), + ); + })); + if (res != null) onReturnAction!(res); + }, + child: Stack( + children: [ + CachedNetworkImage( + height: size?.height, + width: size?.width, + placeholder: (_, __) { + final image = Image.asset( + 'images/placeholder.png', + fit: BoxFit.cover, + package: 'stream_chat_flutter', + ); - final colorTheme = StreamChatTheme.of(context).colorTheme; - return Shimmer.fromColors( - baseColor: colorTheme.greyGainsboro, - highlightColor: colorTheme.whiteSmoke, - child: image, - ); - }, - imageUrl: imageUrl, - errorWidget: (context, url, error) { - return AttachmentError(size: size); - }, - fit: BoxFit.cover, - ), - Positioned( - bottom: 8, - left: 8, - child: Material( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, + final colorTheme = StreamChatTheme.of(context).colorTheme; + return Shimmer.fromColors( + baseColor: colorTheme.greyGainsboro, + highlightColor: colorTheme.whiteSmoke, + child: image, + ); + }, + imageUrl: imageUrl, + errorWidget: (context, url, error) => + AttachmentError(size: size), + fit: BoxFit.cover, + ), + Positioned( + bottom: 8, + left: 8, + child: Material( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), ), - child: Row( - children: [ - StreamSvgIcon.lightning( - color: StreamChatTheme.of(context).colorTheme.white, - size: 16, - ), - Text( - 'GIPHY', - style: TextStyle( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + child: Row( + children: [ + StreamSvgIcon.lightning( color: StreamChatTheme.of(context).colorTheme.white, - fontWeight: FontWeight.bold, - fontSize: 11, + size: 16, ), - ), - ], + Text( + 'GIPHY', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.white, + fontWeight: FontWeight.bold, + fontSize: 11, + ), + ), + ], + ), ), ), ), - ), - ], + ], + ), ), - ), - ); - } + ); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart index 652a6826..5c431d6e 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart @@ -1,21 +1,15 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; +import 'package:stream_chat_flutter/src/attachment/attachment_title.dart'; import 'package:stream_chat_flutter/src/attachment/attachment_upload_state_builder.dart'; +import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import '../full_screen_media.dart'; -import '../stream_chat_theme.dart'; -import 'attachment_title.dart'; -import 'attachment_widget.dart'; - +/// Widget for showing an image attachment class ImageAttachment extends AttachmentWidget { - final MessageTheme messageTheme; - final bool showTitle; - final ShowMessageCallback? onShowMessage; - final ValueChanged? onReturnAction; - final VoidCallback? onAttachmentTap; - + /// Constructor for creating a [ImageAttachment] widget const ImageAttachment({ Key? key, required Message message, @@ -33,6 +27,21 @@ class ImageAttachment extends AttachmentWidget { size: size, ); + /// [MessageTheme] for showing image title + final MessageTheme messageTheme; + + /// Flag for showing title + final bool showTitle; + + /// Callback when show message is tapped + final ShowMessageCallback? onShowMessage; + + /// Callback when attachment is returned to from other screens + final ValueChanged? onReturnAction; + + /// Callback when attachment is tapped + final VoidCallback? onAttachmentTap; + @override Widget build(BuildContext context) => source.when( local: () { diff --git a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart index f74bba14..b743cda1 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart @@ -1,18 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/attachment/attachment_title.dart'; +import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart'; import 'package:stream_chat_flutter/src/full_screen_media.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'attachment_title.dart'; -import 'attachment_upload_state_builder.dart'; -import 'attachment_widget.dart'; - +/// Widget for showing a video attachment class VideoAttachment extends AttachmentWidget { - final MessageTheme messageTheme; - final ShowMessageCallback? onShowMessage; - final ValueChanged? onReturnAction; - final VoidCallback? onAttachmentTap; - + /// Constructor for creating a [VideoAttachment] widget const VideoAttachment({ Key? key, required Message message, @@ -29,6 +24,18 @@ class VideoAttachment extends AttachmentWidget { size: size, ); + /// [MessageTheme] for showing title + final MessageTheme messageTheme; + + /// Callback when show message is tapped + final ShowMessageCallback? onShowMessage; + + /// Callback when attachment is returned to from other screens + final ValueChanged? onReturnAction; + + /// Callback when attachment is tapped + final VoidCallback? onAttachmentTap; + @override Widget build(BuildContext context) => source.when( local: () { diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index 493729b9..abd1680c 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -100,7 +100,7 @@ class ChannelInfo extends StatelessWidget { child: CircularProgressIndicator(), ), ), - SizedBox(width: 10), + const SizedBox(width: 10), Text( 'Searching for Network', style: textStyle, From edba3d2ad5fc2dbd6607508f22e06d474f1aa2b5 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 4 May 2021 16:58:03 +0530 Subject: [PATCH 187/599] attachment lint --- .../lib/src/attachment_actions_modal.dart | 10 +++---- .../lib/src/message_input.dart | 26 ++++++++----------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart index 98aa8d48..a8d1bddd 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart @@ -192,8 +192,7 @@ class AttachmentActionsModal extends StatelessWidget { VoidCallback? onTap, { Color? color, Key? key, - }) { - return Material( + }) => Material( key: key, color: StreamChatTheme.of(context).colorTheme.white, child: InkWell( @@ -216,7 +215,6 @@ class AttachmentActionsModal extends StatelessWidget { ), ), ); - } Widget _buildDownloadProgressDialog( BuildContext context, @@ -245,7 +243,7 @@ class AttachmentActionsModal extends StatelessWidget { ), child: Center( child: progress == null - ? Container( + ? SizedBox( height: 100, width: 100, child: StreamSvgIcon.error( @@ -253,7 +251,7 @@ class AttachmentActionsModal extends StatelessWidget { ), ) : progress.toProgressIndicatorValue == 1.0 - ? Container( + ? SizedBox( key: Key('completedIcon'), height: 160, width: 160, @@ -261,7 +259,7 @@ class AttachmentActionsModal extends StatelessWidget { color: theme.colorTheme.greyGainsboro, ), ) - : Container( + : SizedBox( height: 100, width: 100, child: Stack( diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 4c58f312..b0335e52 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -812,8 +812,7 @@ class MessageInputState extends State { final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; - return OverlayEntry(builder: (context) { - return Positioned( + return OverlayEntry(builder: (context) => Positioned( bottom: size.height + MediaQuery.of(context).viewInsets.bottom, left: 0, right: 0, @@ -821,8 +820,7 @@ class MessageInputState extends State { tween: Tween(begin: 0, end: 1), duration: Duration(milliseconds: 300), curve: Curves.easeInOutExpo, - builder: (context, val, wid) { - return Transform.scale( + builder: (context, val, wid) => Transform.scale( scale: val, child: Padding( padding: const EdgeInsets.all(8), @@ -869,7 +867,7 @@ class MessageInputState extends State { ], ), ), - SizedBox( + const SizedBox( height: 10, ), ...commands @@ -878,21 +876,21 @@ class MessageInputState extends State { onTap: () { _setCommand(c); }, - child: Container( + child: SizedBox( height: 40, child: Row( children: [ - SizedBox( + const SizedBox( width: 16, ), _buildCommandIcon(c.name), - SizedBox( + const SizedBox( width: 8, ), Text.rich( TextSpan( text: c.name.capitalize(), - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold), children: [ TextSpan( @@ -922,10 +920,8 @@ class MessageInputState extends State { ), ), ), - ); - }), - ); - }); + )), + )); } Widget _buildFilePickerSection() { @@ -1038,12 +1034,12 @@ class MessageInputState extends State { child: Container( decoration: BoxDecoration( color: StreamChatTheme.of(context).colorTheme.white, - borderRadius: BorderRadius.only( + borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), ), - child: Container( + child: SizedBox( width: double.infinity, child: Center( child: Padding( From 9acf8d103763a606ecc8e932edb06422f8275b07 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 4 May 2021 17:58:29 +0530 Subject: [PATCH 188/599] lint changes --- .../lib/src/back_button.dart | 13 +- .../lib/src/channel_bottom_sheet.dart | 17 +- .../lib/src/channel_header.dart | 58 +-- .../lib/src/channel_image.dart | 23 +- .../lib/src/channel_info.dart | 19 +- .../lib/src/channel_list_header.dart | 39 +- .../lib/src/channel_list_view.dart | 352 ++++++++------- .../lib/src/channel_name.dart | 85 ++-- .../lib/src/channel_preview.dart | 406 +++++++++--------- .../lib/src/connection_status_builder.dart | 11 +- .../lib/src/date_divider.dart | 13 +- .../lib/src/deleted_message.dart | 7 +- .../lib/src/extension.dart | 8 +- 13 files changed, 533 insertions(+), 518 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/back_button.dart b/packages/stream_chat_flutter/lib/src/back_button.dart index faf08fe8..acb3d1ec 100644 --- a/packages/stream_chat_flutter/lib/src/back_button.dart +++ b/packages/stream_chat_flutter/lib/src/back_button.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/unread_indicator.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import '../stream_chat_flutter.dart'; - +/// Back button implementation class StreamBackButton extends StatelessWidget { + + /// Constructor for creating back button const StreamBackButton({ Key? key, this.onPressed, @@ -12,15 +14,17 @@ class StreamBackButton extends StatelessWidget { this.cid, }) : super(key: key); + /// Callback for when button is pressed final VoidCallback? onPressed; + + /// Show unread count final bool showUnreads; /// Channel cid used to retrieve unread count final String? cid; @override - Widget build(BuildContext context) { - return Stack( + Widget build(BuildContext context) => Stack( alignment: Alignment.center, children: [ RawMaterialButton( @@ -54,5 +58,4 @@ class StreamBackButton extends StatelessWidget { ), ], ); - } } 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 911b306b..89f19466 100644 --- a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/channel_info.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import '../stream_chat_flutter.dart'; -import 'channel_info.dart'; -import 'option_list_tile.dart'; - +/// Bottom Sheet with options class ChannelBottomSheet extends StatefulWidget { - final VoidCallback? onViewInfoTap; + /// Constructor for creating bottom sheet + const ChannelBottomSheet({Key? key, this.onViewInfoTap}) : super(key: key); - const ChannelBottomSheet({this.onViewInfoTap}); + /// Callback when 'View Info' is tapped + final VoidCallback? onViewInfoTap; @override _ChannelBottomSheetState createState() => _ChannelBottomSheetState(); @@ -29,7 +30,7 @@ class _ChannelBottomSheetState extends State { return Material( color: StreamChatTheme.of(context).colorTheme.white, clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( + shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), @@ -120,7 +121,7 @@ class _ChannelBottomSheetState extends State { ), borderRadius: BorderRadius.circular(32), onlineIndicatorConstraints: - BoxConstraints.tight(Size(12, 12)), + BoxConstraints.tight(const Size(12, 12)), ), const SizedBox( height: 6, diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index 1882c5b0..8b0eb8fb 100644 --- a/packages/stream_chat_flutter/lib/src/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_header.dart @@ -4,13 +4,9 @@ import 'package:stream_chat_flutter/src/channel_info.dart'; import 'package:stream_chat_flutter/src/channel_name.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import './channel_name.dart'; -import '../stream_chat_flutter.dart'; -import 'channel_image.dart'; -import 'connection_status_builder.dart'; - /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header_paint.png) /// @@ -43,16 +39,36 @@ import 'connection_status_builder.dart'; /// Usually you would use this widget as an [AppBar] inside a [Scaffold]. /// However you can also use it as a normal widget. /// -/// Make sure to have a [StreamChannel] ancestor in order to provide the information about the channel. -/// Every part of the widget uses a [StreamBuilder] to render the channel information as soon as it updates. +/// Make sure to have a [StreamChannel] ancestor in order to provide the +/// information about the channel. +/// Every part of the widget uses a [StreamBuilder] to render the channel +/// information as soon as it updates. /// /// By default the widget shows a backButton that calls [Navigator.pop]. -/// You can disable this button using the [showBackButton] property of just override the behaviour +/// You can disable this button using the [showBackButton] property of just +/// override the behaviour /// with [onBackPressed]. /// -/// The widget components render the ui based on the first ancestor of type [StreamChatTheme] and on its [ChannelTheme.channelHeaderTheme] property. +/// The widget components render the ui based on the first ancestor of type +/// [StreamChatTheme] and on its [ChannelTheme.channelHeaderTheme] property. /// Modify it to change the widget appearance. class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { + /// Creates a channel header + const ChannelHeader({ + Key? key, + this.showBackButton = true, + this.onBackPressed, + this.onTitleTap, + this.showTypingIndicator = true, + this.onImageTap, + this.showConnectionStateTile = false, + this.title, + this.subtitle, + this.leading, + this.actions, + }) : preferredSize = const Size.fromHeight(kToolbarHeight), + super(key: key); + /// True if this header shows the leading back button final bool showBackButton; @@ -69,6 +85,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { /// If true the typing indicator will be rendered if a user is typing final bool showTypingIndicator; + /// Show connection tile on header final bool showConnectionStateTile; /// Title widget @@ -84,22 +101,6 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { /// By default it shows the [ChannelImage] final List? actions; - /// Creates a channel header - ChannelHeader({ - Key? key, - this.showBackButton = true, - this.onBackPressed, - this.onTitleTap, - this.showTypingIndicator = true, - this.onImageTap, - this.showConnectionStateTile = false, - this.title, - this.subtitle, - this.leading, - this.actions, - }) : preferredSize = Size.fromHeight(kToolbarHeight), - super(key: key); - @override Widget build(BuildContext context) { final channel = StreamChannel.of(context).channel; @@ -110,7 +111,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { onPressed: onBackPressed, showUnreads: true, ) - : SizedBox()); + : const SizedBox()); return ConnectionStatusBuilder( statusBuilder: (context, status) { @@ -131,6 +132,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { } return InfoTile( + // ignore: avoid_bool_literals_in_conditional_expressions showMessage: showConnectionStateTile ? showStatus : false, message: statusString, child: AppBar( @@ -165,7 +167,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { centerTitle: true, title: InkWell( onTap: onTitleTap, - child: Container( + child: SizedBox( height: preferredSize.height, width: preferredSize.width, child: Column( @@ -178,7 +180,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { .channelHeaderTheme .title, ), - SizedBox(height: 2), + const SizedBox(height: 2), subtitle ?? ChannelInfo( showTypingIndicator: showTypingIndicator, diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 74983505..3f3a38be 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -36,12 +36,14 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// } /// ``` /// -/// The widget uses a [StreamBuilder] to render the channel information image as soon as it updates. +/// The widget uses a [StreamBuilder] to render the channel information +/// image as soon as it updates. /// /// By default the widget radius size is 40x40 pixels. /// Set the property [constraints] to set a custom dimension. /// -/// The widget renders the ui based on the first ancestor of type [StreamChatTheme]. +/// The widget renders the ui based on the first ancestor of type +/// [StreamChatTheme]. /// Modify it to change the widget appearance. class ChannelImage extends StatelessWidget { /// Instantiate a new ChannelImage @@ -56,6 +58,7 @@ class ChannelImage extends StatelessWidget { this.selectionThickness = 4, }) : super(key: key); + /// [BorderRadius] to display the widget final BorderRadius? borderRadius; /// The channel to show the image of @@ -67,10 +70,13 @@ class ChannelImage extends StatelessWidget { /// The function called when the image is tapped final VoidCallback? onTap; + /// If image is selected final bool selected; + /// Selection color for image final Color? selectionColor; + /// Thickness of selection image final double selectionThickness; @override @@ -91,8 +97,7 @@ class ChannelImage extends StatelessWidget { stream: streamChat.client.state.usersStream.map( (users) => users[otherMember?.userId] ?? otherMember!.user!), initialData: otherMember!.user, - builder: (context, snapshot) { - return UserAvatar( + builder: (context, snapshot) => UserAvatar( borderRadius: borderRadius ?? StreamChatTheme.of(context) .channelPreviewTheme @@ -109,14 +114,14 @@ class ChannelImage extends StatelessWidget { selectionColor: selectionColor ?? StreamChatTheme.of(context).colorTheme.accentBlue, selectionThickness: selectionThickness, - ); - }); + )); } else { final images = channel.state?.members .where((member) => member.user?.id != streamChat.user?.id && member.user?.extraData['image'] != null) .take(4) + // ignore: cast_nullable_to_non_nullable .map((e) => e.user?.extraData['image'] as String) .toList(); return GroupImage( @@ -161,8 +166,7 @@ class ChannelImage extends StatelessWidget { if (image != null) CachedNetworkImage( imageUrl: image, - errorWidget: (_, __, ___) { - return Center( + errorWidget: (_, __, ___) => Center( child: Text( snapshot.data?.containsKey('name') ?? false ? snapshot.data!['name'][0] @@ -172,8 +176,7 @@ class ChannelImage extends StatelessWidget { fontWeight: FontWeight.bold, ), ), - ); - }, + ), fit: BoxFit.cover, ) else diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index abd1680c..72f6cf18 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -6,6 +6,14 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'connection_status_builder.dart'; class ChannelInfo extends StatelessWidget { + const ChannelInfo({ + Key? key, + required this.channel, + this.textStyle, + this.showTypingIndicator = true, + }) : super(key: key); + + /// The channel about which the info is to be displayed final Channel channel; /// The style of the text displayed @@ -14,13 +22,6 @@ class ChannelInfo extends StatelessWidget { /// If true the typing indicator will be rendered if a user is typing final bool showTypingIndicator; - const ChannelInfo({ - Key? key, - required this.channel, - this.textStyle, - this.showTypingIndicator = true, - }) : super(key: key); - @override Widget build(BuildContext context) { final client = StreamChat.of(context).client; @@ -93,10 +94,10 @@ class ChannelInfo extends StatelessWidget { Widget _buildConnectingTitleState(BuildContext context) => Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( + const SizedBox( height: 16, width: 16, - child: const Center( + child: Center( child: CircularProgressIndicator(), ), ), 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 bb103f7b..c8d2aa69 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -6,10 +6,7 @@ import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'connection_status_builder.dart'; -import 'info_tile.dart'; -import 'stream_chat.dart'; - +/// Widget builder for title typedef TitleBuilder = Widget Function( BuildContext context, ConnectionStatus status, @@ -42,10 +39,13 @@ typedef TitleBuilder = Widget Function( /// Usually you would use this widget as an [AppBar] inside a [Scaffold]. /// However you can also use it as a normal widget. /// -/// The widget by default uses the inherited [StreamChatClient] to fetch information about the status. -/// However you can also pass your own [StreamChatClient] if you don't have it in the widget tree. +/// The widget by default uses the inherited [StreamChatClient] +/// to fetch information about the status. +/// However you can also pass your own [StreamChatClient] +/// if you don't have it in the widget tree. /// -/// The widget components render the ui based on the first ancestor of type [StreamChatTheme] and on its [ChannelListHeaderTheme] property. +/// The widget components render the ui based on the first ancestor of type +/// [StreamChatTheme] and on its [ChannelListHeaderTheme] property. /// Modify it to change the widget appearance. class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { /// Instantiates a ChannelListHeader @@ -75,8 +75,10 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { /// Callback to call when pressing the new chat button. final VoidCallback? onNewChatButtonTap; + /// Show connection state tile final bool showConnectionStateTile; + /// Callback before navigation is performed final VoidCallback? preNavigationCallback; /// Subtitle widget @@ -113,6 +115,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { } return InfoTile( + // ignore: avoid_bool_literals_in_conditional_expressions showMessage: showConnectionStateTile ? showStatus : false, message: statusString, child: AppBar( @@ -143,7 +146,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { .avatarTheme ?.constraints, ) - : Offstage(), + : const Offstage(), ), actions: actions ?? [ @@ -151,7 +154,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { child: IconButton( icon: ConnectionStatusBuilder( statusBuilder: (context, status) { - var color; + Color? color; switch (status) { case ConnectionStatus.connected: color = StreamChatTheme.of(context) @@ -193,11 +196,11 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { case ConnectionStatus.disconnected: return _buildDisconnectedTitleState(context, _client); default: - return Offstage(); + return const Offstage(); } }, ), - subtitle ?? Offstage(), + subtitle ?? const Offstage(), ], ), ), @@ -213,18 +216,17 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { ), ); - Widget _buildConnectingTitleState(BuildContext context) { - return Row( + Widget _buildConnectingTitleState(BuildContext context) => Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( + const SizedBox( height: 16, width: 16, child: Center( child: CircularProgressIndicator(), ), ), - SizedBox(width: 10), + const SizedBox(width: 10), Text( 'Searching for Network', style: StreamChatTheme.of(context) @@ -237,13 +239,11 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { ), ], ); - } Widget _buildDisconnectedTitleState( BuildContext context, StreamChatClient client, - ) { - return Row( + ) => Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( @@ -275,8 +275,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { ), ], ); - } @override - Size get preferredSize => Size.fromHeight(kToolbarHeight); + Size get preferredSize => const Size.fromHeight(kToolbarHeight); } 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 473007db..c58ed42f 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -3,13 +3,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:shimmer/shimmer.dart'; +import 'package:stream_chat_flutter/src/channel_bottom_sheet.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import '../stream_chat_flutter.dart'; -import 'channel_bottom_sheet.dart'; -import 'channel_preview.dart'; /// Callback called when tapping on a channel typedef ChannelTapCallback = void Function(Channel, Widget?); @@ -17,6 +16,7 @@ typedef ChannelTapCallback = void Function(Channel, Widget?); /// Builder used to create a custom [ChannelPreview] from a [Channel] typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel); +/// Callback for when 'View Info' is tapped typedef ViewInfoCallback = void Function(Channel); /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view.png) @@ -47,10 +47,12 @@ typedef ViewInfoCallback = void Function(Channel); /// ``` /// /// -/// Make sure to have a [StreamChat] ancestor in order to provide the information about the channels. +/// Make sure to have a [StreamChat] ancestor in order to provide the +/// information about the channels. /// The widget uses a [ListView.custom] to render the list of channels. /// -/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. +/// The widget components render the ui based on the first ancestor of +/// type [StreamChatTheme]. /// Modify it to change the widget appearance. class ChannelListView extends StatefulWidget { /// Instantiate a new ChannelListView @@ -94,8 +96,10 @@ class ChannelListView extends StatefulWidget { final Map? options; /// The sorting used for the channels matching the filters. - /// Sorting is based on field and direction, multiple sorting options can be provided. - /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. + /// Sorting is based on field and direction, multiple sorting options + /// can be provided. + /// You can sort based on last_updated, last_message_at, updated_at, + /// created_at or member_count. /// Direction can be ascending or descending. final List>? sort; @@ -137,8 +141,10 @@ class ChannelListView extends StatefulWidget { /// The amount of space by which to inset the children. final EdgeInsetsGeometry? padding; + /// List of selected channels which are displayed differently final List selectedChannels; + /// Callback for when 'View Info' is tapped final ViewInfoCallback? onViewInfoTap; /// The builder that will be used in case of error @@ -203,15 +209,14 @@ class _ChannelListViewState extends State { crossAxisCount: widget.crossAxisCount, ), itemCount: channels.length, - physics: AlwaysScrollableScrollPhysics(), - itemBuilder: (context, index) { - return _gridItemBuilder(context, index, channels); - }, + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (context, index) => + _gridItemBuilder(context, index, channels), ); } else { child = ListView.separated( padding: widget.padding, - physics: AlwaysScrollableScrollPhysics(), + physics: const AlwaysScrollableScrollPhysics(), itemCount: channels.isNotEmpty ? channels.length + 1 : channels.length, separatorBuilder: (_, index) { @@ -220,9 +225,8 @@ class _ChannelListViewState extends State { } return _separatorBuilder(context, index); }, - itemBuilder: (context, index) { - return _listItemBuilder(context, index, channels); - }, + itemBuilder: (context, index) => + _listItemBuilder(context, index, channels), ); } } @@ -233,11 +237,9 @@ class _ChannelListViewState extends State { ); } - Widget _buildEmptyWidget(BuildContext context) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), + Widget _buildEmptyWidget(BuildContext context) => LayoutBuilder( + builder: (context, viewportConstraints) => SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), child: Stack( children: [ ConstrainedBox( @@ -307,31 +309,27 @@ class _ChannelListViewState extends State { ), ], ), - ); - }, - ); - } + ), + ); - Widget _buildLoadingWidget(BuildContext context) { - return ListView( - padding: widget.padding, - physics: AlwaysScrollableScrollPhysics(), - children: List.generate( - 25, - (i) { - if (widget.crossAxisCount == 1) { - if (i % 2 != 0) { - if (widget.separatorBuilder != null) { - return widget.separatorBuilder!(context, i); + Widget _buildLoadingWidget(BuildContext context) => ListView( + padding: widget.padding, + physics: const AlwaysScrollableScrollPhysics(), + children: List.generate( + 25, + (i) { + if (widget.crossAxisCount == 1) { + if (i % 2 != 0) { + if (widget.separatorBuilder != null) { + return widget.separatorBuilder!(context, i); + } + return _separatorBuilder(context, i); } - return _separatorBuilder(context, i); } - } - return _buildLoadingItem(context); - }, - ), - ); - } + return _buildLoadingItem(context); + }, + ), + ); Shimmer _buildLoadingItem(BuildContext context) { if (widget.crossAxisCount > 1) { @@ -340,24 +338,24 @@ class _ChannelListViewState extends State { highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke, child: Column( children: [ - SizedBox(height: 4), + const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ for (int i = 0; i < widget.crossAxisCount; i++) Container( - decoration: BoxDecoration( + decoration: const BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 70, width: 70, ), ), ], ), - SizedBox( + const SizedBox( height: 16, ), ], @@ -373,7 +371,7 @@ class _ChannelListViewState extends State { color: StreamChatTheme.of(context).colorTheme.white, shape: BoxShape.circle, ), - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 40, width: 40, ), @@ -389,7 +387,7 @@ class _ChannelListViewState extends State { color: StreamChatTheme.of(context).colorTheme.white, borderRadius: BorderRadius.circular(11), ), - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 16, width: 82, ), @@ -406,7 +404,7 @@ class _ChannelListViewState extends State { color: StreamChatTheme.of(context).colorTheme.white, borderRadius: BorderRadius.circular(11), ), - constraints: BoxConstraints.expand( + constraints: const BoxConstraints.expand( height: 16, ), ), @@ -418,7 +416,7 @@ class _ChannelListViewState extends State { color: StreamChatTheme.of(context).colorTheme.white, borderRadius: BorderRadius.circular(11), ), - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 16, width: 42, ), @@ -430,35 +428,33 @@ class _ChannelListViewState extends State { } } - Widget _buildErrorWidget(BuildContext context, Object error) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text.rich( - TextSpan( - children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only( - right: 2, + Widget _buildErrorWidget(BuildContext context, Object error) => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text.rich( + const TextSpan( + children: [ + WidgetSpan( + child: Padding( + padding: EdgeInsets.only( + right: 2, + ), + child: Icon(Icons.error_outline), ), - child: Icon(Icons.error_outline), ), - ), - TextSpan(text: 'Error loading channels'), - ], + TextSpan(text: 'Error loading channels'), + ], + ), + style: Theme.of(context).textTheme.headline6, ), - style: Theme.of(context).textTheme.headline6, - ), - TextButton( - onPressed: () => _channelListController.loadData!(), - child: Text('Retry'), - ), - ], - ), - ); - } + TextButton( + onPressed: () => _channelListController.loadData!(), + child: const Text('Retry'), + ), + ], + ), + ); Widget _listItemBuilder(BuildContext context, int i, List channels) { final channelsProvider = ChannelsBloc.of(context); @@ -471,82 +467,77 @@ class _ChannelListViewState extends State { key: ValueKey('CHANNEL-${channel.id}'), channel: channel, child: Builder( - builder: (context) { - return Slidable( - controller: _slideController, - enabled: widget.swipeToAction, - actionPane: SlidableBehindActionPane(), - actionExtentRatio: 0.12, - secondaryActions: [ + builder: (context) => Slidable( + controller: _slideController, + enabled: widget.swipeToAction, + actionPane: const SlidableBehindActionPane(), + actionExtentRatio: 0.12, + secondaryActions: [ + IconSlideAction( + color: backgroundColor, + icon: Icons.more_horiz, + onTap: () { + showModalBottomSheet( + clipBehavior: Clip.hardEdge, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(32), + topRight: Radius.circular(32), + ), + ), + context: context, + builder: (context) => StreamChannel( + channel: channel, + child: ChannelBottomSheet( + onViewInfoTap: () { + widget.onViewInfoTap?.call(channel); + }, + ), + ), + ); + }, + ), + if ([ + 'admin', + 'owner', + ].contains(channel.state!.members + .firstWhereOrNull( + (m) => m.userId == channel.client.state.user?.id) + ?.role)) IconSlideAction( color: backgroundColor, - icon: Icons.more_horiz, - onTap: () { - showModalBottomSheet( - clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(32), - topRight: Radius.circular(32), - ), + iconWidget: StreamSvgIcon.delete( + color: StreamChatTheme.of(context).colorTheme.accentRed, + ), + onTap: () async { + final res = await showConfirmationDialog( + context, + title: 'Delete Conversation', + okText: 'DELETE', + question: + 'Are you sure you want to delete this conversation?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.delete( + color: StreamChatTheme.of(context).colorTheme.accentRed, ), - context: context, - builder: (context) { - return StreamChannel( - channel: channel, - child: ChannelBottomSheet( - onViewInfoTap: () { - widget.onViewInfoTap?.call(channel); - }, - ), - ); - }, ); + if (res == true) { + await channel.delete(); + } }, ), - if ([ - 'admin', - 'owner', - ].contains(channel.state!.members - .firstWhereOrNull( - (m) => m.userId == channel.client.state.user?.id) - ?.role)) - IconSlideAction( - color: backgroundColor, - iconWidget: StreamSvgIcon.delete( - color: StreamChatTheme.of(context).colorTheme.accentRed, - ), - onTap: () async { - final res = await showConfirmationDialog( - context, - title: 'Delete Conversation', - okText: 'DELETE', - question: - 'Are you sure you want to delete this conversation?', - cancelText: 'CANCEL', - icon: StreamSvgIcon.delete( - color: - StreamChatTheme.of(context).colorTheme.accentRed, - ), - ); - if (res == true) { - await channel.delete(); - } - }, + ], + child: Container( + color: StreamChatTheme.of(context).colorTheme.whiteSnow, + child: widget.channelPreviewBuilder?.call(context, channel) ?? + ChannelPreview( + onLongPress: widget.onChannelLongPress, + channel: channel, + onImageTap: () => widget.onImageTap?.call(channel), + onTap: (channel) => onTap(channel, widget.channelWidget), ), - ], - child: Container( - color: StreamChatTheme.of(context).colorTheme.whiteSnow, - child: widget.channelPreviewBuilder?.call(context, channel) ?? - ChannelPreview( - onLongPress: widget.onChannelLongPress, - channel: channel, - onImageTap: () => widget.onImageTap?.call(channel), - onTap: (channel) => onTap(channel, widget.channelWidget), - ), - ), - ); - }, + ), + ), ), ); } else { @@ -566,12 +557,10 @@ class _ChannelListViewState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) { - return StreamChannel( - channel: client, - child: widget.channelWidget!, - ); - }, + builder: (context) => StreamChannel( + channel: client, + child: widget.channelWidget!, + ), ), ); }; @@ -593,18 +582,18 @@ class _ChannelListViewState extends State { channel: channel, borderRadius: BorderRadius.circular(32), selected: selected, - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( width: 64, height: 64, ), onTap: () => _getChannelTap(context), ), - SizedBox(height: 7), + const SizedBox(height: 7), Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: StreamChannel( channel: channel, - child: ChannelName( + child: const ChannelName( textStyle: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, @@ -620,35 +609,34 @@ class _ChannelListViewState extends State { Widget _buildQueryProgressIndicator( context, ChannelsBlocState channelsProvider, - ) { - return StreamBuilder( - stream: channelsProvider.queryChannelsLoading, - initialData: false, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Container( - color: StreamChatTheme.of(context) - .colorTheme - .accentRed - .withOpacity(.2), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Center( - child: Text('Error loading channels'), - ), - ), - ); - } - return snapshot.data! - ? Center( - child: Padding( - padding: const EdgeInsets.all(16), - child: const CircularProgressIndicator(), + ) => + StreamBuilder( + stream: channelsProvider.queryChannelsLoading, + initialData: false, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Container( + color: StreamChatTheme.of(context) + .colorTheme + .accentRed + .withOpacity(.2), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 16), + child: Center( + child: Text('Error loading channels'), ), - ) - : Offstage(); - }); - } + ), + ); + } + return snapshot.data! + ? const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: CircularProgressIndicator(), + ), + ) + : const Offstage(); + }); Widget _separatorBuilder(context, i) { final effect = StreamChatTheme.of(context).colorTheme.borderBottom; diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart index 48657419..d76bca0c 100644 --- a/packages/stream_chat_flutter/lib/src/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel_name.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import '../stream_chat_flutter.dart'; /// It shows the current [Channel] name using a [Text] widget. /// -/// The widget uses a [StreamBuilder] to render the channel information image as soon as it updates. +/// The widget uses a [StreamBuilder] to render the channel information +/// image as soon as it updates. class ChannelName extends StatelessWidget { /// Instantiate a new ChannelName const ChannelName({ @@ -25,9 +26,8 @@ class ChannelName extends StatelessWidget { return StreamBuilder>( stream: channel.extraDataStream, initialData: channel.extraData, - builder: (context, snapshot) { - return _buildName(snapshot.data!, channel.state?.members, client); - }, + builder: (context, snapshot) => + _buildName(snapshot.data!, channel.state?.members, client), ); } @@ -35,45 +35,46 @@ class ChannelName extends StatelessWidget { Map extraData, List? members, StreamChatState client, - ) { - return LayoutBuilder( - builder: (context, constraints) { - String? title; - if (extraData['name'] == null) { - final otherMembers = - members?.where((member) => member.userId != client.user!.id); - if (otherMembers?.length == 1) { - title = otherMembers!.first.user?.name; - } else if (otherMembers?.isNotEmpty == true) { - final maxWidth = constraints.maxWidth; - final maxChars = maxWidth / (textStyle?.fontSize ?? 1); - var currentChars = 0; - final currentMembers = []; - otherMembers!.forEach((element) { - final newLength = currentChars + (element.user?.name.length ?? 0); - if (newLength < maxChars) { - currentChars = newLength; - currentMembers.add(element); - } - }); + ) => + LayoutBuilder( + builder: (context, constraints) { + String? title; + if (extraData['name'] == null) { + final otherMembers = + members?.where((member) => member.userId != client.user!.id); + if (otherMembers?.length == 1) { + title = otherMembers!.first.user?.name; + } else if (otherMembers?.isNotEmpty == true) { + final maxWidth = constraints.maxWidth; + final maxChars = maxWidth / (textStyle?.fontSize ?? 1); + var currentChars = 0; + final currentMembers = []; + otherMembers!.forEach((element) { + final newLength = + currentChars + (element.user?.name.length ?? 0); + if (newLength < maxChars) { + currentChars = newLength; + currentMembers.add(element); + } + }); - final exceedingMembers = - otherMembers.length - currentMembers.length; - title = - '${currentMembers.map((e) => e.user?.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + final exceedingMembers = + otherMembers.length - currentMembers.length; + title = + '${currentMembers.map((e) => e.user?.name).join(', ')} ' + '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + } else { + title = 'No title'; + } } else { - title = 'No title'; + title = extraData['name']; } - } else { - title = extraData['name']; - } - return Text( - title!, - style: textStyle, - overflow: TextOverflow.ellipsis, - ); - }, - ); - } + return Text( + title!, + style: textStyle, + overflow: TextOverflow.ellipsis, + ); + }, + ); } diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 9c396065..9c793ba6 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -3,23 +3,39 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import '../stream_chat_flutter.dart'; -import 'channel_name.dart'; - /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png) /// /// It shows the current [Channel] preview. /// -/// The widget uses a [StreamBuilder] to render the channel information image as soon as it updates. +/// The widget uses a [StreamBuilder] to render the channel information +/// image as soon as it updates. /// -/// Usually you don't use this widget as it's the default channel preview used by [ChannelListView]. +/// Usually you don't use this widget as it's the default channel preview +/// used by [ChannelListView]. /// -/// The widget renders the ui based on the first ancestor of type [StreamChatTheme]. +/// The widget renders the ui based on the first ancestor of type +/// [StreamChatTheme]. /// Modify it to change the widget appearance. class ChannelPreview extends StatelessWidget { + + /// Constructor for creating [ChannelPreview] + const ChannelPreview({ + required this.channel, + Key? key, + this.onTap, + this.onLongPress, + this.onImageTap, + this.title, + this.subtitle, + this.leading, + this.sendingIndicator, + this.trailing, + }) : super(key: key); + /// Function called when tapping this widget final void Function(Channel)? onTap; @@ -38,156 +54,145 @@ class ChannelPreview extends StatelessWidget { /// Widget rendering the subtitle final Widget? subtitle; - /// Widget rendering the leading element, by default it shows the [ChannelImage] + /// Widget rendering the leading element, by default + /// it shows the [ChannelImage] final Widget? leading; - /// Widget rendering the trailing element, by default it shows the last message date + /// Widget rendering the trailing element, + /// by default it shows the last message date final Widget? trailing; - /// Widget rendering the sending indicator, by default it uses the [SendingIndicator] widget + /// Widget rendering the sending indicator, + /// by default it uses the [SendingIndicator] widget final Widget? sendingIndicator; - const ChannelPreview({ - required this.channel, - Key? key, - this.onTap, - this.onLongPress, - this.onImageTap, - this.title, - this.subtitle, - this.leading, - this.sendingIndicator, - this.trailing, - }) : super(key: key); - @override Widget build(BuildContext context) { final channelPreviewTheme = StreamChatTheme.of(context).channelPreviewTheme; return StreamBuilder( stream: channel.isMutedStream, initialData: channel.isMuted, - builder: (context, snapshot) { - return Opacity( - opacity: snapshot.data! ? 0.5 : 1, - child: ListTile( - visualDensity: VisualDensity.compact, - contentPadding: const EdgeInsets.symmetric( - horizontal: 8, - ), - onTap: () { - if (onTap != null) { - onTap!(channel); - } - }, - onLongPress: () { - if (onLongPress != null) { - onLongPress!(channel); - } - }, - leading: leading ?? - ChannelImage( - onTap: onImageTap, - ), - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: title ?? - ChannelName( - textStyle: channelPreviewTheme.title, - ), - ), - StreamBuilder>( - stream: channel.state?.membersStream, - initialData: channel.state?.members, - builder: (context, snapshot) { - if (!snapshot.hasData || - snapshot.data!.isEmpty || - !snapshot.data!.any((Member e) => - e.user!.id == channel.client.state.user?.id)) { - return 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 == - StreamChat.of(context).user?.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.user!.id) - .where((element) => element.lastRead - .isAfter(lastMessage.createdAt)) - .isNotEmpty == - true, - ), + builder: (context, snapshot) => Opacity( + opacity: snapshot.data! ? 0.5 : 1, + child: ListTile( + visualDensity: VisualDensity.compact, + contentPadding: const EdgeInsets.symmetric( + horizontal: 8, + ), + onTap: () { + if (onTap != null) { + onTap!(channel); + } + }, + onLongPress: () { + if (onLongPress != null) { + onLongPress!(channel); + } + }, + leading: leading ?? + ChannelImage( + onTap: onImageTap, + ), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: title ?? + ChannelName( + textStyle: channelPreviewTheme.title, + ), + ), + StreamBuilder>( + stream: channel.state?.membersStream, + initialData: channel.state?.members, + builder: (context, snapshot) { + if (!snapshot.hasData || + snapshot.data!.isEmpty || + !snapshot.data!.any((Member e) => + e.user!.id == channel.client.state.user?.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 != true, ); - } - return SizedBox(); - }, - ), - trailing ?? _buildDate(context), - ], + if (lastMessage?.user?.id == + StreamChat.of(context).user?.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.user!.id) + .where((element) => element.lastRead + .isAfter(lastMessage.createdAt)) + .isNotEmpty == + true, + ), + ); + } + return const SizedBox(); + }, + ), + trailing ?? _buildDate(context), + ], + ), ), - ), + )); + } + + Widget _buildDate(BuildContext context) => StreamBuilder( + stream: channel.lastMessageAtStream, + initialData: channel.lastMessageAt, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const SizedBox(); + } + final lastMessageAt = snapshot.data!.toLocal(); + + String stringDate; + final now = DateTime.now(); + + final startOfDay = DateTime(now.year, now.month, now.day); + + if (lastMessageAt.millisecondsSinceEpoch >= + startOfDay.millisecondsSinceEpoch) { + stringDate = Jiffy(lastMessageAt.toLocal()).jm; + } else if (lastMessageAt.millisecondsSinceEpoch >= + startOfDay + .subtract(const Duration(days: 1)) + .millisecondsSinceEpoch) { + stringDate = 'Yesterday'; + } else if (startOfDay.difference(lastMessageAt).inDays < 7) { + stringDate = Jiffy(lastMessageAt.toLocal()).EEEE; + } else { + stringDate = Jiffy(lastMessageAt.toLocal()).yMd; + } + + return Text( + stringDate, + style: + StreamChatTheme.of(context).channelPreviewTheme.lastMessageAt, ); - }); - } - - Widget _buildDate(BuildContext context) { - return StreamBuilder( - stream: channel.lastMessageAtStream, - initialData: channel.lastMessageAt, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return SizedBox(); - } - final lastMessageAt = snapshot.data!.toLocal(); - - String stringDate; - final now = DateTime.now(); - - final startOfDay = DateTime(now.year, now.month, now.day); - - if (lastMessageAt.millisecondsSinceEpoch >= - startOfDay.millisecondsSinceEpoch) { - stringDate = Jiffy(lastMessageAt.toLocal()).jm; - } else if (lastMessageAt.millisecondsSinceEpoch >= - startOfDay.subtract(Duration(days: 1)).millisecondsSinceEpoch) { - stringDate = 'Yesterday'; - } else if (startOfDay.difference(lastMessageAt).inDays < 7) { - stringDate = Jiffy(lastMessageAt.toLocal()).EEEE; - } else { - stringDate = Jiffy(lastMessageAt.toLocal()).yMd; - } - - return Text( - stringDate, - style: StreamChatTheme.of(context).channelPreviewTheme.lastMessageAt, - ); - }, - ); - } + }, + ); Widget _buildSubtitle(BuildContext context) { if (channel.isMuted) { @@ -211,66 +216,71 @@ class ChannelPreview extends StatelessWidget { ); } - Widget _buildLastMessage(BuildContext context) { - return StreamBuilder?>( - stream: channel.state!.messagesStream, - initialData: channel.state!.messages, - builder: (context, snapshot) { - final lastMessage = snapshot.data - ?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted); - if (lastMessage == null) { - return SizedBox(); - } + Widget _buildLastMessage(BuildContext context) => + StreamBuilder?>( + stream: channel.state!.messagesStream, + initialData: channel.state!.messages, + builder: (context, snapshot) { + final lastMessage = snapshot.data + ?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted); + if (lastMessage == null) { + return const SizedBox(); + } - var text = lastMessage.text; - final parts = [ - ...lastMessage.attachments.map((e) { - if (e.type == 'image') { - return '📷'; - } else if (e.type == 'video') { - return 'đŸŽŦ'; - } else if (e.type == 'giphy') { - return '[GIF]'; - } - return e == lastMessage.attachments.last - ? (e.title ?? 'File') - : '${e.title ?? 'File'} , '; - }), - lastMessage.text ?? '', - ]; + var text = lastMessage.text; + final parts = [ + ...lastMessage.attachments.map((e) { + if (e.type == 'image') { + return '📷'; + } else if (e.type == 'video') { + return 'đŸŽŦ'; + } else if (e.type == 'giphy') { + return '[GIF]'; + } + return e == lastMessage.attachments.last + ? (e.title ?? 'File') + : '${e.title ?? 'File'} , '; + }), + lastMessage.text ?? '', + ]; - text = parts.join(' '); + text = parts.join(' '); - return Text.rich( - _getDisplayText( - text, - lastMessage.mentionedUsers, - lastMessage.attachments, - StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( - color: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - ?.color, - fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) - ? FontStyle.italic - : FontStyle.normal), - StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( - color: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - ?.color, - fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) - ? FontStyle.italic - : FontStyle.normal, - fontWeight: FontWeight.bold, - ), - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - }, - ); - } + return Text.rich( + _getDisplayText( + text, + lastMessage.mentionedUsers, + lastMessage.attachments, + StreamChatTheme.of(context) + .channelPreviewTheme + .subtitle + ?.copyWith( + color: StreamChatTheme.of(context) + .channelPreviewTheme + .subtitle + ?.color, + fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) + ? FontStyle.italic + : FontStyle.normal), + StreamChatTheme.of(context) + .channelPreviewTheme + .subtitle + ?.copyWith( + color: StreamChatTheme.of(context) + .channelPreviewTheme + .subtitle + ?.color, + fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + fontWeight: FontWeight.bold, + ), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + }, + ); TextSpan _getDisplayText( String text, diff --git a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart index 620c41ac..7e4e7f8e 100644 --- a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart +++ b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'stream_chat.dart'; - /// Widget that builds itself based on the latest snapshot of interaction with /// a [Stream] of type [ConnectionStatus]. /// -/// The widget will use the closest [StreamChatClient.wsConnectionStatusStream] in case no -/// stream is provided. +/// The widget will use the closest [StreamChatClient.wsConnectionStatusStream] +/// in case no stream is provided. class ConnectionStatusBuilder extends StatelessWidget { /// Creates a new ConnectionStatusBuilder const ConnectionStatusBuilder({ @@ -47,11 +46,11 @@ class ConnectionStatusBuilder extends StatelessWidget { if (errorBuilder != null) { return errorBuilder!(context, snapshot.error); } - return Offstage(); + return const Offstage(); } if (!snapshot.hasData) { if (loadingBuilder != null) return loadingBuilder!(context); - return Offstage(); + return const Offstage(); } return statusBuilder(context, snapshot.data!); }, diff --git a/packages/stream_chat_flutter/lib/src/date_divider.dart b/packages/stream_chat_flutter/lib/src/date_divider.dart index 3674e085..e1b9cfea 100644 --- a/packages/stream_chat_flutter/lib/src/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/date_divider.dart @@ -4,15 +4,20 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; /// It shows a date divider depending on the date difference class DateDivider extends StatelessWidget { - final DateTime dateTime; - final bool uppercase; + /// Constructor for creating a [DateDivider] const DateDivider({ Key? key, required this.dateTime, this.uppercase = false, }) : super(key: key); + /// [DateTime] to display + final DateTime dateTime; + + /// If text is uppercase + final bool uppercase; + @override Widget build(BuildContext context) { final createdAt = Jiffy(dateTime); @@ -22,10 +27,10 @@ class DateDivider extends StatelessWidget { if (Jiffy(createdAt).isSame(now, Units.DAY)) { dayInfo = 'Today'; } else if (Jiffy(createdAt) - .isSame(now.subtract(Duration(days: 1)), Units.DAY)) { + .isSame(now.subtract(const Duration(days: 1)), Units.DAY)) { dayInfo = 'Yesterday'; } else if (Jiffy(createdAt).isAfter( - now.subtract(Duration(days: 7)), + now.subtract(const Duration(days: 7)), Units.DAY, )) { dayInfo = createdAt.EEEE; diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart index 31a9995e..47a5f2ee 100644 --- a/packages/stream_chat_flutter/lib/src/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart @@ -3,7 +3,10 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +/// Widget to display deleted message class DeletedMessage extends StatelessWidget { + + /// Constructor to create [DeletedMessage] const DeletedMessage({ Key? key, required this.messageTheme, @@ -29,8 +32,7 @@ class DeletedMessage extends StatelessWidget { final bool reverse; @override - Widget build(BuildContext context) { - return Transform( + Widget build(BuildContext context) => Transform( transform: Matrix4.rotationY(reverse ? pi : 0), alignment: Alignment.center, child: Material( @@ -70,5 +72,4 @@ class DeletedMessage extends StatelessWidget { ), ), ); - } } diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index 11ff3253..a65272c3 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -9,9 +9,7 @@ final _emojis = Emoji.all(); /// String extension extension StringExtension on String { /// Returns the capitalized string - String capitalize() { - return '${this[0].toUpperCase()}${substring(1)}'; - } + String capitalize() => '${this[0].toUpperCase()}${substring(1)}'; /// Returns whether the string contains only emoji's or not. /// @@ -46,7 +44,9 @@ extension PlatformFileX on PlatformFile { ); } +/// extension InputDecorationX on InputDecoration { + /// InputDecoration merge(InputDecoration? other) { if (other == null) return this; return copyWith( @@ -98,7 +98,9 @@ extension InputDecorationX on InputDecoration { } } +/// Gets text scale factor through context extension BuildContextX on BuildContext { + // ignore: public_member_api_docs double get textScaleFactor => MediaQuery.maybeOf(this)?.textScaleFactor ?? 1.0; } From 95f849e442d2f69b9176fcec2a803e91661ceb00 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 4 May 2021 14:37:30 +0200 Subject: [PATCH 189/599] bump stream_chat_flutter --- packages/stream_chat_flutter/CHANGELOG.md | 2 +- packages/stream_chat_flutter/pubspec.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index fd5d7d32..94e939af 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.0-nullsafety.1 +## 2.0.0-nullsafety.2 - Migrate this package to null safety - Updated `stream_chat_core` dependency diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 7a2725ed..57be4ed1 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.1 +version: 2.0.0-nullsafety.2 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - stream_chat_flutter_core: ^2.0.0-nullsafety.0 + stream_chat_flutter_core: ^2.0.0-nullsafety.1 photo_view: ^0.11.1 rxdart: ^0.26.0 scrollable_positioned_list: ^0.2.0-nullsafety.0 From c7b7618edf8e79c90eab1592e0bd1c74bbc252ed Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 14:24:24 +0530 Subject: [PATCH 190/599] lint changes media --- .../lib/src/full_screen_media.dart | 357 +++++++++--------- .../lib/src/group_image.dart | 20 +- 2 files changed, 204 insertions(+), 173 deletions(-) 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 4b56adc5..edebfa09 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -8,24 +8,24 @@ import 'package:jiffy/jiffy.dart'; import 'package:photo_view/photo_view.dart'; import 'package:stream_chat_flutter/src/image_footer.dart'; import 'package:stream_chat_flutter/src/image_header.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_player/video_player.dart'; -import '../stream_chat_flutter.dart'; +/// Return action for coming back from pages +enum ReturnActionType { -enum ReturnActionType { none, reply } + /// No return action + none, + /// Go to reply message action + reply, +} + +/// Callback when show message is tapped typedef ShowMessageCallback = void Function(Message message, Channel channel); /// A full screen image widget class FullScreenMedia extends StatefulWidget { - /// The url of the image - final List mediaAttachments; - final Message message; - - final int startIndex; - final String userName; - final ShowMessageCallback? onShowMessage; - /// Instantiate a new FullScreenImage const FullScreenMedia({ Key? key, @@ -37,6 +37,21 @@ class FullScreenMedia extends StatefulWidget { }) : userName = userName ?? '', super(key: key); + /// The url of the image + final List mediaAttachments; + + /// Message where attachments are attached + final Message message; + + /// First index of media shown + final int startIndex; + + /// Username of sender + final String userName; + + /// Callback for when show message is tapped + final ShowMessageCallback? onShowMessage; + @override _FullScreenMediaState createState() => _FullScreenMediaState(); } @@ -57,7 +72,7 @@ class _FullScreenMediaState extends State super.initState(); _controller = AnimationController( vsync: this, - duration: Duration(milliseconds: 300), + duration: const Duration(milliseconds: 300), ); _pageController = PageController(initialPage: widget.startIndex); _currentPage = widget.startIndex; @@ -77,139 +92,136 @@ class _FullScreenMediaState extends State } @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack( - children: [ - AnimatedBuilder( - animation: _controller, - builder: (context, snapshot) { - return 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( - 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: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .color, - end: Colors.black, - ).lerp(_controller.value), - ), - onTapUp: (a, b, c) { - setState(() { - _optionsShown = !_optionsShown; - }); - if (_controller.isCompleted) { - _controller.reverse(); - } else { - _controller.forward(); + Widget build(BuildContext context) => Scaffold( + resizeToAvoidBottomInset: false, + body: Stack( + children: [ + AnimatedBuilder( + 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( + 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: StreamChatTheme.of(context) + .channelTheme + .channelHeaderTheme + .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(), + ); } - }, - ); - } else if (attachment.type == 'video') { - final controller = videoPackages[attachment.id]!; - if (!controller.initialized) { - return 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: Duration(milliseconds: 300), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ImageHeader( - userName: widget.userName, - sentAt: - 'Sent ${getDay(widget.message.createdAt.toLocal())} at ${Jiffy(widget.message.createdAt.toLocal()).format('HH:mm')}', - onBackPressed: () { - Navigator.of(context).pop(); - }, - message: widget.message, - currentIndex: _currentPage, - onShowMessage: () { - widget.onShowMessage?.call( - widget.message, - StreamChannel.of(context).channel, - ); - }, - ), - if (widget.message.type != 'ephemeral') - ImageFooter( - currentPage: _currentPage, - totalPages: widget.mediaAttachments.length, - mediaAttachments: widget.mediaAttachments, + 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), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ImageHeader( + userName: widget.userName, + sentAt: + // ignore: lines_longer_than_80_chars + 'Sent ${getDay(widget.message.createdAt.toLocal())} at ${Jiffy(widget.message.createdAt.toLocal()).format('HH:mm')}', + onBackPressed: () { + Navigator.of(context).pop(); + }, message: widget.message, - mediaSelectedCallBack: (val) { - setState(() { - _currentPage = val; - _pageController.animateToPage( - val, - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - Navigator.pop(context); - }); + currentIndex: _currentPage, + onShowMessage: () { + widget.onShowMessage?.call( + widget.message, + StreamChannel.of(context).channel, + ); }, ), - ], + if (widget.message.type != 'ephemeral') + ImageFooter( + currentPage: _currentPage, + totalPages: widget.mediaAttachments.length, + mediaAttachments: widget.mediaAttachments, + message: widget.message, + mediaSelectedCallBack: (val) { + setState(() { + _currentPage = val; + _pageController.animateToPage( + val, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + Navigator.pop(context); + }); + }, + ), + ], + ), ), - ), - ], - ), - ); - } + ], + ), + ); String getDay(DateTime dateTime) { final now = DateTime.now(); @@ -236,47 +248,52 @@ class _FullScreenMediaState extends State } } +/// Class for packaging up things required for videos class VideoPackage { - final bool _showControls; - final bool _autoInitialize; - final VideoPlayerController _videoPlayerController; - ChewieController? _chewieController; - - VideoPlayerController get videoPlayer => _videoPlayerController; - - ChewieController? get chewieController => _chewieController; - - bool get initialized => _videoPlayerController.value.isInitialized; - + /// Constructor for creating [VideoPackage] VideoPackage( - Attachment attachment, { - bool showControls = false, - bool autoInitialize = true, - }) : _showControls = showControls, + Attachment attachment, { + bool showControls = false, + bool autoInitialize = true, + }) : _showControls = showControls, _autoInitialize = autoInitialize, _videoPlayerController = attachment.localUri != null ? VideoPlayerController.file(File.fromUri(attachment.localUri!)) : VideoPlayerController.network(attachment.assetUrl!); - Future initialize() { - return _videoPlayerController.initialize().then((_) { - _chewieController = ChewieController( - videoPlayerController: _videoPlayerController, - autoInitialize: _autoInitialize, - showControls: _showControls, - aspectRatio: _videoPlayerController.value.aspectRatio, - ); - }); - } + final bool _showControls; + final bool _autoInitialize; + final VideoPlayerController _videoPlayerController; + ChewieController? _chewieController; - void addListener(VoidCallback listener) { - return _videoPlayerController.addListener(listener); - } + /// Get video player for video + VideoPlayerController get videoPlayer => _videoPlayerController; - void removeListener(VoidCallback listener) { - return _videoPlayerController.removeListener(listener); - } + /// Get [ChewieController] for video + ChewieController? get chewieController => _chewieController; + /// Check if controller is initialised + bool get initialized => _videoPlayerController.value.isInitialized; + + /// Initialize all things required for [VideoPackage] + Future initialize() => _videoPlayerController.initialize().then((_) { + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + autoInitialize: _autoInitialize, + showControls: _showControls, + aspectRatio: _videoPlayerController.value.aspectRatio, + ); + }); + + /// Add a listener to video player controller + void addListener(VoidCallback listener) => + _videoPlayerController.addListener(listener); + + /// Remove a listener to video player controller + void removeListener(VoidCallback listener) => + _videoPlayerController.removeListener(listener); + + /// Dispose controllers Future dispose() { _chewieController?.dispose(); return _videoPlayerController.dispose(); diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index 3e56eeae..e5c6ecd2 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -1,9 +1,10 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import '../stream_chat_flutter.dart'; - +/// Widget for constructing a group of images class GroupImage extends StatelessWidget { + /// Constructor for creating a [GroupImage] const GroupImage({ Key? key, required this.images, @@ -15,17 +16,30 @@ class GroupImage extends StatelessWidget { this.selectionThickness = 4, }) : super(key: key); + /// List of images to display final List images; + + /// Constraints on the widget final BoxConstraints? constraints; + + /// Callback when widget is tapped final VoidCallback? onTap; + + /// Highlights if selected final bool selected; + + /// [BorderRadius] to pass to the widget final BorderRadius? borderRadius; + + /// Color of selection if selected final Color? selectionColor; + + /// Thickness with which color of selection is shown final double selectionThickness; @override Widget build(BuildContext context) { - var avatar; + Widget? avatar; final streamChatTheme = StreamChatTheme.of(context); avatar = GestureDetector( From a688ab3a1097426a33a3c2d31ad8a217a3fb2e28 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 14:59:21 +0530 Subject: [PATCH 191/599] lint changes image --- .../lib/src/attachment_actions_modal.dart | 43 +-- .../lib/src/back_button.dart | 63 +++-- .../lib/src/channel_image.dart | 50 ++-- .../lib/src/channel_list_header.dart | 99 +++---- .../lib/src/channel_list_view.dart | 1 - .../lib/src/channel_name.dart | 6 +- .../lib/src/channel_preview.dart | 1 - .../lib/src/date_divider.dart | 1 - .../lib/src/deleted_message.dart | 69 +++-- .../lib/src/full_screen_media.dart | 9 +- .../lib/src/image_footer.dart | 262 ++++++++---------- .../lib/src/image_group.dart | 153 +++++----- .../lib/src/image_header.dart | 160 +++++------ .../lib/src/message_input.dart | 213 +++++++------- 14 files changed, 561 insertions(+), 569 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart index a8d1bddd..a9ea651b 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart @@ -192,29 +192,30 @@ class AttachmentActionsModal extends StatelessWidget { VoidCallback? onTap, { Color? color, Key? key, - }) => Material( - key: key, - color: StreamChatTheme.of(context).colorTheme.white, - child: InkWell( - onTap: onTap, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), - child: Row( - children: [ - icon, - SizedBox(width: 16), - Text( - title, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith(color: color), - ), - ], + }) => + Material( + key: key, + color: StreamChatTheme.of(context).colorTheme.white, + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: Row( + children: [ + icon, + SizedBox(width: 16), + Text( + title, + style: StreamChatTheme.of(context) + .textTheme + .body + .copyWith(color: color), + ), + ], + ), ), ), - ), - ); + ); Widget _buildDownloadProgressDialog( BuildContext context, diff --git a/packages/stream_chat_flutter/lib/src/back_button.dart b/packages/stream_chat_flutter/lib/src/back_button.dart index acb3d1ec..e8fe7e31 100644 --- a/packages/stream_chat_flutter/lib/src/back_button.dart +++ b/packages/stream_chat_flutter/lib/src/back_button.dart @@ -5,7 +5,6 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Back button implementation class StreamBackButton extends StatelessWidget { - /// Constructor for creating back button const StreamBackButton({ Key? key, @@ -25,37 +24,37 @@ class StreamBackButton extends StatelessWidget { @override Widget build(BuildContext context) => Stack( - alignment: Alignment.center, - children: [ - RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - ), - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - hoverElevation: 0, - onPressed: () { - if (onPressed != null) { - onPressed!(); - } else { - Navigator.maybePop(context); - } - }, - padding: const EdgeInsets.all(14), - child: StreamSvgIcon.left( - size: 24, - color: StreamChatTheme.of(context).colorTheme.black, - ), - ), - if (showUnreads) - Positioned( - top: 7, - right: 7, - child: UnreadIndicator( - cid: cid, + alignment: Alignment.center, + children: [ + RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + hoverElevation: 0, + onPressed: () { + if (onPressed != null) { + onPressed!(); + } else { + Navigator.maybePop(context); + } + }, + padding: const EdgeInsets.all(14), + child: StreamSvgIcon.left( + size: 24, + color: StreamChatTheme.of(context).colorTheme.black, ), ), - ], - ); + if (showUnreads) + Positioned( + top: 7, + right: 7, + child: UnreadIndicator( + cid: cid, + ), + ), + ], + ); } diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 3f3a38be..8116ca83 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -98,23 +98,23 @@ class ChannelImage extends StatelessWidget { (users) => users[otherMember?.userId] ?? otherMember!.user!), initialData: otherMember!.user, builder: (context, snapshot) => UserAvatar( - borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.borderRadius, - user: snapshot.data ?? otherMember.user!, - constraints: constraints ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.constraints, - onTap: onTap != null ? (_) => onTap!() : null, - selected: selected, - selectionColor: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, - selectionThickness: selectionThickness, - )); + borderRadius: borderRadius ?? + StreamChatTheme.of(context) + .channelPreviewTheme + .avatarTheme + ?.borderRadius, + user: snapshot.data ?? otherMember.user!, + constraints: constraints ?? + StreamChatTheme.of(context) + .channelPreviewTheme + .avatarTheme + ?.constraints, + onTap: onTap != null ? (_) => onTap!() : null, + selected: selected, + selectionColor: selectionColor ?? + StreamChatTheme.of(context).colorTheme.accentBlue, + selectionThickness: selectionThickness, + )); } else { final images = channel.state?.members .where((member) => @@ -167,16 +167,16 @@ class ChannelImage extends StatelessWidget { CachedNetworkImage( imageUrl: image, errorWidget: (_, __, ___) => Center( - child: Text( - snapshot.data?.containsKey('name') ?? false - ? snapshot.data!['name'][0] - : '', - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.white, - fontWeight: FontWeight.bold, - ), + child: Text( + snapshot.data?.containsKey('name') ?? false + ? snapshot.data!['name'][0] + : '', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.white, + fontWeight: FontWeight.bold, ), ), + ), fit: BoxFit.cover, ) else 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 c8d2aa69..afc1884c 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -217,64 +217,65 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { ); Widget _buildConnectingTitleState(BuildContext context) => Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox( - height: 16, - width: 16, - child: Center( - child: CircularProgressIndicator(), + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox( + height: 16, + width: 16, + child: Center( + child: CircularProgressIndicator(), + ), ), - ), - const SizedBox(width: 10), - Text( - 'Searching for Network', - style: StreamChatTheme.of(context) - .channelListHeaderTheme - .title - ?.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ); - - Widget _buildDisconnectedTitleState( - BuildContext context, - StreamChatClient client, - ) => Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Offline...', - style: StreamChatTheme.of(context) - .channelListHeaderTheme - .title - ?.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - TextButton( - onPressed: () async { - await client.disconnect(); - await client.connect(); - }, - child: Text( - 'Try Again', + const SizedBox(width: 10), + Text( + 'Searching for Network', style: StreamChatTheme.of(context) .channelListHeaderTheme .title ?.copyWith( fontSize: 16, fontWeight: FontWeight.bold, - color: StreamChatTheme.of(context).colorTheme.accentBlue, ), ), - ), - ], - ); + ], + ); + + Widget _buildDisconnectedTitleState( + BuildContext context, + StreamChatClient client, + ) => + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Offline...', + style: StreamChatTheme.of(context) + .channelListHeaderTheme + .title + ?.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + TextButton( + onPressed: () async { + await client.disconnect(); + await client.connect(); + }, + child: Text( + 'Try Again', + style: StreamChatTheme.of(context) + .channelListHeaderTheme + .title + ?.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + color: StreamChatTheme.of(context).colorTheme.accentBlue, + ), + ), + ), + ], + ); @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); 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 c58ed42f..fb713ea8 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -9,7 +9,6 @@ import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - /// Callback called when tapping on a channel typedef ChannelTapCallback = void Function(Channel, Widget?); diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart index d76bca0c..1cc510af 100644 --- a/packages/stream_chat_flutter/lib/src/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel_name.dart @@ -3,7 +3,6 @@ import 'package:flutter/rendering.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - /// It shows the current [Channel] name using a [Text] widget. /// /// The widget uses a [StreamBuilder] to render the channel information @@ -60,9 +59,8 @@ class ChannelName extends StatelessWidget { final exceedingMembers = otherMembers.length - currentMembers.length; - title = - '${currentMembers.map((e) => e.user?.name).join(', ')} ' - '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + title = '${currentMembers.map((e) => e.user?.name).join(', ')} ' + '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; } else { title = 'No title'; } diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 9c793ba6..70ad0e3f 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -21,7 +21,6 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// [StreamChatTheme]. /// Modify it to change the widget appearance. class ChannelPreview extends StatelessWidget { - /// Constructor for creating [ChannelPreview] const ChannelPreview({ required this.channel, diff --git a/packages/stream_chat_flutter/lib/src/date_divider.dart b/packages/stream_chat_flutter/lib/src/date_divider.dart index e1b9cfea..f409872b 100644 --- a/packages/stream_chat_flutter/lib/src/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/date_divider.dart @@ -4,7 +4,6 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; /// It shows a date divider depending on the date difference class DateDivider extends StatelessWidget { - /// Constructor for creating a [DateDivider] const DateDivider({ Key? key, diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart index 47a5f2ee..96f5606a 100644 --- a/packages/stream_chat_flutter/lib/src/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart @@ -5,7 +5,6 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; /// Widget to display deleted message class DeletedMessage extends StatelessWidget { - /// Constructor to create [DeletedMessage] const DeletedMessage({ Key? key, @@ -33,43 +32,43 @@ class DeletedMessage extends StatelessWidget { @override Widget build(BuildContext context) => Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: Material( - color: messageTheme.messageBackgroundColor, - shape: shape ?? - RoundedRectangleBorder( - borderRadius: borderRadiusGeometry ?? BorderRadius.zero, - side: borderSide ?? - BorderSide( - color: Theme.of(context).brightness == Brightness.dark - ? StreamChatTheme.of(context) - .colorTheme - .white - .withAlpha(24) - : StreamChatTheme.of(context) - .colorTheme - .black - .withAlpha(24), - ), + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Material( + color: messageTheme.messageBackgroundColor, + shape: shape ?? + RoundedRectangleBorder( + borderRadius: borderRadiusGeometry ?? BorderRadius.zero, + side: borderSide ?? + BorderSide( + color: Theme.of(context).brightness == Brightness.dark + ? StreamChatTheme.of(context) + .colorTheme + .white + .withAlpha(24) + : StreamChatTheme.of(context) + .colorTheme + .black + .withAlpha(24), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, - ), - child: Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: Text( - 'Message deleted', - style: messageTheme.messageText?.copyWith( - fontStyle: FontStyle.italic, - color: messageTheme.createdAt?.color, + child: Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Text( + 'Message deleted', + style: messageTheme.messageText?.copyWith( + fontStyle: FontStyle.italic, + color: messageTheme.createdAt?.color, + ), ), ), ), ), - ), - ); + ); } 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 edebfa09..f86ac60b 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -13,7 +13,6 @@ import 'package:video_player/video_player.dart'; /// Return action for coming back from pages enum ReturnActionType { - /// No return action none, @@ -252,10 +251,10 @@ class _FullScreenMediaState extends State class VideoPackage { /// Constructor for creating [VideoPackage] VideoPackage( - Attachment attachment, { - bool showControls = false, - bool autoInitialize = true, - }) : _showControls = showControls, + Attachment attachment, { + bool showControls = false, + bool autoInitialize = true, + }) : _showControls = showControls, _autoInitialize = autoInitialize, _videoPlayerController = attachment.localUri != null ? VideoPlayerController.file(File.fromUri(attachment.localUri!)) diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/image_footer.dart index f660bad7..9298e73b 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/image_footer.dart @@ -12,7 +12,22 @@ import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +/// Footer widget for media display class ImageFooter extends StatefulWidget implements PreferredSizeWidget { + /// Creates a channel header + const ImageFooter({ + Key? key, + required this.message, + this.onBackPressed, + this.onTitleTap, + this.onImageTap, + this.currentPage = 0, + this.totalPages = 0, + this.mediaAttachments = const [], + this.mediaSelectedCallBack, + }) : preferredSize = const Size.fromHeight(kToolbarHeight), + super(key: key); + /// Callback to call when pressing the back button. /// By default it calls [Navigator.pop] final VoidCallback? onBackPressed; @@ -23,28 +38,21 @@ class ImageFooter extends StatefulWidget implements PreferredSizeWidget { /// Callback to call when the image is tapped. final VoidCallback? onImageTap; + /// Stores the current index of media shown final int currentPage; + + /// Total number of pages of media final int totalPages; + /// All attachments to show final List mediaAttachments; + + /// Message which attachments are attached to final Message message; + /// Callback when media is selected final ValueChanged? mediaSelectedCallBack; - /// Creates a channel header - ImageFooter({ - Key? key, - required this.message, - this.onBackPressed, - this.onTitleTap, - this.onImageTap, - this.currentPage = 0, - this.totalPages = 0, - this.mediaAttachments = const [], - this.mediaSelectedCallBack, - }) : preferredSize = Size.fromHeight(kToolbarHeight), - super(key: key); - @override _ImageFooterState createState() => _ImageFooterState(); @@ -119,7 +127,7 @@ class _ImageFooterState extends State { ), InkWell( onTap: widget.onTitleTap, - child: Container( + child: SizedBox( child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, @@ -169,115 +177,115 @@ class _ImageFooterState extends State { expand: false, initialChildSize: initialChildSize / size.height, minChildSize: initialChildSize / size.height, - builder: (context, scrollController) { - return SingleChildScrollView( - controller: scrollController, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Stack( - children: [ - Center( - child: Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'Photos', - style: StreamChatTheme.of(context) - .textTheme - .headlineBold, - ), + builder: (context, scrollController) => SingleChildScrollView( + controller: scrollController, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'Photos', + style: StreamChatTheme.of(context) + .textTheme + .headlineBold, ), ), - Align( - alignment: Alignment.centerRight, - child: IconButton( - icon: StreamSvgIcon.close( - color: StreamChatTheme.of(context).colorTheme.black, - ), - onPressed: () => Navigator.maybePop(context), + ), + Align( + alignment: Alignment.centerRight, + child: IconButton( + icon: StreamSvgIcon.close( + color: StreamChatTheme.of(context).colorTheme.black, ), + onPressed: () => Navigator.maybePop(context), ), - ], - ), - Flexible( - child: GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: widget.mediaAttachments.length, - padding: const EdgeInsets.all(1), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, - mainAxisSpacing: 2, - crossAxisSpacing: 2, - ), - itemBuilder: (context, index) { - Widget media; - final attachment = widget.mediaAttachments[index]; - if (attachment.type == 'video') { - media = InkWell( - onTap: () => widget.mediaSelectedCallBack!(index), - child: FittedBox( + ), + ], + ), + Flexible( + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.mediaAttachments.length, + padding: const EdgeInsets.all(1), + // ignore: lines_longer_than_80_chars + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 2, + crossAxisSpacing: 2, + ), + itemBuilder: (context, index) { + Widget media; + final attachment = widget.mediaAttachments[index]; + if (attachment.type == 'video') { + media = InkWell( + onTap: () => widget.mediaSelectedCallBack!(index), + child: FittedBox( + fit: BoxFit.cover, + child: VideoThumbnailImage( + video: (attachment.file?.path ?? + attachment.assetUrl)!, + ), + ), + ); + } else { + media = InkWell( + onTap: () => widget.mediaSelectedCallBack!(index), + child: AspectRatio( + aspectRatio: 1, + child: CachedNetworkImage( + imageUrl: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl!, fit: BoxFit.cover, - child: VideoThumbnailImage( - video: (attachment.file?.path ?? - attachment.assetUrl)!, - ), ), - ); - } else { - media = InkWell( - onTap: () => widget.mediaSelectedCallBack!(index), - child: AspectRatio( - aspectRatio: 1, - child: CachedNetworkImage( - imageUrl: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl!, - fit: BoxFit.cover, - ), - ), - ); - } + ), + ); + } - return Stack( - children: [ - media, - if (widget.message.user != null) - Padding( - padding: EdgeInsets.all(8), - child: Container( - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withOpacity(0.6), - boxShadow: [ - BoxShadow( - blurRadius: 8, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.3), - ), - ], - ), - padding: const EdgeInsets.all(2), - child: UserAvatar( - user: widget.message.user!, - constraints: - BoxConstraints.tight(Size(24, 24)), - showOnlineStatus: false, - ), + return Stack( + 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: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.3), + ), + ], + ), + padding: const EdgeInsets.all(2), + child: UserAvatar( + user: widget.message.user!, + constraints: + BoxConstraints.tight(const Size(24, 24)), + showOnlineStatus: false, ), ), - ], - ); - }, - ), + ), + ], + ); + }, ), - ], - ), - ); - }, + ), + ], + ), + ), ); }, ); @@ -304,29 +312,3 @@ class _ImageFooterState extends State { Navigator.pop(context); } } - -/// Used for clipping textfield prefix icon -class IconClipper extends CustomClipper { - @override - Path getClip(Size size) { - final leftX = size.width / 5; - final rightX = 4 * size.width / 5; - final topY = size.height / 5; - final bottomY = 4 * size.height / 5; - - final path = Path(); - path.moveTo(leftX, topY); - path.lineTo(leftX, bottomY); - path.lineTo(rightX, bottomY); - path.lineTo(rightX, topY); - path.lineTo(leftX, topY); - path.lineTo(0, 0); - path.close(); - return path; - } - - @override - bool shouldReclip(CustomClipper oldClipper) { - return false; - } -} diff --git a/packages/stream_chat_flutter/lib/src/image_group.dart b/packages/stream_chat_flutter/lib/src/image_group.dart index 600b7867..51c686c3 100644 --- a/packages/stream_chat_flutter/lib/src/image_group.dart +++ b/packages/stream_chat_flutter/lib/src/image_group.dart @@ -3,7 +3,9 @@ import 'package:stream_chat_flutter/src/full_screen_media.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +/// Widget for constructing a group of images in message class ImageGroup extends StatelessWidget { + /// Constructor for creating [ImageGroup] widget const ImageGroup({ Key? key, required this.images, @@ -13,91 +15,98 @@ class ImageGroup extends StatelessWidget { this.onShowMessage, }) : super(key: key); + /// List of attachments to show final List images; + + /// Message which images are attached to final Message message; + + /// [MessageTheme] to apply to message final MessageTheme messageTheme; + + /// Size of iamges final Size size; + + /// Callback for when show message is tapped final ShowMessageCallback? onShowMessage; @override - Widget build(BuildContext context) { - return ConstrainedBox( - constraints: BoxConstraints.loose(size), - child: Flex( - direction: Axis.vertical, - children: [ - Flexible( - fit: FlexFit.tight, - child: Flex( - crossAxisAlignment: CrossAxisAlignment.stretch, - direction: Axis.horizontal, - children: [ - Flexible( - fit: FlexFit.tight, - child: _buildImage(context, 0), - ), - Flexible( - fit: FlexFit.tight, - child: Padding( - padding: const EdgeInsets.only(left: 2), - child: _buildImage(context, 1), - ), - ), - ], - ), - ), - if (images.length >= 3) + Widget build(BuildContext context) => ConstrainedBox( + constraints: BoxConstraints.loose(size), + child: Flex( + direction: Axis.vertical, + children: [ Flexible( fit: FlexFit.tight, - child: Padding( - padding: const EdgeInsets.only(top: 2), - child: Flex( - direction: Axis.horizontal, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Flexible( - fit: FlexFit.tight, - child: _buildImage(context, 2), + child: Flex( + crossAxisAlignment: CrossAxisAlignment.stretch, + direction: Axis.horizontal, + children: [ + Flexible( + fit: FlexFit.tight, + child: _buildImage(context, 0), + ), + Flexible( + fit: FlexFit.tight, + child: Padding( + padding: const EdgeInsets.only(left: 2), + child: _buildImage(context, 1), ), - if (images.length >= 4) + ), + ], + ), + ), + if (images.length >= 3) + Flexible( + fit: FlexFit.tight, + child: Padding( + padding: const EdgeInsets.only(top: 2), + child: Flex( + direction: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ Flexible( fit: FlexFit.tight, - child: Padding( - padding: const EdgeInsets.only(left: 2), - child: Stack( - fit: StackFit.expand, - children: [ - _buildImage(context, 3), - if (images.length > 4) - Positioned.fill( - child: GestureDetector( - onTap: () => _onTap(context, 3), - child: Material( - color: Colors.black38, - child: Center( - child: Text( - '+ ${images.length - 4}', - style: TextStyle( - color: Colors.white, - fontSize: 26, + child: _buildImage(context, 2), + ), + if (images.length >= 4) + Flexible( + fit: FlexFit.tight, + child: Padding( + padding: const EdgeInsets.only(left: 2), + child: Stack( + fit: StackFit.expand, + children: [ + _buildImage(context, 3), + if (images.length > 4) + Positioned.fill( + child: GestureDetector( + onTap: () => _onTap(context, 3), + child: Material( + color: Colors.black38, + child: Center( + child: Text( + '+ ${images.length - 4}', + style: const TextStyle( + color: Colors.white, + fontSize: 26, + ), ), ), ), ), ), - ), - ], + ], + ), ), ), - ), - ], + ], + ), ), ), - ), - ], - ), - ); - } + ], + ), + ); void _onTap( BuildContext context, @@ -122,13 +131,11 @@ class ImageGroup extends StatelessWidget { ); } - Widget _buildImage(BuildContext context, int index) { - return ImageAttachment( - attachment: images[index], - size: size, - message: message, - messageTheme: messageTheme, - onAttachmentTap: () => _onTap(context, index), - ); - } + Widget _buildImage(BuildContext context, int index) => ImageAttachment( + attachment: images[index], + size: size, + message: message, + messageTheme: messageTheme, + onAttachmentTap: () => _onTap(context, index), + ); } diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/image_header.dart index 18c78770..0b876aa1 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/image_header.dart @@ -1,11 +1,26 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/attachment_actions_modal.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'attachment_actions_modal.dart'; - +/// Header/AppBar widget for media display screen class ImageHeader extends StatelessWidget implements PreferredSizeWidget { + /// Creates a channel header + const ImageHeader({ + Key? key, + required this.message, + this.currentIndex = 0, + this.showBackButton = true, + this.onBackPressed, + this.onShowMessage, + this.onTitleTap, + this.onImageTap, + this.userName = '', + this.sentAt = '', + }) : preferredSize = const Size.fromHeight(kToolbarHeight), + super(key: key); + /// True if this header shows the leading back button final bool showBackButton; @@ -22,83 +37,72 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { /// Callback to call when the image is tapped. final VoidCallback? onImageTap; + /// Message which attachments are attached to final Message message; + /// Username of sender final String userName; + + /// Text which connotes the time the message was sent final String sentAt; + /// Stores the current index of media shown final int currentIndex; - /// Creates a channel header - ImageHeader({ - Key? key, - required this.message, - this.currentIndex = 0, - this.showBackButton = true, - this.onBackPressed, - this.onShowMessage, - this.onTitleTap, - this.onImageTap, - this.userName = '', - this.sentAt = '', - }) : preferredSize = Size.fromHeight(kToolbarHeight), - super(key: key); - @override - Widget build(BuildContext context) { - return AppBar( - brightness: Theme.of(context).brightness, - elevation: 1, - leading: showBackButton - ? IconButton( - icon: StreamSvgIcon.close( - color: StreamChatTheme.of(context).colorTheme.black, - size: 24, - ), - onPressed: onBackPressed, - ) - : SizedBox(), - backgroundColor: - StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, - actions: [ - if (message.type != 'ephemeral') - IconButton( - icon: StreamSvgIcon.iconMenuPoint( - color: StreamChatTheme.of(context).colorTheme.black, - ), - onPressed: () { - _showMessageActionModalBottomSheet(context); - }, - ), - ], - centerTitle: true, - title: message.type != 'ephemeral' - ? InkWell( - onTap: onTitleTap, - child: Container( - height: preferredSize.height, - width: preferredSize.width, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - userName, - style: StreamChatTheme.of(context).textTheme.headlineBold, - ), - Text( - sentAt, - style: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle, - ), - ], + Widget build(BuildContext context) => AppBar( + brightness: Theme.of(context).brightness, + elevation: 1, + leading: showBackButton + ? IconButton( + icon: StreamSvgIcon.close( + color: StreamChatTheme.of(context).colorTheme.black, + size: 24, ), + onPressed: onBackPressed, + ) + : const SizedBox(), + backgroundColor: + StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, + actions: [ + if (message.type != 'ephemeral') + IconButton( + icon: StreamSvgIcon.iconMenuPoint( + color: StreamChatTheme.of(context).colorTheme.black, ), - ) - : SizedBox(), - ); - } + onPressed: () { + _showMessageActionModalBottomSheet(context); + }, + ), + ], + centerTitle: true, + title: message.type != 'ephemeral' + ? InkWell( + onTap: onTitleTap, + child: SizedBox( + height: preferredSize.height, + width: preferredSize.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + userName, + style: + StreamChatTheme.of(context).textTheme.headlineBold, + ), + Text( + sentAt, + style: StreamChatTheme.of(context) + .channelPreviewTheme + .subtitle, + ), + ], + ), + ), + ) + : const SizedBox(), + ); @override final Size preferredSize; @@ -109,16 +113,14 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { final result = await showDialog( context: context, barrierColor: StreamChatTheme.of(context).colorTheme.overlay, - builder: (context) { - return StreamChannel( - channel: channel, - child: AttachmentActionsModal( - message: message, - currentIndex: currentIndex, - onShowMessage: onShowMessage, - ), - ); - }, + builder: (context) => StreamChannel( + channel: channel, + child: AttachmentActionsModal( + message: message, + currentIndex: currentIndex, + onShowMessage: onShowMessage, + ), + ), ); if (result != null) { diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index b0335e52..638c7769 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -812,116 +812,123 @@ class MessageInputState extends State { final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; - return OverlayEntry(builder: (context) => Positioned( - bottom: size.height + MediaQuery.of(context).viewInsets.bottom, - left: 0, - right: 0, - child: TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - duration: Duration(milliseconds: 300), - curve: Curves.easeInOutExpo, - builder: (context, val, wid) => Transform.scale( - scale: val, - child: Padding( - padding: const EdgeInsets.all(8), - child: Card( - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - color: StreamChatTheme.of(context).colorTheme.white, - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose(Size.fromHeight(400)), - decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, - borderRadius: BorderRadius.circular(8)), - child: ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - if (commands.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Row( + return OverlayEntry( + builder: (context) => Positioned( + bottom: size.height + MediaQuery.of(context).viewInsets.bottom, + left: 0, + right: 0, + child: TweenAnimationBuilder( + tween: Tween(begin: 0, end: 1), + duration: Duration(milliseconds: 300), + curve: Curves.easeInOutExpo, + builder: (context, val, wid) => Transform.scale( + scale: val, + child: Padding( + padding: const EdgeInsets.all(8), + child: Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + color: StreamChatTheme.of(context).colorTheme.white, + clipBehavior: Clip.antiAlias, + child: Container( + constraints: + BoxConstraints.loose(Size.fromHeight(400)), + decoration: BoxDecoration( + color: StreamChatTheme.of(context) + .colorTheme + .white, + borderRadius: BorderRadius.circular(8)), + child: ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - ), - child: StreamSvgIcon.lightning( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, + if (commands.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + ), + child: StreamSvgIcon.lightning( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, + ), + ), + Text( + 'Instant Commands', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + ) + ], + ), ), + const SizedBox( + height: 10, ), - Text( - 'Instant Commands', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - ) + ...commands + .map( + (c) => InkWell( + onTap: () { + _setCommand(c); + }, + child: SizedBox( + height: 40, + child: Row( + children: [ + const SizedBox( + width: 16, + ), + _buildCommandIcon(c.name), + const SizedBox( + width: 8, + ), + Text.rich( + TextSpan( + text: c.name.capitalize(), + style: const TextStyle( + fontWeight: + FontWeight.bold), + children: [ + TextSpan( + text: + ' /${c.name} ${c.args}', + style: + StreamChatTheme.of( + context) + .textTheme + .body + .copyWith( + color: StreamChatTheme.of( + context) + .colorTheme + .grey, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ) + .toList(), ], ), ), - const SizedBox( - height: 10, ), - ...commands - .map( - (c) => InkWell( - onTap: () { - _setCommand(c); - }, - child: SizedBox( - height: 40, - child: Row( - children: [ - const SizedBox( - width: 16, - ), - _buildCommandIcon(c.name), - const SizedBox( - width: 8, - ), - Text.rich( - TextSpan( - text: c.name.capitalize(), - style: const TextStyle( - fontWeight: FontWeight.bold), - children: [ - TextSpan( - text: ' /${c.name} ${c.args}', - style: - StreamChatTheme.of(context) - .textTheme - .body - .copyWith( - color: StreamChatTheme - .of(context) - .colorTheme - .grey, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ) - .toList(), - ], - ), - ), - ), - ), - )), - )); + ), + )), + )); } Widget _buildFilePickerSection() { From 5ad74b8f2b1184384e999f440a1cf40b90ee375a Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 15:10:46 +0530 Subject: [PATCH 192/599] lint changes --- .../lib/src/info_tile.dart | 76 +++--- .../lib/src/media_list_view.dart | 218 +++++++++--------- .../lib/src/mention_tile.dart | 147 ++++++------ .../lib/src/message_action.dart | 14 +- 4 files changed, 238 insertions(+), 217 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/info_tile.dart b/packages/stream_chat_flutter/lib/src/info_tile.dart index 53a9755f..81a701c0 100644 --- a/packages/stream_chat_flutter/lib/src/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/info_tile.dart @@ -2,16 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +/// Tile to display a message, used in stream chat to display connection status class InfoTile extends StatelessWidget { - final String message; - final Widget child; - final bool showMessage; - final Alignment? tileAnchor; - final Alignment? childAnchor; - final TextStyle? textStyle; - final Color? backgroundColor; - + /// Constructor for creating an [InfoTile] widget const InfoTile({ + Key? key, required this.message, required this.child, required this.showMessage, @@ -19,31 +14,50 @@ class InfoTile extends StatelessWidget { this.childAnchor, this.textStyle, this.backgroundColor, - }); + }) : super(key: key); + + /// String to display + final String message; + + /// Widget to display over + final Widget child; + + /// Flag to show message + final bool showMessage; + + /// Anchor for tile - [portalAnchor] for [PortalEntry] + final Alignment? tileAnchor; + + /// Alignment for child - [childAnchor] for [PortalEntry] + final Alignment? childAnchor; + + /// [TextStyle] for message + final TextStyle? textStyle; + + /// Background color for tile + final Color? backgroundColor; @override - Widget build(BuildContext context) { - return PortalEntry( - visible: showMessage, - portalAnchor: tileAnchor ?? Alignment.topCenter, - childAnchor: childAnchor ?? Alignment.bottomCenter, - portal: Container( - height: 25, - color: backgroundColor ?? - StreamChatTheme.of(context).colorTheme.grey.withOpacity(0.9), - child: Center( - child: Text( - message, - style: textStyle ?? - StreamChatTheme.of(context).textTheme.body.copyWith( - color: Colors.white, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + Widget build(BuildContext context) => PortalEntry( + visible: showMessage, + portalAnchor: tileAnchor ?? Alignment.topCenter, + childAnchor: childAnchor ?? Alignment.bottomCenter, + portal: Container( + height: 25, + color: backgroundColor ?? + StreamChatTheme.of(context).colorTheme.grey.withOpacity(0.9), + child: Center( + child: Text( + message, + style: textStyle ?? + StreamChatTheme.of(context).textTheme.body.copyWith( + color: Colors.white, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), ), - ), - child: child, - ); - } + child: child, + ); } 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 a34dc8dd..e0d0c27e 100644 --- a/packages/stream_chat_flutter/lib/src/media_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/media_list_view.dart @@ -5,8 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; - -import '../stream_chat_flutter.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; extension on Duration { String format() { @@ -19,16 +18,21 @@ extension on Duration { } } +/// Constructs a list of media class MediaListView extends StatefulWidget { - final List selectedIds; - final void Function(AssetEntity media)? onSelect; - + /// Constructor for creating a [MediaListView] widget const MediaListView({ Key? key, this.selectedIds = const [], this.onSelect, }) : super(key: key); + /// Stores the media selected + final List selectedIds; + + /// Callback for on media selected + final void Function(AssetEntity media)? onSelect; + @override _MediaListViewState createState() => _MediaListViewState(); } @@ -39,103 +43,103 @@ class _MediaListViewState extends State { int _currentPage = 0; @override - Widget build(BuildContext context) { - return LazyLoadScrollView( - onEndOfPage: () async => _getMedia(), - child: GridView.builder( - itemCount: _media.length, - controller: _scrollController, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - ), - itemBuilder: ( - context, - position, - ) { - final media = _media.elementAt(position); - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 1, vertical: 1), - child: InkWell( - onTap: () { - if (widget.onSelect != null) { - widget.onSelect!(media); - } - }, - child: Stack( - children: [ - AspectRatio( - aspectRatio: 1, - child: FadeInImage( - fadeInDuration: Duration(milliseconds: 300), - placeholder: AssetImage( - 'images/placeholder.png', - package: 'stream_chat_flutter', + Widget build(BuildContext context) => LazyLoadScrollView( + onEndOfPage: () async => _getMedia(), + child: GridView.builder( + itemCount: _media.length, + controller: _scrollController, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + ), + itemBuilder: ( + context, + position, + ) { + final media = _media.elementAt(position); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 1, vertical: 1), + child: InkWell( + onTap: () { + if (widget.onSelect != null) { + widget.onSelect!(media); + } + }, + child: Stack( + children: [ + AspectRatio( + aspectRatio: 1, + child: FadeInImage( + fadeInDuration: const Duration(milliseconds: 300), + placeholder: const AssetImage( + 'images/placeholder.png', + package: 'stream_chat_flutter', + ), + image: MediaThumbnailProvider( + media: media, + ), + fit: BoxFit.cover, ), - image: MediaThumbnailProvider( - media: media, - ), - fit: BoxFit.cover, ), - ), - Positioned.fill( - child: IgnorePointer( - child: AnimatedOpacity( - duration: Duration(milliseconds: 300), - opacity: widget.selectedIds.any((id) => id == media.id) - ? 1.0 - : 0.0, - child: Container( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), - alignment: Alignment.topRight, - padding: const EdgeInsets.only( - top: 8, - right: 8, - ), - child: CircleAvatar( - radius: 12, - backgroundColor: - StreamChatTheme.of(context).colorTheme.white, - child: StreamSvgIcon.check( - size: 24, - color: - StreamChatTheme.of(context).colorTheme.black, + Positioned.fill( + child: IgnorePointer( + child: AnimatedOpacity( + duration: const Duration(milliseconds: 300), + opacity: + widget.selectedIds.any((id) => id == media.id) + ? 1.0 + : 0.0, + child: Container( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + alignment: Alignment.topRight, + padding: const EdgeInsets.only( + top: 8, + right: 8, + ), + child: CircleAvatar( + radius: 12, + backgroundColor: + StreamChatTheme.of(context).colorTheme.white, + child: StreamSvgIcon.check( + size: 24, + color: StreamChatTheme.of(context) + .colorTheme + .black, + ), ), ), ), ), ), - ), - if (media.type == AssetType.video) ...[ - Positioned( - left: 8, - bottom: 10, - child: SvgPicture.asset( - 'svgs/video_call_icon.svg', - package: 'stream_chat_flutter', - ), - ), - Positioned( - right: 4, - bottom: 10, - child: Text( - media.videoDuration.format(), - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.white, + if (media.type == AssetType.video) ...[ + Positioned( + left: 8, + bottom: 10, + child: SvgPicture.asset( + 'svgs/video_call_icon.svg', + package: 'stream_chat_flutter', ), ), - ), - ] - ], + Positioned( + right: 4, + bottom: 10, + child: Text( + media.videoDuration.format(), + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.white, + ), + ), + ), + ] + ], + ), ), - ), - ); - }, - ), - ); - } + ); + }, + ), + ); @override void initState() { @@ -165,36 +169,38 @@ class _MediaListViewState extends State { } } +/// ImageProvider implementation class MediaThumbnailProvider extends ImageProvider { + /// Constructor for creating a [MediaThumbnailProvider] const MediaThumbnailProvider({ required this.media, }); + /// Media to load final AssetEntity media; @override - ImageStreamCompleter load(key, decode) { - return MultiFrameImageStreamCompleter( - codec: _loadAsync(key, decode), - scale: 1, - informationCollector: () sync* { - yield ErrorDescription('Id: ${media.id}'); - }, - ); - } + ImageStreamCompleter load( + MediaThumbnailProvider key, DecoderCallback decode) => + MultiFrameImageStreamCompleter( + codec: _loadAsync(key, decode), + scale: 1, + informationCollector: () sync* { + yield ErrorDescription('Id: ${media.id}'); + }, + ); Future _loadAsync( MediaThumbnailProvider key, DecoderCallback decode) async { - assert(key == this); + assert(key == this, 'Checks MediaThumbnailProvider'); final bytes = await media.thumbData; - return await decode(bytes!); + return decode(bytes!); } @override - Future obtainKey(ImageConfiguration configuration) { - return SynchronousFuture(this); - } + Future obtainKey(ImageConfiguration configuration) => + SynchronousFuture(this); @override bool operator ==(dynamic other) { diff --git a/packages/stream_chat_flutter/lib/src/mention_tile.dart b/packages/stream_chat_flutter/lib/src/mention_tile.dart index 186e1f16..e8859946 100644 --- a/packages/stream_chat_flutter/lib/src/mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/mention_tile.dart @@ -3,8 +3,19 @@ import 'package:flutter/material.dart'; import '../stream_chat_flutter.dart'; /// This widget is used for showing user tiles for mentions -/// Use [title], [subtitle], [leading], [trailing] for substituting widgets in respective positions +/// Use [title], [subtitle], [leading], [trailing] for +/// substituting widgets in respective positions class MentionTile extends StatelessWidget { + /// Constructor for creating a [MentionTile] widget + const MentionTile( + this.member, { + Key? key, + this.title, + this.subtitle, + this.leading, + this.trailing, + }) : super(key: key); + /// Member to display in the tile final Member member; @@ -20,82 +31,72 @@ class MentionTile extends StatelessWidget { /// Widget at the end of tile final Widget? trailing; - const MentionTile( - this.member, { - this.title, - this.subtitle, - this.leading, - this.trailing, - }); - @override - Widget build(BuildContext context) { - return Container( - height: 56, - child: Row( - children: [ - SizedBox( - width: 16, - ), - leading ?? - UserAvatar( - constraints: BoxConstraints.tight( - Size( - 40, - 40, + Widget build(BuildContext context) => SizedBox( + height: 56, + child: Row( + children: [ + const SizedBox( + width: 16, + ), + leading ?? + UserAvatar( + constraints: BoxConstraints.tight( + const Size( + 40, + 40, + ), ), + user: member.user!, + ), + const SizedBox( + width: 8, + ), + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title ?? + Text( + member.user!.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: StreamChatTheme.of(context).textTheme.bodyBold, + ), + const SizedBox( + height: 2, + ), + subtitle ?? + Text( + '@${member.userId}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: StreamChatTheme.of(context) + .textTheme + .footnoteBold + .copyWith( + color: + StreamChatTheme.of(context).colorTheme.grey, + ), + ), + ], ), - user: member.user!, - ), - SizedBox( - width: 8, - ), - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - title ?? - Text( - member.user!.name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context).textTheme.bodyBold, - ), - SizedBox( - height: 2, - ), - subtitle ?? - Text( - '@${member.userId}', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context) - .textTheme - .footnoteBold - .copyWith( - color: - StreamChatTheme.of(context).colorTheme.grey, - ), - ), - ], ), ), - ), - trailing ?? - Padding( - padding: const EdgeInsets.only( - right: 18, - left: 8, + trailing ?? + Padding( + padding: const EdgeInsets.only( + right: 18, + left: 8, + ), + child: StreamSvgIcon.mentions( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + ), ), - child: StreamSvgIcon.mentions( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ), - ), - ], - ), - ); - } + ], + ), + ); } diff --git a/packages/stream_chat_flutter/lib/src/message_action.dart b/packages/stream_chat_flutter/lib/src/message_action.dart index 6d4bd93c..49a57b1e 100644 --- a/packages/stream_chat_flutter/lib/src/message_action.dart +++ b/packages/stream_chat_flutter/lib/src/message_action.dart @@ -3,6 +3,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Class describing a message action class MessageAction { + /// returns a new instance of a [MessageAction] + MessageAction({ + this.leading, + this.title, + this.onTap, + }); + /// leading widget final Widget? leading; @@ -11,11 +18,4 @@ class MessageAction { /// callback called on tap final OnMessageTap? onTap; - - /// returns a new instance of a [MessageAction] - MessageAction({ - this.leading, - this.title, - this.onTap, - }); } From bda64e87649d1eb8e3fc61baf17053afb46dedee Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 15:33:42 +0530 Subject: [PATCH 193/599] lint changes message --- .../lib/src/message_actions_modal.dart | 666 +++++----- .../lib/src/message_input.dart | 1111 +++++++++-------- 2 files changed, 899 insertions(+), 878 deletions(-) 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 ecff8799..767cdc76 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -8,37 +8,11 @@ import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; -import 'extension.dart'; -import 'message_input.dart'; -import 'message_widget.dart'; -import 'stream_chat.dart'; -import 'stream_chat_theme.dart'; - +/// Constructs a modal with actions for a message class MessageActionsModal extends StatefulWidget { - final Widget Function(BuildContext, Message)? editMessageInputBuilder; - final OnMessageTap? onThreadReplyTap; - final OnMessageTap? onReplyTap; - final Message message; - final MessageTheme messageTheme; - final bool showReactions; - final OnMessageTap? onCopyTap; - final bool showDeleteMessage; - final bool showCopyMessage; - final bool showEditMessage; - final bool showResendMessage; - final bool showReplyMessage; - final bool showThreadReplyMessage; - final bool showFlagButton; - final bool reverse; - final ShapeBorder? messageShape; - final ShapeBorder? attachmentShape; - final DisplayWidget showUserAvatar; - final BorderRadius? attachmentBorderRadiusGeometry; - - /// List of custom actions - final List customActions; - + /// Constructor for creating a [MessageActionsModal] widget const MessageActionsModal({ Key? key, required this.message, @@ -63,6 +37,66 @@ class MessageActionsModal extends StatefulWidget { this.onCopyTap, }) : super(key: key); + /// Builder for edit message + final Widget Function(BuildContext, Message)? editMessageInputBuilder; + + /// Callback for when thread reply is tapped + final OnMessageTap? onThreadReplyTap; + + /// Callback for when reply is tapped + final OnMessageTap? onReplyTap; + + /// Message in focus for actions + final Message message; + + /// [MessageTheme] for message + final MessageTheme messageTheme; + + /// Flag for showing reactions + final bool showReactions; + + /// Callback when copy is tapped + final OnMessageTap? onCopyTap; + + /// Callback when delete is tapped + final bool showDeleteMessage; + + /// Flag for showing copy action + final bool showCopyMessage; + + /// Flag for showing edit action + final bool showEditMessage; + + /// Flag for showing resend action + final bool showResendMessage; + + /// Flag for showing reply action + final bool showReplyMessage; + + /// Flag for showing thread reply action + final bool showThreadReplyMessage; + + /// Flag for showing flag action + final bool showFlagButton; + + /// Flag for reversing message + final bool reverse; + + /// [ShapeBorder] to apply to the widget + final ShapeBorder? messageShape; + + /// [ShapeBorder] to apply to attachment + final ShapeBorder? attachmentShape; + + /// Enum for displaying user avatar + final DisplayWidget showUserAvatar; + + /// [BorderRadius] for attachment border + final BorderRadius? attachmentBorderRadiusGeometry; + + /// List of custom actions + final List customActions; + @override _MessageActionsModalState createState() => _MessageActionsModalState(); } @@ -71,9 +105,7 @@ class _MessageActionsModalState extends State { bool _showActions = true; @override - Widget build(BuildContext context) { - return _showMessageOptionsModal(); - } + Widget build(BuildContext context) => _showMessageOptionsModal(); Widget _showMessageOptionsModal() { final size = MediaQuery.of(context).size; @@ -120,138 +152,135 @@ class _MessageActionsModalState extends State { if (_showActions) TweenAnimationBuilder( tween: Tween(begin: 0, end: 1), - duration: Duration(milliseconds: 300), + duration: const Duration(milliseconds: 300), curve: Curves.easeInOutBack, - builder: (context, val, snapshot) { - return Transform.scale( - scale: val, - child: Center( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - crossAxisAlignment: widget.reverse - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - children: [ - if (widget.showReactions && - (widget.message.status == - MessageSendingStatus.sent)) - Align( - alignment: Alignment( - user?.id == widget.message.user?.id - ? (divFactor > 1.0 - ? 0.0 - : (1.0 - divFactor)) - : (divFactor > 1.0 - ? 0.0 - : -(1.0 - divFactor)), - 0), - child: ReactionPicker( - message: widget.message, - ), - ), - SizedBox(height: 8), - IgnorePointer( - child: MessageWidget( - key: Key('MessageWidget'), - reverse: widget.reverse, - attachmentBorderRadiusGeometry: - widget.attachmentBorderRadiusGeometry, - message: widget.message.copyWith( - text: widget.message.text!.length > 200 - ? '${widget.message.text!.substring(0, 200)}...' - : widget.message.text, - ), - messageTheme: widget.messageTheme, - showReactions: false, - showUsername: false, - showReplyMessage: false, - showUserAvatar: widget.showUserAvatar, - attachmentPadding: EdgeInsets.all( - hasFileAttachment ? 4 : 2, - ), - showTimestamp: false, - translateUserAvatar: false, - padding: const EdgeInsets.all(0), - textPadding: EdgeInsets.symmetric( - vertical: 8, - horizontal: widget.message.text!.isOnlyEmoji - ? 0 - : 16.0, - ), - showReactionPickerIndicator: - widget.showReactions && - (widget.message.status == - MessageSendingStatus.sent), - showSendingIndicator: false, - shape: widget.messageShape, - attachmentShape: widget.attachmentShape, + builder: (context, val, snapshot) => Transform.scale( + scale: val, + child: Center( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: widget.reverse + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + if (widget.showReactions && + (widget.message.status == + MessageSendingStatus.sent)) + Align( + alignment: Alignment( + user?.id == widget.message.user?.id + ? (divFactor > 1.0 + ? 0.0 + : (1.0 - divFactor)) + : (divFactor > 1.0 + ? 0.0 + : -(1.0 - divFactor)), + 0), + child: ReactionPicker( + message: widget.message, ), ), - SizedBox(height: 8), - Padding( - padding: EdgeInsets.only( - left: widget.reverse ? 0 : 40, + const SizedBox(height: 8), + IgnorePointer( + child: MessageWidget( + key: const Key('MessageWidget'), + reverse: widget.reverse, + attachmentBorderRadiusGeometry: + widget.attachmentBorderRadiusGeometry, + message: widget.message.copyWith( + text: widget.message.text!.length > 200 + // ignore: lines_longer_than_80_chars + ? '${widget.message.text!.substring(0, 200)}...' + : widget.message.text, ), - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.75, - child: Material( - color: StreamChatTheme.of(context) - .colorTheme - .whiteSnow, - clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - if (widget.showReplyMessage && - widget.message.status == - MessageSendingStatus.sent) - _buildReplyButton(context), - if (widget.showThreadReplyMessage && - (widget.message.status == - MessageSendingStatus.sent) && - widget.message.parentId == null) - _buildThreadReplyButton(context), - if (widget.showResendMessage) - _buildResendMessage(context), - if (widget.showEditMessage) - _buildEditMessage(context), - if (widget.showCopyMessage) - _buildCopyButton(context), - if (widget.showFlagButton) - _buildFlagButton(context), - if (widget.showDeleteMessage) - _buildDeleteButton(context), - ...widget.customActions.map((action) { - return _buildCustomAction( - context, - action, - ); - }) - ].insertBetween( - Container( - height: 1, - color: StreamChatTheme.of(context) - .colorTheme - .greyWhisper, - ), + messageTheme: widget.messageTheme, + showReactions: false, + showUsername: false, + showReplyMessage: false, + showUserAvatar: widget.showUserAvatar, + attachmentPadding: EdgeInsets.all( + hasFileAttachment ? 4 : 2, + ), + showTimestamp: false, + translateUserAvatar: false, + padding: const EdgeInsets.all(0), + textPadding: EdgeInsets.symmetric( + vertical: 8, + horizontal: + widget.message.text!.isOnlyEmoji ? 0 : 16.0, + ), + showReactionPickerIndicator: + widget.showReactions && + (widget.message.status == + MessageSendingStatus.sent), + showSendingIndicator: false, + shape: widget.messageShape, + attachmentShape: widget.attachmentShape, + ), + ), + const SizedBox(height: 8), + Padding( + padding: EdgeInsets.only( + left: widget.reverse ? 0 : 40, + ), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.75, + child: Material( + color: StreamChatTheme.of(context) + .colorTheme + .whiteSnow, + clipBehavior: Clip.hardEdge, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + if (widget.showReplyMessage && + widget.message.status == + MessageSendingStatus.sent) + _buildReplyButton(context), + if (widget.showThreadReplyMessage && + (widget.message.status == + MessageSendingStatus.sent) && + widget.message.parentId == null) + _buildThreadReplyButton(context), + if (widget.showResendMessage) + _buildResendMessage(context), + if (widget.showEditMessage) + _buildEditMessage(context), + if (widget.showCopyMessage) + _buildCopyButton(context), + if (widget.showFlagButton) + _buildFlagButton(context), + if (widget.showDeleteMessage) + _buildDeleteButton(context), + ...widget.customActions + .map((action) => _buildCustomAction( + context, + action, + )) + ].insertBetween( + Container( + height: 1, + color: StreamChatTheme.of(context) + .colorTheme + .greyWhisper, ), ), ), ), ), - ], - ), + ), + ], ), ), ), - ); - }, + ), + ), ), ], ), @@ -261,23 +290,22 @@ class _MessageActionsModalState extends State { InkWell _buildCustomAction( BuildContext context, MessageAction messageAction, - ) { - return InkWell( - onTap: () { - messageAction.onTap?.call(widget.message); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - messageAction.leading ?? Offstage(), - const SizedBox(width: 16), - messageAction.title ?? Offstage(), - ], + ) => + InkWell( + onTap: () { + messageAction.onTap?.call(widget.message); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + messageAction.leading ?? const Offstage(), + const SizedBox(width: 16), + messageAction.title ?? const Offstage(), + ], + ), ), - ), - ); - } + ); void _showFlagDialog() async { final client = StreamChat.of(context).client; @@ -290,6 +318,7 @@ class _MessageActionsModalState extends State { size: 24, ), question: + // ignore: lines_longer_than_80_chars 'Do you want to send a copy of this message to a\nmoderator for further investigation?', okText: 'FLAG', cancelText: 'CANCEL', @@ -371,52 +400,48 @@ class _MessageActionsModalState extends State { ); } - Widget _buildReplyButton(BuildContext context) { - return InkWell( - onTap: () { - Navigator.pop(context); - if (widget.onReplyTap != null) { - widget.onReplyTap!(widget.message); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.reply( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Reply', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], + Widget _buildReplyButton(BuildContext context) => InkWell( + onTap: () { + Navigator.pop(context); + if (widget.onReplyTap != null) { + widget.onReplyTap!(widget.message); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.reply( + color: StreamChatTheme.of(context).primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Reply', + style: StreamChatTheme.of(context).textTheme.body, + ), + ], + ), ), - ), - ); - } + ); - Widget _buildFlagButton(BuildContext context) { - return InkWell( - onTap: _showFlagDialog, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.iconFlag( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Flag Message', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], + Widget _buildFlagButton(BuildContext context) => InkWell( + onTap: _showFlagDialog, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.iconFlag( + color: StreamChatTheme.of(context).primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Flag Message', + style: StreamChatTheme.of(context).textTheme.body, + ), + ], + ), ), - ), - ); - } + ); Widget _buildDeleteButton(BuildContext context) { final isDeleteFailed = @@ -444,54 +469,50 @@ class _MessageActionsModalState extends State { ); } - Widget _buildCopyButton(BuildContext context) { - return InkWell( - onTap: () async { - widget.onCopyTap?.call(widget.message); - Navigator.pop(context); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.copy( - size: 24, - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Copy Message', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], + Widget _buildCopyButton(BuildContext context) => InkWell( + onTap: () async { + widget.onCopyTap?.call(widget.message); + Navigator.pop(context); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.copy( + size: 24, + color: StreamChatTheme.of(context).primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Copy Message', + style: StreamChatTheme.of(context).textTheme.body, + ), + ], + ), ), - ), - ); - } + ); - Widget _buildEditMessage(BuildContext context) { - return InkWell( - onTap: () async { - Navigator.pop(context); - _showEditBottomSheet(context); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.edit( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Edit Message', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], + Widget _buildEditMessage(BuildContext context) => InkWell( + onTap: () async { + Navigator.pop(context); + _showEditBottomSheet(context); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.edit( + color: StreamChatTheme.of(context).primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Edit Message', + style: StreamChatTheme.of(context).textTheme.body, + ), + ], + ), ), - ), - ); - } + ); Widget _buildResendMessage(BuildContext context) { final isUpdateFailed = @@ -533,86 +554,81 @@ class _MessageActionsModalState extends State { isScrollControlled: true, backgroundColor: StreamChatTheme.of(context).messageInputTheme.inputBackground, - shape: RoundedRectangleBorder( + shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), ), - builder: (context) { - return StreamChannel( - channel: channel, - child: Flex( - direction: Axis.vertical, - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.all(8), - child: StreamSvgIcon.edit( - color: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, - ), + builder: (context) => StreamChannel( + channel: channel, + child: Flex( + direction: Axis.vertical, + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: StreamSvgIcon.edit( + color: + StreamChatTheme.of(context).colorTheme.greyGainsboro, ), - Text( - 'Edit Message', - style: TextStyle(fontWeight: FontWeight.bold), - ), - IconButton( - visualDensity: VisualDensity.compact, - icon: StreamSvgIcon.closeSmall(), - onPressed: Navigator.of(context).pop, - ), - ], - ), + ), + const Text( + 'Edit Message', + style: TextStyle(fontWeight: FontWeight.bold), + ), + IconButton( + visualDensity: VisualDensity.compact, + icon: StreamSvgIcon.closeSmall(), + onPressed: Navigator.of(context).pop, + ), + ], ), - if (widget.editMessageInputBuilder != null) - widget.editMessageInputBuilder!(context, widget.message) - else - MessageInput( - editMessage: widget.message, - preMessageSending: (m) { - FocusScope.of(context).unfocus(); - Navigator.pop(context); - return m; - }, - ), - ], - ), - ); - }, - ); - } - - Widget _buildThreadReplyButton(BuildContext context) { - return InkWell( - onTap: () { - Navigator.pop(context); - if (widget.onThreadReplyTap != null) { - widget.onThreadReplyTap!(widget.message); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.thread( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Thread Reply', - style: StreamChatTheme.of(context).textTheme.body, ), + if (widget.editMessageInputBuilder != null) + widget.editMessageInputBuilder!(context, widget.message) + else + MessageInput( + editMessage: widget.message, + preMessageSending: (m) { + FocusScope.of(context).unfocus(); + Navigator.pop(context); + return m; + }, + ), ], ), ), ); } + + Widget _buildThreadReplyButton(BuildContext context) => InkWell( + onTap: () { + Navigator.pop(context); + if (widget.onThreadReplyTap != null) { + widget.onThreadReplyTap!(widget.message); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.thread( + color: StreamChatTheme.of(context).primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Thread Reply', + style: StreamChatTheme.of(context).textTheme.body, + ), + ], + ), + ), + ); } diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 638c7769..3091043c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -14,19 +14,18 @@ import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.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/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/video_service.dart'; +import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:substring_highlight/substring_highlight.dart'; import 'package:video_compress/video_compress.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import '../stream_chat_flutter.dart'; -import 'attachment/attachment.dart'; -import 'extension.dart'; -import 'quoted_message_widget.dart'; -import 'video_thumbnail_image.dart'; - +/// Builder for attachment thumbnails typedef AttachmentThumbnailBuilder = Widget Function( BuildContext, Attachment, @@ -39,16 +38,30 @@ typedef MentionTileBuilder = Widget Function( Member member, ); +/// Location for actions on the [MessageInput] enum ActionsLocation { + /// Align to left left, + + /// Align to right right, + + /// Align to left but inside the [TextField] leftInside, + + /// Align to right but inside the [TextField] rightInside, } +/// Default attachments for widget enum DefaultAttachmentTypes { + /// Image Attachment image, + + /// Video Attachment video, + + /// File Attachment file, } @@ -103,9 +116,11 @@ const _kMaxAttachmentSize = 20971520; // 20MB in Bytes /// } /// ``` /// -/// You usually put this widget in the same page of a [MessageListView] as the bottom widget. +/// You usually put this widget in the same page of a [MessageListView] +/// as the bottom widget. /// -/// The widget renders the ui based on the first ancestor of type [StreamChatTheme]. +/// The widget renders the ui based on the first ancestor of +/// type [StreamChatTheme]. /// Modify it to change the widget appearance. class MessageInput extends StatefulWidget { /// Instantiate a new MessageInput @@ -213,6 +228,7 @@ class MessageInput extends StatefulWidget { if (messageInputState == null) { throw Exception( + // ignore: lines_longer_than_80_chars 'You must have a MessageInput widget as ancestor of your widget tree'); } @@ -220,6 +236,7 @@ class MessageInput extends StatefulWidget { } } +/// State of [MessageInput] class MessageInputState extends State { final _attachments = {}; final List _mentionedUsers = []; @@ -313,7 +330,7 @@ class MessageInputState extends State { .greyGainsboro, ), ), - Text( + const Text( 'Reply to Message', style: TextStyle(fontWeight: FontWeight.bold), ), @@ -353,85 +370,83 @@ class MessageInputState extends State { return child; } - Flex _buildTextField(BuildContext context) { - return Flex( - direction: Axis.horizontal, - children: [ - if (!_commandEnabled && widget.actionsLocation == ActionsLocation.left) - _buildExpandActionsButton(), - _buildTextInput(context), - if (!_commandEnabled && widget.actionsLocation == ActionsLocation.right) - _buildExpandActionsButton(), - if (widget.sendButtonLocation == SendButtonLocation.outside) - _animateSendButton(context), - ], - ); - } + Flex _buildTextField(BuildContext context) => Flex( + direction: Axis.horizontal, + children: [ + if (!_commandEnabled && + widget.actionsLocation == ActionsLocation.left) + _buildExpandActionsButton(), + _buildTextInput(context), + if (!_commandEnabled && + widget.actionsLocation == ActionsLocation.right) + _buildExpandActionsButton(), + if (widget.sendButtonLocation == SendButtonLocation.outside) + _animateSendButton(context), + ], + ); - Widget _buildDmCheckbox() { - return Row( - children: [ - Container( - height: 16, - width: 16, - foregroundDecoration: BoxDecoration( - border: _sendAsDm - ? null - : Border.all( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - width: 2, - ), - borderRadius: BorderRadius.circular(3), - ), - child: Center( - child: Material( + Widget _buildDmCheckbox() => Row( + children: [ + Container( + height: 16, + width: 16, + foregroundDecoration: BoxDecoration( + border: _sendAsDm + ? null + : Border.all( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + width: 2, + ), borderRadius: BorderRadius.circular(3), - color: _sendAsDm - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context).colorTheme.white, - child: InkWell( - onTap: () { - setState(() { - _sendAsDm = !_sendAsDm; - }); - }, - child: AnimatedCrossFade( - duration: Duration(milliseconds: 300), - reverseDuration: Duration(milliseconds: 300), - crossFadeState: _sendAsDm - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: StreamSvgIcon.check( - size: 16, - color: StreamChatTheme.of(context).colorTheme.white, - ), - secondChild: SizedBox( - height: 16, - width: 16, + ), + child: Center( + child: Material( + borderRadius: BorderRadius.circular(3), + color: _sendAsDm + ? StreamChatTheme.of(context).colorTheme.accentBlue + : StreamChatTheme.of(context).colorTheme.white, + child: InkWell( + onTap: () { + setState(() { + _sendAsDm = !_sendAsDm; + }); + }, + child: AnimatedCrossFade( + duration: const Duration(milliseconds: 300), + reverseDuration: const Duration(milliseconds: 300), + crossFadeState: _sendAsDm + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: StreamSvgIcon.check( + size: 16, + color: StreamChatTheme.of(context).colorTheme.white, + ), + secondChild: const SizedBox( + height: 16, + width: 16, + ), ), ), ), ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Text( - 'Also send as direct message', - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), - ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + 'Also send as direct message', + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + ), + ), ), - ), - ], - ); - } + ], + ); Widget _animateSendButton(BuildContext context) { final sendButton = widget.activeSendButton != null @@ -452,56 +467,54 @@ class MessageInputState extends State { ); } - Widget _buildExpandActionsButton() { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: AnimatedCrossFade( - crossFadeState: _actionsShrunk - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: IconButton( - onPressed: () => setState(() => _actionsShrunk = false), - icon: Transform.rotate( - angle: (widget.actionsLocation == ActionsLocation.right || - widget.actionsLocation == ActionsLocation.rightInside) - ? pi - : 0, - child: StreamSvgIcon.emptyCircleLeft( - color: StreamChatTheme.of(context) - .messageInputTheme - .expandButtonColor, + Widget _buildExpandActionsButton() => Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: AnimatedCrossFade( + crossFadeState: _actionsShrunk + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: IconButton( + onPressed: () => setState(() => _actionsShrunk = false), + icon: Transform.rotate( + angle: (widget.actionsLocation == ActionsLocation.right || + widget.actionsLocation == ActionsLocation.rightInside) + ? pi + : 0, + child: StreamSvgIcon.emptyCircleLeft( + color: StreamChatTheme.of(context) + .messageInputTheme + .expandButtonColor, + ), + ), + padding: const EdgeInsets.all(0), + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, + ), + secondChild: FittedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (!widget.disableAttachments) _buildAttachmentButton(), + if (widget.showCommandsButton && + widget.editMessage == null && + StreamChannel.of(context) + .channel + .config + ?.commands + .isNotEmpty == + true) + _buildCommandButton(), + ...widget.actions ?? [], + ].insertBetween(const SizedBox(width: 8)), ), ), - padding: const EdgeInsets.all(0), - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, - ), - splashRadius: 24, + duration: const Duration(milliseconds: 300), + alignment: Alignment.center, ), - secondChild: FittedBox( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - if (!widget.disableAttachments) _buildAttachmentButton(), - if (widget.showCommandsButton && - widget.editMessage == null && - StreamChannel.of(context) - .channel - .config - ?.commands - .isNotEmpty == - true) - _buildCommandButton(), - ...widget.actions ?? [], - ].insertBetween(const SizedBox(width: 8)), - ), - ), - duration: Duration(milliseconds: 300), - alignment: Alignment.center, - ), - ); - } + ); Expanded _buildTextInput(BuildContext context) { final theme = StreamChatTheme.of(context); @@ -539,7 +552,7 @@ class MessageInputState extends State { LimitedBox( maxHeight: widget.maxHeight, child: TextField( - key: Key('messageInputText'), + key: const Key('messageInputText'), enabled: _inputEnabled, maxLines: null, onSubmitted: (_) => sendMessage(), @@ -571,27 +584,27 @@ class MessageInputState extends State { hintStyle: theme.messageInputTheme.inputTextStyle!.copyWith( color: theme.colorTheme.grey, ), - border: OutlineInputBorder( + border: const OutlineInputBorder( borderSide: BorderSide( color: Colors.transparent, ), ), - focusedBorder: OutlineInputBorder( + focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.transparent, ), ), - enabledBorder: OutlineInputBorder( + enabledBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.transparent, ), ), - errorBorder: OutlineInputBorder( + errorBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.transparent, ), ), - disabledBorder: OutlineInputBorder( + disabledBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.transparent, ), @@ -604,7 +617,7 @@ class MessageInputState extends State { Padding( padding: const EdgeInsets.all(8), child: Container( - constraints: BoxConstraints.tight(Size(64, 24)), + constraints: BoxConstraints.tight(const Size(64, 24)), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: theme.colorTheme.accentBlue, @@ -640,8 +653,8 @@ class MessageInputState extends State { ], ) : null), - suffixIconConstraints: BoxConstraints.tightFor(height: 40), - prefixIconConstraints: BoxConstraints.tightFor(height: 40), + suffixIconConstraints: const BoxConstraints.tightFor(height: 40), + prefixIconConstraints: const BoxConstraints.tightFor(height: 40), suffixIcon: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -652,7 +665,7 @@ class MessageInputState extends State { icon: StreamSvgIcon.closeSmall(), splashRadius: 24, padding: const EdgeInsets.all(0), - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 24, width: 24, ), @@ -809,6 +822,7 @@ class MessageInputState extends State { return null; } + // ignore: cast_nullable_to_non_nullable final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; @@ -819,7 +833,7 @@ class MessageInputState extends State { right: 0, child: TweenAnimationBuilder( tween: Tween(begin: 0, end: 1), - duration: Duration(milliseconds: 300), + duration: const Duration(milliseconds: 300), curve: Curves.easeInOutExpo, builder: (context, val, wid) => Transform.scale( scale: val, @@ -833,8 +847,8 @@ class MessageInputState extends State { color: StreamChatTheme.of(context).colorTheme.white, clipBehavior: Clip.antiAlias, child: Container( - constraints: - BoxConstraints.loose(Size.fromHeight(400)), + constraints: BoxConstraints.loose( + const Size.fromHeight(400)), decoration: BoxDecoration( color: StreamChatTheme.of(context) .colorTheme @@ -907,8 +921,11 @@ class MessageInputState extends State { .textTheme .body .copyWith( + // ignore: lines_longer_than_80_chars color: StreamChatTheme.of( + // ignore: lines_longer_than_80_chars context) + // ignore: lines_longer_than_80_chars .colorTheme .grey, ), @@ -932,9 +949,8 @@ class MessageInputState extends State { } Widget _buildFilePickerSection() { - final _attachmentContainsFile = _attachments.values.any((it) { - return it.type == 'file'; - }); + final _attachmentContainsFile = + _attachments.values.any((it) => it.type == 'file'); Color _getIconColor(int index) { switch (index) { @@ -973,7 +989,8 @@ class MessageInputState extends State { } return AnimatedContainer( - duration: _animateContainer ? Duration(milliseconds: 300) : Duration.zero, + duration: + _animateContainer ? const Duration(milliseconds: 300) : Duration.zero, height: _openFilePickerSection ? _filePickerSize : 0, child: Material( color: StreamChatTheme.of(context).colorTheme.whiteSmoke, @@ -1095,7 +1112,7 @@ class MessageInputState extends State { void _addAttachment(AssetEntity medium) async { final mediaFile = await medium.originFile.timeout( - Duration(seconds: 5), + const Duration(seconds: 5), onTimeout: () => medium.originFile, ); @@ -1116,13 +1133,14 @@ class MessageInputState extends State { if (mediaInfo.filesize! > _kMaxAttachmentSize) { _showErrorAlert( + // ignore: lines_longer_than_80_chars 'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.', ); return; } file = AttachmentFile( name: file.name, - size: mediaInfo.filesize!, + size: mediaInfo.filesize, bytes: await mediaInfo.file?.readAsBytes(), path: mediaInfo.path, ); @@ -1234,108 +1252,103 @@ class MessageInputState extends State { .then((res) => res.members); } - final members = StreamChannel.of(context).channel.state?.members.where((m) { - return m.user?.name.toLowerCase().contains(query) == true; - }).toList() ?? + final members = StreamChannel.of(context) + .channel + .state + ?.members + .where((m) => m.user?.name.toLowerCase().contains(query) == true) + .toList() ?? []; if (members.isEmpty) { return null; } + // ignore: cast_nullable_to_non_nullable final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; return OverlayEntry( - builder: (context) { - return Positioned( - bottom: size.height + MediaQuery.of(context).viewInsets.bottom, - left: 0, - right: 0, - child: TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - duration: Duration(milliseconds: 300), - curve: Curves.easeInOutExpo, - builder: (context, val, wid) { - return Transform.scale( - scale: val, - child: Card( - margin: EdgeInsets.all(8), - elevation: 2, + builder: (context) => Positioned( + bottom: size.height + MediaQuery.of(context).viewInsets.bottom, + left: 0, + right: 0, + child: TweenAnimationBuilder( + tween: Tween(begin: 0, end: 1), + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOutExpo, + builder: (context, val, wid) => Transform.scale( + scale: val, + child: Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: StreamChatTheme.of(context).colorTheme.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.antiAlias, + child: Container( + constraints: BoxConstraints.loose(const Size.fromHeight(240)), + decoration: BoxDecoration( color: StreamChatTheme.of(context).colorTheme.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose(Size.fromHeight(240)), - decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, - ), - child: FutureBuilder>( - future: queryMembers ?? Future.value(members), - initialData: members, - builder: (context, snapshot) { - return ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - SizedBox( - height: 8, - ), - ...snapshot.data! - .where((it) => it.user != null) - .map( - (m) { - return Material( - color: StreamChatTheme.of(context) - .colorTheme - .white, - child: InkWell( - onTap: () { - if (m.user != null) { - _mentionedUsers.add(m.user!); - } + ), + child: FutureBuilder>( + future: queryMembers ?? Future.value(members), + initialData: members, + builder: (context, snapshot) => ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + children: [ + const SizedBox( + height: 8, + ), + ...snapshot.data! + .where((it) => it.user != null) + .map( + (m) => Material( + color: + StreamChatTheme.of(context).colorTheme.white, + child: InkWell( + onTap: () { + if (m.user != null) { + _mentionedUsers.add(m.user!); + } - splits[splits.length - 1] = m.user!.name; - final rejoin = splits.join('@'); + splits[splits.length - 1] = m.user!.name; + final rejoin = splits.join('@'); - textEditingController.value = - TextEditingValue( - text: rejoin + - textEditingController.text - .substring(textEditingController - .selection.start), - selection: TextSelection.collapsed( - offset: rejoin.length, - ), - ); - _debounce!.cancel(); - _mentionsOverlay?.remove(); - _mentionsOverlay = null; - }, - child: widget.mentionsTileBuilder != null - ? widget.mentionsTileBuilder!( - context, m) - : MentionTile(m), - ), - ); - }, - ).toList(), - SizedBox( - height: 8, + textEditingController.value = + TextEditingValue( + text: rejoin + + textEditingController.text.substring( + textEditingController + .selection.start), + selection: TextSelection.collapsed( + offset: rejoin.length, + ), + ); + _debounce!.cancel(); + _mentionsOverlay?.remove(); + _mentionsOverlay = null; + }, + child: widget.mentionsTileBuilder != null + ? widget.mentionsTileBuilder!(context, m) + : MentionTile(m), + ), ), - ], - ); - }, - ), + ) + .toList(), + const SizedBox( + height: 8, + ), + ], ), ), - ); - }, + ), + ), ), - ); - }, + ), + ), ); } @@ -1358,92 +1371,95 @@ class MessageInputState extends State { return null; } + // ignore: cast_nullable_to_non_nullable final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; - return OverlayEntry(builder: (context) { - return Positioned( - bottom: size.height + MediaQuery.of(context).viewInsets.bottom, - left: 0, - right: 0, - child: Card( - margin: EdgeInsets.all(8), - elevation: 2, - color: StreamChatTheme.of(context).colorTheme.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose(Size.fromHeight(200)), - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - spreadRadius: -8, - blurRadius: 5, - offset: Offset(0, -4), + return OverlayEntry( + builder: (context) => Positioned( + bottom: size.height + MediaQuery.of(context).viewInsets.bottom, + left: 0, + right: 0, + child: Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: StreamChatTheme.of(context).colorTheme.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), ), - ], - color: StreamChatTheme.of(context).colorTheme.white, - ), - child: ListView.builder( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - itemCount: emojis.length + 1, - itemBuilder: (context, i) { - if (i == 0) { - return Padding( - padding: const EdgeInsets.only(left: 8, top: 8), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: StreamSvgIcon.smile( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), - ), - Flexible( - child: Text( - 'Emoji matching "$query"', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - ), - ) - ], + clipBehavior: Clip.antiAlias, + child: Container( + constraints: BoxConstraints.loose(const Size.fromHeight(200)), + decoration: BoxDecoration( + boxShadow: const [ + BoxShadow( + spreadRadius: -8, + blurRadius: 5, + offset: Offset(0, -4), ), - ); - } + ], + color: StreamChatTheme.of(context).colorTheme.white, + ), + child: ListView.builder( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + itemCount: emojis.length + 1, + itemBuilder: (context, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.only(left: 8, top: 8), + child: Row( + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8), + child: StreamSvgIcon.smile( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, + ), + ), + Flexible( + child: Text( + 'Emoji matching "$query"', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + ), + ) + ], + ), + ); + } - final emoji = emojis.elementAt(i - 1)!; - return ListTile( - title: SubstringHighlight( - text: "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", - term: query, - textStyleHighlight: - Theme.of(context).textTheme.headline6!.copyWith( - fontSize: 14.5, - fontWeight: FontWeight.bold, - ), - textStyle: - Theme.of(context).textTheme.headline6!.copyWith( - fontSize: 14.5, - ), - ), - onTap: () { - _chooseEmoji(splits, emoji); - }, - ); - }), - ), - ), - ); - }); + final emoji = emojis.elementAt(i - 1)!; + return ListTile( + title: SubstringHighlight( + text: + // ignore: lines_longer_than_80_chars + "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", + term: query, + textStyleHighlight: + Theme.of(context).textTheme.headline6!.copyWith( + fontSize: 14.5, + fontWeight: FontWeight.bold, + ), + textStyle: + Theme.of(context).textTheme.headline6!.copyWith( + fontSize: 14.5, + ), + ), + onTap: () { + _chooseEmoji(splits, emoji); + }, + ); + }), + ), + ), + )); } void _chooseEmoji(List splits, Emoji emoji) { @@ -1474,7 +1490,7 @@ class MessageInputState extends State { } Widget _buildReplyToMessage() { - if (!_hasQuotedMessage) return Offstage(); + if (!_hasQuotedMessage) return const Offstage(); final containsUrl = widget.quotedMessage!.attachments .any((element) => element.ogScrapeUrl != null) == true; @@ -1492,7 +1508,7 @@ class MessageInputState extends State { } Widget _buildAttachments() { - if (_attachments.isEmpty) return Offstage(); + if (_attachments.isEmpty) return const Offstage(); final fileAttachments = _attachments.values .where((it) => it.type == 'file') .toList(growable: false); @@ -1546,7 +1562,7 @@ class MessageInputState extends State { children: [ AspectRatio( aspectRatio: 1, - child: Container( + child: SizedBox( height: 104, width: 104, child: _buildAttachment(attachment), @@ -1569,31 +1585,30 @@ class MessageInputState extends State { ); } - Widget _buildRemoveButton(Attachment attachment) { - return Container( - height: 24, - width: 24, - child: RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - hoverElevation: 0, - onPressed: () { - setState(() => _attachments.remove(attachment.id)); - }, - fillColor: StreamChatTheme.of(context).colorTheme.black.withOpacity(.5), - child: Center( - child: StreamSvgIcon.close( - size: 24, - color: StreamChatTheme.of(context).colorTheme.white, + Widget _buildRemoveButton(Attachment attachment) => SizedBox( + height: 24, + width: 24, + child: RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + hoverElevation: 0, + onPressed: () { + setState(() => _attachments.remove(attachment.id)); + }, + fillColor: + StreamChatTheme.of(context).colorTheme.black.withOpacity(.5), + child: Center( + child: StreamSvgIcon.close( + size: 24, + color: StreamChatTheme.of(context).colorTheme.white, + ), ), ), - ), - ); - } + ); Widget _buildAttachment(Attachment attachment) { if (widget.attachmentThumbnailBuilders?.containsKey(attachment.type) == @@ -1611,35 +1626,30 @@ class MessageInputState extends State { ? Image.memory( attachment.file!.bytes!, fit: BoxFit.cover, - errorBuilder: (context, _, __) { - return Image.asset( - 'images/placeholder.png', - package: 'stream_chat_flutter', - ); - }, + errorBuilder: (context, _, __) => Image.asset( + 'images/placeholder.png', + package: 'stream_chat_flutter', + ), ) : CachedNetworkImage( imageUrl: attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl!, fit: BoxFit.cover, - errorWidget: (_, obj, trace) { - return getFileTypeImage( - attachment.extraData['other'] as String?); - }, - progressIndicatorBuilder: (context, _, progress) { - return Shimmer.fromColors( - baseColor: - StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - child: Image.asset( - 'images/placeholder.png', - fit: BoxFit.cover, - package: 'stream_chat_flutter', - ), - ); - }, + errorWidget: (_, obj, trace) => + getFileTypeImage(attachment.extraData['other'] as String?), + progressIndicatorBuilder: (context, _, progress) => + Shimmer.fromColors( + baseColor: + StreamChatTheme.of(context).colorTheme.greyGainsboro, + highlightColor: + StreamChatTheme.of(context).colorTheme.whiteSmoke, + child: Image.asset( + 'images/placeholder.png', + fit: BoxFit.cover, + package: 'stream_chat_flutter', + ), + ), ); case 'video': return Stack( @@ -1663,7 +1673,7 @@ class MessageInputState extends State { default: return Container( color: Colors.black26, - child: Icon(Icons.insert_drive_file), + child: const Icon(Icons.insert_drive_file), ); } } @@ -1684,7 +1694,7 @@ class MessageInputState extends State { .actionButtonIdleColor), ), padding: const EdgeInsets.all(0), - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 24, width: 24, ), @@ -1696,7 +1706,7 @@ class MessageInputState extends State { _openFilePickerSection = false; _filePickerSize = _kMinMediaPickerSize; }); - await Future.delayed(Duration(milliseconds: 300)); + await Future.delayed(const Duration(milliseconds: 300)); } if (_commandsOverlay == null) { @@ -1716,43 +1726,42 @@ class MessageInputState extends State { ); } - Widget _buildAttachmentButton() { - return IconButton( - icon: StreamSvgIcon.attach( - color: _openFilePickerSection - ? StreamChatTheme.of(context).messageInputTheme.actionButtonColor - : StreamChatTheme.of(context) - .messageInputTheme - .actionButtonIdleColor, - ), - padding: const EdgeInsets.all(0), - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, - ), - splashRadius: 24, - onPressed: () async { - _emojiOverlay?.remove(); - _emojiOverlay = null; - _commandsOverlay?.remove(); - _commandsOverlay = null; - _mentionsOverlay?.remove(); - _mentionsOverlay = null; + Widget _buildAttachmentButton() => IconButton( + icon: StreamSvgIcon.attach( + color: _openFilePickerSection + ? StreamChatTheme.of(context).messageInputTheme.actionButtonColor + : StreamChatTheme.of(context) + .messageInputTheme + .actionButtonIdleColor, + ), + padding: const EdgeInsets.all(0), + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, + onPressed: () async { + _emojiOverlay?.remove(); + _emojiOverlay = null; + _commandsOverlay?.remove(); + _commandsOverlay = null; + _mentionsOverlay?.remove(); + _mentionsOverlay = null; - if (_openFilePickerSection) { - setState(() { - _animateContainer = true; - _openFilePickerSection = false; - _filePickerSize = _kMinMediaPickerSize; - }); - } else { - showAttachmentModal(); - } - }, - ); - } + if (_openFilePickerSection) { + setState(() { + _animateContainer = true; + _openFilePickerSection = false; + _filePickerSize = _kMinMediaPickerSize; + }); + } else { + showAttachmentModal(); + } + }, + ); - /// Show the attachment modal, making the user choose where to pick a media from + /// Show the attachment modal, making the user choose where to + /// pick a media from void showAttachmentModal() { if (_focusNode.hasFocus) { _focusNode.unfocus(); @@ -1765,7 +1774,7 @@ class MessageInputState extends State { } else { showModalBottomSheet( clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( + shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(32), topRight: Radius.circular(32), @@ -1773,63 +1782,61 @@ class MessageInputState extends State { ), context: context, isScrollControlled: true, - builder: (_) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: Text( - 'Add a file', - style: TextStyle( - fontWeight: FontWeight.bold, + builder: (_) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + const ListTile( + title: Text( + 'Add a file', + style: TextStyle( + fontWeight: FontWeight.bold, + ), ), ), - ), - ListTile( - leading: Icon(Icons.image), - title: Text('Upload a photo'), - onTap: () { - pickFile(DefaultAttachmentTypes.image); - Navigator.pop(context); - }, - ), - ListTile( - leading: Icon(Icons.video_library), - title: Text('Upload a video'), - onTap: () { - pickFile(DefaultAttachmentTypes.video); - Navigator.pop(context); - }, - ), - if (!kIsWeb) ListTile( - leading: Icon(Icons.camera_alt), - title: Text('Photo from camera'), + leading: const Icon(Icons.image), + title: const Text('Upload a photo'), onTap: () { - pickFile(DefaultAttachmentTypes.image, true); + pickFile(DefaultAttachmentTypes.image); Navigator.pop(context); }, ), - if (!kIsWeb) ListTile( - leading: Icon(Icons.videocam), - title: Text('Video from camera'), + leading: const Icon(Icons.video_library), + title: const Text('Upload a video'), onTap: () { - pickFile(DefaultAttachmentTypes.video, true); + pickFile(DefaultAttachmentTypes.video); Navigator.pop(context); }, ), - ListTile( - leading: Icon(Icons.insert_drive_file), - title: Text('Upload a file'), - onTap: () { - pickFile(DefaultAttachmentTypes.file); - Navigator.pop(context); - }, - ), - ], - ); - }); + if (!kIsWeb) + ListTile( + leading: const Icon(Icons.camera_alt), + title: const Text('Photo from camera'), + onTap: () { + pickFile(DefaultAttachmentTypes.image, true); + Navigator.pop(context); + }, + ), + if (!kIsWeb) + ListTile( + leading: const Icon(Icons.videocam), + title: const Text('Video from camera'), + onTap: () { + pickFile(DefaultAttachmentTypes.video, true); + Navigator.pop(context); + }, + ), + ListTile( + leading: const Icon(Icons.insert_drive_file), + title: const Text('Upload a file'), + onTap: () { + pickFile(DefaultAttachmentTypes.file); + Navigator.pop(context); + }, + ), + ], + )); } } @@ -1845,6 +1852,7 @@ class MessageInputState extends State { /// Pick a file from the device /// If [camera] is true then the camera will open + // ignore: avoid_positional_boolean_parameters void pickFile(DefaultAttachmentTypes fileType, [bool camera = false]) async { setState(() => _inputEnabled = false); @@ -1920,13 +1928,14 @@ class MessageInputState extends State { if (mediaInfo.filesize! > _kMaxAttachmentSize) { _showErrorAlert( + // ignore: lines_longer_than_80_chars 'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.', ); return; } file = AttachmentFile( name: file.name, - size: mediaInfo.filesize!, + size: mediaInfo.filesize, bytes: await mediaInfo.file!.readAsBytes(), path: mediaInfo.path, ); @@ -1941,45 +1950,42 @@ class MessageInputState extends State { _attachments[attachment.id] = attachment; setState(() { - _attachments.update(attachment.id, (it) { - return it.copyWith( - file: file, - extraData: {...it.extraData} - ..update('file_size', ((_) => file!.size!)), - ); - }); + _attachments.update( + attachment.id, + (it) => it.copyWith( + file: file, + extraData: {...it.extraData} + ..update('file_size', ((_) => file!.size!)), + )); }); } - Widget _buildIdleSendButton(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8), - child: StreamSvgIcon( - assetName: _getIdleSendIcon(), - color: - StreamChatTheme.of(context).messageInputTheme.sendButtonIdleColor, - ), - ); - } + Widget _buildIdleSendButton(BuildContext context) => Padding( + padding: const EdgeInsets.all(8), + child: StreamSvgIcon( + assetName: _getIdleSendIcon(), + color: + StreamChatTheme.of(context).messageInputTheme.sendButtonIdleColor, + ), + ); - Widget _buildSendButton(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8), - child: IconButton( - onPressed: sendMessage, - padding: const EdgeInsets.all(0), - splashRadius: 24, - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, + Widget _buildSendButton(BuildContext context) => Padding( + padding: const EdgeInsets.all(8), + child: IconButton( + onPressed: sendMessage, + padding: const EdgeInsets.all(0), + splashRadius: 24, + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + icon: StreamSvgIcon( + assetName: _getSendIcon(), + color: + StreamChatTheme.of(context).messageInputTheme.sendButtonColor, + ), ), - icon: StreamSvgIcon( - assetName: _getSendIcon(), - color: StreamChatTheme.of(context).messageInputTheme.sendButtonColor, - ), - ), - ); - } + ); String _getIdleSendIcon() { if (_commandEnabled) { @@ -2009,7 +2015,7 @@ class MessageInputState extends State { final shouldUnfocus = _commandEnabled; if (_commandEnabled) { - text = '/${_chosenCommand!.name} ' + text; + text = '${'/${_chosenCommand!.name} '}$text'; } final attachments = [..._attachments.values]; @@ -2096,70 +2102,68 @@ class MessageInputState extends State { showModalBottomSheet( backgroundColor: StreamChatTheme.of(context).colorTheme.white, context: context, - shape: RoundedRectangleBorder( + shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), )), - builder: (context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - height: 26, + builder: (context) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 26, + ), + StreamSvgIcon.error( + color: StreamChatTheme.of(context).colorTheme.accentRed, + size: 24, + ), + const SizedBox( + height: 26, + ), + Text( + 'Something went wrong', + style: StreamChatTheme.of(context).textTheme.headlineBold, + ), + const SizedBox( + height: 7, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + description, + textAlign: TextAlign.center, ), - StreamSvgIcon.error( - color: StreamChatTheme.of(context).colorTheme.accentRed, - size: 24, - ), - SizedBox( - height: 26, - ), - Text( - 'Something went wrong', - style: StreamChatTheme.of(context).textTheme.headlineBold, - ), - SizedBox( - height: 7, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - description, - textAlign: TextAlign.center, - ), - ), - SizedBox( - height: 36, - ), - Container( - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), - height: 1, - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - 'OK', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue), - ), + ), + const SizedBox( + height: 36, + ), + Container( + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), + height: 1, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + 'OK', + style: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue), ), - ], - ), - ], - ); - }, + ), + ], + ), + ], + ), ); } @@ -2196,12 +2200,6 @@ class MessageInputState extends State { /// Represents a 2-tuple, or pair. class Tuple2 { - /// Returns the first item of the tuple - final T1 item1; - - /// Returns the second item of the tuple - final T2 item2; - /// Creates a new tuple value with the specified items. const Tuple2(this.item1, this.item2); @@ -2214,6 +2212,12 @@ class Tuple2 { return Tuple2(items[0] as T1, items[1] as T2); } + /// Returns the first item of the tuple + final T1 item1; + + /// Returns the second item of the tuple + final T2 item2; + /// Returns a tuple with the first item set to the specified value. Tuple2 withItem1(T1 v) => Tuple2(v, item2); @@ -2243,12 +2247,6 @@ class Tuple2 { } class _PickerWidget extends StatefulWidget { - final int filePickerIndex; - final bool containsFile; - final List selectedMedias; - final void Function(DefaultAttachmentTypes) onAddMoreFilesClick; - final void Function(AssetEntity) onMediaSelected; - const _PickerWidget({ Key? key, required this.filePickerIndex, @@ -2258,6 +2256,12 @@ class _PickerWidget extends StatefulWidget { required this.onMediaSelected, }) : super(key: key); + final int filePickerIndex; + final bool containsFile; + final List selectedMedias; + final void Function(DefaultAttachmentTypes) onAddMoreFilesClick; + final void Function(AssetEntity) onMediaSelected; + @override __PickerWidgetState createState() => __PickerWidgetState(); } @@ -2274,7 +2278,7 @@ class __PickerWidgetState extends State<_PickerWidget> { @override Widget build(BuildContext context) { if (widget.filePickerIndex != 0) { - return Offstage(); + return const Offstage(); } return FutureBuilder( future: requestPermission, @@ -2290,7 +2294,7 @@ class __PickerWidgetState extends State<_PickerWidget> { widget.onAddMoreFilesClick(DefaultAttachmentTypes.file); }, child: Container( - constraints: BoxConstraints.expand(), + constraints: const BoxConstraints.expand(), color: StreamChatTheme.of(context).colorTheme.whiteSmoke, alignment: Alignment.center, child: Text( @@ -2326,12 +2330,13 @@ class __PickerWidgetState extends State<_PickerWidget> { color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ), Text( + // ignore: lines_longer_than_80_chars 'Please enable access to your photos \nand videos so you can share them with friends.', style: StreamChatTheme.of(context).textTheme.body.copyWith( color: StreamChatTheme.of(context).colorTheme.grey), textAlign: TextAlign.center, ), - SizedBox(height: 6), + const SizedBox(height: 6), Center( child: Text( 'Allow access to your gallery', From 160baa5d956a2d9b3c54b914609c8714d562a317 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 15:53:08 +0530 Subject: [PATCH 194/599] lint changes mlv --- .../lib/src/message_list_view.dart | 410 +++++++++--------- 1 file changed, 211 insertions(+), 199 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 5b3b0359..2800a041 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -12,34 +12,60 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/system_message.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:visibility_detector/visibility_detector.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/swipeable.dart'; -import '../stream_chat_flutter.dart'; -import 'connection_status_builder.dart'; -import 'date_divider.dart'; -import 'extension.dart'; -import 'swipeable.dart'; - +/// Widget builder for message typedef MessageBuilder = Widget Function( BuildContext, MessageDetails, List, ); + +/// Widget builder for parent message typedef ParentMessageBuilder = Widget Function( BuildContext, Message?, ); + +/// Widget builder for system message typedef SystemMessageBuilder = Widget Function( BuildContext, Message, ); + +/// Widget builder for thread typedef ThreadBuilder = Widget Function(BuildContext context, Message? parent); + +/// Callback for thread taps typedef ThreadTapCallback = void Function(Message, Widget?); +/// Callback on message swiped typedef OnMessageSwiped = void Function(Message); + +/// Callback on message tapped typedef OnMessageTap = void Function(Message); + +/// Callback on reply tapped typedef ReplyTapCallback = void Function(Message); +/// Class for message details class MessageDetails { + /// Constructor for creating [MessageDetails] + MessageDetails( + BuildContext context, + this.message, + List messages, + this.index, + ) { + isMyMessage = message.user?.id == StreamChat.of(context).user?.id; + isLastUser = index + 1 < messages.length && + message.user?.id == messages[index + 1].user?.id; + isNextUser = + index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id; + } + /// True if the message belongs to the current user bool? isMyMessage; @@ -54,19 +80,6 @@ class MessageDetails { /// The index of the message int index; - - MessageDetails( - BuildContext context, - this.message, - List messages, - this.index, - ) { - isMyMessage = message.user?.id == StreamChat.of(context).user?.id; - isLastUser = index + 1 < messages.length && - message.user?.id == messages[index + 1].user?.id; - isNextUser = - index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id; - } } /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview.png) @@ -104,10 +117,12 @@ class MessageDetails { /// ``` /// /// -/// Make sure to have a [StreamChannel] ancestor in order to provide the information about the channels. +/// Make sure to have a [StreamChannel] ancestor in order to +/// provide the information about the channels. /// The widget uses a [ListView.custom] to render the list of channels. /// -/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. +/// The widget components render the ui based on the first +/// ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. class MessageListView extends StatefulWidget { /// Instantiate a new MessageListView @@ -158,10 +173,12 @@ class MessageListView extends StatefulWidget { final ThreadBuilder? threadBuilder; /// Function called when tapping on a thread - /// By default it calls [Navigator.push] using the widget built using [threadBuilder] + /// By default it calls [Navigator.push] using the widget + /// built using [threadBuilder] final ThreadTapCallback? onThreadTap; - /// If true will show a scroll to bottom message when there are new messages and the scroll offset is not zero + /// If true will show a scroll to bottom message when there are new + /// messages and the scroll offset is not zero final bool showScrollToBottom; /// Parent message in case of a thread @@ -201,8 +218,10 @@ class MessageListView extends StatefulWidget { /// Color used while highlighting initial message final Color? messageHighlightColor; + /// Callback when show message is tapped final ShowMessageCallback? onShowMessage; + /// Flag for showing tile on header final bool showConnectionStateTile; /// Function called when messages are fetched @@ -214,8 +233,10 @@ class MessageListView extends StatefulWidget { /// Function used to build an empty widget final WidgetBuilder? emptyBuilder; - /// Callback triggered when an error occurs while performing the given request. - /// This parameter can be used to display an error message to users in the event + /// Callback triggered when an error occurs while performing the + /// given request. + /// This parameter can be used to display an error message to + /// users in the event /// of a connection failure. final ErrorBuilder? errorWidgetBuilder; @@ -223,10 +244,12 @@ class MessageListView extends StatefulWidget { final bool Function(Message)? messageFilter; /// Attachment builders for the default message widget - /// Please change this in the [MessageWidget] if you are using a custom implementation + /// Please change this in the [MessageWidget] if you are using a + /// custom implementation final Map? customAttachmentBuilders; - /// Called when any message is tapped except a system message (use [onSystemMessageTap] instead) + /// Called when any message is tapped except a system message + /// (use [onSystemMessageTap] instead) final OnMessageTap? onMessageTap; /// Called when system message is tapped @@ -238,6 +261,7 @@ class MessageListView extends StatefulWidget { /// Customize the MessageWidget textBuilder final void Function(BuildContext context, Message message)? textBuilder; + /// Callback for when link is tapped final void Function(String link)? onLinkTap; @override @@ -257,9 +281,8 @@ class _MessageListViewState extends State { if (streamChannel!.initialMessageId != null) { final messages = streamChannel!.channel.state!.messages; final totalMessages = messages.length; - final messageIndex = messages.indexWhere((e) { - return e.id == streamChannel!.initialMessageId; - }); + final messageIndex = + messages.indexWhere((e) => e.id == streamChannel!.initialMessageId); final index = totalMessages - messageIndex; if (index != 0) return index - 1; return index; @@ -272,9 +295,7 @@ class _MessageListViewState extends State { return 0; } - bool _isInitialMessage(String id) { - return streamChannel!.initialMessageId == id; - } + bool _isInitialMessage(String id) => streamChannel!.initialMessageId == id; bool get _upToDate => streamChannel!.channel.state!.isUpToDate; @@ -295,50 +316,46 @@ class _MessageListViewState extends State { final MessageListController _messageListController = MessageListController(); @override - Widget build(BuildContext context) { - return MessageListCore( - messageFilter: widget.messageFilter, - loadingBuilder: widget.loadingBuilder ?? - (context) { - return Center( - child: const CircularProgressIndicator(), - ); - }, - emptyBuilder: widget.emptyBuilder ?? - (context) { - return Center( - child: Text( - 'No chats here yet...', - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5)), - ), - ); - }, - messageListBuilder: widget.messageListBuilder ?? - (context, list) { - return _buildListView(list); - }, - messageListController: _messageListController, - parentMessage: widget.parentMessage, - showScrollToBottom: widget.showScrollToBottom, - errorWidgetBuilder: widget.errorWidgetBuilder ?? - (BuildContext context, Object error) { - return Center( - child: Text( - 'Something went wrong', - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5)), - ), - ); - }, - ); - } + Widget build(BuildContext context) => MessageListCore( + messageFilter: widget.messageFilter, + loadingBuilder: widget.loadingBuilder ?? + (context) => const Center( + child: CircularProgressIndicator(), + ), + emptyBuilder: widget.emptyBuilder ?? + (context) => Center( + child: Text( + 'No chats here yet...', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), + ), + ), + messageListBuilder: widget.messageListBuilder ?? + (context, list) => _buildListView(list), + messageListController: _messageListController, + parentMessage: widget.parentMessage, + showScrollToBottom: widget.showScrollToBottom, + errorWidgetBuilder: widget.errorWidgetBuilder ?? + (BuildContext context, Object error) => Center( + child: Text( + 'Something went wrong', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), + ), + ), + ); Widget _buildListView(List data) { messages = data; @@ -384,6 +401,7 @@ class _MessageListViewState extends State { } return InfoTile( + // ignore: avoid_bool_literals_in_conditional_expressions showMessage: widget.showConnectionStateTile ? showStatus : false, tileAnchor: Alignment.topCenter, childAnchor: Alignment.topCenter, @@ -426,8 +444,8 @@ class _MessageListViewState extends State { itemCount: messages.length + 2 + (_isThreadConversation ? 1 : 0), separatorBuilder: (context, i) { - if (i == messages.length) return Offstage(); - if (i == 0) return SizedBox(height: 30); + if (i == messages.length) return const Offstage(); + if (i == 0) return const SizedBox(height: 30); if (i == messages.length + 1) { final replyCount = widget.parentMessage!.replyCount; return Container( @@ -438,6 +456,7 @@ class _MessageListViewState extends State { child: Padding( padding: const EdgeInsets.all(8), child: Text( + // ignore: lines_longer_than_80_chars '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', textAlign: TextAlign.center, style: StreamChatTheme.of(context) @@ -481,9 +500,9 @@ class _MessageListViewState extends State { !isNextUserSame || isThread || isDeleted) { - return SizedBox(height: 8); + return const SizedBox(height: 8); } - return SizedBox(height: 2); + return const SizedBox(height: 2); }, itemBuilder: (context, i) { if (i == messages.length + 2) { @@ -559,13 +578,13 @@ class _MessageListViewState extends State { builder: (context, values, _) { final items = _itemPositionListener.itemPositions.value; if (items.isEmpty || messages.isEmpty) { - return SizedBox(); + return const SizedBox(); } var index = _getTopElement(values).index; if (index > messages.length) { - return SizedBox(); + return const SizedBox(); } if (index == messages.length) { @@ -587,95 +606,90 @@ class _MessageListViewState extends State { } Future _paginateData( - StreamChannelState? channel, QueryDirection direction) { - return _messageListController.paginateData!(direction: direction); - } + StreamChannelState? channel, QueryDirection direction) => + _messageListController.paginateData!(direction: direction); - ItemPosition _getTopElement(Iterable values) { - return values - .where((ItemPosition position) => position.itemLeadingEdge < 0.9) - .reduce((ItemPosition max, ItemPosition position) => - position.itemLeadingEdge > max.itemLeadingEdge ? position : max); - } + ItemPosition _getTopElement(Iterable values) => values + .where((ItemPosition position) => position.itemLeadingEdge < 0.9) + .reduce((ItemPosition max, ItemPosition position) => + position.itemLeadingEdge > max.itemLeadingEdge ? position : max); - Widget _buildScrollToBottom() { - return StreamBuilder>( - stream: Rx.combineLatest2( - streamChannel!.channel.state!.isUpToDateStream, - streamChannel!.channel.state!.unreadCountStream, - (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), - ), - builder: (_, snapshot) { - if (snapshot.hasError) { - return Offstage(); - } else if (!snapshot.hasData) { - return Offstage(); - } - final isUpToDate = snapshot.data!.item1; - final showScrollToBottom = !isUpToDate || _showScrollToBottom; - if (!showScrollToBottom) { - return Offstage(); - } - final unreadCount = snapshot.data!.item2; - final showUnreadCount = unreadCount > 0 && - streamChannel!.channel.state!.members.any((e) => - e.userId == streamChannel!.channel.client.state.user!.id); - return Positioned( - bottom: 8, - right: 8, - width: 40, - height: 40, - child: Stack( - clipBehavior: Clip.none, - children: [ - FloatingActionButton( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, - onPressed: () { - if (unreadCount > 0) { - streamChannel!.channel.markRead(); - } - if (!_upToDate) { - _bottomPaginationActive = false; - _topPaginationActive = false; - streamChannel!.reloadChannel(); - } else { - setState(() => _showScrollToBottom = false); - _scrollController!.scrollTo( - index: 0, - duration: Duration(seconds: 1), - curve: Curves.easeInOut, - ); - } - }, - child: StreamSvgIcon.down( - color: StreamChatTheme.of(context).colorTheme.black, + Widget _buildScrollToBottom() => StreamBuilder>( + stream: Rx.combineLatest2( + streamChannel!.channel.state!.isUpToDateStream, + streamChannel!.channel.state!.unreadCountStream, + (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), + ), + builder: (_, snapshot) { + if (snapshot.hasError) { + return const Offstage(); + } else if (!snapshot.hasData) { + return const Offstage(); + } + final isUpToDate = snapshot.data!.item1; + final showScrollToBottom = !isUpToDate || _showScrollToBottom; + if (!showScrollToBottom) { + return const Offstage(); + } + final unreadCount = snapshot.data!.item2; + final showUnreadCount = unreadCount > 0 && + streamChannel!.channel.state!.members.any((e) => + e.userId == streamChannel!.channel.client.state.user!.id); + return Positioned( + bottom: 8, + right: 8, + width: 40, + height: 40, + child: Stack( + clipBehavior: Clip.none, + children: [ + FloatingActionButton( + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + onPressed: () { + if (unreadCount > 0) { + streamChannel!.channel.markRead(); + } + if (!_upToDate) { + _bottomPaginationActive = false; + _topPaginationActive = false; + streamChannel!.reloadChannel(); + } else { + setState(() => _showScrollToBottom = false); + _scrollController!.scrollTo( + index: 0, + duration: const Duration(seconds: 1), + curve: Curves.easeInOut, + ); + } + }, + child: StreamSvgIcon.down( + color: StreamChatTheme.of(context).colorTheme.black, + ), ), - ), - if (showUnreadCount) - Positioned( - width: 20, - height: 20, - left: 10, - top: -10, - child: CircleAvatar( - child: Padding( - padding: const EdgeInsets.all(3), - child: Text( - '$unreadCount', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, + if (showUnreadCount) + Positioned( + width: 20, + height: 20, + left: 10, + top: -10, + child: CircleAvatar( + child: Padding( + padding: const EdgeInsets.all(3), + child: Text( + '$unreadCount', + style: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.bold, + ), ), ), ), ), - ), - ], - ), - ); - }, - ); - } + ], + ), + ); + }, + ); Widget _buildLoadingIndicator( StreamChannelState? streamChannel, @@ -685,7 +699,7 @@ class _MessageListViewState extends State { ? streamChannel!.queryTopMessages : streamChannel!.queryBottomMessages; return StreamBuilder( - key: Key('LOADING-INDICATOR'), + key: const Key('LOADING-INDICATOR'), stream: stream, initialData: false, builder: (context, snapshot) { @@ -695,24 +709,24 @@ class _MessageListViewState extends State { .colorTheme .accentRed .withOpacity(.2), - child: Center( + child: const Center( child: Text('Error loading messages'), ), ); } if (!snapshot.data!) { if (!_isThreadConversation && direction == QueryDirection.top) { - return Container( + return const SizedBox( height: 52, width: double.infinity, ); } - return Offstage(); + return const Offstage(); } - return Center( + return const Center( child: Padding( - padding: const EdgeInsets.all(8), - child: const CircularProgressIndicator(), + padding: EdgeInsets.all(8), + child: CircularProgressIndicator(), ), ); }, @@ -728,7 +742,7 @@ class _MessageListViewState extends State { Widget messageWidget; if (widget.messageBuilder != null) { messageWidget = Builder( - key: ValueKey('TOP-MESSAGE'), + key: const ValueKey('TOP-MESSAGE'), builder: (_) => widget.messageBuilder!( context, MessageDetails( @@ -810,7 +824,7 @@ class _MessageListViewState extends State { padding: const EdgeInsets.all(8), showSendingIndicator: false, onThreadTap: _onThreadTap as void Function(Message)?, - borderRadiusGeometry: BorderRadius.only( + borderRadiusGeometry: const BorderRadius.only( topLeft: Radius.circular(16), bottomLeft: Radius.circular(2), topRight: Radius.circular(16), @@ -886,8 +900,8 @@ class _MessageListViewState extends State { final channel = streamChannel!.channel; final readList = channel.state?.read?.where((read) { if (read.user.id == userId) return false; - return (read.lastRead.isAfter(message.createdAt) || - read.lastRead.isAtSameMomentAs(message.createdAt)); + return read.lastRead.isAfter(message.createdAt) || + read.lastRead.isAtSameMomentAs(message.createdAt); }).toList() ?? []; @@ -945,6 +959,7 @@ class _MessageListViewState extends State { showSendingIndicator: showSendingIndicator, showUserAvatar: showUserAvatar, onQuotedMessageTap: (quotedMessageId) async { + // ignore: prefer_function_declarations_over_variables final scrollToIndex = () { final index = messages.indexWhere((m) => m.id == quotedMessageId); _scrollController?.scrollTo( @@ -984,14 +999,14 @@ class _MessageListViewState extends State { ), attachmentPadding: EdgeInsets.all(hasFileAttachment ? 4 : 2), borderRadiusGeometry: BorderRadius.only( - topLeft: Radius.circular(16), + topLeft: const Radius.circular(16), bottomLeft: Radius.circular( (timeDiff >= 1 || !isNextUserSame) && !(hasReplies || isThreadMessage) ? 0 : 16, ), - topRight: Radius.circular(16), - bottomRight: Radius.circular(16), + topRight: const Radius.circular(16), + bottomRight: const Radius.circular(16), ), textPadding: EdgeInsets.symmetric( vertical: 8, @@ -1031,7 +1046,7 @@ class _MessageListViewState extends State { !message.isEphemeral && widget.onMessageSwiped != null) { child = Container( - decoration: BoxDecoration(), + decoration: const BoxDecoration(), clipBehavior: Clip.hardEdge, child: Swipeable( onSwipeEnd: () { @@ -1059,12 +1074,10 @@ class _MessageListViewState extends State { ), duration: const Duration(seconds: 3), onEnd: () => initialMessageHighlightComplete = true, - builder: (_, color, child) { - return Container( - color: color, - child: child, - ); - }, + builder: (_, color, child) => Container( + color: color, + child: child, + ), child: Padding( padding: const EdgeInsets.only(top: 4), child: child, @@ -1124,19 +1137,18 @@ class _MessageListViewState extends State { _onThreadTap = (Message message) { Navigator.push( context, - MaterialPageRoute(builder: (_) { - return StreamBuilder( - stream: streamChannel!.channel.state!.messagesStream.map( - (messages) => - messages!.firstWhere((m) => m.id == message.id)), - initialData: message, - builder: (_, snapshot) { - return StreamChannel( - channel: streamChannel!.channel, - child: widget.threadBuilder!(context, snapshot.data), - ); - }); - }), + MaterialPageRoute( + builder: (_) => StreamBuilder( + stream: streamChannel!.channel.state!.messagesStream.map( + (messages) => + messages!.firstWhere((m) => m.id == message.id)), + initialData: message, + builder: (_, snapshot) => StreamChannel( + channel: streamChannel!.channel, + child: widget.threadBuilder!(context, snapshot.data), + ), + ), + ), ); }; } From 3812eadf53a634200b75d41deeaa58639560bb56 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 15:58:20 +0530 Subject: [PATCH 195/599] lint changes mrm --- .../lib/src/message_reactions_modal.dart | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) 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 937cf681..e2392b50 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -7,22 +7,11 @@ import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; -import 'extension.dart'; -import 'message_widget.dart'; -import 'stream_chat_theme.dart'; - +/// Modal widget for displaying message reactions class MessageReactionsModal extends StatelessWidget { - final Message message; - final MessageTheme messageTheme; - final bool reverse; - final bool showReactions; - final DisplayWidget showUserAvatar; - final ShapeBorder? messageShape; - final ShapeBorder? attachmentShape; - final void Function(User)? onUserAvatarTap; - final BorderRadius? attachmentBorderRadiusGeometry; - + /// Constructor for creating a [MessageReactionsModal] reactions const MessageReactionsModal({ Key? key, required this.message, @@ -36,6 +25,33 @@ class MessageReactionsModal extends StatelessWidget { this.attachmentBorderRadiusGeometry, }) : super(key: key); + /// Message to display reactions of + final Message message; + + /// [MessageTheme] to apply to [message] + final MessageTheme messageTheme; + + /// Flag to reverse message + final bool reverse; + + /// Flag to show reactions on message + final bool showReactions; + + /// Enum to change user avatar config + final DisplayWidget showUserAvatar; + + /// [ShapeBorder] to apply to message + final ShapeBorder? messageShape; + + /// [ShapeBorder] to apply to attachment + final ShapeBorder? attachmentShape; + + /// Callback when user avatar is tapped + final void Function(User)? onUserAvatarTap; + + /// [BorderRadius] to apply to attachments + final BorderRadius? attachmentBorderRadiusGeometry; + @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; @@ -60,7 +76,7 @@ class MessageReactionsModal extends StatelessWidget { return TweenAnimationBuilder( tween: Tween(begin: 0, end: 1), - duration: Duration(milliseconds: 300), + duration: const Duration(milliseconds: 300), curve: Curves.easeInOutBack, builder: (context, val, snapshot) { final hasFileAttachment = @@ -110,7 +126,7 @@ class MessageReactionsModal extends StatelessWidget { const SizedBox(height: 8), IgnorePointer( child: MessageWidget( - key: Key('MessageWidget'), + key: const Key('MessageWidget'), reverse: reverse, message: message.copyWith( text: message.text!.length > 200 @@ -205,7 +221,7 @@ class MessageReactionsModal extends StatelessWidget { ) { final isCurrentUser = reaction.user?.id == currentUser.id; return ConstrainedBox( - constraints: BoxConstraints.loose(Size( + constraints: BoxConstraints.loose(const Size( 64, 98, )), @@ -218,11 +234,11 @@ class MessageReactionsModal extends StatelessWidget { UserAvatar( onTap: onUserAvatarTap, user: reaction.user!, - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 64, width: 64, ), - onlineIndicatorConstraints: BoxConstraints.tightFor( + onlineIndicatorConstraints: const BoxConstraints.tightFor( height: 12, width: 12, ), From 3976ac7f4f3e23dc65ae27537faf5c0b184a453f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 16:03:00 +0530 Subject: [PATCH 196/599] lint changes message --- .../lib/src/message_search_item.dart | 12 +- .../lib/src/message_search_list_view.dart | 142 +++++++++--------- .../lib/src/message_text.dart | 14 +- 3 files changed, 88 insertions(+), 80 deletions(-) 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 7be8893b..dc276cb3 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_item.dart @@ -5,9 +5,11 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// It shows the current [Message] preview. /// -/// Usually you don't use this widget as it's the default item used by [MessageSearchListView]. +/// Usually you don't use this widget as it's the default item used by +/// [MessageSearchListView]. /// -/// The widget renders the ui based on the first ancestor of type [StreamChatTheme]. +/// The widget renders the ui based on the first ancestor of type +/// [StreamChatTheme]. /// Modify it to change the widget appearance. class MessageSearchItem extends StatelessWidget { /// Instantiate a new MessageSearchItem @@ -38,7 +40,7 @@ class MessageSearchItem extends StatelessWidget { leading: UserAvatar( user: user, showOnlineStatus: showOnlineStatus, - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 40, width: 40, ), @@ -69,7 +71,7 @@ class MessageSearchItem extends StatelessWidget { subtitle: Row( children: [ Expanded(child: _buildSubtitle(context, message)), - SizedBox(width: 16), + const SizedBox(width: 16), _buildDate(context, message), ], ), @@ -149,7 +151,7 @@ class MessageSearchItem extends StatelessWidget { TextStyle? mentionsTextStyle) { final textList = text.split(' '); final resList = []; - for (var e in textList) { + for (final e in textList) { if (mentions.isNotEmpty && mentions.any((element) => '@${element.name}' == e)) { resList.add(TextSpan( 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 b88af62e..1d9ef272 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 @@ -2,8 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/message_search_item.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - -import '../stream_chat_flutter.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Callback called when tapping on a user typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); @@ -43,10 +42,12 @@ typedef EmptyMessageSearchBuilder = Widget Function( /// ``` /// /// -/// Make sure to have a [MessageSearchBloc] ancestor in order to provide the information about the messages. +/// Make sure to have a [MessageSearchBloc] ancestor in order to provide the +/// information about the messages. /// The widget uses a [ListView.separated] to render the list of messages. /// -/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. +/// The widget components render the ui based on the first ancestor of type +/// [StreamChatTheme]. /// Modify it to change the widget appearance. class MessageSearchListView extends StatefulWidget { /// Instantiate a new MessageSearchListView @@ -78,8 +79,10 @@ class MessageSearchListView extends StatefulWidget { final Filter filters; /// The sorting used for the channels matching the filters. - /// Sorting is based on field and direction, multiple sorting options can be provided. - /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. + /// Sorting is based on field and direction, multiple sorting options + /// can be provided. + /// You can sort based on last_updated, last_message_at, updated_at, + /// created_at or member_count. /// Direction can be ascending or descending. final List? sortOptions; @@ -109,6 +112,7 @@ class MessageSearchListView extends StatefulWidget { /// Set it to false to disable the pull-to-refresh widget final bool pullToRefresh; + /// Show error tile on top final bool showErrorTile; /// The builder that is used when the search messages are fetched @@ -132,73 +136,63 @@ class _MessageSearchListViewState extends State { MessageSearchListController(); @override - Widget build(BuildContext context) { - return MessageSearchListCore( - filters: widget.filters, - sortOptions: widget.sortOptions, - messageQuery: widget.messageQuery, - paginationParams: widget.paginationParams, - messageFilters: widget.messageFilters, - messageSearchListController: _messageSearchListController, - emptyBuilder: widget.emptyBuilder ?? - (context) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: Text('There are no messages currently'), + Widget build(BuildContext context) => MessageSearchListCore( + filters: widget.filters, + sortOptions: widget.sortOptions, + messageQuery: widget.messageQuery, + paginationParams: widget.paginationParams, + messageFilters: widget.messageFilters, + messageSearchListController: _messageSearchListController, + emptyBuilder: widget.emptyBuilder ?? + (context) => LayoutBuilder( + builder: (context, viewportConstraints) => + SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: const Center( + child: Text('There are no messages currently'), + ), ), ), - ); - }, - ); - }, - errorBuilder: widget.errorBuilder ?? - (BuildContext context, dynamic error) { - if (error is Error) { - print(error.stackTrace); - } - return InfoTile( - showMessage: widget.showErrorTile, - tileAnchor: Alignment.topCenter, - childAnchor: Alignment.topCenter, - message: 'An error occurred.', - child: Container(), - ); - }, - loadingBuilder: widget.loadingBuilder ?? - (context) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: CircularProgressIndicator(), + ), + errorBuilder: widget.errorBuilder ?? + (BuildContext context, dynamic error) { + if (error is Error) { + print(error.stackTrace); + } + return InfoTile( + showMessage: widget.showErrorTile, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: 'An error occurred.', + child: Container(), + ); + }, + loadingBuilder: widget.loadingBuilder ?? + (context) => LayoutBuilder( + builder: (context, viewportConstraints) => + SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: const Center( + child: CircularProgressIndicator(), + ), ), ), - ); - }, - ); - }, - childBuilder: widget.childBuilder ?? _buildListView, - ); - } + ), + childBuilder: widget.childBuilder ?? _buildListView, + ); - Widget _separatorBuilder(BuildContext context, int index) { - return Container( - height: 1, - color: StreamChatTheme.of(context).colorTheme.greyWhisper, - ); - } + Widget _separatorBuilder(BuildContext context, int index) => Container( + height: 1, + color: StreamChatTheme.of(context).colorTheme.greyWhisper, + ); Widget _listItemBuilder( BuildContext context, GetMessageResponse getMessageResponse) { @@ -224,8 +218,8 @@ class _MessageSearchListViewState extends State { .colorTheme .accentRed .withOpacity(.2), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 16), child: Center( child: Text('Error loading messages'), ), @@ -234,9 +228,11 @@ class _MessageSearchListViewState extends State { } return Container( height: 100, - padding: EdgeInsets.all(32), + padding: const EdgeInsets.all(32), child: Center( - child: snapshot.data! ? CircularProgressIndicator() : Container(), + child: snapshot.data! + ? const CircularProgressIndicator() + : Container(), ), ); }); @@ -246,7 +242,7 @@ class _MessageSearchListViewState extends State { final items = data; Widget child = ListView.separated( - physics: AlwaysScrollableScrollPhysics(), + physics: const AlwaysScrollableScrollPhysics(), itemCount: items.isNotEmpty ? items.length + 1 : items.length, separatorBuilder: (_, index) { if (widget.separatorBuilder != null) { diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index b0099eff..8c95120d 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -2,11 +2,13 @@ import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'stream_chat_theme.dart'; -import 'utils.dart'; +import '../stream_chat_flutter.dart'; +/// Text widget to display in message class MessageText extends StatelessWidget { + /// Constructor for creating a [MessageText] widget const MessageText({ Key? key, required this.message, @@ -15,9 +17,16 @@ class MessageText extends StatelessWidget { this.onLinkTap, }) : super(key: key); + /// Message whose text is to be displayed final Message message; + + /// Callback for when mention is tapped final void Function(User)? onMentionTap; + + /// Callback for when link is tapped final void Function(String)? onLinkTap; + + /// [MessageTheme] whose text theme is to be applied final MessageTheme messageTheme; @override @@ -71,6 +80,7 @@ class MessageText extends StatelessWidget { String _replaceMentions(String text) { message.mentionedUsers.map((u) => u.name).toSet().forEach((userName) { + // ignore: parameter_assignments text = text.replaceAll( '@$userName', '[@$userName](@${userName.replaceAll(' ', '')})'); }); From 660bae35832c5cf7d779c2046cf88e914f69366b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 5 May 2021 12:36:13 +0200 Subject: [PATCH 197/599] fix messageinput overflow when no action is available --- .../lib/src/message_input.dart | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index aa14882e..57125e61 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -482,25 +482,29 @@ class MessageInputState extends State { ), splashRadius: 24, ), - secondChild: FittedBox( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - if (!widget.disableAttachments) _buildAttachmentButton(), - if (widget.showCommandsButton && - widget.editMessage == null && - StreamChannel.of(context) - .channel - .config - ?.commands - .isNotEmpty == - true) - _buildCommandButton(), - ...widget.actions ?? [], - ].insertBetween(const SizedBox(width: 8)), - ), - ), + secondChild: widget.disableAttachments && + !widget.showCommandsButton && + widget.actions?.isNotEmpty != true + ? Offstage() + : FittedBox( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (!widget.disableAttachments) _buildAttachmentButton(), + if (widget.showCommandsButton && + widget.editMessage == null && + StreamChannel.of(context) + .channel + .config + ?.commands + .isNotEmpty == + true) + _buildCommandButton(), + ...widget.actions ?? [], + ].insertBetween(const SizedBox(width: 8)), + ), + ), duration: Duration(milliseconds: 300), alignment: Alignment.center, ), From a55af6befb6ca86dafd216801201a300a4864c5d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 16:12:36 +0530 Subject: [PATCH 198/599] lint changes message --- .../lib/src/message_widget.dart | 546 +++++++++--------- 1 file changed, 288 insertions(+), 258 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 32801cd1..1866da0b 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -15,17 +15,17 @@ import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter/src/image_group.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; -import 'attachment/attachment.dart'; -import 'extension.dart'; -import 'image_group.dart'; -import 'message_text.dart'; - +/// Widget builder for building attachments typedef AttachmentBuilder = Widget Function( BuildContext, Message, List, ); + +/// Callback for when quoted message is tapped typedef OnQuotedMessageTap = void Function(String?); /// The display behaviour of a widget @@ -45,119 +45,13 @@ enum DisplayWidget { /// /// It shows a message with reactions, replies and user avatar. /// -/// Usually you don't use this widget as it's the default message widget used by [MessageListView]. +/// Usually you don't use this widget as it's the default message widget used by +/// [MessageListView]. /// -/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. +/// The widget components render the ui based on the first ancestor of type +/// [StreamChatTheme]. /// Modify it to change the widget appearance. class MessageWidget extends StatefulWidget { - /// Function called on mention tap - final void Function(User)? onMentionTap; - - /// The function called when tapping on replies - final void Function(Message)? onThreadTap; - final void Function(Message)? onReplyTap; - final Widget Function(BuildContext, Message)? editMessageInputBuilder; - final Widget Function(BuildContext, Message)? textBuilder; - - /// Function called on long press - final void Function(BuildContext, Message)? onMessageActions; - - /// The message - final Message message; - - /// The message theme - final MessageTheme messageTheme; - - /// If true the widget will be mirrored - final bool reverse; - - /// The shape of the message text - final ShapeBorder? shape; - - /// The shape of an attachment - final ShapeBorder? attachmentShape; - - /// The borderside of the message text - final BorderSide? borderSide; - - /// The borderside of an attachment - final BorderSide? attachmentBorderSide; - - /// The border radius of the message text - final BorderRadiusGeometry? borderRadiusGeometry; - - /// The border radius of an attachment - final BorderRadiusGeometry? attachmentBorderRadiusGeometry; - - /// The padding of the widget - final EdgeInsetsGeometry? padding; - - /// The internal padding of the message text - final EdgeInsets textPadding; - - /// The internal padding of an attachment - final EdgeInsetsGeometry attachmentPadding; - - /// It controls the display behaviour of the user avatar - final DisplayWidget showUserAvatar; - - /// It controls the display behaviour of the sending indicator - final bool showSendingIndicator; - - /// If true the widget will show the reactions - final bool showReactions; - - final bool allRead; - - /// If true the widget will show the thread reply indicator - final bool showThreadReplyIndicator; - - /// If true the widget will show the show in channel indicator - final bool showInChannelIndicator; - - /// The function called when tapping on UserAvatar - final void Function(User)? onUserAvatarTap; - - /// The function called when tapping on a link - final void Function(String)? onLinkTap; - - /// Used in [MessageReactionsModal] and [MessageActionsModal] - final bool showReactionPickerIndicator; - - final List? readList; - - final ShowMessageCallback? onShowMessage; - final ValueChanged? onReturnAction; - - /// If true show the users username next to the timestamp of the message - final bool showUsername; - final bool showTimestamp; - - final bool showReplyMessage; - final bool showThreadReplyMessage; - final bool showEditMessage; - final bool showCopyMessage; - final bool showDeleteMessage; - final bool showResendMessage; - - final bool showFlagButton; - final Map attachmentBuilders; - - /// Center user avatar with bottom of the message - final bool translateUserAvatar; - - /// Function called when quotedMessage is tapped - final OnQuotedMessageTap? onQuotedMessageTap; - - /// Function called when message is tapped - final void Function(Message)? onMessageTap; - - /// List of custom actions shown on message long tap - final List customActions; - - // Customize onTap on attachment - final void Function(Message message, Attachment attachment)? onAttachmentTap; - /// MessageWidget({ Key? key, @@ -273,24 +167,24 @@ class MessageWidget extends StatefulWidget { return wrapAttachmentWidget( context, Column( - children: attachments.map((attachment) { - return VideoAttachment( - attachment: attachment, - messageTheme: messageTheme, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), - message: message, - onShowMessage: onShowMessage, - onReturnAction: onReturnAction, - onAttachmentTap: onAttachmentTap != null - ? () { - onAttachmentTap(message, attachment); - } - : null, - ); - }).toList(), + children: attachments + .map((attachment) => VideoAttachment( + attachment: attachment, + messageTheme: messageTheme, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), + message: message, + onShowMessage: onShowMessage, + onReturnAction: onReturnAction, + onAttachmentTap: onAttachmentTap != null + ? () { + onAttachmentTap(message, attachment); + } + : null, + )) + .toList(), ), border, reverse, @@ -306,18 +200,18 @@ class MessageWidget extends StatefulWidget { return wrapAttachmentWidget( context, Column( - children: attachments.map((attachment) { - return GiphyAttachment( - attachment: attachment, - message: message, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), - onShowMessage: onShowMessage, - onReturnAction: onReturnAction, - ); - }).toList(), + children: attachments + .map((attachment) => GiphyAttachment( + attachment: attachment, + message: message, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), + onShowMessage: onShowMessage, + onReturnAction: onReturnAction, + )) + .toList(), ), border, reverse, @@ -336,23 +230,21 @@ class MessageWidget extends StatefulWidget { return Column( children: attachments - .map((attachment) { - return wrapAttachmentWidget( - context, - FileAttachment( - message: message, - attachment: attachment, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, + .map((attachment) => wrapAttachmentWidget( + context, + FileAttachment( + message: message, + attachment: attachment, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), ), - ), - border, - reverse, - attachmentBorderRadiusGeometry as BorderRadius? ?? - BorderRadius.zero, - ); - }) + border, + reverse, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, + )) .insertBetween(SizedBox( height: attachmentPadding.vertical / 2, )) @@ -362,6 +254,141 @@ class MessageWidget extends StatefulWidget { }..addAll(customAttachmentBuilders ?? {}), super(key: key); + /// Function called on mention tap + final void Function(User)? onMentionTap; + + /// The function called when tapping on threads + final void Function(Message)? onThreadTap; + + /// The function called when tapping on replies + final void Function(Message)? onReplyTap; + + /// Widget builder for edit message layout + final Widget Function(BuildContext, Message)? editMessageInputBuilder; + + /// Widget builder for building text + final Widget Function(BuildContext, Message)? textBuilder; + + /// Function called on long press + final void Function(BuildContext, Message)? onMessageActions; + + /// The message + final Message message; + + /// The message theme + final MessageTheme messageTheme; + + /// If true the widget will be mirrored + final bool reverse; + + /// The shape of the message text + final ShapeBorder? shape; + + /// The shape of an attachment + final ShapeBorder? attachmentShape; + + /// The borderside of the message text + final BorderSide? borderSide; + + /// The borderside of an attachment + final BorderSide? attachmentBorderSide; + + /// The border radius of the message text + final BorderRadiusGeometry? borderRadiusGeometry; + + /// The border radius of an attachment + final BorderRadiusGeometry? attachmentBorderRadiusGeometry; + + /// The padding of the widget + final EdgeInsetsGeometry? padding; + + /// The internal padding of the message text + final EdgeInsets textPadding; + + /// The internal padding of an attachment + final EdgeInsetsGeometry attachmentPadding; + + /// It controls the display behaviour of the user avatar + final DisplayWidget showUserAvatar; + + /// It controls the display behaviour of the sending indicator + final bool showSendingIndicator; + + /// If true the widget will show the reactions + final bool showReactions; + + /// + final bool allRead; + + /// If true the widget will show the thread reply indicator + final bool showThreadReplyIndicator; + + /// If true the widget will show the show in channel indicator + final bool showInChannelIndicator; + + /// The function called when tapping on UserAvatar + final void Function(User)? onUserAvatarTap; + + /// The function called when tapping on a link + final void Function(String)? onLinkTap; + + /// Used in [MessageReactionsModal] and [MessageActionsModal] + final bool showReactionPickerIndicator; + + /// List of users who read + final List? readList; + + /// Callback when show message is tapped + final ShowMessageCallback? onShowMessage; + + /// Handle return actions like reply message + final ValueChanged? onReturnAction; + + /// If true show the users username next to the timestamp of the message + final bool showUsername; + + /// Show message timestamp + final bool showTimestamp; + + /// Show reply action + final bool showReplyMessage; + + /// Show thread reply action + final bool showThreadReplyMessage; + + /// Show edit action + final bool showEditMessage; + + /// Show copy action + final bool showCopyMessage; + + /// Show delete action + final bool showDeleteMessage; + + /// Show resend action + final bool showResendMessage; + + /// Show flag action + final bool showFlagButton; + + /// Builder for respective attachment types + final Map attachmentBuilders; + + /// Center user avatar with bottom of the message + final bool translateUserAvatar; + + /// Function called when quotedMessage is tapped + final OnQuotedMessageTap? onQuotedMessageTap; + + /// Function called when message is tapped + final void Function(Message)? onMessageTap; + + /// List of custom actions shown on message long tap + final List customActions; + + /// Customize onTap on attachment + final void Function(Message message, Attachment attachment)? onAttachmentTap; + @override _MessageWidgetState createState() => _MessageWidgetState(); } @@ -437,7 +464,7 @@ class _MessageWidgetState extends State ? null : () => onLongPress(context), child: Padding( - padding: widget.padding ?? EdgeInsets.all(8), + padding: widget.padding ?? const EdgeInsets.all(8), child: Transform( alignment: Alignment.center, transform: Matrix4.rotationY(widget.reverse ? pi : 0), @@ -464,7 +491,7 @@ class _MessageWidgetState extends State DisplayWidget.show && widget.message.user != null) ...[ _buildUserAvatar(), - SizedBox(width: 4), + const SizedBox(width: 4), ], if (widget.showUserAvatar == DisplayWidget.hide) SizedBox(width: avatarWidth + 4), @@ -473,12 +500,12 @@ class _MessageWidgetState extends State portal: Container( transform: Matrix4.translationValues(-12, 0, 0), - constraints: - BoxConstraints(maxWidth: 22 * 6.0), + constraints: const BoxConstraints( + maxWidth: 22 * 6.0), child: _buildReactionIndicator(context), ), - portalAnchor: Alignment(-1, -1), - childAnchor: Alignment(1, -1), + portalAnchor: const Alignment(-1, -1), + childAnchor: const Alignment(1, -1), child: Stack( clipBehavior: Clip.none, children: [ @@ -501,15 +528,18 @@ class _MessageWidgetState extends State transform: Matrix4.rotationY( widget.reverse ? pi : 0), child: Container( + // ignore: lines_longer_than_80_chars margin: EdgeInsets.symmetric( horizontal: + // ignore: lines_longer_than_80_chars widget.showUserAvatar == - DisplayWidget - .gone + // ignore: lines_longer_than_80_chars + DisplayWidget.gone ? 0 : 4.0), child: DeletedMessage( reverse: widget.reverse, + // ignore: lines_longer_than_80_chars borderRadiusGeometry: widget .borderRadiusGeometry, borderSide: @@ -527,6 +557,7 @@ class _MessageWidgetState extends State horizontal: (isFailedState ? 15.0 : 0.0) + + // ignore: lines_longer_than_80_chars (widget.showUserAvatar == DisplayWidget .gone @@ -539,11 +570,14 @@ class _MessageWidgetState extends State .borderSide ?? BorderSide( color: widget + // ignore: lines_longer_than_80_chars .messageTheme + // ignore: lines_longer_than_80_chars .messageBorderColor ?? Colors.grey, ), borderRadius: widget + // ignore: lines_longer_than_80_chars .borderRadiusGeometry ?? BorderRadius.zero, ), @@ -648,7 +682,7 @@ class _MessageWidgetState extends State color: StreamChatTheme.of(context).colorTheme.grey, size: 16, ), - SizedBox(width: 8), + const SizedBox(width: 8), Text( 'Only visible to you', style: StreamChatTheme.of(context) @@ -672,6 +706,7 @@ class _MessageWidgetState extends State msg = '$replyCount Thread Replies'; } + // ignore: prefer_function_declarations_over_variables final onThreadTap = () async { try { var message = widget.message; @@ -683,6 +718,7 @@ class _MessageWidgetState extends State } catch (e, stk) { print(e); print(stk); + // ignore: avoid_returning_null_for_void return null; } }; @@ -730,7 +766,7 @@ class _MessageWidgetState extends State ((widget.messageTheme.replies?.fontSize ?? 1) / 2), ), child: CustomPaint( - size: Size(16, 32) * context.textScaleFactor, + size: const Size(16, 32) * context.textScaleFactor, painter: _ThreadReplyPainter( context: context, color: widget.messageTheme.messageBorderColor, @@ -742,7 +778,7 @@ class _MessageWidgetState extends State Widget mappedChild = Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, - child: Container( + child: SizedBox( height: context.textScaleFactor * 14, child: child, ), @@ -792,7 +828,7 @@ class _MessageWidgetState extends State padding: const EdgeInsets.all(1), child: UserAvatar( user: user, - constraints: BoxConstraints.loose(Size.fromRadius(7)), + constraints: BoxConstraints.loose(const Size.fromRadius(7)), showOnlineStatus: false, ), ), @@ -816,7 +852,7 @@ class _MessageWidgetState extends State ..sort((a, b) => a.user!.id == ownId ? 1 : -1); return AnimatedSwitcher( - duration: Duration(milliseconds: 300), + duration: const Duration(milliseconds: 300), child: (widget.showReactions && (widget.message.reactionCounts?.isNotEmpty == true) && !widget.message.isDeleted) @@ -835,7 +871,7 @@ class _MessageWidgetState extends State reactions: reactionsList, ), ) - : SizedBox(), + : const SizedBox(), ); } @@ -845,78 +881,75 @@ class _MessageWidgetState extends State showDialog( context: context, barrierColor: StreamChatTheme.of(context).colorTheme.overlay, - builder: (context) { - return StreamChannel( - channel: channel, - child: MessageActionsModal( - onCopyTap: (message) => - Clipboard.setData(ClipboardData(text: message.text)), - attachmentBorderRadiusGeometry: - widget.attachmentBorderRadiusGeometry as BorderRadius?, - showUserAvatar: - widget.message.user!.id == channel.client.state.user!.id - ? DisplayWidget.gone - : DisplayWidget.show, - messageTheme: widget.messageTheme, - messageShape: widget.shape ?? _getDefaultShape(context), - attachmentShape: - widget.attachmentShape ?? _getDefaultAttachmentShape(context), - 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, - customActions: widget.customActions, - ), - ); - }); + builder: (context) => StreamChannel( + channel: channel, + child: MessageActionsModal( + onCopyTap: (message) => + Clipboard.setData(ClipboardData(text: message.text)), + attachmentBorderRadiusGeometry: + widget.attachmentBorderRadiusGeometry as BorderRadius?, + showUserAvatar: + widget.message.user!.id == channel.client.state.user!.id + ? DisplayWidget.gone + : DisplayWidget.show, + messageTheme: widget.messageTheme, + messageShape: widget.shape ?? _getDefaultShape(context), + attachmentShape: widget.attachmentShape ?? + _getDefaultAttachmentShape(context), + 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, + customActions: widget.customActions, + ), + )); } void _showMessageReactionsModalBottomSheet(BuildContext context) { final channel = StreamChannel.of(context).channel; showDialog( - context: context, - barrierColor: StreamChatTheme.of(context).colorTheme.overlay, - builder: (context) { - return StreamChannel( - channel: channel, - child: MessageReactionsModal( - attachmentBorderRadiusGeometry: - widget.attachmentBorderRadiusGeometry as BorderRadius?, - showUserAvatar: - widget.message.user!.id == channel.client.state.user!.id - ? DisplayWidget.gone - : DisplayWidget.show, - onUserAvatarTap: widget.onUserAvatarTap, - messageTheme: widget.messageTheme, - messageShape: widget.shape ?? _getDefaultShape(context), - attachmentShape: - widget.attachmentShape ?? _getDefaultAttachmentShape(context), - reverse: widget.reverse, - message: widget.message, - showReactions: widget.showReactions, - ), - ); - }); + context: context, + barrierColor: StreamChatTheme.of(context).colorTheme.overlay, + builder: (context) => StreamChannel( + channel: channel, + child: MessageReactionsModal( + attachmentBorderRadiusGeometry: + widget.attachmentBorderRadiusGeometry as BorderRadius?, + showUserAvatar: + widget.message.user!.id == channel.client.state.user!.id + ? DisplayWidget.gone + : DisplayWidget.show, + onUserAvatarTap: widget.onUserAvatarTap, + messageTheme: widget.messageTheme, + messageShape: widget.shape ?? _getDefaultShape(context), + attachmentShape: + widget.attachmentShape ?? _getDefaultAttachmentShape(context), + reverse: widget.reverse, + message: widget.message, + showReactions: widget.showReactions, + ), + ), + ); } ShapeBorder _getDefaultAttachmentShape(BuildContext context) { @@ -933,15 +966,13 @@ class _MessageWidgetState extends State ); } - ShapeBorder _getDefaultShape(BuildContext context) { - return RoundedRectangleBorder( - side: widget.borderSide ?? - BorderSide( - color: StreamChatTheme.of(context).colorTheme.greyWhisper, - ), - borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero, - ); - } + ShapeBorder _getDefaultShape(BuildContext context) => RoundedRectangleBorder( + side: widget.borderSide ?? + BorderSide( + color: StreamChatTheme.of(context).colorTheme.greyWhisper, + ), + borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero, + ); Widget _parseAttachments() { final attachmentGroups = >{}; @@ -1003,9 +1034,8 @@ class _MessageWidgetState extends State (message.status == MessageSendingStatus.sending || message.status == MessageSendingStatus.updating)) { final totalAttachments = message.attachments.length; - final uploadRemaining = message.attachments.where((it) { - return !it.uploadState.isSuccess; - }).length; + final uploadRemaining = + message.attachments.where((it) => !it.uploadState.isSuccess).length; if (uploadRemaining == 0) { return StreamSvgIcon.check( size: style!.fontSize, @@ -1033,7 +1063,7 @@ class _MessageWidgetState extends State color: StreamChatTheme.of(context).colorTheme.accentBlue, ), ), - SizedBox(width: 2), + const SizedBox(width: 2), child, ], ); @@ -1064,7 +1094,7 @@ class _MessageWidgetState extends State ); Widget _buildTextBubble() { - if (widget.message.text!.trim().isEmpty) return Offstage(); + if (widget.message.text!.trim().isEmpty) return const Offstage(); return Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, @@ -1135,11 +1165,11 @@ class _MessageWidgetState extends State } class _ThreadReplyPainter extends CustomPainter { + const _ThreadReplyPainter({this.context, required this.color}); + final Color? color; final BuildContext? context; - const _ThreadReplyPainter({this.context, required this.color}); - @override void paint(Canvas canvas, Size size) { final paint = Paint() From ef837a4d7d0eb056197c27ed1de67b2ba1714407 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 16:19:54 +0530 Subject: [PATCH 199/599] lint changes --- .../lib/src/option_list_tile.dart | 134 ++++++----- .../lib/src/quoted_message_widget.dart | 209 ++++++++---------- 2 files changed, 170 insertions(+), 173 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/option_list_tile.dart b/packages/stream_chat_flutter/lib/src/option_list_tile.dart index a4463389..9a11fd7a 100644 --- a/packages/stream_chat_flutter/lib/src/option_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/option_list_tile.dart @@ -1,17 +1,11 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +/// List tile for [ChannelBottomSheet] class OptionListTile extends StatelessWidget { - final String? title; - final Widget? leading; - final Widget? trailing; - final VoidCallback? onTap; - final Color? titleColor; - final Color? tileColor; - final Color? separatorColor; - final TextStyle? titleTextStyle; - + /// Constructor for creating [OptionListTile] const OptionListTile({ + Key? key, this.title, this.leading, this.trailing, @@ -20,61 +14,83 @@ class OptionListTile extends StatelessWidget { this.tileColor, this.separatorColor, this.titleTextStyle, - }); + }) : super(key: key); + + /// Title for tile + final String? title; + + /// Leading widget (start) + final Widget? leading; + + /// Trailing widget (end) + final Widget? trailing; + + /// Callback when tile is tapped + final VoidCallback? onTap; + + /// Title color + final Color? titleColor; + + /// Background tile color + final Color? tileColor; + + /// Separator color + final Color? separatorColor; + + /// [TextStyle] to apply to [title] + final TextStyle? titleTextStyle; @override - Widget build(BuildContext context) { - return Column( - children: [ - Container( - color: separatorColor ?? - StreamChatTheme.of(context).colorTheme.greyGainsboro, - height: 1, - ), - Material( - color: tileColor ?? StreamChatTheme.of(context).colorTheme.white, - child: Container( - height: 63, - child: InkWell( - onTap: onTap, - child: Row( - children: [ - if (leading != null) Center(child: leading), - if (leading == null) - SizedBox( - width: 16, - ), - Expanded( - flex: 4, - child: Text( - title!, - style: titleTextStyle ?? - (titleColor == null - ? StreamChatTheme.of(context).textTheme.bodyBold - : StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: titleColor, - )), - ), - ), - Expanded( - flex: 2, - child: Padding( - padding: const EdgeInsets.only(right: 16), - child: Align( - alignment: Alignment.centerRight, - child: trailing ?? Container(), + Widget build(BuildContext context) => Column( + children: [ + Container( + color: separatorColor ?? + StreamChatTheme.of(context).colorTheme.greyGainsboro, + height: 1, + ), + Material( + color: tileColor ?? StreamChatTheme.of(context).colorTheme.white, + child: SizedBox( + height: 63, + child: InkWell( + onTap: onTap, + child: Row( + children: [ + if (leading != null) Center(child: leading), + if (leading == null) + const SizedBox( + width: 16, + ), + Expanded( + flex: 4, + child: Text( + title!, + style: titleTextStyle ?? + (titleColor == null + ? StreamChatTheme.of(context).textTheme.bodyBold + : StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: titleColor, + )), ), ), - ), - ], + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.only(right: 16), + child: Align( + alignment: Alignment.centerRight, + child: trailing ?? Container(), + ), + ), + ), + ], + ), ), ), ), - ), - ], - ); - } + ], + ); } 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 9d489dab..6be7ded7 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -4,29 +4,25 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:video_player/video_player.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; -import 'attachment/attachment.dart'; -import 'extension.dart'; -import 'message_text.dart'; -import 'stream_chat_theme.dart'; -import 'user_avatar.dart'; -import 'utils.dart'; - +/// Widget builder for quoted message attachment thumnail typedef QuotedMessageAttachmentThumbnailBuilder = Widget Function( BuildContext, Attachment, ); class _VideoAttachmentThumbnail extends StatefulWidget { - final Size size; - final Attachment attachment; - const _VideoAttachmentThumbnail({ Key? key, required this.attachment, this.size = const Size(32, 32), }) : super(key: key); + final Size size; + final Attachment attachment; + @override _VideoAttachmentThumbnailState createState() => _VideoAttachmentThumbnailState(); @@ -51,18 +47,30 @@ class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> { } @override - Widget build(BuildContext context) { - return Container( + Widget build(BuildContext context) => SizedBox( height: widget.size.height, width: widget.size.width, child: _controller.value.isInitialized ? VideoPlayer(_controller) - : CircularProgressIndicator()); - } + : const CircularProgressIndicator(), + ); } /// class QuotedMessageWidget extends StatelessWidget { + /// + const QuotedMessageWidget({ + Key? key, + required this.message, + required this.messageTheme, + this.reverse = false, + this.showBorder = false, + this.textLimit = 170, + this.attachmentThumbnailBuilders, + this.padding = const EdgeInsets.all(8), + this.onTap, + }) : super(key: key); + /// The message final Message message; @@ -82,23 +90,12 @@ class QuotedMessageWidget extends StatelessWidget { final Map? attachmentThumbnailBuilders; + /// Padding around the widget final EdgeInsetsGeometry padding; + /// Callback for tap on widget final GestureTapCallback? onTap; - /// - const QuotedMessageWidget({ - Key? key, - required this.message, - required this.messageTheme, - this.reverse = false, - this.showBorder = false, - this.textLimit = 170, - this.attachmentThumbnailBuilders, - this.padding = const EdgeInsets.all(8), - this.onTap, - }) : super(key: key); - bool get _hasAttachments => message.attachments.isNotEmpty == true; bool get _containsScrapeUrl => @@ -107,23 +104,21 @@ class QuotedMessageWidget extends StatelessWidget { bool get _containsText => message.text?.isNotEmpty == true; @override - Widget build(BuildContext context) { - return Padding( - padding: padding, - child: InkWell( - onTap: onTap, - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Flexible(child: _buildMessage(context)), - SizedBox(width: 8), - if (message.user != null) _buildUserAvatar(), - ], + Widget build(BuildContext context) => Padding( + padding: padding, + child: InkWell( + onTap: onTap, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible(child: _buildMessage(context)), + const SizedBox(width: 8), + if (message.user != null) _buildUserAvatar(), + ], + ), ), - ), - ); - } + ); Widget _buildMessage(BuildContext context) { final isOnlyEmoji = message.text!.isOnlyEmoji; @@ -165,7 +160,7 @@ class QuotedMessageWidget extends StatelessWidget { color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ) : null, - borderRadius: BorderRadius.only( + borderRadius: const BorderRadius.only( topRight: Radius.circular(12), topLeft: Radius.circular(12), bottomLeft: Radius.circular(12), @@ -183,7 +178,7 @@ class QuotedMessageWidget extends StatelessWidget { } Widget _buildUrlAttachment(Attachment attachment) { - final size = Size(32, 32); + const size = Size(32, 32); if (attachment.thumbUrl != null) { return Container( height: size.height, @@ -198,7 +193,7 @@ class QuotedMessageWidget extends StatelessWidget { ), ); } - return AttachmentError(size: size); + return const AttachmentError(size: size); } Widget _parseAttachments(BuildContext context) { @@ -217,7 +212,7 @@ class QuotedMessageWidget extends StatelessWidget { } attachmentBuilder = _defaultAttachmentBuilder[attachment.type]; if (attachmentBuilder == null) { - child = Offstage(); + child = const Offstage(); } else { child = attachmentBuilder(context, attachment); } @@ -235,77 +230,63 @@ class QuotedMessageWidget extends StatelessWidget { ); } - ShapeBorder _getDefaultShape(BuildContext context) { - return RoundedRectangleBorder( - side: BorderSide(width: 0, color: Colors.transparent), - borderRadius: BorderRadius.circular(8), - ); - } + ShapeBorder _getDefaultShape(BuildContext context) => RoundedRectangleBorder( + side: const BorderSide(width: 0, color: Colors.transparent), + borderRadius: BorderRadius.circular(8), + ); - Widget _buildUserAvatar() { - return Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: UserAvatar( - user: message.user!, - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, + Widget _buildUserAvatar() => Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: UserAvatar( + user: message.user!, + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + showOnlineStatus: false, ), - showOnlineStatus: false, - ), - ); - } + ); Map - get _defaultAttachmentBuilder { - return { - 'image': (_, attachment) { - return ImageAttachment( - attachment: attachment, - message: message, - messageTheme: messageTheme, - size: Size(32, 32), - ); - }, - 'video': (_, attachment) { - return _VideoAttachmentThumbnail( - key: ValueKey(attachment.assetUrl), - attachment: attachment, - ); - }, - 'giphy': (_, attachment) { - final size = Size(32, 32); - return CachedNetworkImage( - height: size.height, - width: size.width, - placeholder: (_, __) { - return Container( - width: size.width, - height: size.height, - child: Center( - child: CircularProgressIndicator(), - ), - ); - }, - imageUrl: attachment.thumbUrl ?? - attachment.imageUrl ?? - attachment.assetUrl!, - errorWidget: (context, url, error) { - return AttachmentError(size: size); - }, - fit: BoxFit.cover, - ); - }, - 'file': (_, attachment) { - return Container( - height: 32, - width: 32, - child: getFileTypeImage(attachment.extraData['mime_type'] as String?), - ); - }, - }; - } + get _defaultAttachmentBuilder => { + 'image': (_, attachment) => ImageAttachment( + attachment: attachment, + message: message, + messageTheme: messageTheme, + size: const Size(32, 32), + ), + 'video': (_, attachment) => _VideoAttachmentThumbnail( + key: ValueKey(attachment.assetUrl), + attachment: attachment, + ), + 'giphy': (_, attachment) { + const size = Size(32, 32); + return CachedNetworkImage( + height: size.height, + width: size.width, + placeholder: (_, __) => SizedBox( + width: size.width, + height: size.height, + child: const Center( + child: CircularProgressIndicator(), + ), + ), + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl!, + errorWidget: (context, url, error) => + const AttachmentError(size: size), + fit: BoxFit.cover, + ); + }, + 'file': (_, attachment) => SizedBox( + height: 32, + width: 32, + child: getFileTypeImage( + attachment.extraData['mime_type'] as String?), + ), + }; Color? _getBackgroundColor(BuildContext context) { if (_containsScrapeUrl) { From d1705c26a329c093b1b7dc8aa60637cd285daa5b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 16:29:16 +0530 Subject: [PATCH 200/599] lint changes rb --- .../lib/src/reaction_bubble.dart | 187 ++++++++++-------- 1 file changed, 105 insertions(+), 82 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index d71c395a..9b45c248 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -8,7 +8,9 @@ import 'package:stream_chat_flutter/src/reaction_icon.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +/// Creates reaction bubble widget for displaying over messages class ReactionBubble extends StatelessWidget { + /// Constructor for creating a [ReactionBubble] const ReactionBubble({ Key? key, required this.reactions, @@ -21,13 +23,28 @@ class ReactionBubble extends StatelessWidget { this.tailCirclesSpacing = 0, }) : super(key: key); + /// Reactions to show final List reactions; + + /// Border color of bubble final Color borderColor; + + /// Background color of bubble final Color backgroundColor; + + /// Mask color final Color maskColor; + + /// Reverse for other side final bool reverse; + + /// Reverse tail for other side final bool flipTail; + + /// Flag for highlighting own reactions final bool highlightOwnReactions; + + /// Spacing for tail circles final double tailCirclesSpacing; @override @@ -47,7 +64,7 @@ class ReactionBubble extends StatelessWidget { padding: const EdgeInsets.all(2), decoration: BoxDecoration( color: maskColor, - borderRadius: BorderRadius.all(Radius.circular(16)), + borderRadius: const BorderRadius.all(Radius.circular(16)), ), child: Container( padding: EdgeInsets.symmetric( @@ -59,35 +76,32 @@ class ReactionBubble extends StatelessWidget { color: borderColor, ), color: backgroundColor, - borderRadius: BorderRadius.all(Radius.circular(14)), + borderRadius: const BorderRadius.all(Radius.circular(14)), ), child: LayoutBuilder( - builder: (context, constraints) { - return Flex( - direction: Axis.horizontal, - mainAxisSize: MainAxisSize.min, - children: [ - if (constraints.maxWidth < double.infinity) - ...reactions - .take((constraints.maxWidth) ~/ 24) - .map((reaction) { - return _buildReaction( - reactionIcons, - reaction, - context, - ); - }).toList(), - if (constraints.maxWidth == double.infinity) - ...reactions.map((reaction) { - return _buildReaction( - reactionIcons, - reaction, - context, - ); - }).toList(), - ], - ); - }, + builder: (context, constraints) => Flex( + direction: Axis.horizontal, + mainAxisSize: MainAxisSize.min, + children: [ + if (constraints.maxWidth < double.infinity) + ...reactions + .take((constraints.maxWidth) ~/ 24) + .map((reaction) => _buildReaction( + reactionIcons, + reaction, + context, + )) + .toList(), + if (constraints.maxWidth == double.infinity) + ...reactions + .map((reaction) => _buildReaction( + reactionIcons, + reaction, + context, + )) + .toList(), + ], + ), ), ), ), @@ -160,12 +174,9 @@ class ReactionBubble extends StatelessWidget { } } +/// Painter widget for a reaction bubble class ReactionBubblePainter extends CustomPainter { - final Color color; - final Color borderColor; - final Color maskColor; - final double tailCirclesSpace; - + /// Constructor for creating a [ReactionBubblePainter] ReactionBubblePainter( this.color, this.borderColor, @@ -173,6 +184,18 @@ class ReactionBubblePainter extends CustomPainter { this.tailCirclesSpace = 0, }); + /// Color of bubble + final Color color; + + /// Border color of bubble + final Color borderColor; + + /// Mask color + final Color maskColor; + + /// Tail circle space + final double tailCirclesSpace; + @override void paint(Canvas canvas, Size size) { _drawOvalMask(size, canvas); @@ -193,13 +216,14 @@ class ReactionBubblePainter extends CustomPainter { ..color = maskColor ..style = PaintingStyle.fill; - final path = Path(); - path.addOval( - Rect.fromCircle( - center: Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace), - radius: 4, - ), - ); + final path = Path() + ..addOval( + Rect.fromCircle( + center: + const Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace), + radius: 4, + ), + ); canvas.drawPath(path, paint); } @@ -209,13 +233,14 @@ class ReactionBubblePainter extends CustomPainter { ..strokeWidth = 1 ..style = PaintingStyle.stroke; - final path = Path(); - path.addOval( - Rect.fromCircle( - center: Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace), - radius: 2, - ), - ); + final path = Path() + ..addOval( + Rect.fromCircle( + center: + const Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace), + radius: 2, + ), + ); canvas.drawPath(path, paint); } @@ -224,11 +249,11 @@ class ReactionBubblePainter extends CustomPainter { ..color = color ..strokeWidth = 1; - final path = Path(); - path.addOval(Rect.fromCircle( - center: Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace), - radius: 2, - )); + final path = Path() + ..addOval(Rect.fromCircle( + center: const Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace), + radius: 2, + )); canvas.drawPath(path, paint); } @@ -241,15 +266,15 @@ class ReactionBubblePainter extends CustomPainter { const dy = -2.2; const startAngle = 1.1; const sweepAngle = 1.2; - final path = Path(); - path.addArc( - Rect.fromCircle( - center: Offset(1, dy), - radius: 4, - ), - -pi * startAngle, - -pi / sweepAngle, - ); + final path = Path() + ..addArc( + Rect.fromCircle( + center: const Offset(1, dy), + radius: 4, + ), + -pi * startAngle, + -pi / sweepAngle, + ); canvas.drawPath(path, paint); } @@ -261,15 +286,15 @@ class ReactionBubblePainter extends CustomPainter { const dy = -2.2; const startAngle = 1; const sweepAngle = 1.3; - final path = Path(); - path.addArc( - Rect.fromCircle( - center: Offset(1, dy), - radius: 4, - ), - -pi * startAngle, - -pi * sweepAngle, - ); + final path = Path() + ..addArc( + Rect.fromCircle( + center: const Offset(1, dy), + radius: 4, + ), + -pi * startAngle, + -pi * sweepAngle, + ); canvas.drawPath(path, paint); } @@ -282,20 +307,18 @@ class ReactionBubblePainter extends CustomPainter { const dy = -2.2; const startAngle = 1.1; const sweepAngle = 1.2; - final path = Path(); - path.addArc( - Rect.fromCircle( - center: Offset(1, dy), - radius: 6, - ), - -pi * startAngle, - -pi / sweepAngle, - ); + final path = Path() + ..addArc( + Rect.fromCircle( + center: const Offset(1, dy), + radius: 6, + ), + -pi * startAngle, + -pi / sweepAngle, + ); canvas.drawPath(path, paint); } @override - bool shouldRepaint(CustomPainter oldDelegate) { - return true; - } + bool shouldRepaint(CustomPainter oldDelegate) => true; } From 645d21503a45b1ad56c1f127cd29402d626a476d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 16:32:32 +0530 Subject: [PATCH 201/599] lint changes reactions --- .../lib/src/reaction_icon.dart | 11 +- .../lib/src/reaction_picker.dart | 185 +++++++++--------- 2 files changed, 99 insertions(+), 97 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/reaction_icon.dart b/packages/stream_chat_flutter/lib/src/reaction_icon.dart index 66e19fe7..4466e154 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_icon.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_icon.dart @@ -1,9 +1,14 @@ +/// Reaction icon data class ReactionIcon { - final String type; - final String assetName; - + /// Constructor for creating [ReactionIcon] ReactionIcon({ required this.type, required this.assetName, }); + + /// Type of reaction + final String type; + + /// Asset to display for reaction + final String assetName; } diff --git a/packages/stream_chat_flutter/lib/src/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reaction_picker.dart index aaa2c040..1308876d 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_picker.dart @@ -3,23 +3,24 @@ import 'dart:math'; import 'package:ezanimation/ezanimation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; - -import '../stream_chat_flutter.dart'; -import 'extension.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker_paint.png) /// /// It shows a reaction picker /// -/// Usually you don't use this widget as it's one of the default widgets used by [MessageWidget.onMessageActions]. - +/// Usually you don't use this widget as it's one of the default widgets used +/// by [MessageWidget.onMessageActions]. class ReactionPicker extends StatefulWidget { + /// Constructor for creating a [ReactionPicker] widget const ReactionPicker({ Key? key, required this.message, }) : super(key: key); + /// Message to attach the reaction to final Message message; @override @@ -39,7 +40,7 @@ class _ReactionPickerState extends State animations.add( EzAnimation.tween( Tween(begin: 0.0, end: 1.0), - Duration(milliseconds: 500), + const Duration(milliseconds: 500), curve: Curves.easeInOutBack, ), ); @@ -51,108 +52,104 @@ class _ReactionPickerState extends State return TweenAnimationBuilder( tween: Tween(begin: 0, end: 1), curve: Curves.easeInOutBack, - duration: Duration(milliseconds: 500), - builder: (context, val, wid) { - return Transform.scale( - scale: val, - child: Material( - borderRadius: BorderRadius.circular(24), - color: StreamChatTheme.of(context).colorTheme.white, - clipBehavior: Clip.hardEdge, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: reactionIcons - .map((reactionIcon) { - final ownReactionIndex = widget.message.ownReactions - ?.indexWhere((reaction) => - reaction.type == reactionIcon.type) ?? - -1; - final index = reactionIcons.indexOf(reactionIcon); + duration: const Duration(milliseconds: 500), + builder: (context, val, wid) => Transform.scale( + scale: val, + child: Material( + borderRadius: BorderRadius.circular(24), + color: StreamChatTheme.of(context).colorTheme.white, + clipBehavior: Clip.hardEdge, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: reactionIcons + .map((reactionIcon) { + final ownReactionIndex = widget.message.ownReactions + ?.indexWhere((reaction) => + reaction.type == reactionIcon.type) ?? + -1; + final index = reactionIcons.indexOf(reactionIcon); - return ConstrainedBox( - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, - ), - child: RawMaterialButton( - elevation: 0, - shape: ContinuousRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - constraints: BoxConstraints.tightFor( + return ConstrainedBox( + constraints: const BoxConstraints.tightFor( height: 24, width: 24, ), - onPressed: () { - if (ownReactionIndex != -1) { - removeReaction( - context, - widget - .message.ownReactions![ownReactionIndex], - ); - } else { - sendReaction( - context, - reactionIcon.type, - ); - } - }, - child: AnimatedBuilder( - animation: animations[index], - builder: (context, val) { - return Transform.scale( - scale: animations[index].value, - child: StreamSvgIcon( - assetName: reactionIcon.assetName, - height: max( - 0, - animations[index].value * 24.0, - ), - width: max( - 0, - animations[index].value * 24.0, - ), - color: ownReactionIndex != -1 - ? StreamChatTheme.of(context) - .colorTheme - .accentBlue - : Theme.of(context) - .iconTheme - .color! - .withOpacity(.5), - ), + child: RawMaterialButton( + elevation: 0, + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + onPressed: () { + if (ownReactionIndex != -1) { + removeReaction( + context, + widget.message + .ownReactions![ownReactionIndex], ); - }), - ), - ); - }) - .insertBetween(SizedBox( - width: 16, - )) - .toList(), + } else { + sendReaction( + context, + reactionIcon.type, + ); + } + }, + child: AnimatedBuilder( + animation: animations[index], + builder: (context, val) => Transform.scale( + scale: animations[index].value, + child: StreamSvgIcon( + assetName: reactionIcon.assetName, + height: max( + 0, + animations[index].value * 24.0, + ), + width: max( + 0, + animations[index].value * 24.0, + ), + color: ownReactionIndex != -1 + ? StreamChatTheme.of(context) + .colorTheme + .accentBlue + : Theme.of(context) + .iconTheme + .color! + .withOpacity(.5), + ), + )), + ), + ); + }) + .insertBetween(const SizedBox( + width: 16, + )) + .toList(), + ), ), ), - ), - ); - }); + )); } void triggerAnimations() async { - for (var a in animations) { + for (final a in animations) { a.start(); - await Future.delayed(Duration(milliseconds: 100)); + await Future.delayed(const Duration(milliseconds: 100)); } } void pop() async { - for (var a in animations) { + for (final a in animations) { a.stop(); } Navigator.of(context).pop(); @@ -176,7 +173,7 @@ class _ReactionPickerState extends State @override void dispose() { - for (var a in animations) { + for (final a in animations) { a.dispose(); } super.dispose(); From d55b937364b43f64c5868dfc5d50269406f269b0 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 16:38:05 +0530 Subject: [PATCH 202/599] lint --- .../lib/src/sending_indicator.dart | 16 +++++--- .../lib/src/stream_chat.dart | 38 +++++++++++-------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/sending_indicator.dart b/packages/stream_chat_flutter/lib/src/sending_indicator.dart index 68fc5a0d..88560432 100644 --- a/packages/stream_chat_flutter/lib/src/sending_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/sending_indicator.dart @@ -3,10 +3,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Used to show the sending status of the message class SendingIndicator extends StatelessWidget { - final Message message; - final bool isMessageRead; - final double? size; - + /// Constructor for creating a [SendingIndicator] widget const SendingIndicator({ Key? key, required this.message, @@ -14,6 +11,15 @@ class SendingIndicator extends StatelessWidget { this.size = 12, }) : super(key: key); + /// Message for sending indicator + final Message message; + + /// Flag if message is read + final bool isMessageRead; + + /// Size for message + final double? size; + @override Widget build(BuildContext context) { if (isMessageRead) { @@ -35,6 +41,6 @@ class SendingIndicator extends StatelessWidget { size: size, ); } - return SizedBox(); + return const SizedBox(); } } diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 2c22dde3..424d4bcf 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -31,18 +31,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// /// Use [StreamChat.of] to get the current [StreamChatState] instance. class StreamChat extends StatefulWidget { - final StreamChatClient client; - final Widget? child; - final StreamChatThemeData? streamChatThemeData; - - /// The amount of time that will pass before disconnecting the client in the background - final Duration backgroundKeepAlive; - - /// Handler called whenever the [client] receives a new [Event] while the app - /// is in background. Can be used to display various notifications depending - /// upon the [Event.type] - final EventHandler? onBackgroundEventReceived; - + /// Constructor for creating a [StreamChat] widget const StreamChat({ Key? key, required this.client, @@ -50,9 +39,25 @@ class StreamChat extends StatefulWidget { this.streamChatThemeData, this.onBackgroundEventReceived, this.backgroundKeepAlive = const Duration(minutes: 1), - }) : super( - key: key, - ); + }) : super(key: key); + + /// Client to do chat ops with + final StreamChatClient client; + + /// Child which inherits details + final Widget? child; + + /// Theme to pass on + final StreamChatThemeData? streamChatThemeData; + + /// The amount of time that will pass before disconnecting the client + /// in the background + final Duration backgroundKeepAlive; + + /// Handler called whenever the [client] receives a new [Event] while the app + /// is in background. Can be used to display various notifications depending + /// upon the [Event.type] + final EventHandler? onBackgroundEventReceived; @override StreamChatState createState() => StreamChatState(); @@ -74,6 +79,7 @@ class StreamChat extends StatefulWidget { /// The current state of the StreamChat widget class StreamChatState extends State { + /// Gets client from widget StreamChatClient get client => widget.client; @override @@ -96,7 +102,7 @@ class StreamChatState extends State { client: client, onBackgroundEventReceived: widget.onBackgroundEventReceived, backgroundKeepAlive: widget.backgroundKeepAlive, - child: widget.child ?? Offstage(), + child: widget.child ?? const Offstage(), ), ); }, From dad7bb3f8cd723d570270152a95d5195c9771ca4 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 17:08:49 +0530 Subject: [PATCH 203/599] lint theme --- .../lib/src/stream_chat_theme.dart | 577 +++++++++++------- 1 file changed, 357 insertions(+), 220 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index 1e1eb089..c5646753 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -11,8 +11,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Inherited widget providing the [StreamChatThemeData] to the widget tree class StreamChatTheme extends InheritedWidget { - final StreamChatThemeData data; - + /// Constructor for creating a [StreamChatTheme] const StreamChatTheme({ Key? key, required this.data, @@ -22,10 +21,11 @@ class StreamChatTheme extends InheritedWidget { child: child, ); + /// Theme data + final StreamChatThemeData data; + @override - bool updateShouldNotify(StreamChatTheme old) { - return data != old.data; - } + bool updateShouldNotify(StreamChatTheme old) => data != old.data; /// Use this method to get the current [StreamChatThemeData] instance static StreamChatThemeData of(BuildContext context) { @@ -43,42 +43,6 @@ class StreamChatTheme extends InheritedWidget { /// Theme data class StreamChatThemeData { - /// The text themes used in the widgets - final TextTheme textTheme; - - /// The color themes used in the widgets - final ColorTheme colorTheme; - - /// Theme of the [ChannelPreview] - final ChannelPreviewTheme channelPreviewTheme; - - /// Theme of the [ChannelListHeader] - final ChannelListHeaderTheme channelListHeaderTheme; - - /// Theme of the chat widgets dedicated to a channel - final ChannelTheme channelTheme; - - /// Theme of the current user messages - final MessageTheme ownMessageTheme; - - /// Theme of other users messages - final MessageTheme otherMessageTheme; - - /// Theme dedicated to the [MessageInput] widget - final MessageInputTheme messageInputTheme; - - /// The widget that will be built when the channel image is unavailable - final Widget Function(BuildContext, Channel) defaultChannelImage; - - /// The widget that will be built when the user image is unavailable - final Widget Function(BuildContext, User) defaultUserImage; - - /// Primary icon theme - final IconThemeData primaryIconTheme; - - /// Assets used for rendering reactions - final List reactionIcons; - /// Create a theme from scratch factory StreamChatThemeData({ Brightness? brightness, @@ -121,12 +85,15 @@ class StreamChatThemeData { return defaultData.merge(customizedData); } + /// Theme initialised with light factory StreamChatThemeData.light() => StreamChatThemeData(brightness: Brightness.light); + /// Theme initialised with dark factory StreamChatThemeData.dark() => StreamChatThemeData(brightness: Brightness.dark); + /// Raw theme init const StreamChatThemeData.raw({ required this.textTheme, required this.colorTheme, @@ -154,7 +121,44 @@ class StreamChatThemeData { return defaultTheme.merge(customizedTheme); } - /// Creates a copy of [StreamChatThemeData] with specified attributes overridden. + /// The text themes used in the widgets + final TextTheme textTheme; + + /// The color themes used in the widgets + final ColorTheme colorTheme; + + /// Theme of the [ChannelPreview] + final ChannelPreviewTheme channelPreviewTheme; + + /// Theme of the [ChannelListHeader] + final ChannelListHeaderTheme channelListHeaderTheme; + + /// Theme of the chat widgets dedicated to a channel + final ChannelTheme channelTheme; + + /// Theme of the current user messages + final MessageTheme ownMessageTheme; + + /// Theme of other users messages + final MessageTheme otherMessageTheme; + + /// Theme dedicated to the [MessageInput] widget + final MessageInputTheme messageInputTheme; + + /// The widget that will be built when the channel image is unavailable + final Widget Function(BuildContext, Channel) defaultChannelImage; + + /// The widget that will be built when the user image is unavailable + final Widget Function(BuildContext, User) defaultUserImage; + + /// Primary icon theme + final IconThemeData primaryIconTheme; + + /// Assets used for rendering reactions + final List reactionIcons; + + /// Creates a copy of [StreamChatThemeData] with specified attributes + /// overridden. StreamChatThemeData copyWith({ TextTheme? textTheme, ColorTheme? colorTheme, @@ -185,6 +189,7 @@ class StreamChatThemeData { reactionIcons: reactionIcons ?? this.reactionIcons, ); + /// Merge themes StreamChatThemeData merge(StreamChatThemeData? other) { if (other == null) return this; return copyWith( @@ -204,6 +209,8 @@ class StreamChatThemeData { ); } + /// Create theme from color and text theme + // ignore: prefer_constructors_over_static_methods static StreamChatThemeData fromColorAndTextTheme( ColorTheme colorTheme, TextTheme textTheme, @@ -213,7 +220,7 @@ class StreamChatThemeData { textTheme: textTheme, colorTheme: colorTheme, primaryIconTheme: IconThemeData(color: colorTheme.black.withOpacity(.5)), - defaultChannelImage: (context, channel) => SizedBox(), + defaultChannelImage: (context, channel) => const SizedBox(), defaultUserImage: (context, user) => Center( child: CachedNetworkImage( filterQuality: FilterQuality.high, @@ -225,14 +232,14 @@ class StreamChatThemeData { unreadCounterColor: colorTheme.accentRed, avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 40, width: 40, ), ), title: textTheme.bodyBold, subtitle: textTheme.footnote.copyWith( - color: Color(0xff7A7A7A), + color: const Color(0xff7A7A7A), ), lastMessageAt: textTheme.footnote.copyWith( color: colorTheme.black.withOpacity(.5), @@ -241,7 +248,7 @@ class StreamChatThemeData { channelListHeaderTheme: ChannelListHeaderTheme( avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 40, width: 40, ), @@ -253,7 +260,7 @@ class StreamChatThemeData { channelHeaderTheme: ChannelHeaderTheme( avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 40, width: 40, ), @@ -261,7 +268,7 @@ class StreamChatThemeData { color: colorTheme.white, title: textTheme.headlineBold, subtitle: textTheme.footnote.copyWith( - color: Color(0xff7A7A7A), + color: const Color(0xff7A7A7A), ), ), ), @@ -277,7 +284,7 @@ class StreamChatThemeData { messageBorderColor: colorTheme.greyGainsboro, avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 32, width: 32, ), @@ -301,7 +308,7 @@ class StreamChatThemeData { messageBorderColor: colorTheme.greyWhisper, avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 32, width: 32, ), @@ -309,7 +316,7 @@ class StreamChatThemeData { ), messageInputTheme: MessageInputTheme( borderRadius: BorderRadius.circular(20), - sendAnimationDuration: Duration(milliseconds: 300), + sendAnimationDuration: const Duration(milliseconds: 300), actionButtonColor: colorTheme.accentBlue, actionButtonIdleColor: colorTheme.grey, expandButtonColor: colorTheme.accentBlue, @@ -356,16 +363,9 @@ class StreamChatThemeData { } } +/// Class for holding text theme class TextTheme { - final TextStyle title; - final TextStyle headlineBold; - final TextStyle headline; - final TextStyle bodyBold; - final TextStyle body; - final TextStyle footnoteBold; - final TextStyle footnote; - final TextStyle captionBold; - + /// Initialise light text theme TextTheme.light({ this.title = const TextStyle( fontSize: 22, @@ -408,6 +408,7 @@ class TextTheme { ), }); + /// Initialise with dark theme TextTheme.dark({ this.title = const TextStyle( fontSize: 22, @@ -450,6 +451,31 @@ class TextTheme { ), }); + /// Text theme for title + final TextStyle title; + + /// Body Text theme for headline + final TextStyle headlineBold; + + /// Text theme for headline + final TextStyle headline; + + /// Bold Text theme for body + final TextStyle bodyBold; + + /// Text theme body + final TextStyle body; + + /// Bold Text theme for footnote + final TextStyle footnoteBold; + + /// Text theme for footnote + final TextStyle footnote; + + /// Bold Text theme for caption + final TextStyle captionBold; + + /// Copy with theme TextTheme copyWith({ Brightness brightness = Brightness.light, TextStyle? body, @@ -460,30 +486,30 @@ class TextTheme { TextStyle? footnoteBold, TextStyle? footnote, TextStyle? captionBold, - }) { - return brightness == Brightness.light - ? TextTheme.light( - body: body ?? this.body, - title: title ?? this.title, - headlineBold: headlineBold ?? this.headlineBold, - headline: headline ?? this.headline, - bodyBold: bodyBold ?? this.bodyBold, - footnoteBold: footnoteBold ?? this.footnoteBold, - footnote: footnote ?? this.footnote, - captionBold: captionBold ?? this.captionBold, - ) - : TextTheme.dark( - body: body ?? this.body, - title: title ?? this.title, - headlineBold: headlineBold ?? this.headlineBold, - headline: headline ?? this.headline, - bodyBold: bodyBold ?? this.bodyBold, - footnoteBold: footnoteBold ?? this.footnoteBold, - footnote: footnote ?? this.footnote, - captionBold: captionBold ?? this.captionBold, - ); - } + }) => + brightness == Brightness.light + ? TextTheme.light( + body: body ?? this.body, + title: title ?? this.title, + headlineBold: headlineBold ?? this.headlineBold, + headline: headline ?? this.headline, + bodyBold: bodyBold ?? this.bodyBold, + footnoteBold: footnoteBold ?? this.footnoteBold, + footnote: footnote ?? this.footnote, + captionBold: captionBold ?? this.captionBold, + ) + : TextTheme.dark( + body: body ?? this.body, + title: title ?? this.title, + headlineBold: headlineBold ?? this.headlineBold, + headline: headline ?? this.headline, + bodyBold: bodyBold ?? this.bodyBold, + footnoteBold: footnoteBold ?? this.footnoteBold, + footnote: footnote ?? this.footnote, + captionBold: captionBold ?? this.captionBold, + ); + /// Merge text theme TextTheme merge(TextTheme? other) { if (other == null) return this; return copyWith( @@ -499,28 +525,9 @@ class TextTheme { } } +/// Theme that holds colors class ColorTheme { - final Color black; - final Color grey; - final Color greyGainsboro; - final Color greyWhisper; - final Color whiteSmoke; - final Color whiteSnow; - final Color white; - final Color blueAlice; - final Color accentBlue; - final Color accentRed; - final Color accentGreen; - final Effect borderTop; - final Effect borderBottom; - final Effect shadowIconButton; - final Effect modalShadow; - final Color highlight; - final Color overlay; - final Color overlayDark; - final Gradient bgGradient; - final Brightness brightness; - + /// Initialise with light theme ColorTheme.light({ this.black = const Color(0xff000000), this.grey = const Color(0xff7a7a7a), @@ -552,6 +559,7 @@ class ColorTheme { sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8), }) : brightness = Brightness.light; + /// Initialise with dark theme ColorTheme.dark({ this.black = const Color(0xffffffff), this.grey = const Color(0xff7a7a7a), @@ -605,6 +613,67 @@ class ColorTheme { ), }) : brightness = Brightness.dark; + /// + final Color black; + + /// + final Color grey; + + /// + final Color greyGainsboro; + + /// + final Color greyWhisper; + + /// + final Color whiteSmoke; + + /// + final Color whiteSnow; + + /// + final Color white; + + /// + final Color blueAlice; + + /// + final Color accentBlue; + + /// + final Color accentRed; + + /// + final Color accentGreen; + + /// + final Effect borderTop; + + /// + final Effect borderBottom; + + /// + final Effect shadowIconButton; + + /// + final Effect modalShadow; + + /// + final Color highlight; + + /// + final Color overlay; + + /// + final Color overlayDark; + + /// + final Gradient bgGradient; + + /// + final Brightness brightness; + + /// Copy with theme ColorTheme copyWith({ Brightness brightness = Brightness.light, Color? black, @@ -626,52 +695,52 @@ class ColorTheme { Color? overlay, Color? overlayDark, Gradient? bgGradient, - }) { - return brightness == Brightness.light - ? ColorTheme.light( - black: black ?? this.black, - grey: grey ?? this.grey, - greyGainsboro: greyGainsboro ?? this.greyGainsboro, - greyWhisper: greyWhisper ?? this.greyWhisper, - whiteSmoke: whiteSmoke ?? this.whiteSmoke, - whiteSnow: whiteSnow ?? this.whiteSnow, - white: white ?? this.white, - blueAlice: blueAlice ?? this.blueAlice, - accentBlue: accentBlue ?? this.accentBlue, - accentRed: accentRed ?? this.accentRed, - accentGreen: accentGreen ?? this.accentGreen, - borderTop: borderTop ?? this.borderTop, - borderBottom: borderBottom ?? this.borderBottom, - shadowIconButton: shadowIconButton ?? this.shadowIconButton, - modalShadow: modalShadow ?? this.modalShadow, - highlight: highlight ?? this.highlight, - overlay: overlay ?? this.overlay, - overlayDark: overlayDark ?? this.overlayDark, - bgGradient: bgGradient ?? this.bgGradient, - ) - : ColorTheme.dark( - black: black ?? this.black, - grey: grey ?? this.grey, - greyGainsboro: greyGainsboro ?? this.greyGainsboro, - greyWhisper: greyWhisper ?? this.greyWhisper, - whiteSmoke: whiteSmoke ?? this.whiteSmoke, - whiteSnow: whiteSnow ?? this.whiteSnow, - white: white ?? this.white, - blueAlice: blueAlice ?? this.blueAlice, - accentBlue: accentBlue ?? this.accentBlue, - accentRed: accentRed ?? this.accentRed, - accentGreen: accentGreen ?? this.accentGreen, - borderTop: borderTop ?? this.borderTop, - borderBottom: borderBottom ?? this.borderBottom, - shadowIconButton: shadowIconButton ?? this.shadowIconButton, - modalShadow: modalShadow ?? this.modalShadow, - highlight: highlight ?? this.highlight, - overlay: overlay ?? this.overlay, - overlayDark: overlayDark ?? this.overlayDark, - bgGradient: bgGradient ?? this.bgGradient, - ); - } + }) => + brightness == Brightness.light + ? ColorTheme.light( + black: black ?? this.black, + grey: grey ?? this.grey, + greyGainsboro: greyGainsboro ?? this.greyGainsboro, + greyWhisper: greyWhisper ?? this.greyWhisper, + whiteSmoke: whiteSmoke ?? this.whiteSmoke, + whiteSnow: whiteSnow ?? this.whiteSnow, + white: white ?? this.white, + blueAlice: blueAlice ?? this.blueAlice, + accentBlue: accentBlue ?? this.accentBlue, + accentRed: accentRed ?? this.accentRed, + accentGreen: accentGreen ?? this.accentGreen, + borderTop: borderTop ?? this.borderTop, + borderBottom: borderBottom ?? this.borderBottom, + shadowIconButton: shadowIconButton ?? this.shadowIconButton, + modalShadow: modalShadow ?? this.modalShadow, + highlight: highlight ?? this.highlight, + overlay: overlay ?? this.overlay, + overlayDark: overlayDark ?? this.overlayDark, + bgGradient: bgGradient ?? this.bgGradient, + ) + : ColorTheme.dark( + black: black ?? this.black, + grey: grey ?? this.grey, + greyGainsboro: greyGainsboro ?? this.greyGainsboro, + greyWhisper: greyWhisper ?? this.greyWhisper, + whiteSmoke: whiteSmoke ?? this.whiteSmoke, + whiteSnow: whiteSnow ?? this.whiteSnow, + white: white ?? this.white, + blueAlice: blueAlice ?? this.blueAlice, + accentBlue: accentBlue ?? this.accentBlue, + accentRed: accentRed ?? this.accentRed, + accentGreen: accentGreen ?? this.accentGreen, + borderTop: borderTop ?? this.borderTop, + borderBottom: borderBottom ?? this.borderBottom, + shadowIconButton: shadowIconButton ?? this.shadowIconButton, + modalShadow: modalShadow ?? this.modalShadow, + highlight: highlight ?? this.highlight, + overlay: overlay ?? this.overlay, + overlayDark: overlayDark ?? this.overlayDark, + bgGradient: bgGradient ?? this.bgGradient, + ); + /// Merge color theme ColorTheme merge(ColorTheme? other) { if (other == null) return this; return copyWith( @@ -700,13 +769,14 @@ class ColorTheme { /// Channel theme data class ChannelTheme { - /// Theme of the [ChannelHeader] widget - final ChannelHeaderTheme channelHeaderTheme; - + /// Constructor for creating [ChannelTheme] ChannelTheme({ required this.channelHeaderTheme, }); + /// Theme of the [ChannelHeader] widget + final ChannelHeaderTheme channelHeaderTheme; + /// Creates a copy of [ChannelTheme] with specified attributes overridden. ChannelTheme copyWith({ ChannelHeaderTheme? channelHeaderTheme, @@ -715,6 +785,7 @@ class ChannelTheme { channelHeaderTheme: channelHeaderTheme ?? this.channelHeaderTheme, ); + /// Merge with theme ChannelTheme merge(ChannelTheme? other) { if (other == null) return this; return copyWith( @@ -723,28 +794,30 @@ class ChannelTheme { } } +/// Theme for avatar class AvatarTheme { - final BoxConstraints? _constraints; - final BorderRadius? _borderRadius; - - BoxConstraints get constraints { - return _constraints ?? - BoxConstraints.tightFor( - height: 32, - width: 32, - ); - } - - BorderRadius get borderRadius { - return _borderRadius ?? BorderRadius.circular(20); - } - + /// Constructor for creating [AvatarTheme] AvatarTheme({ BoxConstraints? constraints, BorderRadius? borderRadius, }) : _constraints = constraints, _borderRadius = borderRadius; + final BoxConstraints? _constraints; + final BorderRadius? _borderRadius; + + /// Get constraints for avatar + BoxConstraints get constraints => + _constraints ?? + const BoxConstraints.tightFor( + height: 32, + width: 32, + ); + + /// Get border radius + BorderRadius get borderRadius => _borderRadius ?? BorderRadius.circular(20); + + /// Copy with another theme AvatarTheme copyWith({ BoxConstraints? constraints, BorderRadius? borderRadius, @@ -754,6 +827,7 @@ class AvatarTheme { borderRadius: borderRadius ?? _borderRadius, ); + /// Merge with another AvatarTheme AvatarTheme merge(AvatarTheme? other) { if (other == null) return this; return copyWith( @@ -763,19 +837,9 @@ class AvatarTheme { } } +/// Class for getting message theme class MessageTheme { - final TextStyle? messageText; - final TextStyle? messageAuthor; - final TextStyle? messageLinks; - final TextStyle? createdAt; - final TextStyle? replies; - final Color? messageBackgroundColor; - final Color? messageBorderColor; - final Color? reactionsBackgroundColor; - final Color? reactionsBorderColor; - final Color? reactionsMaskColor; - final AvatarTheme? avatarTheme; - + /// Constructor into [MessageTheme] const MessageTheme({ this.replies, this.messageText, @@ -790,6 +854,40 @@ class MessageTheme { this.createdAt, }); + /// Text style for message text + final TextStyle? messageText; + + /// Text style for message author + final TextStyle? messageAuthor; + + /// Text style for message links + final TextStyle? messageLinks; + + /// Text style for created at text + final TextStyle? createdAt; + + /// Text style for replies + final TextStyle? replies; + + /// Color for messageBackgroundColor + final Color? messageBackgroundColor; + + /// Color for message border color + final Color? messageBorderColor; + + /// Color for reactions + final Color? reactionsBackgroundColor; + + /// Colors reaction border + final Color? reactionsBorderColor; + + /// Color for reaction mask + final Color? reactionsMaskColor; + + /// Theme of the avatar + final AvatarTheme? avatarTheme; + + /// Copy with a theme MessageTheme copyWith({ TextStyle? messageText, TextStyle? messageAuthor, @@ -819,6 +917,7 @@ class MessageTheme { reactionsMaskColor: reactionsMaskColor ?? this.reactionsMaskColor, ); + /// Merge with a theme MessageTheme merge(MessageTheme? other) { if (other == null) return this; return copyWith( @@ -839,14 +938,9 @@ class MessageTheme { } } +/// Theme for channel preview class ChannelPreviewTheme { - final TextStyle? title; - final TextStyle? subtitle; - final TextStyle? lastMessageAt; - final AvatarTheme? avatarTheme; - final Color? unreadCounterColor; - final double? indicatorIconSize; - + /// Constructor for creating [ChannelPreviewTheme] const ChannelPreviewTheme({ this.title, this.subtitle, @@ -856,6 +950,25 @@ class ChannelPreviewTheme { this.indicatorIconSize, }); + /// Theme for title + final TextStyle? title; + + /// Theme for subtitle + final TextStyle? subtitle; + + /// Theme of last message at + final TextStyle? lastMessageAt; + + /// Avatar theme + final AvatarTheme? avatarTheme; + + /// Unread counter color + final Color? unreadCounterColor; + + /// Indicator icon size + final double? indicatorIconSize; + + /// Copy with theme ChannelPreviewTheme copyWith({ TextStyle? title, TextStyle? subtitle, @@ -873,6 +986,7 @@ class ChannelPreviewTheme { indicatorIconSize: indicatorIconSize ?? this.indicatorIconSize, ); + /// Merge with theme ChannelPreviewTheme merge(ChannelPreviewTheme? other) { if (other == null) return this; return copyWith( @@ -886,12 +1000,9 @@ class ChannelPreviewTheme { } } +/// Theme for [ChannelHeader] class ChannelHeaderTheme { - final TextStyle? title; - final TextStyle? subtitle; - final AvatarTheme? avatarTheme; - final Color? color; - + /// Constructor for creating a [ChannelHeaderTheme] const ChannelHeaderTheme({ this.title, this.subtitle, @@ -899,6 +1010,19 @@ class ChannelHeaderTheme { this.color, }); + /// Theme for title + final TextStyle? title; + + /// Theme for subtitle + final TextStyle? subtitle; + + /// Theme for avatar + final AvatarTheme? avatarTheme; + + /// Color for [ChannelHeaderTheme] + final Color? color; + + /// Copy with theme ChannelHeaderTheme copyWith({ TextStyle? title, TextStyle? subtitle, @@ -912,6 +1036,7 @@ class ChannelHeaderTheme { color: color ?? this.color, ); + /// Merge with other [ChannelHeaderTheme] ChannelHeaderTheme merge(ChannelHeaderTheme? other) { if (other == null) return this; return copyWith( @@ -925,6 +1050,13 @@ class ChannelHeaderTheme { /// Theme dedicated to the [ChannelListHeader] class ChannelListHeaderTheme { + /// Returns a new [ChannelListHeaderTheme] + const ChannelListHeaderTheme({ + this.title, + this.avatarTheme, + this.color, + }); + /// Style of the title text final TextStyle? title; @@ -934,13 +1066,6 @@ class ChannelListHeaderTheme { /// Background color of the appbar final Color? color; - /// Returns a new [ChannelListHeaderTheme] - const ChannelListHeaderTheme({ - this.title, - this.avatarTheme, - this.color, - }); - /// Returns a new [ChannelListHeaderTheme] replacing some of its properties ChannelListHeaderTheme copyWith({ TextStyle? title, @@ -966,6 +1091,22 @@ class ChannelListHeaderTheme { /// Defines the theme dedicated to the [MessageInput] widget class MessageInputTheme { + /// Returns a new [MessageInputTheme] + const MessageInputTheme({ + this.sendAnimationDuration, + this.actionButtonColor, + this.sendButtonColor, + this.actionButtonIdleColor, + this.sendButtonIdleColor, + this.inputBackground, + this.inputTextStyle, + this.inputDecoration, + this.activeBorderGradient, + this.idleBorderGradient, + this.borderRadius, + this.expandButtonColor, + }); + /// Duration of the [MessageInput] send button animation final Duration? sendAnimationDuration; @@ -1002,22 +1143,6 @@ class MessageInputTheme { /// Border radius of [MessageInput] final BorderRadius? borderRadius; - /// Returns a new [MessageInputTheme] - const MessageInputTheme({ - this.sendAnimationDuration, - this.actionButtonColor, - this.sendButtonColor, - this.actionButtonIdleColor, - this.sendButtonIdleColor, - this.inputBackground, - this.inputTextStyle, - this.inputDecoration, - this.activeBorderGradient, - this.idleBorderGradient, - this.borderRadius, - this.expandButtonColor, - }); - /// Returns a new [MessageInputTheme] replacing some of its properties MessageInputTheme copyWith({ Duration? sendAnimationDuration, @@ -1071,13 +1196,9 @@ class MessageInputTheme { } } +/// Effect store class Effect { - final double? sigmaX; - final double? sigmaY; - final Color? color; - final double? alpha; - final double? blur; - + /// Constructor for creating [Effect] const Effect({ this.sigmaX, this.sigmaY, @@ -1086,6 +1207,22 @@ class Effect { this.blur, }); + /// + final double? sigmaX; + + /// + final double? sigmaY; + + /// + final Color? color; + + /// + final double? alpha; + + /// + final double? blur; + + /// Copy with new effect Effect copyWith({ double? sigmaX, double? sigmaY, From d63540ba9b63a44701ea0fb5fc7c022694b840bb Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 17:18:26 +0530 Subject: [PATCH 204/599] lint theme --- .../lib/src/stream_neumorphic_button.dart | 20 +- .../lib/src/stream_svg_icon.dart | 1774 +++++++++-------- 2 files changed, 904 insertions(+), 890 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart b/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart index 157d2940..947e2265 100644 --- a/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart +++ b/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart @@ -1,19 +1,24 @@ import 'package:flutter/material.dart'; +/// Neumorphic button class StreamNeumorphicButton extends StatelessWidget { - final Widget child; - final Color backgroundColor; + /// Constructor for creating [StreamNeumorphicButton] const StreamNeumorphicButton({ Key? key, required this.child, this.backgroundColor = Colors.white, }) : super(key: key); + /// Child contained in the button + final Widget child; + + /// Background color of button + final Color backgroundColor; + @override - Widget build(BuildContext context) { - return Container( - margin: EdgeInsets.all(8), + Widget build(BuildContext context) => Container( + margin: const EdgeInsets.all(8), height: 40, width: 40, decoration: BoxDecoration( @@ -22,10 +27,10 @@ class StreamNeumorphicButton extends StatelessWidget { boxShadow: [ BoxShadow( color: Colors.grey.shade700, - offset: Offset(0, 1), + offset: const Offset(0, 1), blurRadius: 0.5, ), - BoxShadow( + const BoxShadow( color: Colors.white, blurRadius: 0.5, ), @@ -33,5 +38,4 @@ class StreamNeumorphicButton extends StatelessWidget { ), child: child, ); - } } diff --git a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart index f115617c..78dfba2c 100644 --- a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart +++ b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart @@ -2,18 +2,904 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +/// Icon set of stream chat class StreamSvgIcon extends StatelessWidget { - final String? assetName; - final double? width; - final double? height; - final Color? color; - + /// Constructor for creating a [StreamSvgIcon] const StreamSvgIcon({ + Key? key, this.assetName, this.color, this.width = 24, this.height = 24, - }); + }) : super(key: key); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.settings({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'settings.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.down({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_down.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.attach({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_attach.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.smile({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_smile.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.mentions({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'mentions.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.record({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_record.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.camera({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_camera.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.files({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'files.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.pictures({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'pictures.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.left({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_left.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.user({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_user.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.userAdd({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_User_add.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.check({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_check.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.checkAll({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_check_all.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.checkSend({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_check_send.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.penWrite({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_pen-write.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.contacts({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_contacts.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.close({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_close.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.search({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_search.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.right({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_right.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.mute({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_mute.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.userRemove({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_User_deselect.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.lightning({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_lightning-command runner.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.emptyCircleLeft({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_empty_circle_left.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.message({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_message.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.thread({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_Thread_Reply.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.reply({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_curve_line_left_up_big.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.edit({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_edit.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.download({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_download.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.cloudDownload({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_cloud_download.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.copy({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_copy.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.delete({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_delete.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.eye({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_eye-off.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.arrowRight({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_arrow_right.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.closeSmall({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_close_sml.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.iconCurveLineLeftUp({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_curve_line_left_up.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.iconMoon({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'icon_moon.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.iconShare({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'icon_SHARE.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.iconGrid({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_grid.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.iconSendMessage({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_send_message.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.iconMenuPoint({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_menu_point_v.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.iconSave({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_save.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.shareArrow({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'share_arrow.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetype7z({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_7z.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeCsv({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_CSV.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeDoc({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_DOC.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeDocx({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_DOCX.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeGeneric({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_Generic.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeHtml({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_html.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeMd({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_MD.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeOdt({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_ODT.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypePdf({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_PDF.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypePpt({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_PPT.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypePptx({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_PPTX.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeRar({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_RAR.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeRtf({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_RTF.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeTar({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_TAR.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeTxt({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_TXT.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeXls({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_XLS.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeXlsx({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_XLSX.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.filetypeZip({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'filetype_ZIP.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.iconGroup({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_group.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.iconNotification({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_notification.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.iconUserDelete({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_user_delete.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.error({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_error.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.circleUp({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_circle_up.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.iconUserSettings({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_user_settings.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.giphyIcon({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'giphy_icon.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.imgur({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'imgur.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.volumeUp({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'volume-up.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.flag({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'flag.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.iconFlag({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'icon_flag.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.retry({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'icon_retry.svg', + color: color, + width: size, + height: size, + ); + + /// Name of icon asset + final String? assetName; + + /// Width of icon + final double? width; + + /// Height of icon + final double? height; + + /// Color of icon + final Color? color; @override Widget build(BuildContext context) { @@ -27,880 +913,4 @@ class StreamSvgIcon extends StatelessWidget { color: color, ); } - - factory StreamSvgIcon.settings({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'settings.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.down({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_down.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.attach({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_attach.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.smile({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_smile.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.mentions({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'mentions.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.record({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_record.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.camera({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_camera.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.files({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'files.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.pictures({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'pictures.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.left({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_left.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.user({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_user.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.userAdd({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_User_add.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.check({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_check.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.checkAll({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_check_all.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.checkSend({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_check_send.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.penWrite({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_pen-write.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.contacts({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_contacts.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.close({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_close.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.search({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_search.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.right({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_right.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.mute({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_mute.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.userRemove({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_User_deselect.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.lightning({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_lightning-command runner.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.emptyCircleLeft({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_empty_circle_left.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.message({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_message.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.thread({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_Thread_Reply.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.reply({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_curve_line_left_up_big.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.edit({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_edit.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.download({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_download.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.cloudDownload({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_cloud_download.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.copy({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_copy.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.delete({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_delete.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.eye({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_eye-off.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.arrowRight({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_arrow_right.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.closeSmall({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_close_sml.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.iconCurveLineLeftUp({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_curve_line_left_up.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.iconMoon({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'icon_moon.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.iconShare({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'icon_SHARE.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.iconGrid({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_grid.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.iconSendMessage({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_send_message.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.iconMenuPoint({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_menu_point_v.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.iconSave({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_save.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.shareArrow({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'share_arrow.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetype7z({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_7z.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeCsv({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_CSV.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeDoc({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_DOC.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeDocx({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_DOCX.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeGeneric({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_Generic.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeHtml({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_html.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeMd({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_MD.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeOdt({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_ODT.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypePdf({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_PDF.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypePpt({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_PPT.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypePptx({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_PPTX.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeRar({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_RAR.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeRtf({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_RTF.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeTar({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_TAR.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeTxt({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_TXT.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeXls({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_XLS.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeXlsx({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_XLSX.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.filetypeZip({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'filetype_ZIP.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.iconGroup({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_group.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.iconNotification({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_notification.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.iconUserDelete({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_user_delete.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.error({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_error.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.circleUp({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_circle_up.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.iconUserSettings({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'Icon_user_settings.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.giphyIcon({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'giphy_icon.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.imgur({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'imgur.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.volumeUp({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'volume-up.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.flag({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'flag.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.iconFlag({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'icon_flag.svg', - color: color, - width: size, - height: size, - ); - } - - factory StreamSvgIcon.retry({ - double? size, - Color? color, - }) { - return StreamSvgIcon( - assetName: 'icon_retry.svg', - color: color, - width: size, - height: size, - ); - } } From 9d05cd20e02b3506f8e42e1409a47b57c30392c7 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 17:23:30 +0530 Subject: [PATCH 205/599] lint --- .../lib/src/swipeable.dart | 109 +++++++------ .../lib/src/system_message.dart | 14 +- .../lib/src/thread_header.dart | 151 +++++++++--------- 3 files changed, 142 insertions(+), 132 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/swipeable.dart b/packages/stream_chat_flutter/lib/src/swipeable.dart index 3d195e4f..5895e8b5 100644 --- a/packages/stream_chat_flutter/lib/src/swipeable.dart +++ b/packages/stream_chat_flutter/lib/src/swipeable.dart @@ -1,27 +1,38 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'stream_chat_theme.dart'; - -/// +/// Widget to make a swipeable tile class Swipeable extends StatefulWidget { - final Widget child; - final Widget backgroundIcon; - final VoidCallback? onSwipeStart; - final VoidCallback? onSwipeCancel; - final VoidCallback? onSwipeEnd; - final double threshold; - - /// + /// Constructor for creating a [Swipeable] widget const Swipeable({ + Key? key, required this.child, required this.backgroundIcon, this.onSwipeStart, this.onSwipeCancel, this.onSwipeEnd, this.threshold = 82.0, - }); + }) : super(key: key); + + /// Child to make swipeable + final Widget child; + + /// Background icon after swipe + final Widget backgroundIcon; + + /// Callback when swipe starts + final VoidCallback? onSwipeStart; + + /// Callback when swipe is cancelled + final VoidCallback? onSwipeCancel; + + /// Callback when swipe ends + final VoidCallback? onSwipeEnd; + + /// Threshold for swipe + final double threshold; @override State createState() => _SwipeableState(); @@ -45,10 +56,10 @@ class _SwipeableState extends State with TickerProviderStateMixin { AnimationController(duration: _animationDuration, vsync: this); _iconMoveController = AnimationController(duration: _animationDuration, vsync: this); - _moveAnimation = Tween(begin: Offset.zero, end: Offset(1, 0)) + _moveAnimation = Tween(begin: Offset.zero, end: const Offset(1, 0)) .animate(_moveController); _iconTransitionAnimation = - Tween(begin: Offset(-0.1, 0), end: Offset(0.4, 0)) + Tween(begin: const Offset(-0.1, 0), end: const Offset(0.4, 0)) .animate(_moveController); _iconFadeAnimation = Tween(begin: 0.7, end: 1).animate(_iconMoveController); @@ -120,44 +131,42 @@ class _SwipeableState extends State with TickerProviderStateMixin { } @override - Widget build(BuildContext context) { - return GestureDetector( - onHorizontalDragStart: _handleDragStart, - onHorizontalDragUpdate: _handleDragUpdate, - onHorizontalDragEnd: _handleDragEnd, - behavior: HitTestBehavior.opaque, - child: Stack( - alignment: Alignment.center, - fit: StackFit.passthrough, - children: [ - SlideTransition( - position: _iconTransitionAnimation, - child: Row( - children: [ - FadeTransition( - opacity: _iconFadeAnimation, - child: Container( - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, + Widget build(BuildContext context) => GestureDetector( + onHorizontalDragStart: _handleDragStart, + onHorizontalDragUpdate: _handleDragUpdate, + onHorizontalDragEnd: _handleDragEnd, + behavior: HitTestBehavior.opaque, + child: Stack( + alignment: Alignment.center, + fit: StackFit.passthrough, + children: [ + SlideTransition( + position: _iconTransitionAnimation, + child: Row( + children: [ + FadeTransition( + opacity: _iconFadeAnimation, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, + ), ), + child: widget.backgroundIcon, ), - child: widget.backgroundIcon, ), - ), - ], + ], + ), ), - ), - SlideTransition( - position: _moveAnimation, - child: widget.child, - ), - ], - ), - ); - } + SlideTransition( + position: _moveAnimation, + child: widget.child, + ), + ], + ), + ); } diff --git a/packages/stream_chat_flutter/lib/src/system_message.dart b/packages/stream_chat_flutter/lib/src/system_message.dart index cdb9294f..b847eb3c 100644 --- a/packages/stream_chat_flutter/lib/src/system_message.dart +++ b/packages/stream_chat_flutter/lib/src/system_message.dart @@ -3,18 +3,20 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// It shows a date divider depending on the date difference class SystemMessage extends StatelessWidget { - /// This message - final Message message; - - /// The function called when tapping on the message when the message is not failed - final void Function(Message)? onMessageTap; - + /// Constructor for creating a [SystemMessage] const SystemMessage({ Key? key, required this.message, this.onMessageTap, }) : super(key: key); + /// This message + final Message message; + + // ignore: lines_longer_than_80_chars + /// The function called when tapping on the message when the message is not failed + final void Function(Message)? onMessageTap; + @override Widget build(BuildContext context) { final theme = StreamChatTheme.of(context); diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart index 0157791e..ba94642f 100644 --- a/packages/stream_chat_flutter/lib/src/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/thread_header.dart @@ -3,9 +3,6 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'back_button.dart'; -import 'channel_name.dart'; - /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header_paint.png) /// @@ -46,16 +43,34 @@ import 'channel_name.dart'; /// Usually you would use this widget as an [AppBar] inside a [Scaffold]. /// However you can also use it as a normal widget. /// -/// Make sure to have a [StreamChannel] ancestor in order to provide the information about the channel. -/// Every part of the widget uses a [StreamBuilder] to render the channel information as soon as it updates. +/// Make sure to have a [StreamChannel] ancestor in order to provide the +/// information about the channel. +/// Every part of the widget uses a [StreamBuilder] to render the channel +/// information as soon as it updates. /// /// By default the widget shows a backButton that calls [Navigator.pop]. -/// You can disable this button using the [showBackButton] property of just override the behaviour +/// You can disable this button using the [showBackButton] property of just +/// override the behaviour /// with [onBackPressed]. /// -/// The widget components render the ui based on the first ancestor of type [StreamChatTheme] and on its [ChannelTheme.channelHeaderTheme] property. +/// The widget components render the ui based on the first ancestor of type +/// [StreamChatTheme] and on its [ChannelTheme.channelHeaderTheme] property. /// Modify it to change the widget appearance. class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { + /// Instantiate a new ThreadHeader + const ThreadHeader({ + Key? key, + required this.parent, + this.showBackButton = true, + this.onBackPressed, + this.title, + this.subtitle, + this.leading, + this.actions, + this.onTitleTap, + }) : preferredSize = const Size.fromHeight(kToolbarHeight), + super(key: key); + /// True if this header shows the leading back button final bool showBackButton; @@ -81,82 +96,66 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { /// AppBar actions final List? actions; - /// Instantiate a new ThreadHeader - ThreadHeader({ - Key? key, - required this.parent, - this.showBackButton = true, - this.onBackPressed, - this.title, - this.subtitle, - this.leading, - this.actions, - this.onTitleTap, - }) : preferredSize = Size.fromHeight(kToolbarHeight), - super(key: key); - @override - Widget build(BuildContext context) { - return AppBar( - automaticallyImplyLeading: false, - brightness: Theme.of(context).brightness, - elevation: 1, - leading: leading ?? - (showBackButton - ? StreamBackButton( - cid: StreamChannel.of(context).channel.cid, - onPressed: onBackPressed, - showUnreads: true, - ) - : SizedBox()), - backgroundColor: - StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, - centerTitle: true, - actions: actions, - title: InkWell( - onTap: onTitleTap, - child: Container( - height: preferredSize.height, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - title ?? - Text( - 'Thread Reply', - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .title, - ), - SizedBox(height: 2), - subtitle ?? - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'with ', - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .subtitle, - ), - Flexible( - child: ChannelName( - textStyle: StreamChatTheme.of(context) + Widget build(BuildContext context) => AppBar( + automaticallyImplyLeading: false, + brightness: Theme.of(context).brightness, + elevation: 1, + leading: leading ?? + (showBackButton + ? StreamBackButton( + cid: StreamChannel.of(context).channel.cid, + onPressed: onBackPressed, + showUnreads: true, + ) + : const SizedBox()), + backgroundColor: + StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, + centerTitle: true, + actions: actions, + title: InkWell( + onTap: onTitleTap, + child: SizedBox( + height: preferredSize.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + title ?? + Text( + 'Thread Reply', + style: StreamChatTheme.of(context) + .channelTheme + .channelHeaderTheme + .title, + ), + const SizedBox(height: 2), + subtitle ?? + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'with ', + style: StreamChatTheme.of(context) .channelTheme .channelHeaderTheme .subtitle, ), - ), - ], - ), - ], + Flexible( + child: ChannelName( + textStyle: StreamChatTheme.of(context) + .channelTheme + .channelHeaderTheme + .subtitle, + ), + ), + ], + ), + ], + ), ), ), - ), - ); - } + ); @override final Size preferredSize; From cca2e381034f2987bfd797663c298045f5f0976f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 17:23:38 +0530 Subject: [PATCH 206/599] lint --- .../lib/src/stream_neumorphic_button.dart | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart b/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart index 947e2265..5867a4a1 100644 --- a/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart +++ b/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; /// Neumorphic button class StreamNeumorphicButton extends StatelessWidget { - /// Constructor for creating [StreamNeumorphicButton] const StreamNeumorphicButton({ Key? key, @@ -18,24 +17,24 @@ class StreamNeumorphicButton extends StatelessWidget { @override Widget build(BuildContext context) => Container( - margin: const EdgeInsets.all(8), - height: 40, - width: 40, - decoration: BoxDecoration( - color: backgroundColor, - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.grey.shade700, - offset: const Offset(0, 1), - blurRadius: 0.5, - ), - const BoxShadow( - color: Colors.white, - blurRadius: 0.5, - ), - ], - ), - child: child, - ); + margin: const EdgeInsets.all(8), + height: 40, + width: 40, + decoration: BoxDecoration( + color: backgroundColor, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.grey.shade700, + offset: const Offset(0, 1), + blurRadius: 0.5, + ), + const BoxShadow( + color: Colors.white, + blurRadius: 0.5, + ), + ], + ), + child: child, + ); } From 7b482fb5cd593f9c2d32ac7b0267e17157122579 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 17:27:40 +0530 Subject: [PATCH 207/599] lint --- .../lib/src/typing_indicator.dart | 64 +++++++++---------- .../lib/src/unread_indicator.dart | 6 +- .../lib/src/upload_progress_indicator.dart | 32 +++++++--- 3 files changed, 58 insertions(+), 44 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart index aecc3bf1..e9377674 100644 --- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart @@ -26,6 +26,7 @@ class TypingIndicator extends StatelessWidget { /// The padding of this widget final EdgeInsets padding; + /// Alignment of the typing indicator final Alignment alignment; @override @@ -35,41 +36,40 @@ class TypingIndicator extends StatelessWidget { return StreamBuilder>( initialData: channelState.typingEvents, stream: channelState.typingEventsStream, - builder: (context, snapshot) { - return AnimatedSwitcher( - duration: Duration(milliseconds: 300), - child: snapshot.data?.isNotEmpty == true - ? Padding( - padding: padding, - child: Align( - key: Key('typings'), - alignment: alignment, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Lottie.asset( - 'animations/typing_dots.json', - package: 'stream_chat_flutter', - height: 4, - ), - Text( - ' ${snapshot.data![0].name}${snapshot.data!.length == 1 ? '' : ' and ${snapshot.data!.length - 1} more'} ${snapshot.data!.length == 1 ? 'is' : 'are'} typing', - maxLines: 1, - style: style, - ), - ], - ), - ), - ) - : Align( - key: Key('alternative'), + builder: (context, snapshot) => AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: snapshot.data?.isNotEmpty == true + ? Padding( + padding: padding, + child: Align( + key: const Key('typings'), alignment: alignment, - child: Container( - child: alternativeWidget ?? Offstage(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Lottie.asset( + 'animations/typing_dots.json', + package: 'stream_chat_flutter', + height: 4, + ), + Text( + // ignore: lines_longer_than_80_chars + ' ${snapshot.data![0].name}${snapshot.data!.length == 1 ? '' : ' and ${snapshot.data!.length - 1} more'} ${snapshot.data!.length == 1 ? 'is' : 'are'} typing', + maxLines: 1, + style: style, + ), + ], ), ), - ); - }, + ) + : Align( + key: const Key('alternative'), + alignment: alignment, + child: Container( + child: alternativeWidget ?? const Offstage(), + ), + ), + ), ); } } diff --git a/packages/stream_chat_flutter/lib/src/unread_indicator.dart b/packages/stream_chat_flutter/lib/src/unread_indicator.dart index 8d2c996a..9c3a5f88 100644 --- a/packages/stream_chat_flutter/lib/src/unread_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/unread_indicator.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +/// Widget for showing an unread indicator class UnreadIndicator extends StatelessWidget { + /// Constructor for creating an [UnreadIndicator] const UnreadIndicator({ Key? key, this.cid, @@ -24,7 +26,7 @@ class UnreadIndicator extends StatelessWidget { : client.state.totalUnreadCount, builder: (context, snapshot) { if (!snapshot.hasData || snapshot.data == 0) { - return SizedBox(); + return const SizedBox(); } return Material( borderRadius: BorderRadius.circular(8), @@ -41,7 +43,7 @@ class UnreadIndicator extends StatelessWidget { child: Center( child: Text( '${snapshot.data! > 99 ? '99+' : snapshot.data}', - style: TextStyle( + style: const TextStyle( fontSize: 11, color: Colors.white, ), diff --git a/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart b/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart index 1175237e..9905141a 100644 --- a/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart @@ -1,15 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'stream_chat_theme.dart'; - +/// Widget for showing upload progress class UploadProgressIndicator extends StatelessWidget { - final int uploaded; - final int total; - final Color progressIndicatorColor; - final EdgeInsetsGeometry padding; - final bool showBackground; - final TextStyle? textStyle; - + /// Constructor for creating an [UploadProgressIndicator] const UploadProgressIndicator({ Key? key, required this.uploaded, @@ -25,6 +19,24 @@ class UploadProgressIndicator extends StatelessWidget { this.textStyle, }) : super(key: key); + /// Bytes uploaded + final int uploaded; + + /// Total bytes + final int total; + + /// Color of progress indicator + final Color progressIndicatorColor; + + /// Padding for widget + final EdgeInsetsGeometry padding; + + /// Flag for showing background + final bool showBackground; + + /// [TextStyle] to be applied to text + final TextStyle? textStyle; + @override Widget build(BuildContext context) { final theme = StreamChatTheme.of(context); @@ -42,7 +54,7 @@ class UploadProgressIndicator extends StatelessWidget { valueColor: AlwaysStoppedAnimation(progressIndicatorColor), ), ), - SizedBox(width: 8), + const SizedBox(width: 8), Text( '${_percentage.toInt()}%', style: textStyle ?? From 8a28f8c2b3851913917e7889ab26691b1fcccb14 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 17:32:54 +0530 Subject: [PATCH 208/599] lint --- .../lib/src/url_attachment.dart | 179 +++++++++--------- .../lib/src/user_avatar.dart | 36 +++- .../lib/src/user_item.dart | 92 +++++---- 3 files changed, 167 insertions(+), 140 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/url_attachment.dart b/packages/stream_chat_flutter/lib/src/url_attachment.dart index e924746d..0b91c2ee 100644 --- a/packages/stream_chat_flutter/lib/src/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/url_attachment.dart @@ -3,107 +3,114 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +/// Widget to display URL attachment class UrlAttachment extends StatelessWidget { - final Attachment urlAttachment; - final String hostDisplayName; - final EdgeInsets textPadding; - + /// Constructor for creating a [UrlAttachment] const UrlAttachment({ + Key? key, required this.urlAttachment, required this.hostDisplayName, this.textPadding = const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), - }); + }) : super(key: key); + + /// Attachment to be displayed + final Attachment urlAttachment; + + /// Host name + final String hostDisplayName; + + /// Padding for text + final EdgeInsets textPadding; @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - launchURL( - context, - urlAttachment.ogScrapeUrl, - ); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (urlAttachment.imageUrl != null) - Container( - clipBehavior: Clip.antiAliasWithSaveLayer, - margin: EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - ), - child: Stack( - children: [ - CachedNetworkImage( - width: double.infinity, - imageUrl: urlAttachment.imageUrl!, - fit: BoxFit.cover, - ), - Positioned( - left: 0, - bottom: -1, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topRight: Radius.circular(16), + Widget build(BuildContext context) => GestureDetector( + onTap: () { + launchURL( + context, + urlAttachment.ogScrapeUrl, + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (urlAttachment.imageUrl != null) + Container( + clipBehavior: Clip.antiAliasWithSaveLayer, + margin: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: Stack( + children: [ + CachedNetworkImage( + width: double.infinity, + imageUrl: urlAttachment.imageUrl!, + fit: BoxFit.cover, + ), + Positioned( + left: 0, + bottom: -1, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(16), + ), + color: + StreamChatTheme.of(context).colorTheme.blueAlice, ), - color: StreamChatTheme.of(context).colorTheme.blueAlice, - ), - child: Padding( - padding: const EdgeInsets.only( - top: 8, - left: 8, - right: 8, - ), - child: Text( - hostDisplayName, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), + child: Padding( + padding: const EdgeInsets.only( + top: 8, + left: 8, + right: 8, + ), + child: Text( + hostDisplayName, + style: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, + ), + ), ), ), ), - ), + ], + ), + ), + Padding( + padding: textPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (urlAttachment.title != null) + Text( + urlAttachment.title!.trim(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: StreamChatTheme.of(context) + .textTheme + .body + .copyWith(fontWeight: FontWeight.w700), + ), + if (urlAttachment.text != null) + Text( + urlAttachment.text!, + style: StreamChatTheme.of(context) + .textTheme + .body + .copyWith(fontWeight: FontWeight.w400), + ), ], ), ), - Padding( - padding: textPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (urlAttachment.title != null) - Text( - urlAttachment.title!.trim(), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith(fontWeight: FontWeight.w700), - ), - if (urlAttachment.text != null) - Text( - urlAttachment.text!, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith(fontWeight: FontWeight.w400), - ), - ], - ), - ), - ], - ), - ); - } + ], + ), + ); } diff --git a/packages/stream_chat_flutter/lib/src/user_avatar.dart b/packages/stream_chat_flutter/lib/src/user_avatar.dart index 0833b1a3..cb968527 100644 --- a/packages/stream_chat_flutter/lib/src/user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/user_avatar.dart @@ -1,10 +1,11 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import '../stream_chat_flutter.dart'; - +/// Widget that displays a user avatar class UserAvatar extends StatelessWidget { + /// Constructor to create a [UserAvatar] const UserAvatar({ Key? key, required this.user, @@ -20,16 +21,37 @@ class UserAvatar extends StatelessWidget { this.selectionThickness = 4, }) : super(key: key); + /// User whose avatar is to displayed final User user; + + /// Alignment of the online indicator final Alignment onlineIndicatorAlignment; + + /// Size of the avatar final BoxConstraints? constraints; + + /// [BorderRadius] of the image final BorderRadius? borderRadius; + + /// Size of the online indicator final BoxConstraints? onlineIndicatorConstraints; + + /// Callback when avatar is tapped final void Function(User)? onTap; + + /// Callback when avatar is long pressed final void Function(User)? onLongPress; + + /// Flag for showing online status final bool showOnlineStatus; + + /// Flag for if avatar is selected final bool selected; + + /// Color of selection final Color? selectionColor; + + /// Selection thickness around the avatar final double selectionThickness; @override @@ -53,10 +75,10 @@ class UserAvatar extends StatelessWidget { child: hasImage ? CachedNetworkImage( filterQuality: FilterQuality.high, + // ignore: cast_nullable_to_non_nullable imageUrl: user.extraData['image'] as String, - errorWidget: (_, __, ___) { - return streamChatTheme.defaultUserImage(context, user); - }, + errorWidget: (_, __, ___) => + streamChatTheme.defaultUserImage(context, user), fit: BoxFit.cover, ) : streamChatTheme.defaultUserImage(context, user), @@ -98,12 +120,12 @@ class UserAvatar extends StatelessWidget { child: Container( margin: const EdgeInsets.all(2), constraints: onlineIndicatorConstraints ?? - BoxConstraints.tightFor( + const BoxConstraints.tightFor( width: 8, height: 8, ), child: Material( - shape: CircleBorder(), + shape: const CircleBorder(), color: streamChatTheme.colorTheme.accentGreen, ), ), diff --git a/packages/stream_chat_flutter/lib/src/user_item.dart b/packages/stream_chat_flutter/lib/src/user_item.dart index ff51fea3..290612e1 100644 --- a/packages/stream_chat_flutter/lib/src/user_item.dart +++ b/packages/stream_chat_flutter/lib/src/user_item.dart @@ -5,16 +5,17 @@ import 'package:stream_chat_flutter/src/user_list_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'stream_chat_theme.dart'; - /// /// It shows the current [User] preview. /// -/// The widget uses a [StreamBuilder] to render the user information image as soon as it updates. +/// The widget uses a [StreamBuilder] to render the user information +/// image as soon as it updates. /// -/// Usually you don't use this widget as it's the default user preview used by [UserListView]. +/// Usually you don't use this widget as it's the default user preview used +/// by [UserListView]. /// -/// The widget renders the ui based on the first ancestor of type [StreamChatTheme]. +/// The widget renders the ui based on the first ancestor of type +/// [StreamChatTheme]. /// Modify it to change the widget appearance. class UserItem extends StatelessWidget { /// Instantiate a new UserItem @@ -47,50 +48,47 @@ class UserItem extends StatelessWidget { final bool showLastOnline; @override - Widget build(BuildContext context) { - return ListTile( - onTap: () { - if (onTap != null) { - onTap!(user); - } - }, - onLongPress: () { - if (onLongPress != null) { - onLongPress!(user); - } - }, - leading: UserAvatar( - user: user, - onTap: (user) { - if (onImageTap != null) { - onImageTap!(user); + Widget build(BuildContext context) => ListTile( + onTap: () { + if (onTap != null) { + onTap!(user); } }, - constraints: BoxConstraints.tightFor( - height: 40, - width: 40, + onLongPress: () { + if (onLongPress != null) { + onLongPress!(user); + } + }, + leading: UserAvatar( + user: user, + onTap: (user) { + if (onImageTap != null) { + onImageTap!(user); + } + }, + constraints: const BoxConstraints.tightFor( + height: 40, + width: 40, + ), ), - ), - trailing: selected - ? StreamSvgIcon.checkSend( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ) - : null, - title: Text( - user.name, - style: StreamChatTheme.of(context).textTheme.bodyBold, - ), - subtitle: showLastOnline ? _buildLastActive(context) : null, - ); - } + trailing: selected + ? StreamSvgIcon.checkSend( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + ) + : null, + title: Text( + user.name, + style: StreamChatTheme.of(context).textTheme.bodyBold, + ), + subtitle: showLastOnline ? _buildLastActive(context) : null, + ); - Widget _buildLastActive(context) { - return Text( - user.online == true - ? 'Online' - : 'Last online ${Jiffy(user.lastActive).fromNow()}', - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: StreamChatTheme.of(context).colorTheme.black.withOpacity(.5)), - ); - } + Widget _buildLastActive(context) => Text( + user.online == true + ? 'Online' + : 'Last online ${Jiffy(user.lastActive).fromNow()}', + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(.5)), + ); } From dca6b10c41a52e5eb357bce8ee39e9ae32215c4d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 17:35:41 +0530 Subject: [PATCH 209/599] lint --- .../lib/src/user_list_view.dart | 189 ++++++++---------- 1 file changed, 88 insertions(+), 101 deletions(-) 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 f1053e58..0c835fbe 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -36,10 +36,13 @@ typedef UserItemBuilder = Widget Function(BuildContext, User, bool); /// ``` /// /// -/// Make sure to have a [UsersBloc] ancestor in order to provide the information about the users. -/// The widget uses a [ListView.separated], [GridView.builder] to render the list, grid of channels. +/// Make sure to have a [UsersBloc] ancestor in order to provide the +/// information about the users. +/// The widget uses a [ListView.separated], [GridView.builder] to render the +/// list, grid of channels. /// -/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. +/// The widget components render the ui based on the first ancestor of +/// type [StreamChatTheme]. /// Modify it to change the widget appearance. class UserListView extends StatefulWidget { /// Instantiate a new UserListView @@ -81,8 +84,10 @@ class UserListView extends StatefulWidget { final Map? options; /// The sorting used for the channels matching the filters. - /// Sorting is based on field and direction, multiple sorting options can be provided. - /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. + /// Sorting is based on field and direction, multiple sorting options can + /// be provided. + /// You can sort based on last_updated, last_message_at, updated_at, created_ + /// at or member_count. /// Direction can be ascending or descending. final List? sort; @@ -115,7 +120,8 @@ class UserListView extends StatefulWidget { /// Set it to false to disable the pull-to-refresh widget final bool pullToRefresh; - /// Sets a blue trailing checkMark in [ListUserItem] for all the [selectedUsers] + /// Sets a blue trailing checkMark in [ListUserItem] for all the + /// [selectedUsers] final Set? selectedUsers; /// Set it to true to group users by their first character @@ -153,35 +159,25 @@ class _UserListViewState extends State Widget build(BuildContext context) { final child = UserListCore( errorBuilder: widget.errorBuilder as Widget Function(Object)? ?? - (err) { - return _buildError(err as Error); - }, - emptyBuilder: widget.emptyBuilder ?? - (context) { - return _buildEmpty(); - }, + (err) => _buildError(err as Error), + emptyBuilder: widget.emptyBuilder ?? (context) => _buildEmpty(), loadingBuilder: widget.loadingBuilder ?? - (context) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), + (context) => LayoutBuilder( + builder: (context, viewportConstraints) => + SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), child: ConstrainedBox( constraints: BoxConstraints( minHeight: viewportConstraints.maxHeight, ), - child: Center( + child: const Center( child: CircularProgressIndicator(), ), ), - ); - }, - ); - }, - listBuilder: widget.listBuilder ?? - (context, list) { - return _buildListView(list); - }, + ), + ), + listBuilder: + widget.listBuilder ?? (context, list) => _buildListView(list), pagination: widget.pagination, options: widget.options, sort: widget.sort, @@ -204,7 +200,7 @@ class _UserListViewState extends State widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false; Widget _buildError(Error error) { - print((error).stackTrace); + print(error.stackTrace); var message = error.toString(); if (error is DioError) { @@ -220,11 +216,11 @@ class _UserListViewState extends State mainAxisAlignment: MainAxisAlignment.center, children: [ Text.rich( - TextSpan( + const TextSpan( children: [ WidgetSpan( child: Padding( - padding: const EdgeInsets.only( + padding: EdgeInsets.only( right: 2, ), child: Icon(Icons.error_outline), @@ -243,37 +239,33 @@ class _UserListViewState extends State ), TextButton( onPressed: () => _userListController.loadData!(), - child: Text('Retry'), + child: const Text('Retry'), ), ], ), ); } - Widget _buildEmpty() { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), + Widget _buildEmpty() => LayoutBuilder( + builder: (context, viewportConstraints) => SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), child: ConstrainedBox( constraints: BoxConstraints( minHeight: viewportConstraints.maxHeight, ), - child: Center( + child: const Center( child: Text('There are no users currently'), ), ), - ); - }, - ); - } + ), + ); Widget _buildListView( List items, ) { final child = _isListView ? ListView.separated( - physics: AlwaysScrollableScrollPhysics(), + physics: const AlwaysScrollableScrollPhysics(), itemCount: items.isNotEmpty ? items.length + 1 : items.length, separatorBuilder: (_, index) { if (widget.separatorBuilder != null) { @@ -281,19 +273,17 @@ class _UserListViewState extends State } return _separatorBuilder(context, index); }, - itemBuilder: (context, index) { - return _listItemBuilder(context, index, items); - }, + itemBuilder: (context, index) => + _listItemBuilder(context, index, items), ) : GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: widget.crossAxisCount, ), itemCount: items.isNotEmpty ? items.length + 1 : items.length, - physics: AlwaysScrollableScrollPhysics(), - itemBuilder: (context, index) { - return _gridItemBuilder(context, index, items); - }, + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (context, index) => + _gridItemBuilder(context, index, items), ); return LazyLoadScrollView( @@ -307,24 +297,21 @@ class _UserListViewState extends State if (i < items.length) { final item = items[i]; return item.when( - headerItem: (header) { - return Container( - key: ValueKey('HEADER-$header'), - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), - child: Text( - header, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14.5, - color: StreamChatTheme.of(context).colorTheme.grey, - ), + headerItem: (header) => Container( + key: ValueKey('HEADER-$header'), + color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + child: Text( + header, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14.5, + color: StreamChatTheme.of(context).colorTheme.grey, ), ), - ); - }, + ), + ), userItem: (user) { final selected = widget.selectedUsers?.contains(user) ?? false; return Container( @@ -351,7 +338,7 @@ class _UserListViewState extends State if (i < items.length) { final item = items[i]; return item.when( - headerItem: (_) => Offstage(), + headerItem: (_) => const Offstage(), userItem: (user) { final selected = widget.selectedUsers?.contains(user) ?? false; return Container( @@ -365,11 +352,12 @@ class _UserListViewState extends State user: user, borderRadius: BorderRadius.circular(32), selected: selected, - constraints: BoxConstraints.tightFor( + constraints: const BoxConstraints.tightFor( height: 64, width: 64, ), - onlineIndicatorConstraints: BoxConstraints.tightFor( + onlineIndicatorConstraints: + const BoxConstraints.tightFor( height: 12, width: 12, ), @@ -377,7 +365,7 @@ class _UserListViewState extends State widget.onUserTap!(user, widget.userWidget), onLongPress: widget.onUserLongPress, ), - SizedBox(height: 4), + const SizedBox(height: 4), Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Text( @@ -385,7 +373,7 @@ class _UserListViewState extends State textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 12, ), @@ -401,39 +389,38 @@ class _UserListViewState extends State } } - Widget _buildQueryProgressIndicator(context, UsersBlocState usersProvider) { - return StreamBuilder( - stream: usersProvider.queryUsersLoading, - initialData: false, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Container( - color: StreamChatTheme.of(context) - .colorTheme - .accentRed - .withOpacity(.2), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Center( - child: Text('Error loading users'), + Widget _buildQueryProgressIndicator(context, UsersBlocState usersProvider) => + StreamBuilder( + stream: usersProvider.queryUsersLoading, + initialData: false, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Container( + color: StreamChatTheme.of(context) + .colorTheme + .accentRed + .withOpacity(.2), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 16), + child: Center( + child: Text('Error loading users'), + ), ), + ); + } + return Container( + height: 100, + padding: const EdgeInsets.all(32), + child: Center( + child: snapshot.data! + ? const CircularProgressIndicator() + : Container(), ), ); - } - return Container( - height: 100, - padding: EdgeInsets.all(32), - child: Center( - child: snapshot.data! ? CircularProgressIndicator() : Container(), - ), - ); - }); - } + }); - Widget _separatorBuilder(context, i) { - return Container( - height: 1, - color: StreamChatTheme.of(context).colorTheme.greyWhisper, - ); - } + Widget _separatorBuilder(context, i) => Container( + height: 1, + color: StreamChatTheme.of(context).colorTheme.greyWhisper, + ); } From 09c6799af84dcf4760843adccd0db5221280f146 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 17:47:58 +0530 Subject: [PATCH 210/599] lint --- .../lib/src/user_reaction_display.dart | 17 ++++--- .../stream_chat_flutter/lib/src/utils.dart | 45 ++++++++--------- .../lib/src/video_service.dart | 48 ++++++++++--------- .../lib/src/video_thumbnail_image.dart | 40 +++++++++------- 4 files changed, 81 insertions(+), 69 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/user_reaction_display.dart b/packages/stream_chat_flutter/lib/src/user_reaction_display.dart index fe61caad..23c723dd 100644 --- a/packages/stream_chat_flutter/lib/src/user_reaction_display.dart +++ b/packages/stream_chat_flutter/lib/src/user_reaction_display.dart @@ -2,7 +2,10 @@ 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, @@ -10,13 +13,17 @@ class UserReactionDisplay extends StatelessWidget { 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) { - return Container( + Widget build(BuildContext context) => Container( color: Colors.black87, child: Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -25,9 +32,8 @@ class UserReactionDisplay extends StatelessWidget { children: reactionToEmoji.keys.map((reactionType) { final firstUserReaction = message.latestReactions! .firstWhere((element) => element.type == reactionType, - orElse: () { - return null; - } as Reaction Function()?); + //ignore: unnecessary_parenthesis + orElse: (() => null) as Reaction Function()?); if (firstUserReaction.user == null) { return IconButton( @@ -52,5 +58,4 @@ class UserReactionDisplay extends StatelessWidget { }).toList(), ), ); - } } diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 4233dd2f..11a1f3c0 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -13,7 +13,7 @@ Future launchURL(BuildContext context, String? url) async { } else { // ignore: deprecated_member_use Scaffold.of(context).showSnackBar( - SnackBar( + const SnackBar( content: Text('Cannot launch the url'), ), ); @@ -27,11 +27,10 @@ Future showConfirmationDialog( Widget? icon, String? question, String? cancelText, -}) { - return showModalBottomSheet( +}) => showModalBottomSheet( backgroundColor: StreamChatTheme.of(context).colorTheme.white, context: context, - shape: RoundedRectangleBorder( + shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), @@ -42,20 +41,20 @@ Future showConfirmationDialog( child: Column( mainAxisSize: MainAxisSize.min, children: [ - SizedBox(height: 26), + const SizedBox(height: 26), if (icon != null) icon, - SizedBox(height: 26), + const SizedBox(height: 26), Text( title, style: StreamChatTheme.of(context).textTheme.headlineBold, ), - SizedBox(height: 7), + const SizedBox(height: 7), if (question != null) Text( question, textAlign: TextAlign.center, ), - SizedBox(height: 36), + const SizedBox(height: 36), Container( color: effect.color!.withOpacity(effect.alpha ?? 1), height: 1, @@ -110,8 +109,8 @@ Future showConfirmationDialog( ), ); }); -} +/// Shows info dialog Future showInfoDialog( BuildContext context, { required String title, @@ -119,26 +118,24 @@ Future showInfoDialog( Widget? icon, String? details, StreamChatThemeData? theme, -}) { - return showModalBottomSheet( +}) => showModalBottomSheet( backgroundColor: theme?.colorTheme.white ?? StreamChatTheme.of(context).colorTheme.white, context: context, - shape: RoundedRectangleBorder( + shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), )), - builder: (context) { - return SafeArea( + builder: (context) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ - SizedBox( + const SizedBox( height: 26, ), if (icon != null) icon, - SizedBox( + const SizedBox( height: 26, ), Text( @@ -146,11 +143,11 @@ Future showInfoDialog( style: theme?.textTheme.headlineBold ?? StreamChatTheme.of(context).textTheme.headlineBold, ), - SizedBox( + const SizedBox( height: 7, ), if (details != null) Text(details), - SizedBox( + const SizedBox( height: 36, ), Container( @@ -175,10 +172,8 @@ Future showInfoDialog( ), ], ), - ); - }, + ), ); -} /// Get random png with initials String getRandomPicUrl(User user) => @@ -335,15 +330,16 @@ StreamSvgIcon getFileTypeImage(String? type) { } } +/// Wraps attachment widget with custom shape Widget wrapAttachmentWidget( BuildContext context, Widget attachmentWidget, ShapeBorder attachmentShape, + // ignore: avoid_positional_boolean_parameters bool reverse, BorderRadius borderRadius, -) { - return ClipRRect( - borderRadius: borderRadius, +) => ClipRRect( + borderRadius: borderRadius, child: Material( clipBehavior: Clip.antiAlias, shape: attachmentShape, @@ -355,4 +351,3 @@ Widget wrapAttachmentWidget( ), ), ); -} diff --git a/packages/stream_chat_flutter/lib/src/video_service.dart b/packages/stream_chat_flutter/lib/src/video_service.dart index a4d0d1ff..3b6bb13e 100644 --- a/packages/stream_chat_flutter/lib/src/video_service.dart +++ b/packages/stream_chat_flutter/lib/src/video_service.dart @@ -5,12 +5,14 @@ import 'package:synchronized/synchronized.dart'; import 'package:video_compress/video_compress.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; +/// class IVideoService { + IVideoService._(); + + /// Singleton instance of [IVideoService] static final IVideoService instance = IVideoService._(); final _lock = Lock(); - IVideoService._(); - /// compress video from [path] /// compress video from [path] return [Future] /// @@ -26,18 +28,20 @@ class IVideoService { /// ); /// debugPrint(info.toJson()); /// ``` - Future compressVideo(String? path) async { - return _lock.synchronized(() { - return VideoCompress.compressVideo( - path!, + Future compressVideo(String? path) async => _lock.synchronized( + () => VideoCompress.compressVideo( + path!, + ), ); - }); - } - /// Generates a thumbnail image data in memory as UInt8List, it can be easily used by Image.memory(...). - /// The video can be a local video file, or an URL repreents iOS or Android native supported video format. - /// Speicify the maximum height or width for the thumbnail or 0 for same resolution as the original video. - /// The lower quality value creates lower quality of the thumbnail image, but it gets ignored for PNG format. + /// Generates a thumbnail image data in memory as UInt8List, + /// it can be easily used by Image.memory(...). + /// The video can be a local video file, or an URL repreents iOS or + /// Android native supported video format. + /// Speicify the maximum height or width for the thumbnail or 0 for + /// same resolution as the original video. + /// The lower quality value creates lower quality of the thumbnail image, + /// but it gets ignored for PNG format. Future generateVideoThumbnail({ required String video, ImageFormat imageFormat = ImageFormat.PNG, @@ -45,17 +49,17 @@ class IVideoService { int maxWidth = 0, int timeMs = 0, int quality = 10, - }) { - return VideoThumbnail.thumbnailData( - video: video, - imageFormat: imageFormat, - maxHeight: maxHeight, - maxWidth: maxWidth, - timeMs: timeMs, - quality: quality, - ); - } + }) => + VideoThumbnail.thumbnailData( + video: video, + imageFormat: imageFormat, + maxHeight: maxHeight, + maxWidth: maxWidth, + timeMs: timeMs, + quality: quality, + ); } // ignore: non_constant_identifier_names +/// Get instance of [IVideoService] IVideoService get VideoService => IVideoService.instance; diff --git a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart index 3d9d5429..cd321ca2 100644 --- a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart +++ b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart @@ -9,14 +9,7 @@ import 'stream_svg_icon.dart'; import 'video_service.dart'; class VideoThumbnailImage extends StatefulWidget { - final String video; - final double? width; - final double? height; - final BoxFit? fit; - final ImageFormat format; - final Widget Function(BuildContext, Object?)? errorBuilder; - final WidgetBuilder? placeholderBuilder; - + /// Constructor for creating [VideoThumbnailImage] const VideoThumbnailImage({ Key? key, required this.video, @@ -28,6 +21,25 @@ class VideoThumbnailImage extends StatefulWidget { this.placeholderBuilder, }) : super(key: key); + /// Video path + final String video; + + /// Width of widget + final double? width; + + /// Height of widget + final double? height; + + /// Fit of iamge + final BoxFit? fit; + + /// Image format + final ImageFormat format; + + /// Builds widget on error + final Widget Function(BuildContext, Object?)? errorBuilder; + final WidgetBuilder? placeholderBuilder; + @override _VideoThumbnailImageState createState() => _VideoThumbnailImageState(); } @@ -56,11 +68,9 @@ class _VideoThumbnailImageState extends State { } @override - Widget build(BuildContext context) { - return FutureBuilder( + Widget build(BuildContext context) => FutureBuilder( future: thumbnailFuture, - builder: (context, snapshot) { - return AnimatedSwitcher( + builder: (context, snapshot) => AnimatedSwitcher( duration: const Duration(milliseconds: 350), child: Builder( key: ValueKey>(snapshot), @@ -73,7 +83,7 @@ class _VideoThumbnailImageState extends State { } if (!snapshot.hasData) { return Container( - constraints: BoxConstraints.expand(), + constraints: const BoxConstraints.expand(), child: widget.placeholderBuilder?.call(context) ?? Shimmer.fromColors( baseColor: StreamChatTheme.of(context) @@ -97,8 +107,6 @@ class _VideoThumbnailImageState extends State { ); }, ), - ); - }, + ), ); - } } From ad86cf0cd698d2be4a990ddf34cb782b2b34d5f8 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 17:49:49 +0530 Subject: [PATCH 211/599] lint --- .../lib/src/user_reaction_display.dart | 61 +++--- .../stream_chat_flutter/lib/src/utils.dart | 175 +++++++++--------- .../lib/src/video_thumbnail_image.dart | 13 +- 3 files changed, 126 insertions(+), 123 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/user_reaction_display.dart b/packages/stream_chat_flutter/lib/src/user_reaction_display.dart index 23c723dd..b5ef623e 100644 --- a/packages/stream_chat_flutter/lib/src/user_reaction_display.dart +++ b/packages/stream_chat_flutter/lib/src/user_reaction_display.dart @@ -4,7 +4,6 @@ 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, @@ -24,38 +23,38 @@ class UserReactionDisplay extends StatelessWidget { @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()?); + 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, + ); + } - 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, + icon: UserAvatar( + user: firstUserReaction.user!, + constraints: BoxConstraints( + maxHeight: size - 5, + maxWidth: size - 5, + ), + onTap: (user) {}, ), - onTap: (user) {}, - ), - onPressed: () {}, - ); - }).toList(), - ), - ); + onPressed: () {}, + ); + }).toList(), + ), + ); } diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 11a1f3c0..8f493cec 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -27,88 +27,89 @@ Future showConfirmationDialog( Widget? icon, String? question, String? cancelText, -}) => showModalBottomSheet( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - )), - builder: (context) { - final effect = StreamChatTheme.of(context).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: StreamChatTheme.of(context).textTheme.headlineBold, - ), - const SizedBox(height: 7), - if (question != null) +}) => + showModalBottomSheet( + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + )), + builder: (context) { + final effect = StreamChatTheme.of(context).colorTheme.borderTop; + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 26), + if (icon != null) icon, + const SizedBox(height: 26), Text( - question, - textAlign: TextAlign.center, + title, + style: StreamChatTheme.of(context).textTheme.headlineBold, ), - const SizedBox(height: 36), - Container( - color: effect.color!.withOpacity(effect.alpha ?? 1), - height: 1, - ), - Row( - children: [ - if (cancelText != null) + 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: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), + ), + ), + ), + ), Flexible( child: Container( alignment: Alignment.center, child: TextButton( onPressed: () { - Navigator.of(context).pop(false); + Navigator.pop(context, true); }, child: Text( - cancelText, + okText, style: StreamChatTheme.of(context) .textTheme .bodyBold .copyWith( color: StreamChatTheme.of(context) .colorTheme - .black - .withOpacity(0.5)), + .accentRed), ), ), ), ), - Flexible( - child: Container( - alignment: Alignment.center, - child: TextButton( - onPressed: () { - Navigator.pop(context, true); - }, - child: Text( - okText, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentRed), - ), - ), - ), - ), - ], - ), - ], - ), - ); - }); + ], + ), + ], + ), + ); + }); /// Shows info dialog Future showInfoDialog( @@ -118,16 +119,17 @@ Future showInfoDialog( Widget? icon, String? details, StreamChatThemeData? theme, -}) => showModalBottomSheet( - backgroundColor: - theme?.colorTheme.white ?? StreamChatTheme.of(context).colorTheme.white, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - )), - builder: (context) => SafeArea( +}) => + showModalBottomSheet( + backgroundColor: theme?.colorTheme.white ?? + StreamChatTheme.of(context).colorTheme.white, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + )), + builder: (context) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -173,7 +175,7 @@ Future showInfoDialog( ], ), ), - ); + ); /// Get random png with initials String getRandomPicUrl(User user) => @@ -338,16 +340,17 @@ Widget wrapAttachmentWidget( // ignore: avoid_positional_boolean_parameters bool reverse, BorderRadius borderRadius, -) => ClipRRect( - borderRadius: borderRadius, - child: Material( - clipBehavior: Clip.antiAlias, - shape: attachmentShape, - type: MaterialType.transparency, - child: Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: attachmentWidget, +) => + ClipRRect( + borderRadius: borderRadius, + child: Material( + clipBehavior: Clip.antiAlias, + shape: attachmentShape, + type: MaterialType.transparency, + child: Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: attachmentWidget, + ), ), - ), - ); + ); diff --git a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart index cd321ca2..8cafbbff 100644 --- a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart +++ b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart @@ -4,10 +4,9 @@ import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; +import 'package:stream_chat_flutter/src/video_service.dart'; -import 'stream_svg_icon.dart'; -import 'video_service.dart'; - +/// Widget for creating video thumbnail image class VideoThumbnailImage extends StatefulWidget { /// Constructor for creating [VideoThumbnailImage] const VideoThumbnailImage({ @@ -38,6 +37,8 @@ class VideoThumbnailImage extends StatefulWidget { /// Builds widget on error final Widget Function(BuildContext, Object?)? errorBuilder; + + /// Builds placeholder final WidgetBuilder? placeholderBuilder; @override @@ -69,8 +70,8 @@ class _VideoThumbnailImageState extends State { @override Widget build(BuildContext context) => FutureBuilder( - future: thumbnailFuture, - builder: (context, snapshot) => AnimatedSwitcher( + future: thumbnailFuture, + builder: (context, snapshot) => AnimatedSwitcher( duration: const Duration(milliseconds: 350), child: Builder( key: ValueKey>(snapshot), @@ -108,5 +109,5 @@ class _VideoThumbnailImageState extends State { }, ), ), - ); + ); } From f5daa8f49ce5f198be7e97f6a2b19ec0501706d9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 17:58:50 +0530 Subject: [PATCH 212/599] lint test --- .../src/attachment_actions_modal_test.dart | 74 +++++------ .../test/src/attachment_widgets_test.dart | 6 +- .../test/src/back_button_test.dart | 16 +-- .../test/src/channel_header_test.dart | 10 +- .../test/src/channel_image_test.dart | 10 +- .../test/src/channel_list_header_test.dart | 16 +-- .../test/src/channel_name_test.dart | 2 +- .../test/src/deleted_message_test.dart | 8 +- .../test/src/info_tile_test.dart | 8 +- .../test/src/message_action_modal_test.dart | 125 ++++++++---------- .../test/src/message_input_test.dart | 4 +- .../src/message_reactions_modal_test.dart | 13 +- .../stream_chat_flutter/test/src/mocks.dart | 1 + .../test/src/reaction_bubble_test.dart | 2 +- 14 files changed, 129 insertions(+), 166 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart index 53516e9a..a1eef962 100644 --- a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart @@ -128,7 +128,7 @@ void main() { home: StreamChat( streamChatThemeData: streamTheme, client: client, - child: Container( + child: SizedBox( child: AttachmentActionsModal( message: Message( text: 'test', @@ -185,7 +185,7 @@ void main() { home: StreamChat( streamChatThemeData: streamTheme, client: client, - child: Container( + child: SizedBox( child: AttachmentActionsModal( message: message, currentIndex: 0, @@ -218,7 +218,7 @@ void main() { home: StreamChat( streamChatThemeData: streamTheme, client: client, - child: Container( + child: SizedBox( child: AttachmentActionsModal( onShowMessage: onShowMessage, message: Message( @@ -250,9 +250,8 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(() => mockChannel.updateMessage(any())).thenAnswer((_) async { - return UpdateMessageResponse(); - }); + when(() => mockChannel.updateMessage(any())) + .thenAnswer((_) async => UpdateMessageResponse()); when(() => client.state).thenReturn(clientState); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); @@ -275,12 +274,10 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( - client: client, - child: child!, - ); - }, + builder: (context, child) => StreamChat( + client: client, + child: child, + ), home: StreamChannel( showLoading: false, channel: mockChannel, @@ -307,9 +304,8 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(() => mockChannel.updateMessage(any())).thenAnswer((_) async { - return UpdateMessageResponse(); - }); + when(() => mockChannel.updateMessage(any())) + .thenAnswer((_) async => UpdateMessageResponse()); when(() => client.state).thenReturn(clientState); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); @@ -328,12 +324,10 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( - client: client, - child: child!, - ); - }, + builder: (context, child) => StreamChat( + client: client, + child: child, + ), home: StreamChannel( showLoading: false, channel: mockChannel, @@ -352,15 +346,15 @@ void main() { ); testWidgets( + // ignore: lines_longer_than_80_chars 'tapping on delete in chat should remove the message if that\'s the only attachment and there is no text', (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); final mockChannel = MockChannel(); - when(() => mockChannel.deleteMessage(any())).thenAnswer((_) async { - return EmptyResponse(); - }); + when(() => mockChannel.deleteMessage(any())) + .thenAnswer((_) async => EmptyResponse()); when(() => client.state).thenReturn(clientState); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); @@ -378,12 +372,10 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, - child: child!, - ); - }, + child: child, + ), home: StreamChannel( showLoading: false, channel: mockChannel, @@ -412,13 +404,11 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, - child: child!, - ); - }, - home: Container( + child: child, + ), + home: SizedBox( child: AttachmentActionsModal( imageDownloader: imageDownloader, message: Message( @@ -451,8 +441,8 @@ void main() { imageDownloader.progressCallback!(100, 100); imageDownloader.completer.complete('path'); await tester.pump(); - expect(find.byKey(Key('completedIcon')), findsOneWidget); - await tester.pumpAndSettle(Duration(milliseconds: 500)); + expect(find.byKey(const Key('completedIcon')), findsOneWidget); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); }, ); @@ -469,12 +459,10 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, child: child!, - ); - }, + ), home: Container( child: AttachmentActionsModal( fileDownloader: fileDownloader, @@ -508,8 +496,8 @@ void main() { fileDownloader.progressCallback!(100, 100); fileDownloader.completer.complete('path'); await tester.pump(); - expect(find.byKey(Key('completedIcon')), findsOneWidget); - await tester.pumpAndSettle(Duration(milliseconds: 500)); + expect(find.byKey(const Key('completedIcon')), findsOneWidget); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); }, ); } diff --git a/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart b/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart index e8dcdd53..ed4ce267 100644 --- a/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart @@ -23,9 +23,9 @@ void main() { data: streamTheme, child: StreamChannel( channel: channel, - child: Container( + child: SizedBox( child: FileAttachment( - size: Size( + size: const Size( 300, 300, ), @@ -33,7 +33,7 @@ void main() { attachment: Attachment( type: 'file', title: 'example.pdf', - extraData: { + extraData: const { 'mime_type': 'pdf', }, ), diff --git a/packages/stream_chat_flutter/test/src/back_button_test.dart b/packages/stream_chat_flutter/test/src/back_button_test.dart index a9c5f724..dc05867f 100644 --- a/packages/stream_chat_flutter/test/src/back_button_test.dart +++ b/packages/stream_chat_flutter/test/src/back_button_test.dart @@ -15,16 +15,14 @@ void main() { MaterialApp( home: const Material(child: Text('Home')), routes: { - '/next': (BuildContext context) { - return Material( + '/next': (BuildContext context) => Material( child: Center( child: StreamChatTheme( data: StreamChatThemeData.fromTheme(theme), child: StreamBackButton(), ), ), - ); - }, + ), }, ), ); @@ -52,7 +50,7 @@ void main() { child: Center( child: StreamChatTheme( data: StreamChatThemeData.fromTheme(theme), - child: StreamBackButton(), + child: const StreamBackButton(), ), ), ), @@ -78,8 +76,7 @@ void main() { MaterialApp( home: const Material(child: Text('Home')), routes: { - '/next': (BuildContext context) { - return Material( + '/next': (BuildContext context) => Material( child: Center( child: StreamChatTheme( data: StreamChatThemeData.fromTheme(theme), @@ -88,8 +85,7 @@ void main() { ), ), ), - ); - }, + ), }, ), ); @@ -131,7 +127,7 @@ void main() { child: Center( child: StreamChat( client: client, - child: StreamBackButton( + child: const StreamBackButton( showUnreads: true, ), ), diff --git a/packages/stream_chat_flutter/test/src/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel_header_test.dart index 4e99daf5..6da3e20f 100644 --- a/packages/stream_chat_flutter/test/src/channel_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_header_test.dart @@ -54,7 +54,7 @@ void main() { client: client, child: StreamChannel( channel: channel, - child: Scaffold( + child: const Scaffold( body: ChannelHeader(), ), ), @@ -115,7 +115,7 @@ void main() { client: client, child: StreamChannel( channel: channel, - child: Scaffold( + child: const Scaffold( body: ChannelHeader( showConnectionStateTile: true, ), @@ -178,7 +178,7 @@ void main() { child: StreamChannel( channel: channel, showLoading: false, - child: Scaffold( + child: const Scaffold( body: ChannelHeader( showConnectionStateTile: true, ), @@ -242,7 +242,7 @@ void main() { client: client, child: StreamChannel( channel: channel, - child: Scaffold( + child: const Scaffold( body: ChannelHeader( leading: Text('leading'), subtitle: Text('subtitle'), @@ -314,7 +314,7 @@ void main() { client: client, child: StreamChannel( channel: channel, - child: Scaffold( + child: const Scaffold( body: ChannelHeader( showTypingIndicator: false, showBackButton: false, diff --git a/packages/stream_chat_flutter/test/src/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel_image_test.dart index 1068bb52..ba913a77 100644 --- a/packages/stream_chat_flutter/test/src/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_image_test.dart @@ -34,7 +34,7 @@ void main() { client: client, child: StreamChannel( channel: channel, - child: Scaffold( + child: const Scaffold( body: ChannelImage(), ), ), @@ -112,7 +112,7 @@ void main() { client: client, child: StreamChannel( channel: channel, - child: Scaffold( + child: const Scaffold( body: ChannelImage(), ), ), @@ -207,7 +207,7 @@ void main() { client: client, child: StreamChannel( channel: channel, - child: Scaffold( + child: const Scaffold( body: ChannelImage(), ), ), @@ -248,7 +248,7 @@ void main() { client: client, child: StreamChannel( channel: channel, - child: Scaffold( + child: const Scaffold( body: ChannelImage( selected: true, ), @@ -257,7 +257,7 @@ void main() { ), )); - expect(find.byKey(Key('selectedImage')), findsOneWidget); + expect(find.byKey(const Key('selectedImage')), findsOneWidget); }, ); } diff --git a/packages/stream_chat_flutter/test/src/channel_list_header_test.dart b/packages/stream_chat_flutter/test/src/channel_list_header_test.dart index f77ea04a..f2f054d4 100644 --- a/packages/stream_chat_flutter/test/src/channel_list_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_list_header_test.dart @@ -21,7 +21,7 @@ void main() { MaterialApp( home: StreamChat( client: client, - child: Scaffold( + child: const Scaffold( body: ChannelListHeader(), ), ), @@ -51,7 +51,7 @@ void main() { MaterialApp( home: StreamChat( client: client, - child: Scaffold( + child: const Scaffold( body: ChannelListHeader( showConnectionStateTile: true, ), @@ -80,7 +80,7 @@ void main() { MaterialApp( home: StreamChat( client: client, - child: Scaffold( + child: const Scaffold( body: ChannelListHeader( showConnectionStateTile: true, ), @@ -111,12 +111,10 @@ void main() { client: client, child: Scaffold( body: ChannelListHeader( - titleBuilder: (context, status, client) { - return Text('TITLE'); - }, - subtitle: Text('SUBTITLE'), - leading: Text('LEADING'), - actions: [ + titleBuilder: (context, status, client) => const Text('TITLE'), + subtitle: const Text('SUBTITLE'), + leading: const Text('LEADING'), + actions: const [ Text('ACTION'), ], client: client, diff --git a/packages/stream_chat_flutter/test/src/channel_name_test.dart b/packages/stream_chat_flutter/test/src/channel_name_test.dart index 586a0776..c8d3df9b 100644 --- a/packages/stream_chat_flutter/test/src/channel_name_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_name_test.dart @@ -61,7 +61,7 @@ void main() { client: client, child: StreamChannel( channel: channel, - child: Scaffold( + child: const Scaffold( body: ChannelName(), ), ), diff --git a/packages/stream_chat_flutter/test/src/deleted_message_test.dart b/packages/stream_chat_flutter/test/src/deleted_message_test.dart index 854c2492..bcf9cf62 100644 --- a/packages/stream_chat_flutter/test/src/deleted_message_test.dart +++ b/packages/stream_chat_flutter/test/src/deleted_message_test.dart @@ -19,7 +19,7 @@ void main() { await tester.pumpWidget(MaterialApp( home: StreamChat( client: client, - child: Scaffold( + child: const Scaffold( body: DeletedMessage( messageTheme: MessageTheme( createdAt: TextStyle( @@ -81,7 +81,7 @@ void main() { ), ), ), - surfaceSize: Size.square(200), + surfaceSize: const Size.square(200), ); await screenMatchesGolden(tester, 'deleted_message_light'); @@ -133,7 +133,7 @@ void main() { ), ), ), - surfaceSize: Size.square(200), + surfaceSize: const Size.square(200), ); await screenMatchesGolden(tester, 'deleted_message_dark'); @@ -189,7 +189,7 @@ void main() { ), ), ), - surfaceSize: Size.square(200), + surfaceSize: const Size.square(200), ); await screenMatchesGolden(tester, 'deleted_message_custom'); diff --git a/packages/stream_chat_flutter/test/src/info_tile_test.dart b/packages/stream_chat_flutter/test/src/info_tile_test.dart index 091c64c7..02387edc 100644 --- a/packages/stream_chat_flutter/test/src/info_tile_test.dart +++ b/packages/stream_chat_flutter/test/src/info_tile_test.dart @@ -19,9 +19,9 @@ void main() { await tester.pumpWidget(MaterialApp( home: StreamChat( client: client, - child: Scaffold( + child: const Scaffold( body: Portal( - child: Container( + child: SizedBox( child: InfoTile( showMessage: true, message: 'message', @@ -49,9 +49,9 @@ void main() { await tester.pumpWidget(MaterialApp( home: StreamChat( client: client, - child: Scaffold( + child: const Scaffold( body: Portal( - child: Container( + child: SizedBox( child: InfoTile( showMessage: false, message: 'message', diff --git a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart index 0be509ae..837065ac 100644 --- a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart @@ -8,7 +8,8 @@ import 'mocks.dart'; void main() { setUpAll(() { - registerFallbackValue(MaterialPageRoute(builder: (context) => SizedBox())); + registerFallbackValue( + MaterialPageRoute(builder: (context) => const SizedBox())); registerFallbackValue(Message()); }); @@ -29,7 +30,7 @@ void main() { home: StreamChat( streamChatThemeData: streamTheme, client: client, - child: Container( + child: SizedBox( child: MessageActionsModal( message: Message( text: 'test', @@ -45,7 +46,7 @@ void main() { ); await tester.pumpAndSettle(); - expect(find.byKey(Key('MessageWidget')), findsOneWidget); + expect(find.byKey(const Key('MessageWidget')), findsOneWidget); expect(find.text('Thread Reply'), findsOneWidget); expect(find.text('Reply'), findsOneWidget); expect(find.text('Edit Message'), findsOneWidget); @@ -71,7 +72,7 @@ void main() { home: StreamChat( streamChatThemeData: streamTheme, client: client, - child: Container( + child: SizedBox( child: MessageActionsModal( showEditMessage: false, showCopyMessage: false, @@ -92,7 +93,7 @@ void main() { ); await tester.pumpAndSettle(); - expect(find.byKey(Key('MessageWidget')), findsOneWidget); + expect(find.byKey(const Key('MessageWidget')), findsOneWidget); expect(find.text('Reply'), findsNothing); expect(find.text('Thread reply'), findsNothing); expect(find.text('Edit message'), findsNothing); @@ -120,7 +121,7 @@ void main() { home: StreamChat( streamChatThemeData: streamTheme, client: client, - child: Container( + child: SizedBox( child: MessageActionsModal( message: Message( text: 'test', @@ -131,8 +132,8 @@ void main() { messageTheme: streamTheme.ownMessageTheme, customActions: [ MessageAction( - leading: Icon(Icons.check), - title: Text('title'), + leading: const Icon(Icons.check), + title: const Text('title'), onTap: (m) { tapped = true; }, @@ -175,7 +176,7 @@ void main() { home: StreamChat( streamChatThemeData: streamTheme, client: client, - child: Container( + child: SizedBox( child: MessageActionsModal( onReplyTap: (m) { tapped = true; @@ -220,7 +221,7 @@ void main() { home: StreamChat( streamChatThemeData: streamTheme, client: client, - child: Container( + child: SizedBox( child: MessageActionsModal( onThreadReplyTap: (m) { tapped = true; @@ -260,18 +261,16 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, streamChatThemeData: streamTheme, - child: child!, - ); - }, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, channel: channel, - child: Container( + child: SizedBox( child: MessageActionsModal( message: Message( text: 'test', @@ -310,22 +309,18 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, streamChatThemeData: streamTheme, - child: child!, - ); - }, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, channel: channel, - child: Container( + child: SizedBox( child: MessageActionsModal( - editMessageInputBuilder: (context, m) { - return Text('test'); - }, + editMessageInputBuilder: (context, m) => const Text('test'), message: Message( text: 'test', user: User( @@ -365,18 +360,16 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, streamChatThemeData: streamTheme, - child: child!, - ); - }, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, channel: channel, - child: Container( + child: SizedBox( child: MessageActionsModal( onCopyTap: (m) => tapped = true, message: Message( @@ -416,18 +409,16 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, streamChatThemeData: streamTheme, - child: child!, - ); - }, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, channel: channel, - child: Container( + child: SizedBox( child: MessageActionsModal( message: Message( status: MessageSendingStatus.failed, @@ -467,18 +458,16 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, streamChatThemeData: streamTheme, - child: child!, - ); - }, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, channel: channel, - child: Container( + child: SizedBox( child: MessageActionsModal( message: Message( status: MessageSendingStatus.failed_update, @@ -516,18 +505,16 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, streamChatThemeData: streamTheme, - child: child!, - ); - }, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, channel: channel, - child: Container( + child: SizedBox( child: MessageActionsModal( message: Message( id: 'testid', @@ -575,18 +562,16 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, streamChatThemeData: streamTheme, - child: child!, - ); - }, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, channel: channel, - child: Container( + child: SizedBox( child: MessageActionsModal( message: Message( id: 'testid', @@ -634,18 +619,16 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, streamChatThemeData: streamTheme, - child: child!, - ); - }, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, channel: channel, - child: Container( + child: SizedBox( child: MessageActionsModal( message: Message( id: 'testid', @@ -689,18 +672,16 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, streamChatThemeData: streamTheme, - child: child!, - ); - }, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, channel: channel, - child: Container( + child: SizedBox( child: MessageActionsModal( message: Message( id: 'testid', @@ -748,18 +729,16 @@ void main() { await tester.pumpWidget( MaterialApp( - builder: (context, child) { - return StreamChat( + builder: (context, child) => StreamChat( client: client, streamChatThemeData: streamTheme, - child: child!, - ); - }, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, channel: channel, - child: Container( + child: SizedBox( child: MessageActionsModal( message: Message( id: 'testid', diff --git a/packages/stream_chat_flutter/test/src/message_input_test.dart b/packages/stream_chat_flutter/test/src/message_input_test.dart index 39e7ae3f..63ddb10f 100644 --- a/packages/stream_chat_flutter/test/src/message_input_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input_test.dart @@ -67,7 +67,7 @@ void main() { client: client, child: StreamChannel( channel: channel, - child: Scaffold( + child: const Scaffold( body: MessageInput(), ), ), @@ -76,7 +76,7 @@ void main() { expect(find.byType(TextField), findsOneWidget); expect(find.byType(StreamSvgIcon), findsNWidgets(8)); - expect(find.byKey(Key('messageInputText')), findsOneWidget); + expect(find.byKey(const Key('messageInputText')), findsOneWidget); }, ); } diff --git a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart index e1136ac5..6bdb72b0 100644 --- a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart @@ -41,7 +41,7 @@ void main() { ), ); - await tester.pump(Duration(milliseconds: 1000)); + await tester.pump(const Duration(milliseconds: 1000)); expect(find.byType(MessageWidget), findsOneWidget); final messageWidget = @@ -95,6 +95,7 @@ void main() { ], ); + // ignore: prefer_function_declarations_over_variables final onUserAvatarTap = (u) => print('ok'); await tester.pumpWidget( @@ -109,16 +110,16 @@ void main() { showUserAvatar: DisplayWidget.gone, reverse: true, attachmentBorderRadiusGeometry: BorderRadius.circular(1), - attachmentShape: RoundedRectangleBorder(), + attachmentShape: const RoundedRectangleBorder(), showReactions: false, - messageShape: RoundedRectangleBorder(), + messageShape: const RoundedRectangleBorder(), onUserAvatarTap: onUserAvatarTap, ), ), ), ); - await tester.pump(Duration(milliseconds: 1000)); + await tester.pump(const Duration(milliseconds: 1000)); expect(find.byType(MessageWidget), findsOneWidget); final messageWidget = @@ -130,8 +131,8 @@ void main() { expect(messageWidget.showUserAvatar, DisplayWidget.gone); expect(messageWidget.reverse, true); expect(messageWidget.showReactions, false); - expect(messageWidget.attachmentShape, RoundedRectangleBorder()); - expect(messageWidget.shape, RoundedRectangleBorder()); + expect(messageWidget.attachmentShape, const RoundedRectangleBorder()); + expect(messageWidget.shape, const RoundedRectangleBorder()); expect(messageWidget.showReactions, false); expect(messageWidget.showUsername, false); expect(messageWidget.showThreadReplyIndicator, false); diff --git a/packages/stream_chat_flutter/test/src/mocks.dart b/packages/stream_chat_flutter/test/src/mocks.dart index 842c1c2f..083c2ff5 100644 --- a/packages/stream_chat_flutter/test/src/mocks.dart +++ b/packages/stream_chat_flutter/test/src/mocks.dart @@ -11,6 +11,7 @@ class MockChannel extends Mock implements Channel { Future get initialized async => true; @override + // ignore: prefer_expression_function_bodies Future keyStroke([String? parentId]) async { return; } diff --git a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart index 10e0f45c..8d575746 100644 --- a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart +++ b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart @@ -16,7 +16,7 @@ void main() { SimpleFrame( child: StreamChatTheme( data: StreamChatThemeData(), - child: Container( + child: const SizedBox( child: ReactionBubble( reactions: [], borderColor: Colors.black, From 9b09874df17dafaa6088c32387024ecd4cf07db7 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 17:59:19 +0530 Subject: [PATCH 213/599] lint test --- .../src/attachment_actions_modal_test.dart | 18 ++--- .../test/src/back_button_test.dart | 22 ++--- .../test/src/message_action_modal_test.dart | 80 +++++++++---------- 3 files changed, 60 insertions(+), 60 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart index a1eef962..51ca5c57 100644 --- a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart @@ -373,9 +373,9 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - child: child, - ), + client: client, + child: child, + ), home: StreamChannel( showLoading: false, channel: mockChannel, @@ -405,9 +405,9 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - child: child, - ), + client: client, + child: child, + ), home: SizedBox( child: AttachmentActionsModal( imageDownloader: imageDownloader, @@ -460,9 +460,9 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - child: child!, - ), + client: client, + child: child!, + ), home: Container( child: AttachmentActionsModal( fileDownloader: fileDownloader, diff --git a/packages/stream_chat_flutter/test/src/back_button_test.dart b/packages/stream_chat_flutter/test/src/back_button_test.dart index dc05867f..b991a01e 100644 --- a/packages/stream_chat_flutter/test/src/back_button_test.dart +++ b/packages/stream_chat_flutter/test/src/back_button_test.dart @@ -16,13 +16,13 @@ void main() { home: const Material(child: Text('Home')), routes: { '/next': (BuildContext context) => Material( - child: Center( - child: StreamChatTheme( - data: StreamChatThemeData.fromTheme(theme), - child: StreamBackButton(), + child: Center( + child: StreamChatTheme( + data: StreamChatThemeData.fromTheme(theme), + child: StreamBackButton(), + ), ), ), - ), }, ), ); @@ -77,15 +77,15 @@ void main() { home: const Material(child: Text('Home')), routes: { '/next': (BuildContext context) => Material( - child: Center( - child: StreamChatTheme( - data: StreamChatThemeData.fromTheme(theme), - child: StreamBackButton( - onPressed: () => customCallbackWasCalled = true, + child: Center( + child: StreamChatTheme( + data: StreamChatThemeData.fromTheme(theme), + child: StreamBackButton( + onPressed: () => customCallbackWasCalled = true, + ), ), ), ), - ), }, ), ); diff --git a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart index 837065ac..6118e7d2 100644 --- a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart @@ -262,10 +262,10 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - streamChatThemeData: streamTheme, - child: child, - ), + client: client, + streamChatThemeData: streamTheme, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, @@ -310,10 +310,10 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - streamChatThemeData: streamTheme, - child: child, - ), + client: client, + streamChatThemeData: streamTheme, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, @@ -361,10 +361,10 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - streamChatThemeData: streamTheme, - child: child, - ), + client: client, + streamChatThemeData: streamTheme, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, @@ -410,10 +410,10 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - streamChatThemeData: streamTheme, - child: child, - ), + client: client, + streamChatThemeData: streamTheme, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, @@ -459,10 +459,10 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - streamChatThemeData: streamTheme, - child: child, - ), + client: client, + streamChatThemeData: streamTheme, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, @@ -506,10 +506,10 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - streamChatThemeData: streamTheme, - child: child, - ), + client: client, + streamChatThemeData: streamTheme, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, @@ -563,10 +563,10 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - streamChatThemeData: streamTheme, - child: child, - ), + client: client, + streamChatThemeData: streamTheme, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, @@ -620,10 +620,10 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - streamChatThemeData: streamTheme, - child: child, - ), + client: client, + streamChatThemeData: streamTheme, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, @@ -673,10 +673,10 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - streamChatThemeData: streamTheme, - child: child, - ), + client: client, + streamChatThemeData: streamTheme, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, @@ -730,10 +730,10 @@ void main() { await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( - client: client, - streamChatThemeData: streamTheme, - child: child, - ), + client: client, + streamChatThemeData: streamTheme, + child: child, + ), theme: themeData, home: StreamChannel( showLoading: false, From 0590fe1a76925c80b0af13bb44729fd8930dee5e Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 5 May 2021 19:07:32 +0530 Subject: [PATCH 214/599] lint --- .../lib/src/attachment/attachment_widget.dart | 1 + .../lib/src/attachment/file_attachment.dart | 4 +- .../lib/src/attachment_actions_modal.dart | 42 +++++++++---------- .../lib/src/channel_image.dart | 2 +- .../lib/src/channel_info.dart | 4 +- .../lib/src/mention_tile.dart | 2 +- .../lib/src/message_text.dart | 2 - .../lib/src/user_list_view.dart | 2 - .../stream_chat_flutter/lib/src/utils.dart | 6 +-- .../lib/src/video_service.dart | 2 +- packages/stream_chat_flutter/pubspec.yaml | 6 +-- .../src/attachment_actions_modal_test.dart | 7 ++-- .../test/src/back_button_test.dart | 2 +- .../test/src/reaction_bubble_test.dart | 16 +++---- .../test/src/simple_frame.dart | 22 +++++----- .../test/src/system_message_test.dart | 4 +- .../test/src/thread_header_test.dart | 8 ++-- .../test/src/typing_indicator_test.dart | 4 +- .../test/src/unread_indicator_test.dart | 2 +- 19 files changed, 68 insertions(+), 70 deletions(-) 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 74769057..ee79d959 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart @@ -67,6 +67,7 @@ class AttachmentError extends StatelessWidget { this.size, }) : super(key: key); + /// Size of error final Size? size; @override 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 c68d72f9..63bcdd9b 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -6,10 +6,12 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_chat_flutter/src/upload_progress_indicator.dart'; -import '../upload_progress_indicator.dart'; +// ignore: always_use_package_imports import 'attachment_widget.dart'; +/// Widget for displaying file attachments class FileAttachment extends AttachmentWidget { /// Constructor for creating a widget when attachment is of type 'file' const FileAttachment({ 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 a9ea651b..363fac45 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart @@ -5,9 +5,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:path_provider/path_provider.dart'; - -import '../stream_chat_flutter.dart'; -import 'extension.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Callback to download an attachment asset typedef AttachmentDownloader = Future Function( @@ -17,6 +16,16 @@ typedef AttachmentDownloader = Future Function( /// Widget that shows the options in the gallery view class AttachmentActionsModal extends StatelessWidget { + /// Returns a new [AttachmentActionsModal] + const AttachmentActionsModal({ + Key? key, + required this.currentIndex, + required this.message, + this.onShowMessage, + this.imageDownloader, + this.fileDownloader, + }) : super(key: key); + /// The message containing the attachments final Message message; @@ -32,15 +41,6 @@ class AttachmentActionsModal extends StatelessWidget { /// Callback to provide download files final AttachmentDownloader? fileDownloader; - /// Returns a new [AttachmentActionsModal] - const AttachmentActionsModal({ - required this.currentIndex, - required this.message, - this.onShowMessage, - this.imageDownloader, - this.fileDownloader, - }); - @override Widget build(BuildContext context) => GestureDetector( behavior: HitTestBehavior.translucent, @@ -62,7 +62,7 @@ class AttachmentActionsModal extends StatelessWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), ), - child: Container( + child: SizedBox( child: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, @@ -89,6 +89,7 @@ class AttachmentActionsModal extends StatelessWidget { ), _buildButton( context, + // ignore: lines_longer_than_80_chars 'Save ${message.attachments[currentIndex].type == 'video' ? 'Video' : 'Image'}', StreamSvgIcon.iconSave( size: 24, @@ -203,7 +204,7 @@ class AttachmentActionsModal extends StatelessWidget { child: Row( children: [ icon, - SizedBox(width: 16), + const SizedBox(width: 16), Text( title, style: StreamChatTheme.of(context) @@ -253,7 +254,7 @@ class AttachmentActionsModal extends StatelessWidget { ) : progress.toProgressIndicatorValue == 1.0 ? SizedBox( - key: Key('completedIcon'), + key: const Key('completedIcon'), height: 160, width: 160, child: StreamSvgIcon.check( @@ -314,14 +315,13 @@ class AttachmentActionsModal extends StatelessWidget { } class _DownloadProgress { - final int total; - final int received; - const _DownloadProgress(this.total, this.received); - factory _DownloadProgress.initial() { - return _DownloadProgress(double.maxFinite.toInt(), 0); - } + factory _DownloadProgress.initial() => + _DownloadProgress(double.maxFinite.toInt(), 0); + + final int total; + final int received; double get toProgressIndicatorValue => received / total; diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 8116ca83..80e32f07 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -196,7 +196,7 @@ class ChannelImage extends StatelessWidget { ); if (selected) { child = ClipRRect( - key: Key('selectedImage'), + key: const Key('selectedImage'), borderRadius: (borderRadius ?? StreamChatTheme.of(context) .ownMessageTheme diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index 72f6cf18..5fa6f704 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -3,9 +3,9 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'connection_status_builder.dart'; - +/// Widget which shows channel info class ChannelInfo extends StatelessWidget { + /// Constructor which creates a [ChannelInfo] widget const ChannelInfo({ Key? key, required this.channel, diff --git a/packages/stream_chat_flutter/lib/src/mention_tile.dart b/packages/stream_chat_flutter/lib/src/mention_tile.dart index e8859946..f3bed355 100644 --- a/packages/stream_chat_flutter/lib/src/mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/mention_tile.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../stream_chat_flutter.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// This widget is used for showing user tiles for mentions /// Use [title], [subtitle], [leading], [trailing] for diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index 8c95120d..24a32a09 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -4,8 +4,6 @@ import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import '../stream_chat_flutter.dart'; - /// Text widget to display in message class MessageText extends StatelessWidget { /// Constructor for creating a [MessageText] widget 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 0c835fbe..4d5d202a 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -2,8 +2,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'user_item.dart'; - /// Callback called when tapping on a user typedef UserTapCallback = void Function(User, Widget?); diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 8f493cec..cdd87e2d 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -3,10 +3,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import '../stream_chat_flutter.dart'; -import 'stream_svg_icon.dart'; - +/// Launch URL Future launchURL(BuildContext context, String? url) async { if (url != null && await canLaunch(url)) { await launch(url); @@ -20,6 +19,7 @@ Future launchURL(BuildContext context, String? url) async { } } +/// Shows confirmation dialog Future showConfirmationDialog( BuildContext context, { required String title, diff --git a/packages/stream_chat_flutter/lib/src/video_service.dart b/packages/stream_chat_flutter/lib/src/video_service.dart index 3b6bb13e..ba4761a2 100644 --- a/packages/stream_chat_flutter/lib/src/video_service.dart +++ b/packages/stream_chat_flutter/lib/src/video_service.dart @@ -60,6 +60,6 @@ class IVideoService { ); } -// ignore: non_constant_identifier_names /// Get instance of [IVideoService] +// ignore: non_constant_identifier_names IVideoService get VideoService => IVideoService.instance; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 7a2725ed..2ef87189 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -12,10 +12,10 @@ dependencies: flutter: sdk: flutter stream_chat_flutter_core: ^2.0.0-nullsafety.0 - photo_view: ^0.11.1 rxdart: ^0.26.0 scrollable_positioned_list: ^0.2.0-nullsafety.0 jiffy: ^4.1.0 + photo_view: ^0.11.1 flutter_svg: ^0.22.0 flutter_portal: ^0.4.0 cached_network_image: ^3.0.0 @@ -57,6 +57,6 @@ dev_dependencies: flutter_test: sdk: flutter mocktail: ^0.1.2 - pedantic: ^1.11.0 golden_toolkit: ^0.9.0 - + pedantic: ^1.11.0 + diff --git a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart index 51ca5c57..3f210742 100644 --- a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart @@ -23,7 +23,8 @@ class MockAttachmentDownloader extends Mock { void main() { setUpAll(() { - registerFallbackValue(MaterialPageRoute(builder: (context) => SizedBox())); + registerFallbackValue( + MaterialPageRoute(builder: (context) => const SizedBox())); registerFallbackValue(Message()); }); @@ -461,9 +462,9 @@ void main() { MaterialApp( builder: (context, child) => StreamChat( client: client, - child: child!, + child: child, ), - home: Container( + home: SizedBox( child: AttachmentActionsModal( fileDownloader: fileDownloader, message: Message( diff --git a/packages/stream_chat_flutter/test/src/back_button_test.dart b/packages/stream_chat_flutter/test/src/back_button_test.dart index b991a01e..3fae1ace 100644 --- a/packages/stream_chat_flutter/test/src/back_button_test.dart +++ b/packages/stream_chat_flutter/test/src/back_button_test.dart @@ -19,7 +19,7 @@ void main() { child: Center( child: StreamChatTheme( data: StreamChatThemeData.fromTheme(theme), - child: StreamBackButton(), + child: const StreamBackButton(), ), ), ), diff --git a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart index 8d575746..770a040b 100644 --- a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart +++ b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart @@ -26,7 +26,7 @@ void main() { ), ), ), - surfaceSize: Size(100, 100), + surfaceSize: const Size(100, 100), ); await screenMatchesGolden(tester, 'reaction_bubble_0'); }, @@ -47,7 +47,7 @@ void main() { StreamChat( client: client, streamChatThemeData: theme, - child: Container( + child: SizedBox( child: ReactionBubble( reactions: [ Reaction( @@ -61,7 +61,7 @@ void main() { ), ), ), - surfaceSize: Size(100, 100), + surfaceSize: const Size(100, 100), ); await screenMatchesGolden(tester, 'reaction_bubble_like_light'); }, @@ -97,7 +97,7 @@ void main() { ), ), ), - surfaceSize: Size(100, 100), + surfaceSize: const Size(100, 100), ); await screenMatchesGolden(tester, 'reaction_bubble_like_dark'); }, @@ -141,7 +141,7 @@ void main() { ), ), ), - surfaceSize: Size(140, 140), + surfaceSize: const Size(140, 140), ); await screenMatchesGolden(tester, 'reaction_bubble_3_light'); }, @@ -185,7 +185,7 @@ void main() { ), ), ), - surfaceSize: Size(140, 140), + surfaceSize: const Size(140, 140), ); await screenMatchesGolden(tester, 'reaction_bubble_3_dark'); }, @@ -205,7 +205,7 @@ void main() { StreamChat( client: client, streamChatThemeData: StreamChatThemeData.fromTheme(themeData), - child: Container( + child: SizedBox( child: ReactionBubble( reactions: [ Reaction( @@ -230,7 +230,7 @@ void main() { ), ), ), - surfaceSize: Size(200, 200), + surfaceSize: const Size(200, 200), ); await screenMatchesGolden(tester, 'reaction_bubble_2'); diff --git a/packages/stream_chat_flutter/test/src/simple_frame.dart b/packages/stream_chat_flutter/test/src/simple_frame.dart index 18d7ae55..0633a3b8 100644 --- a/packages/stream_chat_flutter/test/src/simple_frame.dart +++ b/packages/stream_chat_flutter/test/src/simple_frame.dart @@ -1,19 +1,17 @@ import 'package:flutter/material.dart'; class SimpleFrame extends StatelessWidget { - final Widget child; - const SimpleFrame({Key? key, required this.child}) : super(key: key); + final Widget child; + @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: const Color(0xFFFFFFFF), - border: Border.all(color: const Color(0xFF9E9E9E)), - ), - child: child, - ); - } + Widget build(BuildContext context) => Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: const Color(0xFFFFFFFF), + border: Border.all(color: const Color(0xFF9E9E9E)), + ), + child: child, + ); } diff --git a/packages/stream_chat_flutter/test/src/system_message_test.dart b/packages/stream_chat_flutter/test/src/system_message_test.dart index 53741864..8fa3ad44 100644 --- a/packages/stream_chat_flutter/test/src/system_message_test.dart +++ b/packages/stream_chat_flutter/test/src/system_message_test.dart @@ -102,7 +102,7 @@ void main() { ), ), ), - surfaceSize: Size.square(200), + surfaceSize: const Size.square(200), ); await screenMatchesGolden(tester, 'system_message_light'); @@ -153,7 +153,7 @@ void main() { ), ), ), - surfaceSize: Size.square(200), + surfaceSize: const Size.square(200), ); await screenMatchesGolden(tester, 'system_message_dark'); diff --git a/packages/stream_chat_flutter/test/src/thread_header_test.dart b/packages/stream_chat_flutter/test/src/thread_header_test.dart index 9e64abb7..578b2ad6 100644 --- a/packages/stream_chat_flutter/test/src/thread_header_test.dart +++ b/packages/stream_chat_flutter/test/src/thread_header_test.dart @@ -117,13 +117,13 @@ void main() { child: Scaffold( body: ThreadHeader( parent: Message(), - subtitle: Text('subtitle'), - leading: Text('leading'), - title: Text('title'), + subtitle: const Text('subtitle'), + leading: const Text('leading'), + title: const Text('title'), onTitleTap: () { tapped = true; }, - actions: [ + actions: const [ Text('action'), ], ), diff --git a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart index 8698de91..cc9af663 100644 --- a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart @@ -67,14 +67,14 @@ void main() { client: client, child: StreamChannel( channel: channel, - child: Scaffold( + child: const Scaffold( body: TypingIndicator(), ), ), ), )); - expect(find.byKey(Key('typings')), findsOneWidget); + expect(find.byKey(const Key('typings')), findsOneWidget); }, ); } diff --git a/packages/stream_chat_flutter/test/src/unread_indicator_test.dart b/packages/stream_chat_flutter/test/src/unread_indicator_test.dart index 9b1b5e1e..29bc0d79 100644 --- a/packages/stream_chat_flutter/test/src/unread_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/unread_indicator_test.dart @@ -36,7 +36,7 @@ void main() { client: client, child: StreamChannel( channel: channel, - child: Scaffold( + child: const Scaffold( body: UnreadIndicator(), ), ), From cb25c1de6b395fb355bb0670ab80798744bc40f0 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 5 May 2021 16:12:28 +0200 Subject: [PATCH 215/599] bump version and chanelogs --- packages/stream_chat_flutter/CHANGELOG.md | 4 ++++ packages/stream_chat_flutter/pubspec.yaml | 4 ++-- packages/stream_chat_flutter_core/CHANGELOG.md | 4 ++++ packages/stream_chat_flutter_core/pubspec.yaml | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 746a76d2..9d3b372e 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.0-nullsafety.3 + +- Fix MessageInput overflow when there are no actions + ## 2.0.0-nullsafety.2 - Migrate this package to null safety diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 57be4ed1..687c92e1 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.2 +version: 2.0.0-nullsafety.3 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - stream_chat_flutter_core: ^2.0.0-nullsafety.1 + stream_chat_flutter_core: ^2.0.0-nullsafety.2 photo_view: ^0.11.1 rxdart: ^0.26.0 scrollable_positioned_list: ^0.2.0-nullsafety.0 diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 10443a52..f07b49bd 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.0-nullsafety.2 + +* Fix ChannelsBloc not performing calls if pagination ended + ## 2.0.0-nullsafety.1 * Migrate this package to null safety diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 059d927e..c84242b8 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.1 +version: 2.0.0-nullsafety.2 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues From 3f123c6220d8e7877a026479d0addfb0b6262960 Mon Sep 17 00:00:00 2001 From: Neevash Ramdial Date: Wed, 5 May 2021 12:22:40 -0400 Subject: [PATCH 216/599] Update title links --- packages/stream_chat_flutter_core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/README.md b/packages/stream_chat_flutter_core/README.md index 9d39ab76..8e74c881 100644 --- a/packages/stream_chat_flutter_core/README.md +++ b/packages/stream_chat_flutter_core/README.md @@ -1,4 +1,4 @@ -# Official Core Flutter SDK for [Stream Chat](https://getstream.io/chat/) +# Official Core [Flutter SDK](https://getstream.io/chat/sdk/flutter/) for [Stream Chat API](https://getstream.io/chat/) > The official Flutter core components for Stream Chat, a service for > building chat applications. From 1c5175045d5af5201a7281cd2d919bb64510c4d5 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 6 May 2021 13:55:36 +0530 Subject: [PATCH 217/599] lint --- packages/stream_chat_flutter/pubspec.yaml | 58 +++++++++++------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 2ef87189..8ac73bbf 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -9,41 +9,41 @@ environment: sdk: '>=2.12.0 <3.0.0' dependencies: + cached_network_image: ^3.0.0 + characters: ^1.1.0 + chewie: ^1.0.0 + collection: ^1.15.0 + dio: ^4.0.0 + ezanimation: ^0.5.0 + file_picker: ^3.0.1 flutter: sdk: flutter - stream_chat_flutter_core: ^2.0.0-nullsafety.0 + flutter_keyboard_visibility: ^5.0.1 + flutter_markdown: ^0.6.1 + flutter_portal: ^0.4.0 + flutter_slidable: ^0.6.0 + flutter_svg: ^0.22.0 + http_parser: ^4.0.0 + image_gallery_saver: ^1.6.9 + image_picker: ^0.7.4 + jiffy: ^4.1.0 + lottie: ^1.0.1 + meta: ^1.3.0 + path_provider: ^2.0.1 + photo_manager: ^1.1.4 + photo_view: ^0.11.1 rxdart: ^0.26.0 scrollable_positioned_list: ^0.2.0-nullsafety.0 - jiffy: ^4.1.0 - photo_view: ^0.11.1 - flutter_svg: ^0.22.0 - flutter_portal: ^0.4.0 - cached_network_image: ^3.0.0 - shimmer: ^2.0.0 - flutter_markdown: ^0.6.1 - url_launcher: ^6.0.3 - video_player: ^2.1.1 - chewie: ^1.0.0 - file_picker: ^3.0.1 - image_picker: ^0.7.4 - flutter_keyboard_visibility: ^5.0.1 - video_compress: ^3.0.0 - visibility_detector: ^0.2.0 - http_parser: ^4.0.0 - meta: ^1.3.0 - lottie: ^1.0.1 - substring_highlight: ^1.0.26 - flutter_slidable: ^0.6.0 - image_gallery_saver: ^1.6.9 share_plus: ^2.0.3 - photo_manager: ^1.1.4 - ezanimation: ^0.5.0 + shimmer: ^2.0.0 + stream_chat_flutter_core: ^2.0.0-nullsafety.0 + substring_highlight: ^1.0.26 synchronized: ^3.0.0 - dio: ^4.0.0 - characters: ^1.1.0 - path_provider: ^2.0.1 + url_launcher: ^6.0.3 + video_compress: ^3.0.0 + video_player: ^2.1.1 video_thumbnail: ^0.3.3 - collection: ^1.15.0 + visibility_detector: ^0.2.0 flutter: assets: @@ -56,7 +56,7 @@ flutter: dev_dependencies: flutter_test: sdk: flutter - mocktail: ^0.1.2 golden_toolkit: ^0.9.0 + mocktail: ^0.1.2 pedantic: ^1.11.0 From ba1ab4aed859bf9e4a575514dddcffada0ce1170 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 6 May 2021 14:05:50 +0530 Subject: [PATCH 218/599] lint --- .../lib/src/message_input.dart | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 13f553d7..44b54c24 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -476,7 +476,6 @@ class MessageInputState extends State { firstChild: IconButton( onPressed: () => setState(() => _actionsShrunk = false), icon: Transform.rotate( - alignment: Alignment.center, angle: (widget.actionsLocation == ActionsLocation.right || widget.actionsLocation == ActionsLocation.rightInside) ? pi @@ -495,28 +494,27 @@ class MessageInputState extends State { splashRadius: 24, ), secondChild: widget.disableAttachments && - !widget.showCommandsButton && - widget.actions?.isNotEmpty != true + !widget.showCommandsButton && + widget.actions?.isNotEmpty != true ? const Offstage() : FittedBox( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - if (!widget.disableAttachments) _buildAttachmentButton(), - if (widget.showCommandsButton && - widget.editMessage == null && - StreamChannel.of(context) - .channel - .config - ?.commands - .isNotEmpty == - true) - _buildCommandButton(), - ...widget.actions ?? [], - ].insertBetween(const SizedBox(width: 8)), - ), - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (!widget.disableAttachments) _buildAttachmentButton(), + if (widget.showCommandsButton && + widget.editMessage == null && + StreamChannel.of(context) + .channel + .config + ?.commands + .isNotEmpty == + true) + _buildCommandButton(), + ...widget.actions ?? [], + ].insertBetween(const SizedBox(width: 8)), + ), + ), duration: const Duration(milliseconds: 300), alignment: Alignment.center, ), From 18ce268e48da4a420b89eb954fb11aa0a324a76a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 6 May 2021 11:48:26 +0200 Subject: [PATCH 219/599] use common analysis options --- ...ysis_options.yaml => analysis_options.yaml | 4 +- packages/stream_chat/analysis_options.yaml | 143 ----------------- packages/stream_chat/lib/src/api/channel.dart | 4 +- packages/stream_chat/lib/src/client.dart | 3 +- .../lib/src/db/chat_persistence_client.dart | 5 + .../lib/src/models/channel_model.dart | 2 +- packages/stream_chat/lib/src/models/user.dart | 2 +- .../example/lib/tutorial-part-2.dart | 2 +- .../analysis_options.yaml | 146 ------------------ .../lib/src/stream_channel.dart | 6 +- .../analysis_options.yaml | 145 ----------------- 11 files changed, 16 insertions(+), 446 deletions(-) rename packages/stream_chat_flutter/analysis_options.yaml => analysis_options.yaml (98%) delete mode 100644 packages/stream_chat/analysis_options.yaml delete mode 100644 packages/stream_chat_flutter_core/analysis_options.yaml delete mode 100644 packages/stream_chat_persistence/analysis_options.yaml diff --git a/packages/stream_chat_flutter/analysis_options.yaml b/analysis_options.yaml similarity index 98% rename from packages/stream_chat_flutter/analysis_options.yaml rename to analysis_options.yaml index 3c78646a..f0a87ea5 100644 --- a/packages/stream_chat_flutter/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,5 +1,3 @@ -include: package:pedantic/analysis_options.yaml - analyzer: enable-experiment: - extension-methods @@ -7,6 +5,8 @@ analyzer: - lib/**/*.g.dart - example/** - lib/src/emoji + - lib/**/*.freezed.dart + - test/** linter: rules: diff --git a/packages/stream_chat/analysis_options.yaml b/packages/stream_chat/analysis_options.yaml deleted file mode 100644 index c0e14133..00000000 --- a/packages/stream_chat/analysis_options.yaml +++ /dev/null @@ -1,143 +0,0 @@ -analyzer: - exclude: - - lib/**/*.g.dart - - lib/**/*.freezed.dart - - example/* -linter: - rules: - - always_use_package_imports - - avoid_empty_else - - avoid_relative_lib_imports - - avoid_slow_async_io - - avoid_types_as_parameter_names - - cancel_subscriptions - - close_sinks - - control_flow_in_finally - - empty_statements - - hash_and_equals - - invariant_booleans - - iterable_contains_unrelated_type - - list_remove_unrelated_type - - literal_only_boolean_expressions - - no_adjacent_strings_in_list - - no_duplicate_case_values - - no_logic_in_create_state - - prefer_void_to_null - - test_types_in_equals - - throw_in_finally - - unnecessary_statements - - unrelated_type_equality_checks - - omit_local_variable_types - - use_key_in_widget_constructors - - valid_regexps - - always_declare_return_types - - always_require_non_null_named_parameters - - annotate_overrides - - avoid_bool_literals_in_conditional_expressions - - avoid_catching_errors - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - - avoid_positional_boolean_parameters - - avoid_private_typedef_functions - - avoid_redundant_argument_values - - avoid_return_types_on_setters - - avoid_returning_null_for_void - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_unnecessary_containers - - avoid_unused_constructor_parameters - - await_only_futures - - camel_case_extensions - - camel_case_types - - cascade_invocations - - - constant_identifier_names - - curly_braces_in_flow_control_structures - - directives_ordering - - empty_catches - - empty_constructor_bodies - - exhaustive_cases - - file_names - - implementation_imports - - join_return_with_assignment - - leading_newlines_in_multiline_strings - - library_names - - library_prefixes - - lines_longer_than_80_chars - - missing_whitespace_between_adjacent_strings - - non_constant_identifier_names - - null_closures - - one_member_abstracts - - only_throw_errors - - package_api_docs - - package_prefixed_library_names - - parameter_assignments - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists - - prefer_asserts_with_message - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - - prefer_const_constructors_in_immutables - - prefer_const_declarations - - prefer_const_literals_to_create_immutables - - prefer_constructors_over_static_methods - - prefer_contains - - prefer_equal_for_default_values - - prefer_expression_function_bodies - - prefer_final_fields - - prefer_final_in_for_each - - prefer_final_locals - - prefer_function_declarations_over_variables - - prefer_generic_function_type_aliases - - prefer_if_elements_to_conditional_expressions - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds - - prefer_int_literals - - prefer_interpolation_to_compose_strings - - prefer_is_empty - - prefer_is_not_empty - - prefer_is_not_operator - - prefer_null_aware_operators - - prefer_single_quotes - - prefer_spread_collections - - prefer_typing_uninitialized_variables - - provide_deprecation_message - - public_member_api_docs - - recursive_getters - - sized_box_for_whitespace - - slash_for_doc_comments - - sort_child_properties_last - - sort_constructors_first - - sort_unnamed_constructors_first - - - type_annotate_public_apis - - type_init_formals - - unnecessary_await_in_return - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - - unnecessary_lambdas - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - - unnecessary_nullable_for_final_variable_declarations - - unnecessary_parenthesis - - unnecessary_raw_strings - - unnecessary_string_escapes - - unnecessary_string_interpolations - - unnecessary_this - - use_is_even_rather_than_modulo - - use_late_for_private_fields_and_variables - - use_rethrow_when_possible - - use_setters_to_change_properties - - use_to_and_as_if_applicable - - package_names - - sort_pub_dependencies - - # To be added when null-safe: - # - 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 diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 1674e5eb..a1eab825 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -646,7 +646,7 @@ class Channel { messageId: messageId, createdAt: now, type: type, - user: user!, + user: user, score: 1, extraData: extraData, ); @@ -654,7 +654,7 @@ class Channel { // Inserting at the 0th index as it's the latest reaction latestReactions.insert(0, newReaction); final ownReactions = [...latestReactions] - ..removeWhere((it) => it.userId != user.id); + ..removeWhere((it) => it.userId != user!.id); final newMessage = message.copyWith( reactionCounts: {...message.reactionCounts ?? {}} diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index a2bc6f77..c7d29737 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1495,8 +1495,7 @@ class ClientState { Stream get userStream => _userController.stream; /// The current user - Map get users => - _usersController.value as Map; + Map get users => _usersController.value!; /// The current user as a stream Stream> get usersStream => _usersController.stream; diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index e9c21956..8f60482f 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -78,10 +78,15 @@ abstract class ChatPersistenceClient { getPinnedMessagesByCid(cid, messagePagination: pinnedMessagePagination), ]); return ChannelState( + // ignore: cast_nullable_to_non_nullable members: data[0] as List, + // ignore: cast_nullable_to_non_nullable read: data[1] as List, + // ignore: cast_nullable_to_non_nullable channel: data[2] as ChannelModel?, + // ignore: cast_nullable_to_non_nullable messages: data[3] as List, + // ignore: cast_nullable_to_non_nullable pinnedMessages: data[4] as List, ); } diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index 98d86539..14380592 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -112,7 +112,7 @@ class ChannelModel { /// Shortcut for channel name String get name => - extraData.containsKey('name') ? extraData['name'] as String : cid; + extraData.containsKey('name') ? extraData['name']! as String : cid; /// Serialize to json Map toJson() => Serialization.moveFromExtraDataToRoot( diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/models/user.dart index 88a7440c..2cb8f8ed 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/models/user.dart @@ -83,7 +83,7 @@ class User { /// Shortcut for user name String get name { if (extraData.containsKey('name')) { - final name = extraData['name'] as String; + final name = extraData['name']! as String; if (name.isNotEmpty) return name; } return id; diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart index d1b39774..1e198d61 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart @@ -58,7 +58,7 @@ class ChannelListPage extends StatelessWidget { child: ChannelListView( filter: Filter.in_( 'members', - [StreamChat.of(context).user.id], + [StreamChat.of(context).user!.id], ), sort: [SortOption('last_message_at')], pagination: PaginationParams( diff --git a/packages/stream_chat_flutter_core/analysis_options.yaml b/packages/stream_chat_flutter_core/analysis_options.yaml deleted file mode 100644 index cf09a170..00000000 --- a/packages/stream_chat_flutter_core/analysis_options.yaml +++ /dev/null @@ -1,146 +0,0 @@ -analyzer: - exclude: - - lib/**/*.g.dart - - lib/**/*.freezed.dart - - example/* - - test/* - -linter: - rules: - - always_use_package_imports - - avoid_empty_else - - avoid_relative_lib_imports - - avoid_slow_async_io - - avoid_types_as_parameter_names - - cancel_subscriptions - - close_sinks - - control_flow_in_finally - - empty_statements - - hash_and_equals - - invariant_booleans - - iterable_contains_unrelated_type - - list_remove_unrelated_type - - literal_only_boolean_expressions - - no_adjacent_strings_in_list - - no_duplicate_case_values - - no_logic_in_create_state - - prefer_void_to_null - - test_types_in_equals - - throw_in_finally - - unnecessary_statements - - unrelated_type_equality_checks - - omit_local_variable_types - - use_key_in_widget_constructors - - valid_regexps - - always_declare_return_types - - always_require_non_null_named_parameters - - annotate_overrides - - avoid_bool_literals_in_conditional_expressions - - avoid_catching_errors - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - - avoid_positional_boolean_parameters - - avoid_private_typedef_functions - - avoid_redundant_argument_values - - avoid_return_types_on_setters - - avoid_returning_null - - avoid_returning_null_for_void - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_unnecessary_containers - - avoid_unused_constructor_parameters - - await_only_futures - - camel_case_extensions - - camel_case_types - - cascade_invocations - - - constant_identifier_names - - curly_braces_in_flow_control_structures - - directives_ordering - - empty_catches - - empty_constructor_bodies - - exhaustive_cases - - file_names - - implementation_imports - - join_return_with_assignment - - leading_newlines_in_multiline_strings - - library_names - - library_prefixes - - lines_longer_than_80_chars - - missing_whitespace_between_adjacent_strings - - non_constant_identifier_names - - null_closures - - one_member_abstracts - - only_throw_errors - - package_api_docs - - package_prefixed_library_names - - parameter_assignments - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists - - prefer_asserts_with_message - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - - prefer_const_constructors_in_immutables - - prefer_const_declarations - - prefer_const_literals_to_create_immutables - - prefer_constructors_over_static_methods - - prefer_contains - - prefer_equal_for_default_values - - prefer_expression_function_bodies - - prefer_final_fields - - prefer_final_in_for_each - - prefer_final_locals - - prefer_function_declarations_over_variables - - prefer_generic_function_type_aliases - - prefer_if_elements_to_conditional_expressions - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds - - prefer_int_literals - - prefer_interpolation_to_compose_strings - - prefer_is_empty - - prefer_is_not_empty - - prefer_is_not_operator - - prefer_null_aware_operators - - prefer_single_quotes - - prefer_spread_collections - - prefer_typing_uninitialized_variables - - provide_deprecation_message - - public_member_api_docs - - recursive_getters - - sized_box_for_whitespace - - slash_for_doc_comments - - sort_child_properties_last - - sort_constructors_first - - sort_unnamed_constructors_first - - - type_annotate_public_apis - - type_init_formals - - unnecessary_await_in_return - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - - unnecessary_lambdas - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - - unnecessary_nullable_for_final_variable_declarations - - unnecessary_parenthesis - - unnecessary_raw_strings - - unnecessary_string_escapes - - unnecessary_string_interpolations - - unnecessary_this - - use_is_even_rather_than_modulo - - use_late_for_private_fields_and_variables - - use_rethrow_when_possible - - use_setters_to_change_properties - - use_to_and_as_if_applicable - - package_names - - sort_pub_dependencies - - # To be added when null-safe: - # - 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 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 34f58faa..a6fd83b2 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -357,9 +357,9 @@ class StreamChannelState extends State { if (snapshot.hasError) { var message = snapshot.error.toString(); if (snapshot.error is DioError) { - final dioError = snapshot.error as DioError; - if (dioError.type == DioErrorType.response) { - message = dioError.message; + final dioError = snapshot.error as DioError?; + if (dioError?.type == DioErrorType.response) { + message = dioError!.message; } else { message = 'Check your connection and retry'; } diff --git a/packages/stream_chat_persistence/analysis_options.yaml b/packages/stream_chat_persistence/analysis_options.yaml deleted file mode 100644 index ff0fc87c..00000000 --- a/packages/stream_chat_persistence/analysis_options.yaml +++ /dev/null @@ -1,145 +0,0 @@ -analyzer: - exclude: - - lib/**/*.g.dart - - lib/**/*.freezed.dart - - example/* -linter: - rules: - - always_use_package_imports - - avoid_empty_else - - avoid_relative_lib_imports - - avoid_slow_async_io - - avoid_types_as_parameter_names - - cancel_subscriptions - - close_sinks - - control_flow_in_finally - - diagnostic_describe_all_properties - - empty_statements - - hash_and_equals - - invariant_booleans - - iterable_contains_unrelated_type - - list_remove_unrelated_type - - literal_only_boolean_expressions - - no_adjacent_strings_in_list - - no_duplicate_case_values - - no_logic_in_create_state - - prefer_void_to_null - - test_types_in_equals - - throw_in_finally - - unnecessary_statements - - unrelated_type_equality_checks - - omit_local_variable_types - - use_key_in_widget_constructors - - valid_regexps - - always_declare_return_types - - always_put_required_named_parameters_first - - always_require_non_null_named_parameters - - annotate_overrides - - avoid_bool_literals_in_conditional_expressions - - avoid_catching_errors - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - - avoid_positional_boolean_parameters - - avoid_private_typedef_functions - - avoid_redundant_argument_values - - avoid_return_types_on_setters - - avoid_returning_null_for_void - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_unnecessary_containers - - avoid_unused_constructor_parameters - - await_only_futures - - camel_case_extensions - - camel_case_types - - cascade_invocations - - - constant_identifier_names - - curly_braces_in_flow_control_structures - - directives_ordering - - empty_catches - - empty_constructor_bodies - - exhaustive_cases - - file_names - - implementation_imports - - join_return_with_assignment - - leading_newlines_in_multiline_strings - - library_names - - library_prefixes - - lines_longer_than_80_chars - - missing_whitespace_between_adjacent_strings - - non_constant_identifier_names - - null_closures - - one_member_abstracts - - only_throw_errors - - package_api_docs - - package_prefixed_library_names - - parameter_assignments - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists - - prefer_asserts_with_message - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - - prefer_const_constructors_in_immutables - - prefer_const_declarations - - prefer_const_literals_to_create_immutables - - prefer_constructors_over_static_methods - - prefer_contains - - prefer_equal_for_default_values - - prefer_expression_function_bodies - - prefer_final_fields - - prefer_final_in_for_each - - prefer_final_locals - - prefer_function_declarations_over_variables - - prefer_generic_function_type_aliases - - prefer_if_elements_to_conditional_expressions - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds - - prefer_int_literals - - prefer_interpolation_to_compose_strings - - prefer_is_empty - - prefer_is_not_empty - - prefer_is_not_operator - - prefer_null_aware_operators - - prefer_single_quotes - - prefer_spread_collections - - prefer_typing_uninitialized_variables - - provide_deprecation_message - - public_member_api_docs - - recursive_getters - - sized_box_for_whitespace - - slash_for_doc_comments - - sort_child_properties_last - - sort_constructors_first - - sort_unnamed_constructors_first - - - type_annotate_public_apis - - type_init_formals - - unnecessary_await_in_return - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - - unnecessary_lambdas - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - - unnecessary_nullable_for_final_variable_declarations - - unnecessary_parenthesis - - unnecessary_raw_strings - - unnecessary_string_escapes - - unnecessary_string_interpolations - - unnecessary_this - - use_is_even_rather_than_modulo - - use_late_for_private_fields_and_variables - - use_rethrow_when_possible - - use_setters_to_change_properties - - use_to_and_as_if_applicable - - package_names - - sort_pub_dependencies - - # To be added when null-safe: - # - 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 From 9392d150c068b44148f13695d2729357db1004e6 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 6 May 2021 13:25:41 +0200 Subject: [PATCH 220/599] [core]: move .of calls to didChangeDependencies --- .../lib/src/channel_list_core.dart | 79 +++++++++++-------- .../lib/src/channels_bloc.dart | 76 ++++++++++-------- .../lib/src/message_list_core.dart | 26 ++++-- .../lib/src/message_search_bloc.dart | 19 +++-- .../lib/src/message_search_list_core.dart | 48 +++++------ .../lib/src/stream_channel.dart | 11 ++- .../lib/src/stream_chat_core.dart | 10 +-- .../lib/src/user_list_core.dart | 2 +- .../lib/src/users_bloc.dart | 19 +++-- 9 files changed, 170 insertions(+), 120 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 8ead2637..e327f0ba 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -119,12 +119,11 @@ class ChannelListCore extends StatefulWidget { /// The current state of the [ChannelListCore]. class ChannelListCoreState extends State { - @override - Widget build(BuildContext context) { - final channelsBloc = ChannelsBloc.of(context); + late final ChannelsBlocState _channelsBloc; + late final StreamChatCoreState _streamChatCoreState; - return _buildListView(channelsBloc); - } + @override + Widget build(BuildContext context) => _buildListView(_channelsBloc); StreamBuilder> _buildListView( ChannelsBlocState channelsBlocState, @@ -147,36 +146,42 @@ class ChannelListCoreState extends State { ); /// Fetches initial channels and updates the widget - Future loadData() { - final channelsBloc = ChannelsBloc.of(context); - return channelsBloc.queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination, - options: widget.options, - ); - } + Future loadData() => _channelsBloc.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination, + options: widget.options, + ); /// Fetches more channels with updated pagination and updates the widget - Future paginateData() { - final channelsBloc = ChannelsBloc.of(context); - return channelsBloc.queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination.copyWith( - offset: channelsBloc.channels?.length ?? 0, - ), - options: widget.options, - ); - } + Future paginateData() => _channelsBloc.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination.copyWith( + offset: _channelsBloc.channels?.length ?? 0, + ), + options: widget.options, + ); - late StreamSubscription _subscription; + StreamSubscription? _subscription; @override void initState() { super.initState(); - loadData(); - final client = StreamChatCore.of(context).client; + _setupController(); + } + + @override + void didChangeDependencies() { + _channelsBloc = ChannelsBloc.of(context); + _streamChatCoreState = StreamChatCore.of(context); + + if (_subscription == null) { + loadData(); + } + + final client = _streamChatCoreState.client; + _subscription?.cancel(); _subscription = client .on( EventType.connectionRecovered, @@ -186,10 +191,7 @@ class ChannelListCoreState extends State { ) .listen((event) => loadData()); - if (widget.channelListController != null) { - widget.channelListController!.loadData = loadData; - widget.channelListController!.paginateData = paginateData; - } + super.didChangeDependencies(); } @override @@ -203,11 +205,22 @@ class ChannelListCoreState extends State { oldWidget.pagination.toJson().toString()) { loadData(); } + + if (widget.channelListController != oldWidget.channelListController) { + _setupController(); + } + } + + void _setupController() { + if (widget.channelListController != null) { + widget.channelListController!.loadData = loadData; + widget.channelListController!.paginateData = paginateData; + } } @override void dispose() { - _subscription.cancel(); + _subscription?.cancel(); super.dispose(); } } 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 ce17235a..966a3fe5 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -50,17 +50,20 @@ class ChannelsBloc extends StatefulWidget { streamChatState = context.findAncestorStateOfType(); - if (streamChatState == null) { - throw Exception('You must have a ChannelsBloc widget as ancestor'); - } + assert( + streamChatState != null, + 'You must have a ChannelsBloc widget as ancestor', + ); - return streamChatState; + return streamChatState!; } } /// The current state of the [ChannelsBloc]. class ChannelsBlocState extends State with AutomaticKeepAliveClientMixin { + late final StreamChatCoreState _streamChatCoreState; + @override Widget build(BuildContext context) { super.build(context); @@ -86,6 +89,8 @@ class ChannelsBlocState extends State bool _paginationEnded = false; + final List _subscriptions = []; + /// Calls [client.queryChannels] updating [queryChannelsLoading] stream Future queryChannels({ Filter? filter, @@ -93,7 +98,7 @@ class ChannelsBlocState extends State PaginationParams paginationParams = const PaginationParams(limit: 30), Map? options, }) async { - final client = StreamChatCore.of(context).client; + final client = _streamChatCoreState.client; final clear = paginationParams.offset == 0; @@ -139,14 +144,12 @@ class ChannelsBlocState extends State } } - final List _subscriptions = []; - @override - void initState() { - super.initState(); - - final client = StreamChatCore.of(context).client; + void didChangeDependencies() { + _streamChatCoreState = StreamChatCore.of(context); + final client = _streamChatCoreState.client; + _cancelSubscriptions(); if (!widget.lockChannelsOrder) { _subscriptions.add(client .on( @@ -179,37 +182,44 @@ class ChannelsBlocState extends State })); } - _subscriptions.add(client.on(EventType.channelHidden).listen((event) async { - final newChannels = List.from(channels ?? []); - final channelIndex = newChannels.indexWhere((c) => c.cid == event.cid); - if (channelIndex > -1) { - final channel = newChannels.removeAt(channelIndex); - _hiddenChannels.add(channel); - _channelsController.add(newChannels); - } - })); - // ignore: cascade_invocations - _subscriptions.add(client - .on( - EventType.channelDeleted, - EventType.notificationRemovedFromChannel, - ) - .listen((e) { - // ignore: cascade_invocations - final channel = e.channel; - _channelsController.add(List.from( - (channels ?? [])..removeWhere((c) => c.cid == channel?.cid))); - })); + _subscriptions + ..add(client.on(EventType.channelHidden).listen((event) async { + final newChannels = List.from(channels ?? []); + final channelIndex = newChannels.indexWhere((c) => c.cid == event.cid); + if (channelIndex > -1) { + final channel = newChannels.removeAt(channelIndex); + _hiddenChannels.add(channel); + _channelsController.add(newChannels); + } + })) + ..add(client + .on( + EventType.channelDeleted, + EventType.notificationRemovedFromChannel, + ) + .listen((e) { + final channel = e.channel; + _channelsController.add(List.from( + (channels ?? [])..removeWhere((c) => c.cid == channel?.cid))); + })); + + super.didChangeDependencies(); } @override void dispose() { _channelsController.close(); _queryChannelsLoadingController.close(); - _subscriptions.forEach((s) => s.cancel()); + _cancelSubscriptions(); super.dispose(); } + void _cancelSubscriptions() { + _subscriptions + ..forEach((s) => s.cancel()) + ..clear(); + } + @override bool get wantKeepAlive => true; } diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index e1fed859..74c6c1ea 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -109,7 +109,7 @@ class MessageListCore extends StatefulWidget { /// The current state of the [MessageListCore]. class MessageListCoreState extends State { - late StreamChannelState _streamChannel; + late final StreamChannelState _streamChannel; bool get _upToDate => _streamChannel.channel.state?.isUpToDate ?? true; @@ -174,18 +174,34 @@ class MessageListCoreState extends State { } @override - void initState() { + void didChangeDependencies() { _streamChannel = StreamChannel.of(context); - if (_isThreadConversation) { _streamChannel.getReplies(widget.parentMessage!.id); } + super.didChangeDependencies(); + } + @override + void didUpdateWidget(covariant MessageListCore oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.messageListController != oldWidget.messageListController) { + _setupController(); + } + } + + @override + void initState() { + _setupController(); + + super.initState(); + } + + void _setupController() { if (widget.messageListController != null) { widget.messageListController!.paginateData = paginateData; } - - super.initState(); } @override 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 6c9be4f0..c98c470e 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 @@ -29,17 +29,20 @@ class MessageSearchBloc extends StatefulWidget { state = context.findAncestorStateOfType(); - if (state == null) { - throw Exception('You must have a MessageSearchBloc widget as ancestor'); - } + assert( + state != null, + 'You must have a MessageSearchBloc widget as ancestor', + ); - return state; + return state!; } } /// The current state of the [MessageSearchBloc] class MessageSearchBlocState extends State with AutomaticKeepAliveClientMixin { + late final StreamChatCoreState _streamChatCoreState; + /// The current messages list List? get messageResponses => _messageResponses.value; @@ -64,7 +67,7 @@ class MessageSearchBlocState extends State String? query, PaginationParams? pagination, }) async { - final client = StreamChatCore.of(context).client; + final client = _streamChatCoreState.client; if (_queryMessagesLoadingController.value == true) return; @@ -109,6 +112,12 @@ class MessageSearchBlocState extends State return widget.child; } + @override + void didChangeDependencies() { + _streamChatCoreState = StreamChatCore.of(context); + super.didChangeDependencies(); + } + @override void dispose() { _messageResponses.close(); diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index cb8c9387..5c661ca1 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -105,21 +105,21 @@ class MessageSearchListCore extends StatefulWidget { /// The current state of the [MessageSearchListCore]. class MessageSearchListCoreState extends State { + late final MessageSearchBlocState messageSearchBloc; + @override void didChangeDependencies() { - super.didChangeDependencies(); + messageSearchBloc = MessageSearchBloc.of(context); loadData(); if (widget.messageSearchListController != null) { widget.messageSearchListController!.loadData = loadData; widget.messageSearchListController!.paginateData = paginateData; } + super.didChangeDependencies(); } @override - Widget build(BuildContext context) { - final messageSearchBloc = MessageSearchBloc.of(context); - return _buildListView(messageSearchBloc); - } + Widget build(BuildContext context) => _buildListView(messageSearchBloc); Widget _buildListView(MessageSearchBlocState messageSearchBloc) => StreamBuilder>( @@ -140,30 +140,24 @@ class MessageSearchListCoreState extends State { ); /// Fetches initial messages and updates the widget - Future loadData() { - final messageSearchBloc = MessageSearchBloc.of(context); - return messageSearchBloc.search( - filter: widget.filters, - sort: widget.sortOptions, - query: widget.messageQuery, - pagination: widget.paginationParams, - messageFilter: widget.messageFilters, - ); - } + Future loadData() => messageSearchBloc.search( + filter: widget.filters, + sort: widget.sortOptions, + query: widget.messageQuery, + pagination: widget.paginationParams, + messageFilter: widget.messageFilters, + ); /// Fetches more messages with updated pagination and updates the widget - Future paginateData() { - final messageSearchBloc = MessageSearchBloc.of(context); - return messageSearchBloc.search( - filter: widget.filters, - sort: widget.sortOptions, - pagination: widget.paginationParams!.copyWith( - offset: messageSearchBloc.messageResponses?.length ?? 0, - ), - query: widget.messageQuery, - messageFilter: widget.messageFilters, - ); - } + Future paginateData() => messageSearchBloc.search( + filter: widget.filters, + sort: widget.sortOptions, + pagination: widget.paginationParams!.copyWith( + offset: messageSearchBloc.messageResponses?.length ?? 0, + ), + query: widget.messageQuery, + messageFilter: widget.messageFilters, + ); @override void didUpdateWidget(MessageSearchListCore oldWidget) { 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 a6fd83b2..2760d9cc 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -47,13 +47,12 @@ class StreamChannel extends StatefulWidget { streamChannelState = context.findAncestorStateOfType(); - if (streamChannelState == null) { - throw Exception( - 'You must have a StreamChannel widget at the top of your widget tree', - ); - } + assert( + streamChannelState != null, + 'You must have a StreamChannel widget at the top of your widget tree', + ); - return streamChannelState; + return streamChannelState!; } @override diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index a72425ed..89d4853a 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -70,12 +70,12 @@ class StreamChatCore extends StatefulWidget { streamChatState = context.findAncestorStateOfType(); - if (streamChatState == null) { - throw Exception( - 'You must have a StreamChat widget at the top of your widget tree'); - } + assert( + streamChatState != null, + 'You must have a StreamChat widget at the top of your widget tree', + ); - return streamChatState; + return streamChatState!; } } diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 694c52bc..d608ddc4 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -124,12 +124,12 @@ class UserListCoreState extends State with WidgetsBindingObserver { @override void didChangeDependencies() { - super.didChangeDependencies(); loadData(); if (widget.userListController != null) { widget.userListController!.loadData = loadData; widget.userListController!.paginateData = paginateData; } + super.didChangeDependencies(); } @override 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 9b33f75c..f19efa8d 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -30,11 +30,12 @@ class UsersBloc extends StatefulWidget { state = context.findAncestorStateOfType(); - if (state == null) { - throw Exception('You must have a UsersBloc widget as ancestor'); - } + assert( + state != null, + 'You must have a UsersBloc widget as ancestor', + ); - return state; + return state!; } } @@ -54,6 +55,8 @@ class UsersBlocState extends State /// The stream notifying the state of queryUsers call Stream get queryUsersLoading => _queryUsersLoadingController.stream; + late final StreamChatCoreState _streamChatCore; + /// The Query Users method allows you to search for users and see if they are /// online/offline. /// [API Reference](https://getstream.io/chat/docs/flutter-dart/query_users/?language=dart) @@ -63,7 +66,7 @@ class UsersBlocState extends State Map? options, PaginationParams? pagination, }) async { - final client = StreamChatCore.of(context).client; + final client = _streamChatCore.client; if (_queryUsersLoadingController.value == true) return; @@ -101,6 +104,12 @@ class UsersBlocState extends State } } + @override + void didChangeDependencies() { + _streamChatCore = StreamChatCore.of(context); + super.didChangeDependencies(); + } + @override Widget build(BuildContext context) { super.build(context); From 63c447c7a5cd723c7031eecff2ece5bb70085e1a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 6 May 2021 13:33:12 +0200 Subject: [PATCH 221/599] fix analysis --- .../lib/src/message_search_list_core.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index 5c661ca1..95761365 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -105,11 +105,11 @@ class MessageSearchListCore extends StatefulWidget { /// The current state of the [MessageSearchListCore]. class MessageSearchListCoreState extends State { - late final MessageSearchBlocState messageSearchBloc; + late final MessageSearchBlocState _messageSearchBloc; @override void didChangeDependencies() { - messageSearchBloc = MessageSearchBloc.of(context); + _messageSearchBloc = MessageSearchBloc.of(context); loadData(); if (widget.messageSearchListController != null) { widget.messageSearchListController!.loadData = loadData; @@ -119,7 +119,7 @@ class MessageSearchListCoreState extends State { } @override - Widget build(BuildContext context) => _buildListView(messageSearchBloc); + Widget build(BuildContext context) => _buildListView(_messageSearchBloc); Widget _buildListView(MessageSearchBlocState messageSearchBloc) => StreamBuilder>( @@ -140,7 +140,7 @@ class MessageSearchListCoreState extends State { ); /// Fetches initial messages and updates the widget - Future loadData() => messageSearchBloc.search( + Future loadData() => _messageSearchBloc.search( filter: widget.filters, sort: widget.sortOptions, query: widget.messageQuery, @@ -149,11 +149,11 @@ class MessageSearchListCoreState extends State { ); /// Fetches more messages with updated pagination and updates the widget - Future paginateData() => messageSearchBloc.search( + Future paginateData() => _messageSearchBloc.search( filter: widget.filters, sort: widget.sortOptions, pagination: widget.paginationParams!.copyWith( - offset: messageSearchBloc.messageResponses?.length ?? 0, + offset: _messageSearchBloc.messageResponses?.length ?? 0, ), query: widget.messageQuery, messageFilter: widget.messageFilters, From 777eaa23fc3eb43916770573c7da48f073ed7cd4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 6 May 2021 13:43:02 +0200 Subject: [PATCH 222/599] remove final --- .../stream_chat_flutter_core/lib/src/channel_list_core.dart | 4 ++-- packages/stream_chat_flutter_core/lib/src/channels_bloc.dart | 2 +- .../stream_chat_flutter_core/lib/src/message_list_core.dart | 2 +- .../stream_chat_flutter_core/lib/src/message_search_bloc.dart | 2 +- .../lib/src/message_search_list_core.dart | 2 +- packages/stream_chat_flutter_core/lib/src/users_bloc.dart | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index e327f0ba..0460cf7e 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -119,8 +119,8 @@ class ChannelListCore extends StatefulWidget { /// The current state of the [ChannelListCore]. class ChannelListCoreState extends State { - late final ChannelsBlocState _channelsBloc; - late final StreamChatCoreState _streamChatCoreState; + late ChannelsBlocState _channelsBloc; + late StreamChatCoreState _streamChatCoreState; @override Widget build(BuildContext context) => _buildListView(_channelsBloc); 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 966a3fe5..f29c76f9 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -62,7 +62,7 @@ class ChannelsBloc extends StatefulWidget { /// The current state of the [ChannelsBloc]. class ChannelsBlocState extends State with AutomaticKeepAliveClientMixin { - late final StreamChatCoreState _streamChatCoreState; + late StreamChatCoreState _streamChatCoreState; @override Widget build(BuildContext context) { diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 74c6c1ea..3e0ac1c7 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -109,7 +109,7 @@ class MessageListCore extends StatefulWidget { /// The current state of the [MessageListCore]. class MessageListCoreState extends State { - late final StreamChannelState _streamChannel; + late StreamChannelState _streamChannel; bool get _upToDate => _streamChannel.channel.state?.isUpToDate ?? true; 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 c98c470e..386ba388 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 @@ -41,7 +41,7 @@ class MessageSearchBloc extends StatefulWidget { /// The current state of the [MessageSearchBloc] class MessageSearchBlocState extends State with AutomaticKeepAliveClientMixin { - late final StreamChatCoreState _streamChatCoreState; + late StreamChatCoreState _streamChatCoreState; /// The current messages list List? get messageResponses => _messageResponses.value; diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index 95761365..e2f0ae2e 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -105,7 +105,7 @@ class MessageSearchListCore extends StatefulWidget { /// The current state of the [MessageSearchListCore]. class MessageSearchListCoreState extends State { - late final MessageSearchBlocState _messageSearchBloc; + late MessageSearchBlocState _messageSearchBloc; @override void didChangeDependencies() { 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 f19efa8d..57edfecb 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -55,7 +55,7 @@ class UsersBlocState extends State /// The stream notifying the state of queryUsers call Stream get queryUsersLoading => _queryUsersLoadingController.stream; - late final StreamChatCoreState _streamChatCore; + late StreamChatCoreState _streamChatCore; /// The Query Users method allows you to search for users and see if they are /// online/offline. From fff82c8455b91a22cbc0e0ded554c675f3e07c6c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 6 May 2021 14:52:38 +0200 Subject: [PATCH 223/599] fix tests --- .../lib/src/channel_bottom_sheet.dart | 59 +++++++++++-------- .../lib/src/video_thumbnail_image.dart | 16 +++-- .../test/channel_list_core_test.dart | 2 +- .../test/channels_bloc_test.dart | 2 +- .../test/message_list_core_test.dart | 2 +- .../test/message_search_bloc_test.dart | 19 +----- .../test/message_search_list_core_test.dart | 2 +- .../test/user_list_core_test.dart | 2 +- .../test/users_bloc_test.dart | 19 +----- 9 files changed, 52 insertions(+), 71 deletions(-) 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 89f19466..eaabb7e2 100644 --- a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart @@ -17,18 +17,22 @@ class ChannelBottomSheet extends StatefulWidget { class _ChannelBottomSheetState extends State { bool _showActions = true; + late StreamChannelState _streamChannelState; + late StreamChatThemeData _streamChatThemeData; + late StreamChatState _streamChatState; + @override Widget build(BuildContext context) { - final channel = StreamChannel.of(context).channel; + final channel = _streamChannelState.channel; final members = channel.state?.members ?? []; - final userAsMember = members - .firstWhere((e) => e.user?.id == StreamChat.of(context).user?.id); + final userAsMember = + members.firstWhere((e) => e.user?.id == _streamChatState.user?.id); final isOwner = userAsMember.role == 'owner'; return Material( - color: StreamChatTheme.of(context).colorTheme.white, + color: _streamChatThemeData.colorTheme.white, clipBehavior: Clip.antiAlias, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -48,8 +52,7 @@ class _ChannelBottomSheetState extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: ChannelName( - textStyle: - StreamChatTheme.of(context).textTheme.headlineBold, + textStyle: _streamChatThemeData.textTheme.headlineBold, ), ), ), @@ -59,10 +62,9 @@ class _ChannelBottomSheetState extends State { Center( child: ChannelInfo( showTypingIndicator: false, - channel: StreamChannel.of(context).channel, - textStyle: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle, + channel: _streamChannelState.channel, + textStyle: + _streamChatThemeData.channelPreviewTheme.subtitle, ), ), const SizedBox( @@ -94,8 +96,7 @@ class _ChannelBottomSheetState extends State { .user ?.name ?? '', - style: - StreamChatTheme.of(context).textTheme.footnoteBold, + style: _streamChatThemeData.textTheme.footnoteBold, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -128,9 +129,8 @@ class _ChannelBottomSheetState extends State { ), Text( members[index].user?.name ?? '', - style: StreamChatTheme.of(context) - .textTheme - .footnoteBold, + style: + _streamChatThemeData.textTheme.footnoteBold, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -146,7 +146,7 @@ class _ChannelBottomSheetState extends State { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.user( - color: StreamChatTheme.of(context).colorTheme.grey, + color: _streamChatThemeData.colorTheme.grey, ), ), title: 'View Info', @@ -157,7 +157,7 @@ class _ChannelBottomSheetState extends State { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.userRemove( - color: StreamChatTheme.of(context).colorTheme.grey, + color: _streamChatThemeData.colorTheme.grey, ), ), title: 'Leave Group', @@ -176,12 +176,11 @@ class _ChannelBottomSheetState extends State { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.delete( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: _streamChatThemeData.colorTheme.accentRed, ), ), title: 'Delete Conversation', - titleColor: - StreamChatTheme.of(context).colorTheme.accentRed, + titleColor: _streamChatThemeData.colorTheme.accentRed, onTap: () async { setState(() { _showActions = false; @@ -196,7 +195,7 @@ class _ChannelBottomSheetState extends State { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.closeSmall( - color: StreamChatTheme.of(context).colorTheme.grey, + color: _streamChatThemeData.colorTheme.grey, ), ), title: 'Cancel', @@ -209,6 +208,14 @@ class _ChannelBottomSheetState extends State { ); } + @override + void didChangeDependencies() { + _streamChannelState = StreamChannel.of(context); + _streamChatThemeData = StreamChatTheme.of(context); + _streamChatState = StreamChat.of(context); + super.didChangeDependencies(); + } + Future _showDeleteDialog() async { final res = await showConfirmationDialog( context, @@ -217,10 +224,10 @@ class _ChannelBottomSheetState extends State { question: 'Are you sure you want to delete this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.delete( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: _streamChatThemeData.colorTheme.accentRed, ), ); - final channel = StreamChannel.of(context).channel; + final channel = _streamChannelState.channel; if (res == true) { await channel.delete(); Navigator.pop(context); @@ -235,12 +242,12 @@ class _ChannelBottomSheetState extends State { question: 'Are you sure you want to leave this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.userRemove( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: _streamChatThemeData.colorTheme.accentRed, ), ); if (res == true) { - final channel = StreamChannel.of(context).channel; - final user = StreamChat.of(context).user; + final channel = _streamChannelState.channel; + final user = _streamChatState.user; if (user != null) { await channel.removeMembers([user.id]); } diff --git a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart index 8cafbbff..23cc0e35 100644 --- a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart +++ b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart @@ -2,9 +2,9 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; +import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; -import 'package:stream_chat_flutter/src/video_service.dart'; /// Widget for creating video thumbnail image class VideoThumbnailImage extends StatefulWidget { @@ -47,6 +47,7 @@ class VideoThumbnailImage extends StatefulWidget { class _VideoThumbnailImageState extends State { late Future thumbnailFuture; + late StreamChatThemeData _streamChatTheme; @override void initState() { @@ -57,6 +58,12 @@ class _VideoThumbnailImageState extends State { super.initState(); } + @override + void didChangeDependencies() { + _streamChatTheme = StreamChatTheme.of(context); + super.didChangeDependencies(); + } + @override void didUpdateWidget(covariant VideoThumbnailImage oldWidget) { if (oldWidget.video != widget.video || oldWidget.format != widget.format) { @@ -87,11 +94,8 @@ class _VideoThumbnailImageState extends State { constraints: const BoxConstraints.expand(), child: widget.placeholderBuilder?.call(context) ?? Shimmer.fromColors( - baseColor: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, - highlightColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, + baseColor: _streamChatTheme.colorTheme.greyGainsboro, + highlightColor: _streamChatTheme.colorTheme.whiteSmoke, child: Image.asset( 'images/placeholder.png', fit: BoxFit.cover, diff --git a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart index 53303231..14cbc232 100644 --- a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart @@ -46,7 +46,7 @@ void main() { await tester.pumpWidget(channelListCore); expect(find.byKey(channelListCoreKey), findsNothing); - expect(tester.takeException(), isInstanceOf()); + expect(tester.takeException(), isInstanceOf()); }, ); diff --git a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart index 19d327bd..ff373878 100644 --- a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart @@ -46,7 +46,7 @@ void main() { expect(find.byKey(channelsBlocKey), findsNothing); expect(find.byKey(childKey), findsNothing); - expect(tester.takeException(), isInstanceOf()); + expect(tester.takeException(), isInstanceOf()); }, ); diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index 9aea9489..f6ea335f 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -78,7 +78,7 @@ void main() { await tester.pumpWidget(messageListCore); expect(find.byKey(messageListCoreKey), findsNothing); - expect(tester.takeException(), isInstanceOf()); + expect(tester.takeException(), isInstanceOf()); }, ); diff --git a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart index 91c07435..4d67ebd9 100644 --- a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart @@ -35,27 +35,12 @@ void main() { 'messageSearchBlocState.search() should throw if used where ' 'StreamChat is not present in the widget tree', (tester) async { - const messageSearchBlocKey = Key('messageSearchBloc'); - const childKey = Key('child'); final messageSearchBloc = MessageSearchBloc( - key: messageSearchBlocKey, - child: Offstage(key: childKey), + child: Offstage(), ); await tester.pumpWidget(messageSearchBloc); - - expect(find.byKey(messageSearchBlocKey), findsOneWidget); - expect(find.byKey(childKey), findsOneWidget); - - final usersBlocState = tester.state( - find.byKey(messageSearchBlocKey), - ); - - try { - await usersBlocState.search(filter: testFilter); - } catch (e) { - expect(e, isInstanceOf()); - } + expect(tester.takeException(), isInstanceOf()); }, ); diff --git a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart index 1f7414d0..f704c9fd 100644 --- a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart @@ -45,7 +45,7 @@ void main() { await tester.pumpWidget(messageSearchListCore); expect(find.byKey(messageSearchListCoreKey), findsNothing); - expect(tester.takeException(), isInstanceOf()); + expect(tester.takeException(), isInstanceOf()); }, ); diff --git a/packages/stream_chat_flutter_core/test/user_list_core_test.dart b/packages/stream_chat_flutter_core/test/user_list_core_test.dart index a0f8bd51..820e479f 100644 --- a/packages/stream_chat_flutter_core/test/user_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/user_list_core_test.dart @@ -49,7 +49,7 @@ void main() { await tester.pumpWidget(userListCore); expect(find.byKey(userListCoreKey), findsNothing); - expect(tester.takeException(), isInstanceOf()); + expect(tester.takeException(), isInstanceOf()); }, ); diff --git a/packages/stream_chat_flutter_core/test/users_bloc_test.dart b/packages/stream_chat_flutter_core/test/users_bloc_test.dart index 8c348211..2722d08a 100644 --- a/packages/stream_chat_flutter_core/test/users_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/users_bloc_test.dart @@ -35,27 +35,12 @@ void main() { 'usersBlocState.queryUsers() should throw if used where ' 'StreamChat is not present in the widget tree', (tester) async { - const usersBlocKey = Key('usersBloc'); - const childKey = Key('child'); final usersBloc = UsersBloc( - key: usersBlocKey, - child: Offstage(key: childKey), + child: Offstage(), ); await tester.pumpWidget(usersBloc); - - expect(find.byKey(usersBlocKey), findsOneWidget); - expect(find.byKey(childKey), findsOneWidget); - - final usersBlocState = tester.state( - find.byKey(usersBlocKey), - ); - - try { - await usersBlocState.queryUsers(); - } catch (e) { - expect(e, isInstanceOf()); - } + expect(tester.takeException(), isInstanceOf()); }, ); From 43d5e9063ebec7e91b2e5fe8e6d7c9dbbafdb43f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 10 May 2021 09:21:31 +0200 Subject: [PATCH 224/599] refactoring --- .../lib/src/channel_header.dart | 33 ++-- .../lib/src/channel_image.dart | 60 +++---- .../lib/src/channel_info.dart | 7 +- .../lib/src/channel_list_header.dart | 91 +++++----- .../lib/src/channel_list_view.dart | 158 +++++++++--------- .../lib/src/channel_preview.dart | 46 +++-- 6 files changed, 172 insertions(+), 223 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index 8b0eb8fb..d06c128c 100644 --- a/packages/stream_chat_flutter/lib/src/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_header.dart @@ -104,6 +104,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { final channel = StreamChannel.of(context).channel; + final chatThemeData = StreamChatTheme.of(context); final leadingWidget = leading ?? (showBackButton @@ -139,26 +140,18 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { brightness: Theme.of(context).brightness, elevation: 1, leading: leadingWidget, - backgroundColor: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .color, + backgroundColor: + chatThemeData.channelTheme.channelHeaderTheme.color, actions: actions ?? [ Padding( padding: const EdgeInsets.only(right: 10), child: Center( child: ChannelImage( - borderRadius: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .avatarTheme - ?.borderRadius, - constraints: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .avatarTheme - ?.constraints, + borderRadius: chatThemeData.channelTheme + .channelHeaderTheme.avatarTheme?.borderRadius, + constraints: chatThemeData.channelTheme + .channelHeaderTheme.avatarTheme?.constraints, onTap: onImageTap, ), ), @@ -175,20 +168,16 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { children: [ title ?? ChannelName( - textStyle: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .title, + textStyle: chatThemeData + .channelTheme.channelHeaderTheme.title, ), const SizedBox(height: 2), subtitle ?? ChannelInfo( showTypingIndicator: showTypingIndicator, channel: channel, - textStyle: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .subtitle, + textStyle: chatThemeData + .channelTheme.channelHeaderTheme.subtitle, ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 80e32f07..19495809 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -88,6 +88,7 @@ class ChannelImage extends StatelessWidget { initialData: channel.extraData, builder: (context, snapshot) { String? image; + final chatThemeData = StreamChatTheme.of(context); if (snapshot.data!.containsKey('image') == true) { image = snapshot.data!['image']; } else if (channel.state?.members.length == 2) { @@ -99,20 +100,16 @@ class ChannelImage extends StatelessWidget { initialData: otherMember!.user, builder: (context, snapshot) => UserAvatar( borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.borderRadius, + chatThemeData + .channelPreviewTheme.avatarTheme?.borderRadius, user: snapshot.data ?? otherMember.user!, constraints: constraints ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.constraints, + chatThemeData + .channelPreviewTheme.avatarTheme?.constraints, onTap: onTap != null ? (_) => onTap!() : null, selected: selected, - selectionColor: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, + selectionColor: + selectionColor ?? chatThemeData.colorTheme.accentBlue, selectionThickness: selectionThickness, )); } else { @@ -127,37 +124,25 @@ class ChannelImage extends StatelessWidget { return GroupImage( images: images ?? [], borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.borderRadius, + chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius, constraints: constraints ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.constraints, + chatThemeData.channelPreviewTheme.avatarTheme?.constraints, onTap: onTap, selected: selected, - selectionColor: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, + selectionColor: + selectionColor ?? chatThemeData.colorTheme.accentBlue, selectionThickness: selectionThickness, ); } Widget child = ClipRRect( borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.borderRadius, + chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius, child: Container( constraints: constraints ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.constraints, + chatThemeData.channelPreviewTheme.avatarTheme?.constraints, decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentBlue, ), child: Stack( alignment: Alignment.center, @@ -172,7 +157,7 @@ class ChannelImage extends StatelessWidget { ? snapshot.data!['name'][0] : '', style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, fontWeight: FontWeight.bold, ), ), @@ -180,7 +165,7 @@ class ChannelImage extends StatelessWidget { fit: BoxFit.cover, ) else - StreamChatTheme.of(context).defaultChannelImage( + chatThemeData.defaultChannelImage( context, channel, ), @@ -198,20 +183,13 @@ class ChannelImage extends StatelessWidget { child = ClipRRect( key: const Key('selectedImage'), borderRadius: (borderRadius ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - ?.borderRadius ?? + chatThemeData.ownMessageTheme.avatarTheme?.borderRadius ?? BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( constraints: constraints ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - ?.constraints, - color: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, + chatThemeData.ownMessageTheme.avatarTheme?.constraints, + color: selectionColor ?? chatThemeData.colorTheme.accentBlue, child: Padding( padding: EdgeInsets.all(selectionThickness), child: child, diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index 5fa6f704..f59ac2e0 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -46,7 +46,9 @@ class ChannelInfo extends StatelessWidget { } Widget _buildConnectedTitleState( - BuildContext context, List? members) { + BuildContext context, + List? members, + ) { Widget? alternativeWidget; if (channel.memberCount != null && channel.memberCount! > 2) { @@ -61,8 +63,9 @@ class ChannelInfo extends StatelessWidget { .subtitle, ); } else { + final userId = StreamChat.of(context).user?.id; final otherMember = members?.firstWhereOrNull( - (element) => element.userId != StreamChat.of(context).user?.id, + (element) => element.userId != userId, ); if (otherMember != null) { 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 afc1884c..325736f1 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -114,6 +114,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { break; } + final chatThemeData = StreamChatTheme.of(context); return InfoTile( // ignore: avoid_bool_literals_in_conditional_expressions showMessage: showConnectionStateTile ? showStatus : false, @@ -121,8 +122,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { child: AppBar( brightness: Theme.of(context).brightness, elevation: 1, - backgroundColor: - StreamChatTheme.of(context).channelListHeaderTheme.color, + backgroundColor: chatThemeData.channelListHeaderTheme.color, centerTitle: true, leading: leading ?? Center( @@ -137,14 +137,10 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { } Scaffold.of(context).openDrawer(); }, - borderRadius: StreamChatTheme.of(context) - .channelListHeaderTheme - .avatarTheme - ?.borderRadius, - constraints: StreamChatTheme.of(context) - .channelListHeaderTheme - .avatarTheme - ?.constraints, + borderRadius: chatThemeData + .channelListHeaderTheme.avatarTheme?.borderRadius, + constraints: chatThemeData + .channelListHeaderTheme.avatarTheme?.constraints, ) : const Offstage(), ), @@ -157,9 +153,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { Color? color; switch (status) { case ConnectionStatus.connected: - color = StreamChatTheme.of(context) - .colorTheme - .accentBlue; + color = chatThemeData.colorTheme.accentBlue; break; case ConnectionStatus.connecting: color = Colors.grey; @@ -209,12 +203,15 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { ); } - Widget _buildConnectedTitleState(BuildContext context) => Text( - 'Stream Chat', - style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith( - color: StreamChatTheme.of(context).colorTheme.black, - ), - ); + Widget _buildConnectedTitleState(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return Text( + 'Stream Chat', + style: chatThemeData.textTheme.headlineBold.copyWith( + color: chatThemeData.colorTheme.black, + ), + ); + } Widget _buildConnectingTitleState(BuildContext context) => Row( mainAxisAlignment: MainAxisAlignment.center, @@ -243,39 +240,35 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { Widget _buildDisconnectedTitleState( BuildContext context, StreamChatClient client, - ) => - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Offline...', - style: StreamChatTheme.of(context) - .channelListHeaderTheme - .title - ?.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + ) { + final chatThemeData = StreamChatTheme.of(context); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Offline...', + style: chatThemeData.channelListHeaderTheme.title?.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, ), - TextButton( - onPressed: () async { - await client.disconnect(); - await client.connect(); - }, - child: Text( - 'Try Again', - style: StreamChatTheme.of(context) - .channelListHeaderTheme - .title - ?.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ), + ), + TextButton( + onPressed: () async { + await client.disconnect(); + await client.connect(); + }, + child: Text( + 'Try Again', + style: chatThemeData.channelListHeaderTheme.title?.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + color: chatThemeData.colorTheme.accentBlue, ), ), - ], - ); + ), + ], + ); + } @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); 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 fb713ea8..766fe4ba 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -237,78 +237,70 @@ class _ChannelListViewState extends State { } Widget _buildEmptyWidget(BuildContext context) => LayoutBuilder( - builder: (context, viewportConstraints) => SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - child: Stack( - children: [ - ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, + builder: (context, viewportConstraints) { + final chatThemeData = StreamChatTheme.of(context); + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Stack( + children: [ + ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: StreamSvgIcon.message( + size: 136, + color: chatThemeData.colorTheme.greyGainsboro, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: Text( + 'Let’s start chatting!', + style: chatThemeData.textTheme.headline, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 52, + ), + child: Text( + 'How about sending your first message to a friend?', + textAlign: TextAlign.center, + style: chatThemeData.textTheme.body.copyWith( + color: chatThemeData.colorTheme.grey, + ), + ), + ), + ], + ), ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(8), - child: StreamSvgIcon.message( - size: 136, - color: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, - ), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Text( - 'Let’s start chatting!', - style: StreamChatTheme.of(context).textTheme.headline, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 52, - ), - child: Text( - 'How about sending your first message to a friend?', - textAlign: TextAlign.center, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith( - color: - StreamChatTheme.of(context).colorTheme.grey, - ), - ), - ), - ], - ), - ), - if (widget.onStartChatPressed != null) - Positioned( - right: 0, - left: 0, - bottom: 32, - child: Center( - child: TextButton( - onPressed: widget.onStartChatPressed, - child: Text( - 'Start a chat', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), + if (widget.onStartChatPressed != null) + Positioned( + right: 0, + left: 0, + bottom: 32, + child: Center( + child: TextButton( + onPressed: widget.onStartChatPressed, + child: Text( + 'Start a chat', + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.accentBlue, + ), + ), ), ), ), - ), - ], - ), - ), + ], + ), + ); + }, ); Widget _buildLoadingWidget(BuildContext context) => ListView( @@ -331,10 +323,11 @@ class _ChannelListViewState extends State { ); Shimmer _buildLoadingItem(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); if (widget.crossAxisCount > 1) { return Shimmer.fromColors( - baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke, + baseColor: chatThemeData.colorTheme.greyGainsboro, + highlightColor: chatThemeData.colorTheme.whiteSmoke, child: Column( children: [ const SizedBox(height: 4), @@ -362,12 +355,12 @@ class _ChannelListViewState extends State { ); } else { return Shimmer.fromColors( - baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke, + baseColor: chatThemeData.colorTheme.greyGainsboro, + highlightColor: chatThemeData.colorTheme.whiteSmoke, child: ListTile( leading: Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, shape: BoxShape.circle, ), constraints: const BoxConstraints.tightFor( @@ -383,7 +376,7 @@ class _ChannelListViewState extends State { alignment: Alignment.centerLeft, child: Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, borderRadius: BorderRadius.circular(11), ), constraints: const BoxConstraints.tightFor( @@ -400,7 +393,7 @@ class _ChannelListViewState extends State { alignment: Alignment.centerLeft, child: Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, borderRadius: BorderRadius.circular(11), ), constraints: const BoxConstraints.expand( @@ -412,7 +405,7 @@ class _ChannelListViewState extends State { Container( margin: const EdgeInsets.only(left: 16), decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, borderRadius: BorderRadius.circular(11), ), constraints: const BoxConstraints.tightFor( @@ -456,12 +449,13 @@ class _ChannelListViewState extends State { ); Widget _listItemBuilder(BuildContext context, int i, List channels) { - final channelsProvider = ChannelsBloc.of(context); + final channelsBloc = ChannelsBloc.of(context); if (i < channels.length) { final channel = channels[i]; final onTap = _getChannelTap(context); - final backgroundColor = StreamChatTheme.of(context).colorTheme.whiteSmoke; + final chatThemeData = StreamChatTheme.of(context); + final backgroundColor = chatThemeData.colorTheme.whiteSmoke; return StreamChannel( key: ValueKey('CHANNEL-${channel.id}'), channel: channel, @@ -506,7 +500,7 @@ class _ChannelListViewState extends State { IconSlideAction( color: backgroundColor, iconWidget: StreamSvgIcon.delete( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: chatThemeData.colorTheme.accentRed, ), onTap: () async { final res = await showConfirmationDialog( @@ -517,7 +511,7 @@ class _ChannelListViewState extends State { 'Are you sure you want to delete this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.delete( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: chatThemeData.colorTheme.accentRed, ), ); if (res == true) { @@ -527,7 +521,7 @@ class _ChannelListViewState extends State { ), ], child: Container( - color: StreamChatTheme.of(context).colorTheme.whiteSnow, + color: chatThemeData.colorTheme.whiteSnow, child: widget.channelPreviewBuilder?.call(context, channel) ?? ChannelPreview( onLongPress: widget.onChannelLongPress, @@ -540,7 +534,7 @@ class _ChannelListViewState extends State { ), ); } else { - return _buildQueryProgressIndicator(context, channelsProvider); + return _buildQueryProgressIndicator(context, channelsBloc); } } diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 70ad0e3f..3189b0cb 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -68,6 +68,8 @@ class ChannelPreview extends StatelessWidget { @override Widget build(BuildContext context) { final channelPreviewTheme = StreamChatTheme.of(context).channelPreviewTheme; + final streamChatState = StreamChat.of(context); + return StreamBuilder( stream: channel.isMutedStream, initialData: channel.isMuted, @@ -130,7 +132,7 @@ class ChannelPreview extends StatelessWidget { (m) => !m.isDeleted && m.shadowed != true, ); if (lastMessage?.user?.id == - StreamChat.of(context).user?.id) { + streamChatState.user?.id) { return Padding( padding: const EdgeInsets.only(right: 4), child: SendingIndicator( @@ -194,6 +196,7 @@ class ChannelPreview extends StatelessWidget { ); Widget _buildSubtitle(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); if (channel.isMuted) { return Row( crossAxisAlignment: CrossAxisAlignment.end, @@ -203,7 +206,7 @@ class ChannelPreview extends StatelessWidget { ), Text( ' Channel is muted', - style: StreamChatTheme.of(context).channelPreviewTheme.subtitle, + style: chatThemeData.channelPreviewTheme.subtitle, ), ], ); @@ -211,7 +214,7 @@ class ChannelPreview extends StatelessWidget { return TypingIndicator( channel: channel, alternativeWidget: _buildLastMessage(context), - style: StreamChatTheme.of(context).channelPreviewTheme.subtitle, + style: chatThemeData.channelPreviewTheme.subtitle, ); } @@ -245,35 +248,24 @@ class ChannelPreview extends StatelessWidget { text = parts.join(' '); + final chatThemeData = StreamChatTheme.of(context); return Text.rich( _getDisplayText( text, lastMessage.mentionedUsers, lastMessage.attachments, - StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - ?.copyWith( - color: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - ?.color, - fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) - ? FontStyle.italic - : FontStyle.normal), - StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - ?.copyWith( - color: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - ?.color, - fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) - ? FontStyle.italic - : FontStyle.normal, - fontWeight: FontWeight.bold, - ), + chatThemeData.channelPreviewTheme.subtitle?.copyWith( + color: chatThemeData.channelPreviewTheme.subtitle?.color, + fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) + ? FontStyle.italic + : FontStyle.normal), + chatThemeData.channelPreviewTheme.subtitle?.copyWith( + color: chatThemeData.channelPreviewTheme.subtitle?.color, + fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + fontWeight: FontWeight.bold, + ), ), maxLines: 1, overflow: TextOverflow.ellipsis, From 435a88cd1b44b3947f722d8e88e2eb8540fbcfa1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 10 May 2021 16:01:20 +0200 Subject: [PATCH 225/599] migrate missing files --- .../lib/src/date_divider.dart | 9 +- .../lib/src/deleted_message.dart | 67 +- .../lib/src/group_image.dart | 15 +- .../lib/src/image_footer.dart | 30 +- .../lib/src/image_header.dart | 99 +- .../lib/src/info_tile.dart | 43 +- .../lib/src/media_list_view.dart | 16 +- .../lib/src/mention_tile.dart | 122 ++- .../lib/src/message_actions_modal.dart | 255 ++--- .../lib/src/message_input.dart | 869 +++++++++--------- .../lib/src/message_list_view.dart | 165 ++-- .../lib/src/message_reactions_modal.dart | 12 +- .../lib/src/message_search_item.dart | 37 +- .../lib/src/message_search_list_view.dart | 7 +- .../lib/src/message_text.dart | 19 +- .../lib/src/message_widget.dart | 120 +-- .../lib/src/option_list_tile.dart | 93 +- .../lib/src/reaction_bubble.dart | 22 +- .../lib/src/reaction_picker.dart | 12 +- .../lib/src/thread_header.dart | 107 +-- .../lib/src/url_attachment.dart | 151 ++- .../lib/src/user_avatar.dart | 5 +- .../lib/src/user_item.dart | 83 +- .../lib/src/user_list_view.dart | 29 +- .../stream_chat_flutter/lib/src/utils.dart | 241 +++-- 25 files changed, 1292 insertions(+), 1336 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/date_divider.dart b/packages/stream_chat_flutter/lib/src/date_divider.dart index f409872b..185683be 100644 --- a/packages/stream_chat_flutter/lib/src/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/date_divider.dart @@ -44,18 +44,19 @@ class DateDivider extends StatelessWidget { if (uppercase) dayInfo = dayInfo.toUpperCase(); + final chatThemeData = StreamChatTheme.of(context); return Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1), decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.overlayDark, + color: chatThemeData.colorTheme.overlayDark, borderRadius: BorderRadius.circular(8), ), child: Text( dayInfo, - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: StreamChatTheme.of(context).colorTheme.white, - ), + style: chatThemeData.textTheme.footnote.copyWith( + color: chatThemeData.colorTheme.white, + ), ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart index 96f5606a..93e70b12 100644 --- a/packages/stream_chat_flutter/lib/src/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart @@ -31,44 +31,41 @@ class DeletedMessage extends StatelessWidget { final bool reverse; @override - Widget build(BuildContext context) => Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: Material( - color: messageTheme.messageBackgroundColor, - shape: shape ?? - RoundedRectangleBorder( - borderRadius: borderRadiusGeometry ?? BorderRadius.zero, - side: borderSide ?? - BorderSide( - color: Theme.of(context).brightness == Brightness.dark - ? StreamChatTheme.of(context) - .colorTheme - .white - .withAlpha(24) - : StreamChatTheme.of(context) - .colorTheme - .black - .withAlpha(24), - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Material( + color: messageTheme.messageBackgroundColor, + shape: shape ?? + RoundedRectangleBorder( + borderRadius: borderRadiusGeometry ?? BorderRadius.zero, + side: borderSide ?? + BorderSide( + color: Theme.of(context).brightness == Brightness.dark + ? chatThemeData.colorTheme.white.withAlpha(24) + : chatThemeData.colorTheme.black.withAlpha(24), + ), ), - child: Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: Text( - 'Message deleted', - style: messageTheme.messageText?.copyWith( - fontStyle: FontStyle.italic, - color: messageTheme.createdAt?.color, - ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + child: Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Text( + 'Message deleted', + style: messageTheme.messageText?.copyWith( + fontStyle: FontStyle.italic, + color: messageTheme.createdAt?.color, ), ), ), ), - ); + ), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index e5c6ecd2..c6778a91 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -46,18 +46,12 @@ class GroupImage extends StatelessWidget { onTap: onTap, child: ClipRRect( borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - ?.borderRadius, + streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius, child: Container( constraints: constraints ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - ?.constraints, + streamChatTheme.ownMessageTheme.avatarTheme?.constraints, decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: streamChatTheme.colorTheme.accentBlue, ), child: Flex( direction: Axis.vertical, @@ -125,8 +119,7 @@ class GroupImage extends StatelessWidget { BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( - color: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, + color: selectionColor ?? streamChatTheme.colorTheme.accentBlue, height: 64, width: 64, child: Padding( diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/image_footer.dart index 9298e73b..cc79192b 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/image_footer.dart @@ -77,16 +77,18 @@ class _ImageFooterState extends State { @override Widget build(BuildContext context) { final showShareButton = !kIsWeb; + final mediaQueryData = MediaQuery.of(context); + final chatThemeData = StreamChatTheme.of(context); return SizedBox.fromSize( size: Size( - MediaQuery.of(context).size.width, - MediaQuery.of(context).padding.bottom + widget.preferredSize.height, + mediaQueryData.size.width, + mediaQueryData.padding.bottom + widget.preferredSize.height, ), child: MediaQuery.removePadding( context: context, removeTop: true, child: BottomAppBar( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -98,7 +100,7 @@ class _ImageFooterState extends State { IconButton( icon: StreamSvgIcon.iconShare( size: 24, - color: StreamChatTheme.of(context).colorTheme.black, + color: chatThemeData.colorTheme.black, ), onPressed: () async { final attachment = @@ -134,8 +136,7 @@ class _ImageFooterState extends State { children: [ Text( '${widget.currentPage + 1} of ${widget.totalPages}', - style: - StreamChatTheme.of(context).textTheme.headlineBold, + style: chatThemeData.textTheme.headlineBold, ), ], ), @@ -143,7 +144,7 @@ class _ImageFooterState extends State { ), IconButton( icon: StreamSvgIcon.iconGrid( - color: StreamChatTheme.of(context).colorTheme.black, + color: chatThemeData.colorTheme.black, ), onPressed: () => _showPhotosModal(context), ), @@ -155,10 +156,11 @@ class _ImageFooterState extends State { } void _showPhotosModal(context) { + final chatThemeData = StreamChatTheme.of(context); showModalBottomSheet( context: context, - barrierColor: StreamChatTheme.of(context).colorTheme.overlay, - backgroundColor: StreamChatTheme.of(context).colorTheme.white, + barrierColor: chatThemeData.colorTheme.overlay, + backgroundColor: chatThemeData.colorTheme.white, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -189,9 +191,7 @@ class _ImageFooterState extends State { padding: const EdgeInsets.all(16), child: Text( 'Photos', - style: StreamChatTheme.of(context) - .textTheme - .headlineBold, + style: chatThemeData.textTheme.headlineBold, ), ), ), @@ -199,7 +199,7 @@ class _ImageFooterState extends State { alignment: Alignment.centerRight, child: IconButton( icon: StreamSvgIcon.close( - color: StreamChatTheme.of(context).colorTheme.black, + color: chatThemeData.colorTheme.black, ), onPressed: () => Navigator.maybePop(context), ), @@ -262,9 +262,7 @@ class _ImageFooterState extends State { boxShadow: [ BoxShadow( blurRadius: 8, - color: StreamChatTheme.of(context) - .colorTheme - .black + color: chatThemeData.colorTheme.black .withOpacity(0.3), ), ], diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/image_header.dart index 0b876aa1..095d7534 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/image_header.dart @@ -50,59 +50,58 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { final int currentIndex; @override - Widget build(BuildContext context) => AppBar( - brightness: Theme.of(context).brightness, - elevation: 1, - leading: showBackButton - ? IconButton( - icon: StreamSvgIcon.close( - color: StreamChatTheme.of(context).colorTheme.black, - size: 24, - ), - onPressed: onBackPressed, - ) - : const SizedBox(), - backgroundColor: - StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, - actions: [ - if (message.type != 'ephemeral') - IconButton( - icon: StreamSvgIcon.iconMenuPoint( - color: StreamChatTheme.of(context).colorTheme.black, + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return AppBar( + brightness: Theme.of(context).brightness, + elevation: 1, + leading: showBackButton + ? IconButton( + icon: StreamSvgIcon.close( + color: chatThemeData.colorTheme.black, + size: 24, ), - onPressed: () { - _showMessageActionModalBottomSheet(context); - }, + onPressed: onBackPressed, + ) + : const SizedBox(), + backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color, + actions: [ + if (message.type != 'ephemeral') + IconButton( + icon: StreamSvgIcon.iconMenuPoint( + color: chatThemeData.colorTheme.black, ), - ], - centerTitle: true, - title: message.type != 'ephemeral' - ? InkWell( - onTap: onTitleTap, - child: SizedBox( - height: preferredSize.height, - width: preferredSize.width, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - userName, - style: - StreamChatTheme.of(context).textTheme.headlineBold, - ), - Text( - sentAt, - style: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle, - ), - ], - ), + onPressed: () { + _showMessageActionModalBottomSheet(context); + }, + ), + ], + centerTitle: true, + title: message.type != 'ephemeral' + ? InkWell( + onTap: onTitleTap, + child: SizedBox( + height: preferredSize.height, + width: preferredSize.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + userName, + style: chatThemeData.textTheme.headlineBold, + ), + Text( + sentAt, + style: chatThemeData.channelPreviewTheme.subtitle, + ), + ], ), - ) - : const SizedBox(), - ); + ), + ) + : const SizedBox(), + ); + } @override final Size preferredSize; diff --git a/packages/stream_chat_flutter/lib/src/info_tile.dart b/packages/stream_chat_flutter/lib/src/info_tile.dart index 81a701c0..1e935942 100644 --- a/packages/stream_chat_flutter/lib/src/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/info_tile.dart @@ -38,26 +38,29 @@ class InfoTile extends StatelessWidget { final Color? backgroundColor; @override - Widget build(BuildContext context) => PortalEntry( - visible: showMessage, - portalAnchor: tileAnchor ?? Alignment.topCenter, - childAnchor: childAnchor ?? Alignment.bottomCenter, - portal: Container( - height: 25, - color: backgroundColor ?? - StreamChatTheme.of(context).colorTheme.grey.withOpacity(0.9), - child: Center( - child: Text( - message, - style: textStyle ?? - StreamChatTheme.of(context).textTheme.body.copyWith( - color: Colors.white, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return PortalEntry( + visible: showMessage, + portalAnchor: tileAnchor ?? Alignment.topCenter, + childAnchor: childAnchor ?? Alignment.bottomCenter, + portal: Container( + height: 25, + color: + backgroundColor ?? chatThemeData.colorTheme.grey.withOpacity(0.9), + child: Center( + child: Text( + message, + style: textStyle ?? + chatThemeData.textTheme.body.copyWith( + color: Colors.white, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), ), - child: child, - ); + ), + child: child, + ); + } } 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 e0d0c27e..fe0070f9 100644 --- a/packages/stream_chat_flutter/lib/src/media_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/media_list_view.dart @@ -56,6 +56,7 @@ class _MediaListViewState extends State { position, ) { final media = _media.elementAt(position); + final chatThemeData = StreamChatTheme.of(context); return Padding( padding: const EdgeInsets.symmetric(horizontal: 1, vertical: 1), child: InkWell( @@ -89,10 +90,8 @@ class _MediaListViewState extends State { ? 1.0 : 0.0, child: Container( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), + color: + chatThemeData.colorTheme.black.withOpacity(0.5), alignment: Alignment.topRight, padding: const EdgeInsets.only( top: 8, @@ -100,13 +99,10 @@ class _MediaListViewState extends State { ), child: CircleAvatar( radius: 12, - backgroundColor: - StreamChatTheme.of(context).colorTheme.white, + backgroundColor: chatThemeData.colorTheme.white, child: StreamSvgIcon.check( size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .black, + color: chatThemeData.colorTheme.black, ), ), ), @@ -128,7 +124,7 @@ class _MediaListViewState extends State { child: Text( media.videoDuration.format(), style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/mention_tile.dart b/packages/stream_chat_flutter/lib/src/mention_tile.dart index f3bed355..99b4b962 100644 --- a/packages/stream_chat_flutter/lib/src/mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/mention_tile.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; - import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// This widget is used for showing user tiles for mentions @@ -32,71 +31,70 @@ class MentionTile extends StatelessWidget { final Widget? trailing; @override - Widget build(BuildContext context) => SizedBox( - height: 56, - child: Row( - children: [ - const SizedBox( - width: 16, - ), - leading ?? - UserAvatar( - constraints: BoxConstraints.tight( - const Size( - 40, - 40, - ), + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return SizedBox( + height: 56, + child: Row( + children: [ + const SizedBox( + width: 16, + ), + leading ?? + UserAvatar( + constraints: BoxConstraints.tight( + const Size( + 40, + 40, ), - user: member.user!, ), - const SizedBox( - width: 8, - ), - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - title ?? - Text( - member.user!.name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context).textTheme.bodyBold, + user: member.user!, + ), + const SizedBox( + width: 8, + ), + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title ?? + Text( + member.user!.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: chatThemeData.textTheme.bodyBold, + ), + const SizedBox( + height: 2, + ), + subtitle ?? + Text( + '@${member.userId}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: chatThemeData.textTheme.footnoteBold.copyWith( + color: chatThemeData.colorTheme.grey, ), - const SizedBox( - height: 2, - ), - subtitle ?? - Text( - '@${member.userId}', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context) - .textTheme - .footnoteBold - .copyWith( - color: - StreamChatTheme.of(context).colorTheme.grey, - ), - ), - ], - ), + ), + ], ), ), - trailing ?? - Padding( - padding: const EdgeInsets.only( - right: 18, - left: 8, - ), - child: StreamSvgIcon.mentions( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ), + ), + trailing ?? + Padding( + padding: const EdgeInsets.only( + right: 18, + left: 8, ), - ], - ), - ); + child: StreamSvgIcon.mentions( + color: chatThemeData.colorTheme.accentBlue, + ), + ), + ], + ), + ); + } } 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 767cdc76..82ce94e2 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -3,12 +3,12 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/message_action.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Constructs a modal with actions for a message class MessageActionsModal extends StatefulWidget { @@ -108,7 +108,8 @@ class _MessageActionsModalState extends State { Widget build(BuildContext context) => _showMessageOptionsModal(); Widget _showMessageOptionsModal() { - final size = MediaQuery.of(context).size; + final mediaQueryData = MediaQuery.of(context); + final size = mediaQueryData.size; final user = StreamChat.of(context).user; final roughMaxSize = 2 * size.width / 3; @@ -133,6 +134,7 @@ class _MessageActionsModalState extends State { final hasFileAttachment = widget.message.attachments.any((it) => it.type == 'file') == true; + final streamChatThemeData = StreamChatTheme.of(context); return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => Navigator.maybePop(context), @@ -145,7 +147,7 @@ class _MessageActionsModalState extends State { sigmaY: 10, ), child: Container( - color: StreamChatTheme.of(context).colorTheme.overlay, + color: streamChatThemeData.colorTheme.overlay, ), ), ), @@ -226,11 +228,9 @@ class _MessageActionsModalState extends State { left: widget.reverse ? 0 : 40, ), child: SizedBox( - width: MediaQuery.of(context).size.width * 0.75, + width: mediaQueryData.size.width * 0.75, child: Material( - color: StreamChatTheme.of(context) - .colorTheme - .whiteSnow, + color: streamChatThemeData.colorTheme.whiteSnow, clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -266,9 +266,8 @@ class _MessageActionsModalState extends State { ].insertBetween( Container( height: 1, - color: StreamChatTheme.of(context) - .colorTheme - .greyWhisper, + color: streamChatThemeData + .colorTheme.greyWhisper, ), ), ), @@ -310,11 +309,12 @@ class _MessageActionsModalState extends State { void _showFlagDialog() async { final client = StreamChat.of(context).client; + final streamChatThemeData = StreamChatTheme.of(context); final answer = await showConfirmationDialog( context, title: 'Flag Message', icon: StreamSvgIcon.flag( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: streamChatThemeData.colorTheme.accentRed, size: 24, ), question: @@ -324,7 +324,7 @@ class _MessageActionsModalState extends State { cancelText: 'CANCEL', ); - final theme = StreamChatTheme.of(context); + final theme = streamChatThemeData; if (answer == true) { try { await client.flagMessage(widget.message.id); @@ -400,48 +400,54 @@ class _MessageActionsModalState extends State { ); } - Widget _buildReplyButton(BuildContext context) => InkWell( - onTap: () { - Navigator.pop(context); - if (widget.onReplyTap != null) { - widget.onReplyTap!(widget.message); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.reply( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Reply', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildReplyButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: () { + Navigator.pop(context); + if (widget.onReplyTap != null) { + widget.onReplyTap!(widget.message); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.reply( + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Reply', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } - Widget _buildFlagButton(BuildContext context) => InkWell( - onTap: _showFlagDialog, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.iconFlag( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Flag Message', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildFlagButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: _showFlagDialog, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.iconFlag( + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Flag Message', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } Widget _buildDeleteButton(BuildContext context) { final isDeleteFailed = @@ -469,54 +475,61 @@ class _MessageActionsModalState extends State { ); } - Widget _buildCopyButton(BuildContext context) => InkWell( - onTap: () async { - widget.onCopyTap?.call(widget.message); - Navigator.pop(context); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.copy( - size: 24, - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Copy Message', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildCopyButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: () async { + widget.onCopyTap?.call(widget.message); + Navigator.pop(context); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.copy( + size: 24, + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Copy Message', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } - Widget _buildEditMessage(BuildContext context) => InkWell( - onTap: () async { - Navigator.pop(context); - _showEditBottomSheet(context); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.edit( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Edit Message', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildEditMessage(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: () async { + Navigator.pop(context); + _showEditBottomSheet(context); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.edit( + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Edit Message', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } Widget _buildResendMessage(BuildContext context) { final isUpdateFailed = widget.message.status == MessageSendingStatus.failed_update; + final streamChatThemeData = StreamChatTheme.of(context); return InkWell( onTap: () { Navigator.pop(context); @@ -532,12 +545,12 @@ class _MessageActionsModalState extends State { child: Row( children: [ StreamSvgIcon.circleUp( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: streamChatThemeData.colorTheme.accentBlue, ), const SizedBox(width: 16), Text( isUpdateFailed ? 'Resend Edited Message' : 'Resend', - style: StreamChatTheme.of(context).textTheme.body, + style: streamChatThemeData.textTheme.body, ), ], ), @@ -547,13 +560,13 @@ class _MessageActionsModalState extends State { void _showEditBottomSheet(BuildContext context) { final channel = StreamChannel.of(context).channel; + final streamChatThemeData = StreamChatTheme.of(context); showModalBottomSheet( context: context, elevation: 2, clipBehavior: Clip.hardEdge, isScrollControlled: true, - backgroundColor: - StreamChatTheme.of(context).messageInputTheme.inputBackground, + backgroundColor: streamChatThemeData.messageInputTheme.inputBackground, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16), @@ -575,8 +588,7 @@ class _MessageActionsModalState extends State { Padding( padding: const EdgeInsets.all(8), child: StreamSvgIcon.edit( - color: - StreamChatTheme.of(context).colorTheme.greyGainsboro, + color: streamChatThemeData.colorTheme.greyGainsboro, ), ), const Text( @@ -608,27 +620,30 @@ class _MessageActionsModalState extends State { ); } - Widget _buildThreadReplyButton(BuildContext context) => InkWell( - onTap: () { - Navigator.pop(context); - if (widget.onThreadReplyTap != null) { - widget.onThreadReplyTap!(widget.message); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.thread( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Thread Reply', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildThreadReplyButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: () { + Navigator.pop(context); + if (widget.onThreadReplyTap != null) { + widget.onThreadReplyTap!(widget.message); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.thread( + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Thread Reply', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 44b54c24..a5ccdb6f 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -12,6 +12,7 @@ import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.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/quoted_message_widget.dart'; @@ -19,11 +20,10 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:substring_highlight/substring_highlight.dart'; import 'package:video_compress/video_compress.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Builder for attachment thumbnails typedef AttachmentThumbnailBuilder = Widget Function( @@ -299,8 +299,9 @@ class MessageInputState extends State { @override Widget build(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); Widget child = Container( - color: StreamChatTheme.of(context).messageInputTheme.inputBackground, + color: streamChatThemeData.messageInputTheme.inputBackground, child: SafeArea( child: GestureDetector( onPanUpdate: (details) { @@ -325,9 +326,7 @@ class MessageInputState extends State { Padding( padding: const EdgeInsets.all(8), child: StreamSvgIcon.reply( - color: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, + color: streamChatThemeData.colorTheme.greyGainsboro, ), ), const Text( @@ -385,68 +384,65 @@ class MessageInputState extends State { ], ); - Widget _buildDmCheckbox() => Row( - children: [ - Container( - height: 16, - width: 16, - foregroundDecoration: BoxDecoration( - border: _sendAsDm - ? null - : Border.all( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - width: 2, - ), + Widget _buildDmCheckbox() { + final streamChatThemeData = StreamChatTheme.of(context); + return Row( + children: [ + Container( + height: 16, + width: 16, + foregroundDecoration: BoxDecoration( + border: _sendAsDm + ? null + : Border.all( + color: streamChatThemeData.colorTheme.black.withOpacity(.5), + width: 2, + ), + borderRadius: BorderRadius.circular(3), + ), + child: Center( + child: Material( borderRadius: BorderRadius.circular(3), - ), - child: Center( - child: Material( - borderRadius: BorderRadius.circular(3), - color: _sendAsDm - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context).colorTheme.white, - child: InkWell( - onTap: () { - setState(() { - _sendAsDm = !_sendAsDm; - }); - }, - child: AnimatedCrossFade( - duration: const Duration(milliseconds: 300), - reverseDuration: const Duration(milliseconds: 300), - crossFadeState: _sendAsDm - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: StreamSvgIcon.check( - size: 16, - color: StreamChatTheme.of(context).colorTheme.white, - ), - secondChild: const SizedBox( - height: 16, - width: 16, - ), + color: _sendAsDm + ? streamChatThemeData.colorTheme.accentBlue + : streamChatThemeData.colorTheme.white, + child: InkWell( + onTap: () { + setState(() { + _sendAsDm = !_sendAsDm; + }); + }, + child: AnimatedCrossFade( + duration: const Duration(milliseconds: 300), + reverseDuration: const Duration(milliseconds: 300), + crossFadeState: _sendAsDm + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: StreamSvgIcon.check( + size: 16, + color: streamChatThemeData.colorTheme.white, + ), + secondChild: const SizedBox( + height: 16, + width: 16, ), ), ), ), ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Text( - 'Also send as direct message', - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), - ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + 'Also send as direct message', + style: streamChatThemeData.textTheme.footnote.copyWith( + color: streamChatThemeData.colorTheme.black.withOpacity(0.5), ), ), - ], - ); + ), + ], + ); + } Widget _animateSendButton(BuildContext context) { final sendButton = widget.activeSendButton != null @@ -636,12 +632,9 @@ class MessageInputState extends State { ), Text( _chosenCommand?.name.toUpperCase() ?? '', - style: StreamChatTheme.of(context) - .textTheme - .footnoteBold - .copyWith( - color: Colors.white, - ), + style: theme.textTheme.footnoteBold.copyWith( + color: Colors.white, + ), ), ], ), @@ -839,116 +832,110 @@ class MessageInputState extends State { tween: Tween(begin: 0, end: 1), duration: const Duration(milliseconds: 300), curve: Curves.easeInOutExpo, - builder: (context, val, wid) => Transform.scale( - scale: val, - child: Padding( - padding: const EdgeInsets.all(8), - child: Card( - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - color: StreamChatTheme.of(context).colorTheme.white, - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose( - const Size.fromHeight(400)), - decoration: BoxDecoration( - color: StreamChatTheme.of(context) - .colorTheme - .white, - borderRadius: BorderRadius.circular(8)), - child: ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - if (commands.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - ), - child: StreamSvgIcon.lightning( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), + builder: (context, val, wid) { + final streamChatThemeData = StreamChatTheme.of(context); + return Transform.scale( + scale: val, + child: Padding( + padding: const EdgeInsets.all(8), + child: Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + color: streamChatThemeData.colorTheme.white, + clipBehavior: Clip.antiAlias, + child: Container( + constraints: BoxConstraints.loose( + const Size.fromHeight(400)), + decoration: BoxDecoration( + color: streamChatThemeData.colorTheme.white, + borderRadius: BorderRadius.circular(8)), + child: ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + children: [ + if (commands.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, ), - Text( - 'Instant Commands', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - ) - ], - ), - ), - const SizedBox( - height: 10, - ), - ...commands - .map( - (c) => InkWell( - onTap: () { - _setCommand(c); - }, - child: SizedBox( - height: 40, - child: Row( - children: [ - const SizedBox( - width: 16, - ), - _buildCommandIcon(c.name), - const SizedBox( - width: 8, - ), - Text.rich( - TextSpan( - text: c.name.capitalize(), - style: const TextStyle( - fontWeight: - FontWeight.bold), - children: [ - TextSpan( - text: - ' /${c.name} ${c.args}', - style: - StreamChatTheme.of( - context) - .textTheme - .body - .copyWith( - // ignore: lines_longer_than_80_chars - color: StreamChatTheme.of( - // ignore: lines_longer_than_80_chars - context) - // ignore: lines_longer_than_80_chars - .colorTheme - .grey, - ), - ), - ], - ), - ), - ], - ), + child: StreamSvgIcon.lightning( + color: streamChatThemeData + .colorTheme.accentBlue, ), ), - ) - .toList(), - ], - ), + Text( + 'Instant Commands', + style: TextStyle( + color: streamChatThemeData + .colorTheme.black + .withOpacity(.5), + ), + ) + ], + ), + ), + const SizedBox( + height: 10, + ), + ...commands + .map( + (c) => InkWell( + onTap: () { + _setCommand(c); + }, + child: SizedBox( + height: 40, + child: Row( + children: [ + const SizedBox( + width: 16, + ), + _buildCommandIcon(c.name), + const SizedBox( + width: 8, + ), + Text.rich( + TextSpan( + text: c.name.capitalize(), + style: const TextStyle( + fontWeight: + FontWeight.bold), + children: [ + TextSpan( + text: + ' /${c.name} ${c.args}', + style: streamChatThemeData + .textTheme.body + .copyWith( + // ignore: lines_longer_than_80_chars + color: streamChatThemeData + // ignore: lines_longer_than_80_chars + .colorTheme + .grey, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ) + .toList(), + ], ), ), ), - )), + ), + ); + }), )); } @@ -956,37 +943,30 @@ class MessageInputState extends State { final _attachmentContainsFile = _attachments.values.any((it) => it.type == 'file'); + final chatThemeData = StreamChatTheme.of(context); Color _getIconColor(int index) { + final streamChatThemeData = chatThemeData; switch (index) { case 0: return _attachments.isEmpty - ? StreamChatTheme.of(context).colorTheme.accentBlue + ? streamChatThemeData.colorTheme.accentBlue : (!_attachmentContainsFile - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.2)); + ? streamChatThemeData.colorTheme.accentBlue + : streamChatThemeData.colorTheme.black.withOpacity(0.2)); case 1: return _attachmentContainsFile - ? StreamChatTheme.of(context).colorTheme.accentBlue + ? streamChatThemeData.colorTheme.accentBlue : (_attachments.isEmpty - ? StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5) - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.2)); + ? streamChatThemeData.colorTheme.black.withOpacity(0.5) + : streamChatThemeData.colorTheme.black.withOpacity(0.2)); case 2: return _attachmentContainsFile && _attachments.isNotEmpty - ? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) - : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); + ? streamChatThemeData.colorTheme.black.withOpacity(0.2) + : streamChatThemeData.colorTheme.black.withOpacity(0.5); case 3: return _attachmentContainsFile && _attachments.isNotEmpty - ? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) - : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); + ? streamChatThemeData.colorTheme.black.withOpacity(0.2) + : streamChatThemeData.colorTheme.black.withOpacity(0.5); default: return Colors.black; } @@ -997,7 +977,7 @@ class MessageInputState extends State { _animateContainer ? const Duration(milliseconds: 300) : Duration.zero, height: _openFilePickerSection ? _filePickerSize : 0, child: Material( - color: StreamChatTheme.of(context).colorTheme.whiteSmoke, + color: chatThemeData.colorTheme.whiteSmoke, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -1061,7 +1041,7 @@ class MessageInputState extends State { }, child: Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), @@ -1076,8 +1056,7 @@ class MessageInputState extends State { width: 40, height: 4, decoration: BoxDecoration( - color: - StreamChatTheme.of(context).colorTheme.whiteSmoke, + color: chatThemeData.colorTheme.whiteSmoke, borderRadius: BorderRadius.circular(4), ), ), @@ -1090,7 +1069,7 @@ class MessageInputState extends State { Expanded( child: Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, borderRadius: BorderRadius.circular(8), ), child: _PickerWidget( @@ -1166,6 +1145,7 @@ class MessageInputState extends State { } Widget _buildCommandIcon(String iconType) { + final chatThemeData = StreamChatTheme.of(context); switch (iconType) { case 'giphy': return CircleAvatar( @@ -1176,7 +1156,7 @@ class MessageInputState extends State { ); case 'ban': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.iconUserDelete( size: 16, @@ -1185,7 +1165,7 @@ class MessageInputState extends State { ); case 'flag': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.flag( size: 14, @@ -1194,7 +1174,7 @@ class MessageInputState extends State { ); case 'imgur': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: ClipOval( child: StreamSvgIcon.imgur( @@ -1204,7 +1184,7 @@ class MessageInputState extends State { ); case 'mute': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.mute( size: 16, @@ -1213,7 +1193,7 @@ class MessageInputState extends State { ); case 'unban': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.userAdd( size: 16, @@ -1222,7 +1202,7 @@ class MessageInputState extends State { ); case 'unmute': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.volumeUp( size: 16, @@ -1231,7 +1211,7 @@ class MessageInputState extends State { ); default: return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.lightning( size: 16, @@ -1249,17 +1229,14 @@ class MessageInputState extends State { Future>? queryMembers; + final channelState = StreamChannel.of(context); if (query.isNotEmpty) { - queryMembers = StreamChannel.of(context) - .channel + queryMembers = channelState.channel .queryMembers(filter: Filter.autoComplete('name', query)) .then((res) => res.members); } - final members = StreamChannel.of(context) - .channel - .state - ?.members + final members = channelState.channel.state?.members .where((m) => m.user?.name.toLowerCase().contains(query) == true) .toList() ?? []; @@ -1281,76 +1258,78 @@ class MessageInputState extends State { tween: Tween(begin: 0, end: 1), duration: const Duration(milliseconds: 300), curve: Curves.easeInOutExpo, - builder: (context, val, wid) => Transform.scale( - scale: val, - child: Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: StreamChatTheme.of(context).colorTheme.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose(const Size.fromHeight(240)), - decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + builder: (context, val, wid) { + final chatThemeData = StreamChatTheme.of(context); + return Transform.scale( + scale: val, + child: Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: chatThemeData.colorTheme.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), ), - child: FutureBuilder>( - future: queryMembers ?? Future.value(members), - initialData: members, - builder: (context, snapshot) => ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - const SizedBox( - height: 8, - ), - ...snapshot.data! - .where((it) => it.user != null) - .map( - (m) => Material( - color: - StreamChatTheme.of(context).colorTheme.white, - child: InkWell( - onTap: () { - if (m.user != null) { - _mentionedUsers.add(m.user!); - } + clipBehavior: Clip.antiAlias, + child: Container( + constraints: BoxConstraints.loose(const Size.fromHeight(240)), + decoration: BoxDecoration( + color: chatThemeData.colorTheme.white, + ), + child: FutureBuilder>( + future: queryMembers ?? Future.value(members), + initialData: members, + builder: (context, snapshot) => ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + children: [ + const SizedBox( + height: 8, + ), + ...snapshot.data! + .where((it) => it.user != null) + .map( + (m) => Material( + color: chatThemeData.colorTheme.white, + child: InkWell( + onTap: () { + if (m.user != null) { + _mentionedUsers.add(m.user!); + } - splits[splits.length - 1] = m.user!.name; - final rejoin = splits.join('@'); + splits[splits.length - 1] = m.user!.name; + final rejoin = splits.join('@'); - textEditingController.value = - TextEditingValue( - text: rejoin + - textEditingController.text.substring( - textEditingController - .selection.start), - selection: TextSelection.collapsed( - offset: rejoin.length, - ), - ); - _debounce!.cancel(); - _mentionsOverlay?.remove(); - _mentionsOverlay = null; - }, - child: widget.mentionsTileBuilder != null - ? widget.mentionsTileBuilder!(context, m) - : MentionTile(m), + textEditingController.value = + TextEditingValue( + text: rejoin + + textEditingController.text.substring( + textEditingController + .selection.start), + selection: TextSelection.collapsed( + offset: rejoin.length, + ), + ); + _debounce!.cancel(); + _mentionsOverlay?.remove(); + _mentionsOverlay = null; + }, + child: widget.mentionsTileBuilder != null + ? widget.mentionsTileBuilder!(context, m) + : MentionTile(m), + ), ), - ), - ) - .toList(), - const SizedBox( - height: 8, - ), - ], + ) + .toList(), + const SizedBox( + height: 8, + ), + ], + ), ), ), ), - ), - ), + ); + }, ), ), ); @@ -1379,91 +1358,88 @@ class MessageInputState extends State { final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; - return OverlayEntry( - builder: (context) => Positioned( - bottom: size.height + MediaQuery.of(context).viewInsets.bottom, - left: 0, - right: 0, - child: Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: StreamChatTheme.of(context).colorTheme.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + return OverlayEntry(builder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return Positioned( + bottom: size.height + MediaQuery.of(context).viewInsets.bottom, + left: 0, + right: 0, + child: Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: chatThemeData.colorTheme.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.antiAlias, + child: Container( + constraints: BoxConstraints.loose(const Size.fromHeight(200)), + decoration: BoxDecoration( + boxShadow: const [ + BoxShadow( + spreadRadius: -8, + blurRadius: 5, + offset: Offset(0, -4), ), - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose(const Size.fromHeight(200)), - decoration: BoxDecoration( - boxShadow: const [ - BoxShadow( - spreadRadius: -8, - blurRadius: 5, - offset: Offset(0, -4), - ), - ], - color: StreamChatTheme.of(context).colorTheme.white, - ), - child: ListView.builder( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - itemCount: emojis.length + 1, - itemBuilder: (context, i) { - if (i == 0) { - return Padding( - padding: const EdgeInsets.only(left: 8, top: 8), - child: Row( - children: [ - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 8), - child: StreamSvgIcon.smile( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), - ), - Flexible( - child: Text( - 'Emoji matching "$query"', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - ), - ) - ], + ], + color: chatThemeData.colorTheme.white, + ), + child: ListView.builder( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + itemCount: emojis.length + 1, + itemBuilder: (context, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.only(left: 8, top: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: StreamSvgIcon.smile( + color: chatThemeData.colorTheme.accentBlue, ), - ); - } - - final emoji = emojis.elementAt(i - 1)!; - return ListTile( - title: SubstringHighlight( - text: - // ignore: lines_longer_than_80_chars - "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", - term: query, - textStyleHighlight: - Theme.of(context).textTheme.headline6!.copyWith( - fontSize: 14.5, - fontWeight: FontWeight.bold, - ), - textStyle: - Theme.of(context).textTheme.headline6!.copyWith( - fontSize: 14.5, - ), ), - onTap: () { - _chooseEmoji(splits, emoji); - }, - ); - }), - ), - ), - )); + Flexible( + child: Text( + 'Emoji matching "$query"', + style: TextStyle( + color: chatThemeData.colorTheme.black + .withOpacity(.5), + ), + ), + ) + ], + ), + ); + } + + final emoji = emojis.elementAt(i - 1)!; + final themeData = Theme.of(context); + return ListTile( + title: SubstringHighlight( + text: + // ignore: lines_longer_than_80_chars + "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", + term: query, + textStyleHighlight: + themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + fontWeight: FontWeight.bold, + ), + textStyle: themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + ), + ), + onTap: () { + _chooseEmoji(splits, emoji); + }, + ); + }), + ), + ), + ); + }); } void _chooseEmoji(List splits, Emoji emoji) { @@ -1589,30 +1565,32 @@ class MessageInputState extends State { ); } - Widget _buildRemoveButton(Attachment attachment) => SizedBox( - height: 24, - width: 24, - child: RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - hoverElevation: 0, - onPressed: () { - setState(() => _attachments.remove(attachment.id)); - }, - fillColor: - StreamChatTheme.of(context).colorTheme.black.withOpacity(.5), - child: Center( - child: StreamSvgIcon.close( - size: 24, - color: StreamChatTheme.of(context).colorTheme.white, - ), + Widget _buildRemoveButton(Attachment attachment) { + final chatThemeData = StreamChatTheme.of(context); + return SizedBox( + height: 24, + width: 24, + child: RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + hoverElevation: 0, + onPressed: () { + setState(() => _attachments.remove(attachment.id)); + }, + fillColor: chatThemeData.colorTheme.black.withOpacity(.5), + child: Center( + child: StreamSvgIcon.close( + size: 24, + color: chatThemeData.colorTheme.white, ), ), - ); + ), + ); + } Widget _buildAttachment(Attachment attachment) { if (widget.attachmentThumbnailBuilders?.containsKey(attachment.type) == @@ -1642,18 +1620,18 @@ class MessageInputState extends State { fit: BoxFit.cover, errorWidget: (_, obj, trace) => getFileTypeImage(attachment.extraData['other'] as String?), - progressIndicatorBuilder: (context, _, progress) => - Shimmer.fromColors( - baseColor: - StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - child: Image.asset( - 'images/placeholder.png', - fit: BoxFit.cover, - package: 'stream_chat_flutter', - ), - ), + progressIndicatorBuilder: (context, _, progress) { + final chatThemeData = StreamChatTheme.of(context); + return Shimmer.fromColors( + baseColor: chatThemeData.colorTheme.greyGainsboro, + highlightColor: chatThemeData.colorTheme.whiteSmoke, + child: Image.asset( + 'images/placeholder.png', + fit: BoxFit.cover, + package: 'stream_chat_flutter', + ), + ); + }, ); case 'video': return Stack( @@ -1685,17 +1663,14 @@ class MessageInputState extends State { Widget _buildCommandButton() { final s = textEditingController.text.trim(); + final chatThemeData = StreamChatTheme.of(context); return IconButton( icon: StreamSvgIcon.lightning( color: s.isNotEmpty - ? StreamChatTheme.of(context).colorTheme.greyGainsboro + ? chatThemeData.colorTheme.greyGainsboro : (_commandsOverlay != null - ? StreamChatTheme.of(context) - .messageInputTheme - .actionButtonColor - : StreamChatTheme.of(context) - .messageInputTheme - .actionButtonIdleColor), + ? chatThemeData.messageInputTheme.actionButtonColor + : chatThemeData.messageInputTheme.actionButtonIdleColor), ), padding: const EdgeInsets.all(0), constraints: const BoxConstraints.tightFor( @@ -1730,39 +1705,40 @@ class MessageInputState extends State { ); } - Widget _buildAttachmentButton() => IconButton( - icon: StreamSvgIcon.attach( - color: _openFilePickerSection - ? StreamChatTheme.of(context).messageInputTheme.actionButtonColor - : StreamChatTheme.of(context) - .messageInputTheme - .actionButtonIdleColor, - ), - padding: const EdgeInsets.all(0), - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), - splashRadius: 24, - onPressed: () async { - _emojiOverlay?.remove(); - _emojiOverlay = null; - _commandsOverlay?.remove(); - _commandsOverlay = null; - _mentionsOverlay?.remove(); - _mentionsOverlay = null; + Widget _buildAttachmentButton() { + final chatThemeData = StreamChatTheme.of(context); + return IconButton( + icon: StreamSvgIcon.attach( + color: _openFilePickerSection + ? chatThemeData.messageInputTheme.actionButtonColor + : chatThemeData.messageInputTheme.actionButtonIdleColor, + ), + padding: const EdgeInsets.all(0), + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, + onPressed: () async { + _emojiOverlay?.remove(); + _emojiOverlay = null; + _commandsOverlay?.remove(); + _commandsOverlay = null; + _mentionsOverlay?.remove(); + _mentionsOverlay = null; - if (_openFilePickerSection) { - setState(() { - _animateContainer = true; - _openFilePickerSection = false; - _filePickerSize = _kMinMediaPickerSize; - }); - } else { - showAttachmentModal(); - } - }, - ); + if (_openFilePickerSection) { + setState(() { + _animateContainer = true; + _openFilePickerSection = false; + _filePickerSize = _kMinMediaPickerSize; + }); + } else { + showAttachmentModal(); + } + }, + ); + } /// Show the attachment modal, making the user choose where to /// pick a media from @@ -2103,8 +2079,9 @@ class MessageInputState extends State { StreamSubscription? _keyboardListener; void _showErrorAlert(String description) { + final chatThemeData = StreamChatTheme.of(context); showModalBottomSheet( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, + backgroundColor: chatThemeData.colorTheme.white, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -2118,7 +2095,7 @@ class MessageInputState extends State { height: 26, ), StreamSvgIcon.error( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: chatThemeData.colorTheme.accentRed, size: 24, ), const SizedBox( @@ -2126,7 +2103,7 @@ class MessageInputState extends State { ), Text( 'Something went wrong', - style: StreamChatTheme.of(context).textTheme.headlineBold, + style: chatThemeData.textTheme.headlineBold, ), const SizedBox( height: 7, @@ -2142,8 +2119,7 @@ class MessageInputState extends State { height: 36, ), Container( - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), + color: chatThemeData.colorTheme.black.withOpacity(.08), height: 1, ), Row( @@ -2155,13 +2131,8 @@ class MessageInputState extends State { }, child: Text( 'OK', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue), + style: chatThemeData.textTheme.bodyBold + .copyWith(color: chatThemeData.colorTheme.accentBlue), ), ), ], @@ -2291,6 +2262,7 @@ class __PickerWidgetState extends State<_PickerWidget> { return const Center(child: CircularProgressIndicator()); } + final chatThemeData = StreamChatTheme.of(context); if (snapshot.data!) { if (widget.containsFile) { return GestureDetector( @@ -2299,12 +2271,12 @@ class __PickerWidgetState extends State<_PickerWidget> { }, child: Container( constraints: const BoxConstraints.expand(), - color: StreamChatTheme.of(context).colorTheme.whiteSmoke, + color: chatThemeData.colorTheme.whiteSmoke, alignment: Alignment.center, child: Text( 'Add more files', style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentBlue, fontWeight: FontWeight.bold, ), ), @@ -2322,7 +2294,7 @@ class __PickerWidgetState extends State<_PickerWidget> { PhotoManager.openSetting(); }, child: Container( - color: StreamChatTheme.of(context).colorTheme.whiteSmoke, + color: chatThemeData.colorTheme.whiteSmoke, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -2331,27 +2303,22 @@ class __PickerWidgetState extends State<_PickerWidget> { 'svgs/icon_picture_empty_state.svg', package: 'stream_chat_flutter', height: 140, - color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + color: chatThemeData.colorTheme.greyGainsboro, ), Text( // ignore: lines_longer_than_80_chars 'Please enable access to your photos \nand videos so you can share them with friends.', - style: StreamChatTheme.of(context).textTheme.body.copyWith( - color: StreamChatTheme.of(context).colorTheme.grey), + style: chatThemeData.textTheme.body + .copyWith(color: chatThemeData.colorTheme.grey), textAlign: TextAlign.center, ), const SizedBox(height: 6), Center( child: Text( 'Allow access to your gallery', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.accentBlue, + ), ), ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 2800a041..661ce885 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -6,15 +6,15 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:rxdart/rxdart.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/message_widget.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; +import 'package:stream_chat_flutter/src/swipeable.dart'; import 'package:stream_chat_flutter/src/system_message.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:visibility_detector/visibility_detector.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; -import 'package:stream_chat_flutter/src/swipeable.dart'; /// Widget builder for message typedef MessageBuilder = Widget Function( @@ -274,15 +274,15 @@ class _MessageListViewState extends State { bool _showScrollToBottom = false; late final ItemPositionsListener _itemPositionListener; int? _messageListLength; - StreamChannelState? streamChannel; + late StreamChannelState streamChannel; int? get _initialIndex { if (widget.initialScrollIndex != null) return widget.initialScrollIndex; - if (streamChannel!.initialMessageId != null) { - final messages = streamChannel!.channel.state!.messages; + if (streamChannel.initialMessageId != null) { + final messages = streamChannel.channel.state!.messages; final totalMessages = messages.length; final messageIndex = - messages.indexWhere((e) => e.id == streamChannel!.initialMessageId); + messages.indexWhere((e) => e.id == streamChannel.initialMessageId); final index = totalMessages - messageIndex; if (index != 0) return index - 1; return index; @@ -295,9 +295,9 @@ class _MessageListViewState extends State { return 0; } - bool _isInitialMessage(String id) => streamChannel!.initialMessageId == id; + bool _isInitialMessage(String id) => streamChannel.initialMessageId == id; - bool get _upToDate => streamChannel!.channel.state!.isUpToDate; + bool get _upToDate => streamChannel.channel.state!.isUpToDate; bool get _isThreadConversation => widget.parentMessage != null; @@ -316,46 +316,37 @@ class _MessageListViewState extends State { final MessageListController _messageListController = MessageListController(); @override - Widget build(BuildContext context) => MessageListCore( - messageFilter: widget.messageFilter, - loadingBuilder: widget.loadingBuilder ?? - (context) => const Center( - child: CircularProgressIndicator(), + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return MessageListCore( + messageFilter: widget.messageFilter, + loadingBuilder: widget.loadingBuilder ?? + (context) => const Center( + child: CircularProgressIndicator(), + ), + emptyBuilder: widget.emptyBuilder ?? + (context) => Center( + child: Text( + 'No chats here yet...', + style: chatThemeData.textTheme.footnote.copyWith( + color: chatThemeData.colorTheme.black.withOpacity(.5)), ), - emptyBuilder: widget.emptyBuilder ?? - (context) => Center( - child: Text( - 'No chats here yet...', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5)), - ), + ), + messageListBuilder: + widget.messageListBuilder ?? (context, list) => _buildListView(list), + messageListController: _messageListController, + parentMessage: widget.parentMessage, + showScrollToBottom: widget.showScrollToBottom, + errorWidgetBuilder: widget.errorWidgetBuilder ?? + (BuildContext context, Object error) => Center( + child: Text( + 'Something went wrong', + style: chatThemeData.textTheme.footnote.copyWith( + color: chatThemeData.colorTheme.black.withOpacity(.5)), ), - messageListBuilder: widget.messageListBuilder ?? - (context, list) => _buildListView(list), - messageListController: _messageListController, - parentMessage: widget.parentMessage, - showScrollToBottom: widget.showScrollToBottom, - errorWidgetBuilder: widget.errorWidgetBuilder ?? - (BuildContext context, Object error) => Center( - child: Text( - 'Something went wrong', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5)), - ), - ), - ); + ), + ); + } Widget _buildListView(List data) { messages = data; @@ -448,10 +439,10 @@ class _MessageListViewState extends State { if (i == 0) return const SizedBox(height: 30); if (i == messages.length + 1) { final replyCount = widget.parentMessage!.replyCount; + final chatThemeData = StreamChatTheme.of(context); return Container( decoration: BoxDecoration( - gradient: - StreamChatTheme.of(context).colorTheme.bgGradient, + gradient: chatThemeData.colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.all(8), @@ -459,10 +450,8 @@ class _MessageListViewState extends State { // ignore: lines_longer_than_80_chars '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', textAlign: TextAlign.center, - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .subtitle, + style: chatThemeData + .channelTheme.channelHeaderTheme.subtitle, ), ), ); @@ -616,8 +605,8 @@ class _MessageListViewState extends State { Widget _buildScrollToBottom() => StreamBuilder>( stream: Rx.combineLatest2( - streamChannel!.channel.state!.isUpToDateStream, - streamChannel!.channel.state!.unreadCountStream, + streamChannel.channel.state!.isUpToDateStream, + streamChannel.channel.state!.unreadCountStream, (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), ), builder: (_, snapshot) { @@ -633,8 +622,9 @@ class _MessageListViewState extends State { } final unreadCount = snapshot.data!.item2; final showUnreadCount = unreadCount > 0 && - streamChannel!.channel.state!.members.any((e) => - e.userId == streamChannel!.channel.client.state.user!.id); + streamChannel.channel.state!.members.any((e) => + e.userId == streamChannel.channel.client.state.user!.id); + final chatThemeData = StreamChatTheme.of(context); return Positioned( bottom: 8, right: 8, @@ -644,15 +634,15 @@ class _MessageListViewState extends State { clipBehavior: Clip.none, children: [ FloatingActionButton( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, + backgroundColor: chatThemeData.colorTheme.white, onPressed: () { if (unreadCount > 0) { - streamChannel!.channel.markRead(); + streamChannel.channel.markRead(); } if (!_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; - streamChannel!.reloadChannel(); + streamChannel.reloadChannel(); } else { setState(() => _showScrollToBottom = false); _scrollController!.scrollTo( @@ -663,7 +653,7 @@ class _MessageListViewState extends State { } }, child: StreamSvgIcon.down( - color: StreamChatTheme.of(context).colorTheme.black, + color: chatThemeData.colorTheme.black, ), ), if (showUnreadCount) @@ -692,12 +682,12 @@ class _MessageListViewState extends State { ); Widget _buildLoadingIndicator( - StreamChannelState? streamChannel, + StreamChannelState streamChannel, QueryDirection direction, ) { final stream = direction == QueryDirection.top - ? streamChannel!.queryTopMessages - : streamChannel!.queryBottomMessages; + ? streamChannel.queryTopMessages + : streamChannel.queryBottomMessages; return StreamBuilder( key: const Key('LOADING-INDICATOR'), stream: stream, @@ -764,7 +754,7 @@ class _MessageListViewState extends State { BuildContext context, Message message, List messages, - StreamChannelState? streamChannel, + StreamChannelState streamChannel, ) { Widget messageWidget; if (widget.messageBuilder != null) { @@ -790,7 +780,7 @@ class _MessageListViewState extends State { onVisibilityChanged: (visibility) { final isVisible = visibility.visibleBounds != Rect.zero; if (isVisible) { - final channel = streamChannel!.channel; + final channel = streamChannel.channel; if (_upToDate && channel.config?.readEvents == true && channel.state!.unreadCount! > 0) { @@ -811,6 +801,7 @@ class _MessageListViewState extends State { final isMyMessage = message.user!.id == StreamChat.of(context).user!.id; final isOnlyEmoji = message.text!.isOnlyEmoji; + final chatThemeData = StreamChatTheme.of(context); return MessageWidget( showReplyMessage: false, showResendMessage: false, @@ -837,8 +828,8 @@ class _MessageListViewState extends State { borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null, showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, messageTheme: isMyMessage - ? StreamChatTheme.of(context).ownMessageTheme - : StreamChatTheme.of(context).otherMessageTheme, + ? chatThemeData.ownMessageTheme + : chatThemeData.otherMessageTheme, onShowMessage: widget.onShowMessage, onReturnAction: (action) { switch (action) { @@ -897,7 +888,7 @@ class _MessageListViewState extends State { ); } - final channel = streamChannel!.channel; + final channel = streamChannel.channel; final readList = channel.state?.read?.where((read) { if (read.user.id == userId) return false; return read.lastRead.isAfter(message.createdAt) || @@ -946,6 +937,7 @@ class _MessageListViewState extends State { ? BorderSide.none : null; + final chatThemeData = StreamChatTheme.of(context); Widget child = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), message: message, @@ -970,7 +962,7 @@ class _MessageListViewState extends State { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); } else { - await streamChannel!.loadChannelAtMessage(quotedMessageId).then((_) { + await streamChannel.loadChannelAtMessage(quotedMessageId).then((_) { WidgetsBinding.instance!.addPostFrameCallback((_) { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); @@ -1013,8 +1005,8 @@ class _MessageListViewState extends State { horizontal: isOnlyEmoji ? 0 : 16.0, ), messageTheme: isMyMessage - ? StreamChatTheme.of(context).ownMessageTheme - : StreamChatTheme.of(context).otherMessageTheme, + ? chatThemeData.ownMessageTheme + : chatThemeData.otherMessageTheme, readList: readList, allRead: allRead, onShowMessage: widget.onShowMessage, @@ -1054,7 +1046,7 @@ class _MessageListViewState extends State { widget.onMessageSwiped?.call(message); }, backgroundIcon: StreamSvgIcon.reply( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentBlue, ), child: child, ), @@ -1064,7 +1056,7 @@ class _MessageListViewState extends State { if (!initialMessageHighlightComplete && widget.highlightInitialMessage && _isInitialMessage(message.id)) { - final colorTheme = StreamChatTheme.of(context).colorTheme; + final colorTheme = chatThemeData.colorTheme; final highlightColor = widget.messageHighlightColor ?? colorTheme.highlight; child = TweenAnimationBuilder( @@ -1100,14 +1092,23 @@ class _MessageListViewState extends State { initialIndex = _initialIndex; initialAlignment = _initialAlignment; + _getOnThreadTap(); + super.initState(); + } + + @override + void didChangeDependencies() { + streamChannel = StreamChannel.of(context); + + _messageNewListener?.cancel(); _messageNewListener = - streamChannel!.channel.on(EventType.messageNew).listen((event) { + streamChannel.channel.on(EventType.messageNew).listen((event) { if (_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; } if (event.message!.user!.id == - streamChannel!.channel.client.state.user!.id) { + streamChannel.channel.client.state.user!.id) { WidgetsBinding.instance!.addPostFrameCallback((_) { _scrollController?.jumpTo( index: 0, @@ -1117,11 +1118,9 @@ class _MessageListViewState extends State { }); if (_isThreadConversation) { - streamChannel!.getReplies(widget.parentMessage!.id); + streamChannel.getReplies(widget.parentMessage!.id); } - - _getOnThreadTap(); - super.initState(); + super.didChangeDependencies(); } void _getOnThreadTap() { @@ -1139,12 +1138,12 @@ class _MessageListViewState extends State { context, MaterialPageRoute( builder: (_) => StreamBuilder( - stream: streamChannel!.channel.state!.messagesStream.map( + stream: streamChannel.channel.state!.messagesStream.map( (messages) => messages!.firstWhere((m) => m.id == message.id)), initialData: message, builder: (_, snapshot) => StreamChannel( - channel: streamChannel!.channel, + channel: streamChannel.channel, child: widget.threadBuilder!(context, snapshot.data), ), ), @@ -1157,7 +1156,7 @@ class _MessageListViewState extends State { @override void dispose() { if (!_upToDate) { - streamChannel!.reloadChannel(); + streamChannel.reloadChannel(); } _messageNewListener?.cancel(); super.dispose(); 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 e2392b50..76ce215c 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -1,13 +1,13 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Modal widget for displaying message reactions class MessageReactionsModal extends StatelessWidget { @@ -176,8 +176,9 @@ class MessageReactionsModal extends StatelessWidget { Widget _buildReactionCard(BuildContext context) { final currentUser = StreamChat.of(context).user; + final chatThemeData = StreamChatTheme.of(context); return Card( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -190,7 +191,7 @@ class MessageReactionsModal extends StatelessWidget { children: [ Text( 'Message Reactions', - style: StreamChatTheme.of(context).textTheme.headlineBold, + style: chatThemeData.textTheme.headlineBold, ), const SizedBox(height: 16), Flexible( @@ -220,6 +221,7 @@ class MessageReactionsModal extends StatelessWidget { BuildContext context, ) { final isCurrentUser = reaction.user?.id == currentUser.id; + final chatThemeData = StreamChatTheme.of(context); return ConstrainedBox( constraints: BoxConstraints.loose(const Size( 64, @@ -258,7 +260,7 @@ class MessageReactionsModal extends StatelessWidget { messageTheme.reactionsBorderColor ?? Colors.transparent, backgroundColor: messageTheme.reactionsBackgroundColor ?? Colors.transparent, - maskColor: StreamChatTheme.of(context).colorTheme.white, + maskColor: chatThemeData.colorTheme.white, tailCirclesSpacing: 1, highlightOwnReactions: false, ), @@ -269,7 +271,7 @@ class MessageReactionsModal extends StatelessWidget { const SizedBox(height: 8), Text( reaction.user!.name.split(' ')[0], - style: StreamChatTheme.of(context).textTheme.footnoteBold, + style: chatThemeData.textTheme.footnoteBold, textAlign: TextAlign.center, ), ], 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 dc276cb3..1f8a31ea 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_item.dart @@ -35,6 +35,7 @@ class MessageSearchItem extends StatelessWidget { final channel = getMessageResponse.channel; final channelName = channel?.extraData['name']; final user = message.user!; + final chatThemeData = StreamChatTheme.of(context); return ListTile( onTap: onTap, leading: UserAvatar( @@ -49,21 +50,18 @@ class MessageSearchItem extends StatelessWidget { children: [ Text( user.id == StreamChat.of(context).user?.id ? 'You' : user.name, - style: StreamChatTheme.of(context).channelPreviewTheme.title, + style: chatThemeData.channelPreviewTheme.title, ), if (channelName != null) ...[ Text( ' in ', - style: StreamChatTheme.of(context) - .channelPreviewTheme - .title - ?.copyWith( - fontWeight: FontWeight.normal, - ), + style: chatThemeData.channelPreviewTheme.title?.copyWith( + fontWeight: FontWeight.normal, + ), ), Text( channelName as String, - style: StreamChatTheme.of(context).channelPreviewTheme.title, + style: chatThemeData.channelPreviewTheme.title, ), ], ], @@ -121,22 +119,23 @@ class MessageSearchItem extends StatelessWidget { text = parts.join(' '); } + final chatThemeData = StreamChatTheme.of(context); return Text.rich( _getDisplayText( text!, message.mentionedUsers, message.attachments, - StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( - fontStyle: (message.isSystem || message.isDeleted) - ? FontStyle.italic - : FontStyle.normal, - ), - StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( - fontStyle: (message.isSystem || message.isDeleted) - ? FontStyle.italic - : FontStyle.normal, - fontWeight: FontWeight.bold, - ), + chatThemeData.channelPreviewTheme.subtitle?.copyWith( + fontStyle: (message.isSystem || message.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + ), + chatThemeData.channelPreviewTheme.subtitle?.copyWith( + fontStyle: (message.isSystem || message.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + fontWeight: FontWeight.bold, + ), ), maxLines: 1, overflow: TextOverflow.ellipsis, 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 1d9ef272..8b8a8dec 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 @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/message_search_item.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Callback called when tapping on a user typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); @@ -270,12 +270,13 @@ class _MessageSearchListViewState extends State { ); if (widget.showResultCount) { + final chatThemeData = StreamChatTheme.of(context); child = Column( children: [ Container( width: double.maxFinite, decoration: BoxDecoration( - gradient: StreamChatTheme.of(context).colorTheme.bgGradient, + gradient: chatThemeData.colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.symmetric( @@ -285,7 +286,7 @@ class _MessageSearchListViewState extends State { child: Text( '${items.length} results', style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.grey, + color: chatThemeData.colorTheme.grey, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index 24a32a09..e5abede0 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -1,8 +1,8 @@ import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Text widget to display in message class MessageText extends StatelessWidget { @@ -31,6 +31,7 @@ class MessageText extends StatelessWidget { Widget build(BuildContext context) { final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\\\n'); + final themeData = Theme.of(context); return MarkdownBody( data: text, onTapLink: ( @@ -60,14 +61,14 @@ class MessageText extends StatelessWidget { } }, styleSheet: MarkdownStyleSheet.fromTheme( - Theme.of(context).copyWith( - textTheme: Theme.of(context).textTheme.apply( - bodyColor: messageTheme.messageText?.color, - decoration: messageTheme.messageText?.decoration, - decorationColor: messageTheme.messageText?.decorationColor, - decorationStyle: messageTheme.messageText?.decorationStyle, - fontFamily: messageTheme.messageText?.fontFamily, - ), + themeData.copyWith( + textTheme: themeData.textTheme.apply( + bodyColor: messageTheme.messageText?.color, + decoration: messageTheme.messageText?.decoration, + decorationColor: messageTheme.messageText?.decorationColor, + decorationStyle: messageTheme.messageText?.decorationStyle, + fontFamily: messageTheme.messageText?.fontFamily, + ), ), ).copyWith( a: messageTheme.messageLinks, diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 1866da0b..85caf4a6 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -8,6 +8,8 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/image_group.dart'; import 'package:stream_chat_flutter/src/message_action.dart'; import 'package:stream_chat_flutter/src/message_actions_modal.dart'; import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; @@ -15,8 +17,6 @@ import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/image_group.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Widget builder for building attachments typedef AttachmentBuilder = Widget Function( @@ -109,6 +109,7 @@ class MessageWidget extends StatefulWidget { borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, ); + final mediaQueryData = MediaQuery.of(context); if (attachments.length > 1) { return Padding( padding: attachmentPadding, @@ -118,8 +119,8 @@ class MessageWidget extends StatefulWidget { color: messageTheme.messageBackgroundColor, child: ImageGroup( size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, ), images: attachments, message: message, @@ -142,8 +143,8 @@ class MessageWidget extends StatefulWidget { message: message, messageTheme: messageTheme, size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, ), onShowMessage: onShowMessage, onReturnAction: onReturnAction, @@ -167,24 +168,25 @@ class MessageWidget extends StatefulWidget { return wrapAttachmentWidget( context, Column( - children: attachments - .map((attachment) => VideoAttachment( - attachment: attachment, - messageTheme: messageTheme, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), - message: message, - onShowMessage: onShowMessage, - onReturnAction: onReturnAction, - onAttachmentTap: onAttachmentTap != null - ? () { - onAttachmentTap(message, attachment); - } - : null, - )) - .toList(), + children: attachments.map((attachment) { + final mediaQueryData = MediaQuery.of(context); + return VideoAttachment( + attachment: attachment, + messageTheme: messageTheme, + size: Size( + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, + ), + message: message, + onShowMessage: onShowMessage, + onReturnAction: onReturnAction, + onAttachmentTap: onAttachmentTap != null + ? () { + onAttachmentTap(message, attachment); + } + : null, + ); + }).toList(), ), border, reverse, @@ -200,18 +202,19 @@ class MessageWidget extends StatefulWidget { return wrapAttachmentWidget( context, Column( - children: attachments - .map((attachment) => GiphyAttachment( - attachment: attachment, - message: message, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), - onShowMessage: onShowMessage, - onReturnAction: onReturnAction, - )) - .toList(), + children: attachments.map((attachment) { + final mediaQueryData = MediaQuery.of(context); + return GiphyAttachment( + attachment: attachment, + message: message, + size: Size( + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, + ), + onShowMessage: onShowMessage, + onReturnAction: onReturnAction, + ); + }).toList(), ), border, reverse, @@ -230,21 +233,24 @@ class MessageWidget extends StatefulWidget { return Column( children: attachments - .map((attachment) => wrapAttachmentWidget( - context, - FileAttachment( - message: message, - attachment: attachment, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), + .map((attachment) { + final mediaQueryData = MediaQuery.of(context); + return wrapAttachmentWidget( + context, + FileAttachment( + message: message, + attachment: attachment, + size: Size( + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, ), - border, - reverse, - attachmentBorderRadiusGeometry as BorderRadius? ?? - BorderRadius.zero, - )) + ), + border, + reverse, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, + ); + }) .insertBetween(SizedBox( height: attachmentPadding.vertical / 2, )) @@ -658,12 +664,13 @@ class _MessageWidgetState extends State widget.onQuotedMessageTap != null ? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId) : null; + final chatThemeData = StreamChatTheme.of(context); return QuotedMessageWidget( onTap: onTap, message: widget.message.quotedMessage!, messageTheme: isMyMessage - ? StreamChatTheme.of(context).otherMessageTheme - : StreamChatTheme.of(context).ownMessageTheme, + ? chatThemeData.otherMessageTheme + : chatThemeData.ownMessageTheme, reverse: widget.reverse, padding: EdgeInsets.only( right: 8, left: 8, top: 8, bottom: hasNonUrlAttachments ? 8 : 0), @@ -672,6 +679,7 @@ class _MessageWidgetState extends State Widget get _bottomRow { if (isDeleted) { + final chatThemeData = StreamChatTheme.of(context); return Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, @@ -679,16 +687,14 @@ class _MessageWidgetState extends State mainAxisSize: MainAxisSize.min, children: [ StreamSvgIcon.eye( - color: StreamChatTheme.of(context).colorTheme.grey, + color: chatThemeData.colorTheme.grey, size: 16, ), const SizedBox(width: 8), Text( 'Only visible to you', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith(color: StreamChatTheme.of(context).colorTheme.grey), + style: chatThemeData.textTheme.footnote + .copyWith(color: chatThemeData.colorTheme.grey), ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/option_list_tile.dart b/packages/stream_chat_flutter/lib/src/option_list_tile.dart index 9a11fd7a..10ae1b5f 100644 --- a/packages/stream_chat_flutter/lib/src/option_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/option_list_tile.dart @@ -41,56 +41,55 @@ class OptionListTile extends StatelessWidget { final TextStyle? titleTextStyle; @override - Widget build(BuildContext context) => Column( - children: [ - Container( - color: separatorColor ?? - StreamChatTheme.of(context).colorTheme.greyGainsboro, - height: 1, - ), - Material( - color: tileColor ?? StreamChatTheme.of(context).colorTheme.white, - child: SizedBox( - height: 63, - child: InkWell( - onTap: onTap, - child: Row( - children: [ - if (leading != null) Center(child: leading), - if (leading == null) - const SizedBox( - width: 16, - ), - Expanded( - flex: 4, - child: Text( - title!, - style: titleTextStyle ?? - (titleColor == null - ? StreamChatTheme.of(context).textTheme.bodyBold - : StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: titleColor, - )), + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return Column( + children: [ + Container( + color: separatorColor ?? chatThemeData.colorTheme.greyGainsboro, + height: 1, + ), + Material( + color: tileColor ?? chatThemeData.colorTheme.white, + child: SizedBox( + height: 63, + child: InkWell( + onTap: onTap, + child: Row( + children: [ + if (leading != null) Center(child: leading), + if (leading == null) + const SizedBox( + width: 16, + ), + Expanded( + flex: 4, + child: Text( + title!, + style: titleTextStyle ?? + (titleColor == null + ? chatThemeData.textTheme.bodyBold + : chatThemeData.textTheme.bodyBold.copyWith( + color: titleColor, + )), + ), + ), + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.only(right: 16), + child: Align( + alignment: Alignment.centerRight, + child: trailing ?? Container(), ), ), - Expanded( - flex: 2, - child: Padding( - padding: const EdgeInsets.only(right: 16), - child: Align( - alignment: Alignment.centerRight, - child: trailing ?? Container(), - ), - ), - ), - ], - ), + ), + ], ), ), ), - ], - ); + ), + ], + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index 9b45c248..82ebc6a9 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -126,6 +126,8 @@ class ReactionBubble extends StatelessWidget { (r) => r.type == reaction.type, ); + final chatThemeData = StreamChatTheme.of(context); + final userId = StreamChat.of(context).user?.id; return Padding( padding: const EdgeInsets.symmetric( horizontal: 4, @@ -135,24 +137,16 @@ class ReactionBubble extends StatelessWidget { assetName: reactionIcon.assetName, width: 16, height: 16, - color: (!highlightOwnReactions || - reaction.user?.id == StreamChat.of(context).user?.id) - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), + color: (!highlightOwnReactions || reaction.user?.id == userId) + ? chatThemeData.colorTheme.accentBlue + : chatThemeData.colorTheme.black.withOpacity(.5), ) : Icon( Icons.help_outline_rounded, size: 16, - color: (!highlightOwnReactions || - reaction.user?.id == StreamChat.of(context).user?.id) - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), + color: (!highlightOwnReactions || reaction.user?.id == userId) + ? chatThemeData.colorTheme.accentBlue + : chatThemeData.colorTheme.black.withOpacity(.5), ), ); } diff --git a/packages/stream_chat_flutter/lib/src/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reaction_picker.dart index 1308876d..8a89b8c3 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_picker.dart @@ -2,8 +2,8 @@ import 'dart:math'; import 'package:ezanimation/ezanimation.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png) @@ -33,7 +33,8 @@ class _ReactionPickerState extends State @override Widget build(BuildContext context) { - final reactionIcons = StreamChatTheme.of(context).reactionIcons; + final chatThemeData = StreamChatTheme.of(context); + final reactionIcons = chatThemeData.reactionIcons; if (animations.isEmpty && reactionIcons.isNotEmpty) { reactionIcons.forEach((element) { @@ -57,7 +58,7 @@ class _ReactionPickerState extends State scale: val, child: Material( borderRadius: BorderRadius.circular(24), - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, clipBehavior: Clip.hardEdge, child: Padding( padding: const EdgeInsets.symmetric( @@ -119,9 +120,8 @@ class _ReactionPickerState extends State animations[index].value * 24.0, ), color: ownReactionIndex != -1 - ? StreamChatTheme.of(context) - .colorTheme - .accentBlue + ? chatThemeData + .colorTheme.accentBlue : Theme.of(context) .iconTheme .color! diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart index ba94642f..347fc59d 100644 --- a/packages/stream_chat_flutter/lib/src/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/thread_header.dart @@ -97,65 +97,60 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { final List? actions; @override - Widget build(BuildContext context) => AppBar( - automaticallyImplyLeading: false, - brightness: Theme.of(context).brightness, - elevation: 1, - leading: leading ?? - (showBackButton - ? StreamBackButton( - cid: StreamChannel.of(context).channel.cid, - onPressed: onBackPressed, - showUnreads: true, - ) - : const SizedBox()), - backgroundColor: - StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, - centerTitle: true, - actions: actions, - title: InkWell( - onTap: onTitleTap, - child: SizedBox( - height: preferredSize.height, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - title ?? - Text( - 'Thread Reply', - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .title, - ), - const SizedBox(height: 2), - subtitle ?? - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'with ', - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .subtitle, + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return AppBar( + automaticallyImplyLeading: false, + brightness: Theme.of(context).brightness, + elevation: 1, + leading: leading ?? + (showBackButton + ? StreamBackButton( + cid: StreamChannel.of(context).channel.cid, + onPressed: onBackPressed, + showUnreads: true, + ) + : const SizedBox()), + backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color, + centerTitle: true, + actions: actions, + title: InkWell( + onTap: onTitleTap, + child: SizedBox( + height: preferredSize.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + title ?? + Text( + 'Thread Reply', + style: chatThemeData.channelTheme.channelHeaderTheme.title, + ), + const SizedBox(height: 2), + subtitle ?? + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'with ', + style: chatThemeData + .channelTheme.channelHeaderTheme.subtitle, + ), + Flexible( + child: ChannelName( + textStyle: chatThemeData + .channelTheme.channelHeaderTheme.subtitle, ), - Flexible( - child: ChannelName( - textStyle: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .subtitle, - ), - ), - ], - ), - ], - ), + ), + ], + ), + ], ), ), - ); + ), + ); + } @override final Size preferredSize; diff --git a/packages/stream_chat_flutter/lib/src/url_attachment.dart b/packages/stream_chat_flutter/lib/src/url_attachment.dart index 0b91c2ee..995475a8 100644 --- a/packages/stream_chat_flutter/lib/src/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/url_attachment.dart @@ -26,91 +26,84 @@ class UrlAttachment extends StatelessWidget { final EdgeInsets textPadding; @override - Widget build(BuildContext context) => GestureDetector( - onTap: () { - launchURL( - context, - urlAttachment.ogScrapeUrl, - ); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (urlAttachment.imageUrl != null) - Container( - clipBehavior: Clip.antiAliasWithSaveLayer, - margin: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - ), - child: Stack( - children: [ - CachedNetworkImage( - width: double.infinity, - imageUrl: urlAttachment.imageUrl!, - fit: BoxFit.cover, - ), - Positioned( - left: 0, - bottom: -1, - child: Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topRight: Radius.circular(16), - ), - color: - StreamChatTheme.of(context).colorTheme.blueAlice, + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return GestureDetector( + onTap: () { + launchURL( + context, + urlAttachment.ogScrapeUrl, + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (urlAttachment.imageUrl != null) + Container( + clipBehavior: Clip.antiAliasWithSaveLayer, + margin: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: Stack( + children: [ + CachedNetworkImage( + width: double.infinity, + imageUrl: urlAttachment.imageUrl!, + fit: BoxFit.cover, + ), + Positioned( + left: 0, + bottom: -1, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(16), ), - child: Padding( - padding: const EdgeInsets.only( - top: 8, - left: 8, - right: 8, - ), - child: Text( - hostDisplayName, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), + color: chatThemeData.colorTheme.blueAlice, + ), + child: Padding( + padding: const EdgeInsets.only( + top: 8, + left: 8, + right: 8, + ), + child: Text( + hostDisplayName, + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.accentBlue, ), ), ), ), - ], - ), - ), - Padding( - padding: textPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (urlAttachment.title != null) - Text( - urlAttachment.title!.trim(), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith(fontWeight: FontWeight.w700), - ), - if (urlAttachment.text != null) - Text( - urlAttachment.text!, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith(fontWeight: FontWeight.w400), - ), + ), ], ), ), - ], - ), - ); + Padding( + padding: textPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (urlAttachment.title != null) + Text( + urlAttachment.title!.trim(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: chatThemeData.textTheme.body + .copyWith(fontWeight: FontWeight.w700), + ), + if (urlAttachment.text != null) + Text( + urlAttachment.text!, + style: chatThemeData.textTheme.body + .copyWith(fontWeight: FontWeight.w400), + ), + ], + ), + ), + ], + ), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/user_avatar.dart b/packages/stream_chat_flutter/lib/src/user_avatar.dart index cb968527..ecf1783f 100644 --- a/packages/stream_chat_flutter/lib/src/user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/user_avatar.dart @@ -1,7 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget that displays a user avatar class UserAvatar extends StatelessWidget { @@ -95,8 +95,7 @@ class UserAvatar extends StatelessWidget { child: Container( constraints: constraints ?? streamChatTheme.ownMessageTheme.avatarTheme?.constraints, - color: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, + color: selectionColor ?? streamChatTheme.colorTheme.accentBlue, child: Padding( padding: EdgeInsets.all(selectionThickness), child: avatar, diff --git a/packages/stream_chat_flutter/lib/src/user_item.dart b/packages/stream_chat_flutter/lib/src/user_item.dart index 290612e1..e9d9f7c0 100644 --- a/packages/stream_chat_flutter/lib/src/user_item.dart +++ b/packages/stream_chat_flutter/lib/src/user_item.dart @@ -48,47 +48,52 @@ class UserItem extends StatelessWidget { final bool showLastOnline; @override - Widget build(BuildContext context) => ListTile( - onTap: () { - if (onTap != null) { - onTap!(user); + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return ListTile( + onTap: () { + if (onTap != null) { + onTap!(user); + } + }, + onLongPress: () { + if (onLongPress != null) { + onLongPress!(user); + } + }, + leading: UserAvatar( + user: user, + onTap: (user) { + if (onImageTap != null) { + onImageTap!(user); } }, - onLongPress: () { - if (onLongPress != null) { - onLongPress!(user); - } - }, - leading: UserAvatar( - user: user, - onTap: (user) { - if (onImageTap != null) { - onImageTap!(user); - } - }, - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), + constraints: const BoxConstraints.tightFor( + height: 40, + width: 40, ), - trailing: selected - ? StreamSvgIcon.checkSend( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ) - : null, - title: Text( - user.name, - style: StreamChatTheme.of(context).textTheme.bodyBold, - ), - subtitle: showLastOnline ? _buildLastActive(context) : null, - ); + ), + trailing: selected + ? StreamSvgIcon.checkSend( + color: chatThemeData.colorTheme.accentBlue, + ) + : null, + title: Text( + user.name, + style: chatThemeData.textTheme.bodyBold, + ), + subtitle: showLastOnline ? _buildLastActive(context) : null, + ); + } - Widget _buildLastActive(context) => Text( - user.online == true - ? 'Online' - : 'Last online ${Jiffy(user.lastActive).fromNow()}', - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(.5)), - ); + Widget _buildLastActive(context) { + final chatTheme = StreamChatTheme.of(context); + return Text( + user.online == true + ? 'Online' + : 'Last online ${Jiffy(user.lastActive).fromNow()}', + style: chatTheme.textTheme.footnote + .copyWith(color: chatTheme.colorTheme.black.withOpacity(.5)), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/user_list_view.dart b/packages/stream_chat_flutter/lib/src/user_list_view.dart index 4d5d202a..3b1efd04 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -295,21 +295,24 @@ class _UserListViewState extends State if (i < items.length) { final item = items[i]; return item.when( - headerItem: (header) => Container( - key: ValueKey('HEADER-$header'), - color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), - child: Text( - header, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14.5, - color: StreamChatTheme.of(context).colorTheme.grey, + headerItem: (header) { + final chatThemeData = StreamChatTheme.of(context); + return Container( + key: ValueKey('HEADER-$header'), + color: chatThemeData.colorTheme.black.withOpacity(0.05), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + child: Text( + header, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14.5, + color: chatThemeData.colorTheme.grey, + ), ), ), - ), - ), + ); + }, userItem: (user) { final selected = widget.selectedUsers?.contains(user) ?? false; return Container( diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index cdd87e2d..ede3e2bd 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -1,9 +1,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Launch URL Future launchURL(BuildContext context, String? url) async { @@ -27,89 +27,81 @@ Future showConfirmationDialog( Widget? icon, String? question, String? cancelText, -}) => - showModalBottomSheet( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - )), - builder: (context) { - final effect = StreamChatTheme.of(context).colorTheme.borderTop; - return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 26), - if (icon != null) icon, - const SizedBox(height: 26), +}) { + final chatThemeData = StreamChatTheme.of(context); + return showModalBottomSheet( + backgroundColor: chatThemeData.colorTheme.white, + 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), + Text( + title, + style: chatThemeData.textTheme.headlineBold, + ), + const SizedBox(height: 7), + if (question != null) Text( - title, - style: StreamChatTheme.of(context).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: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .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, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentRed), + cancelText, + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.black + .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.accentRed), + ), + ), + ), + ), + ], + ), + ], + ), + ); + }); +} /// Shows info dialog Future showInfoDialog( @@ -119,63 +111,64 @@ Future showInfoDialog( Widget? icon, String? details, StreamChatThemeData? theme, -}) => - showModalBottomSheet( - backgroundColor: theme?.colorTheme.white ?? - StreamChatTheme.of(context).colorTheme.white, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - )), - builder: (context) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox( - height: 26, - ), - if (icon != null) icon, - const SizedBox( - height: 26, - ), - Text( - title, - style: theme?.textTheme.headlineBold ?? - StreamChatTheme.of(context).textTheme.headlineBold, - ), - const SizedBox( - height: 7, - ), - if (details != null) Text(details), - const SizedBox( - height: 36, - ), - Container( - color: theme?.colorTheme.black.withOpacity(.08) ?? - StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), - height: 1, - ), - Center( - child: TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - okText, - style: TextStyle( - color: theme?.colorTheme.black.withOpacity(0.5) ?? - StreamChatTheme.of(context).colorTheme.accentBlue, - fontWeight: FontWeight.w400, - ), +}) { + final chatThemeData = StreamChatTheme.of(context); + return showModalBottomSheet( + backgroundColor: theme?.colorTheme.white ?? chatThemeData.colorTheme.white, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + )), + builder: (context) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 26, + ), + if (icon != null) icon, + const SizedBox( + height: 26, + ), + Text( + title, + style: theme?.textTheme.headlineBold ?? + chatThemeData.textTheme.headlineBold, + ), + const SizedBox( + height: 7, + ), + if (details != null) Text(details), + const SizedBox( + height: 36, + ), + Container( + color: theme?.colorTheme.black.withOpacity(.08) ?? + chatThemeData.colorTheme.black.withOpacity(.08), + height: 1, + ), + Center( + child: TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + okText, + style: TextStyle( + color: theme?.colorTheme.black.withOpacity(0.5) ?? + chatThemeData.colorTheme.accentBlue, + fontWeight: FontWeight.w400, ), ), ), - ], - ), + ), + ], ), - ); + ), + ); +} /// Get random png with initials String getRandomPicUrl(User user) => From 45213fa7f150e0d147ce30c362544ac9609f8df4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 10 May 2021 17:07:45 +0200 Subject: [PATCH 226/599] add textoverflow param to channel name --- packages/stream_chat_flutter/lib/src/channel_name.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart index 1cc510af..f2055931 100644 --- a/packages/stream_chat_flutter/lib/src/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel_name.dart @@ -12,11 +12,15 @@ class ChannelName extends StatelessWidget { const ChannelName({ Key? key, this.textStyle, + this.textOverflow = TextOverflow.ellipsis, }) : super(key: key); /// The style of the text displayed final TextStyle? textStyle; + /// How visual overflow should be handled. + final TextOverflow textOverflow; + @override Widget build(BuildContext context) { final client = StreamChat.of(context); @@ -71,7 +75,7 @@ class ChannelName extends StatelessWidget { return Text( title!, style: textStyle, - overflow: TextOverflow.ellipsis, + overflow: textOverflow, ); }, ); From 4c11dca79240cabcca83e2c4815bb5e3485783ba Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 11 May 2021 10:11:38 +0530 Subject: [PATCH 227/599] Added a new message list controller --- packages/stream_chat/example/lib/main.dart | 8 ++++---- packages/stream_chat/lib/src/api/channel.dart | 12 +++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat/example/lib/main.dart b/packages/stream_chat/example/lib/main.dart index 11e9779f..bbcc86b8 100644 --- a/packages/stream_chat/example/lib/main.dart +++ b/packages/stream_chat/example/lib/main.dart @@ -79,21 +79,21 @@ class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { - final messages = channel.state!.channelStateStream; + final messages = channel.state!.messagesStream; return Scaffold( appBar: AppBar( title: Text('Channel: ${channel.id}'), ), body: SafeArea( - child: StreamBuilder( + child: StreamBuilder?>( stream: messages, builder: ( BuildContext context, - AsyncSnapshot snapshot, + AsyncSnapshot?> snapshot, ) { if (snapshot.hasData && snapshot.data != null) { return MessageView( - messages: snapshot.data!.messages.reversed.toList(), + messages: snapshot.data!.reversed.toList(), channel: channel, ); } else if (snapshot.hasError) { diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index a1eab825..8b744cec 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; -import 'package:collection/collection.dart' show IterableExtension; +import 'package:collection/collection.dart' show IterableExtension, ListEquality; import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; @@ -1296,6 +1296,8 @@ class ChannelClientState { _channelStateController = BehaviorSubject.seeded(channelState); + _messageListController = BehaviorSubject.seeded(channelState.messages); + _listenTypingEvents(); _listenMessageNew(); @@ -1601,8 +1603,7 @@ class ChannelClientState { List get messages => _channelState.messages; /// Channel message list as a stream - Stream?> get messagesStream => - channelStateStream.map((cs) => cs.messages); + Stream?> get messagesStream => _messageListController.stream; /// Channel pinned message list List? get pinnedMessages => _channelState.pinnedMessages.toList(); @@ -1772,10 +1773,14 @@ class ChannelClientState { /// The channel state related to this client ChannelState get channelState => _channelStateController.value!; late BehaviorSubject _channelStateController; + late BehaviorSubject> _messageListController; final Debounce _debouncedUpdatePersistenceChannelState; set _channelState(ChannelState v) { + if(!const ListEquality().equals(_messageListController.value, v.messages)) { + _messageListController.add(v.messages); + } _channelStateController.add(v); _debouncedUpdatePersistenceChannelState.call([v]); } @@ -1933,6 +1938,7 @@ class ChannelClientState { retryQueue!.dispose(); _subscriptions.forEach((s) => s.cancel()); _channelStateController.close(); + _messageListController.close(); _isUpToDateController.close(); _threadsController.close(); _cleaningTimer.cancel(); From 16ecd0568e2dc96ed899fed7ee6b129345d5cd28 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 11 May 2021 10:19:09 +0530 Subject: [PATCH 228/599] fix: dartfmt --- packages/stream_chat/lib/src/api/channel.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 8b744cec..e51725e3 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -2,7 +2,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; -import 'package:collection/collection.dart' show IterableExtension, ListEquality; +import 'package:collection/collection.dart' + show IterableExtension, ListEquality; import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; @@ -1778,7 +1779,8 @@ class ChannelClientState { final Debounce _debouncedUpdatePersistenceChannelState; set _channelState(ChannelState v) { - if(!const ListEquality().equals(_messageListController.value, v.messages)) { + if (!const ListEquality() + .equals(_messageListController.value, v.messages)) { _messageListController.add(v.messages); } _channelStateController.add(v); From 682fe3f4476d673c514b460ca48d0d3a66e9c3c3 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 11 May 2021 14:55:25 +0530 Subject: [PATCH 229/599] Changed to distinct implementation --- packages/stream_chat/lib/src/api/channel.dart | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index e51725e3..ca3c638a 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1297,8 +1297,6 @@ class ChannelClientState { _channelStateController = BehaviorSubject.seeded(channelState); - _messageListController = BehaviorSubject.seeded(channelState.messages); - _listenTypingEvents(); _listenMessageNew(); @@ -1604,7 +1602,9 @@ class ChannelClientState { List get messages => _channelState.messages; /// Channel message list as a stream - Stream?> get messagesStream => _messageListController.stream; + Stream?> get messagesStream => channelStateStream + .map((cs) => cs.messages) + .distinct((prev, next) => const ListEquality().equals(prev, next)); /// Channel pinned message list List? get pinnedMessages => _channelState.pinnedMessages.toList(); @@ -1774,15 +1774,10 @@ class ChannelClientState { /// The channel state related to this client ChannelState get channelState => _channelStateController.value!; late BehaviorSubject _channelStateController; - late BehaviorSubject> _messageListController; final Debounce _debouncedUpdatePersistenceChannelState; set _channelState(ChannelState v) { - if (!const ListEquality() - .equals(_messageListController.value, v.messages)) { - _messageListController.add(v.messages); - } _channelStateController.add(v); _debouncedUpdatePersistenceChannelState.call([v]); } @@ -1940,7 +1935,6 @@ class ChannelClientState { retryQueue!.dispose(); _subscriptions.forEach((s) => s.cancel()); _channelStateController.close(); - _messageListController.close(); _isUpToDateController.close(); _threadsController.close(); _cleaningTimer.cancel(); From 5c044921766e8a60f86d00f12556e46afdfd0560 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 11 May 2021 15:21:30 +0200 Subject: [PATCH 230/599] perform heavy calls only first time --- .../lib/src/message_list_view.dart | 40 +++---- .../lib/src/channel_list_core.dart | 20 ++-- .../lib/src/channels_bloc.dart | 101 +++++++++--------- .../lib/src/message_list_core.dart | 5 +- .../lib/src/message_search_list_core.dart | 9 +- .../lib/src/user_list_core.dart | 7 +- 6 files changed, 99 insertions(+), 83 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 661ce885..3a07c117 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1087,8 +1087,6 @@ class _MessageListViewState extends State { _itemPositionListener = widget.itemPositionListener ?? ItemPositionsListener.create(); - streamChannel = StreamChannel.of(context); - initialIndex = _initialIndex; initialAlignment = _initialAlignment; @@ -1100,25 +1098,27 @@ class _MessageListViewState extends State { void didChangeDependencies() { streamChannel = StreamChannel.of(context); - _messageNewListener?.cancel(); - _messageNewListener = - streamChannel.channel.on(EventType.messageNew).listen((event) { - if (_upToDate) { - _bottomPaginationActive = false; - _topPaginationActive = false; - } - if (event.message!.user!.id == - streamChannel.channel.client.state.user!.id) { - WidgetsBinding.instance!.addPostFrameCallback((_) { - _scrollController?.jumpTo( - index: 0, - ); - }); - } - }); + if (_messageNewListener == null) { + _messageNewListener?.cancel(); + _messageNewListener = + streamChannel.channel.on(EventType.messageNew).listen((event) { + if (_upToDate) { + _bottomPaginationActive = false; + _topPaginationActive = false; + } + if (event.message!.user!.id == + streamChannel.channel.client.state.user!.id) { + WidgetsBinding.instance!.addPostFrameCallback((_) { + _scrollController?.jumpTo( + index: 0, + ); + }); + } + }); - if (_isThreadConversation) { - streamChannel.getReplies(widget.parentMessage!.id); + if (_isThreadConversation) { + streamChannel.getReplies(widget.parentMessage!.id); + } } super.didChangeDependencies(); } diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 0460cf7e..084ee20f 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -178,19 +178,17 @@ class ChannelListCoreState extends State { if (_subscription == null) { loadData(); + final client = _streamChatCoreState.client; + _subscription = client + .on( + EventType.connectionRecovered, + EventType.notificationAddedToChannel, + EventType.notificationMessageNew, + EventType.channelVisible, + ) + .listen((event) => loadData()); } - final client = _streamChatCoreState.client; - _subscription?.cancel(); - _subscription = client - .on( - EventType.connectionRecovered, - EventType.notificationAddedToChannel, - EventType.notificationMessageNew, - EventType.channelVisible, - ) - .listen((event) => loadData()); - super.didChangeDependencies(); } 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 f29c76f9..b23b9fc4 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -149,59 +149,62 @@ class ChannelsBlocState extends State _streamChatCoreState = StreamChatCore.of(context); final client = _streamChatCoreState.client; - _cancelSubscriptions(); - if (!widget.lockChannelsOrder) { - _subscriptions.add(client - .on( - EventType.messageNew, - ) - .listen((e) { - final newChannels = List.from(channels ?? []); - final index = newChannels.indexWhere((c) => c.cid == e.cid); - if (index != -1) { - if (index > 0) { - final channel = newChannels.removeAt(index); - newChannels.insert(0, channel); - } - } else if (widget.shouldAddChannel?.call(e) == true) { - final hiddenIndex = _hiddenChannels.indexWhere((c) => c.cid == e.cid); - if (hiddenIndex != -1) { - newChannels.insert(0, _hiddenChannels[hiddenIndex]); - _hiddenChannels.removeAt(hiddenIndex); - } else { - if (client.state.channels[e.cid] != null) { - newChannels.insert(0, client.state.channels[e.cid]!); + if (_subscriptions.isEmpty) { + if (!widget.lockChannelsOrder) { + _subscriptions.add(client + .on( + EventType.messageNew, + ) + .listen((e) { + final newChannels = List.from(channels ?? []); + final index = newChannels.indexWhere((c) => c.cid == e.cid); + if (index != -1) { + if (index > 0) { + final channel = newChannels.removeAt(index); + newChannels.insert(0, channel); + } + } else if (widget.shouldAddChannel?.call(e) == true) { + final hiddenIndex = + _hiddenChannels.indexWhere((c) => c.cid == e.cid); + if (hiddenIndex != -1) { + newChannels.insert(0, _hiddenChannels[hiddenIndex]); + _hiddenChannels.removeAt(hiddenIndex); + } else { + if (client.state.channels[e.cid] != null) { + newChannels.insert(0, client.state.channels[e.cid]!); + } } } - } - if (widget.channelsComparator != null) { - newChannels.sort(widget.channelsComparator); - } - _channelsController.add(newChannels); - })); - } - - _subscriptions - ..add(client.on(EventType.channelHidden).listen((event) async { - final newChannels = List.from(channels ?? []); - final channelIndex = newChannels.indexWhere((c) => c.cid == event.cid); - if (channelIndex > -1) { - final channel = newChannels.removeAt(channelIndex); - _hiddenChannels.add(channel); + if (widget.channelsComparator != null) { + newChannels.sort(widget.channelsComparator); + } _channelsController.add(newChannels); - } - })) - ..add(client - .on( - EventType.channelDeleted, - EventType.notificationRemovedFromChannel, - ) - .listen((e) { - final channel = e.channel; - _channelsController.add(List.from( - (channels ?? [])..removeWhere((c) => c.cid == channel?.cid))); - })); + })); + } + + _subscriptions + ..add(client.on(EventType.channelHidden).listen((event) async { + final newChannels = List.from(channels ?? []); + final channelIndex = + newChannels.indexWhere((c) => c.cid == event.cid); + if (channelIndex > -1) { + final channel = newChannels.removeAt(channelIndex); + _hiddenChannels.add(channel); + _channelsController.add(newChannels); + } + })) + ..add(client + .on( + EventType.channelDeleted, + EventType.notificationRemovedFromChannel, + ) + .listen((e) { + final channel = e.channel; + _channelsController.add(List.from( + (channels ?? [])..removeWhere((c) => c.cid == channel?.cid))); + })); + } super.didChangeDependencies(); } diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 3e0ac1c7..babbfd36 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -173,10 +173,13 @@ class MessageListCoreState extends State { } } + var _initialized = false; + @override void didChangeDependencies() { _streamChannel = StreamChannel.of(context); - if (_isThreadConversation) { + if (!_initialized && _isThreadConversation) { + _initialized = true; _streamChannel.getReplies(widget.parentMessage!.id); } super.didChangeDependencies(); diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index e2f0ae2e..e6cb1aad 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -107,10 +107,17 @@ class MessageSearchListCore extends StatefulWidget { class MessageSearchListCoreState extends State { late MessageSearchBlocState _messageSearchBloc; + var _initialized = false; + @override void didChangeDependencies() { _messageSearchBloc = MessageSearchBloc.of(context); - loadData(); + + if (!_initialized) { + loadData(); + _initialized = true; + } + if (widget.messageSearchListController != null) { widget.messageSearchListController!.loadData = loadData; widget.messageSearchListController!.paginateData = paginateData; diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index d608ddc4..da675706 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -122,9 +122,14 @@ class UserListCore extends StatefulWidget { /// The current state of the [UserListCore]. class UserListCoreState extends State with WidgetsBindingObserver { + var _initialized = false; + @override void didChangeDependencies() { - loadData(); + if (!_initialized) { + loadData(); + _initialized = true; + } if (widget.userListController != null) { widget.userListController!.loadData = loadData; widget.userListController!.paginateData = paginateData; From 19ce7ff756485cf6c568f543c20dad134daba104 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 11 May 2021 15:29:02 +0200 Subject: [PATCH 231/599] fix message list --- .../stream_chat_flutter/lib/src/message_list_view.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 3a07c117..7435a11b 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1087,9 +1087,6 @@ class _MessageListViewState extends State { _itemPositionListener = widget.itemPositionListener ?? ItemPositionsListener.create(); - initialIndex = _initialIndex; - initialAlignment = _initialAlignment; - _getOnThreadTap(); super.initState(); } @@ -1099,7 +1096,9 @@ class _MessageListViewState extends State { streamChannel = StreamChannel.of(context); if (_messageNewListener == null) { - _messageNewListener?.cancel(); + initialIndex = _initialIndex; + initialAlignment = _initialAlignment; + _messageNewListener = streamChannel.channel.on(EventType.messageNew).listen((event) { if (_upToDate) { From 3f65f04c1715c70a406f1959fe48871949802f6c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 11 May 2021 16:29:31 +0200 Subject: [PATCH 232/599] fix unread count --- packages/stream_chat/lib/src/api/channel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index a1eab825..c109b41c 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1588,7 +1588,7 @@ class ChannelClientState { readList.add(Read( user: event.user!, lastRead: event.createdAt!, - unreadMessages: event.totalUnreadCount!, + unreadMessages: event.totalUnreadCount ?? 0, )); _channelState = _channelState.copyWith(read: readList); } From f717b4c88a9c4156620474346c3604f696d08a16 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 11 May 2021 17:15:31 +0200 Subject: [PATCH 233/599] title not null --- packages/stream_chat_flutter/lib/src/channel_name.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart index f2055931..2cd804c9 100644 --- a/packages/stream_chat_flutter/lib/src/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel_name.dart @@ -41,12 +41,14 @@ class ChannelName extends StatelessWidget { ) => LayoutBuilder( builder: (context, constraints) { - String? title; + var title = 'No title'; if (extraData['name'] == null) { final otherMembers = members?.where((member) => member.userId != client.user!.id); if (otherMembers?.length == 1) { - title = otherMembers!.first.user?.name; + if (otherMembers!.first.user != null) { + title = otherMembers.first.user!.name; + } } else if (otherMembers?.isNotEmpty == true) { final maxWidth = constraints.maxWidth; final maxChars = maxWidth / (textStyle?.fontSize ?? 1); @@ -65,15 +67,13 @@ class ChannelName extends StatelessWidget { otherMembers.length - currentMembers.length; title = '${currentMembers.map((e) => e.user?.name).join(', ')} ' '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; - } else { - title = 'No title'; } } else { title = extraData['name']; } return Text( - title!, + title, style: textStyle, overflow: textOverflow, ); From a7f00b3d85eb7b222b614653a3e38168354cd9b0 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 12 May 2021 11:50:06 +0200 Subject: [PATCH 234/599] update didchangedependencies --- .../lib/src/message_list_view.dart | 53 ++++++++-------- .../lib/src/channel_list_core.dart | 10 +-- .../lib/src/channels_bloc.dart | 13 ++-- .../lib/src/message_list_core.dart | 30 ++++----- .../lib/src/message_search_list_core.dart | 22 +++---- .../lib/src/user_list_core.dart | 62 +++++++------------ 6 files changed, 88 insertions(+), 102 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 7435a11b..e8f8cdbc 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -274,15 +274,15 @@ class _MessageListViewState extends State { bool _showScrollToBottom = false; late final ItemPositionsListener _itemPositionListener; int? _messageListLength; - late StreamChannelState streamChannel; + StreamChannelState? streamChannel; int? get _initialIndex { if (widget.initialScrollIndex != null) return widget.initialScrollIndex; - if (streamChannel.initialMessageId != null) { - final messages = streamChannel.channel.state!.messages; + if (streamChannel!.initialMessageId != null) { + final messages = streamChannel!.channel.state!.messages; final totalMessages = messages.length; final messageIndex = - messages.indexWhere((e) => e.id == streamChannel.initialMessageId); + messages.indexWhere((e) => e.id == streamChannel!.initialMessageId); final index = totalMessages - messageIndex; if (index != 0) return index - 1; return index; @@ -295,9 +295,9 @@ class _MessageListViewState extends State { return 0; } - bool _isInitialMessage(String id) => streamChannel.initialMessageId == id; + bool _isInitialMessage(String id) => streamChannel!.initialMessageId == id; - bool get _upToDate => streamChannel.channel.state!.isUpToDate; + bool get _upToDate => streamChannel!.channel.state!.isUpToDate; bool get _isThreadConversation => widget.parentMessage != null; @@ -506,13 +506,13 @@ class _MessageListViewState extends State { } if (i == messages.length + 1) { return _buildLoadingIndicator( - streamChannel, + streamChannel!, QueryDirection.top, ); } if (i == 0) { return _buildLoadingIndicator( - streamChannel, + streamChannel!, QueryDirection.bottom, ); } @@ -525,7 +525,7 @@ class _MessageListViewState extends State { context, message, messages, - streamChannel, + streamChannel!, ); } else if (i == messages.length - 1) { messageWidget = _buildTopMessage( @@ -605,8 +605,8 @@ class _MessageListViewState extends State { Widget _buildScrollToBottom() => StreamBuilder>( stream: Rx.combineLatest2( - streamChannel.channel.state!.isUpToDateStream, - streamChannel.channel.state!.unreadCountStream, + streamChannel!.channel.state!.isUpToDateStream, + streamChannel!.channel.state!.unreadCountStream, (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), ), builder: (_, snapshot) { @@ -622,8 +622,8 @@ class _MessageListViewState extends State { } final unreadCount = snapshot.data!.item2; final showUnreadCount = unreadCount > 0 && - streamChannel.channel.state!.members.any((e) => - e.userId == streamChannel.channel.client.state.user!.id); + streamChannel!.channel.state!.members.any((e) => + e.userId == streamChannel!.channel.client.state.user!.id); final chatThemeData = StreamChatTheme.of(context); return Positioned( bottom: 8, @@ -637,12 +637,12 @@ class _MessageListViewState extends State { backgroundColor: chatThemeData.colorTheme.white, onPressed: () { if (unreadCount > 0) { - streamChannel.channel.markRead(); + streamChannel!.channel.markRead(); } if (!_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; - streamChannel.reloadChannel(); + streamChannel!.reloadChannel(); } else { setState(() => _showScrollToBottom = false); _scrollController!.scrollTo( @@ -888,7 +888,7 @@ class _MessageListViewState extends State { ); } - final channel = streamChannel.channel; + final channel = streamChannel!.channel; final readList = channel.state?.read?.where((read) { if (read.user.id == userId) return false; return read.lastRead.isAfter(message.createdAt) || @@ -962,7 +962,7 @@ class _MessageListViewState extends State { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); } else { - await streamChannel.loadChannelAtMessage(quotedMessageId).then((_) { + await streamChannel!.loadChannelAtMessage(quotedMessageId).then((_) { WidgetsBinding.instance!.addPostFrameCallback((_) { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); @@ -1093,20 +1093,22 @@ class _MessageListViewState extends State { @override void didChangeDependencies() { - streamChannel = StreamChannel.of(context); + final newStreamChannel = StreamChannel.of(context); - if (_messageNewListener == null) { + if (newStreamChannel != streamChannel) { + streamChannel = newStreamChannel; + _messageNewListener?.cancel(); initialIndex = _initialIndex; initialAlignment = _initialAlignment; _messageNewListener = - streamChannel.channel.on(EventType.messageNew).listen((event) { + streamChannel!.channel.on(EventType.messageNew).listen((event) { if (_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; } if (event.message!.user!.id == - streamChannel.channel.client.state.user!.id) { + streamChannel!.channel.client.state.user!.id) { WidgetsBinding.instance!.addPostFrameCallback((_) { _scrollController?.jumpTo( index: 0, @@ -1116,9 +1118,10 @@ class _MessageListViewState extends State { }); if (_isThreadConversation) { - streamChannel.getReplies(widget.parentMessage!.id); + streamChannel!.getReplies(widget.parentMessage!.id); } } + super.didChangeDependencies(); } @@ -1137,12 +1140,12 @@ class _MessageListViewState extends State { context, MaterialPageRoute( builder: (_) => StreamBuilder( - stream: streamChannel.channel.state!.messagesStream.map( + stream: streamChannel!.channel.state!.messagesStream.map( (messages) => messages!.firstWhere((m) => m.id == message.id)), initialData: message, builder: (_, snapshot) => StreamChannel( - channel: streamChannel.channel, + channel: streamChannel!.channel, child: widget.threadBuilder!(context, snapshot.data), ), ), @@ -1155,7 +1158,7 @@ class _MessageListViewState extends State { @override void dispose() { if (!_upToDate) { - streamChannel.reloadChannel(); + streamChannel!.reloadChannel(); } _messageNewListener?.cancel(); super.dispose(); diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 084ee20f..a3be273d 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -120,7 +120,7 @@ class ChannelListCore extends StatefulWidget { /// The current state of the [ChannelListCore]. class ChannelListCoreState extends State { late ChannelsBlocState _channelsBloc; - late StreamChatCoreState _streamChatCoreState; + StreamChatCoreState? _streamChatCoreState; @override Widget build(BuildContext context) => _buildListView(_channelsBloc); @@ -174,11 +174,13 @@ class ChannelListCoreState extends State { @override void didChangeDependencies() { _channelsBloc = ChannelsBloc.of(context); - _streamChatCoreState = StreamChatCore.of(context); + final newStreamChatCoreState = StreamChatCore.of(context); - if (_subscription == null) { + if (newStreamChatCoreState != _streamChatCoreState) { + _streamChatCoreState = newStreamChatCoreState; loadData(); - final client = _streamChatCoreState.client; + final client = _streamChatCoreState!.client; + _subscription?.cancel(); _subscription = client .on( EventType.connectionRecovered, 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 b23b9fc4..d46ee4bd 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -62,7 +62,7 @@ class ChannelsBloc extends StatefulWidget { /// The current state of the [ChannelsBloc]. class ChannelsBlocState extends State with AutomaticKeepAliveClientMixin { - late StreamChatCoreState _streamChatCoreState; + StreamChatCoreState? _streamChatCoreState; @override Widget build(BuildContext context) { @@ -98,7 +98,7 @@ class ChannelsBlocState extends State PaginationParams paginationParams = const PaginationParams(limit: 30), Map? options, }) async { - final client = _streamChatCoreState.client; + final client = _streamChatCoreState!.client; final clear = paginationParams.offset == 0; @@ -146,10 +146,13 @@ class ChannelsBlocState extends State @override void didChangeDependencies() { - _streamChatCoreState = StreamChatCore.of(context); - final client = _streamChatCoreState.client; + final newStreamChatCoreState = StreamChatCore.of(context); - if (_subscriptions.isEmpty) { + if (newStreamChatCoreState != _streamChatCoreState) { + _streamChatCoreState = newStreamChatCoreState; + final client = _streamChatCoreState!.client; + + _cancelSubscriptions(); if (!widget.lockChannelsOrder) { _subscriptions.add(client .on( diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index babbfd36..732ac3e6 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -109,23 +109,23 @@ class MessageListCore extends StatefulWidget { /// The current state of the [MessageListCore]. class MessageListCoreState extends State { - late StreamChannelState _streamChannel; + StreamChannelState? _streamChannel; - bool get _upToDate => _streamChannel.channel.state?.isUpToDate ?? true; + bool get _upToDate => _streamChannel!.channel.state?.isUpToDate ?? true; bool get _isThreadConversation => widget.parentMessage != null; - OwnUser? get _currentUser => _streamChannel.channel.client.state.user; + OwnUser? get _currentUser => _streamChannel!.channel.client.state.user; var _messages = []; @override Widget build(BuildContext context) { final messagesStream = _isThreadConversation - ? _streamChannel.channel.state?.threadsStream + ? _streamChannel!.channel.state?.threadsStream .where((threads) => threads.containsKey(widget.parentMessage!.id)) .map((threads) => threads[widget.parentMessage!.id]) - : _streamChannel.channel.state?.messagesStream; + : _streamChannel!.channel.state?.messagesStream; bool defaultFilter(Message m) { final isMyMessage = m.user?.id == _currentUser?.id; @@ -167,21 +167,23 @@ class MessageListCoreState extends State { QueryDirection direction = QueryDirection.top, }) { if (!_isThreadConversation) { - return _streamChannel.queryMessages(direction: direction); + return _streamChannel!.queryMessages(direction: direction); } else { - return _streamChannel.getReplies(widget.parentMessage!.id); + return _streamChannel!.getReplies(widget.parentMessage!.id); } } - var _initialized = false; - @override void didChangeDependencies() { - _streamChannel = StreamChannel.of(context); - if (!_initialized && _isThreadConversation) { - _initialized = true; - _streamChannel.getReplies(widget.parentMessage!.id); + final newStreamChannel = StreamChannel.of(context); + + if (newStreamChannel != _streamChannel) { + _streamChannel = newStreamChannel; + if (_isThreadConversation) { + _streamChannel!.getReplies(widget.parentMessage!.id); + } } + super.didChangeDependencies(); } @@ -210,7 +212,7 @@ class MessageListCoreState extends State { @override void dispose() { if (!_upToDate) { - _streamChannel.reloadChannel(); + _streamChannel!.reloadChannel(); } super.dispose(); } diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index e6cb1aad..199852b9 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -105,28 +105,22 @@ class MessageSearchListCore extends StatefulWidget { /// The current state of the [MessageSearchListCore]. class MessageSearchListCoreState extends State { - late MessageSearchBlocState _messageSearchBloc; - - var _initialized = false; + MessageSearchBlocState? _messageSearchBloc; @override void didChangeDependencies() { - _messageSearchBloc = MessageSearchBloc.of(context); + final newMessageSearchBloc = MessageSearchBloc.of(context); - if (!_initialized) { + if (newMessageSearchBloc != _messageSearchBloc) { + _messageSearchBloc = newMessageSearchBloc; loadData(); - _initialized = true; } - if (widget.messageSearchListController != null) { - widget.messageSearchListController!.loadData = loadData; - widget.messageSearchListController!.paginateData = paginateData; - } super.didChangeDependencies(); } @override - Widget build(BuildContext context) => _buildListView(_messageSearchBloc); + Widget build(BuildContext context) => _buildListView(_messageSearchBloc!); Widget _buildListView(MessageSearchBlocState messageSearchBloc) => StreamBuilder>( @@ -147,7 +141,7 @@ class MessageSearchListCoreState extends State { ); /// Fetches initial messages and updates the widget - Future loadData() => _messageSearchBloc.search( + Future loadData() => _messageSearchBloc!.search( filter: widget.filters, sort: widget.sortOptions, query: widget.messageQuery, @@ -156,11 +150,11 @@ class MessageSearchListCoreState extends State { ); /// Fetches more messages with updated pagination and updates the widget - Future paginateData() => _messageSearchBloc.search( + Future paginateData() => _messageSearchBloc!.search( filter: widget.filters, sort: widget.sortOptions, pagination: widget.paginationParams!.copyWith( - offset: _messageSearchBloc.messageResponses?.length ?? 0, + offset: _messageSearchBloc!.messageResponses?.length ?? 0, ), query: widget.messageQuery, messageFilter: widget.messageFilters, diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index da675706..3e4839b0 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -122,34 +122,25 @@ class UserListCore extends StatefulWidget { /// The current state of the [UserListCore]. class UserListCoreState extends State with WidgetsBindingObserver { - var _initialized = false; + UsersBlocState? _usersBloc; @override void didChangeDependencies() { - if (!_initialized) { + final newUsersBloc = UsersBloc.of(context); + if (newUsersBloc != _usersBloc) { + _usersBloc = newUsersBloc; loadData(); - _initialized = true; - } - if (widget.userListController != null) { - widget.userListController!.loadData = loadData; - widget.userListController!.paginateData = paginateData; } super.didChangeDependencies(); } @override - Widget build(BuildContext context) { - final _usersBloc = UsersBloc.of(context); - return _buildListView(_usersBloc); - } + Widget build(BuildContext context) => _buildListView(); bool get _isListAlreadySorted => widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false; - Stream> _buildUserStream( - UsersBlocState usersBlocState, - ) => - usersBlocState.usersStream.map( + Stream> _buildUserStream() => _usersBloc!.usersStream.map( (users) { if (widget.groupAlphabetically) { var temp = users; @@ -174,11 +165,8 @@ class UserListCoreState extends State }, ); - StreamBuilder> _buildListView( - UsersBlocState usersBlocState, - ) => - StreamBuilder( - stream: _buildUserStream(usersBlocState), + StreamBuilder> _buildListView() => StreamBuilder( + stream: _buildUserStream(), builder: (context, snapshot) { if (snapshot.hasError) { return widget.errorBuilder(snapshot.error!); @@ -195,28 +183,22 @@ class UserListCoreState extends State ); // ignore: public_member_api_docs - Future loadData() { - final _usersBloc = UsersBloc.of(context); - return _usersBloc.queryUsers( - filter: widget.filter, - sort: widget.sort, - pagination: widget.pagination, - options: widget.options, - ); - } + Future loadData() => _usersBloc!.queryUsers( + filter: widget.filter, + sort: widget.sort, + pagination: widget.pagination, + options: widget.options, + ); // ignore: public_member_api_docs - Future paginateData() { - final _usersBloc = UsersBloc.of(context); - return _usersBloc.queryUsers( - filter: widget.filter, - sort: widget.sort, - pagination: widget.pagination!.copyWith( - offset: _usersBloc.users?.length ?? 0, - ), - options: widget.options, - ); - } + Future paginateData() => _usersBloc!.queryUsers( + filter: widget.filter, + sort: widget.sort, + pagination: widget.pagination!.copyWith( + offset: _usersBloc!.users?.length ?? 0, + ), + options: widget.options, + ); @override void didUpdateWidget(UserListCore oldWidget) { From 0f6aa466a2e46b00bd56941c57d8a3dbbedf8d90 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 12 May 2021 16:16:50 +0530 Subject: [PATCH 235/599] rfac: Removed basic transforms and adjusted borders --- .../lib/src/deleted_message.dart | 62 ++- .../lib/src/message_list_view.dart | 22 +- .../lib/src/message_widget.dart | 439 ++++++++---------- .../lib/src/quoted_message_widget.dart | 56 +-- .../lib/src/reaction_bubble.dart | 110 ++--- .../stream_chat_flutter/lib/src/utils.dart | 6 +- 6 files changed, 319 insertions(+), 376 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart index 96f5606a..38b6b1ea 100644 --- a/packages/stream_chat_flutter/lib/src/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart @@ -31,42 +31,34 @@ class DeletedMessage extends StatelessWidget { final bool reverse; @override - Widget build(BuildContext context) => Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: Material( - color: messageTheme.messageBackgroundColor, - shape: shape ?? - RoundedRectangleBorder( - borderRadius: borderRadiusGeometry ?? BorderRadius.zero, - side: borderSide ?? - BorderSide( - color: Theme.of(context).brightness == Brightness.dark - ? StreamChatTheme.of(context) - .colorTheme - .white - .withAlpha(24) - : StreamChatTheme.of(context) - .colorTheme - .black - .withAlpha(24), - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, + Widget build(BuildContext context) => Material( + color: messageTheme.messageBackgroundColor, + shape: shape ?? + RoundedRectangleBorder( + borderRadius: borderRadiusGeometry ?? BorderRadius.zero, + side: borderSide ?? + BorderSide( + color: Theme.of(context).brightness == Brightness.dark + ? StreamChatTheme.of(context) + .colorTheme + .white + .withAlpha(24) + : StreamChatTheme.of(context) + .colorTheme + .black + .withAlpha(24), + ), ), - child: Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: Text( - 'Message deleted', - style: messageTheme.messageText?.copyWith( - fontStyle: FontStyle.italic, - color: messageTheme.createdAt?.color, - ), - ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + child: Text( + 'Message deleted', + style: messageTheme.messageText?.copyWith( + fontStyle: FontStyle.italic, + color: messageTheme.createdAt?.color, ), ), ), 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 2800a041..695f6474 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1000,13 +1000,23 @@ class _MessageListViewState extends State { attachmentPadding: EdgeInsets.all(hasFileAttachment ? 4 : 2), borderRadiusGeometry: BorderRadius.only( topLeft: const Radius.circular(16), - bottomLeft: Radius.circular( - (timeDiff >= 1 || !isNextUserSame) && !(hasReplies || isThreadMessage) - ? 0 - : 16, - ), + bottomLeft: isMyMessage + ? const Radius.circular(16) + : Radius.circular( + (timeDiff >= 1 || !isNextUserSame) && + !(hasReplies || isThreadMessage) + ? 0 + : 16, + ), topRight: const Radius.circular(16), - bottomRight: const Radius.circular(16), + bottomRight: isMyMessage + ? Radius.circular( + (timeDiff >= 1 || !isNextUserSame) && + !(hasReplies || isThreadMessage) + ? 0 + : 16, + ) + : const Radius.circular(16), ), textPadding: EdgeInsets.symmetric( vertical: 8, diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 1866da0b..83cd2593 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -465,184 +465,166 @@ class _MessageWidgetState extends State : () => onLongPress(context), child: Padding( padding: widget.padding ?? const EdgeInsets.all(8), - child: Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - child: FractionallySizedBox( - alignment: Alignment.centerLeft, - widthFactor: 0.78, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Stack( - clipBehavior: Clip.none, - alignment: AlignmentDirectional.bottomStart, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.showUserAvatar == - DisplayWidget.show && - widget.message.user != null) ...[ - _buildUserAvatar(), - const SizedBox(width: 4), - ], - if (widget.showUserAvatar == DisplayWidget.hide) - SizedBox(width: avatarWidth + 4), - Flexible( - child: PortalEntry( - portal: Container( - transform: - Matrix4.translationValues(-12, 0, 0), - constraints: const BoxConstraints( - maxWidth: 22 * 6.0), - child: _buildReactionIndicator(context), - ), - portalAnchor: const Alignment(-1, -1), - childAnchor: const Alignment(1, -1), - child: Stack( - clipBehavior: Clip.none, - children: [ - Padding( - padding: widget.showReactions - ? EdgeInsets.only( - top: widget - .message - .reactionCounts - ?.isNotEmpty == - true - ? 18 - : 0, - ) - : EdgeInsets.zero, - child: (widget.message.isDeleted && - !isFailedState) - ? Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY( - widget.reverse ? pi : 0), - child: 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), - child: DeletedMessage( - reverse: widget.reverse, - // ignore: lines_longer_than_80_chars - borderRadiusGeometry: widget - .borderRadiusGeometry, - borderSide: - widget.borderSide, - shape: widget.shape, - messageTheme: - widget.messageTheme, - ), - ), - ) - : Card( - clipBehavior: Clip.antiAlias, - elevation: 0, - margin: EdgeInsets.symmetric( - horizontal: (isFailedState - ? 15.0 - : 0.0) + + child: FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: 0.78, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + clipBehavior: Clip.none, + alignment: AlignmentDirectional.bottomStart, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.showUserAvatar == DisplayWidget.show && + widget.message.user != null) ...[ + _buildUserAvatar(), + const SizedBox(width: 4), + ], + if (widget.showUserAvatar == DisplayWidget.hide) + SizedBox(width: avatarWidth + 4), + Flexible( + child: PortalEntry( + portal: Container( + transform: + Matrix4.translationValues(-12, 0, 0), + constraints: const BoxConstraints( + maxWidth: 22 * 6.0), + child: _buildReactionIndicator(context), + ), + portalAnchor: const Alignment(-1, -1), + childAnchor: const Alignment(1, -1), + child: Stack( + clipBehavior: Clip.none, + children: [ + Padding( + padding: widget.showReactions + ? EdgeInsets.only( + top: widget + .message + .reactionCounts + ?.isNotEmpty == + true + ? 18 + : 0, + ) + : EdgeInsets.zero, + child: (widget.message.isDeleted && + !isFailedState) + ? Container( + // ignore: lines_longer_than_80_chars + margin: EdgeInsets.symmetric( + horizontal: // ignore: lines_longer_than_80_chars - (widget.showUserAvatar == + widget.showUserAvatar == + // ignore: lines_longer_than_80_chars DisplayWidget .gone ? 0 : 4.0), - ), - shape: widget.shape ?? - RoundedRectangleBorder( - side: widget - .borderSide ?? - BorderSide( - color: widget - // ignore: lines_longer_than_80_chars - .messageTheme - // ignore: lines_longer_than_80_chars - .messageBorderColor ?? - Colors.grey, - ), - borderRadius: widget - // ignore: lines_longer_than_80_chars - .borderRadiusGeometry ?? - BorderRadius.zero, - ), - color: _getBackgroundColor(), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.end, - mainAxisSize: - MainAxisSize.min, - children: [ - if (hasQuotedMessage) - _buildQuotedMessage(), - if (hasNonUrlAttachments) - _parseAttachments(), - if (!isGiphy) - _buildTextBubble(), - ], - ), + child: DeletedMessage( + borderRadiusGeometry: widget + .borderRadiusGeometry, + borderSide: widget.borderSide, + shape: widget.shape, + messageTheme: + widget.messageTheme, ), - ), - if (widget.showReactionPickerIndicator) - Positioned( - right: 4, - top: -8, - child: Transform( - transform: Matrix4.rotationY( - widget.reverse ? pi : 0), - child: CustomPaint( - painter: ReactionBubblePainter( - StreamChatTheme.of(context) - .colorTheme - .white, - Colors.transparent, - Colors.transparent, - tailCirclesSpace: 1, + ) + : Card( + clipBehavior: Clip.antiAlias, + elevation: 0, + margin: EdgeInsets.symmetric( + horizontal: (isFailedState + ? 15.0 + : 0.0) + + // ignore: lines_longer_than_80_chars + (widget.showUserAvatar == + DisplayWidget.gone + ? 0 + : 4.0), + ), + shape: widget.shape ?? + RoundedRectangleBorder( + side: widget.borderSide ?? + BorderSide( + color: widget + // ignore: lines_longer_than_80_chars + .messageTheme + // ignore: lines_longer_than_80_chars + .messageBorderColor ?? + Colors.grey, + ), + borderRadius: widget + // ignore: lines_longer_than_80_chars + .borderRadiusGeometry ?? + BorderRadius.zero, + ), + color: _getBackgroundColor(), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + mainAxisSize: + MainAxisSize.min, + children: [ + if (hasQuotedMessage) + _buildQuotedMessage(), + if (hasNonUrlAttachments) + _parseAttachments(), + if (!isGiphy) + _buildTextBubble(), + ], ), ), + ), + if (widget.showReactionPickerIndicator) + Positioned( + right: 4, + top: -8, + child: CustomPaint( + painter: ReactionBubblePainter( + StreamChatTheme.of(context) + .colorTheme + .white, + Colors.transparent, + Colors.transparent, + tailCirclesSpace: 1, ), ), - ], - ), + ), + ], ), ), - ], - ), - if (showBottomRow) - SizedBox(height: context.textScaleFactor * 18.0), - ], + ), + ], + ), + if (showBottomRow) + SizedBox(height: context.textScaleFactor * 18.0), + ], + ), + if (showBottomRow) + Padding( + padding: EdgeInsets.only(left: leftPadding), + child: _bottomRow, ), - if (showBottomRow) - Padding( - padding: EdgeInsets.only(left: leftPadding), - child: _bottomRow, - ), - if (isFailedState) - Positioned( - left: widget.reverse ? 0 : null, - right: widget.reverse ? null : 0, - bottom: showBottomRow ? 18 : -2, - child: StreamSvgIcon.error(size: 20), - ), - ], - ), - ], - ), + if (isFailedState) + Positioned( + left: widget.reverse ? 0 : null, + right: widget.reverse ? null : 0, + bottom: showBottomRow ? 18 : -2, + child: StreamSvgIcon.error(size: 20), + ), + ], + ), + ], ), ), ), @@ -672,26 +654,22 @@ class _MessageWidgetState extends State Widget get _bottomRow { if (isDeleted) { - return Transform( - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - alignment: Alignment.center, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - StreamSvgIcon.eye( - color: StreamChatTheme.of(context).colorTheme.grey, - size: 16, - ), - const SizedBox(width: 8), - Text( - 'Only visible to you', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith(color: StreamChatTheme.of(context).colorTheme.grey), - ), - ], - ), + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + StreamSvgIcon.eye( + color: StreamChatTheme.of(context).colorTheme.grey, + size: 16, + ), + const SizedBox(width: 8), + Text( + 'Only visible to you', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith(color: StreamChatTheme.of(context).colorTheme.grey), + ), + ], ); } @@ -775,13 +753,9 @@ class _MessageWidgetState extends State ), ...children.map( (child) { - Widget mappedChild = Transform( - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - alignment: Alignment.center, - child: SizedBox( - height: context.textScaleFactor * 14, - child: child, - ), + Widget mappedChild = SizedBox( + height: context.textScaleFactor * 14, + child: child, ); if (child.key == usernameKey) { mappedChild = Flexible(child: mappedChild); @@ -1071,56 +1045,47 @@ class _MessageWidgetState extends State return child; } - Widget _buildUserAvatar() => Transform( - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - alignment: Alignment.center, - child: Transform.translate( - offset: Offset( - 0, - widget.translateUserAvatar - ? (widget.messageTheme.avatarTheme?.constraints.maxHeight ?? - 40) / - 2 - : 0, - ), - child: UserAvatar( - user: widget.message.user!, - onTap: widget.onUserAvatarTap, - constraints: widget.messageTheme.avatarTheme!.constraints, - borderRadius: widget.messageTheme.avatarTheme!.borderRadius, - showOnlineStatus: false, - ), + Widget _buildUserAvatar() => Transform.translate( + offset: Offset( + 0, + widget.translateUserAvatar + ? (widget.messageTheme.avatarTheme?.constraints.maxHeight ?? 40) / + 2 + : 0, + ), + child: UserAvatar( + user: widget.message.user!, + onTap: widget.onUserAvatarTap, + constraints: widget.messageTheme.avatarTheme!.constraints, + borderRadius: widget.messageTheme.avatarTheme!.borderRadius, + showOnlineStatus: false, ), ); Widget _buildTextBubble() { if (widget.message.text!.trim().isEmpty) return const Offstage(); - return Transform( - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - alignment: Alignment.center, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: isOnlyEmoji ? EdgeInsets.zero : widget.textPadding, - child: widget.textBuilder != null - ? widget.textBuilder!(context, widget.message) - : MessageText( - onLinkTap: widget.onLinkTap, - message: widget.message, - onMentionTap: widget.onMentionTap, - messageTheme: isOnlyEmoji - ? widget.messageTheme.copyWith( - messageText: - widget.messageTheme.messageText!.copyWith( - fontSize: 42, - )) - : widget.messageTheme, - ), - ), - if (hasUrlAttachments && !hasQuotedMessage) _buildUrlAttachment(), - ], - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: isOnlyEmoji ? EdgeInsets.zero : widget.textPadding, + child: widget.textBuilder != null + ? widget.textBuilder!(context, widget.message) + : MessageText( + onLinkTap: widget.onLinkTap, + message: widget.message, + onMentionTap: widget.onMentionTap, + messageTheme: isOnlyEmoji + ? widget.messageTheme.copyWith( + messageText: + widget.messageTheme.messageText!.copyWith( + fontSize: 42, + )) + : widget.messageTheme, + ), + ), + if (hasUrlAttachments && !hasQuotedMessage) _buildUrlAttachment(), + ], ); } 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 6be7ded7..08b0c1dd 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -133,21 +133,17 @@ class QuotedMessageWidget extends StatelessWidget { if (_hasAttachments) _parseAttachments(context), if (msg.text!.isNotEmpty) Flexible( - child: Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: MessageText( - message: msg, - messageTheme: isOnlyEmoji && _containsText - ? messageTheme.copyWith( - messageText: messageTheme.messageText?.copyWith( - fontSize: 32, - )) - : messageTheme.copyWith( - messageText: messageTheme.messageText?.copyWith( - fontSize: 12, - )), - ), + child: MessageText( + message: msg, + messageTheme: isOnlyEmoji && _containsText + ? messageTheme.copyWith( + messageText: messageTheme.messageText?.copyWith( + fontSize: 32, + )) + : messageTheme.copyWith( + messageText: messageTheme.messageText?.copyWith( + fontSize: 12, + )), ), ), ].insertBetween(const SizedBox(width: 8)); @@ -218,15 +214,11 @@ class QuotedMessageWidget extends StatelessWidget { } } child = AbsorbPointer(child: child); - return Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: Material( - clipBehavior: Clip.antiAlias, - type: MaterialType.transparency, - shape: attachment.type == 'file' ? null : _getDefaultShape(context), - child: child, - ), + return Material( + clipBehavior: Clip.antiAlias, + type: MaterialType.transparency, + shape: attachment.type == 'file' ? null : _getDefaultShape(context), + child: child, ); } @@ -235,17 +227,13 @@ class QuotedMessageWidget extends StatelessWidget { borderRadius: BorderRadius.circular(8), ); - Widget _buildUserAvatar() => Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: UserAvatar( - user: message.user!, - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), - showOnlineStatus: false, + Widget _buildUserAvatar() => UserAvatar( + user: message.user!, + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, ), + showOnlineStatus: false, ); Map diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index 9b45c248..44549675 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -52,68 +52,64 @@ class ReactionBubble extends StatelessWidget { final reactionIcons = StreamChatTheme.of(context).reactionIcons; final totalReactions = reactions.length; final offset = totalReactions > 1 ? 16.0 : 2.0; - return Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), + return Stack( alignment: Alignment.center, - child: Stack( - alignment: Alignment.center, - children: [ - Transform.translate( - offset: Offset(reverse ? offset : -offset, 0), + children: [ + Transform.translate( + offset: Offset(reverse ? offset : -offset, 0), + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: maskColor, + borderRadius: const BorderRadius.all(Radius.circular(16)), + ), child: Container( - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - color: maskColor, - borderRadius: const BorderRadius.all(Radius.circular(16)), + padding: EdgeInsets.symmetric( + vertical: 4, + horizontal: totalReactions > 1 ? 4 : 0, ), - child: Container( - padding: EdgeInsets.symmetric( - vertical: 4, - horizontal: totalReactions > 1 ? 4 : 0, + decoration: BoxDecoration( + border: Border.all( + color: borderColor, ), - decoration: BoxDecoration( - border: Border.all( - color: borderColor, - ), - color: backgroundColor, - borderRadius: const BorderRadius.all(Radius.circular(14)), - ), - child: LayoutBuilder( - builder: (context, constraints) => Flex( - direction: Axis.horizontal, - mainAxisSize: MainAxisSize.min, - children: [ - if (constraints.maxWidth < double.infinity) - ...reactions - .take((constraints.maxWidth) ~/ 24) - .map((reaction) => _buildReaction( - reactionIcons, - reaction, - context, - )) - .toList(), - if (constraints.maxWidth == double.infinity) - ...reactions - .map((reaction) => _buildReaction( - reactionIcons, - reaction, - context, - )) - .toList(), - ], - ), + color: backgroundColor, + borderRadius: const BorderRadius.all(Radius.circular(14)), + ), + child: LayoutBuilder( + builder: (context, constraints) => Flex( + direction: Axis.horizontal, + mainAxisSize: MainAxisSize.min, + children: [ + if (constraints.maxWidth < double.infinity) + ...reactions + .take((constraints.maxWidth) ~/ 24) + .map((reaction) => _buildReaction( + reactionIcons, + reaction, + context, + )) + .toList(), + if (constraints.maxWidth == double.infinity) + ...reactions + .map((reaction) => _buildReaction( + reactionIcons, + reaction, + context, + )) + .toList(), + ], ), ), ), ), - Positioned( - bottom: 2, - left: reverse ? null : 13, - right: !reverse ? null : 13, - child: _buildReactionsTail(context), - ), - ], - ), + ), + Positioned( + bottom: 2, + left: reverse ? null : 13, + right: !reverse ? null : 13, + child: _buildReactionsTail(context), + ), + ], ); } @@ -166,11 +162,7 @@ class ReactionBubble extends StatelessWidget { tailCirclesSpace: tailCirclesSpacing, ), ); - return Transform( - transform: Matrix4.rotationY(flipTail ? 0 : pi), - alignment: Alignment.center, - child: tail, - ); + return tail; } } diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index cdd87e2d..8a35f60b 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -347,10 +347,6 @@ Widget wrapAttachmentWidget( clipBehavior: Clip.antiAlias, shape: attachmentShape, type: MaterialType.transparency, - child: Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: attachmentWidget, - ), + child: attachmentWidget, ), ); From 5f010ce82d4545e204b4bde463d5ba785e03b248 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 12 May 2021 13:52:02 +0200 Subject: [PATCH 236/599] fix tests --- .../lib/src/message_search_list_core.dart | 4 ++++ packages/stream_chat_flutter_core/lib/src/user_list_core.dart | 4 ++++ .../test/message_search_list_core_test.dart | 1 + 3 files changed, 9 insertions(+) diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index 199852b9..a1eb5d68 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -114,6 +114,10 @@ class MessageSearchListCoreState extends State { if (newMessageSearchBloc != _messageSearchBloc) { _messageSearchBloc = newMessageSearchBloc; loadData(); + if (widget.messageSearchListController != null) { + widget.messageSearchListController!.loadData = loadData; + widget.messageSearchListController!.paginateData = paginateData; + } } super.didChangeDependencies(); diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 3e4839b0..d34c563c 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -130,6 +130,10 @@ class UserListCoreState extends State if (newUsersBloc != _usersBloc) { _usersBloc = newUsersBloc; loadData(); + if (widget.userListController != null) { + widget.userListController!.loadData = loadData; + widget.userListController!.paginateData = paginateData; + } } super.didChangeDependencies(); } diff --git a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart index f704c9fd..932e35b4 100644 --- a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart @@ -107,6 +107,7 @@ void main() { ), ), ); + await tester.pumpAndSettle(); expect(find.byKey(messageSearchListCoreKey), findsOneWidget); expect(controller.loadData, isNotNull); From 485d99ddb4ae3d8c2536d200a0e79372390852de Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 12 May 2021 17:54:22 +0530 Subject: [PATCH 237/599] rfac: Alignment of messages and reaction bubbles --- .../lib/src/message_widget.dart | 20 ++++++++++++++----- .../lib/src/reaction_bubble.dart | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 83cd2593..e76c6532 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -466,18 +466,25 @@ class _MessageWidgetState extends State child: Padding( padding: widget.padding ?? const EdgeInsets.all(8), child: FractionallySizedBox( - alignment: Alignment.centerLeft, + alignment: + widget.reverse ? Alignment.centerRight : Alignment.centerLeft, widthFactor: 0.78, child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: widget.reverse + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Stack( clipBehavior: Clip.none, - alignment: AlignmentDirectional.bottomStart, + alignment: widget.reverse + ? AlignmentDirectional.bottomEnd + : AlignmentDirectional.bottomStart, children: [ Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: widget.reverse + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( @@ -501,7 +508,8 @@ class _MessageWidgetState extends State child: _buildReactionIndicator(context), ), portalAnchor: const Alignment(-1, -1), - childAnchor: const Alignment(1, -1), + childAnchor: + Alignment(widget.reverse ? -1 : 1, -1), child: Stack( clipBehavior: Clip.none, children: [ @@ -736,6 +744,8 @@ class _MessageWidgetState extends State return Row( crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: + widget.reverse ? MainAxisAlignment.end : MainAxisAlignment.start, children: [ if (showThreadTail) Container( diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index 44549675..f5ff2235 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -56,7 +56,7 @@ class ReactionBubble extends StatelessWidget { alignment: Alignment.center, children: [ Transform.translate( - offset: Offset(reverse ? offset : -offset, 0), + offset: Offset(reverse ? -offset : offset, 0), child: Container( padding: const EdgeInsets.all(2), decoration: BoxDecoration( From ea19e9000bbdcc5af30b0349d09e2d358446ec26 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 12 May 2021 15:39:21 +0200 Subject: [PATCH 238/599] move get replies logic to didupdatewidget --- .../lib/src/message_list_core.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 732ac3e6..22847f88 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -178,10 +178,10 @@ class MessageListCoreState extends State { final newStreamChannel = StreamChannel.of(context); if (newStreamChannel != _streamChannel) { - _streamChannel = newStreamChannel; - if (_isThreadConversation) { + if (_streamChannel == null /*only first time*/ && _isThreadConversation) { _streamChannel!.getReplies(widget.parentMessage!.id); } + _streamChannel = newStreamChannel; } super.didChangeDependencies(); @@ -194,6 +194,12 @@ class MessageListCoreState extends State { if (widget.messageListController != oldWidget.messageListController) { _setupController(); } + + if (widget.parentMessage?.id != widget.parentMessage?.id) { + if (_isThreadConversation) { + _streamChannel!.getReplies(widget.parentMessage!.id); + } + } } @override From 5de52d6601e30de92e02292b40ea51941ccc7139 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 12 May 2021 16:09:46 +0200 Subject: [PATCH 239/599] fix tests --- .../stream_chat_flutter_core/lib/src/message_list_core.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 22847f88..cf11ef5f 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -179,7 +179,7 @@ class MessageListCoreState extends State { if (newStreamChannel != _streamChannel) { if (_streamChannel == null /*only first time*/ && _isThreadConversation) { - _streamChannel!.getReplies(widget.parentMessage!.id); + newStreamChannel.getReplies(widget.parentMessage!.id); } _streamChannel = newStreamChannel; } From 2b9f20a04d6b8bfa528d7b2be59e943367465fc2 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 12 May 2021 16:36:44 +0200 Subject: [PATCH 240/599] add small test --- .../test/message_list_core_test.dart | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index f6ea335f..e53cd0a8 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -147,6 +147,56 @@ void main() { }, ); + testWidgets( + 'should assign paginateData callback and paginate data correctly if a MessageListController is passed', + (tester) async { + const messageListCoreKey = Key('messageListCore'); + final controller = MessageListController(); + final messageListCore = MessageListCore( + key: messageListCoreKey, + messageListBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + messageListController: controller, + ); + + expect(controller.paginateData, isNull); + + final mockChannel = MockChannel(); + + when(() => mockChannel.state.isUpToDate).thenReturn(true); + // when(() => mockChannel.query( + // messagesPagination: any(named: 'messagesPagination'), + // preferOffline: any(named: 'preferOffline'), + // )).thenAnswer((_) => mockChannel.state); + final messages = _generateMessages(); + when(() => mockChannel.state.messages).thenReturn(messages); + when(() => mockChannel.state.messagesStream) + .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true)); + + await tester.pumpWidget( + StreamChannel( + channel: mockChannel, + child: messageListCore, + ), + ); + + final finder = find.byKey(messageListCoreKey); + final coreState = tester.firstState(finder); + expect(finder, findsOneWidget); + expect(controller.paginateData, isNotNull); + + await coreState.paginateData(); + + verify(() => mockChannel.query( + messagesPagination: any(named: 'messagesPagination'), + preferOffline: any(named: 'preferOffline'), + )).called(1); + }, + ); + testWidgets( 'should build error widget if messagesStream emits error', (tester) async { From 1767200c39c17732b570ad0b10d491c1517a12cf Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 13 May 2021 17:19:40 +0530 Subject: [PATCH 241/599] rfac: Alignment of messages and reaction bubbles --- .../lib/src/reaction_bubble.dart | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index f5ff2235..ed864822 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -56,7 +56,7 @@ class ReactionBubble extends StatelessWidget { alignment: Alignment.center, children: [ Transform.translate( - offset: Offset(reverse ? -offset : offset, 0), + offset: Offset(-offset, 0), child: Container( padding: const EdgeInsets.all(2), decoration: BoxDecoration( @@ -106,7 +106,6 @@ class ReactionBubble extends StatelessWidget { Positioned( bottom: 2, left: reverse ? null : 13, - right: !reverse ? null : 13, child: _buildReactionsTail(context), ), ], @@ -160,6 +159,7 @@ class ReactionBubble extends StatelessWidget { borderColor, maskColor, tailCirclesSpace: tailCirclesSpacing, + flipTail: !flipTail, ), ); return tail; @@ -174,6 +174,7 @@ class ReactionBubblePainter extends CustomPainter { this.borderColor, this.maskColor, { this.tailCirclesSpace = 0, + this.flipTail = false, }); /// Color of bubble @@ -188,6 +189,9 @@ class ReactionBubblePainter extends CustomPainter { /// Tail circle space final double tailCirclesSpace; + /// Flip tail + final bool flipTail; + @override void paint(Canvas canvas, Size size) { _drawOvalMask(size, canvas); @@ -211,8 +215,9 @@ class ReactionBubblePainter extends CustomPainter { final path = Path() ..addOval( Rect.fromCircle( - center: - const Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace), + center: const Offset(4, 3).mirrorConditionally(flipTail) + + Offset(tailCirclesSpace, tailCirclesSpace) + .mirrorConditionally(flipTail), radius: 4, ), ); @@ -228,8 +233,9 @@ class ReactionBubblePainter extends CustomPainter { final path = Path() ..addOval( Rect.fromCircle( - center: - const Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace), + center: const Offset(4, 3).mirrorConditionally(flipTail) + + Offset(tailCirclesSpace, tailCirclesSpace) + .mirrorConditionally(flipTail), radius: 2, ), ); @@ -243,7 +249,9 @@ class ReactionBubblePainter extends CustomPainter { final path = Path() ..addOval(Rect.fromCircle( - center: const Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace), + center: const Offset(4, 3).mirrorConditionally(flipTail) + + Offset(tailCirclesSpace, tailCirclesSpace) + .mirrorConditionally(flipTail), radius: 2, )); canvas.drawPath(path, paint); @@ -261,7 +269,7 @@ class ReactionBubblePainter extends CustomPainter { final path = Path() ..addArc( Rect.fromCircle( - center: const Offset(1, dy), + center: const Offset(1, dy).mirrorConditionally(flipTail), radius: 4, ), -pi * startAngle, @@ -281,7 +289,7 @@ class ReactionBubblePainter extends CustomPainter { final path = Path() ..addArc( Rect.fromCircle( - center: const Offset(1, dy), + center: const Offset(1, dy).mirrorConditionally(flipTail), radius: 4, ), -pi * startAngle, @@ -302,7 +310,7 @@ class ReactionBubblePainter extends CustomPainter { final path = Path() ..addArc( Rect.fromCircle( - center: const Offset(1, dy), + center: const Offset(1, dy).mirrorConditionally(flipTail), radius: 6, ), -pi * startAngle, @@ -314,3 +322,10 @@ class ReactionBubblePainter extends CustomPainter { @override bool shouldRepaint(CustomPainter oldDelegate) => true; } + +/// Extension on [Offset] +extension YTransformer on Offset { + /// Flips x coordinate when flip is true + // ignore: avoid_positional_boolean_parameters + Offset mirrorConditionally(bool flip) => Offset(flip ? -dx : dx, dy); +} From 0ab428ce2e4093c828d35afcd335ebbe7c3f41f6 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 14 May 2021 11:24:05 +0200 Subject: [PATCH 242/599] use Map for extradata --- packages/stream_chat/lib/src/api/channel.dart | 4 ++-- packages/stream_chat/lib/src/client.dart | 2 +- packages/stream_chat/lib/src/models/attachment.dart | 4 ++-- .../stream_chat/lib/src/models/attachment.g.dart | 5 +---- .../lib/src/models/attachment_file.freezed.dart | 12 ++++-------- .../lib/src/models/attachment_file.g.dart | 2 +- .../stream_chat/lib/src/models/channel_model.dart | 4 ++-- .../stream_chat/lib/src/models/channel_model.g.dart | 5 +---- packages/stream_chat/lib/src/models/event.dart | 6 +++--- packages/stream_chat/lib/src/models/event.g.dart | 10 ++-------- packages/stream_chat/lib/src/models/message.dart | 4 ++-- packages/stream_chat/lib/src/models/message.g.dart | 5 +---- packages/stream_chat/lib/src/models/own_user.dart | 2 +- packages/stream_chat/lib/src/models/own_user.g.dart | 4 +--- packages/stream_chat/lib/src/models/reaction.dart | 4 ++-- packages/stream_chat/lib/src/models/reaction.g.dart | 4 +--- packages/stream_chat/lib/src/models/user.dart | 9 ++++++--- packages/stream_chat/lib/src/models/user.g.dart | 4 +--- 18 files changed, 34 insertions(+), 56 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 5f33f966..8cc5dd2d 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -22,7 +22,7 @@ class Channel { this._client, this._type, this._id, { - Map extraData = const {}, + Map extraData = const {}, }) : _cid = _id != null ? '$_type:$_id' : null, _extraData = extraData { _client.logger.info('New Channel instance not initialized created'); @@ -630,7 +630,7 @@ class Channel { Future sendReaction( Message message, String type, { - Map extraData = const {}, + Map extraData = const {}, bool enforceUnique = false, }) async { _checkInitialized(); diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index c7d29737..3cabe274 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1194,7 +1194,7 @@ class StreamChatClient { Channel channel( String type, { String? id, - Map extraData = const {}, + Map extraData = const {}, }) { if (id != null && state.channels.containsKey('$type:$id')) { return state.channels['$type:$id']!; diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/models/attachment.dart index 09ea4f58..b68a1bae 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/models/attachment.dart @@ -114,7 +114,7 @@ class Attachment extends Equatable { includeIfNull: false, defaultValue: {}, ) - final Map extraData; + final Map extraData; /// The attachment ID. /// @@ -183,7 +183,7 @@ class Attachment extends Equatable { List? actions, AttachmentFile? file, UploadState? uploadState, - Map? extraData, + Map? extraData, }) => Attachment( id: id ?? this.id, diff --git a/packages/stream_chat/lib/src/models/attachment.g.dart b/packages/stream_chat/lib/src/models/attachment.g.dart index c42aaf5f..de129ab6 100644 --- a/packages/stream_chat/lib/src/models/attachment.g.dart +++ b/packages/stream_chat/lib/src/models/attachment.g.dart @@ -30,10 +30,7 @@ Attachment _$AttachmentFromJson(Map json) { ?.map((e) => Action.fromJson(e as Map)) .toList() ?? [], - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k, e as Object), - ) ?? - {}, + extraData: json['extra_data'] as Map? ?? {}, file: json['file'] == null ? null : AttachmentFile.fromJson(json['file'] as Map), diff --git a/packages/stream_chat/lib/src/models/attachment_file.freezed.dart b/packages/stream_chat/lib/src/models/attachment_file.freezed.dart index a6c5e0c6..5d7075c3 100644 --- a/packages/stream_chat/lib/src/models/attachment_file.freezed.dart +++ b/packages/stream_chat/lib/src/models/attachment_file.freezed.dart @@ -132,9 +132,8 @@ class _$PreparingCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res> Preparing get _value => super._value as Preparing; } -@JsonSerializable() - /// @nodoc +@JsonSerializable() class _$Preparing implements Preparing { const _$Preparing(); @@ -253,9 +252,8 @@ class _$InProgressCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res> } } -@JsonSerializable() - /// @nodoc +@JsonSerializable() class _$InProgress implements InProgress { const _$InProgress({required this.uploaded, required this.total}); @@ -382,9 +380,8 @@ class _$SuccessCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res> Success get _value => super._value as Success; } -@JsonSerializable() - /// @nodoc +@JsonSerializable() class _$Success implements Success { const _$Success(); @@ -497,9 +494,8 @@ class _$FailedCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res> } } -@JsonSerializable() - /// @nodoc +@JsonSerializable() class _$Failed implements Failed { const _$Failed({required this.error}); diff --git a/packages/stream_chat/lib/src/models/attachment_file.g.dart b/packages/stream_chat/lib/src/models/attachment_file.g.dart index 55fcea6c..5844ae16 100644 --- a/packages/stream_chat/lib/src/models/attachment_file.g.dart +++ b/packages/stream_chat/lib/src/models/attachment_file.g.dart @@ -8,10 +8,10 @@ part of 'attachment_file.dart'; AttachmentFile _$AttachmentFileFromJson(Map json) { return AttachmentFile( + size: json['size'] as int?, path: json['path'] as String?, name: json['name'] as String?, bytes: _fromString(json['bytes'] as String?), - size: json['size'] as int?, ); } diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index 14380592..41230bf7 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -87,7 +87,7 @@ class ChannelModel { includeIfNull: false, defaultValue: {}, ) - final Map extraData; + final Map extraData; /// The team the channel belongs to @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @@ -132,7 +132,7 @@ class ChannelModel { DateTime? updatedAt, DateTime? deletedAt, int? memberCount, - Map? extraData, + Map? extraData, String? team, }) => ChannelModel( diff --git a/packages/stream_chat/lib/src/models/channel_model.g.dart b/packages/stream_chat/lib/src/models/channel_model.g.dart index 78e93910..4bde3d4a 100644 --- a/packages/stream_chat/lib/src/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/models/channel_model.g.dart @@ -31,10 +31,7 @@ ChannelModel _$ChannelModelFromJson(Map json) { ? null : DateTime.parse(json['deleted_at'] as String), memberCount: json['member_count'] as int? ?? 0, - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k, e as Object), - ) ?? - {}, + extraData: json['extra_data'] as Map? ?? {}, team: json['team'] as String?, ); } diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index 1675197d..b4ee9c1d 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -93,7 +93,7 @@ class Event { /// Map of custom channel extraData @JsonKey(defaultValue: {}) - final Map extraData; + final Map extraData; /// Known top level fields. /// Useful for [Serialization] methods. @@ -140,7 +140,7 @@ class Event { int? unreadChannels, bool? online, String? parentId, - Map? extraData, + Map? extraData, }) => Event( type: type ?? this.type, @@ -180,7 +180,7 @@ class EventChannel extends ChannelModel { required DateTime updatedAt, DateTime? deletedAt, required int memberCount, - Map? extraData, + Map? extraData, }) : super( id: id, type: type, diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index b76c675c..aba142f8 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -38,10 +38,7 @@ Event _$EventFromJson(Map json) { channelId: json['channel_id'] as String?, channelType: json['channel_type'] as String?, parentId: json['parent_id'] as String?, - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k, e as Object), - ) ?? - {}, + extraData: json['extra_data'] as Map? ?? {}, isLocal: json['is_local'] as bool? ?? false, ); } @@ -89,10 +86,7 @@ EventChannel _$EventChannelFromJson(Map json) { ? null : DateTime.parse(json['deleted_at'] as String), memberCount: json['member_count'] as int? ?? 0, - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k, e as Object), - ) ?? - {}, + extraData: json['extra_data'] as Map? ?? {}, ); } diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index cec591c0..f928ce3a 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -208,7 +208,7 @@ class Message extends Equatable { includeIfNull: false, defaultValue: {}, ) - final Map extraData; + final Map extraData; /// True if the message is a system info bool get isSystem => type == 'system'; @@ -289,7 +289,7 @@ class Message extends Equatable { DateTime? pinnedAt, Object? pinExpires = _pinExpires, User? pinnedBy, - Map? extraData, + Map? extraData, MessageSendingStatus? status, bool? skipPush, }) { diff --git a/packages/stream_chat/lib/src/models/message.g.dart b/packages/stream_chat/lib/src/models/message.g.dart index 4168777a..ca094a83 100644 --- a/packages/stream_chat/lib/src/models/message.g.dart +++ b/packages/stream_chat/lib/src/models/message.g.dart @@ -63,10 +63,7 @@ Message _$MessageFromJson(Map json) { pinnedBy: json['pinned_by'] == null ? null : User.fromJson(json['pinned_by'] as Map), - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k, e as Object), - ) ?? - {}, + extraData: json['extra_data'] as Map? ?? {}, deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), diff --git a/packages/stream_chat/lib/src/models/own_user.dart b/packages/stream_chat/lib/src/models/own_user.dart index 292e0f4a..a93af19a 100644 --- a/packages/stream_chat/lib/src/models/own_user.dart +++ b/packages/stream_chat/lib/src/models/own_user.dart @@ -23,7 +23,7 @@ class OwnUser extends User { DateTime? updatedAt, DateTime? lastActive, bool online = false, - Map extraData = const {}, + Map extraData = const {}, bool banned = false, }) : super( id: id, diff --git a/packages/stream_chat/lib/src/models/own_user.g.dart b/packages/stream_chat/lib/src/models/own_user.g.dart index a5c27402..760edc01 100644 --- a/packages/stream_chat/lib/src/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/models/own_user.g.dart @@ -34,9 +34,7 @@ OwnUser _$OwnUserFromJson(Map json) { ? null : DateTime.parse(json['last_active'] as String), online: json['online'] as bool? ?? false, - extraData: (json['extra_data'] as Map).map( - (k, e) => MapEntry(k, e as Object), - ), + extraData: json['extra_data'] as Map? ?? {}, banned: json['banned'] as bool? ?? false, ); } diff --git a/packages/stream_chat/lib/src/models/reaction.dart b/packages/stream_chat/lib/src/models/reaction.dart index 0e60e281..c619af76 100644 --- a/packages/stream_chat/lib/src/models/reaction.dart +++ b/packages/stream_chat/lib/src/models/reaction.dart @@ -50,7 +50,7 @@ class Reaction { /// Reaction custom extraData @JsonKey(includeIfNull: false) - final Map? extraData; + final Map? extraData; /// Map of custom user extraData static const topLevelFields = [ @@ -75,7 +75,7 @@ class Reaction { User? user, String? userId, int? score, - Map? extraData, + Map? extraData, }) => Reaction( messageId: messageId ?? this.messageId, diff --git a/packages/stream_chat/lib/src/models/reaction.g.dart b/packages/stream_chat/lib/src/models/reaction.g.dart index 24b60830..963b4d21 100644 --- a/packages/stream_chat/lib/src/models/reaction.g.dart +++ b/packages/stream_chat/lib/src/models/reaction.g.dart @@ -18,9 +18,7 @@ Reaction _$ReactionFromJson(Map json) { : User.fromJson(json['user'] as Map), userId: json['user_id'] as String?, score: json['score'] as int? ?? 0, - extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k, e as Object), - ), + extraData: json['extra_data'] as Map?, ); } diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/models/user.dart index 2cb8f8ed..9335f8c8 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/models/user.dart @@ -74,8 +74,11 @@ class User { final bool banned; /// Map of custom user extraData - @JsonKey(includeIfNull: false) - final Map extraData; + @JsonKey( + includeIfNull: false, + defaultValue: {}, + ) + final Map extraData; @override int get hashCode => id.hashCode; @@ -107,7 +110,7 @@ class User { DateTime? updatedAt, DateTime? lastActive, bool? online, - Map? extraData, + Map? extraData, bool? banned, List? teams, }) => diff --git a/packages/stream_chat/lib/src/models/user.g.dart b/packages/stream_chat/lib/src/models/user.g.dart index 11538395..befcac03 100644 --- a/packages/stream_chat/lib/src/models/user.g.dart +++ b/packages/stream_chat/lib/src/models/user.g.dart @@ -20,9 +20,7 @@ User _$UserFromJson(Map json) { ? null : DateTime.parse(json['last_active'] as String), online: json['online'] as bool? ?? false, - extraData: (json['extra_data'] as Map).map( - (k, e) => MapEntry(k, e as Object), - ), + extraData: json['extra_data'] as Map? ?? {}, banned: json['banned'] as bool? ?? false, teams: (json['teams'] as List?)?.map((e) => e as String).toList() ?? From a46186e6a7176e8026b9664d9a9ba0590da42669 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 14 May 2021 11:28:32 +0200 Subject: [PATCH 243/599] use Map for extradata in persistence --- .../lib/src/db/moor_chat_database.g.dart | 120 +++++++++--------- .../lib/src/entity/channels.dart | 2 +- .../lib/src/entity/messages.dart | 2 +- .../lib/src/entity/reactions.dart | 2 +- .../lib/src/entity/users.dart | 2 +- 5 files changed, 63 insertions(+), 65 deletions(-) diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index 8220ee4e..b8d404b2 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -42,22 +42,20 @@ class ChannelEntity extends DataClass implements Insertable { final String? createdById; /// Map of custom channel extraData - final Map? extraData; - - ChannelEntity({ - required this.id, - required this.type, - required this.cid, - required this.config, - required this.frozen, - this.lastMessageAt, - required this.createdAt, - required this.updatedAt, - this.deletedAt, - required this.memberCount, - this.createdById, - this.extraData, - }); + final Map? extraData; + ChannelEntity( + {required this.id, + required this.type, + required this.cid, + required this.config, + required this.frozen, + this.lastMessageAt, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.memberCount, + this.createdById, + this.extraData}); factory ChannelEntity.fromData( Map data, GeneratedDatabase db, {String? prefix}) { @@ -135,7 +133,7 @@ class ChannelEntity extends DataClass implements Insertable { deletedAt: serializer.fromJson(json['deletedAt']), memberCount: serializer.fromJson(json['memberCount']), createdById: serializer.fromJson(json['createdById']), - extraData: serializer.fromJson?>(json['extraData']), + extraData: serializer.fromJson?>(json['extraData']), ); } @override @@ -153,7 +151,7 @@ class ChannelEntity extends DataClass implements Insertable { 'deletedAt': serializer.toJson(deletedAt), 'memberCount': serializer.toJson(memberCount), 'createdById': serializer.toJson(createdById), - 'extraData': serializer.toJson?>(extraData), + 'extraData': serializer.toJson?>(extraData), }; } @@ -169,7 +167,7 @@ class ChannelEntity extends DataClass implements Insertable { Value deletedAt = const Value.absent(), int? memberCount, Value createdById = const Value.absent(), - Value?> extraData = const Value.absent()}) => + Value?> extraData = const Value.absent()}) => ChannelEntity( id: id ?? this.id, type: type ?? this.type, @@ -257,7 +255,7 @@ class ChannelsCompanion extends UpdateCompanion { final Value deletedAt; final Value memberCount; final Value createdById; - final Value?> extraData; + final Value?> extraData; const ChannelsCompanion({ this.id = const Value.absent(), this.type = const Value.absent(), @@ -301,7 +299,7 @@ class ChannelsCompanion extends UpdateCompanion { Expression? deletedAt, Expression? memberCount, Expression? createdById, - Expression?>? extraData, + Expression?>? extraData, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -331,7 +329,7 @@ class ChannelsCompanion extends UpdateCompanion { Value? deletedAt, Value? memberCount, Value? createdById, - Value?>? extraData}) { + Value?>? extraData}) { return ChannelsCompanion( id: id ?? this.id, type: type ?? this.type, @@ -638,8 +636,8 @@ class $ChannelsTable extends Channels static TypeConverter, String> $converter0 = MapConverter(); - static TypeConverter, String> $converter1 = - MapConverter(); + static TypeConverter, String> $converter1 = + MapConverter(); } class MessageEntity extends DataClass implements Insertable { @@ -714,7 +712,7 @@ class MessageEntity extends DataClass implements Insertable { final String? channelCid; /// Message custom extraData - final Map? extraData; + final Map? extraData; MessageEntity( {required this.id, this.messageText, @@ -901,7 +899,7 @@ class MessageEntity extends DataClass implements Insertable { pinExpires: serializer.fromJson(json['pinExpires']), pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), channelCid: serializer.fromJson(json['channelCid']), - extraData: serializer.fromJson?>(json['extraData']), + extraData: serializer.fromJson?>(json['extraData']), ); } @override @@ -931,7 +929,7 @@ class MessageEntity extends DataClass implements Insertable { 'pinExpires': serializer.toJson(pinExpires), 'pinnedByUserId': serializer.toJson(pinnedByUserId), 'channelCid': serializer.toJson(channelCid), - 'extraData': serializer.toJson?>(extraData), + 'extraData': serializer.toJson?>(extraData), }; } @@ -959,7 +957,7 @@ class MessageEntity extends DataClass implements Insertable { Value pinExpires = const Value.absent(), Value pinnedByUserId = const Value.absent(), Value channelCid = const Value.absent(), - Value?> extraData = const Value.absent()}) => + Value?> extraData = const Value.absent()}) => MessageEntity( id: id ?? this.id, messageText: messageText.present ? messageText.value : this.messageText, @@ -1121,7 +1119,7 @@ class MessagesCompanion extends UpdateCompanion { final Value pinExpires; final Value pinnedByUserId; final Value channelCid; - final Value?> extraData; + final Value?> extraData; const MessagesCompanion({ this.id = const Value.absent(), this.messageText = const Value.absent(), @@ -1200,7 +1198,7 @@ class MessagesCompanion extends UpdateCompanion { Expression? pinExpires, Expression? pinnedByUserId, Expression? channelCid, - Expression?>? extraData, + Expression?>? extraData, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -1254,7 +1252,7 @@ class MessagesCompanion extends UpdateCompanion { Value? pinExpires, Value? pinnedByUserId, Value? channelCid, - Value?>? extraData}) { + Value?>? extraData}) { return MessagesCompanion( id: id ?? this.id, messageText: messageText ?? this.messageText, @@ -1818,8 +1816,8 @@ class $MessagesTable extends Messages MapConverter(); static TypeConverter, String> $converter4 = MapConverter(); - static TypeConverter, String> $converter5 = - MapConverter(); + static TypeConverter, String> $converter5 = + MapConverter(); } class PinnedMessageEntity extends DataClass @@ -1895,7 +1893,7 @@ class PinnedMessageEntity extends DataClass final String? channelCid; /// Message custom extraData - final Map? extraData; + final Map? extraData; PinnedMessageEntity( {required this.id, this.messageText, @@ -2082,7 +2080,7 @@ class PinnedMessageEntity extends DataClass pinExpires: serializer.fromJson(json['pinExpires']), pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), channelCid: serializer.fromJson(json['channelCid']), - extraData: serializer.fromJson?>(json['extraData']), + extraData: serializer.fromJson?>(json['extraData']), ); } @override @@ -2112,7 +2110,7 @@ class PinnedMessageEntity extends DataClass 'pinExpires': serializer.toJson(pinExpires), 'pinnedByUserId': serializer.toJson(pinnedByUserId), 'channelCid': serializer.toJson(channelCid), - 'extraData': serializer.toJson?>(extraData), + 'extraData': serializer.toJson?>(extraData), }; } @@ -2140,7 +2138,7 @@ class PinnedMessageEntity extends DataClass Value pinExpires = const Value.absent(), Value pinnedByUserId = const Value.absent(), Value channelCid = const Value.absent(), - Value?> extraData = const Value.absent()}) => + Value?> extraData = const Value.absent()}) => PinnedMessageEntity( id: id ?? this.id, messageText: messageText.present ? messageText.value : this.messageText, @@ -2302,7 +2300,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { final Value pinExpires; final Value pinnedByUserId; final Value channelCid; - final Value?> extraData; + final Value?> extraData; const PinnedMessagesCompanion({ this.id = const Value.absent(), this.messageText = const Value.absent(), @@ -2381,7 +2379,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { Expression? pinExpires, Expression? pinnedByUserId, Expression? channelCid, - Expression?>? extraData, + Expression?>? extraData, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -2435,7 +2433,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { Value? pinExpires, Value? pinnedByUserId, Value? channelCid, - Value?>? extraData}) { + Value?>? extraData}) { return PinnedMessagesCompanion( id: id ?? this.id, messageText: messageText ?? this.messageText, @@ -3000,8 +2998,8 @@ class $PinnedMessagesTable extends PinnedMessages MapConverter(); static TypeConverter, String> $converter4 = MapConverter(); - static TypeConverter, String> $converter5 = - MapConverter(); + static TypeConverter, String> $converter5 = + MapConverter(); } class ReactionEntity extends DataClass implements Insertable { @@ -3021,7 +3019,7 @@ class ReactionEntity extends DataClass implements Insertable { final int score; /// Reaction custom extraData - final Map? extraData; + final Map? extraData; ReactionEntity( {required this.userId, required this.messageId, @@ -3073,7 +3071,7 @@ class ReactionEntity extends DataClass implements Insertable { type: serializer.fromJson(json['type']), createdAt: serializer.fromJson(json['createdAt']), score: serializer.fromJson(json['score']), - extraData: serializer.fromJson?>(json['extraData']), + extraData: serializer.fromJson?>(json['extraData']), ); } @override @@ -3085,7 +3083,7 @@ class ReactionEntity extends DataClass implements Insertable { 'type': serializer.toJson(type), 'createdAt': serializer.toJson(createdAt), 'score': serializer.toJson(score), - 'extraData': serializer.toJson?>(extraData), + 'extraData': serializer.toJson?>(extraData), }; } @@ -3095,7 +3093,7 @@ class ReactionEntity extends DataClass implements Insertable { String? type, DateTime? createdAt, int? score, - Value?> extraData = const Value.absent()}) => + Value?> extraData = const Value.absent()}) => ReactionEntity( userId: userId ?? this.userId, messageId: messageId ?? this.messageId, @@ -3144,7 +3142,7 @@ class ReactionsCompanion extends UpdateCompanion { final Value type; final Value createdAt; final Value score; - final Value?> extraData; + final Value?> extraData; const ReactionsCompanion({ this.userId = const Value.absent(), this.messageId = const Value.absent(), @@ -3169,7 +3167,7 @@ class ReactionsCompanion extends UpdateCompanion { Expression? type, Expression? createdAt, Expression? score, - Expression?>? extraData, + Expression?>? extraData, }) { return RawValuesInsertable({ if (userId != null) 'user_id': userId, @@ -3187,7 +3185,7 @@ class ReactionsCompanion extends UpdateCompanion { Value? type, Value? createdAt, Value? score, - Value?>? extraData}) { + Value?>? extraData}) { return ReactionsCompanion( userId: userId ?? this.userId, messageId: messageId ?? this.messageId, @@ -3357,8 +3355,8 @@ class $ReactionsTable extends Reactions return $ReactionsTable(_db, alias); } - static TypeConverter, String> $converter0 = - MapConverter(); + static TypeConverter, String> $converter0 = + MapConverter(); } class UserEntity extends DataClass implements Insertable { @@ -3384,7 +3382,7 @@ class UserEntity extends DataClass implements Insertable { final bool banned; /// Map of custom user extraData - final Map extraData; + final Map extraData; UserEntity( {required this.id, this.role, @@ -3449,7 +3447,7 @@ class UserEntity extends DataClass implements Insertable { lastActive: serializer.fromJson(json['lastActive']), online: serializer.fromJson(json['online']), banned: serializer.fromJson(json['banned']), - extraData: serializer.fromJson>(json['extraData']), + extraData: serializer.fromJson>(json['extraData']), ); } @override @@ -3463,7 +3461,7 @@ class UserEntity extends DataClass implements Insertable { 'lastActive': serializer.toJson(lastActive), 'online': serializer.toJson(online), 'banned': serializer.toJson(banned), - 'extraData': serializer.toJson>(extraData), + 'extraData': serializer.toJson>(extraData), }; } @@ -3475,7 +3473,7 @@ class UserEntity extends DataClass implements Insertable { Value lastActive = const Value.absent(), bool? online, bool? banned, - Map? extraData}) => + Map? extraData}) => UserEntity( id: id ?? this.id, role: role.present ? role.value : this.role, @@ -3536,7 +3534,7 @@ class UsersCompanion extends UpdateCompanion { final Value lastActive; final Value online; final Value banned; - final Value> extraData; + final Value> extraData; const UsersCompanion({ this.id = const Value.absent(), this.role = const Value.absent(), @@ -3555,7 +3553,7 @@ class UsersCompanion extends UpdateCompanion { this.lastActive = const Value.absent(), this.online = const Value.absent(), this.banned = const Value.absent(), - required Map extraData, + required Map extraData, }) : id = Value(id), extraData = Value(extraData); static Insertable custom({ @@ -3566,7 +3564,7 @@ class UsersCompanion extends UpdateCompanion { Expression? lastActive, Expression? online, Expression? banned, - Expression>? extraData, + Expression>? extraData, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -3588,7 +3586,7 @@ class UsersCompanion extends UpdateCompanion { Value? lastActive, Value? online, Value? banned, - Value>? extraData}) { + Value>? extraData}) { return UsersCompanion( id: id ?? this.id, role: role ?? this.role, @@ -3791,8 +3789,8 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { return $UsersTable(_db, alias); } - static TypeConverter, String> $converter0 = - MapConverter(); + static TypeConverter, String> $converter0 = + MapConverter(); } class MemberEntity extends DataClass implements Insertable { diff --git a/packages/stream_chat_persistence/lib/src/entity/channels.dart b/packages/stream_chat_persistence/lib/src/entity/channels.dart index e6c280ed..2c65719b 100644 --- a/packages/stream_chat_persistence/lib/src/entity/channels.dart +++ b/packages/stream_chat_persistence/lib/src/entity/channels.dart @@ -39,7 +39,7 @@ class Channels extends Table { TextColumn get createdById => text().nullable()(); /// Map of custom channel extraData - TextColumn get extraData => text().nullable().map(MapConverter())(); + TextColumn get extraData => text().nullable().map(MapConverter())(); @override Set get primaryKey => {cid}; diff --git a/packages/stream_chat_persistence/lib/src/entity/messages.dart b/packages/stream_chat_persistence/lib/src/entity/messages.dart index 7cb1326d..4006d6ce 100644 --- a/packages/stream_chat_persistence/lib/src/entity/messages.dart +++ b/packages/stream_chat_persistence/lib/src/entity/messages.dart @@ -81,7 +81,7 @@ class Messages extends Table { 'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE')(); /// Message custom extraData - TextColumn get extraData => text().nullable().map(MapConverter())(); + TextColumn get extraData => text().nullable().map(MapConverter())(); @override Set get primaryKey => {id}; diff --git a/packages/stream_chat_persistence/lib/src/entity/reactions.dart b/packages/stream_chat_persistence/lib/src/entity/reactions.dart index 2b7d12b5..62380408 100644 --- a/packages/stream_chat_persistence/lib/src/entity/reactions.dart +++ b/packages/stream_chat_persistence/lib/src/entity/reactions.dart @@ -22,7 +22,7 @@ class Reactions extends Table { IntColumn get score => integer().withDefault(const Constant(0))(); /// Reaction custom extraData - TextColumn get extraData => text().nullable().map(MapConverter())(); + TextColumn get extraData => text().nullable().map(MapConverter())(); @override Set get primaryKey => { diff --git a/packages/stream_chat_persistence/lib/src/entity/users.dart b/packages/stream_chat_persistence/lib/src/entity/users.dart index 49fb4687..303092c9 100644 --- a/packages/stream_chat_persistence/lib/src/entity/users.dart +++ b/packages/stream_chat_persistence/lib/src/entity/users.dart @@ -27,7 +27,7 @@ class Users extends Table { BoolColumn get banned => boolean().withDefault(const Constant(false))(); /// Map of custom user extraData - TextColumn get extraData => text().map(MapConverter())(); + TextColumn get extraData => text().map(MapConverter())(); @override Set get primaryKey => {id}; From af59b7af8a20e902353d5213beffb65010ecda41 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 14 May 2021 11:44:48 +0200 Subject: [PATCH 244/599] map correctly mentioned users --- .../stream_chat_persistence/lib/src/mapper/message_mapper.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart index c97cc92d..558c6e45 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart @@ -42,6 +42,8 @@ extension MessageEntityX on MessageEntity { pinnedAt: pinnedAt, pinExpires: pinExpires, pinnedBy: pinnedBy, + mentionedUsers: + mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), ); } From 09cbec649fbe8309eddc914eb0e809345c20fff9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 14 May 2021 15:51:54 +0530 Subject: [PATCH 245/599] alignment changes --- .../stream_chat_flutter/lib/src/message_widget.dart | 7 ++++--- .../lib/src/reaction_bubble.dart | 13 +++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 711ae8ae..61b5d13f 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -507,13 +507,14 @@ class _MessageWidgetState extends State Flexible( child: PortalEntry( portal: Container( - transform: - Matrix4.translationValues(-12, 0, 0), + transform: Matrix4.translationValues( + widget.reverse ? 12 : -12, 0, 0), constraints: const BoxConstraints( maxWidth: 22 * 6.0), child: _buildReactionIndicator(context), ), - portalAnchor: const Alignment(-1, -1), + portalAnchor: + Alignment(widget.reverse ? 1 : -1, -1), childAnchor: Alignment(widget.reverse ? -1 : 1, -1), child: Stack( diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index 19bd5977..1929e5bf 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -51,7 +51,8 @@ class ReactionBubble extends StatelessWidget { Widget build(BuildContext context) { final reactionIcons = StreamChatTheme.of(context).reactionIcons; final totalReactions = reactions.length; - final offset = totalReactions > 1 ? 16.0 : 2.0; + final offset = + totalReactions > 1 ? 16.0.mirrorConditionally(flipTail) : 2.0; return Stack( alignment: Alignment.center, children: [ @@ -66,7 +67,7 @@ class ReactionBubble extends StatelessWidget { child: Container( padding: EdgeInsets.symmetric( vertical: 4, - horizontal: totalReactions > 1 ? 4 : 0, + horizontal: totalReactions > 1 ? 4.0 : 0, ), decoration: BoxDecoration( border: Border.all( @@ -106,6 +107,7 @@ class ReactionBubble extends StatelessWidget { Positioned( bottom: 2, left: reverse ? null : 13, + right: reverse ? 13 : null, child: _buildReactionsTail(context), ), ], @@ -323,3 +325,10 @@ extension YTransformer on Offset { // ignore: avoid_positional_boolean_parameters Offset mirrorConditionally(bool flip) => Offset(flip ? -dx : dx, dy); } + +/// Extension on [Offset] +extension IntTransformer on double { + /// Flips x coordinate when flip is true + // ignore: avoid_positional_boolean_parameters + double mirrorConditionally(bool flip) => flip ? -this : this; +} From 084d339cc4115609d331480ac926214a0c00287d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 14 May 2021 12:55:34 +0200 Subject: [PATCH 246/599] add tests --- .../test/src/mapper/message_mapper_test.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart index f73102f8..2c852ff2 100644 --- a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -48,7 +48,9 @@ void main() { (prev, curr) => prev?..update(curr.type, (value) => value + 1, ifAbsent: () => 1), ), - mentionedUsers: const [], + mentionedUsers: [ + jsonEncode(User(id: 'testuser')), + ], status: MessageSendingStatus.sent, updatedAt: DateTime.now(), extraData: {'extra_test_data': 'extraData'}, @@ -77,6 +79,11 @@ void main() { expect(message.createdAt, isSameDateAs(entity.createdAt)); expect(message.shadowed, entity.shadowed); expect(message.showInChannel, entity.showInChannel); + for (var i = 0; i < message.mentionedUsers.length; i++) { + final entityMentionedUser = + User.fromJson(jsonDecode(entity.mentionedUsers[i])); + expect(message.mentionedUsers[i].id, entityMentionedUser.id); + } expect(message.replyCount, entity.replyCount); expect(message.reactionScores, entity.reactionScores); expect(message.reactionCounts, entity.reactionCounts); @@ -135,6 +142,9 @@ void main() { shadowed: math.Random().nextBool(), showInChannel: math.Random().nextBool(), replyCount: 33, + mentionedUsers: [ + User(id: 'testuser'), + ], reactionScores: {for (final r in reactions) r.type: r.score}, reactionCounts: reactions.fold( {}, @@ -163,6 +173,8 @@ void main() { expect(entity.shadowed, message.shadowed); expect(entity.showInChannel, message.showInChannel); expect(entity.replyCount, message.replyCount); + expect(entity.mentionedUsers, + message.mentionedUsers.map((e) => jsonEncode(e)).toList()); expect(entity.reactionScores, message.reactionScores); expect(entity.reactionCounts, message.reactionCounts); expect(entity.status, message.status); From 3bd11d0c45038288ca7dd2d3960de871f18ff5dd Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 14 May 2021 13:39:49 +0200 Subject: [PATCH 247/599] fix reaction extradata --- packages/stream_chat/lib/src/models/reaction.dart | 9 ++++++--- packages/stream_chat/lib/src/models/reaction.g.dart | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat/lib/src/models/reaction.dart b/packages/stream_chat/lib/src/models/reaction.dart index c619af76..85697204 100644 --- a/packages/stream_chat/lib/src/models/reaction.dart +++ b/packages/stream_chat/lib/src/models/reaction.dart @@ -15,7 +15,7 @@ class Reaction { this.user, String? userId, this.score = 0, - this.extraData, + this.extraData = const {}, }) : userId = userId ?? user?.id, createdAt = createdAt ?? DateTime.now(); @@ -49,8 +49,11 @@ class Reaction { final String? userId; /// Reaction custom extraData - @JsonKey(includeIfNull: false) - final Map? extraData; + @JsonKey( + includeIfNull: false, + defaultValue: {}, + ) + final Map extraData; /// Map of custom user extraData static const topLevelFields = [ diff --git a/packages/stream_chat/lib/src/models/reaction.g.dart b/packages/stream_chat/lib/src/models/reaction.g.dart index 963b4d21..3e92150d 100644 --- a/packages/stream_chat/lib/src/models/reaction.g.dart +++ b/packages/stream_chat/lib/src/models/reaction.g.dart @@ -18,7 +18,7 @@ Reaction _$ReactionFromJson(Map json) { : User.fromJson(json['user'] as Map), userId: json['user_id'] as String?, score: json['score'] as int? ?? 0, - extraData: json['extra_data'] as Map?, + extraData: json['extra_data'] as Map? ?? {}, ); } @@ -38,6 +38,6 @@ Map _$ReactionToJson(Reaction instance) { writeNotNull('user', readonly(instance.user)); val['score'] = instance.score; writeNotNull('user_id', readonly(instance.userId)); - writeNotNull('extra_data', instance.extraData); + val['extra_data'] = instance.extraData; return val; } From 527e979660636b0d5154504beffab8d97cbb88c1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 14 May 2021 13:48:36 +0200 Subject: [PATCH 248/599] fix reaction extradata in persistence --- .../stream_chat_persistence/lib/src/mapper/reaction_mapper.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart index 003802b9..62a0c427 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart @@ -5,7 +5,7 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; extension ReactionEntityX on ReactionEntity { /// Maps a [ReactionEntity] into [Reaction] Reaction toReaction({User? user}) => Reaction( - extraData: extraData, + extraData: extraData ?? {}, type: type, createdAt: createdAt, userId: userId, From a04c46090aca55bebdf4c9ac19e6517f52095f83 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 14 May 2021 14:03:31 +0200 Subject: [PATCH 249/599] bump db version --- .../stream_chat_persistence/lib/src/db/moor_chat_database.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart index 622e0a61..fbb53fd3 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 @@ -57,7 +57,7 @@ class MoorChatDatabase extends _$MoorChatDatabase { // you should bump this number whenever you change or add a table definition. @override - int get schemaVersion => 2; + int get schemaVersion => 3; @override MigrationStrategy get migration => MigrationStrategy( From 38e3d32bcb7949b1cf2d80115a5466735df3def6 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 14 May 2021 17:53:23 +0530 Subject: [PATCH 250/599] alignment changes --- .../lib/src/message_reactions_modal.dart | 12 ++++++------ .../stream_chat_flutter/lib/src/message_widget.dart | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) 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 76ce215c..8d5b408e 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -112,12 +112,12 @@ class MessageReactionsModal extends StatelessWidget { Align( alignment: Alignment( user!.id == message.user!.id - ? (divFactor > 1.0 - ? 0.0 - : (1.0 - divFactor)) - : (divFactor > 1.0 - ? 0.0 - : -(1.0 - divFactor)), + ? (divFactor >= 1.0 + ? -0.2 + : (1.2 - divFactor)) + : (divFactor >= 1.0 + ? 0.2 + : -(1.2 - divFactor)), 0), child: ReactionPicker( message: message, diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 61b5d13f..8edc1cb1 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -602,7 +602,8 @@ class _MessageWidgetState extends State ), if (widget.showReactionPickerIndicator) Positioned( - right: 4, + right: widget.reverse ? null : 4, + left: widget.reverse ? 4 : null, top: -8, child: CustomPaint( painter: ReactionBubblePainter( From 08635729d566122c3bf8c6fe186444024b175845 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 14 May 2021 18:16:38 +0530 Subject: [PATCH 251/599] fix: Fixed threads --- .../lib/src/message_widget.dart | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 8edc1cb1..c30fc543 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -755,7 +755,7 @@ class _MessageWidgetState extends State mainAxisAlignment: widget.reverse ? MainAxisAlignment.end : MainAxisAlignment.start, children: [ - if (showThreadTail) + if (showThreadTail && !widget.reverse) Container( margin: EdgeInsets.only( bottom: context.textScaleFactor * @@ -766,6 +766,7 @@ class _MessageWidgetState extends State painter: _ThreadReplyPainter( context: context, color: widget.messageTheme.messageBorderColor, + reverse: widget.reverse, ), ), ), @@ -781,6 +782,21 @@ class _MessageWidgetState extends State return mappedChild; }, ), + if (showThreadTail && widget.reverse) + Container( + margin: EdgeInsets.only( + bottom: context.textScaleFactor * + ((widget.messageTheme.replies?.fontSize ?? 1) / 2), + ), + child: CustomPaint( + size: const Size(16, 32) * context.textScaleFactor, + painter: _ThreadReplyPainter( + context: context, + color: widget.messageTheme.messageBorderColor, + reverse: widget.reverse, + ), + ), + ), ].insertBetween(const SizedBox(width: 8)), ); } @@ -1148,10 +1164,15 @@ class _MessageWidgetState extends State } class _ThreadReplyPainter extends CustomPainter { - const _ThreadReplyPainter({this.context, required this.color}); + const _ThreadReplyPainter({ + this.context, + required this.color, + this.reverse = false, + }); final Color? color; final BuildContext? context; + final bool reverse; @override void paint(Canvas canvas, Size size) { @@ -1162,12 +1183,13 @@ class _ThreadReplyPainter extends CustomPainter { ..strokeCap = StrokeCap.round; final path = Path() - ..moveTo(0, 0) - ..quadraticBezierTo(0, size.height * 0.38, 0, size.height * 0.50) + ..moveTo(reverse ? size.width : 0, 0) + ..quadraticBezierTo(reverse ? size.width : 0, size.height * 0.38, + reverse ? size.width : 0, size.height * 0.50) ..quadraticBezierTo( - 0, + reverse ? size.width : 0, size.height, - size.width, + reverse ? 0 : size.width, size.height, ); canvas.drawPath(path, paint); From 5f0ed1bfcbc8ec3e03f71b7c442e0d5097b98b4f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 14 May 2021 18:19:19 +0530 Subject: [PATCH 252/599] fix: analysis --- packages/stream_chat_flutter/lib/src/deleted_message.dart | 2 -- packages/stream_chat_flutter/lib/src/message_widget.dart | 1 - packages/stream_chat_flutter/lib/src/quoted_message_widget.dart | 2 -- packages/stream_chat_flutter/lib/src/utils.dart | 2 -- 4 files changed, 7 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart index c29437cc..9a4e0ae2 100644 --- a/packages/stream_chat_flutter/lib/src/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index c30fc543..781f9187 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -1,4 +1,3 @@ -import 'dart:math'; import 'dart:ui'; import 'package:flutter/cupertino.dart'; 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 08b0c1dd..a7c7c798 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index e98c4755..d24ccc7e 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; From 712a156cce71dc82d256eff1c4e86d60eea9abcc Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 14 May 2021 15:31:40 +0200 Subject: [PATCH 253/599] fix querymembers --- packages/stream_chat/lib/src/api/channel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 5f33f966..caf5ab3d 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1099,7 +1099,7 @@ class Channel { }) async { final payload = { 'sort': sort, - 'filter_conditions': filter, + 'filter_conditions': filter ?? {}, 'type': type, }; From 2263dc097bbcbecc38d980e0710792b90f96a059 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 17 May 2021 11:30:05 +0200 Subject: [PATCH 254/599] fix messageinput --- .../lib/src/message_input.dart | 97 +++++++++---------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index a5ccdb6f..ed098903 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -463,58 +463,57 @@ class MessageInputState extends State { ); } - Widget _buildExpandActionsButton() => Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: AnimatedCrossFade( - crossFadeState: _actionsShrunk - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: IconButton( - onPressed: () => setState(() => _actionsShrunk = false), - icon: Transform.rotate( - angle: (widget.actionsLocation == ActionsLocation.right || - widget.actionsLocation == ActionsLocation.rightInside) - ? pi - : 0, - child: StreamSvgIcon.emptyCircleLeft( - color: StreamChatTheme.of(context) - .messageInputTheme - .expandButtonColor, - ), + Widget _buildExpandActionsButton() { + final channel = StreamChannel.of(context).channel; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: AnimatedCrossFade( + crossFadeState: _actionsShrunk + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: IconButton( + onPressed: () => setState(() => _actionsShrunk = false), + icon: Transform.rotate( + angle: (widget.actionsLocation == ActionsLocation.right || + widget.actionsLocation == ActionsLocation.rightInside) + ? pi + : 0, + child: StreamSvgIcon.emptyCircleLeft( + color: StreamChatTheme.of(context) + .messageInputTheme + .expandButtonColor, ), - padding: const EdgeInsets.all(0), - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), - splashRadius: 24, ), - secondChild: widget.disableAttachments && - !widget.showCommandsButton && - widget.actions?.isNotEmpty != true - ? const Offstage() - : FittedBox( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - if (!widget.disableAttachments) _buildAttachmentButton(), - if (widget.showCommandsButton && - widget.editMessage == null && - StreamChannel.of(context) - .channel - .config - ?.commands - .isNotEmpty == - true) - _buildCommandButton(), - ...widget.actions ?? [], - ].insertBetween(const SizedBox(width: 8)), - ), - ), - duration: const Duration(milliseconds: 300), - alignment: Alignment.center, + padding: const EdgeInsets.all(0), + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, ), - ); + secondChild: widget.disableAttachments && + !widget.showCommandsButton && + widget.actions?.isNotEmpty != true + ? const Offstage() + : FittedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (!widget.disableAttachments) _buildAttachmentButton(), + if (widget.showCommandsButton && + widget.editMessage == null && + channel.state != null && + channel.config?.commands.isNotEmpty == true) + _buildCommandButton(), + ...widget.actions ?? [], + ].insertBetween(const SizedBox(width: 8)), + ), + ), + duration: const Duration(milliseconds: 300), + alignment: Alignment.center, + ), + ); + } Expanded _buildTextInput(BuildContext context) { final theme = StreamChatTheme.of(context); From 6088c0bd9995ede41149a4689cee37065e31eb37 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 17 May 2021 12:27:22 +0200 Subject: [PATCH 255/599] operator can be null in certain cases --- packages/stream_chat/lib/src/models/filter.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat/lib/src/models/filter.dart b/packages/stream_chat/lib/src/models/filter.dart index 2356c57b..82ca1177 100644 --- a/packages/stream_chat/lib/src/models/filter.dart +++ b/packages/stream_chat/lib/src/models/filter.dart @@ -87,8 +87,8 @@ extension FilterOperatorX on FilterOperator { /// See Query Channels Documentation class Filter extends Equatable { const Filter.__({ - required this.operator, required this.value, + this.operator, this.key, }); @@ -159,13 +159,13 @@ class Filter extends Equatable { /// Creates a custom [Filter] if there isn't one already available. const factory Filter.custom({ - required String operator, required Object value, + String? operator, String? key, }) = Filter.__; /// An operator used for the filter. The operator string must start with `$` - final String operator; + final String? operator; /// The "left-hand" side of the filter. /// Specifies the name of the field the filter should match. @@ -188,7 +188,7 @@ class Filter extends Equatable { final groupOperators = _groupOperators.map((it) => it.rawValue); assert( - groupOperators.contains(operator) || key != null, + (operator != null && groupOperators.contains(operator)) || key != null, 'Filter must contain the `key` when the operator is not a ' 'group operator.', ); @@ -196,11 +196,13 @@ class Filter extends Equatable { if (groupOperators.contains(operator)) { // Filters with group operators are encoded in the following form: // { $: [ , ] } - json[operator] = value; - } else { + json[operator!] = value; + } else if (operator != null) { // Normal filters are encoded in the following form: // { key: { $: } } json[key!] = {operator: value}; + } else { + json[key!] = value; } return json; From c7eeb35ef98cb033fa069c1a42239cc08bd10c65 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 17 May 2021 12:35:13 +0200 Subject: [PATCH 256/599] add test --- .../stream_chat/test/src/models/filter_test.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/stream_chat/test/src/models/filter_test.dart b/packages/stream_chat/test/src/models/filter_test.dart index c73c0d0b..e09aff53 100644 --- a/packages/stream_chat/test/src/models/filter_test.dart +++ b/packages/stream_chat/test/src/models/filter_test.dart @@ -180,6 +180,19 @@ void main() { '{"$key":{"${FilterOperator.in_.rawValue}":${json.encode(values)}}}', ); }); + + test('custom with no operator', () { + const key = 'testKey'; + const values = ['testValue']; + final filter = Filter.custom(key: key, value: values); + final encoded = json.encode(filter); + expect( + encoded, + '{"$key":${json.encode(values)}}', + ); + print('asdasda'); + print('{"$key":${json.encode(values)}}'); + }); }); test('groupedFilter', () { From 16953b3a0a096f2a0bc49624cce689a5f9fd6e12 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 17 May 2021 12:35:57 +0200 Subject: [PATCH 257/599] remove print --- packages/stream_chat/test/src/models/filter_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/stream_chat/test/src/models/filter_test.dart b/packages/stream_chat/test/src/models/filter_test.dart index e09aff53..99c20233 100644 --- a/packages/stream_chat/test/src/models/filter_test.dart +++ b/packages/stream_chat/test/src/models/filter_test.dart @@ -190,8 +190,6 @@ void main() { encoded, '{"$key":${json.encode(values)}}', ); - print('asdasda'); - print('{"$key":${json.encode(values)}}'); }); }); From 26e1c21e70d9a7f2ad80db4c4e9efe9e51ea76e0 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 17 May 2021 12:53:37 +0200 Subject: [PATCH 258/599] add filter.raw --- .../stream_chat/lib/src/models/filter.dart | 21 +++++++++++-------- .../test/src/models/filter_test.dart | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/packages/stream_chat/lib/src/models/filter.dart b/packages/stream_chat/lib/src/models/filter.dart index 82ca1177..a2f4ce4b 100644 --- a/packages/stream_chat/lib/src/models/filter.dart +++ b/packages/stream_chat/lib/src/models/filter.dart @@ -1,5 +1,7 @@ // ignore_for_file: non_constant_identifier_names, constant_identifier_names +import 'dart:convert'; + import 'package:equatable/equatable.dart'; const _groupOperators = [ @@ -164,6 +166,11 @@ class Filter extends Equatable { String? key, }) = Filter.__; + /// Creates a custom [Filter] from a raw value + const factory Filter.raw({ + required Map value, + }) = Filter.__; + /// An operator used for the filter. The operator string must start with `$` final String? operator; @@ -183,16 +190,10 @@ class Filter extends Equatable { List get props => [operator, key, value]; /// Serializes to json object - Map toJson() { - final json = {}; + Map toJson() { + final json = {}; final groupOperators = _groupOperators.map((it) => it.rawValue); - assert( - (operator != null && groupOperators.contains(operator)) || key != null, - 'Filter must contain the `key` when the operator is not a ' - 'group operator.', - ); - if (groupOperators.contains(operator)) { // Filters with group operators are encoded in the following form: // { $: [ , ] } @@ -201,8 +202,10 @@ class Filter extends Equatable { // Normal filters are encoded in the following form: // { key: { $: } } json[key!] = {operator: value}; - } else { + } else if (key != null) { json[key!] = value; + } else { + return value as Map; } return json; diff --git a/packages/stream_chat/test/src/models/filter_test.dart b/packages/stream_chat/test/src/models/filter_test.dart index 99c20233..ac026c6c 100644 --- a/packages/stream_chat/test/src/models/filter_test.dart +++ b/packages/stream_chat/test/src/models/filter_test.dart @@ -130,6 +130,14 @@ void main() { expect(filter.operator, operator); }); + test('raw', () { + const value = { + 'test': ['a', 'b'], + }; + const filter = Filter.raw(value: value); + expect(filter.value, value); + }); + group('groupedOperator', () { final filter1 = Filter.equal('testKey', 'testValue'); final filter2 = Filter.in_('testKey', const ['testValue']); @@ -191,6 +199,19 @@ void main() { '{"$key":${json.encode(values)}}', ); }); + + test('raw', () { + const value = { + 'test': ['a', 'b'], + }; + const filter = Filter.raw(value: value); + + final encoded = json.encode(filter); + expect( + encoded, + json.encode(value), + ); + }); }); test('groupedFilter', () { From 5d4b2d33faa3628a2de0fdf91a9423edb450f0af Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 17 May 2021 13:28:40 +0200 Subject: [PATCH 259/599] add doc comment --- packages/stream_chat/lib/src/models/filter.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/models/filter.dart b/packages/stream_chat/lib/src/models/filter.dart index a2f4ce4b..eb3932ff 100644 --- a/packages/stream_chat/lib/src/models/filter.dart +++ b/packages/stream_chat/lib/src/models/filter.dart @@ -1,7 +1,5 @@ // ignore_for_file: non_constant_identifier_names, constant_identifier_names -import 'dart:convert'; - import 'package:equatable/equatable.dart'; const _groupOperators = [ @@ -166,7 +164,15 @@ class Filter extends Equatable { String? key, }) = Filter.__; - /// Creates a custom [Filter] from a raw value + /// Creates a custom [Filter] from a raw map value + /// + /// ```dart + /// final filter = Filter.raw( + /// { + /// 'members': [user1.id, user2.id], + /// } + /// ) + /// ``` const factory Filter.raw({ required Map value, }) = Filter.__; From 7cb3d449c2a89e07794938263e7cebdbf392dd89 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 17 May 2021 14:32:23 +0200 Subject: [PATCH 260/599] fix image group navigation --- packages/stream_chat_flutter/lib/src/image_group.dart | 9 +++++++-- .../stream_chat_flutter/lib/src/message_list_view.dart | 6 +++--- packages/stream_chat_flutter/lib/src/message_widget.dart | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/image_group.dart b/packages/stream_chat_flutter/lib/src/image_group.dart index 51c686c3..4647fe57 100644 --- a/packages/stream_chat_flutter/lib/src/image_group.dart +++ b/packages/stream_chat_flutter/lib/src/image_group.dart @@ -12,12 +12,16 @@ class ImageGroup extends StatelessWidget { required this.message, required this.messageTheme, required this.size, + this.onReturnAction, this.onShowMessage, }) : super(key: key); /// List of attachments to show final List images; + /// Callback when attachment is returned to from other screens + final ValueChanged? onReturnAction; + /// Message which images are attached to final Message message; @@ -111,10 +115,10 @@ class ImageGroup extends StatelessWidget { void _onTap( BuildContext context, int index, - ) { + ) async { final channel = StreamChannel.of(context).channel; - Navigator.push( + final res = await Navigator.push( context, MaterialPageRoute( builder: (context) => StreamChannel( @@ -129,6 +133,7 @@ class ImageGroup extends StatelessWidget { ), ), ); + if (res != null) onReturnAction?.call(res); } Widget _buildImage(BuildContext context, int index) => ImageAttachment( 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 e8f8cdbc..a460eaa5 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -270,7 +270,7 @@ class MessageListView extends StatefulWidget { class _MessageListViewState extends State { ItemScrollController? _scrollController; - Function? _onThreadTap; + void Function(Message)? _onThreadTap; bool _showScrollToBottom = false; late final ItemPositionsListener _itemPositionListener; int? _messageListLength; @@ -814,7 +814,7 @@ class _MessageListViewState extends State { showUsername: !isMyMessage, padding: const EdgeInsets.all(8), showSendingIndicator: false, - onThreadTap: _onThreadTap as void Function(Message)?, + onThreadTap: _onThreadTap, borderRadiusGeometry: const BorderRadius.only( topLeft: Radius.circular(16), bottomLeft: Radius.circular(2), @@ -976,7 +976,7 @@ class _MessageListViewState extends State { showThreadReplyMessage: !isThreadMessage, showFlagButton: !isMyMessage, borderSide: borderSide, - onThreadTap: _onThreadTap as void Function(Message)?, + onThreadTap: _onThreadTap, onReplyTap: widget.onReplyTap, attachmentBorderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(attachmentBorderRadius), diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 85caf4a6..fc25769d 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -126,6 +126,7 @@ class MessageWidget extends StatefulWidget { message: message, messageTheme: messageTheme, onShowMessage: onShowMessage, + onReturnAction: onReturnAction, ), ), border, From fe262ca4220c22a7a30a2334f705207c6290e734 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 17 May 2021 14:37:56 +0200 Subject: [PATCH 261/599] fix stream channel query --- packages/stream_chat_flutter_core/lib/src/stream_channel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2760d9cc..9d533ffb 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -159,7 +159,7 @@ class StreamChannelState extends State { bool preferOffline = false, }) async { if (_topPaginationEnded || - _queryTopMessagesController.value! || + _queryTopMessagesController.value == true || channel.state == null) return; _queryTopMessagesController.add(true); From c00faa123d460f89f3dc96b75aa3119ce0efff89 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 17 May 2021 14:48:17 +0200 Subject: [PATCH 262/599] fix stream channel query --- packages/stream_chat_flutter_core/lib/src/stream_channel.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 9d533ffb..20304cdd 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -163,7 +163,7 @@ class StreamChannelState extends State { channel.state == null) return; _queryTopMessagesController.add(true); - late Message message; + Message? message; if (channel.state!.threads.containsKey(parentId)) { final thread = channel.state!.threads[parentId]!; if (thread.isNotEmpty) { @@ -175,7 +175,7 @@ class StreamChannelState extends State { final response = await channel.getReplies( parentId, PaginationParams( - lessThan: message.id, + lessThan: message?.id, limit: limit, ), preferOffline: preferOffline, From 3dc4e9c0168417103ff88b142acb74bcd59aa7fd Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 17 May 2021 19:10:01 +0530 Subject: [PATCH 263/599] fix: paint bubble --- .../stream_chat_flutter/lib/src/reaction_bubble.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index 1929e5bf..afdf0ec5 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -260,8 +260,8 @@ class ReactionBubblePainter extends CustomPainter { ..style = PaintingStyle.stroke; const dy = -2.2; - const startAngle = 1.1; - const sweepAngle = 1.2; + final startAngle = flipTail ? -0.1 : 1.1; + final sweepAngle = flipTail ? -1.2 : 1.2; final path = Path() ..addArc( Rect.fromCircle( @@ -280,8 +280,8 @@ class ReactionBubblePainter extends CustomPainter { ..strokeWidth = 1; const dy = -2.2; - const startAngle = 1; - const sweepAngle = 1.3; + final startAngle = flipTail ? -0.0 : 1.0; + final sweepAngle = flipTail ? -1.3 : 1.3; final path = Path() ..addArc( Rect.fromCircle( @@ -301,8 +301,8 @@ class ReactionBubblePainter extends CustomPainter { ..style = PaintingStyle.fill; const dy = -2.2; - const startAngle = 1.1; - const sweepAngle = 1.2; + final startAngle = flipTail ? -0.1 : 1.1; + final sweepAngle = flipTail ? -1.2 : 1.2; final path = Path() ..addArc( Rect.fromCircle( From 80bc94ba386c801762e629f19b54d1a20dc89aa4 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 17 May 2021 20:17:33 +0530 Subject: [PATCH 264/599] fix: paint bubble --- packages/stream_chat_flutter/lib/src/reaction_bubble.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index afdf0ec5..ce75445b 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -156,6 +156,7 @@ class ReactionBubble extends StatelessWidget { maskColor, tailCirclesSpace: tailCirclesSpacing, flipTail: !flipTail, + numberOfReactions: reactions.length, ), ); return tail; @@ -171,6 +172,7 @@ class ReactionBubblePainter extends CustomPainter { this.maskColor, { this.tailCirclesSpace = 0, this.flipTail = false, + this.numberOfReactions = 0, }); /// Color of bubble @@ -188,6 +190,9 @@ class ReactionBubblePainter extends CustomPainter { /// Flip tail final bool flipTail; + /// Number of reactions on the page + final int numberOfReactions; + @override void paint(Canvas canvas, Size size) { _drawOvalMask(size, canvas); @@ -261,7 +266,7 @@ class ReactionBubblePainter extends CustomPainter { const dy = -2.2; final startAngle = flipTail ? -0.1 : 1.1; - final sweepAngle = flipTail ? -1.2 : 1.2; + final sweepAngle = flipTail ? -1.2 : (numberOfReactions > 1 ? 1.2 : 0.9); final path = Path() ..addArc( Rect.fromCircle( From 292c21004c6652a5f4d5911dee88407b3d316348 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 17 May 2021 17:03:57 +0200 Subject: [PATCH 265/599] fix action modal --- .../lib/src/message_actions_modal.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 82ce94e2..f0ce9539 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -173,12 +173,12 @@ class _MessageActionsModalState extends State { Align( alignment: Alignment( user?.id == widget.message.user?.id - ? (divFactor > 1.0 - ? 0.0 - : (1.0 - divFactor)) - : (divFactor > 1.0 - ? 0.0 - : -(1.0 - divFactor)), + ? (divFactor >= 1.0 + ? -0.2 + : (1.2 - divFactor)) + : (divFactor >= 1.0 + ? 0.2 + : -(1.2 - divFactor)), 0), child: ReactionPicker( message: widget.message, From 15cfcc5143f28950e928862ddca5f068381720da Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 18 May 2021 10:36:03 +0200 Subject: [PATCH 266/599] fix: .copy and export reactionIcons --- .../lib/src/stream_chat_theme.dart | 19 ++++++++++--------- .../lib/stream_chat_flutter.dart | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index c5646753..97363df7 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -175,17 +175,18 @@ class StreamChatThemeData { }) => StreamChatThemeData.raw( channelListHeaderTheme: - channelListHeaderTheme ?? this.channelListHeaderTheme, - textTheme: textTheme ?? this.textTheme, - colorTheme: colorTheme ?? this.colorTheme, - primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme, + this.channelListHeaderTheme.merge(channelListHeaderTheme), + textTheme: this.textTheme.merge(textTheme), + colorTheme: this.colorTheme.merge(colorTheme), + primaryIconTheme: this.primaryIconTheme.merge(primaryIconTheme), defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage, defaultUserImage: defaultUserImage ?? this.defaultUserImage, - channelPreviewTheme: channelPreviewTheme ?? this.channelPreviewTheme, - channelTheme: channelTheme ?? this.channelTheme, - ownMessageTheme: ownMessageTheme ?? this.ownMessageTheme, - otherMessageTheme: otherMessageTheme ?? this.otherMessageTheme, - messageInputTheme: messageInputTheme ?? this.messageInputTheme, + channelPreviewTheme: + this.channelPreviewTheme.merge(channelPreviewTheme), + channelTheme: this.channelTheme.merge(channelTheme), + ownMessageTheme: this.ownMessageTheme.merge(ownMessageTheme), + otherMessageTheme: this.otherMessageTheme.merge(otherMessageTheme), + messageInputTheme: this.messageInputTheme.merge(messageInputTheme), reactionIcons: reactionIcons ?? this.reactionIcons, ); diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index eb35c69b..62bfd299 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -24,6 +24,7 @@ export 'src/message_search_list_view.dart'; export 'src/message_text.dart'; export 'src/message_widget.dart'; export 'src/option_list_tile.dart'; +export 'src/reaction_icon.dart'; export 'src/reaction_picker.dart'; export 'src/sending_indicator.dart'; export 'src/stream_chat.dart'; From 5155940ddd24ae9d5f791a06fa58239f1afda833 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 18 May 2021 14:39:01 +0530 Subject: [PATCH 267/599] fix: attachment borders --- packages/stream_chat_flutter/lib/src/extension.dart | 12 ++++++++++++ .../lib/src/message_actions_modal.dart | 6 ++++-- .../lib/src/message_reactions_modal.dart | 4 +++- .../stream_chat_flutter/lib/src/message_widget.dart | 1 + packages/stream_chat_flutter/lib/src/utils.dart | 3 ++- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index a65272c3..3b6ba3e2 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -104,3 +104,15 @@ extension BuildContextX on BuildContext { double get textScaleFactor => MediaQuery.maybeOf(this)?.textScaleFactor ?? 1.0; } + +/// Extension on [BorderRadius] +extension FlipBorder on BorderRadius { + /// Flips borders (Y) + BorderRadius mirrorBorderIfReversed({bool reverse = true}) => reverse + ? BorderRadius.only( + topLeft: topRight, + topRight: topLeft, + bottomLeft: bottomRight, + bottomRight: bottomLeft) + : this; +} 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 f0ce9539..c0a4acaf 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -189,8 +189,10 @@ class _MessageActionsModalState extends State { child: MessageWidget( key: const Key('MessageWidget'), reverse: widget.reverse, - attachmentBorderRadiusGeometry: - widget.attachmentBorderRadiusGeometry, + attachmentBorderRadiusGeometry: widget + .attachmentBorderRadiusGeometry + ?.mirrorBorderIfReversed( + reverse: widget.reverse), message: widget.message.copyWith( text: widget.message.text!.length > 200 // ignore: lines_longer_than_80_chars 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 8d5b408e..35cd45e3 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -144,7 +144,9 @@ class MessageReactionsModal extends StatelessWidget { attachmentShape: attachmentShape, padding: const EdgeInsets.all(0), attachmentBorderRadiusGeometry: - attachmentBorderRadiusGeometry, + attachmentBorderRadiusGeometry + ?.mirrorBorderIfReversed( + reverse: reverse), attachmentPadding: EdgeInsets.all( hasFileAttachment ? 4 : 2, ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 42ce4947..dfacd111 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -16,6 +16,7 @@ import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'extension.dart'; /// Widget builder for building attachments typedef AttachmentBuilder = Widget Function( diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index d24ccc7e..d8bbcde6 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; /// Launch URL Future launchURL(BuildContext context, String? url) async { @@ -333,7 +334,7 @@ Widget wrapAttachmentWidget( BorderRadius borderRadius, ) => ClipRRect( - borderRadius: borderRadius, + borderRadius: borderRadius.mirrorBorderIfReversed(reverse: reverse), child: Material( clipBehavior: Clip.antiAlias, shape: attachmentShape, From 9e9cd2b66f8a0a7369281ecb0d77e4390cee2782 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 18 May 2021 15:22:41 +0530 Subject: [PATCH 268/599] fix: attachment borders --- .../lib/src/message_actions_modal.dart | 2 +- .../lib/src/message_list_view.dart | 23 +++++++++++++------ .../lib/src/message_reactions_modal.dart | 2 +- .../lib/src/message_widget.dart | 10 -------- .../stream_chat_flutter/lib/src/utils.dart | 14 ++++------- 5 files changed, 23 insertions(+), 28 deletions(-) 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 c0a4acaf..5336ed48 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -192,7 +192,7 @@ class _MessageActionsModalState extends State { attachmentBorderRadiusGeometry: widget .attachmentBorderRadiusGeometry ?.mirrorBorderIfReversed( - reverse: widget.reverse), + reverse: !widget.reverse), message: widget.message.copyWith( text: widget.message.text!.length > 200 // ignore: lines_longer_than_80_chars 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 c0b8ac0d..c24ceb00 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -980,14 +980,23 @@ class _MessageListViewState extends State { onReplyTap: widget.onReplyTap, attachmentBorderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(attachmentBorderRadius), - bottomLeft: Radius.circular( - (timeDiff >= 1 || !isNextUserSame) && - !(hasReplies || isThreadMessage || hasFileAttachment) - ? 0 - : attachmentBorderRadius, - ), + bottomLeft: isMyMessage + ? Radius.circular(attachmentBorderRadius) + : Radius.circular( + (timeDiff >= 1 || !isNextUserSame) && + !(hasReplies || isThreadMessage || hasFileAttachment) + ? 0 + : attachmentBorderRadius, + ), topRight: Radius.circular(attachmentBorderRadius), - bottomRight: Radius.circular(attachmentBorderRadius), + bottomRight: isMyMessage + ? Radius.circular( + (timeDiff >= 1 || !isNextUserSame) && + !(hasReplies || isThreadMessage || hasFileAttachment) + ? 0 + : attachmentBorderRadius, + ) + : Radius.circular(attachmentBorderRadius), ), attachmentPadding: EdgeInsets.all(hasFileAttachment ? 4 : 2), borderRadiusGeometry: BorderRadius.only( 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 35cd45e3..5f3bc5a4 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -146,7 +146,7 @@ class MessageReactionsModal extends StatelessWidget { attachmentBorderRadiusGeometry: attachmentBorderRadiusGeometry ?.mirrorBorderIfReversed( - reverse: reverse), + reverse: !reverse), attachmentPadding: EdgeInsets.all( hasFileAttachment ? 4 : 2, ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index dfacd111..a2d19c7e 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -131,8 +131,6 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry as BorderRadius? ?? - BorderRadius.zero, ), ); } @@ -157,8 +155,6 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry as BorderRadius? ?? - BorderRadius.zero, ); }, 'video': (context, message, attachments) { @@ -191,8 +187,6 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry as BorderRadius? ?? - BorderRadius.zero, ); }, 'giphy': (context, message, attachments) { @@ -219,8 +213,6 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry as BorderRadius? ?? - BorderRadius.zero, ); }, 'file': (context, message, attachments) { @@ -248,8 +240,6 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry as BorderRadius? ?? - BorderRadius.zero, ); }) .insertBetween(SizedBox( diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index d8bbcde6..159c02e0 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -331,14 +331,10 @@ Widget wrapAttachmentWidget( ShapeBorder attachmentShape, // ignore: avoid_positional_boolean_parameters bool reverse, - BorderRadius borderRadius, ) => - ClipRRect( - borderRadius: borderRadius.mirrorBorderIfReversed(reverse: reverse), - child: Material( - clipBehavior: Clip.antiAlias, - shape: attachmentShape, - type: MaterialType.transparency, - child: attachmentWidget, - ), + Material( + clipBehavior: Clip.antiAlias, + shape: attachmentShape, + type: MaterialType.transparency, + child: attachmentWidget, ); From 54c5b288d0de69dc861e2f3fef39f8e2b6b62cba Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 18 May 2021 15:34:37 +0530 Subject: [PATCH 269/599] fix: analysis --- packages/stream_chat_flutter/lib/src/message_widget.dart | 1 - packages/stream_chat_flutter/lib/src/utils.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index a2d19c7e..97e061ae 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -16,7 +16,6 @@ import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'extension.dart'; /// Widget builder for building attachments typedef AttachmentBuilder = Widget Function( diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 159c02e0..5a86d4ef 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Launch URL Future launchURL(BuildContext context, String? url) async { From 13cc5f1c4b0a6fad8aea58c3f7a3defeaa6d90fb Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 18 May 2021 15:47:38 +0530 Subject: [PATCH 270/599] fix: update goldens --- .../src/goldens/deleted_message_custom.png | Bin 2936 -> 2334 bytes .../test/src/goldens/deleted_message_dark.png | Bin 2785 -> 2262 bytes .../src/goldens/deleted_message_light.png | Bin 2750 -> 2161 bytes .../test/src/goldens/reaction_bubble_2.png | Bin 2955 -> 2937 bytes .../test/src/goldens/system_message_dark.png | Bin 1812 -> 1679 bytes .../test/src/goldens/system_message_light.png | Bin 1866 -> 1746 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png index c464bc01de48fcef06c538bd284ed284370f55aa..693db3c78ee724f41d6376bd5c7ba1eb1385750d 100644 GIT binary patch delta 1816 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 h$U5GC0pAKv z4-*p;<;L15EYl^oH9Thj_h-*O6$Oz!wmIeJ=S?D&!PzAxK481cqK19J`T+_?jn;C# z93aJH%bvSZsq__yPgd4f#JISyxHvR8jjS_EO6SJb3wkQo=0-_*=8X1M@zNjv`fMya z_baXO-Y$tuhH`iR?b(KGe$O0Kt(a!2-)mG#Sov@y@bi{9mjp*{rv!c=M$UXyS}Fw+ zw~16y^x}m}!;juH8V%ZpCKk(pTeA-8=;#2#1Ox^3EiS%{m6Y=N?-EBqfB`-tl9F&> z^KYqU%k&iCmHbelR6bm_&BpidADXjH%J28B>Lg)I1W`ZykX9YUOW9O}_V)Dr#o@H$ z>UzrC=n;Aq!vh01e(ZTjm;3knE&8fxlAsX8|Mr4_V(WT=sTgb1zI$R z2bGre6_XanE6IHo39#v{l2gnMskE0Potg0t4Q1uzWa2~5=4|; zfu{IF&0O+OYW;R8V@uXJ*TC?P(K%6l{cT@%Hh|jlSi`OwmAtCc?lVJX1j}zBVQ58o ztia9!fzVOqFbz8dSS=ZPw^d6=a+BcP&}Qw4Y)#8G7*?!LBod<>?AvY+s_t`A!RxsL z!qBmN&@CE;qH#i^FCeri%>_VMbLC~qRu1hYp?q_=x_i@d2GFV2M1%TiQ5w=T5d)5n zO;y^~lW{278;_q=R!=?3GD!%aJ Goqqs9Ee-zw diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_dark.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_dark.png index 7db0a6ef17a3f5586f9a0f7d67b1f49dd6fd5b60..d9bb3138bf3a71dbde33bda6e4605afbe76b5146 100644 GIT binary patch delta 1719 zcmV;o21xng71j}uKyCE<|^YbkM_@IGa4*)pNlTiU$lVAZ$lVAZ$lVAZ2F(iAn;loy|SDQv38b~C1 zvfZ8q(rUGI*lH=!qa8h$9}k1j+Y!SJvo$#EPx$@hPICYR01aUAW`YG*yPGjn_lYH!}0YTeTwi(EFV zzkUDxnT+c^x)^_f1VQ`_e|Opt3=eC3d|dTV1UXH&em(6NClac3n+NssFR4S?2Y)X>S zsgw${v+_JoVHj$qT-MgsR@Y^&>#8t2t1r@NNz$wBZIypYC52HGe{C+C)!^Wu_V@R- zy}hj<2=vcifBoR{rBdmmSKfEW?oF zKJ0qzx{a&fTuP_YfA7VHTkXFG`4qp+`)#qPN~NMJSFR`u!}z3{$>%k2>5|^mY8o9G z(UWpnl2m`3o0I2x`t8vpRVo#Yjg86oeO0T~IDq>PAL{YSiY|ZlnX=QV;9HTCFC}^Wv|QB+qpvNoyM$y8qxo*JYA) z|G|HQ_=Ilm?{|!|FgvTKm5SCkHWWsYwyRZz?U-HHjqh;Bac@_v@t96>T~||6Q}MdT z$iD48eXrH|Eb8~vyzH(g+0RjVnV%PE)5DvTnnZ*0hQU3s1tU&XElfgY7g@pD7RYYl%7 z%k?~!+VgYY_jP-5@qf=4A6eFDG<17$QBNxsmCI#?QKZ7mjQqhtNs8ZDJH8ShZ|gYh zAPDq)b2ENzi|4u>uk-FPu=elW=MQV$U!Z6f+v)Of-<2dm)GMS9V z$H(K7YxAAJUKC!hQVsu?`Op6-ANqxt#v}`|o*_|KpnNW;q~qxM^XJ;#+k1a+pBs&af*{cB*I#RFY)oMocH9-L{hRpmONCK%s$(%Q zFraiQrKzbYy?FIX2L}h{`rXF$XY}$RJuyE&-;@7d!K>sH6~X}(z0(@KRn`aoFUKcx4AwT%tU z7mM;dPxX3TOQlj@|M2XugeexzHKtg=IYuCWfj|HQfdB>q0Sp8J7<&41kQSd2f-YJ; z{)mj81(HZ4xP76kUVlVJ&jRV?hoS(d#YG&GApwe$U;#^$U;!x>_%Fneu2z=3CMo~` N002ovPDHLkV1k_0l*0f3 delta 2230 zcmV;n2ub(W5#be(Kx_(&Nkl?)dLhd1;NlrLHLns#kaa*+u)$L5{ zT6a79U+sU@+8L+Sb~qt{14Qpb`j}kRVA&_6I@+Z{OI<7-HYApLCwbljp#B zpGyLIPM$p35P+BRc6tH8K}M0$+LK@bB`$-nQ!l(zuWP5?=+w)ji}ZLs>hX5wT;8!j zB+2Xb$m8*JatS!#8ja4LM7hNRlRyC$e}=avkZ?GxP$=}u@nA5ha5(%{e=vFa)G1{$ znYQzKqfvb@JFDK_-Z!wS*I(B`^i~8C3I=uI{CWN5lTYM*$&Vz&Vln;o!UbJ8fBvQ8 zZ-YoUtRsViul32Z>$*xgj-Eb!+IDVlG^)(eqrdpX@^jX85WN*gOEQ^k{W%(qe?Bwj z@p$yd)2Ag##bQxAJ3DW5ZEs4_aU@BrtE+A2I?1F;rIKp3THCqfN>t8h51)wMNc1_qQ)r}g>8i!bbNI2_jbbLZso zc$8aR)!68$d_JF+mX_M?UteEe>)%;(?<_1R6b{Sh^J#s3UDax}?Y7&ks~dHFAfaGT z!9YN{wKc^P3B_Wu*4X=}PpecasZb~=?Ydga=jHeN_2JpGdidy(f397>E=f8wJF9p+ zE=ii4m{2Gf)Mpnjs#dG%$l#zRjvbTN>(#OG@mG%ZMx*k0Jo@(PRps+}`FuY8?bA;c z3r8&@_H9+GRSjh_dhgwL^@kHD z^ws6dDwWIT*xng`C7092#)iVpx13kk)|5yj(vLdvwHgUss4NGmJ-d&|K9UQAP`VKpV!szzH4p6V%i(@(qWI(xQtrjAA;l6HTK za?e02f2B&LqOGkhg+d`U8jY8(>3NYzM2$wHHRkbnluRbI^7wJv{d@lT!=&CH*vH1?p0qbrYqlk(>mxSwIV8)%ZkV28X6kXlZ_1x z4-f0mp+mZS<%$MfS3^TXs#Gc}mCG8+WK^%$e|OLFV`Hxz+q+ssA`vB%NxhTJDjtvP zLG$5st9b^Unw-?_J9m_JUAeBS<%bU?Nu^RrDaX;&jnj%YBQ zRyLcJB>nT+HC3xs6^ca-rqdc48q#n!t6Ht5>vMA|7K`TCcBLXGnbcr9t<2G*+ANjS z+uN&^Tu!U2tJ>Pyl9Nm-nMi0TlWEVO_d(N%eiVQ9j@91^smns9-RtN~QA35Adx0d)>KSuUCGbPutI3 z#jI_&Ti4IkXf))JexLtoLnl4|P2X?tU6|iuED#9j&*#o*C709Vl@+-I14=oLzP)<& znK-1^3uDjTJ58?~H}>y!=ho}>X_t;!+ith6Zu3_BsL4TAsZ{jC+?>LpkftUl<@fvb zkMF;4JqO@m``una-lV1F<<{pV=q_AA00V&l1_A*L1OgZc1TYW?VCd-Ey!CoRQg{BY zGTxjXZ|DA_jgAG<lY3<4pEcg2j7wZA{WE|0l%51RqPmgaR2}S07*qoM6N<$ Ef*kF!`v3p{ diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_light.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_light.png index d5a453ffc0cf157efe7d6a8e84648ab9a4a90530..1f6330eb9bd92be6130bae15872bfe6d51878d88 100644 GIT binary patch delta 1615 zcmV-V2C(_Q74Z;|Kz|4uNklLx2g!BEI-42W6ODdH5(ESyy6gwnH+%rXA?#h&NE7<+wo zGSkdGleuQ@iLnpjIQ}I7SNhMm2LRk<1OgZc1TYW?U?32{K$E@+>61_a2Rx?6wc(SK z6ZLw%acv$GB$Y~Oetv!|+a6mWQ55O;_*l-laSZ_zAdX}G`t|G23q+EfbDEu<9p?}L z6VH=j0UCez$j6T#)oeCLt{2C#Ha0dChT$!Y>gMCRi|$Pz8yg$i+}yl!SscgO+}yl& z`F(MCc&L0nKXSbw2;`hoCX=~^QQdr8chS8FBnSdYXCcIKd}+Dod5Ys$3kwUkJ+xah zI0m^~?ndk0^jLVFrw<=K+{(D_qI(m_;0?bxZ3usYKzn<8%4V|@`zGDQ@0|2O5Zw4! z{LZ@n>$vWsv3^UO&$k3|@Us@Ln1&ZbDc zUQa74DDv+>J&VUftI4|3 zKLM<)tZ03GU4=qH+uPeCm-Tu*)oL~6ayfskudi!uZB6_8`^g>6_kGptbuBI~s#>k8 zTrO*8XD11u)9J|bJeA93t*@^upUjsmS;JdWXk9BilXS!{`Pu3?dkeK+I-BQ@+Q0Kto|~+j&1RE3ptb&}aZRd;pJJVV z6Ph=222L-uYPGJ}-ilSr;(phYA949BWB-LuQp)p-GclUi?*=%;`6R7hi{iyv5f?#O6 zIBMPVC%3i!sBulIdlAU_D^;u2lIMAH≪w6h+B8hlht+US3X4l8dj^>-B$<`FR+I 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 literal 1812 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 GGywo&aKg+0 literal 1866 zcmeHI`%}_c6vr~hn9R~@OEhTKO4-aElX6{=LPOD1M8#G`+)9{)wuea;XR|jx#XuiI^SobqAZlN2l4Q$g7syqt+CTM%jqkq8%|yZ5$fSJ z)U$HwMKfs`hcmm+TicNWHJM))X^;#_33RC*V^JgySgAU z^j=*RBCAV~RqJ%W5XnrY7%#jtJO2dD%K~;)DOhjsAplcrt{s{hR?vhbe{Dhp()%*$ z0XcNfmN2*cSME$YouG`XH7=d;c!(DY{2j~2$LVWUrOWH|LncOD9-l9(ttAZ_6f@gy zK7A~e3iI-|Jd#MNNlq;Jmfma?-*tr}=J&6s)9Ihy_k2G!*bw4MYt<|)$b*YKggrf> zQ~gq(2V|nHt?h@&$)3^Cie0;RZ|3@b`l#F2l5D_peer6wI*LY%1n9TpQXjJRivfti zhSOrPI55Y*V*kv-XFZy)f}5|Bb4-%Fyu3|d_h_dr27__7yY;eoX}k*&@3w8H&DtS( zQXrK|y|l8tWX;g)_42bL8J?b=_c~96qj?FOv2#rWv48{V(Ib&Hu@T_D%Bm{xI)Tw> zbXh#bLM|45*_GS*UIT?CFqvWQcG~{_`&m!RfbETGL=gf1XkNhGiu81|BsM$YMG5wj zX}UPu;pa-Dkxgx82Vt>Tutt7<^6ooBBYXN4H1VQT|4~<0NH6hDi83U|s8lM?- z=H|_}2t3u;rgawj?&Zb#x`KiNO>b}6+qb`B+}$1l(IePr2Xrh7$22{3zP`S`@`M`%LC!%z zLC(B@lgE!AS7VfMq_=01)jAUspmTJ#;X37ZE}mz5*i(Mox>!m#eg1s1PePR^PGoQQ zN)}{fWXKr^1i}u1P=jE|8XHB&q@r_8dx!}v7LZMR{9zzTfSbIRABN^tQ@=KkY#S|n z{W^AR?&8ytvk~ac#FdM;ym7c1p>W6H@=!3m(}t^P=`-Qlnwox+$z%}8Nz~9_;v466 zAGzGMNf$1N6$+YZW;jwv`m5tM@`^_^l^TKO^}cx{0FmN#(U}|<7Z>OJBOie5;F;|K zyKZ7mc(A>_-Oj-wA~CV9HFc?4C=}8LCKz?OofQNELDSPC1X5gHP6Y^%-N&T& z|LEx81y?ao53Ej4>n_FB;fd3FiJRIyR!N* Date: Tue, 18 May 2021 12:34:35 +0200 Subject: [PATCH 271/599] regenerate golden --- .../src/goldens/deleted_message_custom.png | Bin 2334 -> 2936 bytes .../test/src/goldens/deleted_message_dark.png | Bin 2262 -> 2785 bytes .../src/goldens/deleted_message_light.png | Bin 2161 -> 2750 bytes .../test/src/goldens/reaction_bubble_2.png | Bin 2937 -> 2920 bytes .../test/src/goldens/system_message_dark.png | Bin 1679 -> 1812 bytes .../test/src/goldens/system_message_light.png | Bin 1746 -> 1866 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png index 693db3c78ee724f41d6376bd5c7ba1eb1385750d..c464bc01de48fcef06c538bd284ed284370f55aa 100644 GIT binary patch delta 2416 zcmXArc~n#97R9U9ieQCW;#h`i3J8i+$YTbzfCOYrVi*#rr3#1;5JJEJSEQ)43sVw8 ztP!xHrXhqpjA0(7JSHK6Kx9UykVL`^CSmAD-(B~wyY4!7pMCcEeK+sGr-Q$rgEWpK zoz9~Z9?uRBqK*;unin+p{-V!{HamRo)L6pP0QrsPtS+0ps8_jN$7K29^hRg0_}j*? z<^`v~XH#ZpZ%;kE@Z`hqv0v*8chg{QiWgE8V@!p3cVu7~nKCFR^XrD;+?J`EyLTkO zDTBK{^E|tA$G*>Z9#;F@OiS%YeXV+$nc91r*^VQS@vv_F`3$EE6}V1C`qut@p?JNJ z$0L!g9{h?k#YU60Y;A1=f`bj!U8(CSbpZhZXn)D(!r;oem;FV3B9Wl1OgwEC{W`%; z*M4L9-J)tk{-CkLaCC4Gb?V@W{olm+u6~K#mC^LmK!1NsteRCre0=5@p$guVSMbUYs$q-f%ZG-rIE}w&TMxC}W2)3i-8&lD zd*O7qlXN$oDmz-M5#5dL9lcRI^L6ZolGyR)O*W1|CI^0q_6rOQ+r=_J)lB`Dt2D%x+DeQ)Z^Rse!C1m#6@XQ~Vu17_A!eFrPM0gwy4L{%$85!A1 zEGsKR*xIVfG*3ZS|1I?M)vHYkYh8+7z-1%t;BSGUtiA7)Z0s{j0|XRB!S>gwXv~z z?CAB}S2;m()XsE)seRrvIy(2!tODoh?(QA{MCoh2T@pIZRi}0a7$7w?G{7XfJ3AlT zB9nXj`z0T7;M0#DKSo;|#Y9GOD=RB4936QYhfG^07L4+R5um;H_V&W!;ywJ%; z6Trdlbd5vH-R@`D-bOP6f(Y zm><|^X$$U3LV{mPT`B>WAg3w-GlpF>FVP^J3 zsZ>%ak(nGEJz2j=anbBBIwYiEX(X4;KH2os_jsr_$=`8(KFxgVFc|d6NS>W8C1qm; zi&U|$A`l4bGpY1CQnfXIrh5KP=VR-Fyu3VWI{-mvz9WxLhk1B-oTRn7QXesyf~qPG z(~?lgVqNU$=%8*^D|)-S{_O3=L`Fpw3IrHS!bmn8xbI3eFMjHG>P1tN^OyVfK~@)6 z#(S$)SK~cAJs%B{q5)e7b#&a?nAp6i*%VQH6SJD?bo{HtnxNe~cYvGA<(>rl3|tU* z=Ax35Ykn$u2zrDg`F#GOBB|{fzq89;OU+1I9Z6NHRA8Z<>!u`?yes*6UbU9S4mVO}skH>(4nDDA^h7EOZcADGR7>7I7k2~LOYjY!#NbM8W zNjg`rU31Z;H1Fkq@@raJ_uUrjbA1@NAQt<|ws*n?mikH{Kq{BQboSrFpil*yn<>r( zK1j&(Ex*6$PQ#NC@|?Z+d-e1?V4buxoU$(&K)oUntG3qR^y$;v`R8yrAm`1d?Nc`^ zVD;}4NvB}M8o=BX0sGD=i0ORBH;gpHCdL?Nt zFE4qupE|fUzup*N2_Tv4saN^mD_lnli=Q8W7!bE(u%K6xuyQ>%76s`gFK2<7$38Q4 zL1VEjfdJv*<;8ja+!=*J0q%>QyEC>?B9VkEHc}847Gc1IARhKlZ h$U5GC0pAKv z4-*p;<;L15EYl^oH9Thj_h-*O6$Oz!wmIeJ=S?D&!PzAxK481cqK19J`T+_?jn;C# z93aJH%bvSZsq__yPgd4f#JISyxHvR8jjS_EO6SJb3wkQo=0-_*=8X1M@zNjv`fMya z_baXO-Y$tuhH`iR?b(KGe$O0Kt(a!2-)mG#Sov@y@bi{9mjp*{rv!c=M$UXyS}Fw+ zw~16y^x}m}!;juH8V%ZpCKk(pTeA-8=;#2#1Ox^3EiS%{m6Y=N?-EBqfB`-tl9F&> z^KYqU%k&iCmHbelR6bm_&BpidADXjH%J28B>Lg)I1W`ZykX9YUOW9O}_V)Dr#o@H$ z>UzrC=n;Aq!vh01e(ZTjm;3knE&8fxlAsX8|Mr4_V(WT=sTgb1zI$R z2bGre6_XanE6IHo39#v{l2gnMskE0Potg0t4Q1uzWa2~5=4|; zfu{IF&0O+OYW;R8V@uXJ*TC?P(K%6l{cT@%Hh|jlSi`OwmAtCc?lVJX1j}zBVQ58o ztia9!fzVOqFbz8dSS=ZPw^d6=a+BcP&}Qw4Y)#8G7*?!LBod<>?AvY+s_t`A!RxsL z!qBmN&@CE;qH#i^FCeri%>_VMbLC~qRu1hYp?q_=x_i@d2GFV2M1%TiQ5w=T5d)5n zO;y^~lW{278;_q=R!=?3GD!%aJ Goqqs9Ee-zw delta 1816 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 Ef*kF!`v3p{ delta 1719 zcmV;o21xng71j}uKyCE<|^YbkM_@IGa4*)pNlTiU$lVAZ$lVAZ$lVAZ2F(iAn;loy|SDQv38b~C1 zvfZ8q(rUGI*lH=!qa8h$9}k1j+Y!SJvo$#EPx$@hPICYR01aUAW`YG*yPGjn_lYH!}0YTeTwi(EFV zzkUDxnT+c^x)^_f1VQ`_e|Opt3=eC3d|dTV1UXH&em(6NClac3n+NssFR4S?2Y)X>S zsgw${v+_JoVHj$qT-MgsR@Y^&>#8t2t1r@NNz$wBZIypYC52HGe{C+C)!^Wu_V@R- zy}hj<2=vcifBoR{rBdmmSKfEW?oF zKJ0qzx{a&fTuP_YfA7VHTkXFG`4qp+`)#qPN~NMJSFR`u!}z3{$>%k2>5|^mY8o9G z(UWpnl2m`3o0I2x`t8vpRVo#Yjg86oeO0T~IDq>PAL{YSiY|ZlnX=QV;9HTCFC}^Wv|QB+qpvNoyM$y8qxo*JYA) z|G|HQ_=Ilm?{|!|FgvTKm5SCkHWWsYwyRZz?U-HHjqh;Bac@_v@t96>T~||6Q}MdT z$iD48eXrH|Eb8~vyzH(g+0RjVnV%PE)5DvTnnZ*0hQU3s1tU&XElfgY7g@pD7RYYl%7 z%k?~!+VgYY_jP-5@qf=4A6eFDG<17$QBNxsmCI#?QKZ7mjQqhtNs8ZDJH8ShZ|gYh zAPDq)b2ENzi|4u>uk-FPu=elW=MQV$U!Z6f+v)Of-<2dm)GMS9V z$H(K7YxAAJUKC!hQVsu?`Op6-ANqxt#v}`|o*_|KpnNW;q~qxM^XJ;#+k1a+pBs&af*{cB*I#RFY)oMocH9-L{hRpmONCK%s$(%Q zFraiQrKzbYy?FIX2L}h{`rXF$XY}$RJuyE&-;@7d!K>sH6~X}(z0(@KRn`aoFUKcx4AwT%tU z7mM;dPxX3TOQlj@|M2XugeexzHKtg=IYuCWfj|HQfdB>q0Sp8J7<&41kQSd2f-YJ; z{)mj81(HZ4xP76kUVlVJ&jRV?hoS(d#YG&GApwe$U;#^$U;!x>_%Fneu2z=3CMo~` N002ovPDHLkV1k_0l*0f3 diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_light.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_light.png index 1f6330eb9bd92be6130bae15872bfe6d51878d88..d5a453ffc0cf157efe7d6a8e84648ab9a4a90530 100644 GIT binary patch delta 2211 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≪w6h+B8hlht+US3X4l8dj^>-B$<`FR+I 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 WMtx2FJ+qtu0000#)RxWP1EP?e> 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 diff --git a/packages/stream_chat_flutter/test/src/goldens/system_message_light.png b/packages/stream_chat_flutter/test/src/goldens/system_message_light.png index 32c2196cf3616acd94887e02b232c13f333e2d99..d82d8d609c2e3f352ef20dc48029787c3ac8cad5 100644 GIT binary patch literal 1866 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 GGywo&aKg+0 From 3ab1e1956ecea5c30bb68347bf872f9db09fb6f8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 12:43:52 +0200 Subject: [PATCH 272/599] bump changelogs and pubspecs --- packages/stream_chat/CHANGELOG.md | 6 ++++++ packages/stream_chat/lib/version.dart | 2 +- packages/stream_chat/pubspec.yaml | 4 ++-- packages/stream_chat_flutter/CHANGELOG.md | 6 ++++++ packages/stream_chat_flutter/pubspec.yaml | 8 ++++---- packages/stream_chat_flutter_core/CHANGELOG.md | 5 +++++ packages/stream_chat_flutter_core/pubspec.yaml | 8 ++++---- packages/stream_chat_persistence/CHANGELOG.md | 6 ++++++ packages/stream_chat_persistence/pubspec.yaml | 6 +++--- 9 files changed, 37 insertions(+), 14 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 7fc05197..75fecc34 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.0-nullsafety.2 + +- Added new `Filter.raw` constructor +- Changed extraData +- Minor fixes + ## 2.0.0-nullsafety.1 - Migrate this package to null safety diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index ef903a2a..6c68818a 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -3,4 +3,4 @@ import 'package:stream_chat/src/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header // ignore: constant_identifier_names -const PACKAGE_VERSION = '2.0.0-nullsafety.1'; +const PACKAGE_VERSION = '2.0.0-nullsafety.2'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 6b8ee0aa..d473beea 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat homepage: https://getstream.io/ description: The official Dart client for Stream Chat, a service for building chat applications. -version: 2.0.0-nullsafety.1 +version: 2.0.0-nullsafety.2 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -19,7 +19,7 @@ dependencies: logging: ^1.0.1 meta: ^1.3.0 mime: ^1.0.0 - rxdart: ^0.26.0 + rxdart: ^0.27.0 uuid: ^3.0.4 web_socket_channel: ^2.0.0 diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 9d3b372e..e0b60fad 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.0-nullsafety.4 + +- Minor fixes and improvements +- Updated `stream_chat_core` dependency +- Improved performance of `MessageWidget` component + ## 2.0.0-nullsafety.3 - Fix MessageInput overflow when there are no actions diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 1b2dbe2f..2aa5ab4a 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.3 +version: 2.0.0-nullsafety.4 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -30,13 +30,13 @@ dependencies: lottie: ^1.0.1 meta: ^1.3.0 path_provider: ^2.0.1 - photo_manager: ^1.1.4 + photo_manager: ^1.1.6 photo_view: ^0.11.1 - rxdart: ^0.26.0 + rxdart: ^0.27.0 scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 shimmer: ^2.0.0 - stream_chat_flutter_core: ^2.0.0-nullsafety.2 + stream_chat_flutter_core: ^2.0.0-nullsafety.3 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index f07b49bd..1930e36a 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0-nullsafety.3 + +* Update llc dependency +* Minor fixes and improvements + ## 2.0.0-nullsafety.2 * Fix ChannelsBloc not performing calls if pagination ended diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index c84242b8..e8d1014d 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.2 +version: 2.0.0-nullsafety.3 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -10,12 +10,12 @@ environment: flutter: ">=1.17.0" dependencies: - collection: ^1.15.0-nullsafety.4 + collection: ^1.15.0 flutter: sdk: flutter meta: ^1.3.0 - rxdart: ^0.26.0 - stream_chat: ^2.0.0-nullsafety.1 + rxdart: ^0.27.0 + stream_chat: ^2.0.0-nullsafety.2 dev_dependencies: fake_async: ^1.2.0 diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index 961e3de0..c35459ca 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.0-nullsafety.2 + +* Update llc dependency +* Minor fixes and improvements +* Fixed bug not saving message.mentioned_users + ## 2.0.0-nullsafety.1 * Migrate this package to null safety diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 5d1927c3..661f55bb 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_persistence homepage: https://github.com/GetStream/stream-chat-flutter description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.1 +version: 2.0.0-nullsafety.2 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -17,8 +17,8 @@ dependencies: mutex: ^3.0.0 path: ^1.8.0 path_provider: ^2.0.1 - sqlite3_flutter_libs: ^0.4.1 - stream_chat: ^2.0.0-nullsafety.0 + sqlite3_flutter_libs: ^0.4.2 + stream_chat: ^2.0.0-nullsafety.2 dev_dependencies: build_runner: ^2.0.1 From f5e73d3cc61c2073da6df5c6d43e7aceb8891268 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 12:59:21 +0200 Subject: [PATCH 273/599] update rxdart --- packages/stream_chat/lib/src/api/channel.dart | 12 ++++++------ packages/stream_chat/lib/src/client.dart | 4 ++-- .../lib/src/channel_list_core.dart | 2 ++ .../lib/src/channels_bloc.dart | 4 ++-- .../lib/src/message_search_bloc.dart | 3 +-- .../stream_chat_flutter_core/lib/src/users_bloc.dart | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index cb3cfe37..69d967fe 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1427,7 +1427,7 @@ class ChannelClientState { /// This flag should be managed by UI sdks. /// When false, any new message (received by WebSocket event /// - [EventType.messageNew]) will not be pushed on to message list. - bool get isUpToDate => _isUpToDateController.value ?? true; + bool get isUpToDate => _isUpToDateController.value; set isUpToDate(bool isUpToDate) => _isUpToDateController.add(isUpToDate); @@ -1528,7 +1528,7 @@ class ChannelClientState { } if (_countMessageAsUnread(message)) { - _unreadCountController.add(_unreadCountController.value! + 1); + _unreadCountController.add(_unreadCountController.value + 1); } })); } @@ -1766,13 +1766,13 @@ class ChannelClientState { a.createdAt.compareTo(b.createdAt); /// The channel state related to this client - ChannelState get _channelState => _channelStateController.value!; + ChannelState get _channelState => _channelStateController.value; /// The channel state related to this client as a stream Stream get channelStateStream => _channelStateController.stream; /// The channel state related to this client - ChannelState get channelState => _channelStateController.value!; + ChannelState get channelState => _channelStateController.value; late BehaviorSubject _channelStateController; final Debounce _debouncedUpdatePersistenceChannelState; @@ -1784,7 +1784,7 @@ class ChannelClientState { /// The channel threads related to this channel Map> get threads => - _threadsController.value!.map((key, value) => MapEntry(key, value)); + _threadsController.value.map((key, value) => MapEntry(key, value)); /// The channel threads related to this channel as a stream Stream>> get threadsStream => @@ -1801,7 +1801,7 @@ class ChannelClientState { } /// Channel related typing users last value - List get typingEvents => _typingEventsController.value!; + List get typingEvents => _typingEventsController.value; /// Channel related typing users stream Stream> get typingEventsStream => _typingEventsController.stream; diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 3cabe274..b6efb6a5 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1495,7 +1495,7 @@ class ClientState { Stream get userStream => _userController.stream; /// The current user - Map get users => _usersController.value!; + Map get users => _usersController.value; /// The current user as a stream Stream> get usersStream => _usersController.stream; @@ -1517,7 +1517,7 @@ class ClientState { _channelsController.stream; /// The current list of channels in memory - Map get channels => _channelsController.value!; + Map get channels => _channelsController.value; set channels(Map v) { _channelsController.add(v); diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index a3be273d..dbd57269 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -132,6 +132,8 @@ class ChannelListCoreState extends State { stream: channelsBlocState.channelsStream, builder: (context, snapshot) { if (snapshot.hasError) { + print(snapshot.error); + print(snapshot.stackTrace); return widget.errorBuilder(context, snapshot.error!); } if (!snapshot.hasData) { 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 d46ee4bd..a3f0b58c 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -71,7 +71,7 @@ class ChannelsBlocState extends State } /// The current channel list - List? get channels => _channelsController.value; + List? get channels => _channelsController.valueOrNull; /// The current channel list as a stream Stream> get channelsStream => _channelsController.stream; @@ -128,7 +128,7 @@ class ChannelsBlocState extends State _channelsController.add(temp); } if (_channelsController.hasValue && - _queryChannelsLoadingController.value!) { + _queryChannelsLoadingController.value) { _queryChannelsLoadingController.sink.add(false); } } 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 386ba388..70e6df11 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 @@ -93,8 +93,7 @@ class MessageSearchBlocState extends State final temp = oldMessages + messages.results; _messageResponses.add(temp); } - if (_messageResponses.hasValue && - _queryMessagesLoadingController.value!) { + if (_messageResponses.hasValue && _queryMessagesLoadingController.value) { _queryMessagesLoadingController.add(false); } } catch (e, stk) { 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 57edfecb..0c0d46cd 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -92,7 +92,7 @@ class UsersBlocState extends State final temp = oldUsers + usersResponse.users; _usersController.add(temp); } - if (_usersController.hasValue && _queryUsersLoadingController.value!) { + if (_usersController.hasValue && _queryUsersLoadingController.value) { _queryUsersLoadingController.add(false); } } catch (e, stk) { From d7edb844f77453d9eb9f5a72496c1e5d539d2b0f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 13:11:23 +0200 Subject: [PATCH 274/599] regenerate files --- .../lib/src/db/moor_chat_database.g.dart | 296 +++++++++--------- 1 file changed, 142 insertions(+), 154 deletions(-) diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index b8d404b2..8da71717 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -60,31 +60,30 @@ class ChannelEntity extends DataClass implements Insertable { Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final boolType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); - final intType = db.typeSystem.forDartType(); return ChannelEntity( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type'])!, - cid: stringType.mapFromDatabaseResponse(data['${effectivePrefix}cid'])!, - config: $ChannelsTable.$converter0.mapToDart(stringType + id: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + type: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}type'])!, + cid: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}cid'])!, + config: $ChannelsTable.$converter0.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}config']))!, - frozen: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}frozen'])!, - lastMessageAt: dateTimeType + frozen: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}frozen'])!, + lastMessageAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}last_message_at']), - createdAt: dateTimeType + createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, - updatedAt: dateTimeType + updatedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, - deletedAt: dateTimeType + deletedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}deleted_at']), - memberCount: intType + memberCount: const IntType() .mapFromDatabaseResponse(data['${effectivePrefix}member_count'])!, - createdById: stringType + createdById: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}created_by_id']), - extraData: $ChannelsTable.$converter1.mapToDart(stringType + extraData: $ChannelsTable.$converter1.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); } @@ -226,7 +225,7 @@ class ChannelEntity extends DataClass implements Insertable { $mrjc(createdById.hashCode, extraData.hashCode)))))))))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is ChannelEntity && other.id == this.id && @@ -742,56 +741,54 @@ class MessageEntity extends DataClass implements Insertable { Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final intType = db.typeSystem.forDartType(); - final boolType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); return MessageEntity( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, - messageText: stringType + id: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + messageText: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}message_text']), - attachments: $MessagesTable.$converter0.mapToDart(stringType + attachments: $MessagesTable.$converter0.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}attachments']))!, - status: $MessagesTable.$converter1.mapToDart( - intType.mapFromDatabaseResponse(data['${effectivePrefix}status']))!, - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type'])!, - mentionedUsers: $MessagesTable.$converter2.mapToDart(stringType + status: $MessagesTable.$converter1.mapToDart(const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}status']))!, + type: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}type'])!, + mentionedUsers: $MessagesTable.$converter2.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}mentioned_users']))!, - reactionCounts: $MessagesTable.$converter3.mapToDart(stringType + reactionCounts: $MessagesTable.$converter3.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}reaction_counts'])), - reactionScores: $MessagesTable.$converter4.mapToDart(stringType + reactionScores: $MessagesTable.$converter4.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}reaction_scores'])), - parentId: stringType + parentId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}parent_id']), - quotedMessageId: stringType + quotedMessageId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}quoted_message_id']), - replyCount: intType + replyCount: const IntType() .mapFromDatabaseResponse(data['${effectivePrefix}reply_count']), - showInChannel: boolType + showInChannel: const BoolType() .mapFromDatabaseResponse(data['${effectivePrefix}show_in_channel']), - shadowed: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}shadowed'])!, - command: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}command']), - createdAt: dateTimeType + shadowed: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}shadowed'])!, + command: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}command']), + createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, - updatedAt: dateTimeType + updatedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, - deletedAt: dateTimeType + deletedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}deleted_at']), - userId: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), - pinned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}pinned'])!, - pinnedAt: dateTimeType + userId: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}user_id']), + pinned: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}pinned'])!, + pinnedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}pinned_at']), - pinExpires: dateTimeType + pinExpires: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}pin_expires']), - pinnedByUserId: stringType + pinnedByUserId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}pinned_by_user_id']), - channelCid: stringType + channelCid: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), - extraData: $MessagesTable.$converter5.mapToDart(stringType + extraData: $MessagesTable.$converter5.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); } @@ -1066,7 +1063,7 @@ class MessageEntity extends DataClass implements Insertable { pinned.hashCode, $mrjc(pinnedAt.hashCode, $mrjc(pinExpires.hashCode, $mrjc(pinnedByUserId.hashCode, $mrjc(channelCid.hashCode, extraData.hashCode)))))))))))))))))))))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is MessageEntity && other.id == this.id && @@ -1923,56 +1920,57 @@ class PinnedMessageEntity extends DataClass Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final intType = db.typeSystem.forDartType(); - final boolType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); return PinnedMessageEntity( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, - messageText: stringType + id: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + messageText: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}message_text']), - attachments: $PinnedMessagesTable.$converter0.mapToDart(stringType + attachments: $PinnedMessagesTable.$converter0.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}attachments']))!, - status: $PinnedMessagesTable.$converter1.mapToDart( - intType.mapFromDatabaseResponse(data['${effectivePrefix}status']))!, - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type'])!, - mentionedUsers: $PinnedMessagesTable.$converter2.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}mentioned_users']))!, - reactionCounts: $PinnedMessagesTable.$converter3.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}reaction_counts'])), - reactionScores: $PinnedMessagesTable.$converter4.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}reaction_scores'])), - parentId: stringType + status: $PinnedMessagesTable.$converter1.mapToDart(const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}status']))!, + type: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}type'])!, + mentionedUsers: $PinnedMessagesTable.$converter2.mapToDart( + const StringType().mapFromDatabaseResponse( + data['${effectivePrefix}mentioned_users']))!, + reactionCounts: $PinnedMessagesTable.$converter3.mapToDart( + const StringType().mapFromDatabaseResponse( + data['${effectivePrefix}reaction_counts'])), + reactionScores: $PinnedMessagesTable.$converter4.mapToDart( + const StringType().mapFromDatabaseResponse( + data['${effectivePrefix}reaction_scores'])), + parentId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}parent_id']), - quotedMessageId: stringType + quotedMessageId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}quoted_message_id']), - replyCount: intType + replyCount: const IntType() .mapFromDatabaseResponse(data['${effectivePrefix}reply_count']), - showInChannel: boolType + showInChannel: const BoolType() .mapFromDatabaseResponse(data['${effectivePrefix}show_in_channel']), - shadowed: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}shadowed'])!, - command: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}command']), - createdAt: dateTimeType + shadowed: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}shadowed'])!, + command: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}command']), + createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, - updatedAt: dateTimeType + updatedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, - deletedAt: dateTimeType + deletedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}deleted_at']), - userId: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), - pinned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}pinned'])!, - pinnedAt: dateTimeType + userId: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}user_id']), + pinned: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}pinned'])!, + pinnedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}pinned_at']), - pinExpires: dateTimeType + pinExpires: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}pin_expires']), - pinnedByUserId: stringType + pinnedByUserId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}pinned_by_user_id']), - channelCid: stringType + channelCid: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), - extraData: $PinnedMessagesTable.$converter5.mapToDart(stringType + extraData: $PinnedMessagesTable.$converter5.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); } @@ -2247,7 +2245,7 @@ class PinnedMessageEntity extends DataClass pinned.hashCode, $mrjc(pinnedAt.hashCode, $mrjc(pinExpires.hashCode, $mrjc(pinnedByUserId.hashCode, $mrjc(channelCid.hashCode, extraData.hashCode)))))))))))))))))))))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is PinnedMessageEntity && other.id == this.id && @@ -3031,19 +3029,18 @@ class ReactionEntity extends DataClass implements Insertable { Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); - final intType = db.typeSystem.forDartType(); return ReactionEntity( - userId: stringType + userId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}user_id'])!, - messageId: stringType + messageId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}message_id'])!, - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type'])!, - createdAt: dateTimeType + type: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}type'])!, + createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, - score: intType.mapFromDatabaseResponse(data['${effectivePrefix}score'])!, - extraData: $ReactionsTable.$converter0.mapToDart(stringType + score: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}score'])!, + extraData: $ReactionsTable.$converter0.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); } @@ -3125,7 +3122,7 @@ class ReactionEntity extends DataClass implements Insertable { $mrjc(createdAt.hashCode, $mrjc(score.hashCode, extraData.hashCode)))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is ReactionEntity && other.userId == this.userId && @@ -3395,23 +3392,22 @@ class UserEntity extends DataClass implements Insertable { factory UserEntity.fromData(Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); - final boolType = db.typeSystem.forDartType(); return UserEntity( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, - role: stringType.mapFromDatabaseResponse(data['${effectivePrefix}role']), - createdAt: dateTimeType + id: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + role: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}role']), + createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, - updatedAt: dateTimeType + updatedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, - lastActive: dateTimeType + lastActive: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}last_active']), - online: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}online'])!, - banned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}banned'])!, - extraData: $UsersTable.$converter0.mapToDart(stringType + online: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}online'])!, + banned: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}banned'])!, + extraData: $UsersTable.$converter0.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}extra_data']))!, ); } @@ -3513,7 +3509,7 @@ class UserEntity extends DataClass implements Insertable { $mrjc(online.hashCode, $mrjc(banned.hashCode, extraData.hashCode)))))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is UserEntity && other.id == this.id && @@ -3554,7 +3550,7 @@ class UsersCompanion extends UpdateCompanion { this.online = const Value.absent(), this.banned = const Value.absent(), required Map extraData, - }) : id = Value(id), + }) : id = Value(id), extraData = Value(extraData); static Insertable custom({ Expression? id, @@ -3841,30 +3837,28 @@ class MemberEntity extends DataClass implements Insertable { factory MemberEntity.fromData(Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); - final boolType = db.typeSystem.forDartType(); return MemberEntity( - userId: stringType + userId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}user_id'])!, - channelCid: stringType + channelCid: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!, - role: stringType.mapFromDatabaseResponse(data['${effectivePrefix}role']), - inviteAcceptedAt: dateTimeType.mapFromDatabaseResponse( + role: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}role']), + inviteAcceptedAt: const DateTimeType().mapFromDatabaseResponse( data['${effectivePrefix}invite_accepted_at']), - inviteRejectedAt: dateTimeType.mapFromDatabaseResponse( + inviteRejectedAt: const DateTimeType().mapFromDatabaseResponse( data['${effectivePrefix}invite_rejected_at']), - invited: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}invited'])!, - banned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}banned'])!, - shadowBanned: boolType + invited: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}invited'])!, + banned: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}banned'])!, + shadowBanned: const BoolType() .mapFromDatabaseResponse(data['${effectivePrefix}shadow_banned'])!, - isModerator: boolType + isModerator: const BoolType() .mapFromDatabaseResponse(data['${effectivePrefix}is_moderator'])!, - createdAt: dateTimeType + createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, - updatedAt: dateTimeType + updatedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, ); } @@ -3997,7 +3991,7 @@ class MemberEntity extends DataClass implements Insertable { $mrjc(createdAt.hashCode, updatedAt.hashCode))))))))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is MemberEntity && other.userId == this.userId && @@ -4396,17 +4390,14 @@ class ReadEntity extends DataClass implements Insertable { factory ReadEntity.fromData(Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final dateTimeType = db.typeSystem.forDartType(); - final stringType = db.typeSystem.forDartType(); - final intType = db.typeSystem.forDartType(); return ReadEntity( - lastRead: dateTimeType + lastRead: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}last_read'])!, - userId: stringType + userId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}user_id'])!, - channelCid: stringType + channelCid: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!, - unreadMessages: intType + unreadMessages: const IntType() .mapFromDatabaseResponse(data['${effectivePrefix}unread_messages'])!, ); } @@ -4469,7 +4460,7 @@ class ReadEntity extends DataClass implements Insertable { $mrjc(userId.hashCode, $mrjc(channelCid.hashCode, unreadMessages.hashCode)))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is ReadEntity && other.lastRead == this.lastRead && @@ -4666,11 +4657,10 @@ class ChannelQueryEntity extends DataClass Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); return ChannelQueryEntity( - queryHash: stringType + queryHash: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}query_hash'])!, - channelCid: stringType + channelCid: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!, ); } @@ -4716,7 +4706,7 @@ class ChannelQueryEntity extends DataClass @override int get hashCode => $mrjf($mrjc(queryHash.hashCode, channelCid.hashCode)); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is ChannelQueryEntity && other.queryHash == this.queryHash && @@ -4733,7 +4723,7 @@ class ChannelQueriesCompanion extends UpdateCompanion { ChannelQueriesCompanion.insert({ required String queryHash, required String channelCid, - }) : queryHash = Value(queryHash), + }) : queryHash = Value(queryHash), channelCid = Value(channelCid); static Insertable custom({ Expression? queryHash, @@ -4876,20 +4866,18 @@ class ConnectionEventEntity extends DataClass Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final intType = db.typeSystem.forDartType(); - final stringType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); return ConnectionEventEntity( - id: intType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, - ownUser: $ConnectionEventsTable.$converter0.mapToDart(stringType + id: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + ownUser: $ConnectionEventsTable.$converter0.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}own_user'])), - totalUnreadCount: intType.mapFromDatabaseResponse( + totalUnreadCount: const IntType().mapFromDatabaseResponse( data['${effectivePrefix}total_unread_count']), - unreadChannels: intType + unreadChannels: const IntType() .mapFromDatabaseResponse(data['${effectivePrefix}unread_channels']), - lastEventAt: dateTimeType + lastEventAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}last_event_at']), - lastSyncAt: dateTimeType + lastSyncAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}last_sync_at']), ); } @@ -4982,7 +4970,7 @@ class ConnectionEventEntity extends DataClass $mrjc(unreadChannels.hashCode, $mrjc(lastEventAt.hashCode, lastSyncAt.hashCode)))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is ConnectionEventEntity && other.id == this.id && From b67db631e4ef64ffc26774fab53bff04dc688b5e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 13:30:37 +0200 Subject: [PATCH 275/599] use specific analysis option files --- .../stream_chat/analysis_options.yaml | 0 .../stream_chat_flutter/analysis_options.yaml | 150 ++++++++++++++++++ .../analysis_options.yaml | 150 ++++++++++++++++++ .../analysis_options.yaml | 150 ++++++++++++++++++ 4 files changed, 450 insertions(+) rename analysis_options.yaml => packages/stream_chat/analysis_options.yaml (100%) create mode 100644 packages/stream_chat_flutter/analysis_options.yaml create mode 100644 packages/stream_chat_flutter_core/analysis_options.yaml create mode 100644 packages/stream_chat_persistence/analysis_options.yaml diff --git a/analysis_options.yaml b/packages/stream_chat/analysis_options.yaml similarity index 100% rename from analysis_options.yaml rename to packages/stream_chat/analysis_options.yaml diff --git a/packages/stream_chat_flutter/analysis_options.yaml b/packages/stream_chat_flutter/analysis_options.yaml new file mode 100644 index 00000000..f0a87ea5 --- /dev/null +++ b/packages/stream_chat_flutter/analysis_options.yaml @@ -0,0 +1,150 @@ +analyzer: + enable-experiment: + - extension-methods + exclude: + - lib/**/*.g.dart + - example/** + - lib/src/emoji + - lib/**/*.freezed.dart + - test/** + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_use_package_imports + - avoid_empty_else + - avoid_relative_lib_imports + - avoid_slow_async_io + - avoid_types_as_parameter_names + - cancel_subscriptions + - close_sinks + - control_flow_in_finally + - empty_statements + - hash_and_equals + - invariant_booleans + - iterable_contains_unrelated_type + - list_remove_unrelated_type + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - no_duplicate_case_values + - no_logic_in_create_state + - prefer_void_to_null + - test_types_in_equals + - throw_in_finally + - unnecessary_statements + - unrelated_type_equality_checks + - omit_local_variable_types + - use_key_in_widget_constructors + - valid_regexps + - always_declare_return_types + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catching_errors + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_private_typedef_functions + - avoid_redundant_argument_values + - avoid_return_types_on_setters + - avoid_returning_null_for_void + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_extensions + - camel_case_types + - cascade_invocations + + - constant_identifier_names + - curly_braces_in_flow_control_structures + - directives_ordering + - empty_catches + - empty_constructor_bodies + - exhaustive_cases + - file_names + - implementation_imports + - join_return_with_assignment + - leading_newlines_in_multiline_strings + - library_names + - library_prefixes + - lines_longer_than_80_chars + - missing_whitespace_between_adjacent_strings + - non_constant_identifier_names + - null_closures + - one_member_abstracts + - only_throw_errors + - package_api_docs + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_asserts_with_message + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_equal_for_default_values + - prefer_expression_function_bodies + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_int_literals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - provide_deprecation_message + - public_member_api_docs + - recursive_getters + - sized_box_for_whitespace + - slash_for_doc_comments + - sort_child_properties_last + - sort_constructors_first + - sort_unnamed_constructors_first + + - type_annotate_public_apis + - type_init_formals + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_parenthesis + - unnecessary_raw_strings + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - use_is_even_rather_than_modulo + - use_late_for_private_fields_and_variables + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_to_and_as_if_applicable + - package_names + - sort_pub_dependencies + + - cast_nullable_to_non_nullable + - unnecessary_null_checks + - tighten_type_of_initializing_formals + - null_check_on_nullable_type_parameter diff --git a/packages/stream_chat_flutter_core/analysis_options.yaml b/packages/stream_chat_flutter_core/analysis_options.yaml new file mode 100644 index 00000000..f0a87ea5 --- /dev/null +++ b/packages/stream_chat_flutter_core/analysis_options.yaml @@ -0,0 +1,150 @@ +analyzer: + enable-experiment: + - extension-methods + exclude: + - lib/**/*.g.dart + - example/** + - lib/src/emoji + - lib/**/*.freezed.dart + - test/** + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_use_package_imports + - avoid_empty_else + - avoid_relative_lib_imports + - avoid_slow_async_io + - avoid_types_as_parameter_names + - cancel_subscriptions + - close_sinks + - control_flow_in_finally + - empty_statements + - hash_and_equals + - invariant_booleans + - iterable_contains_unrelated_type + - list_remove_unrelated_type + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - no_duplicate_case_values + - no_logic_in_create_state + - prefer_void_to_null + - test_types_in_equals + - throw_in_finally + - unnecessary_statements + - unrelated_type_equality_checks + - omit_local_variable_types + - use_key_in_widget_constructors + - valid_regexps + - always_declare_return_types + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catching_errors + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_private_typedef_functions + - avoid_redundant_argument_values + - avoid_return_types_on_setters + - avoid_returning_null_for_void + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_extensions + - camel_case_types + - cascade_invocations + + - constant_identifier_names + - curly_braces_in_flow_control_structures + - directives_ordering + - empty_catches + - empty_constructor_bodies + - exhaustive_cases + - file_names + - implementation_imports + - join_return_with_assignment + - leading_newlines_in_multiline_strings + - library_names + - library_prefixes + - lines_longer_than_80_chars + - missing_whitespace_between_adjacent_strings + - non_constant_identifier_names + - null_closures + - one_member_abstracts + - only_throw_errors + - package_api_docs + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_asserts_with_message + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_equal_for_default_values + - prefer_expression_function_bodies + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_int_literals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - provide_deprecation_message + - public_member_api_docs + - recursive_getters + - sized_box_for_whitespace + - slash_for_doc_comments + - sort_child_properties_last + - sort_constructors_first + - sort_unnamed_constructors_first + + - type_annotate_public_apis + - type_init_formals + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_parenthesis + - unnecessary_raw_strings + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - use_is_even_rather_than_modulo + - use_late_for_private_fields_and_variables + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_to_and_as_if_applicable + - package_names + - sort_pub_dependencies + + - cast_nullable_to_non_nullable + - unnecessary_null_checks + - tighten_type_of_initializing_formals + - null_check_on_nullable_type_parameter diff --git a/packages/stream_chat_persistence/analysis_options.yaml b/packages/stream_chat_persistence/analysis_options.yaml new file mode 100644 index 00000000..f0a87ea5 --- /dev/null +++ b/packages/stream_chat_persistence/analysis_options.yaml @@ -0,0 +1,150 @@ +analyzer: + enable-experiment: + - extension-methods + exclude: + - lib/**/*.g.dart + - example/** + - lib/src/emoji + - lib/**/*.freezed.dart + - test/** + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_use_package_imports + - avoid_empty_else + - avoid_relative_lib_imports + - avoid_slow_async_io + - avoid_types_as_parameter_names + - cancel_subscriptions + - close_sinks + - control_flow_in_finally + - empty_statements + - hash_and_equals + - invariant_booleans + - iterable_contains_unrelated_type + - list_remove_unrelated_type + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - no_duplicate_case_values + - no_logic_in_create_state + - prefer_void_to_null + - test_types_in_equals + - throw_in_finally + - unnecessary_statements + - unrelated_type_equality_checks + - omit_local_variable_types + - use_key_in_widget_constructors + - valid_regexps + - always_declare_return_types + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catching_errors + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_private_typedef_functions + - avoid_redundant_argument_values + - avoid_return_types_on_setters + - avoid_returning_null_for_void + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_extensions + - camel_case_types + - cascade_invocations + + - constant_identifier_names + - curly_braces_in_flow_control_structures + - directives_ordering + - empty_catches + - empty_constructor_bodies + - exhaustive_cases + - file_names + - implementation_imports + - join_return_with_assignment + - leading_newlines_in_multiline_strings + - library_names + - library_prefixes + - lines_longer_than_80_chars + - missing_whitespace_between_adjacent_strings + - non_constant_identifier_names + - null_closures + - one_member_abstracts + - only_throw_errors + - package_api_docs + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_asserts_with_message + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_equal_for_default_values + - prefer_expression_function_bodies + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_int_literals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - provide_deprecation_message + - public_member_api_docs + - recursive_getters + - sized_box_for_whitespace + - slash_for_doc_comments + - sort_child_properties_last + - sort_constructors_first + - sort_unnamed_constructors_first + + - type_annotate_public_apis + - type_init_formals + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_parenthesis + - unnecessary_raw_strings + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - use_is_even_rather_than_modulo + - use_late_for_private_fields_and_variables + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_to_and_as_if_applicable + - package_names + - sort_pub_dependencies + + - cast_nullable_to_non_nullable + - unnecessary_null_checks + - tighten_type_of_initializing_formals + - null_check_on_nullable_type_parameter From 80cbb266d2f8e85386143bc7f4a3adc1d9edb834 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 13:45:22 +0200 Subject: [PATCH 276/599] fix tests --- packages/stream_chat/lib/src/client.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index b6efb6a5..05f2559b 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1489,7 +1489,7 @@ class ClientState { void _updateUser(User? user) => _updateUsers([user]); /// The current user - OwnUser? get user => _userController.value; + OwnUser? get user => _userController.valueOrNull; /// The current user as a stream Stream get userStream => _userController.stream; @@ -1501,13 +1501,13 @@ class ClientState { Stream> get usersStream => _usersController.stream; /// The current unread channels count - int? get unreadChannels => _unreadChannelsController.value; + int? get unreadChannels => _unreadChannelsController.valueOrNull; /// The current unread channels count as a stream Stream get unreadChannelsStream => _unreadChannelsController.stream; /// The current total unread messages count - int? get totalUnreadCount => _totalUnreadCountController.value; + int? get totalUnreadCount => _totalUnreadCountController.valueOrNull; /// The current total unread messages count as a stream Stream get totalUnreadCountStream => _totalUnreadCountController.stream; From d5820d25d9d2b56a8927ee7eec8402e1a0fe328d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 13:50:25 +0200 Subject: [PATCH 277/599] fix rxdart --- .../stream_chat_flutter_core/lib/src/message_search_bloc.dart | 3 ++- packages/stream_chat_flutter_core/lib/src/users_bloc.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) 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 70e6df11..daffadfe 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 @@ -44,7 +44,8 @@ class MessageSearchBlocState extends State late StreamChatCoreState _streamChatCoreState; /// The current messages list - List? get messageResponses => _messageResponses.value; + List? get messageResponses => + _messageResponses.valueOrNull; /// The current messages list as a stream Stream> get messagesStream => 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 0c0d46cd..1806ec36 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -43,7 +43,7 @@ class UsersBloc extends StatefulWidget { class UsersBlocState extends State with AutomaticKeepAliveClientMixin { /// The current users list - List? get users => _usersController.value; + List? get users => _usersController.valueOrNull; /// The current users list as a stream Stream> get usersStream => _usersController.stream; From 9e71299cb686e4dd2b384fe8ef8b4b8ea65bcc54 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 14:30:48 +0200 Subject: [PATCH 278/599] remove transform --- .../lib/src/message_input.dart | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index ed098903..c71fd341 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1473,16 +1473,12 @@ class MessageInputState extends State { final containsUrl = widget.quotedMessage!.attachments .any((element) => element.ogScrapeUrl != null) == true; - return Transform( - transform: Matrix4.rotationY(pi), - alignment: Alignment.center, - child: QuotedMessageWidget( - reverse: true, - showBorder: !containsUrl, - message: widget.quotedMessage!, - messageTheme: StreamChatTheme.of(context).otherMessageTheme, - padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), - ), + return QuotedMessageWidget( + reverse: true, + showBorder: !containsUrl, + message: widget.quotedMessage!, + messageTheme: StreamChatTheme.of(context).otherMessageTheme, + padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), ); } From 99562d9ba4e076f6e96cc6507ae77a578bedf0d8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 15:01:50 +0200 Subject: [PATCH 279/599] fix quoted message --- packages/stream_chat_flutter/lib/src/message_input.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index c71fd341..91f16b7b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1474,7 +1474,6 @@ class MessageInputState extends State { .any((element) => element.ogScrapeUrl != null) == true; return QuotedMessageWidget( - reverse: true, showBorder: !containsUrl, message: widget.quotedMessage!, messageTheme: StreamChatTheme.of(context).otherMessageTheme, From be0378e4f0351914181917e096bcca74734f8790 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 15:14:36 +0200 Subject: [PATCH 280/599] fix quoted message --- .../lib/src/message_input.dart | 1 + .../lib/src/quoted_message_widget.dart | 31 ++++++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 91f16b7b..c71fd341 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1474,6 +1474,7 @@ class MessageInputState extends State { .any((element) => element.ogScrapeUrl != null) == true; return QuotedMessageWidget( + reverse: true, showBorder: !containsUrl, message: widget.quotedMessage!, messageTheme: StreamChatTheme.of(context).otherMessageTheme, 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 a7c7c798..4b45494c 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -102,21 +102,24 @@ class QuotedMessageWidget extends StatelessWidget { bool get _containsText => message.text?.isNotEmpty == true; @override - Widget build(BuildContext context) => Padding( - padding: padding, - child: InkWell( - onTap: onTap, - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Flexible(child: _buildMessage(context)), - const SizedBox(width: 8), - if (message.user != null) _buildUserAvatar(), - ], - ), + Widget build(BuildContext context) { + final children = [ + Flexible(child: _buildMessage(context)), + const SizedBox(width: 8), + if (message.user != null) _buildUserAvatar(), + ]; + return Padding( + padding: padding, + child: InkWell( + onTap: onTap, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: reverse ? children.reversed.toList() : children, ), - ); + ), + ); + } Widget _buildMessage(BuildContext context) { final isOnlyEmoji = message.text!.isOnlyEmoji; From 52739738c3dec37a2352172bc38a0c31ac2d7cd4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 15:29:58 +0200 Subject: [PATCH 281/599] fix quoted message --- .../lib/src/quoted_message_widget.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 4b45494c..07a820b9 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -157,10 +157,11 @@ class QuotedMessageWidget extends StatelessWidget { color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ) : null, - borderRadius: const BorderRadius.only( - topRight: Radius.circular(12), - topLeft: Radius.circular(12), - bottomLeft: Radius.circular(12), + borderRadius: BorderRadius.only( + topRight: const Radius.circular(12), + topLeft: const Radius.circular(12), + bottomRight: reverse ? const Radius.circular(12) : Radius.zero, + bottomLeft: reverse ? Radius.zero : const Radius.circular(12), ), ), padding: const EdgeInsets.all(8), From 4c90b7229fde6746ec1c1ad95a208d6cffe7b910 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 15:58:49 +0200 Subject: [PATCH 282/599] remove prints --- .../stream_chat_flutter_core/lib/src/channel_list_core.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index dbd57269..a3be273d 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -132,8 +132,6 @@ class ChannelListCoreState extends State { stream: channelsBlocState.channelsStream, builder: (context, snapshot) { if (snapshot.hasError) { - print(snapshot.error); - print(snapshot.stackTrace); return widget.errorBuilder(context, snapshot.error!); } if (!snapshot.hasData) { From fd9bd99792500f9c12a5d8e0ad2e7b89250a1a62 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 17:12:04 +0200 Subject: [PATCH 283/599] fix prent message --- .../lib/src/message_list_view.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index c24ceb00..e07d9358 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -815,11 +815,13 @@ class _MessageListViewState extends State { padding: const EdgeInsets.all(8), showSendingIndicator: false, onThreadTap: _onThreadTap, - borderRadiusGeometry: const BorderRadius.only( - topLeft: Radius.circular(16), - bottomLeft: Radius.circular(2), - topRight: Radius.circular(16), - bottomRight: Radius.circular(16), + borderRadiusGeometry: BorderRadius.only( + topLeft: const Radius.circular(16), + bottomLeft: + isMyMessage ? const Radius.circular(16) : const Radius.circular(2), + topRight: const Radius.circular(16), + bottomRight: + isMyMessage ? const Radius.circular(2) : const Radius.circular(16), ), textPadding: EdgeInsets.symmetric( vertical: 8, From e1981492430feb48201ad0edd03f1935e1b520ae Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 19 May 2021 17:14:37 +0200 Subject: [PATCH 284/599] update readme --- packages/stream_chat_flutter_core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/README.md b/packages/stream_chat_flutter_core/README.md index 8e74c881..952caf05 100644 --- a/packages/stream_chat_flutter_core/README.md +++ b/packages/stream_chat_flutter_core/README.md @@ -1,4 +1,4 @@ -# Official Core [Flutter SDK](https://getstream.io/chat/sdk/flutter/) for [Stream Chat API](https://getstream.io/chat/) +# Official Core [Flutter SDK](https://getstream.io/chat/sdk/flutter/) for [Stream Chat](https://getstream.io/chat/) > The official Flutter core components for Stream Chat, a service for > building chat applications. From 229a30bd651d1f047589e9e61e2f645a5b3428e9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 20 May 2021 11:59:43 +0200 Subject: [PATCH 285/599] fix persistence not building for web --- .../lib/src/db/moor_chat_database.dart | 6 ------ .../test/src/dao/channel_dao_test.dart | 4 +++- .../test/src/dao/channel_query_dao_test.dart | 3 ++- .../test/src/dao/connection_event_dao_test.dart | 3 ++- .../test/src/dao/member_dao_test.dart | 3 ++- .../test/src/dao/message_dao_test.dart | 4 +++- .../test/src/dao/pinned_message_dao_test.dart | 4 +++- .../test/src/dao/reaction_dao_test.dart | 4 +++- .../test/src/dao/read_dao_test.dart | 3 ++- .../test/src/dao/user_dao_test.dart | 4 +++- .../test/stream_chat_persistence_client_test.dart | 13 +++++++------ 11 files changed, 30 insertions(+), 21 deletions(-) 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 fbb53fd3..efc8610b 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 @@ -1,5 +1,3 @@ -import 'package:meta/meta.dart'; -import 'package:moor/ffi.dart'; import 'package:moor/moor.dart'; import 'package:stream_chat/stream_chat.dart'; @@ -46,10 +44,6 @@ class MoorChatDatabase extends _$MoorChatDatabase { DatabaseConnection connection, ) : super.connect(connection); - /// Custom constructor used only for testing - @visibleForTesting - MoorChatDatabase.testable(this._userId) : super(VmDatabase.memory()); - final String _userId; /// User id to which the database is connected diff --git a/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart index d6ac8123..9a458574 100644 --- a/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart @@ -3,12 +3,14 @@ import 'package:stream_chat_persistence/src/dao/channel_dao.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; +import '../../stream_chat_persistence_client_test.dart'; + void main() { late ChannelDao channelDao; late MoorChatDatabase database; setUp(() { - database = MoorChatDatabase.testable('testUserId'); + database = testDatabaseProvider('testUserId'); channelDao = database.channelDao; }); 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 9b5e3470..a69a07ee 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 @@ -5,6 +5,7 @@ import 'package:stream_chat_persistence/src/dao/channel_query_dao.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; +import '../../stream_chat_persistence_client_test.dart'; import '../utils/date_matcher.dart'; void main() { @@ -12,7 +13,7 @@ void main() { late ChannelQueryDao channelQueryDao; setUp(() { - database = MoorChatDatabase.testable('testUserId'); + database = testDatabaseProvider('testUserId'); channelQueryDao = database.channelQueryDao; }); diff --git a/packages/stream_chat_persistence/test/src/dao/connection_event_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/connection_event_dao_test.dart index 536fe3bc..40c385ba 100644 --- a/packages/stream_chat_persistence/test/src/dao/connection_event_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/connection_event_dao_test.dart @@ -3,6 +3,7 @@ import 'package:stream_chat_persistence/src/dao/connection_event_dao.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; +import '../../stream_chat_persistence_client_test.dart'; import '../utils/date_matcher.dart'; void main() { @@ -10,7 +11,7 @@ void main() { late MoorChatDatabase database; setUp(() { - database = MoorChatDatabase.testable('testUserId'); + database = testDatabaseProvider('testUserId'); eventDao = database.connectionEventDao; }); diff --git a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart index 6654de4c..8ce704a4 100644 --- a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart @@ -5,6 +5,7 @@ import 'package:stream_chat_persistence/src/dao/dao.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; +import '../../stream_chat_persistence_client_test.dart'; import '../utils/date_matcher.dart'; void main() { @@ -12,7 +13,7 @@ void main() { late MoorChatDatabase database; setUp(() { - database = MoorChatDatabase.testable('testUserId'); + database = testDatabaseProvider('testUserId'); memberDao = database.memberDao; }); diff --git a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart index 0d986590..54cc219a 100644 --- a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart @@ -5,12 +5,14 @@ import 'package:stream_chat_persistence/src/dao/dao.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; +import '../../stream_chat_persistence_client_test.dart'; + void main() { late MessageDao messageDao; late MoorChatDatabase database; setUp(() { - database = MoorChatDatabase.testable('testUserId'); + database = testDatabaseProvider('testUserId'); messageDao = database.messageDao; }); diff --git a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart index 33bf96b1..42a54107 100644 --- a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart @@ -5,12 +5,14 @@ import 'package:stream_chat_persistence/src/dao/dao.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; +import '../../stream_chat_persistence_client_test.dart'; + void main() { late PinnedMessageDao pinnedMessageDao; late MoorChatDatabase database; setUp(() { - database = MoorChatDatabase.testable('testUserId'); + database = testDatabaseProvider('testUserId'); pinnedMessageDao = database.pinnedMessageDao; }); diff --git a/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart index 73ff63fe..4edda680 100644 --- a/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart @@ -5,12 +5,14 @@ import 'package:stream_chat_persistence/src/dao/reaction_dao.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; +import '../../stream_chat_persistence_client_test.dart'; + void main() { late ReactionDao reactionDao; late MoorChatDatabase database; setUp(() { - database = MoorChatDatabase.testable('testUserId'); + database = testDatabaseProvider('testUserId'); reactionDao = database.reactionDao; }); diff --git a/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart index 77b7a7f9..287dcad4 100644 --- a/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart @@ -3,6 +3,7 @@ import 'package:stream_chat_persistence/src/dao/dao.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; +import '../../stream_chat_persistence_client_test.dart'; import '../utils/date_matcher.dart'; void main() { @@ -10,7 +11,7 @@ void main() { late MoorChatDatabase database; setUp(() { - database = MoorChatDatabase.testable('testUserId'); + database = testDatabaseProvider('testUserId'); readDao = database.readDao; }); diff --git a/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart index 5332a298..ffb50edd 100644 --- a/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart @@ -5,12 +5,14 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; +import '../../stream_chat_persistence_client_test.dart'; + void main() { late UserDao userDao; late MoorChatDatabase database; setUp(() { - database = MoorChatDatabase.testable('testUserId'); + database = testDatabaseProvider('testUserId'); userDao = database.userDao; }); diff --git a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart index 8a275201..83524870 100644 --- a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart +++ b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart @@ -1,4 +1,5 @@ import 'package:mocktail/mocktail.dart'; +import 'package:moor/ffi.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart'; @@ -6,8 +7,8 @@ import 'package:test/test.dart'; import 'mock_chat_database.dart'; import 'src/utils/date_matcher.dart'; -MoorChatDatabase _testDatabaseProvider(String userId, ConnectionMode mode) => - MoorChatDatabase.testable(userId); +MoorChatDatabase testDatabaseProvider(String userId, [ConnectionMode? mode]) => + MoorChatDatabase(userId, VmDatabase.memory()); void main() { group('connect', () { @@ -15,7 +16,7 @@ void main() { test('successfully connects with the Database', () async { final client = StreamChatPersistenceClient(logLevel: Level.ALL); expect(client.db, isNull); - await client.connect(userId, databaseProvider: _testDatabaseProvider); + await client.connect(userId, databaseProvider: testDatabaseProvider); expect(client.db, isNotNull); expect(client.db, isA()); expect(client.db!.userId, userId); @@ -28,13 +29,13 @@ void main() { test('throws if already connected', () async { final client = StreamChatPersistenceClient(logLevel: Level.ALL); expect(client.db, isNull); - await client.connect(userId, databaseProvider: _testDatabaseProvider); + await client.connect(userId, databaseProvider: testDatabaseProvider); expect(client.db, isNotNull); expect(client.db, isNotNull); expect(client.db, isA()); expect(client.db!.userId, userId); expect( - () => client.connect(userId, databaseProvider: _testDatabaseProvider), + () => client.connect(userId, databaseProvider: testDatabaseProvider), throwsException, ); @@ -47,7 +48,7 @@ void main() { test('disconnect', () async { const userId = 'testUserId'; final client = StreamChatPersistenceClient(logLevel: Level.ALL); - await client.connect(userId, databaseProvider: _testDatabaseProvider); + await client.connect(userId, databaseProvider: testDatabaseProvider); expect(client.db, isNotNull); await client.disconnect(flush: true); expect(client.db, isNull); From ecf0f42d6c2b750d1b3089f392b25de0f7b8220a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 20 May 2021 15:52:03 +0200 Subject: [PATCH 286/599] add MessageInput.maxAttachmentSize prop --- .../lib/src/message_input.dart | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index c71fd341..6d3ec5cf 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -76,7 +76,7 @@ enum SendButtonLocation { const _kMinMediaPickerSize = 360.0; -const _kMaxAttachmentSize = 20971520; // 20MB in Bytes +const _kDefaultMaxAttachmentSize = 20971520; // 20MB in Bytes /// Inactive state /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input.png) @@ -148,11 +148,16 @@ class MessageInput extends StatefulWidget { this.activeSendButton, this.showCommandsButton = true, this.mentionsTileBuilder, + this.maxAttachmentSize = _kDefaultMaxAttachmentSize, }) : super(key: key); /// Message to edit final Message? editMessage; + /// Max attachment size in bytes + /// If you're using our default CDN do not set it + final int maxAttachmentSize; + /// Message to start with final Message? initialMessage; @@ -1108,12 +1113,12 @@ class MessageInputState extends State { bytes: mediaFile.readAsBytesSync(), ); - if (file.size! > _kMaxAttachmentSize) { + if (file.size! > widget.maxAttachmentSize) { if (medium.type == AssetType.video) { final mediaInfo = await (VideoService.compressVideo(file.path) as FutureOr); - if (mediaInfo.filesize! > _kMaxAttachmentSize) { + if (mediaInfo.filesize! > widget.maxAttachmentSize) { _showErrorAlert( // ignore: lines_longer_than_80_chars 'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.', @@ -1896,12 +1901,12 @@ class MessageInputState extends State { extraData: extraDataMap, ); - if (file.size! > _kMaxAttachmentSize) { + if (file.size! > widget.maxAttachmentSize) { if (attachmentType == 'Video') { final mediaInfo = await (VideoService.compressVideo(file.path) as FutureOr); - if (mediaInfo.filesize! > _kMaxAttachmentSize) { + if (mediaInfo.filesize! > widget.maxAttachmentSize) { _showErrorAlert( // ignore: lines_longer_than_80_chars 'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.', From 988f19ece837806dc5c680e0644ecc57e1c8b01b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 20 May 2021 16:31:36 +0200 Subject: [PATCH 287/599] fix file attachment in messageinput --- .../lib/src/attachment/file_attachment.dart | 112 ++++++++---------- .../lib/src/message_input.dart | 13 +- .../lib/src/video_service.dart | 4 +- 3 files changed, 59 insertions(+), 70 deletions(-) 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 63bcdd9b..a676e576 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -159,6 +159,7 @@ class FileAttachment extends AttachmentWidget { shape: _getDefaultShape(context), child: source.when( local: () => VideoThumbnailImage( + fit: BoxFit.cover, video: attachment.file!.path!, placeholderBuilder: (_) => const Center( child: SizedBox( @@ -169,6 +170,7 @@ class FileAttachment extends AttachmentWidget { ), ), network: () => VideoThumbnailImage( + fit: BoxFit.cover, video: attachment.assetUrl!, placeholderBuilder: (_) => const Center( child: SizedBox( @@ -212,50 +214,42 @@ class FileAttachment extends AttachmentWidget { final attachmentId = attachment.id; var trailingWidget = trailing; trailingWidget ??= attachment.uploadState.when( - preparing: () => Padding( - padding: const EdgeInsets.all(8), - child: _buildButton( - icon: StreamSvgIcon.close(color: theme.colorTheme.white), - fillColor: theme.colorTheme.overlayDark, - onPressed: () => channel.cancelAttachmentUpload(attachmentId), - ), + preparing: () => Padding( + padding: const EdgeInsets.all(8), + child: _buildButton( + icon: StreamSvgIcon.close(color: theme.colorTheme.white), + fillColor: theme.colorTheme.overlayDark, + onPressed: () => channel.cancelAttachmentUpload(attachmentId), + ), + ), + inProgress: (_, __) => Padding( + padding: const EdgeInsets.all(8), + child: _buildButton( + icon: StreamSvgIcon.close(color: theme.colorTheme.white), + fillColor: theme.colorTheme.overlayDark, + onPressed: () => channel.cancelAttachmentUpload(attachmentId), + ), + ), + success: () => Padding( + padding: const EdgeInsets.all(8), + child: CircleAvatar( + backgroundColor: theme.colorTheme.accentBlue, + maxRadius: 12, + child: StreamSvgIcon.check(color: theme.colorTheme.white), + ), + ), + failed: (_) => Padding( + padding: const EdgeInsets.all(8), + child: _buildButton( + icon: StreamSvgIcon.retry(color: theme.colorTheme.white), + fillColor: theme.colorTheme.overlayDark, + onPressed: () => channel.retryAttachmentUpload( + message.id, + attachmentId, ), - inProgress: (_, __) => Padding( - padding: const EdgeInsets.all(8), - child: _buildButton( - icon: StreamSvgIcon.close(color: theme.colorTheme.white), - fillColor: theme.colorTheme.overlayDark, - onPressed: () => channel.cancelAttachmentUpload(attachmentId), - ), - ), - success: () => Padding( - padding: const EdgeInsets.all(8), - child: CircleAvatar( - backgroundColor: theme.colorTheme.accentBlue, - maxRadius: 12, - child: StreamSvgIcon.check(color: theme.colorTheme.white), - ), - ), - failed: (_) => Padding( - padding: const EdgeInsets.all(8), - child: _buildButton( - icon: StreamSvgIcon.retry(color: theme.colorTheme.white), - fillColor: theme.colorTheme.overlayDark, - onPressed: () => channel.retryAttachmentUpload( - message.id, - attachmentId, - ), - ), - ), - ) ?? - IconButton( - icon: StreamSvgIcon.cloudDownload(color: theme.colorTheme.black), - visualDensity: VisualDensity.compact, - splashRadius: 16, - onPressed: () { - launchURL(context, attachment.assetUrl); - }, - ); + ), + ), + ); if (message.status == MessageSendingStatus.sent) { trailingWidget = IconButton( @@ -281,25 +275,17 @@ class FileAttachment extends AttachmentWidget { color: theme.colorTheme.grey, ); return attachment.uploadState.when( - preparing: () => UploadProgressIndicator( - uploaded: 0, - total: double.maxFinite.toInt(), - showBackground: false, - padding: EdgeInsets.zero, - textStyle: textStyle, - progressIndicatorColor: theme.colorTheme.accentBlue, - ), - inProgress: (sent, total) => UploadProgressIndicator( - uploaded: sent, - total: total, - showBackground: false, - padding: EdgeInsets.zero, - textStyle: textStyle, - progressIndicatorColor: theme.colorTheme.accentBlue, - ), - success: () => Text(fileSize(size), style: textStyle), - failed: (_) => Text('UPLOAD ERROR', style: textStyle), - ) ?? - Text(fileSize(size), style: textStyle); + preparing: () => Text(fileSize(size), style: textStyle), + inProgress: (sent, total) => UploadProgressIndicator( + uploaded: sent, + total: total, + showBackground: false, + padding: EdgeInsets.zero, + textStyle: textStyle, + progressIndicatorColor: theme.colorTheme.accentBlue, + ), + success: () => Text(fileSize(size), style: textStyle), + failed: (_) => Text('UPLOAD ERROR', style: textStyle), + ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 6d3ec5cf..db082c1c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1114,8 +1114,8 @@ class MessageInputState extends State { ); if (file.size! > widget.maxAttachmentSize) { - if (medium.type == AssetType.video) { - final mediaInfo = await (VideoService.compressVideo(file.path) + if (medium.type == AssetType.video && file.path != null) { + final mediaInfo = await (VideoService.compressVideo(file.path!) as FutureOr); if (mediaInfo.filesize! > widget.maxAttachmentSize) { @@ -1510,7 +1510,9 @@ class MessageInputState extends State { (e) => ClipRRect( borderRadius: BorderRadius.circular(10), child: FileAttachment( - message: Message(), // dummy message + message: Message( + status: MessageSendingStatus.sending, + ), // dummy message attachment: e, size: Size( MediaQuery.of(context).size.width * 0.65, @@ -1898,12 +1900,13 @@ class MessageInputState extends State { final attachment = Attachment( file: file, type: attachmentType, + uploadState: const UploadState.preparing(), extraData: extraDataMap, ); if (file.size! > widget.maxAttachmentSize) { - if (attachmentType == 'Video') { - final mediaInfo = await (VideoService.compressVideo(file.path) + if (attachmentType == 'video' && file.path != null) { + final mediaInfo = await (VideoService.compressVideo(file.path!) as FutureOr); if (mediaInfo.filesize! > widget.maxAttachmentSize) { diff --git a/packages/stream_chat_flutter/lib/src/video_service.dart b/packages/stream_chat_flutter/lib/src/video_service.dart index ba4761a2..290dd54b 100644 --- a/packages/stream_chat_flutter/lib/src/video_service.dart +++ b/packages/stream_chat_flutter/lib/src/video_service.dart @@ -28,9 +28,9 @@ class IVideoService { /// ); /// debugPrint(info.toJson()); /// ``` - Future compressVideo(String? path) async => _lock.synchronized( + Future compressVideo(String path) async => _lock.synchronized( () => VideoCompress.compressVideo( - path!, + path, ), ); From 3d2210233f8a0d47b5d99da345a2cf1b56c276b7 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 20 May 2021 16:56:45 +0200 Subject: [PATCH 288/599] update comment --- packages/stream_chat_flutter/lib/src/message_input.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index db082c1c..0d2070e7 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -155,7 +155,8 @@ class MessageInput extends StatefulWidget { final Message? editMessage; /// Max attachment size in bytes - /// If you're using our default CDN do not set it + /// Defaults to 20 MB + /// do not set it if you're using our default CDN final int maxAttachmentSize; /// Message to start with From 02037b684ce0cafb7116c0ee66e87a6edf3f3cfb Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 21 May 2021 09:40:03 +0200 Subject: [PATCH 289/599] fix analyze --- packages/stream_chat_flutter/lib/src/image_footer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/image_footer.dart index cc79192b..0a6571cc 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/image_footer.dart @@ -76,7 +76,7 @@ class _ImageFooterState extends State { @override Widget build(BuildContext context) { - final showShareButton = !kIsWeb; + const showShareButton = !kIsWeb; final mediaQueryData = MediaQuery.of(context); final chatThemeData = StreamChatTheme.of(context); return SizedBox.fromSize( From bdc19a9d5975b60a039147f6c61053396f290ed8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 21 May 2021 10:41:50 +0200 Subject: [PATCH 290/599] make timeoutOrExpirationDate optional --- packages/stream_chat/lib/src/client.dart | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 05f2559b..6e7caa9e 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1361,12 +1361,13 @@ class StreamChatClient { /// [timeoutOrExpirationDate] can either be a [DateTime] or a value in seconds /// to be added to [DateTime.now] Future pinMessage( - Message message, - Object timeoutOrExpirationDate, - ) { + Message message, [ + Object? timeoutOrExpirationDate, + ]) { assert(() { if (timeoutOrExpirationDate is! DateTime && - timeoutOrExpirationDate is! num) { + timeoutOrExpirationDate is! num && + timeoutOrExpirationDate != null) { throw ArgumentError('Invalid timeout or Expiration date'); } return true; @@ -1383,13 +1384,19 @@ class StreamChatClient { .toUtc(); } return updateMessage( - message.copyWith(pinned: true, pinExpires: pinExpires), + message.copyWith( + pinned: true, + pinExpires: pinExpires, + ), ); } /// Unpins provided message - Future unpinMessage(Message message) => - updateMessage(message.copyWith(pinned: false)); + Future unpinMessage(Message message) => updateMessage( + message.copyWith( + pinned: false, + ), + ); } /// The class that handles the state of the channel listening to the events From 32cb1e77001d038911f538a4181562d08e44c0da Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 21 May 2021 10:45:59 +0200 Subject: [PATCH 291/599] replicate in channel --- packages/stream_chat/lib/src/api/channel.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 69d967fe..384ca105 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -507,9 +507,9 @@ class Channel { /// Pins provided message Future pinMessage( - Message message, + Message message, [ Object? timeoutOrExpirationDate, - ) { + ]) { assert(() { if (timeoutOrExpirationDate is! DateTime && timeoutOrExpirationDate != null && From 9e200313202dfd7fdcc5414f451073562ef4f269 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 21 May 2021 14:18:21 +0530 Subject: [PATCH 292/599] fix: Channel list builder perf fix --- .../stream_chat_flutter/lib/src/channel_list_view.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 766fe4ba..44962dd4 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -450,12 +450,13 @@ class _ChannelListViewState extends State { Widget _listItemBuilder(BuildContext context, int i, List channels) { final channelsBloc = ChannelsBloc.of(context); + final onTap = _getChannelTap(context); + final chatThemeData = StreamChatTheme.of(context); + final backgroundColor = chatThemeData.colorTheme.whiteSmoke; + if (i < channels.length) { final channel = channels[i]; - final onTap = _getChannelTap(context); - final chatThemeData = StreamChatTheme.of(context); - final backgroundColor = chatThemeData.colorTheme.whiteSmoke; return StreamChannel( key: ValueKey('CHANNEL-${channel.id}'), channel: channel, From a543cf325a11fcfd8315ecbeaa211fecc37e1bef Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 21 May 2021 10:50:35 +0200 Subject: [PATCH 293/599] fix assert error message --- packages/stream_chat/lib/src/api/channel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 384ca105..657126c1 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -517,7 +517,7 @@ class Channel { throw ArgumentError('Invalid timeout or Expiration date'); } return true; - }(), 'Check for invalid token or expiration date'); + }(), 'Check whether time out is valid'); DateTime? pinExpires; if (timeoutOrExpirationDate is DateTime) { From 5b39893f768970f68ad80c47e64e254851d99982 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 21 May 2021 11:36:08 +0200 Subject: [PATCH 294/599] add test --- .../stream_chat/test/src/client_test.dart | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index b2a5bbac..53792764 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -1112,6 +1112,28 @@ void main() { data: {'message': anything})).called(1); }); + test('should complete successfully with a null value', () async { + final message = Message(text: 'Hello'); + + when( + () => mockDio.post( + '/messages/${message.id}', + data: anything, + ), + ).thenAnswer( + (_) async => Response( + data: jsonEncode({'message': message}), + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); + + await client.pinMessage(message); + + verify(() => mockDio.post('/messages/${message.id}', + data: {'message': anything})).called(1); + }); + test('should unpin message successfully', () async { final message = Message(text: 'Hello'); From d764de9f689d59c6c71320e4f7d02e57b4bfcd1f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 21 May 2021 11:37:03 +0200 Subject: [PATCH 295/599] add test --- .../test/src/api/channel_test.dart | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index c3409b7e..f92b05bb 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -449,6 +449,51 @@ void main() { .called(1); }); + test('should be pinned successfully with null timeout', () async { + final mockDio = MockDio(); + + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + + final client = StreamChatClient( + 'api-key', + httpClient: mockDio, + tokenProvider: (_) async => '', + ); + final channelClient = client.channel('messaging', id: 'testid'); + final message = Message(text: 'Hello', id: 'test'); + + when(() => mockDio.post( + any(), + data: any(named: 'data'), + )).thenAnswer((_) async => Response( + data: jsonEncode(ChannelState()), + statusCode: 200, + requestOptions: FakeRequestOptions(), + )); + + await channelClient.watch(); + + when( + () => mockDio.post( + '/messages/${message.id}', + data: anything, + ), + ).thenAnswer( + (_) async => Response( + data: jsonEncode({'message': message}), + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); + + await channelClient.pinMessage(message); + + verify(() => + mockDio.post('/messages/${message.id}', data: anything)) + .called(1); + }); + test('should be unpinned successfully', () async { final mockDio = MockDio(); From 847e5120af9d4b1ef1c80c6cc6655d0f8085dfff Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 21 May 2021 15:04:25 +0200 Subject: [PATCH 296/599] minor fixes --- .../lib/src/message_list_view.dart | 1 + .../lib/src/message_widget.dart | 337 +++++++++--------- 2 files changed, 166 insertions(+), 172 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index e07d9358..a17dccf0 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -432,6 +432,7 @@ class _MessageListViewState extends State { physics: widget.scrollPhysics, itemScrollController: _scrollController, reverse: true, + addAutomaticKeepAlives: false, itemCount: messages.length + 2 + (_isThreadConversation ? 1 : 0), separatorBuilder: (context, i) { diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 97e061ae..b18a0532 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -449,189 +449,182 @@ class _MessageWidgetState extends State final leftPadding = widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; - return Material( - type: MaterialType.transparency, - child: Portal( - child: InkWell( - onTap: () { - widget.onMessageTap!(widget.message); - }, - onLongPress: widget.message.isDeleted && !isFailedState - ? null - : () => onLongPress(context), - child: Padding( - padding: widget.padding ?? const EdgeInsets.all(8), - child: FractionallySizedBox( - alignment: - widget.reverse ? Alignment.centerRight : Alignment.centerLeft, - widthFactor: 0.78, - child: Column( - crossAxisAlignment: widget.reverse - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Stack( - clipBehavior: Clip.none, - alignment: widget.reverse - ? AlignmentDirectional.bottomEnd - : AlignmentDirectional.bottomStart, - children: [ - Column( - crossAxisAlignment: widget.reverse - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.showUserAvatar == DisplayWidget.show && - widget.message.user != null) ...[ - _buildUserAvatar(), - const SizedBox(width: 4), - ], - if (widget.showUserAvatar == DisplayWidget.hide) - SizedBox(width: avatarWidth + 4), - Flexible( - child: PortalEntry( - portal: Container( - transform: Matrix4.translationValues( - widget.reverse ? 12 : -12, 0, 0), - constraints: const BoxConstraints( - maxWidth: 22 * 6.0), - child: _buildReactionIndicator(context), - ), - portalAnchor: - Alignment(widget.reverse ? 1 : -1, -1), - childAnchor: - Alignment(widget.reverse ? -1 : 1, -1), - child: Stack( - clipBehavior: Clip.none, - children: [ - Padding( - padding: widget.showReactions - ? EdgeInsets.only( - top: widget - .message - .reactionCounts - ?.isNotEmpty == - true - ? 18 - : 0, - ) - : EdgeInsets.zero, - child: (widget.message.isDeleted && - !isFailedState) - ? 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), - child: DeletedMessage( - borderRadiusGeometry: widget - .borderRadiusGeometry, - borderSide: widget.borderSide, - shape: widget.shape, - messageTheme: - widget.messageTheme, - ), - ) - : Card( - clipBehavior: Clip.antiAlias, - elevation: 0, - margin: EdgeInsets.symmetric( - horizontal: (isFailedState - ? 15.0 - : 0.0) + + return Portal( + child: GestureDetector( + onTap: () { + widget.onMessageTap!(widget.message); + }, + onLongPress: widget.message.isDeleted && !isFailedState + ? null + : () => onLongPress(context), + child: Padding( + padding: widget.padding ?? const EdgeInsets.all(8), + child: FractionallySizedBox( + alignment: + widget.reverse ? Alignment.centerRight : Alignment.centerLeft, + widthFactor: 0.78, + child: Column( + crossAxisAlignment: widget.reverse + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + clipBehavior: Clip.none, + alignment: widget.reverse + ? AlignmentDirectional.bottomEnd + : AlignmentDirectional.bottomStart, + children: [ + Column( + crossAxisAlignment: widget.reverse + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.showUserAvatar == DisplayWidget.show && + widget.message.user != null) ...[ + _buildUserAvatar(), + const SizedBox(width: 4), + ], + if (widget.showUserAvatar == DisplayWidget.hide) + SizedBox(width: avatarWidth + 4), + Flexible( + child: PortalEntry( + portal: Container( + transform: Matrix4.translationValues( + widget.reverse ? 12 : -12, 0, 0), + constraints: + const BoxConstraints(maxWidth: 22 * 6.0), + child: _buildReactionIndicator(context), + ), + portalAnchor: + Alignment(widget.reverse ? 1 : -1, -1), + childAnchor: + Alignment(widget.reverse ? -1 : 1, -1), + child: Stack( + clipBehavior: Clip.none, + children: [ + Padding( + padding: widget.showReactions + ? EdgeInsets.only( + top: widget.message.reactionCounts + ?.isNotEmpty == + true + ? 18 + : 0, + ) + : EdgeInsets.zero, + child: (widget.message.isDeleted && + !isFailedState) + ? Container( + // ignore: lines_longer_than_80_chars + margin: EdgeInsets.symmetric( + horizontal: // ignore: lines_longer_than_80_chars - (widget.showUserAvatar == + widget.showUserAvatar == + // ignore: lines_longer_than_80_chars DisplayWidget.gone ? 0 : 4.0), - ), - shape: widget.shape ?? - RoundedRectangleBorder( - side: widget.borderSide ?? - BorderSide( - color: widget - // ignore: lines_longer_than_80_chars - .messageTheme - // ignore: lines_longer_than_80_chars - .messageBorderColor ?? - Colors.grey, - ), - borderRadius: widget - // ignore: lines_longer_than_80_chars - .borderRadiusGeometry ?? - BorderRadius.zero, - ), - color: _getBackgroundColor(), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.end, - mainAxisSize: - MainAxisSize.min, - children: [ - if (hasQuotedMessage) - _buildQuotedMessage(), - if (hasNonUrlAttachments) - _parseAttachments(), - if (!isGiphy) - _buildTextBubble(), - ], - ), + child: DeletedMessage( + borderRadiusGeometry: + widget.borderRadiusGeometry, + borderSide: widget.borderSide, + shape: widget.shape, + messageTheme: + widget.messageTheme, + ), + ) + : Card( + clipBehavior: Clip.antiAlias, + elevation: 0, + margin: EdgeInsets.symmetric( + horizontal: (isFailedState + ? 15.0 + : 0.0) + + // ignore: lines_longer_than_80_chars + (widget.showUserAvatar == + DisplayWidget.gone + ? 0 + : 4.0), + ), + shape: widget.shape ?? + RoundedRectangleBorder( + side: widget.borderSide ?? + BorderSide( + color: widget + // ignore: lines_longer_than_80_chars + .messageTheme + // ignore: lines_longer_than_80_chars + .messageBorderColor ?? + Colors.grey, + ), + borderRadius: widget + // ignore: lines_longer_than_80_chars + .borderRadiusGeometry ?? + BorderRadius.zero, + ), + color: _getBackgroundColor(), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (hasQuotedMessage) + _buildQuotedMessage(), + if (hasNonUrlAttachments) + _parseAttachments(), + if (!isGiphy) + _buildTextBubble(), + ], ), - ), - if (widget.showReactionPickerIndicator) - Positioned( - right: widget.reverse ? null : 4, - left: widget.reverse ? 4 : null, - top: -8, - child: CustomPaint( - painter: ReactionBubblePainter( - StreamChatTheme.of(context) - .colorTheme - .white, - Colors.transparent, - Colors.transparent, - tailCirclesSpace: 1, ), + ), + if (widget.showReactionPickerIndicator) + Positioned( + right: widget.reverse ? null : 4, + left: widget.reverse ? 4 : null, + top: -8, + child: CustomPaint( + painter: ReactionBubblePainter( + StreamChatTheme.of(context) + .colorTheme + .white, + Colors.transparent, + Colors.transparent, + tailCirclesSpace: 1, ), ), - ], - ), + ), + ], ), ), - ], - ), - if (showBottomRow) - SizedBox(height: context.textScaleFactor * 18.0), - ], + ), + ], + ), + if (showBottomRow) + SizedBox(height: context.textScaleFactor * 18.0), + ], + ), + if (showBottomRow) + Padding( + padding: EdgeInsets.only(left: leftPadding), + child: _bottomRow, ), - if (showBottomRow) - Padding( - padding: EdgeInsets.only(left: leftPadding), - child: _bottomRow, - ), - if (isFailedState) - Positioned( - left: widget.reverse ? 0 : null, - right: widget.reverse ? null : 0, - bottom: showBottomRow ? 18 : -2, - child: StreamSvgIcon.error(size: 20), - ), - ], - ), - ], - ), + if (isFailedState) + Positioned( + left: widget.reverse ? 0 : null, + right: widget.reverse ? null : 0, + bottom: showBottomRow ? 18 : -2, + child: StreamSvgIcon.error(size: 20), + ), + ], + ), + ], ), ), ), From 69fdeb813d983bbb83c09ec7f8ae4dc93bf3cd1a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 21 May 2021 15:08:58 +0200 Subject: [PATCH 297/599] fix channel header text theme --- .../lib/src/channel_header.dart | 1 + .../lib/src/channel_name.dart | 7 ++-- .../lib/src/stream_chat_theme.dart | 34 ++++++++++--------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index d06c128c..7773b36f 100644 --- a/packages/stream_chat_flutter/lib/src/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_header.dart @@ -137,6 +137,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { showMessage: showConnectionStateTile ? showStatus : false, message: statusString, child: AppBar( + textTheme: Theme.of(context).textTheme, brightness: Theme.of(context).brightness, elevation: 1, leading: leadingWidget, diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart index 2cd804c9..5db3341a 100644 --- a/packages/stream_chat_flutter/lib/src/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel_name.dart @@ -29,8 +29,11 @@ class ChannelName extends StatelessWidget { return StreamBuilder>( stream: channel.extraDataStream, initialData: channel.extraData, - builder: (context, snapshot) => - _buildName(snapshot.data!, channel.state?.members, client), + builder: (context, snapshot) => _buildName( + snapshot.data!, + channel.state?.members, + client, + ), ); } diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index 97363df7..25c2ca32 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -230,22 +230,23 @@ class StreamChatThemeData { ), ), channelPreviewTheme: ChannelPreviewTheme( - unreadCounterColor: colorTheme.accentRed, - avatarTheme: AvatarTheme( - borderRadius: BorderRadius.circular(20), - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), + unreadCounterColor: colorTheme.accentRed, + avatarTheme: AvatarTheme( + borderRadius: BorderRadius.circular(20), + constraints: const BoxConstraints.tightFor( + height: 40, + width: 40, ), - title: textTheme.bodyBold, - subtitle: textTheme.footnote.copyWith( - color: const Color(0xff7A7A7A), - ), - lastMessageAt: textTheme.footnote.copyWith( - color: colorTheme.black.withOpacity(.5), - ), - indicatorIconSize: 16), + ), + title: textTheme.bodyBold, + subtitle: textTheme.footnote.copyWith( + color: const Color(0xff7A7A7A), + ), + lastMessageAt: textTheme.footnote.copyWith( + color: colorTheme.black.withOpacity(.5), + ), + indicatorIconSize: 16, + ), channelListHeaderTheme: ChannelListHeaderTheme( avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), @@ -1186,7 +1187,8 @@ class MessageInputTheme { actionButtonIdleColor: other.actionButtonIdleColor, sendButtonColor: other.sendButtonColor, sendButtonIdleColor: other.sendButtonIdleColor, - inputTextStyle: other.inputTextStyle, + inputTextStyle: + inputTextStyle?.merge(other.inputTextStyle) ?? other.inputTextStyle, inputDecoration: inputDecoration?.merge(other.inputDecoration) ?? other.inputDecoration, activeBorderGradient: other.activeBorderGradient, From 05402ee3384044cfe5ca8afff64a798dc2be8b83 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 21 May 2021 15:10:07 +0200 Subject: [PATCH 298/599] apply texttheme to rest of headers --- packages/stream_chat_flutter/lib/src/channel_list_header.dart | 1 + packages/stream_chat_flutter/lib/src/image_header.dart | 1 + packages/stream_chat_flutter/lib/src/thread_header.dart | 1 + 3 files changed, 3 insertions(+) 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 325736f1..4a8f78c3 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -120,6 +120,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { showMessage: showConnectionStateTile ? showStatus : false, message: statusString, child: AppBar( + textTheme: Theme.of(context).textTheme, brightness: Theme.of(context).brightness, elevation: 1, backgroundColor: chatThemeData.channelListHeaderTheme.color, diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/image_header.dart index 095d7534..434a73c5 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/image_header.dart @@ -53,6 +53,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { Widget build(BuildContext context) { final chatThemeData = StreamChatTheme.of(context); return AppBar( + textTheme: Theme.of(context).textTheme, brightness: Theme.of(context).brightness, elevation: 1, leading: showBackButton diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart index 347fc59d..d16aa530 100644 --- a/packages/stream_chat_flutter/lib/src/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/thread_header.dart @@ -101,6 +101,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { final chatThemeData = StreamChatTheme.of(context); return AppBar( automaticallyImplyLeading: false, + textTheme: Theme.of(context).textTheme, brightness: Theme.of(context).brightness, elevation: 1, leading: leading ?? From c151a86fc746fa6282f56e3a869058f05d803e83 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 21 May 2021 15:23:20 +0200 Subject: [PATCH 299/599] fix typo --- packages/stream_chat/lib/src/api/channel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 657126c1..2b6bc0a9 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -517,7 +517,7 @@ class Channel { throw ArgumentError('Invalid timeout or Expiration date'); } return true; - }(), 'Check whether time out is valid'); + }(), 'Check whether timeout is valid'); DateTime? pinExpires; if (timeoutOrExpirationDate is DateTime) { From 0e1e9a12f97cf1a8c5241711780cdfddd1e6ced7 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 26 May 2021 16:39:48 +0530 Subject: [PATCH 300/599] feat: Added pin message functionality --- packages/stream_chat/lib/src/client.dart | 6 ++- .../lib/src/message_actions_modal.dart | 49 ++++++++++++++++++ .../lib/src/message_reactions_modal.dart | 1 + .../lib/src/message_widget.dart | 50 ++++++++++++++++++- .../lib/src/stream_svg_icon.dart | 12 +++++ .../stream_chat_flutter/lib/svgs/icon_pin.svg | 3 ++ 6 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/svgs/icon_pin.svg diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 6e7caa9e..98b05634 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -315,7 +315,7 @@ class StreamChatClient { await connectUser(User(id: userId), newToken); try { - handler.resolve( + return handler.resolve( await httpClient.request( err.requestOptions.path, cancelToken: err.requestOptions.cancelToken, @@ -343,10 +343,12 @@ class StreamChatClient { ), ); } on DioError { - handler.reject(err); + return handler.reject(err); } } } + + return handler.next(err); } LogHandlerFunction _getDefaultLogHandler() { 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 5336ed48..7f2a2430 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -27,6 +27,8 @@ class MessageActionsModal extends StatefulWidget { this.showResendMessage = true, this.showThreadReplyMessage = true, this.showFlagButton = true, + this.showPinButton = true, + this.showPinHighlight = false, this.showUserAvatar = DisplayWidget.show, this.editMessageInputBuilder, this.messageShape, @@ -79,6 +81,12 @@ class MessageActionsModal extends StatefulWidget { /// Flag for showing flag action final bool showFlagButton; + /// Flag for showing pin action + final bool showPinButton; + + /// Display Pin Highlight + final bool showPinHighlight; + /// Flag for reversing message final bool reverse; @@ -222,6 +230,7 @@ class _MessageActionsModalState extends State { showSendingIndicator: false, shape: widget.messageShape, attachmentShape: widget.attachmentShape, + showPinHighlight: false, ), ), const SizedBox(height: 8), @@ -258,6 +267,8 @@ class _MessageActionsModalState extends State { _buildCopyButton(context), if (widget.showFlagButton) _buildFlagButton(context), + if (widget.showPinButton) + _buildPinButton(context), if (widget.showDeleteMessage) _buildDeleteButton(context), ...widget.customActions @@ -359,6 +370,21 @@ class _MessageActionsModalState extends State { } } + void _togglePin() async { + final channel = StreamChannel.of(context).channel; + + try { + if (!widget.message.pinned) { + await channel.pinMessage(widget.message); + } else { + await channel.unpinMessage(widget.message); + } + Navigator.pop(context); + } catch (e) { + _showErrorAlert(); + } + } + void _showDeleteDialog() async { setState(() { _showActions = false; @@ -451,6 +477,29 @@ class _MessageActionsModalState extends State { ); } + Widget _buildPinButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: _togglePin, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.pin( + color: streamChatThemeData.primaryIconTheme.color, + size: 24, + ), + const SizedBox(width: 16), + Text( + '${widget.message.pinned ? 'Unpin from' : 'Pin to'} Conversation', + style: streamChatThemeData.textTheme.body, + ), + ], + ), + ), + ); + } + Widget _buildDeleteButton(BuildContext context) { final isDeleteFailed = widget.message.status == MessageSendingStatus.failed_delete; 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 5f3bc5a4..23522508 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -157,6 +157,7 @@ class MessageReactionsModal extends StatelessWidget { ), showReactionPickerIndicator: showReactions && (message.status == MessageSendingStatus.sent), + showPinHighlight: false, ), ), if (message.latestReactions?.isNotEmpty == true) ...[ diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 97e061ae..3c4aec14 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -83,6 +83,8 @@ class MessageWidget extends StatefulWidget { this.showResendMessage = true, this.showCopyMessage = true, this.showFlagButton = true, + this.showPinButton = true, + this.showPinHighlight = true, this.onUserAvatarTap, this.onLinkTap, this.onMessageActions, @@ -367,6 +369,12 @@ class MessageWidget extends StatefulWidget { /// Show flag action final bool showFlagButton; + /// Show flag action + final bool showPinButton; + + /// Display Pin Highlight + final bool showPinHighlight; + /// Builder for respective attachment types final Map attachmentBuilders; @@ -450,7 +458,12 @@ class _MessageWidgetState extends State widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; return Material( - type: MaterialType.transparency, + type: widget.message.pinned && widget.showPinHighlight + ? MaterialType.card + : MaterialType.transparency, + color: widget.message.pinned && widget.showPinHighlight + ? StreamChatTheme.of(context).colorTheme.highlight + : null, child: Portal( child: InkWell( onTap: () { @@ -483,6 +496,10 @@ class _MessageWidgetState extends State : CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ + if (widget.message.pinned && + widget.message.pinnedBy != null && + widget.showPinHighlight) + _buildPinnedMessage(widget.message), Row( crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, @@ -918,6 +935,7 @@ class _MessageWidgetState extends State !isFailedState && widget.onThreadTap != null, showFlagButton: widget.showFlagButton, + showPinButton: widget.showPinButton, customActions: widget.customActions, ), )); @@ -1113,8 +1131,38 @@ class _MessageWidgetState extends State ); } + Widget _buildPinnedMessage(Message message) { + final pinnedBy = message.pinnedBy; + final pinnedByMe = StreamChat.of(context).user!.id == pinnedBy!.id; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + StreamSvgIcon.pin( + size: 16, + ), + const SizedBox( + width: 4, + ), + Text( + 'Pinned by ${pinnedByMe ? 'You' : pinnedBy.name}', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.grey, + fontSize: 13, + fontWeight: FontWeight.w400, + ), + ) + ], + ), + ); + } + bool get isOnlyEmoji => widget.message.text!.isOnlyEmoji; + bool get isPinned => widget.message.pinned; + Color? _getBackgroundColor() { if (hasQuotedMessage) { return widget.messageTheme.messageBackgroundColor; diff --git a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart index 78dfba2c..ae344a89 100644 --- a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart +++ b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart @@ -889,6 +889,18 @@ class StreamSvgIcon extends StatelessWidget { height: size, ); + /// [StreamSvgIcon] type + factory StreamSvgIcon.pin({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'icon_pin.svg', + color: color, + width: size, + height: size, + ); + /// Name of icon asset final String? assetName; diff --git a/packages/stream_chat_flutter/lib/svgs/icon_pin.svg b/packages/stream_chat_flutter/lib/svgs/icon_pin.svg new file mode 100644 index 00000000..0f494729 --- /dev/null +++ b/packages/stream_chat_flutter/lib/svgs/icon_pin.svg @@ -0,0 +1,3 @@ + + + From d07b3776ecc5037904c4238d2faf8f317c6c76db Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 27 May 2021 09:49:11 +0200 Subject: [PATCH 301/599] fix use of reduce --- .../lib/src/message_list_view.dart | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index e07d9358..db14924b 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -570,9 +570,9 @@ class _MessageListViewState extends State { return const SizedBox(); } - var index = _getTopElement(values).index; + var index = _getTopElement(values)?.index; - if (index > messages.length) { + if (index == null || index > messages.length) { return const SizedBox(); } @@ -598,10 +598,17 @@ class _MessageListViewState extends State { StreamChannelState? channel, QueryDirection direction) => _messageListController.paginateData!(direction: direction); - ItemPosition _getTopElement(Iterable values) => values - .where((ItemPosition position) => position.itemLeadingEdge < 0.9) - .reduce((ItemPosition max, ItemPosition position) => - position.itemLeadingEdge > max.itemLeadingEdge ? position : max); + ItemPosition? _getTopElement(Iterable values) { + final inView = + values.where((ItemPosition position) => position.itemLeadingEdge < 0.9); + + if (inView.isEmpty) { + return null; + } + + return inView.reduce((ItemPosition max, ItemPosition position) => + position.itemLeadingEdge > max.itemLeadingEdge ? position : max); + } Widget _buildScrollToBottom() => StreamBuilder>( stream: Rx.combineLatest2( From 259f717ec824dace08a7ffc955cec5cd169ca867 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 27 May 2021 14:03:46 +0530 Subject: [PATCH 302/599] feat: Added pin permissions list --- .../lib/src/message_list_view.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index e07d9358..03c64606 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -158,6 +158,7 @@ class MessageListView extends StatefulWidget { this.onAttachmentTap, this.textBuilder, this.onLinkTap, + this.pinPermissions = const [], }) : super(key: key); /// Function used to build a custom message widget @@ -264,6 +265,9 @@ class MessageListView extends StatefulWidget { /// Callback for when link is tapped final void Function(String link)? onLinkTap; + /// A List of user types that have permission to pin messages + final List pinPermissions; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -800,6 +804,10 @@ class _MessageListViewState extends State { ) { final isMyMessage = message.user!.id == StreamChat.of(context).user!.id; final isOnlyEmoji = message.text!.isOnlyEmoji; + final currentUser = StreamChat.of(context).user; + final members = StreamChannel.of(context).channel.state?.members ?? []; + final currentUserMember = + members.firstWhere((e) => e.user!.id == currentUser!.id); final chatThemeData = StreamChatTheme.of(context); return MessageWidget( @@ -853,6 +861,7 @@ class _MessageListViewState extends State { textBuilder: widget.textBuilder as Widget Function(BuildContext, Message)?, onLinkTap: widget.onLinkTap, + showPinButton: widget.pinPermissions.contains(currentUserMember.role), ); } @@ -939,6 +948,11 @@ class _MessageListViewState extends State { ? BorderSide.none : null; + final currentUser = StreamChat.of(context).user; + final members = StreamChannel.of(context).channel.state?.members ?? []; + final currentUserMember = + members.firstWhere((e) => e.user!.id == currentUser!.id); + final chatThemeData = StreamChatTheme.of(context); Widget child = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), @@ -1052,6 +1066,7 @@ class _MessageListViewState extends State { textBuilder: widget.textBuilder as Widget Function(BuildContext, Message)?, onLinkTap: widget.onLinkTap, + showPinButton: widget.pinPermissions.contains(currentUserMember.role), ); if (!message.isDeleted && From 4f5958a067e33129eddc46ae1caea635b6bdbb0a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 27 May 2021 11:04:30 +0200 Subject: [PATCH 303/599] implement connectivity plus --- .../stream_chat/lib/src/api/websocket.dart | 2 +- .../stream_chat_flutter/example/lib/main.dart | 1 - .../lib/src/stream_chat_core.dart | 89 ++++++--- .../stream_chat_flutter_core/pubspec.yaml | 1 + .../test/stream_chat_core_test.dart | 169 ++++++++++++++++++ 5 files changed, 236 insertions(+), 26 deletions(-) diff --git a/packages/stream_chat/lib/src/api/websocket.dart b/packages/stream_chat/lib/src/api/websocket.dart index b5bbbb69..657aa125 100644 --- a/packages/stream_chat/lib/src/api/websocket.dart +++ b/packages/stream_chat/lib/src/api/websocket.dart @@ -100,7 +100,7 @@ class WebSocket { /// connection unhealthy final int reconnectionMonitorTimeout; - final _connectionStatusController = + final BehaviorSubject _connectionStatusController = BehaviorSubject.seeded(ConnectionStatus.disconnected); set _connectionStatus(ConnectionStatus status) => diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 1f3b51d2..d3522886 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -4,7 +4,6 @@ import 'package:stream_chat_persistence/stream_chat_persistence.dart'; final chatPersistentClient = StreamChatPersistenceClient( logLevel: Level.INFO, - connectionMode: ConnectionMode.background, ); void main() async { diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index 89d4853a..e65eb4e2 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; @@ -43,6 +44,7 @@ class StreamChatCore extends StatefulWidget { required this.child, this.onBackgroundEventReceived, this.backgroundKeepAlive = const Duration(minutes: 1), + this.connectivityStream, }) : super(key: key); /// Instance of Stream Chat Client containing information about the current @@ -61,6 +63,11 @@ class StreamChatCore extends StatefulWidget { /// upon the [Event.type] final EventHandler? onBackgroundEventReceived; + /// Stream of connectivity result + /// Visible for testing + @visibleForTesting + final Stream? connectivityStream; + @override StreamChatCoreState createState() => StreamChatCoreState(); @@ -96,50 +103,84 @@ class StreamChatCoreState extends State /// The current user as a stream Stream get userStream => client.state.userStream; + late final StreamSubscription _connectivitySubscription; + + var _isInForeground = true; + var _isConnectionAvailable = true; + @override void initState() { super.initState(); WidgetsBinding.instance?.addObserver(this); + _connectivitySubscription = + (widget.connectivityStream ?? Connectivity().onConnectivityChanged) + .listen((ConnectivityResult result) async { + _isConnectionAvailable = result != ConnectivityResult.none; + if (!_isInForeground) { + return; + } + if (_isConnectionAvailable) { + if (client.wsConnectionStatus == ConnectionStatus.disconnected) { + await client.connect(); + } + } else { + if (client.wsConnectionStatus == ConnectionStatus.connected) { + await client.disconnect(); + } + } + }); } StreamSubscription? _eventSubscription; @override void didChangeAppLifecycleState(AppLifecycleState state) { + _isInForeground = state == AppLifecycleState.resumed; if (user != null) { - if (state == AppLifecycleState.paused) { - if (widget.onBackgroundEventReceived == null) { - client.disconnect(); - return; - } - _eventSubscription = client.on().listen( - widget.onBackgroundEventReceived, - ); - - void onTimerComplete() { - _eventSubscription?.cancel(); - client.disconnect(); - } - - _disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete); - } else if (state == AppLifecycleState.resumed) { - if (_disconnectTimer?.isActive == true) { - _eventSubscription?.cancel(); - _disconnectTimer?.cancel(); - } else { - if (client.wsConnectionStatus == ConnectionStatus.disconnected) { - client.connect(); - } - } + if (!_isInForeground) { + _onBackground(); + } else { + _onForeground(); } } } + void _onForeground() { + if (_disconnectTimer?.isActive == true) { + _eventSubscription?.cancel(); + _disconnectTimer?.cancel(); + } else if (client.wsConnectionStatus == ConnectionStatus.disconnected && + _isConnectionAvailable) { + client.connect(); + } + } + + void _onBackground() { + if (widget.onBackgroundEventReceived == null) { + if (client.wsConnectionStatus != ConnectionStatus.disconnected) { + client.disconnect(); + } + return; + } + _eventSubscription = client.on().listen( + widget.onBackgroundEventReceived, + ); + + void onTimerComplete() { + _eventSubscription?.cancel(); + client.disconnect(); + } + + _disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete); + return; + } + @override void dispose() { WidgetsBinding.instance?.removeObserver(this); _eventSubscription?.cancel(); _disconnectTimer?.cancel(); + _connectivitySubscription.cancel(); super.dispose(); } } diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index e8d1014d..7bae31f9 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: meta: ^1.3.0 rxdart: ^0.27.0 stream_chat: ^2.0.0-nullsafety.2 + connectivity_plus: ^1.0.1 dev_dependencies: fake_async: ^1.2.0 diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index bc5324b8..936e03f0 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -1,8 +1,10 @@ import 'dart:async'; +import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:rxdart/rxdart.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'mocks.dart'; @@ -100,6 +102,7 @@ void main() { child: Offstage(key: childKey), onBackgroundEventReceived: mockOnBackgroundEventReceived, backgroundKeepAlive: backgroundKeepAlive, + connectivityStream: Stream.value(ConnectivityResult.mobile), ); await tester.pumpWidget(streamChatCore); @@ -191,6 +194,7 @@ void main() { key: streamChatCoreKey, client: mockClient, child: Offstage(key: childKey), + connectivityStream: Stream.value(ConnectivityResult.mobile), ); await tester.pumpWidget(streamChatCore); @@ -222,6 +226,51 @@ void main() { }, ); + testWidgets( + 'didChangeAppLifecycleState should not call client.connect() ' + 'if connection is not available in case the ' + 'widget lifestyle changes to AppLifecycleState.resume', + (tester) async { + await tester.runAsync(() async { + final mockClient = MockClient(); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), + connectivityStream: Stream.value(ConnectivityResult.none), + ); + + await tester.pumpWidget(streamChatCore); + + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + + final event = Event(); + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(() => mockClient.connect()).thenAnswer((_) async => event); + when(mockClient.disconnect).thenAnswer((_) async => null); + when(() => mockClient.wsConnectionStatus) + .thenReturn(ConnectionStatus.disconnected); + + final streamChatCoreState = tester.state( + find.byKey(streamChatCoreKey), + ); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.paused); + + await Future.delayed(const Duration(seconds: 1)); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.resumed); + + verifyNever(() => mockClient.connect()); + }); + }, + ); + testWidgets( 'streamChatCoreState.userStream should emit all the user events ' 'provided by client', @@ -264,4 +313,124 @@ void main() { }); }, ); + + testWidgets( + 'should call connect if in foreground and connection is back', + (tester) async { + await tester.runAsync(() async { + final mockClient = MockClient(); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final _connectivityController = + BehaviorSubject.seeded(ConnectivityResult.none); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), + connectivityStream: _connectivityController.stream, + ); + + await tester.pumpWidget(streamChatCore); + + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + + final event = Event(); + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(() => mockClient.connect()).thenAnswer((_) async => event); + when(mockClient.disconnect).thenAnswer((_) async => null); + when(() => mockClient.wsConnectionStatus) + .thenReturn(ConnectionStatus.disconnected); + + _connectivityController.add(ConnectivityResult.mobile); + + await Future.delayed(const Duration(seconds: 1)); + + verify(() => mockClient.connect()).called(1); + }); + }, + ); + + testWidgets( + 'should call disconnect if in foreground and connection goes away', + (tester) async { + await tester.runAsync(() async { + final mockClient = MockClient(); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final _connectivityController = + BehaviorSubject.seeded(ConnectivityResult.mobile); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), + connectivityStream: _connectivityController.stream, + ); + + await tester.pumpWidget(streamChatCore); + + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + + final event = Event(); + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(() => mockClient.connect()).thenAnswer((_) async => event); + when(mockClient.disconnect).thenAnswer((_) async => null); + when(() => mockClient.wsConnectionStatus) + .thenReturn(ConnectionStatus.connected); + + _connectivityController.add(ConnectivityResult.none); + + await Future.delayed(const Duration(seconds: 1)); + + verify(() => mockClient.disconnect()).called(1); + }); + }, + ); + + testWidgets( + 'should ignore connectivity in background', + (tester) async { + await tester.runAsync(() async { + final mockClient = MockClient(); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final _connectivityController = + BehaviorSubject.seeded(ConnectivityResult.none); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), + connectivityStream: _connectivityController.stream, + ); + + await tester.pumpWidget(streamChatCore); + + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + + final event = Event(); + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(() => mockClient.connect()).thenAnswer((_) async => event); + when(mockClient.disconnect).thenAnswer((_) async => null); + when(() => mockClient.wsConnectionStatus) + .thenReturn(ConnectionStatus.disconnected); + + final streamChatCoreState = tester.state( + find.byKey(streamChatCoreKey), + ); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.paused); + + await Future.delayed(const Duration(seconds: 1)); + + _connectivityController.add(ConnectivityResult.mobile); + + await Future.delayed(const Duration(seconds: 1)); + + verifyNever(() => mockClient.disconnect()); + }); + }, + ); } From 23fbfbeff9b831576d9652a540aef54feddf6a1a Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 27 May 2021 16:41:04 +0530 Subject: [PATCH 304/599] fix: Now caches message before update --- packages/stream_chat/lib/src/api/channel.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 2b6bc0a9..2f95be78 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -411,6 +411,8 @@ class Channel { /// Waits for a [_messageAttachmentsUploadCompleter] to complete /// before actually updating the message. Future updateMessage(Message message) async { + var currentMessage = state?.messages.firstWhere((e) => e.id == message.id); + // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. _messageAttachmentsUploadCompleter @@ -459,6 +461,10 @@ class Channel { } catch (error) { if (error is DioError && error.type != DioErrorType.response) { state?.retryQueue?.add([message]); + } else if (error is ApiError) { + if(currentMessage != null) { + state?.addMessage(currentMessage); + } } rethrow; } From edf0d84b317a065a939326de41b3432c4a63fd1d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 27 May 2021 18:57:30 +0530 Subject: [PATCH 305/599] fix: Corrected message padding --- packages/stream_chat_flutter/lib/src/message_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 3c4aec14..342f17c3 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -1136,7 +1136,7 @@ class _MessageWidgetState extends State final pinnedByMe = StreamChat.of(context).user!.id == pinnedBy!.id; return Padding( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8), child: Row( mainAxisSize: MainAxisSize.min, children: [ From 65bf45306a15e8f641664cc7b81ce437b9edd512 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 27 May 2021 16:27:23 +0200 Subject: [PATCH 306/599] fix lint and tests --- packages/stream_chat_flutter/lib/src/stream_chat.dart | 7 +++++++ .../stream_chat_flutter/test/src/deleted_message_test.dart | 3 +++ .../stream_chat_flutter/test/src/reaction_bubble_test.dart | 5 +++++ .../stream_chat_flutter/test/src/system_message_test.dart | 2 ++ .../lib/stream_chat_flutter_core.dart | 1 + packages/stream_chat_flutter_core/pubspec.yaml | 4 ++-- 6 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 424d4bcf..64acae98 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -39,6 +39,7 @@ class StreamChat extends StatefulWidget { this.streamChatThemeData, this.onBackgroundEventReceived, this.backgroundKeepAlive = const Duration(minutes: 1), + this.connectivityStream, }) : super(key: key); /// Client to do chat ops with @@ -59,6 +60,11 @@ class StreamChat extends StatefulWidget { /// upon the [Event.type] final EventHandler? onBackgroundEventReceived; + /// Stream of connectivity result + /// Visible for testing + @visibleForTesting + final Stream? connectivityStream; + @override StreamChatState createState() => StreamChatState(); @@ -102,6 +108,7 @@ class StreamChatState extends State { client: client, onBackgroundEventReceived: widget.onBackgroundEventReceived, backgroundKeepAlive: widget.backgroundKeepAlive, + connectivityStream: widget.connectivityStream, child: widget.child ?? const Offstage(), ), ); diff --git a/packages/stream_chat_flutter/test/src/deleted_message_test.dart b/packages/stream_chat_flutter/test/src/deleted_message_test.dart index bcf9cf62..206d92e3 100644 --- a/packages/stream_chat_flutter/test/src/deleted_message_test.dart +++ b/packages/stream_chat_flutter/test/src/deleted_message_test.dart @@ -79,6 +79,7 @@ void main() { ), ), ), + connectivityStream: Stream.value(ConnectivityResult.mobile), ), ), surfaceSize: const Size.square(200), @@ -131,6 +132,7 @@ void main() { ), ), ), + connectivityStream: Stream.value(ConnectivityResult.mobile), ), ), surfaceSize: const Size.square(200), @@ -187,6 +189,7 @@ void main() { ), ), ), + connectivityStream: Stream.value(ConnectivityResult.mobile), ), ), surfaceSize: const Size.square(200), diff --git a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart index 770a040b..c5183f35 100644 --- a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart +++ b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart @@ -60,6 +60,7 @@ void main() { maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), + connectivityStream: Stream.value(ConnectivityResult.mobile), ), surfaceSize: const Size(100, 100), ); @@ -96,6 +97,7 @@ void main() { maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), + connectivityStream: Stream.value(ConnectivityResult.mobile), ), surfaceSize: const Size(100, 100), ); @@ -118,6 +120,7 @@ void main() { StreamChat( client: client, streamChatThemeData: StreamChatThemeData.fromTheme(themeData), + connectivityStream: Stream.value(ConnectivityResult.mobile), child: Container( color: Colors.black, child: ReactionBubble( @@ -162,6 +165,7 @@ void main() { StreamChat( client: client, streamChatThemeData: StreamChatThemeData.fromTheme(themeData), + connectivityStream: Stream.value(ConnectivityResult.mobile), child: Container( color: Colors.black, child: ReactionBubble( @@ -204,6 +208,7 @@ void main() { await tester.pumpWidgetBuilder( StreamChat( client: client, + connectivityStream: Stream.value(ConnectivityResult.mobile), streamChatThemeData: StreamChatThemeData.fromTheme(themeData), child: SizedBox( child: ReactionBubble( diff --git a/packages/stream_chat_flutter/test/src/system_message_test.dart b/packages/stream_chat_flutter/test/src/system_message_test.dart index 8fa3ad44..6bb1da43 100644 --- a/packages/stream_chat_flutter/test/src/system_message_test.dart +++ b/packages/stream_chat_flutter/test/src/system_message_test.dart @@ -100,6 +100,7 @@ void main() { ), ), ), + connectivityStream: Stream.value(ConnectivityResult.mobile), ), ), surfaceSize: const Size.square(200), @@ -151,6 +152,7 @@ void main() { ), ), ), + connectivityStream: Stream.value(ConnectivityResult.mobile), ), ), surfaceSize: const Size.square(200), diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index 01463de0..ef947a0f 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -1,5 +1,6 @@ library stream_chat_flutter_core; +export 'package:connectivity_plus/connectivity_plus.dart'; export 'package:stream_chat/stream_chat.dart'; export 'src/channel_list_core.dart' hide ChannelListCoreState; diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 7bae31f9..4292772b 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -11,16 +11,16 @@ environment: dependencies: collection: ^1.15.0 + connectivity_plus: ^1.0.1 flutter: sdk: flutter meta: ^1.3.0 rxdart: ^0.27.0 stream_chat: ^2.0.0-nullsafety.2 - connectivity_plus: ^1.0.1 dev_dependencies: fake_async: ^1.2.0 flutter_test: sdk: flutter - mocktail: ^0.1.1 + mocktail: ^0.1.3 From 839ced618bf8c0478d8e2c31ebfadad55a1d44c6 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 27 May 2021 19:59:34 +0530 Subject: [PATCH 307/599] major refactoring Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/api/channel.dart | 306 +-- .../stream_chat/lib/src/api/retry_queue.dart | 2 +- .../lib/src/api/web_socket_channel_html.dart | 7 - .../lib/src/api/web_socket_channel_io.dart | 7 - .../lib/src/api/web_socket_channel_stub.dart | 9 - .../stream_chat/lib/src/api/websocket.dart | 321 --- packages/stream_chat/lib/src/client.dart | 1761 ++++++++--------- .../api}/attachment_file_uploader.dart | 16 +- .../lib/src/core/api/channel_api.dart | 293 +++ .../lib/src/core/api/device_api.dart | 59 + .../lib/src/core/api/general_api.dart | 94 + .../lib/src/core/api/guest_api.dart | 20 + .../lib/src/core/api/message_api.dart | 160 ++ .../lib/src/core/api/moderation_api.dart | 128 ++ .../lib/src/{ => core}/api/requests.dart | 43 +- .../lib/src/{ => core}/api/requests.g.dart | 18 + .../lib/src/{ => core}/api/responses.dart | 48 +- .../lib/src/{ => core}/api/responses.g.dart | 9 + .../lib/src/core/api/stream_chat_api.dart | 79 + .../lib/src/core/api/user_api.dart | 49 + .../src/core/http/connection_id_manager.dart | 27 + .../http/interceptor/auth_interceptor.dart | 91 + .../connection_id_interceptor.dart | 25 + .../http/interceptor/logging_interceptor.dart | 338 ++++ .../src/core/http/stream_chat_dio_error.dart | 21 + .../lib/src/core/http/stream_http_client.dart | 241 +++ .../core/http/stream_http_client_options.dart | 69 + .../stream_chat/lib/src/core/http/token.dart | 85 + .../lib/src/core/http/token_manager.dart | 80 + .../lib/src/{ => core}/models/action.dart | 0 .../lib/src/{ => core}/models/action.g.dart | 0 .../lib/src/{ => core}/models/attachment.dart | 6 +- .../src/{ => core}/models/attachment.g.dart | 0 .../{ => core}/models/attachment_file.dart | 0 .../models/attachment_file.freezed.dart | 0 .../{ => core}/models/attachment_file.g.dart | 0 .../src/{ => core}/models/channel_config.dart | 2 +- .../{ => core}/models/channel_config.g.dart | 0 .../src/{ => core}/models/channel_model.dart | 6 +- .../{ => core}/models/channel_model.g.dart | 0 .../src/{ => core}/models/channel_state.dart | 10 +- .../{ => core}/models/channel_state.g.dart | 0 .../lib/src/{ => core}/models/command.dart | 0 .../lib/src/{ => core}/models/command.g.dart | 0 .../lib/src/{ => core}/models/device.dart | 0 .../lib/src/{ => core}/models/device.g.dart | 0 .../lib/src/{ => core}/models/event.dart | 18 +- .../lib/src/{ => core}/models/event.g.dart | 4 +- .../lib/src/{ => core}/models/filter.dart | 0 .../lib/src/{ => core}/models/member.dart | 2 +- .../lib/src/{ => core}/models/member.g.dart | 0 .../lib/src/{ => core}/models/message.dart | 8 +- .../lib/src/{ => core}/models/message.g.dart | 0 .../lib/src/{ => core}/models/mute.dart | 6 +- .../lib/src/{ => core}/models/mute.g.dart | 0 .../lib/src/{ => core}/models/own_user.dart | 20 +- .../lib/src/{ => core}/models/own_user.g.dart | 0 .../lib/src/{ => core}/models/reaction.dart | 4 +- .../lib/src/{ => core}/models/reaction.g.dart | 0 .../lib/src/{ => core}/models/read.dart | 2 +- .../lib/src/{ => core}/models/read.g.dart | 0 .../src/{ => core}/models/serialization.dart | 2 +- .../lib/src/{ => core}/models/user.dart | 2 +- .../lib/src/{ => core}/models/user.g.dart | 0 packages/stream_chat/lib/src/core/utils.dart | 16 + .../lib/src/db/chat_persistence_client.dart | 20 +- .../lib/src/errors/chat_error_code.dart | 91 + .../lib/src/errors/stream_chat_error.dart | 77 + packages/stream_chat/lib/src/event_type.dart | 3 + .../src/{api => ws}/connection_status.dart | 0 .../stream_chat/lib/src/ws/socket_error.dart | 25 + .../stream_chat/lib/src/ws/timer_helper.dart | 51 + .../stream_chat/lib/src/ws/websocket.dart | 395 ++++ packages/stream_chat/lib/stream_chat.dart | 45 +- packages/stream_chat/pubspec.yaml | 1 + .../test/src/api/channel_test.dart | 10 +- .../test/src/api/responses_test.dart | 12 +- .../test/src/api/web_socket_stub_test.dart | 2 +- .../test/src/api/websocket_test.dart | 24 +- .../stream_chat/test/src/client_test.dart | 22 +- .../test/src/models/filter_test.dart | 2 +- packages/stream_chat/test/version_test.dart | 43 +- .../stream_chat_flutter/example/pubspec.yaml | 4 + .../lib/src/channel_info.dart | 7 +- .../lib/src/channel_list_header.dart | 7 +- .../lib/src/channel_list_view.dart | 42 +- .../lib/src/user_list_view.dart | 13 +- packages/stream_chat_flutter/pubspec.yaml | 6 + .../lib/src/channel_list_core.dart | 45 +- .../lib/src/channels_bloc.dart | 14 +- .../lib/src/stream_chat_core.dart | 6 +- .../lib/src/user_list_core.dart | 21 +- .../lib/src/users_bloc.dart | 4 +- .../stream_chat_flutter_core/pubspec.yaml | 4 + .../test/channel_list_core_test.dart | 84 +- .../test/channels_bloc_test.dart | 145 +- .../test/stream_chat_core_test.dart | 22 +- .../test/user_list_core_test.dart | 32 +- .../test/users_bloc_test.dart | 24 +- .../lib/src/dao/connection_event_dao.dart | 3 +- .../lib/src/db/moor_chat_database.g.dart | 356 ++-- .../lib/src/entity/connection_events.dart | 3 + .../lib/src/mapper/event_mapper.dart | 2 + packages/stream_chat_persistence/pubspec.yaml | 4 + .../test/src/mapper/event_mapper_test.dart | 11 +- .../stream_chat_persistence_client_test.dart | 4 +- 106 files changed, 4263 insertions(+), 1937 deletions(-) delete mode 100644 packages/stream_chat/lib/src/api/web_socket_channel_html.dart delete mode 100644 packages/stream_chat/lib/src/api/web_socket_channel_io.dart delete mode 100644 packages/stream_chat/lib/src/api/web_socket_channel_stub.dart delete mode 100644 packages/stream_chat/lib/src/api/websocket.dart rename packages/stream_chat/lib/src/{ => core/api}/attachment_file_uploader.dart (90%) create mode 100644 packages/stream_chat/lib/src/core/api/channel_api.dart create mode 100644 packages/stream_chat/lib/src/core/api/device_api.dart create mode 100644 packages/stream_chat/lib/src/core/api/general_api.dart create mode 100644 packages/stream_chat/lib/src/core/api/guest_api.dart create mode 100644 packages/stream_chat/lib/src/core/api/message_api.dart create mode 100644 packages/stream_chat/lib/src/core/api/moderation_api.dart rename packages/stream_chat/lib/src/{ => core}/api/requests.dart (77%) rename packages/stream_chat/lib/src/{ => core}/api/requests.g.dart (63%) rename packages/stream_chat/lib/src/{ => core}/api/responses.dart (90%) rename packages/stream_chat/lib/src/{ => core}/api/responses.g.dart (97%) create mode 100644 packages/stream_chat/lib/src/core/api/stream_chat_api.dart create mode 100644 packages/stream_chat/lib/src/core/api/user_api.dart create mode 100644 packages/stream_chat/lib/src/core/http/connection_id_manager.dart create mode 100644 packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart create mode 100644 packages/stream_chat/lib/src/core/http/interceptor/connection_id_interceptor.dart create mode 100644 packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart create mode 100644 packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart create mode 100644 packages/stream_chat/lib/src/core/http/stream_http_client.dart create mode 100644 packages/stream_chat/lib/src/core/http/stream_http_client_options.dart create mode 100644 packages/stream_chat/lib/src/core/http/token.dart create mode 100644 packages/stream_chat/lib/src/core/http/token_manager.dart rename packages/stream_chat/lib/src/{ => core}/models/action.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/action.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/attachment.dart (97%) rename packages/stream_chat/lib/src/{ => core}/models/attachment.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/attachment_file.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/attachment_file.freezed.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/attachment_file.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/channel_config.dart (97%) rename packages/stream_chat/lib/src/{ => core}/models/channel_config.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/channel_model.dart (96%) rename packages/stream_chat/lib/src/{ => core}/models/channel_model.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/channel_state.dart (87%) rename packages/stream_chat/lib/src/{ => core}/models/channel_state.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/command.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/command.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/device.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/device.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/event.dart (93%) rename packages/stream_chat/lib/src/{ => core}/models/event.g.dart (97%) rename packages/stream_chat/lib/src/{ => core}/models/filter.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/member.dart (98%) rename packages/stream_chat/lib/src/{ => core}/models/member.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/message.dart (98%) rename packages/stream_chat/lib/src/{ => core}/models/message.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/mute.dart (85%) rename packages/stream_chat/lib/src/{ => core}/models/mute.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/own_user.dart (79%) rename packages/stream_chat/lib/src/{ => core}/models/own_user.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/reaction.dart (95%) rename packages/stream_chat/lib/src/{ => core}/models/reaction.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/read.dart (94%) rename packages/stream_chat/lib/src/{ => core}/models/read.g.dart (100%) rename packages/stream_chat/lib/src/{ => core}/models/serialization.dart (96%) rename packages/stream_chat/lib/src/{ => core}/models/user.dart (98%) rename packages/stream_chat/lib/src/{ => core}/models/user.g.dart (100%) create mode 100644 packages/stream_chat/lib/src/core/utils.dart create mode 100644 packages/stream_chat/lib/src/errors/chat_error_code.dart create mode 100644 packages/stream_chat/lib/src/errors/stream_chat_error.dart rename packages/stream_chat/lib/src/{api => ws}/connection_status.dart (100%) create mode 100644 packages/stream_chat/lib/src/ws/socket_error.dart create mode 100644 packages/stream_chat/lib/src/ws/timer_helper.dart create mode 100644 packages/stream_chat/lib/src/ws/websocket.dart diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index cb3cfe37..abdae6ce 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:math'; import 'package:collection/collection.dart' @@ -10,9 +9,9 @@ import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/api/retry_queue.dart'; import 'package:stream_chat/src/event_type.dart'; import 'package:stream_chat/src/extensions/rate_limit.dart'; -import 'package:stream_chat/src/models/attachment_file.dart'; -import 'package:stream_chat/src/models/channel_state.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/attachment_file.dart'; +import 'package:stream_chat/src/core/models/channel_state.dart'; +import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/stream_chat.dart'; /// This a the class that manages a specific channel. @@ -214,8 +213,6 @@ class Channel { StreamChatClient get client => _client; final StreamChatClient _client; - String get _channelURL => '/channels/$type/$id'; - final Completer _initializedCompleter = Completer(); /// True if this is initialized @@ -492,7 +489,7 @@ class Channel { state?.addMessage(message); - final response = await _client.deleteMessage(message); + final response = await _client.deleteMessage(message.id); state?.addMessage(message.copyWith(status: MessageSendingStatus.sent)); @@ -507,9 +504,9 @@ class Channel { /// Pins provided message Future pinMessage( - Message message, - Object? timeoutOrExpirationDate, - ) { + Message message, { + Object? /*num|DateTime*/ timeoutOrExpirationDate, + }) { assert(() { if (timeoutOrExpirationDate is! DateTime && timeoutOrExpirationDate != null && @@ -517,7 +514,7 @@ class Channel { throw ArgumentError('Invalid timeout or Expiration date'); } return true; - }(), 'Check for invalid token or expiration date'); + }(), 'Check for invalid timeout or expiration date'); DateTime? pinExpires; if (timeoutOrExpirationDate is DateTime) { @@ -619,10 +616,7 @@ class Channel { /// Send an event on this channel Future sendEvent(Event event) { _checkInitialized(); - return _client.post( - '$_channelURL/event', - data: {'event': event.toJson()}, - ).then((res) => _client.decode(res.data, EmptyResponse.fromJson)!); + return _client.sendEvent(id!, type, event); } /// Send a reaction to this channel @@ -674,21 +668,13 @@ class Channel { state?.addMessage(newMessage); - final data = Map.from(extraData) - ..addAll({ - 'type': type, - }); - try { - final res = await _client.post( - '/messages/$messageId/reaction', - data: { - 'reaction': data, - 'enforce_unique': enforceUnique, - }, + final reactionResp = await _client.sendReaction( + messageId, + type, + extraData: extraData, + enforceUnique: enforceUnique, ); - final reactionResp = - _client.decode(res.data, SendReactionResponse.fromJson); return reactionResp; } catch (_) { // Reset the message if the update fails @@ -731,9 +717,11 @@ class Channel { state?.addMessage(newMessage); try { - final res = await client - .delete('/messages/${message.id}/reaction/${reaction.type}'); - return _client.decode(res.data, EmptyResponse.fromJson); + final deleteResponse = await _client.deleteReaction( + message.id, + reaction.type, + ); + return deleteResponse; } catch (_) { // Reset the message if the update fails state?.addMessage(message); @@ -746,45 +734,45 @@ class Channel { Map channelData, [ Message? updateMessage, ]) async { - final response = await _client.post(_channelURL, data: { - if (updateMessage != null) - 'message': updateMessage.copyWith(updatedAt: DateTime.now()).toJson(), - 'data': channelData, - }); - return _client.decode(response.data, UpdateChannelResponse.fromJson); + _checkInitialized(); + return _client.updateChannel( + id!, + type, + channelData, + message: updateMessage, + ); } /// Edit the channel custom data Future updatePartial( - Map channelData) async { - final response = await _client.patch(_channelURL, data: channelData); - return _client.decode(response.data, PartialUpdateChannelResponse.fromJson); + Map channelData, + ) async { + _checkInitialized(); + return _client.updateChannelPartial(id!, type, channelData); } /// Delete this channel. Messages are permanently removed. Future delete() async { - final response = await _client.delete(_channelURL); - return _client.decode(response.data, EmptyResponse.fromJson); + _checkInitialized(); + return _client.deleteChannel(id!, type); } /// Removes all messages from the channel Future truncate() async { - final response = await _client.post('$_channelURL/truncate'); - return _client.decode(response.data, EmptyResponse.fromJson); + _checkInitialized(); + return _client.truncateChannel(id!, type); } /// Accept invitation to the channel Future acceptInvite([Message? message]) async { - final res = await _client.post(_channelURL, - data: {'accept_invite': true, 'message': message?.toJson()}); - return _client.decode(res.data, AcceptInviteResponse.fromJson); + _checkInitialized(); + return _client.acceptChannelInvite(id!, type, message: message); } /// Reject invitation to the channel Future rejectInvite([Message? message]) async { - final res = await _client.post(_channelURL, - data: {'reject_invite': true, 'message': message?.toJson()}); - return _client.decode(res.data, RejectInviteResponse.fromJson); + _checkInitialized(); + return _client.rejectChannelInvite(id!, type, message: message); } /// Add members to the channel @@ -792,11 +780,8 @@ class Channel { List memberIds, [ Message? message, ]) async { - final res = await _client.post(_channelURL, data: { - 'add_members': memberIds, - 'message': message?.toJson(), - }); - return _client.decode(res.data, AddMembersResponse.fromJson); + _checkInitialized(); + return _client.addChannelMembers(id!, type, memberIds, message: message); } /// Invite members to the channel @@ -804,11 +789,8 @@ class Channel { List memberIds, [ Message? message, ]) async { - final res = await _client.post(_channelURL, data: { - 'invites': memberIds, - 'message': message?.toJson(), - }); - return _client.decode(res.data, InviteMembersResponse.fromJson); + _checkInitialized(); + return _client.inviteChannelMembers(id!, type, memberIds, message: message); } /// Remove members from the channel @@ -816,11 +798,8 @@ class Channel { List memberIds, [ Message? message, ]) async { - final res = await _client.post(_channelURL, data: { - 'remove_members': memberIds, - 'message': message?.toJson(), - }); - return _client.decode(res.data, RemoveMembersResponse.fromJson); + _checkInitialized(); + return _client.removeChannelMembers(id!, type, memberIds, message: message); } /// Send action for a specific message of this channel @@ -829,16 +808,8 @@ class Channel { Map formData, ) async { _checkInitialized(); - final messageId = message.id; - final response = await _client.post('/messages/$messageId/action', data: { - 'id': id, - 'type': type, - 'form_data': formData, - 'message_id': messageId, - }); - - final res = _client.decode(response.data, SendActionResponse.fromJson); + final res = await _client.sendAction(id!, type, messageId, formData); if (res.message != null) { state!.addMessage(res.message!); @@ -867,21 +838,20 @@ class Channel { state!.threads[oldMessage.parentId!]!..remove(oldMessage)); } } - await _client.chatPersistenceClient?.deleteMessageById(messageId); } - return res; } - /// Mark all channel messages as read - Future markRead() async { + /// Mark all messages as read + /// Optionally provide a [messageId] if you want to mark a + /// particular message as read + Future markRead({String? messageId}) async { _checkInitialized(); client.state.totalUnreadCount = max( 0, (client.state.totalUnreadCount ?? 0) - (state!.unreadCount ?? 0)); state!._unreadCountController.add(0); - final response = await _client.post('$_channelURL/read', data: {}); - return _client.decode(response.data, EmptyResponse.fromJson); + return _client.markChannelRead(id!, type, messageId: messageId); } /// Loads the initial channel state and watches for changes @@ -924,11 +894,8 @@ class Channel { /// Stop watching the channel Future stopWatching() async { - final response = await _client.post( - '$_channelURL/stop-watching', - data: {}, - ); - return _client.decode(response.data, EmptyResponse.fromJson); + _checkInitialized(); + return _client.stopChannelWatching(id!, type); } /// List the message replies for a parent message @@ -949,50 +916,29 @@ class Channel { return QueryRepliesResponse()..messages = cachedReplies; } } - - final response = await _client.get('/messages/$parentId/replies', - queryParameters: options.toJson()); - - final repliesResponse = _client.decode( - response.data, - QueryRepliesResponse.fromJson, - ); - + final repliesResponse = await _client.getReplies(parentId, options); state?.updateThreadInfo(parentId, repliesResponse.messages); - return repliesResponse; } /// List the reactions for a message in the channel Future getReactions( - String messageID, + String messageId, PaginationParams options, - ) async { - final response = await _client.get( - '/messages/$messageID/reactions', - queryParameters: options.toJson(), - ); - return _client.decode( - response.data, QueryReactionsResponse.fromJson); - } + ) => + _client.getReactions( + messageId, + options, + ); /// Retrieves a list of messages by ID Future getMessagesById( - List messageIDs) async { - final response = await _client.get( - '$_channelURL/messages', - queryParameters: {'ids': messageIDs.join(',')}, - ); - - final res = _client.decode( - response.data, - GetMessagesByIdResponse.fromJson, - ); - + List messageIDs, + ) async { + _checkInitialized(); + final res = await _client.getMessagesById(id!, type, messageIDs); final messages = res.messages; - state?.updateChannelState(ChannelState(messages: messages)); - return res; } @@ -1000,18 +946,11 @@ class Channel { Future translateMessage( String messageId, String language, - ) async { - final response = await _client.post( - '/messages/$messageId/translate', - data: { - 'language': language, - }, - ); - return _client.decode( - response.data, - TranslateMessageResponse.fromJson, - ); - } + ) => + _client.translateMessage( + messageId, + language, + ); /// Creates a new channel Future create() async => query(options: { @@ -1030,36 +969,10 @@ class Channel { PaginationParams? watchersPagination, bool preferOffline = false, }) async { - var path = '/channels/$type'; - if (id != null) path = '$path/$id'; - path = '$path/query'; - - final payload = Map.from({ - 'state': true, - }) - ..addAll(options); - - if (_extraData.isNotEmpty) { - payload['data'] = _extraData; - } - - if (messagesPagination != null) { - payload['messages'] = messagesPagination.toJson(); - } - if (membersPagination != null) { - payload['members'] = membersPagination.toJson(); - } - if (watchersPagination != null) { - payload['watchers'] = watchersPagination.toJson(); - } - if (preferOffline && cid != null) { - final updatedState = - (await _client.chatPersistenceClient?.getChannelStateByCid( - cid!, - messagePagination: messagesPagination, - ))!; - if (updatedState.messages.isNotEmpty) { + final updatedState = await _client.chatPersistenceClient + ?.getChannelStateByCid(cid!, messagePagination: messagesPagination); + if (updatedState != null && updatedState.messages.isNotEmpty) { if (state == null) { _initState(updatedState); } else { @@ -1070,8 +983,14 @@ class Channel { } try { - final response = await _client.post(path, data: payload); - final updatedState = _client.decode(response.data, ChannelState.fromJson); + final updatedState = await _client.queryChannel( + type, + channelId: id, + channelData: _extraData, + messagesPagination: messagesPagination, + membersPagination: membersPagination, + watchersPagination: watchersPagination, + ); if (_id == null) { _id = updatedState.channel!.id; @@ -1096,45 +1015,26 @@ class Channel { Filter? filter, List? sort, PaginationParams? pagination, - }) async { - final payload = { - 'sort': sort, - 'filter_conditions': filter ?? {}, - 'type': type, - }; - - if (pagination != null) { - payload.addAll(pagination.toJson()); - } - - if (id != null) { - payload['id'] = id; - } else if (state?.members.isNotEmpty == true) { - payload['members'] = state!.members; - } - - final rawRes = await _client.get('/members', queryParameters: { - 'payload': jsonEncode(payload), - }); - final response = _client.decode(rawRes.data, QueryMembersResponse.fromJson); - return response; - } + }) => + _client.queryMembers( + type, + channelId: id, + filter: filter, + members: state?.members, + sort: sort, + pagination: pagination, + ); /// Mutes the channel - Future mute({Duration? expiration}) async { - final response = await _client.post('/moderation/mute/channel', data: { - 'channel_cid': cid, - if (expiration != null) 'expiration': expiration.inMilliseconds, - }); - return _client.decode(response.data, EmptyResponse.fromJson); + Future mute({Duration? expiration}) { + _checkInitialized(); + return _client.muteChannel(cid!, expiration: expiration); } /// Unmutes the channel - Future unmute() async { - final response = await _client.post('/moderation/unmute/channel', data: { - 'channel_cid': cid, - }); - return _client.decode(response.data, EmptyResponse.fromJson); + Future unmute() { + _checkInitialized(); + return _client.unmuteChannel(cid!); } /// Bans a user from the channel @@ -1188,9 +1088,11 @@ class Channel { /// will be removed for the user Future hide({bool clearHistory = false}) async { _checkInitialized(); - final response = await _client - .post('$_channelURL/hide', data: {'clear_history': clearHistory}); - + final response = await _client.hideChannel( + id!, + type, + clearHistory: clearHistory, + ); if (clearHistory == true) { state!.truncate(); final cid = _cid; @@ -1198,15 +1100,13 @@ class Channel { await _client.chatPersistenceClient?.deleteMessageByCid(cid); } } - - return _client.decode(response.data, EmptyResponse.fromJson); + return response; } /// Removes the hidden status for the channel Future show() async { _checkInitialized(); - final response = await _client.post('$_channelURL/show'); - return _client.decode(response.data, EmptyResponse.fromJson); + return _client.showChannel(id!, type); } /// Stream of [Event] coming from websocket connection specific for the @@ -1588,7 +1488,7 @@ class ChannelClientState { } readList.add(Read( user: event.user!, - lastRead: event.createdAt!, + lastRead: event.createdAt, unreadMessages: event.totalUnreadCount ?? 0, )); _channelState = _channelState.copyWith(read: readList); @@ -1793,7 +1693,7 @@ class ChannelClientState { BehaviorSubject.seeded({}); set _threads(Map> v) { - _channel._client.chatPersistenceClient?.updateMessages( + _channel.client.chatPersistenceClient?.updateMessages( _channel.cid!, v.values.expand((v) => v).toList(), ); diff --git a/packages/stream_chat/lib/src/api/retry_queue.dart b/packages/stream_chat/lib/src/api/retry_queue.dart index c13db9c6..e6d883a9 100644 --- a/packages/stream_chat/lib/src/api/retry_queue.dart +++ b/packages/stream_chat/lib/src/api/retry_queue.dart @@ -6,7 +6,7 @@ import 'package:stream_chat/src/api/channel.dart'; import 'package:stream_chat/src/api/retry_policy.dart'; import 'package:stream_chat/src/event_type.dart'; import 'package:stream_chat/src/exceptions.dart'; -import 'package:stream_chat/src/models/message.dart'; +import 'package:stream_chat/src/core/models/message.dart'; import 'package:stream_chat/stream_chat.dart'; /// The retry queue associated to a channel diff --git a/packages/stream_chat/lib/src/api/web_socket_channel_html.dart b/packages/stream_chat/lib/src/api/web_socket_channel_html.dart deleted file mode 100644 index ab821ca9..00000000 --- a/packages/stream_chat/lib/src/api/web_socket_channel_html.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:web_socket_channel/html.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; - -/// Html version of websocket implementation -/// Used in Flutter web version -WebSocketChannel connectWebSocket(String url, {Iterable? protocols}) => - HtmlWebSocketChannel.connect(url, protocols: protocols); diff --git a/packages/stream_chat/lib/src/api/web_socket_channel_io.dart b/packages/stream_chat/lib/src/api/web_socket_channel_io.dart deleted file mode 100644 index 8402ca0c..00000000 --- a/packages/stream_chat/lib/src/api/web_socket_channel_io.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:web_socket_channel/io.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; - -/// IO version of websocket implementation -/// Used in Flutter mobile version -WebSocketChannel connectWebSocket(String url, {Iterable? protocols}) => - IOWebSocketChannel.connect(url, protocols: protocols); diff --git a/packages/stream_chat/lib/src/api/web_socket_channel_stub.dart b/packages/stream_chat/lib/src/api/web_socket_channel_stub.dart deleted file mode 100644 index e2efaee6..00000000 --- a/packages/stream_chat/lib/src/api/web_socket_channel_stub.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:web_socket_channel/web_socket_channel.dart'; - -/// Stub version of websocket implementation -/// Used just for conditional library import -WebSocketChannel connectWebSocket(String url, - {Iterable? protocols, - Map? headers, - Duration? pingInterval}) => - throw UnimplementedError(); diff --git a/packages/stream_chat/lib/src/api/websocket.dart b/packages/stream_chat/lib/src/api/websocket.dart deleted file mode 100644 index b5bbbb69..00000000 --- a/packages/stream_chat/lib/src/api/websocket.dart +++ /dev/null @@ -1,321 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:math'; - -import 'package:logging/logging.dart'; -import 'package:meta/meta.dart'; -import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat/src/api/connection_status.dart'; -import 'package:stream_chat/src/models/event.dart'; -import 'package:stream_chat/src/models/user.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; - -/// Typedef which exposes an [Event] as the only parameter. -typedef EventHandler = void Function(Event); - -/// Typedef used for connecting to a websocket. Method returns a -/// [WebSocketChannel] and accepts a connection [url] and an optional -/// [Iterable] of `protocols`. -typedef ConnectWebSocket = WebSocketChannel Function(String? url, - {Iterable? protocols}); - -// TODO: parse error even -// TODO: if parsing an error into an event fails we should not hide the -// TODO: original error -/// A WebSocket connection that reconnects upon failure. -class WebSocket { - /// Creates a new websocket - /// To connect the WS call [connect] - WebSocket({ - required this.baseUrl, - required this.user, - required this.handler, - this.connectParams = const {}, - this.connectPayload = const {}, - this.logger, - this.connectFunc, - this.reconnectionMonitorInterval = 1, - this.healthCheckInterval = 20, - this.reconnectionMonitorTimeout = 40, - }) { - final qs = Map.from(connectParams); - - final data = Map.from(connectPayload); - - data['user_details'] = user.toJson(); - qs['json'] = json.encode(data); - - if (baseUrl.startsWith('https')) { - _path = baseUrl.replaceFirst('https://', ''); - _path = Uri.https(_path, 'connect', qs) - .toString() - .replaceFirst('https', 'wss'); - } else if (baseUrl.startsWith('http')) { - _path = baseUrl.replaceFirst('http://', ''); - _path = - Uri.http(_path, 'connect', qs).toString().replaceFirst('http', 'ws'); - } else { - _path = Uri.https(baseUrl, 'connect', qs) - .toString() - .replaceFirst('https', 'wss'); - } - } - - /// WS base url - final String baseUrl; - - /// User performing the WS connection - final User user; - - /// Querystring connection parameters - final Map connectParams; - - /// WS connection payload - final Map connectPayload; - - /// Functions that will be called every time a new event is received from the - /// connection - final EventHandler handler; - - /// A WS specific logger instance - final Logger? logger; - - /// Connection function - /// Used only for testing purpose - @visibleForTesting - final ConnectWebSocket? connectFunc; - - /// Interval of the reconnection monitor timer - /// This checks that it received a new event in the last - /// [reconnectionMonitorTimeout] seconds, otherwise it considers the - /// connection unhealthy and reconnects the WS - final int reconnectionMonitorInterval; - - /// Interval of the health event sending timer - /// This sends a health event every [healthCheckInterval] seconds in order to - /// make the server aware that the client is still listening - final int healthCheckInterval; - - /// The timeout that uses the reconnection monitor timer to consider the - /// connection unhealthy - final int reconnectionMonitorTimeout; - - final _connectionStatusController = - BehaviorSubject.seeded(ConnectionStatus.disconnected); - - set _connectionStatus(ConnectionStatus status) => - _connectionStatusController.add(status); - - /// The current connection status value - ConnectionStatus? get connectionStatus => _connectionStatusController.value; - - /// This notifies of connection status changes - Stream get connectionStatusStream => - _connectionStatusController.stream; - - late String _path; - int _retryAttempt = 1; - late WebSocketChannel _channel; - Timer? _healthCheck, _reconnectionMonitor; - DateTime? _lastEventAt; - bool _manuallyDisconnected = false; - bool _connecting = false; - bool _reconnecting = false; - - Event _decodeEvent(String source) => Event.fromJson(json.decode(source)); - - Completer _connectionCompleter = Completer(); - - /// Connect the WS using the parameters passed in the constructor - Future connect() async { - _manuallyDisconnected = false; - - if (_connecting) { - logger?.severe('already connecting'); - return null; - } - - _connecting = true; - _connectionStatus = ConnectionStatus.connecting; - - logger?.info('connecting to $_path'); - - _channel = - connectFunc?.call(_path) ?? WebSocketChannel.connect(Uri.parse(_path)); - _channel.stream.listen( - (data) async { - final jsonData = json.decode(data); - if (jsonData['error'] != null) { - return _onConnectionError(jsonData['error']); - } - _onData(data); - }, - onError: (error, stacktrace) { - _onConnectionError(error, stacktrace); - }, - onDone: _onDone, - ); - return _connectionCompleter.future; - } - - void _onDone() { - _connecting = false; - if (_manuallyDisconnected) { - return; - } - - logger?.info('connection closed | closeCode: ${_channel.closeCode} | ' - 'closedReason: ${_channel.closeReason}'); - - if (!_reconnecting) { - _reconnect(); - } - } - - void _onData(data) { - if (_manuallyDisconnected) { - return; - } - - final event = _decodeEvent(data); - logger?.info('received new event: $data'); - - if (_lastEventAt == null) { - logger?.info('connection estabilished'); - _connecting = false; - _reconnecting = false; - _lastEventAt = DateTime.now(); - - _connectionStatus = ConnectionStatus.connected; - _retryAttempt = 1; - - if (!_connectionCompleter.isCompleted) { - _connectionCompleter.complete(event); - } - - _startReconnectionMonitor(); - _startHealthCheck(); - } - - handler(event); - _lastEventAt = DateTime.now(); - } - - Future _onConnectionError(error, [stacktrace]) async { - logger?..severe('error connecting')..severe(error); - if (stacktrace != null) { - logger?.severe(stacktrace); - } - _connecting = false; - - if (!_reconnecting) { - _connectionStatus = ConnectionStatus.disconnected; - } - - if (!_connectionCompleter.isCompleted) { - _cancelTimers(); - _connectionCompleter.completeError(error, stacktrace); - } else if (!_reconnecting) { - return _reconnect(); - } - } - - void _reconnectionTimer(_) { - final now = DateTime.now(); - if (_lastEventAt != null && - now.difference(_lastEventAt!).inSeconds > reconnectionMonitorTimeout) { - _channel.sink.close(); - } - } - - void _startReconnectionMonitor() { - _reconnectionMonitor = Timer.periodic( - Duration(seconds: reconnectionMonitorInterval), - _reconnectionTimer, - ); - - _reconnectionTimer(_reconnectionMonitor); - } - - void _reconnectTimer() async { - if (!_reconnecting) { - return; - } - if (_connecting) { - logger?.info('already connecting'); - return; - } - - logger?.info('reconnecting..'); - - _cancelTimers(); - - try { - await connect(); - } catch (e) { - logger?.log(Level.SEVERE, e.toString()); - } - await Future.delayed( - Duration(seconds: min(_retryAttempt * 5, 25)), - () { - _reconnectTimer(); - _retryAttempt++; - }, - ); - } - - Future _reconnect() async { - logger?.info('reconnect'); - if (!_reconnecting) { - _reconnecting = true; - _connectionStatus = ConnectionStatus.connecting; - } - - _reconnectTimer(); - } - - void _cancelTimers() { - _lastEventAt = null; - if (_healthCheck != null) { - _healthCheck!.cancel(); - } - if (_reconnectionMonitor != null) { - _reconnectionMonitor!.cancel(); - } - } - - void _healthCheckTimer(_) { - logger?.info('sending health.check'); - _channel.sink.add("{'type': 'health.check'}"); - } - - void _startHealthCheck() { - logger?.info('start health check monitor'); - - _healthCheck = Timer.periodic( - Duration(seconds: healthCheckInterval), - _healthCheckTimer, - ); - - _healthCheckTimer(_healthCheck); - } - - /// Disconnects the WS and releases eventual resources - Future disconnect() async { - _connecting = false; - if (!_connectionCompleter.isCompleted) { - _connectionCompleter.complete(); - } - if (_manuallyDisconnected) { - return; - } - logger?.info('disconnecting'); - _connectionCompleter = Completer(); - _cancelTimers(); - _reconnecting = false; - _manuallyDisconnected = true; - _connectionStatus = ConnectionStatus.disconnected; - await _connectionStatusController.close(); - await _channel.sink.close(); - } -} diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 3cabe274..936ccfc0 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -5,61 +5,46 @@ import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; -import 'package:meta/meta.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/api/channel.dart'; -import 'package:stream_chat/src/api/connection_status.dart'; -import 'package:stream_chat/src/api/requests.dart'; -import 'package:stream_chat/src/api/responses.dart'; +import 'package:stream_chat/src/ws/connection_status.dart'; +import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; +import 'package:stream_chat/src/core/api/requests.dart'; +import 'package:stream_chat/src/core/api/responses.dart'; import 'package:stream_chat/src/api/retry_policy.dart'; -import 'package:stream_chat/src/api/websocket.dart'; -import 'package:stream_chat/src/attachment_file_uploader.dart'; +import 'package:stream_chat/src/ws/websocket.dart'; +import 'package:stream_chat/src/core/api/stream_chat_api.dart'; +import 'package:stream_chat/src/core/http/connection_id_manager.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; +import 'package:stream_chat/src/core/http/token.dart'; +import 'package:stream_chat/src/core/http/token_manager.dart'; import 'package:stream_chat/src/db/chat_persistence_client.dart'; import 'package:stream_chat/src/event_type.dart'; -import 'package:stream_chat/src/exceptions.dart'; -import 'package:stream_chat/src/extensions/map_extension.dart'; -import 'package:stream_chat/src/models/attachment_file.dart'; -import 'package:stream_chat/src/models/channel_model.dart'; -import 'package:stream_chat/src/models/channel_state.dart'; -import 'package:stream_chat/src/models/event.dart'; -import 'package:stream_chat/src/models/filter.dart'; -import 'package:stream_chat/src/models/message.dart'; -import 'package:stream_chat/src/models/own_user.dart'; -import 'package:stream_chat/src/models/user.dart'; -import 'package:stream_chat/src/platform_detector/platform_detector.dart'; -import 'package:stream_chat/version.dart'; -import 'package:uuid/uuid.dart'; +import 'package:stream_chat/src/core/models/attachment_file.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/channel_state.dart'; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/own_user.dart'; +import 'package:stream_chat/src/core/models/user.dart'; + +import 'package:stream_chat/src/core/models/filter.dart'; + +import 'package:stream_chat/src/core/models/member.dart'; /// Handler function used for logging records. Function requires a single /// [LogRecord] as the only parameter. typedef LogHandlerFunction = void Function(LogRecord record); -/// Used for decoding [Map] data to a generic type `T`. -typedef DecoderFunction = T Function(Map); - /// A function which can be used to request a Stream Chat API token from your /// own backend server. Function requires a single [userId]. -typedef TokenProvider = Future Function(String? userId); +typedef TokenProvider = Future Function(String userId); -/// Provider used to send push notifications. -enum PushProvider { - /// Send notifications using Google's Firebase Cloud Messaging - firebase, - - /// Send notifications using Apple's Push Notification service - apn -} - -extension on PushProvider { - /// Returns the string notion for [PushProvider]. - String get name { - if (this == PushProvider.apn) { - return 'apn'; - } else { - return 'firebase'; - } - } -} +final _levelEmojiMapper = { + Level.INFO: 'â„šī¸', + Level.WARNING: 'âš ī¸', + Level.SEVERE: '🚨', +}; /// The official Dart client for Stream Chat, /// a service for building chat applications. @@ -79,37 +64,64 @@ class StreamChatClient { /// You should only create the client once and re-use it across your /// application. StreamChatClient( - this.apiKey, { - this.tokenProvider, - this.baseURL = _defaultBaseURL, - this.logLevel = Level.WARNING, + String apiKey, { + this.logLevel = Level.ALL, LogHandlerFunction? logHandlerFunction, + RetryPolicy? retryPolicy, + Location? location, + String? baseURL, Duration connectTimeout = const Duration(seconds: 6), Duration receiveTimeout = const Duration(seconds: 6), - Dio? httpClient, - RetryPolicy? retryPolicy, - this.attachmentFileUploader, + StreamChatApi? chatApi, + AttachmentFileUploader? attachmentFileUploader, }) { - _retryPolicy = retryPolicy ?? - RetryPolicy( - retryTimeout: - (StreamChatClient client, int attempt, ApiError? error) => - Duration(seconds: 1 * attempt), - shouldRetry: - (StreamChatClient client, int attempt, ApiError? error) => - attempt < 5, + _setupLogger(logHandlerFunction); + + final options = StreamHttpClientOptions( + baseUrl: baseURL, + location: location, + connectTimeout: connectTimeout, + receiveTimeout: receiveTimeout, + ); + + _chatApi = chatApi ?? + StreamChatApi( + apiKey, + options: options, + tokenManager: _tokenManager, + connectionIdManager: _connectionIdManager, + attachmentFileUploader: attachmentFileUploader, + logger: _detachedLogger('đŸ•¸ī¸'), ); - attachmentFileUploader ??= StreamAttachmentFileUploader(this); + _ws = WebSocket( + apiKey: apiKey, + baseUrl: options.baseUrl, + tokenManager: _tokenManager, + handler: handleEvent, + logger: _detachedLogger('🔌'), + ); + + _retryPolicy = retryPolicy ?? + RetryPolicy( + retryTimeout: (_, int attempt, __) => Duration(seconds: 1 * attempt), + shouldRetry: (_, int attempt, __) => attempt < 5, + ); state = ClientState(this); - _setupLogger(logHandlerFunction); - _setupDio(httpClient, receiveTimeout, connectTimeout); - logger.info('instantiating new client'); } + late final StreamChatApi _chatApi; + late final WebSocket _ws; + + /// This client state + late ClientState state; + + final _tokenManager = TokenManager(); + final _connectionIdManager = ConnectionIdManager(); + set chatPersistenceClient(ChatPersistenceClient? value) { _originalChatPersistenceClient = value; } @@ -121,22 +133,20 @@ class StreamChatClient { ChatPersistenceClient? _chatPersistenceClient; - /// Attachment uploader - AttachmentFileUploader? attachmentFileUploader; - /// Whether the chat persistence is available or not bool get persistenceEnabled => _chatPersistenceClient != null; RetryPolicy? _retryPolicy; + // sync state of the channels present inside state, defaults to false bool _synced = false; + // the last dateTime at the which all the channels were synced + DateTime? _lastSyncedAt; + /// The retry policy options getter RetryPolicy? get retryPolicy => _retryPolicy; - /// This client state - late ClientState state; - /// By default the Chat client will write all messages with level Warn or /// Error to stdout. /// @@ -173,38 +183,13 @@ class StreamChatClient { ///``` late LogHandlerFunction logHandlerFunction; - /// Your project Stream Chat api key. - /// Find your API keys here https://getstream.io/dashboard/ - String apiKey; - - /// Your project Stream Chat base url. - final String baseURL; - - /// A function in which you send a request to your own backend to get a Stream - /// Chat API token. - /// - /// The token will be the return value of the function. - /// It's used by the client to refresh the token once expired or to connect - /// the user without a predefined token using [connectUserWithProvider]. - final TokenProvider? tokenProvider; - - /// [Dio] httpClient - /// It's be chosen because it's easy to use and supports interesting features - /// out of the box (Interceptors, Global configuration, FormData, - /// File downloading etc.) - @visibleForTesting - Dio httpClient = Dio(); - - static const _defaultBaseURL = 'chat-us-east-1.stream-io-api.com'; - static const _tokenExpiredErrorCode = 40; StreamSubscription? _connectionStatusSubscription; - Future Function(ConnectionStatus)? _connectionStatusHandler; - final BehaviorSubject _controller = BehaviorSubject(); + final _eventController = BehaviorSubject(); /// Stream of [Event] coming from websocket connection /// Listen to this or use the [on] method to filter specific event types - Stream get stream => _controller.stream; + Stream get eventStream => _eventController.stream; final _wsConnectionStatusController = BehaviorSubject.seeded(ConnectionStatus.disconnected); @@ -219,153 +204,17 @@ class StreamChatClient { /// This notifies the connection status of the websocket connection. /// Listen to this to get notified when the websocket tries to reconnect. Stream get wsConnectionStatusStream => - _wsConnectionStatusController.stream; + _wsConnectionStatusController.stream.distinct(); - /// The current user token - String? token; - - /// The id of the current websocket connection - String? get connectionId => _connectionId; - - bool _anonymous = false; - String? _connectionId; - late WebSocket _ws; - - bool get _hasConnectionId => _connectionId != null; - - void _setupDio( - Dio? httpClient, - Duration receiveTimeout, - Duration connectTimeout, - ) { - logger.info('http client setup'); - - this.httpClient = httpClient ?? Dio(); - - String url; - if (!baseURL.startsWith('https') && !baseURL.startsWith('http')) { - url = Uri.https(baseURL, '').toString(); - } else { - url = baseURL; - } - - this.httpClient.options.baseUrl = url; - this.httpClient.options.receiveTimeout = receiveTimeout.inMilliseconds; - this.httpClient.options.connectTimeout = connectTimeout.inMilliseconds; - this.httpClient.interceptors.add( - InterceptorsWrapper( - onRequest: (options, handler) async { - options.queryParameters.addAll(_commonQueryParams); - options.headers.addAll(_httpHeaders); - - if (_connectionId != null && - (options.data is Map || options.data == null)) { - options.data = { - 'connection_id': _connectionId, - ...options.data ?? {}, - }; - } - - var stringData = options.data.toString(); - - if (options.data is FormData) { - final multiPart = (options.data as FormData).files[0].value; - stringData = '${multiPart.filename} - ${multiPart.contentType}'; - } - - logger.info(''' - - method: ${options.method} - url: ${options.uri} - headers: ${options.headers} - data: $stringData - - '''); - handler.next(options); - }, - onError: _tokenExpiredInterceptor, - ), + LogHandlerFunction get _defaultLogHandler => (LogRecord record) { + print( + '${record.time} ' + '${_levelEmojiMapper[record.level] ?? record.level.name} ' + '${record.loggerName} ${record.message} ', ); - } - - Future _tokenExpiredInterceptor( - DioError err, - ErrorInterceptorHandler handler, - ) async { - final apiError = ApiError( - err.response?.data, - err.response?.statusCode, - ); - - if (apiError.code == _tokenExpiredErrorCode) { - logger.info('token expired'); - - if (tokenProvider != null) { - httpClient.lock(); - final userId = state.user!.id; - - await _disconnect(); - - final newToken = await tokenProvider!(userId); - await Future.delayed(const Duration(seconds: 4)); - token = newToken; - - httpClient.unlock(); - - await connectUser(User(id: userId), newToken); - - try { - handler.resolve( - await httpClient.request( - err.requestOptions.path, - cancelToken: err.requestOptions.cancelToken, - data: err.requestOptions.data, - onReceiveProgress: err.requestOptions.onReceiveProgress, - onSendProgress: err.requestOptions.onSendProgress, - queryParameters: err.requestOptions.queryParameters, - options: Options( - method: err.requestOptions.method, - sendTimeout: err.requestOptions.sendTimeout, - receiveTimeout: err.requestOptions.receiveTimeout, - extra: err.requestOptions.extra, - headers: err.requestOptions.headers, - responseType: err.requestOptions.responseType, - contentType: err.requestOptions.contentType, - validateStatus: err.requestOptions.validateStatus, - receiveDataWhenStatusError: - err.requestOptions.receiveDataWhenStatusError, - followRedirects: err.requestOptions.followRedirects, - maxRedirects: err.requestOptions.maxRedirects, - requestEncoder: err.requestOptions.requestEncoder, - responseDecoder: err.requestOptions.responseDecoder, - listFormat: err.requestOptions.listFormat, - ), - ), - ); - } on DioError { - handler.reject(err); - } - } - } - } - - LogHandlerFunction _getDefaultLogHandler() { - final levelEmojiMapper = { - Level.INFO.name: 'â„šī¸', - Level.WARNING.name: 'âš ī¸', - Level.SEVERE.name: '🚨', - }; - return (LogRecord record) { - print( - '(${record.time}) ' - '${levelEmojiMapper[record.level.name] ?? record.level.name} ' - '${record.loggerName} ${record.message}', - ); - if (record.stackTrace != null) { - print(record.stackTrace); - } - }; - } + if (record.error != null) print(record.error); + if (record.stackTrace != null) print(record.stackTrace); + }; Logger _detachedLogger( String name, @@ -376,265 +225,272 @@ class StreamChatClient { void _setupLogger(LogHandlerFunction? logHandlerFunction) { logger.level = logLevel; - - this.logHandlerFunction = logHandlerFunction ?? _getDefaultLogHandler(); - + this.logHandlerFunction = logHandlerFunction ?? _defaultLogHandler; logger.onRecord.listen(this.logHandlerFunction); - logger.info('logger setup'); } - /// Call this function to dispose the client - void dispose() async { - await _chatPersistenceClient?.disconnect(); - await _disconnect(); - httpClient.close(); - await _controller.close(); - state.dispose(); - await _wsConnectionStatusController.close(); - } - - Map get _httpHeaders => { - 'Authorization': token, - 'stream-auth-type': _authType, - 'X-Stream-Client': _userAgent, - 'Content-Encoding': 'gzip', - }; - + /// Set the current user, this triggers a connection to the API. /// Set the current user, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. @Deprecated('Use `connectUser` instead. Will be removed in Future releases') - Future setUser(User user, String token) => connectUser(user, token); + Future setUser(User user, String token) => connectUser(user, token); /// Connects the current user, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. - Future connectUser(User? user, String? token) async { - if (_connectCompleter != null && !_connectCompleter!.isCompleted) { - logger.warning('Already connecting'); - throw Exception('Already connecting'); - } - - _connectCompleter = Completer(); - - logger.info('connect user'); - - if (user == null) { - final e = Error(); - _connectCompleter! - .completeError(e, StackTrace.fromString('No user provided.')); - throw e; - } - - state.user = OwnUser.fromJson(user.toJson()); - this.token = token; - _anonymous = false; - - return connect().then((event) { - _connectCompleter!.complete(event); - return event; - }).catchError((e, s) { - _connectCompleter!.completeError(e, s); - throw e; - }); - } + Future connectUser(User user, String token) => + _connectUser(user, token: Token.fromRawValue(token)); /// Set the current user using the [tokenProvider] to fetch the token. /// It returns a [Future] that resolves when the connection is setup. @Deprecated( 'Use `connectUserWithProvider` instead. Will be removed in Future releases', ) - Future setUserWithProvider(User user) => - connectUserWithProvider(user); + Future setUserWithProvider(User user, TokenProvider tokenProvider) => + connectUserWithProvider(user, tokenProvider); /// Connects the current user using the [tokenProvider] to fetch the token. /// It returns a [Future] that resolves when the connection is setup. - Future connectUserWithProvider(User user) async { - if (tokenProvider == null) { - throw Exception(''' - TokenProvider must be provided in the constructor in order to use `connectUserWithProvider` method. - Use `connectUser` providing a token. - '''); - } - final token = await tokenProvider!(user.id); - return connectUser(user, token); + Future connectUserWithProvider( + User user, TokenProvider tokenProvider) => + _connectUser(user, provider: tokenProvider); + + /// Set the current user with an anonymous id, this triggers a connection to + /// the API. It returns a [Future] that resolves when the connection is setup. + @Deprecated( + 'Use `connectAnonymousUser` instead. Will be removed in Future releases', + ) + Future setAnonymousUser() => connectAnonymousUser(); + + /// Connects the current user with an anonymous id, this triggers a connection + /// to the API. It returns a [Future] that resolves when the connection is + /// setup. + Future connectAnonymousUser() async { + final token = Token.anonymous(); + final user = OwnUser(id: token.userId); + return _connectUser(user, token: token); } - /// Stream of [Event] coming from websocket connection + /// Set the current user as guest, this triggers a connection to the API. + /// It returns a [Future] that resolves when the connection is setup. + @Deprecated( + 'Use `connectGuestUser` instead. Will be removed in Future releases', + ) + Future setGuestUser(User user) => connectGuestUser(user); + + /// Connects the current user as guest, this triggers a connection to the API. + /// It returns a [Future] that resolves when the connection is setup. + Future connectGuestUser(User user) async { + final userId = user.id; + final anonymousToken = Token.anonymous(userId: userId); + + // setting anonymous token so that getGuestUser works + _tokenManager.setTokenOrProvider(userId, token: anonymousToken); + + final guestUser = await _chatApi.guest.getGuestUser(user); + + // resetting tokenManager after successful request + _tokenManager.reset(); + + final guestUserToken = Token.fromRawValue(guestUser.accessToken); + return _connectUser(guestUser.user, token: guestUserToken); + } + + Future _connectUser( + User user, { + Token? token, + TokenProvider? provider, + }) async { + if (_ws.connectionCompleter?.isCompleted == false) { + throw Exception( + 'User already getting connected, try calling `disconnectUser` ' + 'before trying to connect again', + ); + } + + logger.info('connecting user : ${user.id}'); + + await _tokenManager.setTokenOrProvider( + user.id, + token: token, + provider: provider, + ); + + final ownUser = OwnUser.fromUser(user); + state.user = ownUser; + + try { + if (_originalChatPersistenceClient != null) { + _chatPersistenceClient = _originalChatPersistenceClient; + await _chatPersistenceClient!.connect(ownUser.id); + } + final event = await openConnection(); + return event; + } catch (e, stk) { + logger.severe('error connecting user : ${ownUser.id}', e, stk); + rethrow; + } + } + + /// Creates a new WebSocket connection with the current user. + Future openConnection() async { + assert( + state.user != null, + 'User is not set on client, ' + 'use `connectUser` or `connectAnonymousUser` instead', + ); + + final user = state.user!; + + logger.info('Opening web-socket connection for ${user.id}'); + + if (wsConnectionStatus == ConnectionStatus.connecting) { + throw Exception('Connection already in progress for ${user.id}'); + } + + if (wsConnectionStatus == ConnectionStatus.connected) { + throw Exception('Connection already connected for ${user.id}'); + } + + _wsConnectionStatus = ConnectionStatus.connecting; + + _connectionStatusSubscription = _ws.connectionStatusStream.listen( + _connectionStatusHandler, + ); + + try { + return await _ws.connect(user); + } catch (e, stk) { + logger.severe('error connecting ws', e, stk); + rethrow; + } + } + + /// Disconnects the [_ws] connection, + /// without removing the user set on client. + /// + /// This will not trigger default auto-retry mechanism for reconnection. + /// You need to call [openConnection] to reconnect to [_ws]. + void closeConnection() { + _wsConnectionStatus = ConnectionStatus.disconnected; + + _connectionStatusSubscription?.cancel(); + _connectionStatusSubscription = null; + + _ws.disconnect(); + } + + void _handleHealthCheckEvent(Event event) { + final user = event.me; + if (user != null) state.user = user; + + final connectionId = event.connectionId; + if (connectionId != null) { + _connectionIdManager.setConnectionId(connectionId); + _chatPersistenceClient?.updateConnectionInfo(event); + } + } + + /// Method called to add a new event to the [_eventController]. + void handleEvent(Event event) { + if (event.type == EventType.healthCheck) { + return _handleHealthCheckEvent(event); + } + if (!event.isLocal && _synced) { + _lastSyncedAt = event.createdAt; + _chatPersistenceClient?.updateLastSyncAt(event.createdAt); + } + state.updateUser(event.user); + return _eventController.add(event); + } + + void _connectionStatusHandler(ConnectionStatus status) async { + final currentState = _wsConnectionStatus = status; + + handleEvent(Event( + type: EventType.connectionChanged, + online: status == ConnectionStatus.connected, + )); + + if (currentState == ConnectionStatus.connected) { + // connection recovered + final cids = state.channels.keys.toList(growable: false); + if (cids.isNotEmpty) { + await queryChannelsOnline( + filter: Filter.in_('cid', cids), + paginationParams: const PaginationParams(limit: 30), + ).then((_) => sync(cids: cids, lastSyncAt: _lastSyncedAt)); + } + handleEvent(Event( + type: EventType.connectionRecovered, + online: true, + )); + } else { + _synced = false; + } + } + + /// Stream of [Event] coming from [_ws] connection /// Pass an eventType as parameter in order to filter just a type of event Stream on([ String? eventType, String? eventType2, String? eventType3, String? eventType4, - ]) => - stream.where((event) => - eventType == null || - (event.type != null && - (event.type == eventType || - event.type == eventType2 || - event.type == eventType3 || - event.type == eventType4))); - - /// Method called to add a new event to the [_controller]. - void handleEvent(Event event) async { - logger.info('handle new event: ${event.toJson()}'); - if (event.connectionId != null) { - _connectionId = event.connectionId; - } - - if (!event.isLocal) { - final createdAt = event.createdAt; - if (_synced && createdAt != null) { - await _chatPersistenceClient?.updateConnectionInfo(event); - await _chatPersistenceClient?.updateLastSyncAt(createdAt); - } - } - - if (event.user != null) { - state._updateUser(event.user); - } - - if (event.me != null) { - state.user = event.me; - } - _controller.add(event); - } - - Completer? _connectCompleter; - - /// Connect the client websocket - Future connect() async { - logger.info('connecting'); - if (wsConnectionStatus == ConnectionStatus.connecting) { - logger.warning('Already connecting'); - throw Exception('Already connecting'); - } - - if (wsConnectionStatus == ConnectionStatus.connected) { - logger.warning('Already connected'); - throw Exception('Already connected'); - } - - _wsConnectionStatus = ConnectionStatus.connecting; - - if (_originalChatPersistenceClient != null) { - _chatPersistenceClient = _originalChatPersistenceClient; - await _chatPersistenceClient!.connect(state.user!.id); - } - - _ws = WebSocket( - baseUrl: baseURL, - user: state.user!, - connectParams: { - 'api_key': apiKey, - 'authorization': token!, - 'stream-auth-type': _authType, - 'X-Stream-Client': _userAgent, - }, - connectPayload: { - 'user_id': state.user!.id, - 'server_determines_connection_id': true, - }, - handler: handleEvent, - logger: _detachedLogger('🔌'), - ); - - _connectionStatusHandler = (ConnectionStatus status) async { - _wsConnectionStatus = status; - handleEvent( - Event( - type: EventType.connectionChanged, - online: status == ConnectionStatus.connected, - ), - ); - - if (status == ConnectionStatus.connected) { - handleEvent(const Event( - type: EventType.connectionRecovered, - online: true, - )); - if (state.channels.isNotEmpty == true) { - // ignore: unawaited_futures - queryChannelsOnline( - filter: Filter.in_('cid', state.channels.keys.toList()), - ).then( - (_) async { - await resync(); - }, - ); - } else { - _synced = false; - } - } - }; - - _connectionStatusSubscription = - _ws.connectionStatusStream.listen(_connectionStatusHandler); - - var event = await _chatPersistenceClient?.getConnectionInfo(); - - await _ws.connect().then((e) async { - if (e != null) { - _chatPersistenceClient?.updateConnectionInfo(e); - event = e; - } - resync(); - }).catchError((err, stacktrace) { - logger.severe('error connecting ws', err, stacktrace); - if (err is Map) { - // ignore: only_throw_errors - throw err; - } - }); - - return event; + ]) { + if (eventType == null) return eventStream; + return eventStream.where((event) => + event.type == eventType || + event.type == eventType2 || + event.type == eventType3 || + event.type == eventType4); } /// Get the events missed while offline to sync the offline storage - Future resync([List? cids]) async { - final lastSyncAt = await _chatPersistenceClient?.getLastSyncAt(); + /// Will automatically fetch [cids] and [lastSyncedAt] if [persistenceEnabled] + Future sync({List? cids, DateTime? lastSyncAt}) async { + cids ??= await _chatPersistenceClient?.getChannelCids(); + if (cids == null || cids.isEmpty) { + _synced = true; + return; + } + lastSyncAt ??= await _chatPersistenceClient?.getLastSyncAt(); if (lastSyncAt == null) { _synced = true; return; } - cids ??= await _chatPersistenceClient?.getChannelCids(); - - if (cids?.isEmpty == true) { - return; - } - try { - final rawRes = await post('/sync', data: { - 'channel_cids': cids, - 'last_sync_at': lastSyncAt.toUtc().toIso8601String(), - }); - logger.fine('rawRes: $rawRes'); + final res = await _chatApi.general.sync(cids, lastSyncAt); + final events = res.events + ..sort((a, b) => a.createdAt.compareTo(b.createdAt)); - final res = decode( - rawRes.data, - SyncResponse.fromJson, - ); + for (final event in events) { + logger.fine('event.type: ${event.type}'); + final messageText = event.message?.text; + if (messageText != null) { + logger.fine('event.message.text: $messageText'); + } + handleEvent(event); + } - res.events.sort((a, b) => a.createdAt!.compareTo(b.createdAt!)); - - res.events.forEach((element) { - logger - ..fine('element.type: ${element.type}') - ..fine('element.message.text: ${element.message?.text}'); - }); - - res.events.forEach(handleEvent); - - await _chatPersistenceClient?.updateLastSyncAt(DateTime.now()); _synced = true; - } catch (error) { - logger.severe('Error during resync $error'); + final now = DateTime.now(); + _lastSyncedAt = now; + _chatPersistenceClient?.updateLastSyncAt(now); + } catch (e, stk) { + _synced = false; + logger.severe('Error during sync', e, stk); } } - String? _asMap(sort) => sort?.map((s) => s.toJson().toString())?.join(''); + String _generateHash(List objects) { + final payload = json.encode(objects); + final payloadBytes = utf8.encode(payload); + final payloadB64 = base64.encode(payloadBytes); + return payloadB64; + } final _queryChannelsStreams = >>{}; @@ -642,15 +498,29 @@ class StreamChatClient { Stream> queryChannels({ Filter? filter, List>? sort, - Map? options, - PaginationParams paginationParams = const PaginationParams(), + bool state = true, + bool watch = true, + bool presence = false, + int? memberLimit, int? messageLimit, + PaginationParams paginationParams = const PaginationParams(), bool waitForConnect = true, }) async* { - final hash = base64.encode(utf8.encode( - '$filter${_asMap(sort)}$options${paginationParams.toJson()}' - '$messageLimit', - )); + if (!_connectionIdManager.hasConnectionId) { + // ignore: parameter_assignments + watch = false; + } + + final hash = _generateHash([ + filter, + sort, + state, + watch, + presence, + memberLimit, + messageLimit, + paginationParams, + ]); if (_queryChannelsStreams.containsKey(hash)) { yield await _queryChannelsStreams[hash]!; @@ -666,9 +536,12 @@ class StreamChatClient { final newQueryChannelsFuture = queryChannelsOnline( filter: filter, sort: sort, - options: options, - paginationParams: paginationParams, + state: state, + watch: watch, + presence: presence, + memberLimit: memberLimit, messageLimit: messageLimit, + paginationParams: paginationParams, waitForConnect: waitForConnect, ).whenComplete(() { _queryChannelsStreams.remove(hash); @@ -687,67 +560,52 @@ class StreamChatClient { Future> queryChannelsOnline({ Filter? filter, List>? sort, - Map? options, + bool state = true, + bool watch = true, + bool presence = false, + int? memberLimit, int? messageLimit, - PaginationParams paginationParams = const PaginationParams(), bool waitForConnect = true, + PaginationParams paginationParams = const PaginationParams(), }) async { if (waitForConnect) { - if (_connectCompleter != null && !_connectCompleter!.isCompleted) { + if (_ws.connectionCompleter?.isCompleted == false) { logger.info('awaiting connection completer'); - await _connectCompleter!.future; + await _ws.connectionCompleter?.future; } if (wsConnectionStatus != ConnectionStatus.connected) { throw Exception( - 'You cannot use queryChannels without an active connection.' - ' Please call `connectUser` to connect the client.', + 'You cannot use queryChannels without an active connection. ' + 'Please call `connectUser` to connect the client.', ); } } + if (!_connectionIdManager.hasConnectionId) { + // ignore: parameter_assignments + watch = false; + } + logger.info('Query channel start'); - final defaultOptions = { - 'state': true, - 'watch': true, - 'presence': false, - }; - - final payload = { - 'filter_conditions': filter, - 'sort': sort, - }; - - if (messageLimit != null) { - payload['message_limit'] = messageLimit; - } - - payload.addAll(defaultOptions); - - if (options != null) { - payload.addAll(options); - } - - payload.addAll(paginationParams.toJson()); - - final response = await get( - '/channels', - queryParameters: { - 'payload': jsonEncode(payload), - }, - ); - - final res = decode( - response.data, - QueryChannelsResponse.fromJson, + print('Query Channel Started : ${DateTime.now()}'); + final res = await _chatApi.channel.queryChannels( + filter: filter, + sort: sort, + state: state, + watch: watch, + presence: presence, + memberLimit: memberLimit, + messageLimit: messageLimit, + paginationParams: paginationParams, ); + print('Query Channel Completed : ${DateTime.now()}'); if (res.channels.isEmpty && paginationParams.offset == 0) { - logger.warning( - ''' + logger.warning(''' We could not find any channel for this query. - Please make sure to take a look at the Flutter tutorial: https://getstream.io/chat/flutter/tutorial - If your application already has users and channels, you might need to adjust your query channel as explained in the docs https://getstream.io/chat/docs/query_channels/?language=dart''', - ); + Please make sure to take a look at the Flutter tutorial: https://getstream.io/chat/flutter/tutorial + If your application already has users and channels, you might need to adjust your query channel as explained in the docs https://getstream.io/chat/docs/query_channels/?language=dart + '''); return []; } @@ -758,7 +616,7 @@ class StreamChatClient { .map((it) => it.user) .toList(growable: false); - state._updateUsers(users); + this.state.updateUsers(users); logger.info('Got ${res.channels.length} channels from api'); @@ -770,7 +628,7 @@ class StreamChatClient { clearQueryCache: paginationParams.offset == 0, ); - state.channels = updateData.key; + this.state.channels = updateData.key; return updateData.value; } @@ -812,253 +670,20 @@ class StreamChatClient { return MapEntry(channels, newChannels); } - Object _parseError(DioError error) { - if (error.type == DioErrorType.response) { - final apiError = - ApiError(error.response?.data, error.response?.statusCode); - logger.severe('apiError: ${apiError.toString()}'); - return apiError; - } - - return error; - } - - /// Handy method to make http GET request with error parsing. - Future> get( - String path, { - Map? queryParameters, - }) async { - try { - final response = await httpClient.get( - path, - queryParameters: queryParameters, - ); - return response; - } on DioError catch (error) { - // ignore: only_throw_errors - throw _parseError(error); - } - } - - /// Handy method to make http POST request with error parsing. - Future> post( - String path, { - dynamic data, - ProgressCallback? onSendProgress, - CancelToken? cancelToken, - }) async { - try { - final response = await httpClient.post( - path, - data: data, - onSendProgress: onSendProgress, - cancelToken: cancelToken, - ); - return response; - } on DioError catch (error) { - // ignore: only_throw_errors - throw _parseError(error); - } - } - - /// Handy method to make http DELETE request with error parsing. - Future> delete( - String path, { - Map? queryParameters, - CancelToken? cancelToken, - }) async { - try { - final response = await httpClient.delete( - path, - queryParameters: queryParameters, - cancelToken: cancelToken, - ); - return response; - } on DioError catch (error) { - // ignore: only_throw_errors - throw _parseError(error); - } - } - - /// Handy method to make http PATCH request with error parsing. - Future> patch( - String path, { - Map? queryParameters, - dynamic data, - }) async { - try { - final response = await httpClient.patch( - path, - queryParameters: queryParameters, - data: data, - ); - return response; - } on DioError catch (error) { - // ignore: only_throw_errors - throw _parseError(error); - } - } - - /// Handy method to make http PUT request with error parsing. - Future> put( - String path, { - Map? queryParameters, - dynamic data, - }) async { - try { - final response = await httpClient.put( - path, - queryParameters: queryParameters, - data: data, - ); - return response; - } on DioError catch (error) { - // ignore: only_throw_errors - throw _parseError(error); - } - } - - /// Used to log errors and stacktrace in case of bad json deserialization - T decode(String? j, DecoderFunction decoderFunction) { - try { - final data = j ?? '{}'; - return decoderFunction(json.decode(data)); - } catch (error, stacktrace) { - logger.severe('Error decoding response', error, stacktrace); - rethrow; - } - } - - String get _authType => _anonymous ? 'anonymous' : 'jwt'; - - String get _userAgent => 'stream-chat-dart-client-${CurrentPlatform.name}-' - '${PACKAGE_VERSION.split('+')[0]}'; - - Map get _commonQueryParams => { - 'user_id': state.user?.id, - 'api_key': apiKey, - 'connection_id': _connectionId, - }; - - /// Set the current user with an anonymous id, this triggers a connection to - /// the API. It returns a [Future] that resolves when the connection is setup. - @Deprecated( - 'Use `connectAnonymousUser` instead. Will be removed in Future releases') - Future setAnonymousUser() => connectAnonymousUser(); - - /// Connects the current user with an anonymous id, this triggers a connection - /// to the API. It returns a [Future] that resolves when the connection is - /// setup. - Future connectAnonymousUser() async { - if (_connectCompleter != null && !_connectCompleter!.isCompleted) { - logger.warning('Already connecting'); - throw Exception('Already connecting'); - } - - _connectCompleter = Completer(); - - _anonymous = true; - const uuid = Uuid(); - state.user = OwnUser(id: uuid.v4()); - - return connect().then((event) { - _connectCompleter!.complete(event); - return event; - }).catchError((e, s) { - _connectCompleter!.completeError(e, s); - throw e; - }); - } - - /// Set the current user as guest, this triggers a connection to the API. - /// It returns a [Future] that resolves when the connection is setup. - @Deprecated( - 'Use `connectGuestUser` instead. Will be removed in Future releases') - Future setGuestUser(User user) => connectGuestUser(user); - - /// Connects the current user as guest, this triggers a connection to the API. - /// It returns a [Future] that resolves when the connection is setup. - Future connectGuestUser(User user) async { - _anonymous = true; - final response = await post('/guest', data: {'user': user.toJson()}) - .then((res) => decode( - res.data, ConnectGuestUserResponse.fromJson)) - .whenComplete(() => _anonymous = false); - - return connectUser( - response.user, - response.accessToken, - ); - } - - /// Closes the websocket connection and resets the client - /// If [flushChatPersistence] is true the client deletes all offline - /// user's data. If [clearUser] is true the client unsets the current user - Future disconnect({ - bool flushChatPersistence = false, - bool clearUser = false, - }) async { - logger.info('Disconnecting flushOfflineStorage: $flushChatPersistence; ' - 'clearUser: $clearUser'); - - await _chatPersistenceClient?.disconnect(flush: flushChatPersistence); - _chatPersistenceClient = null; - - _connectCompleter = null; - - if (clearUser == true) { - state.dispose(); - state = ClientState(this); - } - - await _disconnect(); - } - - Future _disconnect() async { - logger.info('Client disconnecting'); - - await _ws.disconnect(); - await _connectionStatusSubscription?.cancel(); - } - /// Requests users with a given query. Future queryUsers({ + bool? presence, Filter? filter, List? sort, - Map? options, PaginationParams? pagination, }) async { - final defaultOptions = { - 'presence': _hasConnectionId, - }; - - final payload = { - 'filter_conditions': filter, - 'sort': sort, - }..addAll(defaultOptions); - - if (pagination != null) { - payload.addAll(pagination.toJson()); - } - - if (options != null) { - payload.addAll(options); - } - - final rawRes = await get( - '/users', - queryParameters: { - 'payload': jsonEncode(payload), - }, + final response = await _chatApi.user.queryUsers( + presence: presence ?? _connectionIdManager.hasConnectionId, + filter: filter, + sort: sort, + pagination: pagination, ); - - final response = decode( - rawRes.data, - QueryUsersResponse.fromJson, - ); - - state._updateUsers(response.users); - + state.updateUsers(response.users); return response; } @@ -1069,34 +694,14 @@ class StreamChatClient { List? sort, PaginationParams? paginationParams, Filter? messageFilters, - }) async { - assert(() { - if (query == null && messageFilters == null) { - throw ArgumentError('Provide at least `query` or `messageFilters`'); - } - if (query != null && messageFilters != null) { - throw ArgumentError( - "Can't provide both `query` and `messageFilters` at the same time", - ); - } - return true; - }(), 'Check incoming params.'); - - final payload = { - 'filter_conditions': filter, - 'message_filter_conditions': messageFilters, - 'query': query, - 'sort': sort, - if (paginationParams != null) ...paginationParams.toJson(), - }.nullProtected; - - final response = await get('/search', queryParameters: { - 'payload': json.encode(payload), - }); - - return decode( - response.data, SearchMessagesResponse.fromJson); - } + }) => + _chatApi.general.searchMessages( + filter, + query: query, + sort: sort, + pagination: paginationParams, + messageFilters: messageFilters, + ); /// Send a [file] to the [channelId] of type [channelType] Future sendFile( @@ -1106,7 +711,7 @@ class StreamChatClient { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) => - attachmentFileUploader!.sendFile( + _chatApi.fileUploader.sendFile( file, channelId, channelType, @@ -1122,7 +727,7 @@ class StreamChatClient { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) => - attachmentFileUploader!.sendImage( + _chatApi.fileUploader.sendImage( image, channelId, channelType, @@ -1137,7 +742,7 @@ class StreamChatClient { String channelType, { CancelToken? cancelToken, }) => - attachmentFileUploader!.deleteFile( + _chatApi.fileUploader.deleteFile( url, channelId, channelType, @@ -1151,44 +756,52 @@ class StreamChatClient { String channelType, { CancelToken? cancelToken, }) => - attachmentFileUploader!.deleteImage( + _chatApi.fileUploader.deleteImage( url, channelId, channelType, cancelToken: cancelToken, ); + /// Replaces the [channelId] of type [ChannelType] data with [data] + Future updateChannel( + String channelId, + String channelType, + Map data, { + Message? message, + }) => + _chatApi.channel.updateChannel( + channelId, + channelType, + data, + message: message, + ); + + /// Updates the [channelId] of type [ChannelType] data with [data] + Future updateChannelPartial( + String channelId, + String channelType, + Map data, + ) => + _chatApi.channel.updateChannelPartial( + channelId, + channelType, + data, + ); + /// Add a device for Push Notifications. - Future addDevice(String id, PushProvider pushProvider) async { - final response = await post('/devices', data: { - 'id': id, - 'push_provider': pushProvider.name, - }); - return decode(response.data, EmptyResponse.fromJson); - } + Future addDevice(String id, PushProvider pushProvider) => + _chatApi.device.addDevice(id, pushProvider); /// Gets a list of user devices. - Future getDevices() async { - final response = await get('/devices'); - return decode( - response.data, ListDevicesResponse.fromJson); - } + Future getDevices() => _chatApi.device.getDevices(); /// Remove a user's device. - Future removeDevice(String id) async { - final response = await delete('/devices', queryParameters: { - 'id': id, - }); - return decode(response.data, EmptyResponse.fromJson); - } + Future removeDevice(String id) => + _chatApi.device.removeDevice(id); /// Get a development token - String devToken(String userId) { - final payload = json.encode({'user_id': userId}); - final payloadBytes = utf8.encode(payload); - final payloadB64 = base64.encode(payloadBytes); - return 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.$payloadB64.devtoken'; - } + String devToken(String userId) => Token.development(userId).rawValue; /// Returns a channel client with the given type, id and custom data. Channel channel( @@ -1202,58 +815,268 @@ class StreamChatClient { return Channel(this, type, id, extraData: extraData); } + /// Creates a new channel + Future createChannel( + String channelType, { + String? channelId, + Map? channelData, + }) => + queryChannel( + channelType, + channelId: channelId, + state: false, + channelData: channelData, + ); + + /// watches the provided channel + Future watchChannel( + String channelType, { + String? channelId, + Map? channelData, + }) => + queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: channelData, + ); + + /// Query the API, get messages, members or other channel fields + Future queryChannel( + String channelType, { + bool state = true, + bool watch = false, + bool presence = false, + String? channelId, + Map? channelData, + PaginationParams? messagesPagination, + PaginationParams? membersPagination, + PaginationParams? watchersPagination, + }) => + _chatApi.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: state, + watch: watch, + presence: presence, + messagesPagination: messagesPagination, + membersPagination: membersPagination, + watchersPagination: watchersPagination, + ); + + /// Query channel members + Future queryMembers( + String channelType, { + Filter? filter, + String? channelId, + List? members, + List? sort, + PaginationParams? pagination, + }) => + _chatApi.general.queryMembers( + channelType, + channelId: channelId, + filter: filter, + members: members, + sort: sort, + pagination: pagination, + ); + + /// Hides the channel from [queryChannels] for the user + /// until a message is added If [clearHistory] is set to true - all messages + /// will be removed for the user + Future hideChannel( + String channelId, + String channelType, { + bool clearHistory = false, + }) => + _chatApi.channel.hideChannel( + channelId, + channelType, + clearHistory: clearHistory, + ); + + /// Removes the hidden status for the channel + Future showChannel( + String channelId, + String channelType, + ) => + _chatApi.channel.showChannel( + channelId, + channelType, + ); + + /// Delete this channel. Messages are permanently removed. + Future deleteChannel( + String channelId, + String channelType, + ) => + _chatApi.channel.deleteChannel( + channelId, + channelType, + ); + + /// Removes all messages from the channel + Future truncateChannel( + String channelId, + String channelType, + ) => + _chatApi.channel.truncateChannel( + channelId, + channelType, + ); + + /// Mutes the channel + Future muteChannel( + String channelCid, { + Duration? expiration, + }) => + _chatApi.moderation.muteChannel( + channelCid, + expiration: expiration, + ); + + /// Unmutes the channel + Future unmuteChannel(String channelCid) => + _chatApi.moderation.unmuteChannel(channelCid); + + /// Accept invitation to the channel + Future acceptChannelInvite( + String channelId, + String channelType, { + Message? message, + }) => + _chatApi.channel.acceptChannelInvite( + channelId, + channelType, + message: message, + ); + + /// Reject invitation to the channel + Future rejectChannelInvite( + String channelId, + String channelType, { + Message? message, + }) => + _chatApi.channel.rejectChannelInvite( + channelId, + channelType, + message: message, + ); + + /// Add members to the channel + Future addChannelMembers( + String channelId, + String channelType, + List memberIds, { + Message? message, + }) => + _chatApi.channel.addMembers( + channelId, + channelType, + memberIds, + message: message, + ); + + /// Remove members from the channel + Future removeChannelMembers( + String channelId, + String channelType, + List memberIds, { + Message? message, + }) => + _chatApi.channel.removeMembers( + channelId, + channelType, + memberIds, + message: message, + ); + + /// Invite members to the channel + Future inviteChannelMembers( + String channelId, + String channelType, + List memberIds, { + Message? message, + }) => + _chatApi.channel.inviteChannelMembers( + channelId, + channelType, + memberIds, + message: message, + ); + + /// Stop watching the channel + Future stopChannelWatching( + String channelId, + String channelType, + ) => + _chatApi.channel.stopWatching( + channelId, + channelType, + ); + + /// Send action for a specific message of this channel + Future sendAction( + String channelId, + String channelType, + String messageId, + Map formData, + ) => + _chatApi.message.sendAction( + channelId, + channelType, + messageId, + formData, + ); + + /// Mark [channelId] of type [channelType] all messages as read + /// Optionally provide a [messageId] if you want to mark a + /// particular message as read + Future markChannelRead( + String channelId, + String channelType, { + String? messageId, + }) => + _chatApi.channel.markRead( + channelId, + channelType, + messageId: messageId, + ); + /// Update or Create the given user object. - Future updateUser(User user) async => - updateUsers([user]); + Future updateUser(User user) => updateUsers([user]); /// Batch update a list of users - Future updateUsers(List users) async { - final response = await post('/users', data: { - 'users': users.asMap().map((_, u) => MapEntry(u.id, u.toJson())), - }); - return decode( - response.data, - UpdateUsersResponse.fromJson, - ); - } + Future updateUsers(List users) => + _chatApi.user.updateUsers(users); /// Bans a user from all channels Future banUser( - String targetUserID, [ + String targetUserId, [ Map options = const {}, - ]) async { - final data = Map.from(options) - ..addAll({ - 'target_user_id': targetUserID, - }); - final response = await post( - '/moderation/ban', - data: data, - ); - return decode(response.data, EmptyResponse.fromJson); - } + ]) => + _chatApi.moderation.banUser( + targetUserId, + options: options, + ); /// Remove global ban for a user Future unbanUser( - String targetUserID, [ + String targetUserId, [ Map options = const {}, - ]) async { - final data = Map.from(options) - ..addAll({ - 'target_user_id': targetUserID, - }); - final response = await delete( - '/moderation/ban', - queryParameters: data, - ); - return decode(response.data, EmptyResponse.fromJson); - } + ]) => + _chatApi.moderation.unbanUser( + targetUserId, + options: options, + ); /// Shadow bans a user Future shadowBan( String targetID, [ Map options = const {}, - ]) async => + ]) => banUser(targetID, { 'shadow': true, ...options, @@ -1263,124 +1086,166 @@ class StreamChatClient { Future removeShadowBan( String targetID, [ Map options = const {}, - ]) async => + ]) => unbanUser(targetID, { 'shadow': true, ...options, }); /// Mutes a user - Future muteUser(String targetID) async { - final response = await post('/moderation/mute', data: { - 'target_id': targetID, - }); - return decode(response.data, EmptyResponse.fromJson); - } + Future muteUser(String userId) => + _chatApi.moderation.muteUser(userId); /// Unmutes a user - Future unmuteUser(String targetID) async { - final response = await post('/moderation/unmute', data: { - 'target_id': targetID, - }); - return decode(response.data, EmptyResponse.fromJson); - } + Future unmuteUser(String userId) => + _chatApi.moderation.unmuteUser(userId); /// Flag a message - Future flagMessage(String messageID) async { - final response = await post('/moderation/flag', data: { - 'target_message_id': messageID, - }); - return decode(response.data, EmptyResponse.fromJson); - } + Future flagMessage(String messageId) => + _chatApi.moderation.flagMessage(messageId); /// Unflag a message - Future unflagMessage(String messageId) async { - final response = await post('/moderation/unflag', data: { - 'target_message_id': messageId, - }); - return decode(response.data, EmptyResponse.fromJson); - } + Future unflagMessage(String messageId) => + _chatApi.moderation.unflagMessage(messageId); /// Flag a user - Future flagUser(String userId) async { - final response = await post('/moderation/flag', data: { - 'target_user_id': userId, - }); - return decode(response.data, EmptyResponse.fromJson); - } + Future flagUser(String userId) => + _chatApi.moderation.flagUser(userId); /// Unflag a message - Future unflagUser(String userId) async { - final response = await post('/moderation/unflag', data: { - 'target_user_id': userId, - }); - return decode(response.data, EmptyResponse.fromJson); - } + Future unflagUser(String userId) => + _chatApi.moderation.unflagUser(userId); /// Mark all channels for this user as read - Future markAllRead() async { - final response = await post('/channels/read'); - return decode(response.data, EmptyResponse.fromJson); - } + Future markAllRead() => _chatApi.channel.markAllRead(); + + /// Send an event to a particular channel + Future sendEvent( + String channelId, + String channelType, + Event event, + ) => + _chatApi.channel.sendEvent( + channelId, + channelType, + event, + ); + + /// Send a [reactionType] for this [messageId] + /// Set [enforceUnique] to true to remove the existing user reaction + Future sendReaction( + String messageId, + String reactionType, { + Map extraData = const {}, + bool enforceUnique = false, + }) => + _chatApi.message.sendReaction( + messageId, + reactionType, + extraData: extraData, + enforceUnique: enforceUnique, + ); + + /// Delete a [reactionType] from this [messageId] + Future deleteReaction( + String messageId, + String reactionType, + ) => + _chatApi.message.deleteReaction( + messageId, + reactionType, + ); /// Sends the message to the given channel Future sendMessage( Message message, String channelId, String channelType, - ) async { - final response = await post( - '/channels/$channelType/$channelId/message', - data: {'message': message.toJson()}, - ); - return decode(response.data, SendMessageResponse.fromJson); - } + ) => + _chatApi.message.sendMessage( + channelId, + channelType, + message, + ); + + /// Lists all the message replies for the [parentId] + Future getReplies( + String parentId, + PaginationParams options, + ) => + _chatApi.message.getReplies( + parentId, + options, + ); + + /// Get all the reactions for a [messageId] + Future getReactions( + String messageId, + PaginationParams options, + ) => + _chatApi.message.getReactions( + messageId, + options, + ); /// Update the given message - Future updateMessage(Message message) async { - final response = await post( - '/messages/${message.id}', - data: {'message': message.toJson()}, - ); - return decode(response.data, UpdateMessageResponse.fromJson); - } + Future updateMessage(Message message) => + _chatApi.message.updateMessage(message); /// Deletes the given message - Future deleteMessage(Message message) async { - final response = await delete('/messages/${message.id}'); - return decode(response.data, EmptyResponse.fromJson); - } + Future deleteMessage(String messageId) => + _chatApi.message.deleteMessage(messageId); - /// Get a message by id - Future getMessage(String messageId) async { - final response = await get('/messages/$messageId'); - return decode(response.data, GetMessageResponse.fromJson); - } + /// Get a message by [messageId] + Future getMessage(String messageId) => + _chatApi.message.getMessage(messageId); + + /// Retrieves a list of messages by [messageIDs] + /// from the given [channelId] of type [channelType] + Future getMessagesById( + String channelId, + String channelType, + List messageIDs, + ) => + _chatApi.message.getMessagesById( + channelId, + channelType, + messageIDs, + ); + + /// Translates the [messageId] in provided [language] + Future translateMessage( + String messageId, + String language, + ) => + _chatApi.message.translateMessage( + messageId, + language, + ); /// Pins provided message /// [timeoutOrExpirationDate] can either be a [DateTime] or a value in seconds /// to be added to [DateTime.now] Future pinMessage( - Message message, - Object timeoutOrExpirationDate, - ) { + Message message, { + Object? /*num|DateTime*/ timeoutOrExpirationDate, + }) { assert(() { if (timeoutOrExpirationDate is! DateTime && + timeoutOrExpirationDate != null && timeoutOrExpirationDate is! num) { throw ArgumentError('Invalid timeout or Expiration date'); } return true; - }(), 'Check whether time out is valid'); + }(), 'Check for invalid timeout or expiration date'); DateTime? pinExpires; if (timeoutOrExpirationDate is DateTime) { - pinExpires = timeoutOrExpirationDate.toUtc(); + pinExpires = timeoutOrExpirationDate; } else if (timeoutOrExpirationDate is num) { - pinExpires = DateTime.now() - .add( - Duration(seconds: timeoutOrExpirationDate.toInt()), - ) - .toUtc(); + pinExpires = DateTime.now().add( + Duration(seconds: timeoutOrExpirationDate.toInt()), + ); } return updateMessage( message.copyWith(pinned: true, pinExpires: pinExpires), @@ -1390,6 +1255,39 @@ class StreamChatClient { /// Unpins provided message Future unpinMessage(Message message) => updateMessage(message.copyWith(pinned: false)); + + /// Closes the [_ws] connection and resets the [state] + /// If [flushChatPersistence] is true the client deletes all offline + /// user's data. + Future disconnectUser({bool flushChatPersistence = false}) async { + // resetting state + state.dispose(); + state = ClientState(this); + + // resetting credentials + _tokenManager.reset(); + _connectionIdManager.reset(); + + // disconnecting persistence client + await _chatPersistenceClient?.disconnect(flush: flushChatPersistence); + _chatPersistenceClient = null; + + // closing web-socket connection + closeConnection(); + } + + /// Call this function to dispose the client + Future dispose() async { + state.dispose(); + await _eventController.close(); + await _wsConnectionStatusController.close(); + + // disconnecting persistence client + await _chatPersistenceClient?.disconnect(); + + // closing web-socket connection + closeConnection(); + } } /// The class that handles the state of the channel listening to the events @@ -1453,7 +1351,7 @@ class ClientState { if (event.user!.id == user!.id) { user = OwnUser.fromJson(event.user!.toJson()); } - _updateUser(event.user); + updateUser(event.user); })); } @@ -1478,15 +1376,16 @@ class ClientState { _userController.add(user); } - void _updateUsers(List userList) { + void updateUsers(List userList) { final newUsers = { ...users, - for (var user in userList) user!.id: user, + for (var user in userList) + if (user != null) user.id: user, }; _usersController.add(newUsers); } - void _updateUser(User? user) => _updateUsers([user]); + void updateUser(User? user) => updateUsers([user]); /// The current user OwnUser? get user => _userController.value; @@ -1495,10 +1394,10 @@ class ClientState { Stream get userStream => _userController.stream; /// The current user - Map get users => _usersController.value!; + Map get users => _usersController.value!; /// The current user as a stream - Stream> get usersStream => _usersController.stream; + Stream> get usersStream => _usersController.stream; /// The current unread channels count int? get unreadChannels => _unreadChannelsController.value; @@ -1513,23 +1412,21 @@ class ClientState { Stream get totalUnreadCountStream => _totalUnreadCountController.stream; /// The current list of channels in memory as a stream - Stream?> get channelsStream => - _channelsController.stream; + Stream> get channelsStream => _channelsController.stream; /// The current list of channels in memory Map get channels => _channelsController.value!; - set channels(Map v) { - _channelsController.add(v); + set channels(Map channelMap) { + final newChannels = {...channels, ...channelMap}; + _channelsController.add(newChannels); } - final BehaviorSubject> _channelsController = - BehaviorSubject.seeded({}); - final BehaviorSubject _userController = BehaviorSubject(); - final BehaviorSubject> _usersController = - BehaviorSubject.seeded({}); - final BehaviorSubject _unreadChannelsController = BehaviorSubject(); - final BehaviorSubject _totalUnreadCountController = BehaviorSubject(); + final _channelsController = BehaviorSubject>.seeded({}); + final _userController = BehaviorSubject(); + final _usersController = BehaviorSubject>.seeded({}); + final _unreadChannelsController = BehaviorSubject(); + final _totalUnreadCountController = BehaviorSubject(); /// Call this method to dispose this object void dispose() { diff --git a/packages/stream_chat/lib/src/attachment_file_uploader.dart b/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart similarity index 90% rename from packages/stream_chat/lib/src/attachment_file_uploader.dart rename to packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart index d00dd4e7..272188af 100644 --- a/packages/stream_chat/lib/src/attachment_file_uploader.dart +++ b/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart @@ -1,8 +1,8 @@ import 'package:dio/dio.dart'; -import 'package:stream_chat/src/api/responses.dart'; -import 'package:stream_chat/src/client.dart'; +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/extensions/string_extension.dart'; -import 'package:stream_chat/src/models/attachment_file.dart'; +import 'package:stream_chat/src/core/models/attachment_file.dart'; /// Class responsible for uploading images and files to a given channel abstract class AttachmentFileUploader { @@ -60,7 +60,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { /// Creates a new [StreamAttachmentFileUploader] instance. const StreamAttachmentFileUploader(this._client); - final StreamChatClient _client; + final StreamHttpClient _client; @override Future sendImage( @@ -96,7 +96,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { onSendProgress: onSendProgress, cancelToken: cancelToken, ); - return _client.decode(response.data, SendImageResponse.fromJson); + return SendImageResponse.fromJson(response.data); } @override @@ -133,7 +133,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { onSendProgress: onSendProgress, cancelToken: cancelToken, ); - return _client.decode(response.data, SendFileResponse.fromJson); + return SendFileResponse.fromJson(response.data); } @override @@ -148,7 +148,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { queryParameters: {'url': url}, cancelToken: cancelToken, ); - return _client.decode(response.data, EmptyResponse.fromJson); + return EmptyResponse.fromJson(response.data); } @override @@ -163,6 +163,6 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { queryParameters: {'url': url}, cancelToken: cancelToken, ); - return _client.decode(response.data, EmptyResponse.fromJson); + return EmptyResponse.fromJson(response.data); } } diff --git a/packages/stream_chat/lib/src/core/api/channel_api.dart b/packages/stream_chat/lib/src/core/api/channel_api.dart new file mode 100644 index 00000000..7407bcb5 --- /dev/null +++ b/packages/stream_chat/lib/src/core/api/channel_api.dart @@ -0,0 +1,293 @@ +import 'dart:convert'; + +import 'package:stream_chat/src/core/api/requests.dart'; +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/channel_state.dart'; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/filter.dart'; +import 'package:stream_chat/src/core/models/message.dart'; + +/// +class ChannelApi { + /// + ChannelApi(this._client); + + final StreamHttpClient _client; + + String _getChannelUrl(String channelId, String channelType) => + '/channels/$channelType/$channelId'; + + /// Query the API, get messages, members or other channel fields + Future queryChannel( + String channelType, { + bool state = true, + bool watch = false, + bool presence = false, + String? channelId, + Map? channelData, + PaginationParams? messagesPagination, + PaginationParams? membersPagination, + PaginationParams? watchersPagination, + }) async { + var channelPath = '/channels/$channelType'; + if (channelId != null) channelPath = '$channelPath/$channelId'; + final response = await _client.post( + '$channelPath/query', + data: { + 'state': state, + 'watch': watch, + 'presence': presence, + if (channelData != null) 'data': channelData, + if (messagesPagination != null) 'messages': messagesPagination, + if (membersPagination != null) 'members': membersPagination, + if (watchersPagination != null) 'watchers': watchersPagination, + }, + ); + return ChannelState.fromJson(response.data); + } + + /// Requests channels with a given query from the API. + Future queryChannels({ + Filter? filter, + List>? sort, + int? memberLimit, + int? messageLimit, + bool state = true, + bool watch = true, + bool presence = false, + PaginationParams paginationParams = const PaginationParams(), + }) async { + print('Query Channel Started 2 : ${DateTime.now()}'); + final response = await _client.get( + '/channels', + queryParameters: { + 'payload': jsonEncode({ + // default options + 'state': state, + 'watch': watch, + 'presence': presence, + + // passed options + if (sort != null) 'sort': sort, + if (filter != null) 'filter_conditions': filter, + if (memberLimit != null) 'member_limit': memberLimit, + if (messageLimit != null) 'message_limit': messageLimit, + + // pagination + ...paginationParams.toJson() + }) + }, + ); + print('Query Channel Completed 2 : ${DateTime.now()}'); + return QueryChannelsResponse.fromJson(response.data); + } + + /// Mark all channels for this user as read + Future markAllRead() async { + final response = await _client.post('channels/read'); + return EmptyResponse.fromJson(response.data); + } + + /// Replaces the [channelId] of type [ChannelType] data with [data] + Future updateChannel( + String channelId, + String channelType, + Map data, { + Message? message, + }) async { + final response = await _client.post( + _getChannelUrl(channelId, channelType), + data: { + 'data': data, + if (message != null) + 'message': message.copyWith(updatedAt: DateTime.now()), + }, + ); + return UpdateChannelResponse.fromJson(response.data); + } + + /// Updates the [channelId] of type [ChannelType] data with [data] + Future updateChannelPartial( + String channelId, + String channelType, + Map data, + ) async { + final response = await _client.patch( + _getChannelUrl(channelId, channelType), + data: data, + ); + return PartialUpdateChannelResponse.fromJson(response.data); + } + + /// Accept invitation to the channel + Future acceptChannelInvite( + String channelId, + String channelType, { + Message? message, + }) async { + final response = await _client.post( + _getChannelUrl(channelId, channelType), + data: { + 'accept_invite': true, + 'message': message, + }, + ); + return AcceptInviteResponse.fromJson(response.data); + } + + /// Reject invitation to the channel + Future rejectChannelInvite( + String channelId, + String channelType, { + Message? message, + }) async { + final response = await _client.post( + _getChannelUrl(channelId, channelType), + data: { + 'reject_invite': true, + 'message': message, + }, + ); + return RejectInviteResponse.fromJson(response.data); + } + + /// Invite members to the channel + Future inviteChannelMembers( + String channelId, + String channelType, + List memberIds, { + Message? message, + }) async { + final response = await _client.post( + _getChannelUrl(channelId, channelType), + data: { + 'invites': memberIds, + 'message': message, + }, + ); + return InviteMembersResponse.fromJson(response.data); + } + + /// Add members to the channel + Future addMembers( + String channelId, + String channelType, + List memberIds, { + Message? message, + }) async { + final response = await _client.post( + _getChannelUrl(channelId, channelType), + data: { + 'add_members': memberIds, + 'message': message, + }, + ); + return AddMembersResponse.fromJson(response.data); + } + + /// Remove members from the channel + Future removeMembers( + String channelId, + String channelType, + List memberIds, { + Message? message, + }) async { + final response = await _client.post( + _getChannelUrl(channelId, channelType), + data: { + 'remove_members': memberIds, + 'message': message, + }, + ); + return RemoveMembersResponse.fromJson(response.data); + } + + /// Send an event on this channel + Future sendEvent( + String channelId, + String channelType, + Event event, + ) async { + final response = await _client.post( + '${_getChannelUrl(channelId, channelType)}/event', + data: {'event': event}, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Delete this channel. Messages are permanently removed. + Future deleteChannel( + String channelId, + String channelType, + ) async { + final response = await _client.delete( + _getChannelUrl(channelId, channelType), + ); + return EmptyResponse.fromJson(response.data); + } + + /// Removes all messages from the channel + Future truncateChannel( + String channelId, + String channelType, + ) async { + final response = await _client.post( + '${_getChannelUrl(channelId, channelType)}/truncate', + ); + return EmptyResponse.fromJson(response.data); + } + + /// Hides the channel from [StreamChatClient.queryChannels] for the user + /// until a message is added If [clearHistory] is set to true - all messages + /// will be removed for the user + Future hideChannel( + String channelId, + String channelType, { + bool clearHistory = false, + }) async { + final response = await _client.post( + '${_getChannelUrl(channelId, channelType)}/hide', + data: {'clear_history': clearHistory}, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Removes the hidden status for the channel + Future showChannel( + String channelId, + String channelType, + ) async { + final response = await _client.post( + '${_getChannelUrl(channelId, channelType)}/show', + ); + return EmptyResponse.fromJson(response.data); + } + + /// Mark [channelId] of type [channelType] all messages as read + /// Optionally provide a [messageId] if you want to mark a + /// particular message as read + Future markRead( + String channelId, + String channelType, { + String? messageId, + }) async { + final response = await _client.post( + '${_getChannelUrl(channelId, channelType)}/read', + data: {if (messageId != null) 'message_id': messageId}, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Stop watching the channel + Future stopWatching( + String channelId, + String channelType, + ) async { + final response = await _client.post( + '${_getChannelUrl(channelId, channelType)}/stop-watching', + ); + return EmptyResponse.fromJson(response.data); + } +} diff --git a/packages/stream_chat/lib/src/core/api/device_api.dart b/packages/stream_chat/lib/src/core/api/device_api.dart new file mode 100644 index 00000000..2d5e0fea --- /dev/null +++ b/packages/stream_chat/lib/src/core/api/device_api.dart @@ -0,0 +1,59 @@ +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; + +/// Provider used to send push notifications. +enum PushProvider { + /// Send notifications using Google's Firebase Cloud Messaging + firebase, + + /// Send notifications using Apple's Push Notification service + apn +} + +extension on PushProvider { + /// Returns the string notion for [PushProvider]. + String get name => { + PushProvider.apn: 'apn', + PushProvider.firebase: 'firebase', + }[this]!; +} + +/// +class DeviceApi { + /// + DeviceApi(this._client); + + final StreamHttpClient _client; + + /// Add a device for Push Notifications. + Future addDevice( + String deviceId, + PushProvider pushProvider, + ) async { + final response = await _client.post( + '/devices', + data: { + 'id': deviceId, + 'push_provider': pushProvider.name, + }, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Gets a list of user devices. + Future getDevices() async { + final response = await _client.get('/devices'); + return ListDevicesResponse.fromJson(response.data); + } + + /// Remove a user's device. + Future removeDevice( + String deviceId, + ) async { + final response = await _client.delete( + '/devices', + queryParameters: {'id': deviceId}, + ); + return EmptyResponse.fromJson(response.data); + } +} diff --git a/packages/stream_chat/lib/src/core/api/general_api.dart b/packages/stream_chat/lib/src/core/api/general_api.dart new file mode 100644 index 00000000..d26ffc7d --- /dev/null +++ b/packages/stream_chat/lib/src/core/api/general_api.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; + +import 'package:stream_chat/src/core/api/requests.dart'; +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; +import 'package:stream_chat/src/core/models/filter.dart'; +import 'package:stream_chat/src/core/models/member.dart'; + +/// +class GeneralApi { + /// + GeneralApi(this._client); + + final StreamHttpClient _client; + + /// Get all the missed events + Future sync( + List cids, + DateTime lastSyncAt, + ) async { + final response = await _client.post( + '/sync', + data: { + 'channel_cids': cids, + 'last_sync_at': lastSyncAt.toUtc().toIso8601String(), + }, + ); + return SyncResponse.fromJson(response.data); + } + + /// A message search. + Future searchMessages( + Filter filter, { + String? query, + List? sort, + PaginationParams? pagination, + Filter? messageFilters, + }) async { + assert(() { + if (query == null && messageFilters == null) { + throw ArgumentError('Provide at least `query` or `messageFilters`'); + } + if (query != null && messageFilters != null) { + throw ArgumentError( + "Can't provide both `query` and `messageFilters` at the same time", + ); + } + return true; + }(), 'Check incoming params.'); + + final response = await _client.get( + '/search', + queryParameters: { + 'payload': jsonEncode({ + 'filter_conditions': filter, + if (sort != null) 'sort': sort, + if (query != null) 'query': query, + if (messageFilters != null) + 'message_filter_conditions': messageFilters, + if (pagination != null) ...pagination.toJson(), + }), + }, + ); + + return SearchMessagesResponse.fromJson(response.data); + } + + /// Query channel members + Future queryMembers( + String channelType, { + Filter? filter, + String? channelId, + List? members, + List? sort, + PaginationParams? pagination, + }) async { + final response = await _client.get( + '/members', + queryParameters: { + 'payload': jsonEncode({ + 'type': channelType, + if (channelId != null) + 'id': channelId + else if (members != null) + 'members': members, + if (sort != null) 'sort': sort, + if (filter != null) 'filter': filter, + if (pagination != null) ...pagination.toJson(), + }), + }, + ); + return QueryMembersResponse.fromJson(response.data); + } +} diff --git a/packages/stream_chat/lib/src/core/api/guest_api.dart b/packages/stream_chat/lib/src/core/api/guest_api.dart new file mode 100644 index 00000000..438a637f --- /dev/null +++ b/packages/stream_chat/lib/src/core/api/guest_api.dart @@ -0,0 +1,20 @@ +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; +import 'package:stream_chat/src/core/models/user.dart'; + +/// +class GuestApi { + /// + GuestApi(this._client); + + final StreamHttpClient _client; + + /// + Future getGuestUser(User user) async { + final response = await _client.post( + '/guest', + data: {'user': user}, + ); + return ConnectGuestUserResponse.fromJson(response.data); + } +} diff --git a/packages/stream_chat/lib/src/core/api/message_api.dart b/packages/stream_chat/lib/src/core/api/message_api.dart new file mode 100644 index 00000000..eccd2f44 --- /dev/null +++ b/packages/stream_chat/lib/src/core/api/message_api.dart @@ -0,0 +1,160 @@ +import 'package:stream_chat/src/core/api/requests.dart'; +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; +import 'package:stream_chat/src/core/models/message.dart'; + +/// +class MessageApi { + /// + MessageApi(this._client); + + final StreamHttpClient _client; + + /// Sends the [message] to the given [channelId] of given [channelType] + Future sendMessage( + String channelId, + String channelType, + Message message, + ) async { + final response = await _client.post( + '/channels/$channelType/$channelId/message', + data: {'message': message}, + ); + return SendMessageResponse.fromJson(response.data); + } + + /// Retrieves a list of messages by [messageIDs] + /// from the given [channelId] of type [channelType] + Future getMessagesById( + String channelId, + String channelType, + List messageIDs, + ) async { + final response = await _client.get( + '/channels/$channelType/$channelId/messages', + queryParameters: {'ids': messageIDs.join(',')}, + ); + return GetMessagesByIdResponse.fromJson(response.data); + } + + /// Get a message by [messageId] + Future getMessage(String messageId) async { + final response = await _client.get( + '/messages/$messageId', + ); + return GetMessageResponse.fromJson(response.data); + } + + /// Updates the given [message] + Future updateMessage( + Message message, + ) async { + final response = await _client.post( + '/messages/${message.id}', + data: {'message': message}, + ); + return UpdateMessageResponse.fromJson(response.data); + } + + /// Deletes the given [messageId] + Future deleteMessage( + String messageId, + ) async { + final response = await _client.delete( + '/messages/$messageId', + ); + return EmptyResponse.fromJson(response.data); + } + + /// Send action for a specific [messageId] + /// of the given [channelId] of given [channelType] + Future sendAction( + String channelId, + String channelType, + String messageId, + Map formData, + ) async { + final response = await _client.post( + '/messages/$messageId/action', + data: { + 'id': channelId, + 'type': channelType, + 'form_data': formData, + 'message_id': messageId, + }, + ); + return SendActionResponse.fromJson(response.data); + } + + /// Send a [reactionType] for this [messageId] + /// Set [enforceUnique] to true to remove the existing user reaction + Future sendReaction( + String messageId, + String reactionType, { + Map extraData = const {}, + bool enforceUnique = false, + }) async { + final reaction = Map.from(extraData) + ..addAll({'type': reactionType}); + + final response = await _client.post( + '/messages/$messageId/reaction', + data: { + 'reaction': reaction, + 'enforce_unique': enforceUnique, + }, + ); + return SendReactionResponse.fromJson(response.data); + } + + /// Delete a [reactionType] from this [messageId] + Future deleteReaction( + String messageId, + String reactionType, + ) async { + final response = await _client.delete( + '/messages/$messageId/reaction/$reactionType', + ); + return EmptyResponse.fromJson(response.data); + } + + /// Get all the reactions for a [messageId] + Future getReactions( + String messageId, + PaginationParams options, + ) async { + final response = await _client.get( + '/messages/$messageId/reactions', + queryParameters: { + ...options.toJson(), + }, + ); + return QueryReactionsResponse.fromJson(response.data); + } + + /// Translates the [messageId] in provided [language] + Future translateMessage( + String messageId, + String language, + ) async { + final response = await _client.post( + '/messages/$messageId/translate', + data: {'language': language}, + ); + return TranslateMessageResponse.fromJson(response.data); + } + + /// Lists all the message replies for the [parentId] + Future getReplies( + String parentId, + PaginationParams options, + ) async { + final response = await _client.get( + '/messages/$parentId/replies', + queryParameters: { + ...options.toJson(), + }, + ); + return QueryRepliesResponse.fromJson(response.data); + } +} diff --git a/packages/stream_chat/lib/src/core/api/moderation_api.dart b/packages/stream_chat/lib/src/core/api/moderation_api.dart new file mode 100644 index 00000000..22c58c35 --- /dev/null +++ b/packages/stream_chat/lib/src/core/api/moderation_api.dart @@ -0,0 +1,128 @@ +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; + +/// +class ModerationApi { + /// + ModerationApi(this._client); + + final StreamHttpClient _client; + + /// Mutes a user + Future muteUser(String userId) async { + final response = await _client.post( + '/moderation/mute', + data: {'target_id': userId}, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Unmutes a user + Future unmuteUser(String userId) async { + final response = await _client.post( + '/moderation/unmute', + data: {'target_id': userId}, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Mutes the channel + Future muteChannel( + String channelCid, { + Duration? expiration, + }) async { + final response = await _client.post( + '/moderation/mute/channel', + data: { + 'channel_cid': channelCid, + if (expiration != null) 'expiration': expiration.inMilliseconds, + }, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Unmutes the channel + Future unmuteChannel( + String channelCid, + ) async { + final response = await _client.post( + '/moderation/unmute/channel', + data: {'channel_cid': channelCid}, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Flag a message + Future flagMessage( + String messageId, + ) async { + final response = await _client.post( + '/moderation/flag', + data: {'target_message_id': messageId}, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Unflag a message + Future unflagMessage( + String messageId, + ) async { + final response = await _client.post( + '/moderation/unflag', + data: {'target_message_id': messageId}, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Flag a user + Future flagUser( + String userId, + ) async { + final response = await _client.post( + '/moderation/flag', + data: {'target_user_id': userId}, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Unflag a user + Future unflagUser( + String userId, + ) async { + final response = await _client.post( + '/moderation/unflag', + data: {'target_user_id': userId}, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Bans a user from all channels + Future banUser( + String targetUserId, { + Map? options, + }) async { + final response = await _client.post( + '/moderation/ban', + data: { + 'target_user_id': targetUserId, + if (options != null) ...options, + }, + ); + return EmptyResponse.fromJson(response.data); + } + + /// Remove global ban for a user + Future unbanUser( + String targetUserId, { + Map? options, + }) async { + final response = await _client.delete( + '/moderation/ban', + queryParameters: { + 'target_user_id': targetUserId, + if (options != null) ...options, + }, + ); + return EmptyResponse.fromJson(response.data); + } +} diff --git a/packages/stream_chat/lib/src/api/requests.dart b/packages/stream_chat/lib/src/core/api/requests.dart similarity index 77% rename from packages/stream_chat/lib/src/api/requests.dart rename to packages/stream_chat/lib/src/core/api/requests.dart index e78a80ec..29112d72 100644 --- a/packages/stream_chat/lib/src/api/requests.dart +++ b/packages/stream_chat/lib/src/core/api/requests.dart @@ -1,9 +1,10 @@ +import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; part 'requests.g.dart'; /// Sorting options -@JsonSerializable(createFactory: false) +@JsonSerializable(includeIfNull: false) class SortOption { /// Creates a new SortOption instance /// @@ -18,6 +19,10 @@ class SortOption { this.comparator, }); + /// Create a new instance from a json + factory SortOption.fromJson(Map json) => + _$SortOptionFromJson(json); + /// Ascending order // ignore: constant_identifier_names static const ASC = 1; @@ -41,8 +46,8 @@ class SortOption { } /// Pagination options. -@JsonSerializable(createFactory: false, includeIfNull: false) -class PaginationParams { +@JsonSerializable(includeIfNull: false) +class PaginationParams extends Equatable { /// Creates a new PaginationParams instance /// /// For example: @@ -62,6 +67,10 @@ class PaginationParams { this.lessThanOrEqual, }); + /// Create a new instance from a json + factory PaginationParams.fromJson(Map json) => + _$PaginationParamsFromJson(json); + /// The amount of items requested from the APIs. final int limit; @@ -106,24 +115,12 @@ class PaginationParams { ); @override - @JsonKey(ignore: true) - int get hashCode => - runtimeType.hashCode ^ - limit.hashCode ^ - offset.hashCode ^ - greaterThan.hashCode ^ - greaterThanOrEqual.hashCode ^ - lessThan.hashCode ^ - lessThanOrEqual.hashCode; - - @override - bool operator ==(covariant PaginationParams other) => - identical(this, other) || - runtimeType == other.runtimeType && - limit == other.limit && - offset == other.offset && - greaterThan == other.greaterThan && - greaterThanOrEqual == other.greaterThanOrEqual && - lessThan == other.lessThan && - lessThanOrEqual == other.lessThanOrEqual; + List get props => [ + limit, + offset, + greaterThan, + greaterThanOrEqual, + lessThan, + lessThanOrEqual, + ]; } diff --git a/packages/stream_chat/lib/src/api/requests.g.dart b/packages/stream_chat/lib/src/core/api/requests.g.dart similarity index 63% rename from packages/stream_chat/lib/src/api/requests.g.dart rename to packages/stream_chat/lib/src/core/api/requests.g.dart index 1ec7b5fc..6cd935b8 100644 --- a/packages/stream_chat/lib/src/api/requests.g.dart +++ b/packages/stream_chat/lib/src/core/api/requests.g.dart @@ -6,12 +6,30 @@ part of 'requests.dart'; // JsonSerializableGenerator // ************************************************************************** +SortOption _$SortOptionFromJson(Map json) { + return SortOption( + json['field'] as String, + direction: json['direction'] as int, + ); +} + Map _$SortOptionToJson(SortOption instance) => { 'field': instance.field, 'direction': instance.direction, }; +PaginationParams _$PaginationParamsFromJson(Map json) { + return PaginationParams( + limit: json['limit'] as int, + offset: json['offset'] as int, + greaterThan: json['id_gt'] as String?, + greaterThanOrEqual: json['id_gte'] as String?, + lessThan: json['id_lt'] as String?, + lessThanOrEqual: json['id_lte'] as String?, + ); +} + Map _$PaginationParamsToJson(PaginationParams instance) { final val = { 'limit': instance.limit, diff --git a/packages/stream_chat/lib/src/api/responses.dart b/packages/stream_chat/lib/src/core/api/responses.dart similarity index 90% rename from packages/stream_chat/lib/src/api/responses.dart rename to packages/stream_chat/lib/src/core/api/responses.dart index 92493e8a..c639a960 100644 --- a/packages/stream_chat/lib/src/api/responses.dart +++ b/packages/stream_chat/lib/src/core/api/responses.dart @@ -1,14 +1,15 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/client.dart'; -import 'package:stream_chat/src/models/channel_model.dart'; -import 'package:stream_chat/src/models/channel_state.dart'; -import 'package:stream_chat/src/models/device.dart'; -import 'package:stream_chat/src/models/event.dart'; -import 'package:stream_chat/src/models/member.dart'; -import 'package:stream_chat/src/models/message.dart'; -import 'package:stream_chat/src/models/reaction.dart'; -import 'package:stream_chat/src/models/read.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/channel_state.dart'; +import 'package:stream_chat/src/core/models/device.dart'; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/member.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/reaction.dart'; +import 'package:stream_chat/src/core/models/read.dart'; +import 'package:stream_chat/src/core/models/user.dart'; +import 'package:stream_chat/src/errors/stream_chat_error.dart'; part 'responses.g.dart'; @@ -16,7 +17,34 @@ class _BaseResponse { String? duration; } -/// Model response for [StreamChatClient.resync] api call +/// Model response for [StreamChatNetworkError] data +@JsonSerializable(createToJson: false) +class ErrorResponse extends _BaseResponse { + /// + int? code; + + /// + String? message; + + /// + @JsonKey(name: 'StatusCode') + int? statusCode; + + /// + String? moreInfo; + + /// Create a new instance from a json + static ErrorResponse fromJson(Map json) => + _$ErrorResponseFromJson(json); + + @override + String toString() => 'ErrorResponse(code: $code, ' + 'message: $message, ' + 'statusCode: $statusCode, ' + 'moreInfo: $moreInfo)'; +} + +/// Model response for [StreamChatClient.sync] api call @JsonSerializable(createToJson: false) class SyncResponse extends _BaseResponse { /// The list of events diff --git a/packages/stream_chat/lib/src/api/responses.g.dart b/packages/stream_chat/lib/src/core/api/responses.g.dart similarity index 97% rename from packages/stream_chat/lib/src/api/responses.g.dart rename to packages/stream_chat/lib/src/core/api/responses.g.dart index 2358593d..c1c1f302 100644 --- a/packages/stream_chat/lib/src/api/responses.g.dart +++ b/packages/stream_chat/lib/src/core/api/responses.g.dart @@ -6,6 +6,15 @@ part of 'responses.dart'; // JsonSerializableGenerator // ************************************************************************** +ErrorResponse _$ErrorResponseFromJson(Map json) { + return ErrorResponse() + ..duration = json['duration'] as String? + ..code = json['code'] as int? + ..message = json['message'] as String? + ..statusCode = json['StatusCode'] as int? + ..moreInfo = json['more_info'] as String?; +} + SyncResponse _$SyncResponseFromJson(Map json) { return SyncResponse() ..duration = json['duration'] as String? diff --git a/packages/stream_chat/lib/src/core/api/stream_chat_api.dart b/packages/stream_chat/lib/src/core/api/stream_chat_api.dart new file mode 100644 index 00000000..13342160 --- /dev/null +++ b/packages/stream_chat/lib/src/core/api/stream_chat_api.dart @@ -0,0 +1,79 @@ +import 'package:logging/logging.dart'; +import 'package:stream_chat/src/core/api/channel_api.dart'; +import 'package:stream_chat/src/core/api/device_api.dart'; +import 'package:stream_chat/src/core/api/general_api.dart'; +import 'package:stream_chat/src/core/api/guest_api.dart'; +import 'package:stream_chat/src/core/api/message_api.dart'; +import 'package:stream_chat/src/core/api/moderation_api.dart'; +import 'package:stream_chat/src/core/http/connection_id_manager.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; +import 'package:stream_chat/src/core/api/user_api.dart'; +import 'package:stream_chat/src/core/http/token_manager.dart'; +import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; + +export 'device_api.dart' show PushProvider; + +/// +class StreamChatApi { + /// + StreamChatApi( + String apiKey, { + StreamHttpClient? client, + StreamHttpClientOptions? options, + TokenManager? tokenManager, + ConnectionIdManager? connectionIdManager, + AttachmentFileUploader? attachmentFileUploader, + Logger? logger, + }) : _fileUploader = attachmentFileUploader, + _client = client ?? + StreamHttpClient( + apiKey, + options: options, + tokenManager: tokenManager, + connectionIdManager: connectionIdManager, + logger: logger, + ); + + final StreamHttpClient _client; + + UserApi? _user; + + /// + UserApi get user => _user ??= UserApi(_client); + + GuestApi? _guest; + + /// + GuestApi get guest => _guest ??= GuestApi(_client); + + MessageApi? _message; + + /// + MessageApi get message => _message ??= MessageApi(_client); + + ChannelApi? _channel; + + /// + ChannelApi get channel => _channel ??= ChannelApi(_client); + + DeviceApi? _device; + + /// + DeviceApi get device => _device ??= DeviceApi(_client); + + ModerationApi? _moderation; + + /// + ModerationApi get moderation => _moderation ??= ModerationApi(_client); + + GeneralApi? _general; + + /// + GeneralApi get general => _general ??= GeneralApi(_client); + + AttachmentFileUploader? _fileUploader; + + /// + AttachmentFileUploader get fileUploader => + _fileUploader ??= StreamAttachmentFileUploader(_client); +} diff --git a/packages/stream_chat/lib/src/core/api/user_api.dart b/packages/stream_chat/lib/src/core/api/user_api.dart new file mode 100644 index 00000000..04c72284 --- /dev/null +++ b/packages/stream_chat/lib/src/core/api/user_api.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; + +import 'package:stream_chat/src/core/api/requests.dart'; +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; +import 'package:stream_chat/src/core/models/filter.dart'; +import 'package:stream_chat/src/core/models/user.dart'; + +/// +class UserApi { + /// + UserApi(this._client); + + final StreamHttpClient _client; + + /// Requests users with a given query. + Future queryUsers({ + bool presence = false, + Filter? filter, + List? sort, + PaginationParams? pagination, + }) async { + final response = await _client.get( + '/users', + queryParameters: { + 'payload': jsonEncode({ + 'presence': presence, + if (sort != null) 'sort': sort, + if (filter != null) 'filter_conditions': filter, + if (pagination != null) ...pagination.toJson(), + }), + }, + ); + return QueryUsersResponse.fromJson(response.data); + } + + /// Batch update a list of users + Future updateUsers( + List users, + ) async { + final response = await _client.post( + '/users', + data: { + 'users': {for (final user in users) user.id: user}, + }, + ); + return UpdateUsersResponse.fromJson(response.data); + } +} diff --git a/packages/stream_chat/lib/src/core/http/connection_id_manager.dart b/packages/stream_chat/lib/src/core/http/connection_id_manager.dart new file mode 100644 index 00000000..55fa1896 --- /dev/null +++ b/packages/stream_chat/lib/src/core/http/connection_id_manager.dart @@ -0,0 +1,27 @@ +// ignore_for_file: use_setters_to_change_properties + +/// +class ConnectionIdManager { + /// + ConnectionIdManager({ + String? connectionId, + }) : _connectionId = connectionId; + + String? _connectionId; + + /// + String? get connectionId => _connectionId; + + /// + bool get hasConnectionId => _connectionId != null; + + /// + void setConnectionId(String connectionId) { + _connectionId = connectionId; + } + + /// + void reset() { + _connectionId = null; + } +} diff --git a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart new file mode 100644 index 00000000..19d0293b --- /dev/null +++ b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart @@ -0,0 +1,91 @@ +import 'package:dio/dio.dart'; +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart'; +import 'package:stream_chat/src/core/http/token.dart'; + +import 'package:stream_chat/src/core/http/token_manager.dart'; +import 'package:stream_chat/src/errors/chat_error_code.dart'; +import 'package:stream_chat/src/errors/stream_chat_error.dart'; + +/// +class AuthInterceptor extends Interceptor { + /// + AuthInterceptor(this._httpClient, this._tokenManager); + + final Dio _httpClient; + + /// + final TokenManager _tokenManager; + + @override + Future onRequest( + RequestOptions options, + RequestInterceptorHandler handler, + ) async { + late Token token; + try { + token = await _tokenManager.loadToken(); + } catch (_) { + final error = StreamChatError(ChatErrorCode.undefinedToken); + final dioError = StreamChatDioError( + error: error, + requestOptions: options, + ); + return handler.reject(dioError); + } + final params = {'user_id': token.userId}; + final headers = { + 'Authorization': token.rawValue, + 'stream-auth-type': token.authType.raw, + }; + options..queryParameters.addAll(params)..headers.addAll(headers); + return handler.next(options); + } + + @override + void onError( + DioError err, + ErrorInterceptorHandler handler, + ) async { + ErrorResponse? error; + final data = err.response?.data; + if (data != null) error = ErrorResponse.fromJson(data); + if (error?.code == ChatErrorCode.tokenExpired.code) { + if (_tokenManager.isStatic) return handler.next(err); + _httpClient.lock(); + await _tokenManager.loadToken(refresh: true); + _httpClient.unlock(); + try { + final options = err.requestOptions; + final response = await _httpClient.request( + options.path, + cancelToken: options.cancelToken, + data: options.data, + onReceiveProgress: options.onReceiveProgress, + onSendProgress: options.onSendProgress, + queryParameters: options.queryParameters, + options: Options( + method: options.method, + sendTimeout: options.sendTimeout, + receiveTimeout: options.receiveTimeout, + extra: options.extra, + headers: options.headers, + responseType: options.responseType, + contentType: options.contentType, + validateStatus: options.validateStatus, + receiveDataWhenStatusError: options.receiveDataWhenStatusError, + followRedirects: options.followRedirects, + maxRedirects: options.maxRedirects, + requestEncoder: options.requestEncoder, + responseDecoder: options.responseDecoder, + listFormat: options.listFormat, + ), + ); + return handler.resolve(response); + } on DioError catch (error) { + return handler.reject(error); + } + } + return handler.next(err); + } +} diff --git a/packages/stream_chat/lib/src/core/http/interceptor/connection_id_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/connection_id_interceptor.dart new file mode 100644 index 00000000..78a3147f --- /dev/null +++ b/packages/stream_chat/lib/src/core/http/interceptor/connection_id_interceptor.dart @@ -0,0 +1,25 @@ +import 'package:dio/dio.dart'; + +import 'package:stream_chat/src/core/http/connection_id_manager.dart'; + +/// +class ConnectionIdInterceptor extends Interceptor { + /// + ConnectionIdInterceptor(this.connectionIdManager); + + /// + final ConnectionIdManager connectionIdManager; + + @override + void onRequest( + RequestOptions options, + RequestInterceptorHandler handler, + ) async { + if (connectionIdManager.hasConnectionId) { + options.queryParameters.addAll({ + 'connection_id': connectionIdManager.connectionId, + }); + } + handler.next(options); + } +} 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 new file mode 100644 index 00000000..a9075e34 --- /dev/null +++ b/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart @@ -0,0 +1,338 @@ +// ignore_for_file: lines_longer_than_80_chars + +import 'dart:math' as math; + +import 'package:dio/dio.dart'; + +/// +enum InterceptStep { + /// + request, + + /// + response, + + /// + error, +} + +/// +typedef LogPrint = void Function(InterceptStep step, Object object); + +void _defaultLogPrint(InterceptStep step, Object object) => print(object); + +/// +class LoggingInterceptor extends Interceptor { + /// + LoggingInterceptor({ + this.request = true, + this.requestHeader = false, + this.requestBody = true, + this.responseHeader = false, + this.responseBody = true, + this.error = true, + this.maxWidth = 120, + this.compact = true, + this.logPrint = _defaultLogPrint, + }); + + /// Print request [Options] + final bool request; + + /// Print request header [Options.headers] + final bool requestHeader; + + /// Print request data [Options.data] + final bool requestBody; + + /// Print [Response.data] + final bool responseBody; + + /// Print [Response.headers] + final bool responseHeader; + + /// Print error message + final bool error; + + /// InitialTab count to logPrint json response + static const int initialTab = 1; + + /// 1 tab length + static const String tabStep = ' '; + + /// Print compact json response + final bool compact; + + /// Width size per logPrint + final int maxWidth; + + /// Log printer; defaults logPrint log to console. + /// In flutter, you'd better use debugPrint. + /// you can also write log in a file. + void Function(InterceptStep step, Object object) logPrint; + + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + if (request) { + _printRequestHeader(_logPrintRequest, options); + } + if (requestHeader) { + _printMapAsTable( + _logPrintRequest, + options.queryParameters, + header: 'Query Parameters', + ); + final requestHeaders = {...options.headers}; + requestHeaders['contentType'] = options.contentType?.toString(); + requestHeaders['responseType'] = options.responseType.toString(); + requestHeaders['followRedirects'] = options.followRedirects; + requestHeaders['connectTimeout'] = options.connectTimeout; + requestHeaders['receiveTimeout'] = options.receiveTimeout; + _printMapAsTable(_logPrintRequest, requestHeaders, header: 'Headers'); + _printMapAsTable(_logPrintRequest, options.extra, header: 'Extras'); + } + if (requestBody && options.method != 'GET') { + final dynamic data = options.data; + if (data != null) { + if (data is Map) { + _printMapAsTable( + _logPrintRequest, + options.data as Map?, + header: 'Body', + ); + } else if (data is FormData) { + final formDataMap = {} + ..addEntries(data.fields) + ..addEntries(data.files); + _printMapAsTable(_logPrintRequest, formDataMap, + header: 'Form data | ${data.boundary}'); + } else { + _printBlock(_logPrintRequest, data.toString()); + } + } + } + super.onRequest(options, handler); + } + + @override + void onError(DioError err, ErrorInterceptorHandler handler) { + if (error) { + if (err.type == DioErrorType.response) { + final uri = err.response?.requestOptions.uri; + _printBoxed( + _logPrintError, + header: + 'DioError ║ Status: ${err.response?.statusCode} ${err.response?.statusMessage}', + text: uri.toString(), + ); + if (err.response != null && err.response?.data != null) { + _logPrintError('╔ ${err.type.toString()}'); + _printResponse(_logPrintError, err.response!); + } + _printLine(_logPrintError, '╚'); + _logPrintError(''); + } else { + _printBoxed( + _logPrintError, + header: 'DioError ║ ${err.type}', + text: err.message, + ); + _printRequestHeader(_logPrintError, err.requestOptions); + } + } + super.onError(err, handler); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) { + _printResponseHeader(_logPrintResponse, response); + if (responseHeader) { + final responseHeaders = {}; + response.headers + .forEach((k, list) => responseHeaders[k] = list.toString()); + _printMapAsTable(_logPrintResponse, responseHeaders, header: 'Headers'); + } + + if (responseBody) { + _logPrintResponse('╔ Body'); + _logPrintResponse('║'); + _printResponse(_logPrintResponse, response); + _logPrintResponse('║'); + _printLine(_logPrintResponse, '╚'); + } + super.onResponse(response, handler); + } + + void _printBoxed( + void Function(Object) logPrint, { + String? header, + String? text, + }) { + logPrint(''); + logPrint('â•”â•Ŗ $header'); + logPrint('║ $text'); + _printLine(logPrint, '╚'); + } + + void _printResponse(void Function(Object) logPrint, Response response) { + if (response.data != null) { + if (response.data is Map) { + _printPrettyMap(logPrint, response.data as Map); + } else if (response.data is List) { + logPrint('║${_indent()}['); + _printList(logPrint, response.data as List); + logPrint('║${_indent()}['); + } else { + _printBlock(logPrint, response.data.toString()); + } + } + } + + void _printResponseHeader(void Function(Object) logPrint, Response response) { + final uri = response.requestOptions.uri; + final method = response.requestOptions.method; + _printBoxed( + logPrint, + header: + 'Response ║ $method ║ Status: ${response.statusCode} ${response.statusMessage}', + text: uri.toString(), + ); + } + + void _printRequestHeader( + 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 = '╝']) => + logPrint('$pre${'═' * maxWidth}$suf'); + + void _printKV(void Function(Object) logPrint, String? key, Object? v) { + final pre = '╟ $key: '; + final msg = v.toString(); + + if (pre.length + msg.length > maxWidth) { + logPrint(pre); + _printBlock(logPrint, msg); + } else { + logPrint('$pre$msg'); + } + } + + void _printBlock(void Function(Object) logPrint, String msg) { + 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))); + } + } + + String _indent([int tabCount = initialTab]) => tabStep * tabCount; + + void _printPrettyMap( + void Function(Object) logPrint, + Map data, { + int tabs = initialTab, + bool isListItem = false, + bool isLast = false, + }) { + var _tabs = tabs; + final isRoot = _tabs == initialTab; + final initialIndent = _indent(_tabs); + _tabs++; + + if (isRoot || isListItem) logPrint('║$initialIndent{'); + + data.keys.toList().asMap().forEach((index, dynamic key) { + final isLast = index == data.length - 1; + dynamic value = data[key]; + if (value is String) { + value = '"${value.toString().replaceAll(RegExp(r'(\r|\n)+'), " ")}"'; + } + if (value is Map) { + if (compact) { + logPrint('║${_indent(_tabs)} $key: $value${!isLast ? ',' : ''}'); + } else { + logPrint('║${_indent(_tabs)} $key: {'); + _printPrettyMap(logPrint, value, tabs: _tabs); + } + } else if (value is List) { + if (compact) { + logPrint('║${_indent(_tabs)} $key: ${value.toString()}'); + } else { + logPrint('║${_indent(_tabs)} $key: ['); + _printList(logPrint, value, tabs: _tabs); + logPrint('║${_indent(_tabs)} ]${isLast ? '' : ','}'); + } + } else { + final msg = value.toString().replaceAll('\n', ''); + final indent = _indent(_tabs); + final linWidth = maxWidth - indent.length; + if (msg.length + indent.length > linWidth) { + final lines = (msg.length / linWidth).ceil(); + for (var i = 0; i < lines; ++i) { + logPrint('║${_indent(_tabs)} ${msg.substring( + i * linWidth, + math.min(i * linWidth + linWidth, msg.length), + )}'); + } + } else { + logPrint('║${_indent(_tabs)} $key: $msg${!isLast ? ',' : ''}'); + } + } + }); + + logPrint('║$initialIndent}${isListItem && !isLast ? ',' : ''}'); + } + + void _printList( + void Function(Object) logPrint, + List list, { + int tabs = initialTab, + }) { + list.asMap().forEach((i, dynamic e) { + final isLast = i == list.length - 1; + if (e is Map) { + if (compact) { + logPrint('║${_indent(tabs)} $e${!isLast ? ',' : ''}'); + } else { + _printPrettyMap(logPrint, e, + tabs: tabs + 1, isListItem: true, isLast: isLast); + } + } else { + logPrint('║${_indent(tabs + 2)} $e${isLast ? '' : ','}'); + } + }); + } + + bool _canFlattenMap(Map map) => + map.values.where((dynamic val) => val is Map || val is List).isEmpty && + map.toString().length < maxWidth; + + bool _canFlattenList(List list) => + list.length < 10 && list.toString().length < maxWidth; + + void _printMapAsTable( + void Function(Object) logPrint, + Map? map, { + String? header, + }) { + if (map == null || map.isEmpty) return; + logPrint('╔ $header '); + map.forEach((dynamic key, dynamic value) => + _printKV(logPrint, key.toString(), value)); + _printLine(logPrint, '╚'); + } + + void _logPrintRequest(Object object) => + logPrint(InterceptStep.request, object); + + void _logPrintResponse(Object object) => + logPrint(InterceptStep.response, object); + + void _logPrintError(Object object) => logPrint(InterceptStep.error, object); +} diff --git a/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart b/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart new file mode 100644 index 00000000..dd2c2535 --- /dev/null +++ b/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart @@ -0,0 +1,21 @@ +import 'package:dio/dio.dart'; +import 'package:stream_chat/src/errors/stream_chat_error.dart'; + +/// +class StreamChatDioError extends DioError { + /// + StreamChatDioError({ + required this.error, + required RequestOptions requestOptions, + Response? response, + DioErrorType type = DioErrorType.other, + }) : super( + error: error, + requestOptions: requestOptions, + response: response, + type: type, + ); + + @override + final StreamChatError error; +} diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client.dart b/packages/stream_chat/lib/src/core/http/stream_http_client.dart new file mode 100644 index 00000000..ae0a5f72 --- /dev/null +++ b/packages/stream_chat/lib/src/core/http/stream_http_client.dart @@ -0,0 +1,241 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:dio/dio.dart'; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; +import 'package:stream_chat/src/core/http/interceptor/auth_interceptor.dart'; +import 'package:stream_chat/src/core/http/interceptor/connection_id_interceptor.dart'; +import 'package:stream_chat/src/core/http/interceptor/logging_interceptor.dart'; +import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart'; +import 'package:stream_chat/src/core/http/token_manager.dart'; +import 'package:stream_chat/src/errors/stream_chat_error.dart'; +import 'package:stream_chat/src/platform_detector/platform_detector.dart'; +import 'package:stream_chat/version.dart'; + +import 'package:stream_chat/src/core/http/connection_id_manager.dart'; + +part 'stream_http_client_options.dart'; + +/// This is where we configure the base url, headers, +/// query parameters and convenient methods for http verbs with error parsing. +class StreamHttpClient { + /// [StreamHttpClient] constructor + StreamHttpClient( + this.apiKey, { + Dio? dio, + StreamHttpClientOptions? options, + TokenManager? tokenManager, + ConnectionIdManager? connectionIdManager, + Logger? logger, + }) : _options = options ?? const StreamHttpClientOptions(), + httpClient = dio ?? Dio() { + httpClient + ..options.baseUrl = _options.baseUrl + ..options.receiveTimeout = _options.receiveTimeout.inMilliseconds + ..options.connectTimeout = _options.connectTimeout.inMilliseconds + ..options.queryParameters = {'api_key': apiKey} + ..options.headers = { + 'Content-Type': 'application/json', + 'X-Stream-Client': _options.userAgent, + 'Content-Encoding': 'application/gzip', + } + ..interceptors.addAll([ + if (tokenManager != null) AuthInterceptor(httpClient, tokenManager), + if (connectionIdManager != null) + ConnectionIdInterceptor(connectionIdManager), + if (logger != null && logger.level != Level.OFF) + LoggingInterceptor( + requestHeader: true, + logPrint: (step, message) { + switch (step) { + case InterceptStep.request: + return logger.info(message); + case InterceptStep.response: + return logger.info(message); + case InterceptStep.error: + return logger.severe(message); + } + }, + ), + ]); + } + + /// Your project Stream Chat api key. + /// Find your API keys here https://getstream.io/dashboard/ + final String apiKey; + + /// Your project Stream Chat ClientOptions + final StreamHttpClientOptions _options; + + /// [Dio] httpClient + /// It's been chosen because it's easy to use + /// and supports interesting features out of the box + /// (Interceptors, Global configuration, FormData, File downloading etc.) + @visibleForTesting + final Dio httpClient; + + /// Shuts down the [httpClient]. + /// + /// If [force] is `false` (the default) the [httpClient] will be kept alive + /// until all active connections are done. If [force] is `true` any active + /// connections will be closed to immediately release all resources. These + /// closed connections will receive an error event to indicate that the client + /// was shut down. In both cases trying to establish a new connection after + /// calling [close] will throw an exception. + void close({bool force = false}) => httpClient.close(force: force); + + StreamChatNetworkError _parseError(DioError err) { + // locally thrown dio error + if (err is StreamChatDioError) { + final code = err.error.code; + final message = err.error.message; + return StreamChatNetworkError.raw(code: code, message: message); + } + // real network request dio error + return StreamChatNetworkError.fromDioError(err); + } + + /// Handy method to make http GET request with error parsing. + Future> get( + String path, { + Map? queryParameters, + Map? headers, + ProgressCallback? onReceiveProgress, + CancelToken? cancelToken, + }) async { + try { + final response = await httpClient.get( + path, + queryParameters: queryParameters, + options: Options(headers: headers), + onReceiveProgress: onReceiveProgress, + cancelToken: cancelToken, + ); + return response; + } on DioError catch (error) { + throw _parseError(error); + } + } + + /// Handy method to make http POST request with error parsing. + Future> post( + String path, { + Object? data, + Map? queryParameters, + Map? headers, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + CancelToken? cancelToken, + }) async { + try { + final response = await httpClient.post( + path, + queryParameters: queryParameters, + data: data, + options: Options(headers: headers), + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + cancelToken: cancelToken, + ); + return response; + } on DioError catch (error) { + throw _parseError(error); + } + } + + /// Handy method to make http DELETE request with error parsing. + Future> delete( + String path, { + Map? queryParameters, + Map? headers, + CancelToken? cancelToken, + }) async { + try { + final response = await httpClient.delete( + path, + queryParameters: queryParameters, + options: Options(headers: headers), + cancelToken: cancelToken, + ); + return response; + } on DioError catch (error) { + throw _parseError(error); + } + } + + /// Handy method to make http PATCH request with error parsing. + Future> patch( + String path, { + Object? data, + Map? queryParameters, + Map? headers, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + CancelToken? cancelToken, + }) async { + try { + final response = await httpClient.patch( + path, + queryParameters: queryParameters, + data: data, + options: Options(headers: headers), + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + cancelToken: cancelToken, + ); + return response; + } on DioError catch (error) { + throw _parseError(error); + } + } + + /// Handy method to make http PUT request with error parsing. + Future> put( + String path, { + Object? data, + Map? queryParameters, + Map? headers, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + CancelToken? cancelToken, + }) async { + try { + final response = await httpClient.put( + path, + queryParameters: queryParameters, + data: data, + options: Options(headers: headers), + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + cancelToken: cancelToken, + ); + return response; + } on DioError catch (error) { + throw _parseError(error); + } + } + + /// Handy method to post files with error parsing. + Future> postFile( + String path, + MultipartFile file, { + Map? queryParameters, + Map? headers, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + CancelToken? cancelToken, + }) async { + final formData = FormData.fromMap({'file': file}); + final response = await post( + path, + data: formData, + queryParameters: queryParameters, + headers: headers, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + cancelToken: cancelToken, + ); + return response; + } +} diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart b/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart new file mode 100644 index 00000000..0f06e4d0 --- /dev/null +++ b/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart @@ -0,0 +1,69 @@ +part of 'stream_http_client.dart'; + +/// +enum Location { + /// + usEast, + + /// + euWest, + + /// + mumbai, + + /// + sydney, + + /// + singapore, +} + +/// +extension LocationX on Location { + /// + String get name => { + Location.usEast: 'us-east', + Location.euWest: 'dublin', + Location.mumbai: 'mumbai', + Location.sydney: 'sydney', + Location.singapore: 'singapore', + }[this]!; +} + +const _defaultBaseURL = 'https://chat-us-east-1.stream-io-api.com'; + +/// Client options to modify [StreamHttpClient] +class StreamHttpClientOptions { + /// Instantiates a new [StreamHttpClientOptions] + const StreamHttpClientOptions({ + String? baseUrl, + this.location, + this.connectTimeout = const Duration(seconds: 6), + this.receiveTimeout = const Duration(seconds: 6), + }) : _baseUrl = baseUrl ?? _defaultBaseURL; + + final String _baseUrl; + + /// base url to use with client. + String get baseUrl { + if (location == null) return _baseUrl; + const serviceName = 'chat'; + final locationName = location!.name; + const baseDomainName = 'stream-io-api.com'; + return 'https://$serviceName-proxy-$locationName.$baseDomainName'; + } + + /// data center to use with client + final Location? location; + + /// connect timeout, default to 6s + final Duration connectTimeout; + + /// received timeout, default to 6s + final Duration receiveTimeout; + + /// Get the current user agent + String get userAgent => 'stream-chat-dart-client-' + '${CurrentPlatform.name}-' + '${PACKAGE_VERSION.split('+')[0]}'; +} diff --git a/packages/stream_chat/lib/src/core/http/token.dart b/packages/stream_chat/lib/src/core/http/token.dart new file mode 100644 index 00000000..a914521c --- /dev/null +++ b/packages/stream_chat/lib/src/core/http/token.dart @@ -0,0 +1,85 @@ +import 'dart:convert'; + +import 'package:equatable/equatable.dart'; +import 'package:jose/jose.dart'; +import 'package:stream_chat/src/core/models/user.dart'; +import 'package:stream_chat/src/core/utils.dart'; + +/// +typedef GuestTokenProvider = Future Function(User user); + +/// +enum AuthType { + /// + jwt, + + /// + anonymous, +} + +/// +extension AuthTypeX on AuthType { + /// + String get raw => { + AuthType.jwt: 'jwt', + AuthType.anonymous: 'anonymous', + }[this]!; +} + +/// Token designed to store the JWT and the user it is related to. +class Token extends Equatable { + const Token._({ + required this.rawValue, + required this.userId, + required this.authType, + }); + + /// The token that can be used when user is unknown. + /// Is used by `anonymous` token provider. + factory Token.anonymous({String? userId}) => Token._( + rawValue: '', + userId: userId ?? randomId(), + authType: AuthType.anonymous, + ); + + /// Creates a [Token] instance from the provided [rawValue] if it's valid. + factory Token.fromRawValue(String rawValue) { + final jwtBody = JsonWebToken.unverified(rawValue); + final userId = jwtBody.claims.getTyped('user_id'); + assert( + userId != null, + 'Invalid `token`, It should contain `user_id`', + ); + return Token._(rawValue: rawValue, userId: userId!, authType: AuthType.jwt); + } + + /// The token which can be used during the development. + /// Is used by `development(userId:)` token provider. + factory Token.development(String userId) { + const devSignature = 'devtoken'; + const header = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'; + final payload = json.encode({'user_id': userId}); + final payloadBytes = utf8.encode(payload); + final payloadB64 = base64.encode(payloadBytes); + final jwt = '$header.$payloadB64.$devSignature'; + return Token._(rawValue: jwt, userId: userId, authType: AuthType.jwt); + } + + /// The token which designed to be used for guest users. + static Future guest(User user, GuestTokenProvider provider) async { + final rawToken = await provider(user); + return Token.fromRawValue(rawToken); + } + + /// + final AuthType authType; + + /// + final String rawValue; + + /// + final String userId; + + @override + List get props => [authType, rawValue, userId]; +} diff --git a/packages/stream_chat/lib/src/core/http/token_manager.dart b/packages/stream_chat/lib/src/core/http/token_manager.dart new file mode 100644 index 00000000..88e9746d --- /dev/null +++ b/packages/stream_chat/lib/src/core/http/token_manager.dart @@ -0,0 +1,80 @@ +import 'package:stream_chat/src/core/http/token.dart'; + +/// A function which can be used to request a Stream Chat API token from your +/// own backend server. Function requires a single [userId]. +typedef TokenProvider = Future Function(String userId); + +/// +class TokenManager { + /// + TokenManager({ + String? userId, + Token? token, + TokenProvider? tokenProvider, + }) : _userId = userId, + _token = token, + _provider = tokenProvider; + + String? _type; + Token? _token; + + TokenProvider? _provider; + + String? _userId; + + /// User id to which this TokenManager is configured to + String? get userId => _userId; + + /// + bool get isStatic => _type == 'static'; + + /// + Future setTokenOrProvider( + String userId, { + Token? token, + TokenProvider? provider, + }) async { + assert(() { + if (token == null && provider == null) { + throw AssertionError('Provide at-least token or provider'); + } + if (token != null && provider != null) { + throw AssertionError("Can't set both token and provider"); + } + return true; + }(), ''); + + _userId = userId; + + if (token != null) { + _type = 'static'; + _token = token; + } + if (provider != null) { + _type = 'provider'; + _provider = provider; + } + + return loadToken(); + } + + /// + Future loadToken({bool refresh = false}) async { + assert( + _userId != null && _type != null, + 'Please call `setTokenOrProvider` before calling `loadToken`', + ); + if (refresh || _token == null) { + final rawValue = await _provider!(_userId!); + _token = Token.fromRawValue(rawValue); + } + return _token!; + } + + /// + void reset() { + _userId = null; + _token = null; + _provider = null; + } +} diff --git a/packages/stream_chat/lib/src/models/action.dart b/packages/stream_chat/lib/src/core/models/action.dart similarity index 100% rename from packages/stream_chat/lib/src/models/action.dart rename to packages/stream_chat/lib/src/core/models/action.dart diff --git a/packages/stream_chat/lib/src/models/action.g.dart b/packages/stream_chat/lib/src/core/models/action.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/action.g.dart rename to packages/stream_chat/lib/src/core/models/action.g.dart diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/core/models/attachment.dart similarity index 97% rename from packages/stream_chat/lib/src/models/attachment.dart rename to packages/stream_chat/lib/src/core/models/attachment.dart index b68a1bae..1df785a8 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.dart @@ -2,9 +2,9 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/models/action.dart'; -import 'package:stream_chat/src/models/attachment_file.dart'; -import 'package:stream_chat/src/models/serialization.dart'; +import 'package:stream_chat/src/core/models/action.dart'; +import 'package:stream_chat/src/core/models/attachment_file.dart'; +import 'package:stream_chat/src/core/models/serialization.dart'; import 'package:uuid/uuid.dart'; part 'attachment.g.dart'; diff --git a/packages/stream_chat/lib/src/models/attachment.g.dart b/packages/stream_chat/lib/src/core/models/attachment.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/attachment.g.dart rename to packages/stream_chat/lib/src/core/models/attachment.g.dart diff --git a/packages/stream_chat/lib/src/models/attachment_file.dart b/packages/stream_chat/lib/src/core/models/attachment_file.dart similarity index 100% rename from packages/stream_chat/lib/src/models/attachment_file.dart rename to packages/stream_chat/lib/src/core/models/attachment_file.dart diff --git a/packages/stream_chat/lib/src/models/attachment_file.freezed.dart b/packages/stream_chat/lib/src/core/models/attachment_file.freezed.dart similarity index 100% rename from packages/stream_chat/lib/src/models/attachment_file.freezed.dart rename to packages/stream_chat/lib/src/core/models/attachment_file.freezed.dart diff --git a/packages/stream_chat/lib/src/models/attachment_file.g.dart b/packages/stream_chat/lib/src/core/models/attachment_file.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/attachment_file.g.dart rename to packages/stream_chat/lib/src/core/models/attachment_file.g.dart diff --git a/packages/stream_chat/lib/src/models/channel_config.dart b/packages/stream_chat/lib/src/core/models/channel_config.dart similarity index 97% rename from packages/stream_chat/lib/src/models/channel_config.dart rename to packages/stream_chat/lib/src/core/models/channel_config.dart index 9ba36e4f..2d5182b3 100644 --- a/packages/stream_chat/lib/src/models/channel_config.dart +++ b/packages/stream_chat/lib/src/core/models/channel_config.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/models/command.dart'; +import 'package:stream_chat/src/core/models/command.dart'; part 'channel_config.g.dart'; diff --git a/packages/stream_chat/lib/src/models/channel_config.g.dart b/packages/stream_chat/lib/src/core/models/channel_config.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/channel_config.g.dart rename to packages/stream_chat/lib/src/core/models/channel_config.g.dart diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/core/models/channel_model.dart similarity index 96% rename from packages/stream_chat/lib/src/models/channel_model.dart rename to packages/stream_chat/lib/src/core/models/channel_model.dart index 41230bf7..892713e7 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.dart @@ -1,7 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/models/channel_config.dart'; -import 'package:stream_chat/src/models/serialization.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/channel_config.dart'; +import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/models/user.dart'; part 'channel_model.g.dart'; diff --git a/packages/stream_chat/lib/src/models/channel_model.g.dart b/packages/stream_chat/lib/src/core/models/channel_model.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/channel_model.g.dart rename to packages/stream_chat/lib/src/core/models/channel_model.g.dart diff --git a/packages/stream_chat/lib/src/models/channel_state.dart b/packages/stream_chat/lib/src/core/models/channel_state.dart similarity index 87% rename from packages/stream_chat/lib/src/models/channel_state.dart rename to packages/stream_chat/lib/src/core/models/channel_state.dart index 9d18f434..998efa98 100644 --- a/packages/stream_chat/lib/src/models/channel_state.dart +++ b/packages/stream_chat/lib/src/core/models/channel_state.dart @@ -1,9 +1,9 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/models/channel_model.dart'; -import 'package:stream_chat/src/models/member.dart'; -import 'package:stream_chat/src/models/message.dart'; -import 'package:stream_chat/src/models/read.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/member.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/read.dart'; +import 'package:stream_chat/src/core/models/user.dart'; part 'channel_state.g.dart'; diff --git a/packages/stream_chat/lib/src/models/channel_state.g.dart b/packages/stream_chat/lib/src/core/models/channel_state.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/channel_state.g.dart rename to packages/stream_chat/lib/src/core/models/channel_state.g.dart diff --git a/packages/stream_chat/lib/src/models/command.dart b/packages/stream_chat/lib/src/core/models/command.dart similarity index 100% rename from packages/stream_chat/lib/src/models/command.dart rename to packages/stream_chat/lib/src/core/models/command.dart diff --git a/packages/stream_chat/lib/src/models/command.g.dart b/packages/stream_chat/lib/src/core/models/command.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/command.g.dart rename to packages/stream_chat/lib/src/core/models/command.g.dart diff --git a/packages/stream_chat/lib/src/models/device.dart b/packages/stream_chat/lib/src/core/models/device.dart similarity index 100% rename from packages/stream_chat/lib/src/models/device.dart rename to packages/stream_chat/lib/src/core/models/device.dart diff --git a/packages/stream_chat/lib/src/models/device.g.dart b/packages/stream_chat/lib/src/core/models/device.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/device.g.dart rename to packages/stream_chat/lib/src/core/models/device.g.dart diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/core/models/event.dart similarity index 93% rename from packages/stream_chat/lib/src/models/event.dart rename to packages/stream_chat/lib/src/core/models/event.dart index b4ee9c1d..d0d17aa0 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/core/models/event.dart @@ -1,7 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/models/channel_model.dart'; -import 'package:stream_chat/src/models/message.dart'; -import 'package:stream_chat/src/models/serialization.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/serialization.dart'; import 'package:stream_chat/stream_chat.dart'; part 'event.g.dart'; @@ -10,11 +10,11 @@ part 'event.g.dart'; @JsonSerializable() class Event { /// Constructor used for json serialization - const Event({ - this.type, + Event({ + this.type = 'local.event', this.cid, this.connectionId, - this.createdAt, + DateTime? createdAt, this.me, this.user, this.message, @@ -29,7 +29,7 @@ class Event { this.parentId, this.extraData = const {}, this.isLocal = true, - }); + }) : createdAt = createdAt ?? DateTime.now(); /// Create a new instance from a json factory Event.fromJson(Map json) => @@ -40,7 +40,7 @@ class Event { /// The type of the event /// [EventType] contains some predefined constant types - final String? type; + final String type; /// The channel cid to which the event belongs final String? cid; @@ -55,7 +55,7 @@ class Event { final String? connectionId; /// The date of creation of the event - final DateTime? createdAt; + final DateTime createdAt; /// User object of the health check user final OwnUser? me; diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/core/models/event.g.dart similarity index 97% rename from packages/stream_chat/lib/src/models/event.g.dart rename to packages/stream_chat/lib/src/core/models/event.g.dart index aba142f8..90abeb71 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/core/models/event.g.dart @@ -8,7 +8,7 @@ part of 'event.dart'; Event _$EventFromJson(Map json) { return Event( - type: json['type'] as String?, + type: json['type'] as String, cid: json['cid'] as String?, connectionId: json['connection_id'] as String?, createdAt: json['created_at'] == null @@ -49,7 +49,7 @@ Map _$EventToJson(Event instance) => { 'channel_id': instance.channelId, 'channel_type': instance.channelType, 'connection_id': instance.connectionId, - 'created_at': instance.createdAt?.toIso8601String(), + 'created_at': instance.createdAt.toIso8601String(), 'me': instance.me?.toJson(), 'user': instance.user?.toJson(), 'message': instance.message?.toJson(), diff --git a/packages/stream_chat/lib/src/models/filter.dart b/packages/stream_chat/lib/src/core/models/filter.dart similarity index 100% rename from packages/stream_chat/lib/src/models/filter.dart rename to packages/stream_chat/lib/src/core/models/filter.dart diff --git a/packages/stream_chat/lib/src/models/member.dart b/packages/stream_chat/lib/src/core/models/member.dart similarity index 98% rename from packages/stream_chat/lib/src/models/member.dart rename to packages/stream_chat/lib/src/core/models/member.dart index 49df170c..87c82473 100644 --- a/packages/stream_chat/lib/src/models/member.dart +++ b/packages/stream_chat/lib/src/core/models/member.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/user.dart'; part 'member.g.dart'; diff --git a/packages/stream_chat/lib/src/models/member.g.dart b/packages/stream_chat/lib/src/core/models/member.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/member.g.dart rename to packages/stream_chat/lib/src/core/models/member.g.dart diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart similarity index 98% rename from packages/stream_chat/lib/src/models/message.dart rename to packages/stream_chat/lib/src/core/models/message.dart index f928ce3a..f3bf100f 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -1,9 +1,9 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/models/attachment.dart'; -import 'package:stream_chat/src/models/reaction.dart'; -import 'package:stream_chat/src/models/serialization.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/attachment.dart'; +import 'package:stream_chat/src/core/models/reaction.dart'; +import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/models/user.dart'; import 'package:uuid/uuid.dart'; part 'message.g.dart'; diff --git a/packages/stream_chat/lib/src/models/message.g.dart b/packages/stream_chat/lib/src/core/models/message.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/message.g.dart rename to packages/stream_chat/lib/src/core/models/message.g.dart diff --git a/packages/stream_chat/lib/src/models/mute.dart b/packages/stream_chat/lib/src/core/models/mute.dart similarity index 85% rename from packages/stream_chat/lib/src/models/mute.dart rename to packages/stream_chat/lib/src/core/models/mute.dart index 3ba26230..8c3bef89 100644 --- a/packages/stream_chat/lib/src/models/mute.dart +++ b/packages/stream_chat/lib/src/core/models/mute.dart @@ -1,7 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/models/channel_model.dart'; -import 'package:stream_chat/src/models/serialization.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/models/user.dart'; part 'mute.g.dart'; diff --git a/packages/stream_chat/lib/src/models/mute.g.dart b/packages/stream_chat/lib/src/core/models/mute.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/mute.g.dart rename to packages/stream_chat/lib/src/core/models/mute.g.dart diff --git a/packages/stream_chat/lib/src/models/own_user.dart b/packages/stream_chat/lib/src/core/models/own_user.dart similarity index 79% rename from packages/stream_chat/lib/src/models/own_user.dart rename to packages/stream_chat/lib/src/core/models/own_user.dart index a93af19a..6f975a94 100644 --- a/packages/stream_chat/lib/src/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -1,8 +1,8 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/models/device.dart'; -import 'package:stream_chat/src/models/mute.dart'; -import 'package:stream_chat/src/models/serialization.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/device.dart'; +import 'package:stream_chat/src/core/models/mute.dart'; +import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/models/user.dart'; part 'own_user.g.dart'; @@ -40,6 +40,18 @@ class OwnUser extends User { factory OwnUser.fromJson(Map json) => _$OwnUserFromJson( Serialization.moveToExtraDataFromRoot(json, topLevelFields)); + /// Create a new instance from [User] object + factory OwnUser.fromUser(User user) => OwnUser( + id: user.id, + role: user.role, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + lastActive: user.lastActive, + online: user.online, + banned: user.banned, + extraData: user.extraData, + ); + /// List of user devices @JsonKey( includeIfNull: false, diff --git a/packages/stream_chat/lib/src/models/own_user.g.dart b/packages/stream_chat/lib/src/core/models/own_user.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/own_user.g.dart rename to packages/stream_chat/lib/src/core/models/own_user.g.dart diff --git a/packages/stream_chat/lib/src/models/reaction.dart b/packages/stream_chat/lib/src/core/models/reaction.dart similarity index 95% rename from packages/stream_chat/lib/src/models/reaction.dart rename to packages/stream_chat/lib/src/core/models/reaction.dart index 85697204..ace4a15e 100644 --- a/packages/stream_chat/lib/src/models/reaction.dart +++ b/packages/stream_chat/lib/src/core/models/reaction.dart @@ -1,6 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/models/serialization.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/models/user.dart'; part 'reaction.g.dart'; diff --git a/packages/stream_chat/lib/src/models/reaction.g.dart b/packages/stream_chat/lib/src/core/models/reaction.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/reaction.g.dart rename to packages/stream_chat/lib/src/core/models/reaction.g.dart diff --git a/packages/stream_chat/lib/src/models/read.dart b/packages/stream_chat/lib/src/core/models/read.dart similarity index 94% rename from packages/stream_chat/lib/src/models/read.dart rename to packages/stream_chat/lib/src/core/models/read.dart index cbd47dd1..55912762 100644 --- a/packages/stream_chat/lib/src/models/read.dart +++ b/packages/stream_chat/lib/src/core/models/read.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/user.dart'; part 'read.g.dart'; diff --git a/packages/stream_chat/lib/src/models/read.g.dart b/packages/stream_chat/lib/src/core/models/read.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/read.g.dart rename to packages/stream_chat/lib/src/core/models/read.g.dart diff --git a/packages/stream_chat/lib/src/models/serialization.dart b/packages/stream_chat/lib/src/core/models/serialization.dart similarity index 96% rename from packages/stream_chat/lib/src/models/serialization.dart rename to packages/stream_chat/lib/src/core/models/serialization.dart index d584912e..692ad01a 100644 --- a/packages/stream_chat/lib/src/models/serialization.dart +++ b/packages/stream_chat/lib/src/core/models/serialization.dart @@ -1,4 +1,4 @@ -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/user.dart'; /// Used to avoid to serialize properties to json // ignore: prefer_void_to_null diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart similarity index 98% rename from packages/stream_chat/lib/src/models/user.dart rename to packages/stream_chat/lib/src/core/models/user.dart index 9335f8c8..ae1dd742 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/models/serialization.dart'; +import 'package:stream_chat/src/core/models/serialization.dart'; part 'user.g.dart'; diff --git a/packages/stream_chat/lib/src/models/user.g.dart b/packages/stream_chat/lib/src/core/models/user.g.dart similarity index 100% rename from packages/stream_chat/lib/src/models/user.g.dart rename to packages/stream_chat/lib/src/core/models/user.g.dart diff --git a/packages/stream_chat/lib/src/core/utils.dart b/packages/stream_chat/lib/src/core/utils.dart new file mode 100644 index 00000000..9366e34e --- /dev/null +++ b/packages/stream_chat/lib/src/core/utils.dart @@ -0,0 +1,16 @@ +import 'dart:math' as math; + +// This alphabet uses `A-Za-z0-9_-` symbols. The genetic algorithm helped +// optimize the gzip compression for this alphabet. +const _alphabet = + 'ModuleSymbhasOwnPr0123456789ABCDEFGHNRVfgctiUvzKqYTJkLxpZXIjQW'; + +/// Generates a random String id +/// Adopted from: https://github.com/ai/nanoid/blob/main/non-secure/index.js +String randomId({int size = 21}) { + var id = ''; + for (var i = 0; i < size; i++) { + id += _alphabet[(math.Random().nextInt(32) * 64) | 0]; + } + return id; +} diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 8f60482f..d7e0f765 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -1,13 +1,13 @@ -import 'package:stream_chat/src/api/requests.dart'; -import 'package:stream_chat/src/models/channel_model.dart'; -import 'package:stream_chat/src/models/channel_state.dart'; -import 'package:stream_chat/src/models/event.dart'; -import 'package:stream_chat/src/models/filter.dart'; -import 'package:stream_chat/src/models/member.dart'; -import 'package:stream_chat/src/models/message.dart'; -import 'package:stream_chat/src/models/reaction.dart'; -import 'package:stream_chat/src/models/read.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/api/requests.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/channel_state.dart'; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/filter.dart'; +import 'package:stream_chat/src/core/models/member.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/reaction.dart'; +import 'package:stream_chat/src/core/models/read.dart'; +import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/src/extensions/iterable_extension.dart'; /// A simple client used for persisting chat data locally. diff --git a/packages/stream_chat/lib/src/errors/chat_error_code.dart b/packages/stream_chat/lib/src/errors/chat_error_code.dart new file mode 100644 index 00000000..3dbb600c --- /dev/null +++ b/packages/stream_chat/lib/src/errors/chat_error_code.dart @@ -0,0 +1,91 @@ +/// +enum ChatErrorCode { + // client error codes + networkFailed, + parserError, + socketClosed, + socketFailure, + cantParseConnectionEvent, + cantParseEvent, + invalidToken, + undefinedToken, + unableToParseSocketEvent, + noErrorBody, + + // server error codes + authenticationError, + tokenExpired, + tokenNotValid, + tokenDateIncorrect, + tokenSignatureIncorrect, + apiKeyNotFound, +} + +/// +extension ChatErrorCodeX on ChatErrorCode { + /// + String get message => { + // client error message + ChatErrorCode.networkFailed: 'Response is failed. See cause', + ChatErrorCode.parserError: 'Unable to parse error', + ChatErrorCode.socketClosed: 'Server closed connection', + ChatErrorCode.socketFailure: 'See stack trace in logs. ' + 'Intercept error in error handler of setUser', + ChatErrorCode.cantParseConnectionEvent: + 'Unable to parse connection event', + ChatErrorCode.cantParseEvent: 'Unable to parse event', + ChatErrorCode.invalidToken: 'Invalid token', + ChatErrorCode.undefinedToken: + 'No defined token. Check if client.setUser was called and finished', + ChatErrorCode.unableToParseSocketEvent: + 'Socket event payload either invalid or null', + ChatErrorCode.noErrorBody: 'No error body. See http status code', + + // server error message + ChatErrorCode.authenticationError: + 'Unauthenticated, problem with authentication', + ChatErrorCode.tokenExpired: 'Token expired, new one must be requested.', + ChatErrorCode.tokenNotValid: 'Unauthenticated, token not valid yet', + ChatErrorCode.tokenDateIncorrect: + 'Unauthenticated, token date incorrect', + ChatErrorCode.tokenSignatureIncorrect: + 'Unauthenticated, token signature invalid', + ChatErrorCode.apiKeyNotFound: + "Api key is not found, verify it if it's correct or was created.", + }[this]!; + + /// + int get code => { + // client error codes + ChatErrorCode.networkFailed: 1000, + ChatErrorCode.parserError: 1001, + ChatErrorCode.socketClosed: 1002, + ChatErrorCode.socketFailure: 1003, + ChatErrorCode.cantParseConnectionEvent: 1004, + ChatErrorCode.cantParseEvent: 1005, + ChatErrorCode.invalidToken: 1006, + ChatErrorCode.undefinedToken: 1007, + ChatErrorCode.unableToParseSocketEvent: 1008, + ChatErrorCode.noErrorBody: 1009, + + // server error codes + ChatErrorCode.authenticationError: 5, + ChatErrorCode.tokenExpired: 40, + ChatErrorCode.tokenNotValid: 41, + ChatErrorCode.tokenDateIncorrect: 42, + ChatErrorCode.tokenSignatureIncorrect: 43, + ChatErrorCode.apiKeyNotFound: 2, + }[this]!; + + /// + Set get authenticationErrors => { + ChatErrorCode.authenticationError, + ChatErrorCode.tokenExpired, + ChatErrorCode.tokenNotValid, + ChatErrorCode.tokenDateIncorrect, + ChatErrorCode.tokenSignatureIncorrect, + }.map((it) => it.code).toSet(); + + /// + bool isAuthenticationError(int code) => authenticationErrors.contains(code); +} diff --git a/packages/stream_chat/lib/src/errors/stream_chat_error.dart b/packages/stream_chat/lib/src/errors/stream_chat_error.dart new file mode 100644 index 00000000..280ab6c7 --- /dev/null +++ b/packages/stream_chat/lib/src/errors/stream_chat_error.dart @@ -0,0 +1,77 @@ +import 'package:equatable/equatable.dart'; +import 'package:stream_chat/src/errors/chat_error_code.dart'; +import 'package:stream_chat/stream_chat.dart'; + +/// +class StreamChatError extends Equatable implements Exception { + /// + StreamChatError(ChatErrorCode errorCode) + : code = errorCode.code, + message = errorCode.message; + + /// + const StreamChatError.raw(this.code, this.message); + + /// Error code + final int code; + + /// Error message + final String message; + + @override + List get props => [code, message]; + + @override + String toString() => 'StreamChatError(code: $code, message: $message)'; +} + +/// +class StreamChatNetworkError extends StreamChatError { + /// + StreamChatNetworkError({ + required ChatErrorCode errorCode, + int? statusCode, + this.data, + }) : statusCode = statusCode ?? data?.statusCode, + super(errorCode); + + /// + const StreamChatNetworkError.raw({ + required int code, + required String message, + this.statusCode, + this.data, + }) : super.raw(code, message); + + /// + factory StreamChatNetworkError.fromDioError(DioError error) { + final response = error.response; + ErrorResponse? errorResponse; + final data = response?.data; + if (data != null) { + errorResponse = ErrorResponse.fromJson(data); + } + return StreamChatNetworkError.raw( + code: errorResponse?.code ?? -1, + message: errorResponse?.message ?? response?.statusMessage ?? '', + statusCode: errorResponse?.statusCode ?? response?.statusCode, + data: errorResponse, + ); + } + + /// HTTP status code + final int? statusCode; + + /// Response body. please refer to [ErrorResponse]. + final ErrorResponse? data; + + @override + List get props => [...super.props, statusCode]; + + @override + String toString() => 'StreamChatNetworkError(' + 'code: $code, ' + 'message: $message, ' + 'statusCode: $statusCode, ' + 'data: $data)'; +} diff --git a/packages/stream_chat/lib/src/event_type.dart b/packages/stream_chat/lib/src/event_type.dart index fd93af2a..87529399 100644 --- a/packages/stream_chat/lib/src/event_type.dart +++ b/packages/stream_chat/lib/src/event_type.dart @@ -3,6 +3,9 @@ class EventType { /// Indicates any type of events static const String any = '*'; + /// + static const String healthCheck = 'health.check'; + /// Event sent when a user starts typing a message static const String typingStart = 'typing.start'; diff --git a/packages/stream_chat/lib/src/api/connection_status.dart b/packages/stream_chat/lib/src/ws/connection_status.dart similarity index 100% rename from packages/stream_chat/lib/src/api/connection_status.dart rename to packages/stream_chat/lib/src/ws/connection_status.dart diff --git a/packages/stream_chat/lib/src/ws/socket_error.dart b/packages/stream_chat/lib/src/ws/socket_error.dart new file mode 100644 index 00000000..00b052b9 --- /dev/null +++ b/packages/stream_chat/lib/src/ws/socket_error.dart @@ -0,0 +1,25 @@ +/// +class SocketError { + /// + const SocketError({ + this.code = -1, + this.statusCode = -1, + this.message = '', + }); + + /// + factory SocketError.fromJson(Map json) => SocketError( + code: json['code'], + statusCode: json['StatusCode'], + message: json['message'], + ); + + /// + final int code; + + /// + final int statusCode; + + /// + final String message; +} diff --git a/packages/stream_chat/lib/src/ws/timer_helper.dart b/packages/stream_chat/lib/src/ws/timer_helper.dart new file mode 100644 index 00000000..0974ee80 --- /dev/null +++ b/packages/stream_chat/lib/src/ws/timer_helper.dart @@ -0,0 +1,51 @@ +import 'dart:async'; +import 'package:uuid/uuid.dart'; + +/// +class TimerHelper { + final _uuid = const Uuid(); + late final _timers = {}; + + /// + String setTimer( + Duration duration, + void Function() callback, { + bool immediate = false, + }) { + final id = _uuid.v1(); + final timer = Timer(duration, callback); + if (immediate) callback(); + _timers[id] = timer; + return id; + } + + /// + String setPeriodicTimer( + Duration duration, + void Function(Timer) callback, { + bool immediate = false, + }) { + final id = _uuid.v1(); + final timer = Timer.periodic(duration, callback); + if (immediate) callback.call(timer); + _timers[id] = timer; + return id; + } + + /// + void cancelTimer(String id) { + final timer = _timers.remove(id); + return timer?.cancel(); + } + + /// + void cancelAllTimers() { + for (final t in _timers.values) { + t.cancel(); + } + _timers.clear(); + } + + /// + bool get hasTimers => _timers.isNotEmpty; +} diff --git a/packages/stream_chat/lib/src/ws/websocket.dart b/packages/stream_chat/lib/src/ws/websocket.dart new file mode 100644 index 00000000..0e19f9a6 --- /dev/null +++ b/packages/stream_chat/lib/src/ws/websocket.dart @@ -0,0 +1,395 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:math' as math; + +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:stream_chat/src/ws/connection_status.dart'; +import 'package:stream_chat/src/core/http/token.dart'; +import 'package:stream_chat/src/core/http/token_manager.dart'; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/user.dart'; +import 'package:stream_chat/src/event_type.dart'; +import 'package:stream_chat/src/ws/socket_error.dart'; +import 'package:stream_chat/src/ws/timer_helper.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; +import 'package:web_socket_channel/status.dart' as status; + +/// Typedef which exposes an [Event] as the only parameter. +typedef EventHandler = void Function(Event); + +/// Typedef used for connecting to a websocket. Method returns a +/// [WebSocketChannel] and accepts a connection [url] and an optional +/// [Iterable] of `protocols`. +typedef WebSocketChannelProvider = WebSocketChannel Function( + Uri uri, { + Iterable? protocols, +}); + +const _tokenExpiredErrorCode = 40; + +/// A WebSocket connection that reconnects upon failure. +class WebSocket with TimerHelper { + /// Creates a new websocket + /// To connect the WS call [connect] + WebSocket({ + required this.apiKey, + required this.baseUrl, + required this.tokenManager, + this.handler, + Logger? logger, + this.webSocketChannelProvider, + this.reconnectionMonitorInterval = 10, + this.healthCheckInterval = 20, + this.reconnectionMonitorTimeout = 40, + }) : _logger = logger; + + /// + final String apiKey; + + /// WS base url + final String baseUrl; + + /// + final TokenManager tokenManager; + + /// Functions that will be called every time a new event is received from the + /// connection + final EventHandler? handler; + + final Logger? _logger; + + /// Connection function + /// Used only for testing purpose + @visibleForTesting + final WebSocketChannelProvider? webSocketChannelProvider; + + /// Interval of the reconnection monitor timer + /// This checks that it received a new event in the last + /// [reconnectionMonitorTimeout] seconds, otherwise it considers the + /// connection unhealthy and reconnects the WS + final int reconnectionMonitorInterval; + + /// Interval of the health event sending timer + /// This sends a health event every [healthCheckInterval] seconds in order to + /// make the server aware that the client is still listening + final int healthCheckInterval; + + /// The timeout that uses the reconnection monitor timer to consider the + /// connection unhealthy + final int reconnectionMonitorTimeout; + + User? _user; + String? _connectionId; + DateTime? _lastEventAt; + WebSocketChannel? _webSocketChannel; + StreamSubscription? _webSocketChannelSubscription; + + /// + Completer? connectionCompleter; + + /// + String? get connectionId => _connectionId; + + final _connectionStatusController = + BehaviorSubject.seeded(ConnectionStatus.disconnected); + + set _connectionStatus(ConnectionStatus status) => + _connectionStatusController.add(status); + + /// The current connection status value + ConnectionStatus get connectionStatus => _connectionStatusController.value!; + + /// This notifies of connection status changes + Stream get connectionStatusStream => + _connectionStatusController.stream.distinct(); + + void _initWebSocketChannel(Uri uri) { + _logger?.info('Initiating connection with $baseUrl'); + if (_webSocketChannel != null) { + _closeWebSocketChannel(); + } + _webSocketChannel = + webSocketChannelProvider?.call(uri) ?? WebSocketChannel.connect(uri); + _subscribeToWebSocketChannel(); + } + + void _closeWebSocketChannel() { + _logger?.info('Closing connection with $baseUrl'); + if (_webSocketChannel != null) { + _unsubscribeFromWebSocketChannel(); + _webSocketChannel?.sink.close(status.goingAway); + _webSocketChannel = null; + } + } + + void _subscribeToWebSocketChannel() { + _logger?.info('Started listening to $baseUrl'); + if (_webSocketChannelSubscription != null) { + _unsubscribeFromWebSocketChannel(); + } + _webSocketChannelSubscription = _webSocketChannel?.stream.listen( + _onDataReceived, + onError: _onConnectionError, + onDone: _onConnectionClosed, + ); + } + + void _unsubscribeFromWebSocketChannel() { + _logger?.info('Stopped listening to $baseUrl'); + if (_webSocketChannelSubscription != null) { + _webSocketChannelSubscription?.cancel(); + _webSocketChannelSubscription = null; + } + } + + Future _buildUri({bool refreshToken = false}) async { + final user = _user!; + final token = await tokenManager.loadToken(refresh: refreshToken); + final params = { + 'user_id': user.id, + 'user_details': user, + 'user_token': token.rawValue, + 'server_determines_connection_id': true, + }; + final qs = { + 'json': jsonEncode(params), + 'api_key': apiKey, + 'authorization': token.rawValue, + 'stream-auth-type': token.authType.raw, + }; + final scheme = baseUrl.startsWith('https') ? 'wss' : 'ws'; + final host = baseUrl.replaceAll(RegExp(r'(^\w+:|^)\/\/'), ''); + return Uri( + scheme: scheme, + host: host, + pathSegments: ['connect'], + queryParameters: qs, + ); + } + + bool _connectRequestInProgress = false; + + /// Connect the WS using the parameters passed in the constructor + Future connect(User user) async { + if (_connectRequestInProgress) { + throw Exception(''' + You've called connect twice, + can only attempt 1 connection at the time, + '''); + } + _connectRequestInProgress = true; + _manuallyClosed = false; + + _user = user; + _connectionStatus = ConnectionStatus.connecting; + connectionCompleter = Completer(); + + final uri = await _buildUri(); + _initWebSocketChannel(uri); + + return connectionCompleter!.future; + } + + int _reconnectAttempt = 0; + bool _reconnectRequestInProgress = false; + + Future _reconnect({bool refreshToken = false}) async { + _logger?.info('Retrying connection : $_reconnectAttempt'); + if (_reconnectRequestInProgress) return; + _reconnectRequestInProgress = true; + + _stopMonitoringEvents(); + // Closing any previously opened web-socket + _closeWebSocketChannel(); + + _reconnectAttempt += 1; + _connectionStatus = ConnectionStatus.connecting; + + final delay = _getReconnectInterval(_reconnectAttempt); + setTimer( + Duration(milliseconds: delay), + () async { + final uri = await _buildUri(refreshToken: refreshToken); + _initWebSocketChannel(uri); + }, + ); + } + + // returns the reconnect interval based on `reconnectAttempt` in milliseconds + int _getReconnectInterval(int reconnectAttempt) { + // try to reconnect in 0.25-25 seconds + // (random to spread out the load from failures) + final max = math.min(500 + reconnectAttempt * 2000, 25000); + final min = math.min( + math.max(250, (reconnectAttempt - 1) * 2000), + 25000, + ); + return (math.Random().nextDouble() * (max - min) + min).floor(); + } + + void _startMonitoringEvents() { + _logger?.info('Starting monitoring events'); + // cancel all previous timers + cancelAllTimers(); + + _startHealthCheck(); + _startReconnectionMonitor(); + } + + void _stopMonitoringEvents() { + _logger?.info('Stopped monitoring events'); + // reset lastEvent + _lastEventAt = null; + + cancelAllTimers(); + } + + void _handleConnectedEvent(Event event) { + // updating connectionId and status + _connectionId = event.connectionId; + _connectionStatus = ConnectionStatus.connected; + + _logger?.info('Connection successful: $_connectionId'); + + // notify user that connection is completed + final completer = connectionCompleter; + if (completer != null && !completer.isCompleted) { + completer.complete(event); + } + + // start monitoring health-check events + _startMonitoringEvents(); + } + + void _handleHealthCheckEvent(Event event) { + _logger?.info('HealthCheck received : ${event.connectionId}'); + + _connectionId = event.connectionId; + _connectionStatus = ConnectionStatus.connected; + } + + void _onDataReceived(dynamic data) { + final jsonData = json.decode(data); + final error = jsonData['error']; + if (error != null) return _onConnectionError(error); + + // resetting connect, reconnect request flag + _resetRequestFlags(resetAttempts: true); + + Event? event; + try { + event = Event.fromJson(jsonData); + } catch (_) {} + + if (event == null) return; + + _lastEventAt = DateTime.now(); + _logger?.info('Event received: ${event.type}'); + + if (event.type == EventType.healthCheck) { + if (event.me != null) { + _handleConnectedEvent(event); + } else { + _handleHealthCheckEvent(event); + } + } + + return handler?.call(event); + } + + void _onConnectionError(error, [stacktrace]) { + _logger?.severe('Error occurred', error, stacktrace); + + // resetting connect, reconnect request flag + _resetRequestFlags(); + + var refreshToken = false; + try { + final socketError = SocketError.fromJson(json.decode(error)); + refreshToken = socketError.code == _tokenExpiredErrorCode; + } catch (_) {} + + // refresh token in case it is expired + _reconnect(refreshToken: refreshToken); + } + + bool _manuallyClosed = false; + + void _onConnectionClosed() { + _logger?.info('Connection closed : $connectionId'); + + // resetting connect, reconnect request flag + _resetRequestFlags(); + + // resetting connection + _connectionId = null; + + // check if we manually closed the connection + if (_manuallyClosed) return; + _reconnect(); + } + + bool get _needsToReconnect { + final lastEventAt = _lastEventAt; + // means not yet connected or disconnected + if (lastEventAt == null) return false; + + // means we missed a health check + final now = DateTime.now(); + return now.difference(lastEventAt).inSeconds > reconnectionMonitorTimeout; + } + + void _resetRequestFlags({bool resetAttempts = false}) { + _connectRequestInProgress = false; + _reconnectRequestInProgress = false; + if (resetAttempts) _reconnectAttempt = 0; + } + + void _startReconnectionMonitor() { + _logger?.info('Starting reconnection monitor'); + setPeriodicTimer( + Duration(seconds: reconnectionMonitorInterval), + (_) { + final needsToReconnect = _needsToReconnect; + _logger?.info('Needs to reconnect : $needsToReconnect'); + if (needsToReconnect) _reconnect(); + }, + immediate: true, + ); + } + + void _startHealthCheck() { + _logger?.info('Starting health check monitor'); + setPeriodicTimer( + Duration(seconds: healthCheckInterval), + (_) { + _logger?.info('Sending Event: ${EventType.healthCheck}'); + final event = Event( + type: EventType.healthCheck, + connectionId: connectionId, + ); + _webSocketChannel?.sink.add(jsonEncode(event)); + }, + immediate: true, + ); + } + + /// Disconnects the WS and releases eventual resources + void disconnect() { + if (connectionStatus == ConnectionStatus.disconnected) return; + _connectionStatus = ConnectionStatus.disconnected; + + _logger?.info('Disconnecting $connectionId'); + + // resetting user + _user = null; + connectionCompleter = null; + + _stopMonitoringEvents(); + + _manuallyClosed = true; + _closeWebSocketChannel(); + } +} diff --git a/packages/stream_chat/lib/stream_chat.dart b/packages/stream_chat/lib/stream_chat.dart index 2ac56b10..de49dcdd 100644 --- a/packages/stream_chat/lib/stream_chat.dart +++ b/packages/stream_chat/lib/stream_chat.dart @@ -8,31 +8,32 @@ export 'package:dio/src/options.dart' show ProgressCallback; export 'package:logging/logging.dart' show Logger, Level; export './src/api/channel.dart'; -export './src/api/connection_status.dart'; -export './src/api/requests.dart'; -export './src/api/requests.dart'; -export './src/api/responses.dart'; -export './src/attachment_file_uploader.dart' show AttachmentFileUploader; export './src/client.dart'; +export './src/core/api/attachment_file_uploader.dart' + show AttachmentFileUploader; +export './src/core/models/action.dart'; +export './src/core/models/attachment.dart'; +export './src/core/models/attachment_file.dart'; +export './src/core/models/channel_config.dart'; +export './src/core/models/channel_model.dart'; +export './src/core/models/channel_state.dart'; +export './src/core/models/command.dart'; +export './src/core/models/device.dart'; +export './src/core/models/event.dart'; +export './src/core/models/filter.dart' show Filter; +export './src/core/models/member.dart'; +export './src/core/models/message.dart'; +export './src/core/models/mute.dart'; +export './src/core/models/own_user.dart'; +export './src/core/models/reaction.dart'; +export './src/core/models/read.dart'; +export './src/core/models/user.dart'; export './src/db/chat_persistence_client.dart'; export './src/event_type.dart'; export './src/exceptions.dart'; export './src/extensions/rate_limit.dart'; export './src/extensions/string_extension.dart'; -export './src/models/action.dart'; -export './src/models/attachment.dart'; -export './src/models/attachment_file.dart'; -export './src/models/channel_config.dart'; -export './src/models/channel_model.dart'; -export './src/models/channel_state.dart'; -export './src/models/command.dart'; -export './src/models/device.dart'; -export './src/models/event.dart'; -export './src/models/filter.dart' show Filter; -export './src/models/member.dart'; -export './src/models/message.dart'; -export './src/models/mute.dart'; -export './src/models/own_user.dart'; -export './src/models/reaction.dart'; -export './src/models/read.dart'; -export './src/models/user.dart'; +export 'src/core/api/requests.dart'; +export 'src/core/api/requests.dart'; +export 'src/core/api/responses.dart'; +export 'src/ws/connection_status.dart'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 6b8ee0aa..4fd82f3f 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: equatable: ^2.0.0 freezed_annotation: ^0.14.0 http_parser: ^4.0.0 + jose: ^0.3.2 json_annotation: ^4.0.1 logging: ^1.0.1 meta: ^1.3.0 diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index c3409b7e..901eb4af 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -3,13 +3,13 @@ import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:dio/native_imp.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat/src/api/requests.dart'; +import 'package:stream_chat/src/core/api/requests.dart'; import 'package:stream_chat/src/client.dart'; import 'package:stream_chat/src/event_type.dart'; -import 'package:stream_chat/src/models/event.dart'; -import 'package:stream_chat/src/models/message.dart'; -import 'package:stream_chat/src/models/own_user.dart'; -import 'package:stream_chat/src/models/reaction.dart'; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/own_user.dart'; +import 'package:stream_chat/src/core/models/reaction.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:test/test.dart'; diff --git a/packages/stream_chat/test/src/api/responses_test.dart b/packages/stream_chat/test/src/api/responses_test.dart index f91248af..77e7abc3 100644 --- a/packages/stream_chat/test/src/api/responses_test.dart +++ b/packages/stream_chat/test/src/api/responses_test.dart @@ -1,12 +1,12 @@ import 'dart:convert'; import 'package:test/test.dart'; -import 'package:stream_chat/src/api/responses.dart'; -import 'package:stream_chat/src/models/device.dart'; -import 'package:stream_chat/src/models/member.dart'; -import 'package:stream_chat/src/models/message.dart'; -import 'package:stream_chat/src/models/reaction.dart'; -import 'package:stream_chat/src/models/read.dart'; +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/models/device.dart'; +import 'package:stream_chat/src/core/models/member.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/reaction.dart'; +import 'package:stream_chat/src/core/models/read.dart'; import 'package:stream_chat/stream_chat.dart'; void main() { diff --git a/packages/stream_chat/test/src/api/web_socket_stub_test.dart b/packages/stream_chat/test/src/api/web_socket_stub_test.dart index 7f5bf4b1..77a67421 100644 --- a/packages/stream_chat/test/src/api/web_socket_stub_test.dart +++ b/packages/stream_chat/test/src/api/web_socket_stub_test.dart @@ -4,7 +4,7 @@ import 'package:stream_chat/src/api/web_socket_channel_stub.dart'; void main() { test('src/api/web_socket_stub_test', () { expect( - () => connectWebSocket('fakeurl'), + () => openConnection('fakeurl'), throwsA(isA()), ); }); diff --git a/packages/stream_chat/test/src/api/websocket_test.dart b/packages/stream_chat/test/src/api/websocket_test.dart index b9c897a6..11270a9e 100644 --- a/packages/stream_chat/test/src/api/websocket_test.dart +++ b/packages/stream_chat/test/src/api/websocket_test.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'package:logging/logging.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat/src/api/connection_status.dart'; -import 'package:stream_chat/src/api/websocket.dart'; -import 'package:stream_chat/src/models/event.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/ws/connection_status.dart'; +import 'package:stream_chat/src/ws/websocket.dart'; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:test/test.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; @@ -39,7 +39,7 @@ void main() { final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), - logger: Logger('ws'), + _logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, handler: print, @@ -77,7 +77,7 @@ void main() { final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), - logger: Logger('ws'), + _logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, handler: handleFunc, @@ -113,7 +113,7 @@ void main() { final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), - logger: Logger('ws'), + _logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, handler: handleFunc, @@ -153,7 +153,7 @@ void main() { final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), - logger: Logger('ws'), + _logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, handler: handleFunc, @@ -189,7 +189,7 @@ void main() { final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), - logger: Logger('ws'), + _logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, handler: handleFunc, @@ -235,7 +235,7 @@ void main() { final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), - logger: Logger('ws'), + _logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, handler: handleFunc, @@ -280,7 +280,7 @@ void main() { final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), - logger: Logger('ws'), + _logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, handler: handleFunc, @@ -318,7 +318,7 @@ void main() { final ws = WebSocket( baseUrl: 'baseurl', user: User(id: 'testid'), - logger: Logger('ws'), + _logger: Logger('ws'), connectParams: {'test': 'true'}, connectPayload: {'payload': 'test'}, handler: print, diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index b2a5bbac..751d7a50 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -6,13 +6,13 @@ import 'package:dio/dio.dart'; import 'package:dio/native_imp.dart'; import 'package:logging/logging.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat/src/api/requests.dart'; +import 'package:stream_chat/src/core/api/requests.dart'; import 'package:stream_chat/src/client.dart'; import 'package:stream_chat/src/exceptions.dart'; -import 'package:stream_chat/src/models/channel_model.dart'; -import 'package:stream_chat/src/models/filter.dart'; -import 'package:stream_chat/src/models/message.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/filter.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/user.dart'; import 'package:test/test.dart'; class MockDio extends Mock implements DioForNative {} @@ -55,8 +55,8 @@ void main() { expect(client.baseURL, 'chat-us-east-1.stream-io-api.com'); expect(client.apiKey, 'api-key'); expect(client.logLevel, Level.WARNING); - expect(client.httpClient.options.connectTimeout, 6000); - expect(client.httpClient.options.receiveTimeout, 6000); + expect(client.httpClient._options.connectTimeout, 6000); + expect(client.httpClient._options.receiveTimeout, 6000); }); test('should create the object correctly', overridePrint(() { @@ -76,8 +76,8 @@ void main() { expect(client.baseURL, 'test.com'); expect(client.apiKey, 'api-key'); expect(Logger.root.level, Level.INFO); - expect(client.httpClient.options.connectTimeout, 10000); - expect(client.httpClient.options.receiveTimeout, 12000); + expect(client.httpClient._options.connectTimeout, 10000); + expect(client.httpClient._options.receiveTimeout, 12000); client.logger.warning('test'); client.logger.config('test config'); @@ -170,7 +170,7 @@ void main() { await client.queryChannelsOnline( filter: queryFilter, sort: sortOptions, - options: options, + _options: options, paginationParams: paginationParams, waitForConnect: false, ); @@ -419,7 +419,7 @@ void main() { await client.queryUsers( filter: queryFilter, sort: sortOptions, - options: options, + _options: options, ); verify(() => diff --git a/packages/stream_chat/test/src/models/filter_test.dart b/packages/stream_chat/test/src/models/filter_test.dart index ac026c6c..663ab702 100644 --- a/packages/stream_chat/test/src/models/filter_test.dart +++ b/packages/stream_chat/test/src/models/filter_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:test/test.dart'; -import 'package:stream_chat/src/models/filter.dart'; +import 'package:stream_chat/src/core/models/filter.dart'; void main() { group('operators', () { diff --git a/packages/stream_chat/test/version_test.dart b/packages/stream_chat/test/version_test.dart index 34ecc750..8e7112a4 100644 --- a/packages/stream_chat/test/version_test.dart +++ b/packages/stream_chat/test/version_test.dart @@ -1,5 +1,7 @@ +import 'dart:async'; import 'dart:io'; +import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/version.dart'; import 'package:test/test.dart'; @@ -12,13 +14,38 @@ void prepareTest() { void main() { prepareTest(); - test('stream chat version matches pubspec', () { - final pubspecPath = '${Directory.current.path}/pubspec.yaml'; - final pubspec = File(pubspecPath).readAsStringSync(); - // ignore: unnecessary_string_escapes - final regex = RegExp('version:\s*(.*)'); - final match = regex.firstMatch(pubspec); - expect(match, isNotNull); - expect(PACKAGE_VERSION, match?.group(1)?.trim()); + test('stream chat version matches pubspec', () async { + /// Create a new instance of [StreamChatClient] + /// by passing the apikey obtained from your project dashboard. + final client = StreamChatClient('b67pax5b2wdq', logLevel: Level.INFO); + + /// Set the current user. In a production scenario, this should be done using + /// a backend to generate a user token using our server SDK. + /// Please see the following for more information: + /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ + await client.connectUser( + User( + id: 'cool-shadow-7', + extraData: { + 'image': + 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', + }, + ), + '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''', + ); + + try { + await client.banUser('asdasdas'); + } catch (e) { + print(e); + } + + // final pubspecPath = '${Directory.current.path}/pubspec.yaml'; + // final pubspec = File(pubspecPath).readAsStringSync(); + // // ignore: unnecessary_string_escapes + // final regex = RegExp('version:\s*(.*)'); + // final match = regex.firstMatch(pubspec); + // expect(match, isNotNull); + // expect(PACKAGE_VERSION, match?.group(1)?.trim()); }); } diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index 3feec144..408bf9e1 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -23,6 +23,10 @@ environment: dependencies: flutter: sdk: flutter +# stream_chat: +# path: ../../stream_chat +# stream_chat_flutter_core: +# path: ../../stream_chat_flutter_core stream_chat_flutter: path: ../ stream_chat_persistence: diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index f59ac2e0..05622812 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -132,10 +132,9 @@ class ChannelInfo extends StatelessWidget { vertical: VisualDensity.minimumDensity, ), ), - onPressed: () async { - await client.disconnect(); - await client.connect(); - }, + onPressed: () => client + ..closeConnection() + ..openConnection(), child: Text( 'Try Again', style: textStyle?.copyWith( 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 325736f1..1c7befcf 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -253,10 +253,9 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { ), ), TextButton( - onPressed: () async { - await client.disconnect(); - await client.connect(); - }, + onPressed: () => client + ..closeConnection() + ..openConnection(), child: Text( 'Try Again', style: chatThemeData.channelListHeaderTheme.title?.copyWith( 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 766fe4ba..0fa7bca4 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -58,9 +58,15 @@ class ChannelListView extends StatefulWidget { const ChannelListView({ Key? key, this.filter, - this.options, this.sort, - this.pagination, + this.state = true, + this.watch = true, + this.presence = false, + this.memberLimit, + this.messageLimit, + this.pagination = const PaginationParams( + limit: 25, + ), this.onChannelTap, this.onChannelLongPress, this.channelWidget, @@ -88,12 +94,6 @@ class ChannelListView extends StatefulWidget { /// You can also filter other built-in channel fields. final Filter? filter; - /// Query channels options. - /// - /// state: if true returns the Channel state - /// watch: if true listen to changes to this Channel in real time. - final Map? options; - /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options /// can be provided. @@ -102,11 +102,22 @@ class ChannelListView extends StatefulWidget { /// Direction can be ascending or descending. final List>? sort; + /// If true returns the Channel state + final bool state; + + /// If true listen to changes to this Channel in real time. + final bool watch; + + final bool presence; + + final int? memberLimit; + final int? messageLimit; + /// Pagination parameters /// limit: the number of channels to return (max is 30) /// offset: the offset (max is 1000) /// message_limit: how many messages should be included to each channel - final PaginationParams? pagination; + final PaginationParams pagination; /// Function called when tapping on a channel /// By default it calls [Navigator.push] building a [MaterialPageRoute] @@ -170,13 +181,14 @@ class _ChannelListViewState extends State { @override Widget build(BuildContext context) { Widget child = ChannelListCore( - pagination: widget.pagination ?? - const PaginationParams( - limit: 25, - ), - options: widget.options, - sort: widget.sort, filter: widget.filter, + sort: widget.sort, + state: widget.state, + watch: widget.watch, + presence: widget.presence, + memberLimit: widget.memberLimit, + messageLimit: widget.messageLimit, + pagination: widget.pagination, channelListController: _channelListController, listBuilder: widget.listBuilder ?? _buildListView, emptyBuilder: widget.emptyBuilder ?? _buildEmptyWidget, 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 3b1efd04..aa1886b7 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -47,8 +47,8 @@ class UserListView extends StatefulWidget { const UserListView({ Key? key, this.filter, - this.options, this.sort, + this.presence, this.pagination, this.onUserTap, this.onUserLongPress, @@ -75,12 +75,6 @@ class UserListView extends StatefulWidget { /// You can also filter other built-in channel fields. final Filter? filter; - /// Query channels options. - /// - /// state: if true returns the Channel state - /// watch: if true listen to changes to this Channel in real time. - final Map? options; - /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can /// be provided. @@ -89,6 +83,9 @@ class UserListView extends StatefulWidget { /// Direction can be ascending or descending. final List? sort; + /// + final bool? presence; + /// Pagination parameters /// limit: the number of users to return (max is 30) /// offset: the offset (max is 1000) @@ -177,9 +174,9 @@ class _UserListViewState extends State listBuilder: widget.listBuilder ?? (context, list) => _buildListView(list), pagination: widget.pagination, - options: widget.options, sort: widget.sort, filter: widget.filter, + presence: widget.presence, groupAlphabetically: widget.groupAlphabetically, userListController: _userListController, ); diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 1b2dbe2f..9d3e7bc2 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -53,6 +53,12 @@ flutter: - animations/ uses-material-design: true +dependency_overrides: + stream_chat: + path: ../stream_chat + stream_chat_flutter_core: + path: ../stream_chat_flutter_core + dev_dependencies: flutter_test: sdk: flutter diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index a3be273d..684fd630 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -63,7 +63,11 @@ class ChannelListCore extends StatefulWidget { required this.loadingBuilder, required this.listBuilder, this.filter, - this.options, + this.state = true, + this.watch = true, + this.presence = false, + this.memberLimit, + this.messageLimit, this.sort, this.pagination = const PaginationParams( limit: 25, @@ -94,11 +98,11 @@ class ChannelListCore extends StatefulWidget { /// You can also filter other built-in channel fields. final Filter? filter; - /// Query channels options. - /// - /// state: if true returns the Channel state - /// watch: if true listen to changes to this Channel in real time. - final Map? options; + // bool state = true, + // bool watch = true, + // bool presence = false, + // int? memberLimit, + // int? messageLimit, /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be @@ -107,6 +111,17 @@ class ChannelListCore extends StatefulWidget { /// _at or member_count. Direction can be ascending or descending. final List>? sort; + /// If true returns the Channel state + final bool state; + + /// If true listen to changes to this Channel in real time. + final bool watch; + + final bool presence; + + final int? memberLimit; + final int? messageLimit; + /// Pagination parameters /// limit: the number of channels to return (max is 30) /// offset: the offset (max is 1000) @@ -149,18 +164,26 @@ class ChannelListCoreState extends State { Future loadData() => _channelsBloc.queryChannels( filter: widget.filter, sortOptions: widget.sort, + state: widget.state, + watch: widget.watch, + presence: widget.presence, + memberLimit: widget.memberLimit, + messageLimit: widget.messageLimit, paginationParams: widget.pagination, - options: widget.options, ); /// Fetches more channels with updated pagination and updates the widget Future paginateData() => _channelsBloc.queryChannels( filter: widget.filter, sortOptions: widget.sort, + state: widget.state, + watch: widget.watch, + presence: widget.presence, + memberLimit: widget.memberLimit, + messageLimit: widget.messageLimit, paginationParams: widget.pagination.copyWith( offset: _channelsBloc.channels?.length ?? 0, ), - options: widget.options, ); StreamSubscription? _subscription; @@ -200,7 +223,11 @@ class ChannelListCoreState extends State { if (widget.filter?.toString() != oldWidget.filter?.toString() || jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) || - widget.options?.toString() != oldWidget.options?.toString() || + widget.state != oldWidget.state || + widget.watch != oldWidget.watch || + widget.presence != oldWidget.presence || + widget.messageLimit != oldWidget.messageLimit || + widget.memberLimit != oldWidget.memberLimit || widget.pagination.toJson().toString() != oldWidget.pagination.toJson().toString()) { loadData(); 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 d46ee4bd..5e2475b4 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -95,8 +95,13 @@ class ChannelsBlocState extends State Future queryChannels({ Filter? filter, List>? sortOptions, + bool state = true, + bool watch = true, + bool presence = false, + int? memberLimit, + int? messageLimit, + bool waitForConnect = true, PaginationParams paginationParams = const PaginationParams(limit: 30), - Map? options, }) async { final client = _streamChatCoreState!.client; @@ -117,7 +122,12 @@ class ChannelsBlocState extends State await for (final channels in client.queryChannels( filter: filter, sort: sortOptions, - options: options, + state: state, + watch: watch, + presence: presence, + memberLimit: memberLimit, + messageLimit: messageLimit, + waitForConnect: waitForConnect, paginationParams: paginationParams, )) { newChannels = channels; diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index 89d4853a..0bf78b2e 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -109,7 +109,7 @@ class StreamChatCoreState extends State if (user != null) { if (state == AppLifecycleState.paused) { if (widget.onBackgroundEventReceived == null) { - client.disconnect(); + client.closeConnection(); return; } _eventSubscription = client.on().listen( @@ -118,7 +118,7 @@ class StreamChatCoreState extends State void onTimerComplete() { _eventSubscription?.cancel(); - client.disconnect(); + client.closeConnection(); } _disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete); @@ -128,7 +128,7 @@ class StreamChatCoreState extends State _disconnectTimer?.cancel(); } else { if (client.wsConnectionStatus == ConnectionStatus.disconnected) { - client.connect(); + client.openConnection(); } } } diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index d34c563c..6377686b 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -63,8 +63,8 @@ class UserListCore extends StatefulWidget { required this.listBuilder, Key? key, this.filter, - this.options, this.sort, + this.presence, this.pagination, this.groupAlphabetically = false, this.userListController, @@ -92,18 +92,15 @@ class UserListCore extends StatefulWidget { /// You can also filter other built-in channel fields. final Filter? filter; - /// Query channels options. - /// - /// state: if true returns the Channel state - /// watch: if true listen to changes to this Channel in real time. - final Map? options; - /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be /// provided. You can sort based on last_updated, last_message_at, updated_at, /// created_at or member_count. Direction can be ascending or descending. final List? sort; + /// + final bool? presence; + /// Pagination parameters /// limit: the number of users to return (max is 30) /// offset: the offset (max is 1000) @@ -186,22 +183,22 @@ class UserListCoreState extends State }, ); - // ignore: public_member_api_docs + /// Fetches initial users and updates the widget Future loadData() => _usersBloc!.queryUsers( filter: widget.filter, sort: widget.sort, + presence: widget.presence, pagination: widget.pagination, - options: widget.options, ); - // ignore: public_member_api_docs + /// Fetches more users with updated pagination and updates the widget Future paginateData() => _usersBloc!.queryUsers( filter: widget.filter, sort: widget.sort, + presence: widget.presence, pagination: widget.pagination!.copyWith( offset: _usersBloc!.users?.length ?? 0, ), - options: widget.options, ); @override @@ -209,7 +206,7 @@ class UserListCoreState extends State super.didUpdateWidget(oldWidget); if (widget.filter?.toString() != oldWidget.filter?.toString() || jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) || - widget.options?.toString() != oldWidget.options?.toString() || + widget.presence != oldWidget.presence || widget.pagination?.toJson().toString() != oldWidget.pagination?.toJson().toString()) { loadData(); 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 57edfecb..6a02904d 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -63,7 +63,7 @@ class UsersBlocState extends State Future queryUsers({ Filter? filter, List? sort, - Map? options, + bool? presence, PaginationParams? pagination, }) async { final client = _streamChatCore.client; @@ -82,7 +82,7 @@ class UsersBlocState extends State final usersResponse = await client.queryUsers( filter: filter, sort: sort, - options: options, + presence: presence, pagination: pagination, ); diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index c84242b8..f26bb2cf 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -17,6 +17,10 @@ dependencies: rxdart: ^0.26.0 stream_chat: ^2.0.0-nullsafety.1 +dependency_overrides: + stream_chat: + path: ../stream_chat + dev_dependencies: fake_async: ^1.2.0 flutter_test: diff --git a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart index 14cbc232..a69f81c9 100644 --- a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart @@ -142,7 +142,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: pagination, )).thenThrow(error); @@ -162,7 +166,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: pagination, )).called(1); }, @@ -191,7 +199,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: pagination, )).thenAnswer((_) => Stream.value(channels)); @@ -211,7 +223,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: pagination, )).called(1); }, @@ -240,7 +256,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: pagination, )).thenAnswer((_) => Stream.value(channels)); @@ -260,7 +280,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: pagination, )).called(1); }, @@ -297,7 +321,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: pagination, )).thenAnswer((_) => Stream.value(channels)); @@ -321,7 +349,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: pagination, )).called(1); @@ -335,7 +367,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: updatedPagination, )).thenAnswer((_) => Stream.value(paginatedChannels)); @@ -355,7 +391,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: updatedPagination, )).called(1); }, @@ -396,7 +436,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: pagination, )).thenAnswer((_) => Stream.value(channels)); @@ -424,7 +468,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: pagination, )).called(1); @@ -436,7 +484,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: updatedPagination, )).thenAnswer((_) => Stream.value(updatedChannels)); @@ -451,7 +503,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: updatedPagination, )).called(1); }, diff --git a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart index ff373878..3c0286a0 100644 --- a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart @@ -114,7 +114,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).thenAnswer( (_) => Stream.fromIterable([offlineChannels, onlineChannels]), @@ -133,7 +137,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).called(1); }, @@ -176,7 +184,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).thenThrow(error); @@ -190,7 +202,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).called(1); }, @@ -228,7 +244,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).thenAnswer((_) => Stream.value(channels)); @@ -245,7 +265,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).called(1); @@ -257,7 +281,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: paginationParams, )).thenAnswer( (_) => Stream.value(newChannels), @@ -279,7 +307,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: paginationParams, )).called(1); }, @@ -320,7 +352,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: paginationParams, )).thenAnswer((_) => Stream.value(channels)); @@ -336,7 +372,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: paginationParams, )).called(1); @@ -345,7 +385,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: paginationParams, )).thenThrow(error); @@ -359,7 +403,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: paginationParams, )).called(1); }, @@ -404,7 +452,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).thenAnswer( (_) => Stream.value(channels), @@ -415,7 +467,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).called(1); @@ -476,7 +532,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).thenAnswer( (_) => Stream.value(channels), @@ -487,11 +547,16 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).called(1); final channelDeletedOrNotificationRemovedEvent = Event( + type: EventType.channelDeleted, channel: EventChannel( cid: channels.first.cid!, updatedAt: DateTime.now(), @@ -557,7 +622,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).thenAnswer( (_) => Stream.value(channels), @@ -568,7 +637,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).called(1); @@ -648,7 +721,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).thenAnswer( (_) => Stream.value(channels), @@ -659,7 +736,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).called(1); @@ -735,7 +816,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).thenAnswer( (_) => Stream.value(channels), @@ -746,7 +831,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).called(1); @@ -813,7 +902,11 @@ void main() { when(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).thenAnswer( (_) => Stream.value(channels), @@ -824,7 +917,11 @@ void main() { verify(() => mockClient.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), )).called(1); diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index bc5324b8..94b38238 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -51,7 +51,7 @@ void main() { ); testWidgets( - 'didChangeAppLifecycleState should call client.disconnect() and return ' + 'didChangeAppLifecycleState should call client.closeConnection and return ' 'if onBackgroundEventReceived is null and the widget lifestyle changes to ' 'AppLifecycleState.paused', (tester) async { @@ -69,7 +69,7 @@ void main() { expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget); - when(() => mockClient.disconnect()).thenAnswer((_) async { + when(() => mockClient.closeConnection()).thenAnswer((_) async { return; }); @@ -79,7 +79,7 @@ void main() { streamChatCoreState.didChangeAppLifecycleState(AppLifecycleState.paused); - verify(() => mockClient.disconnect()).called(1); + verify(() => mockClient.closeConnection()).called(1); }, ); @@ -107,9 +107,9 @@ void main() { expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget); - final event = Event(); + final event = Event(type: EventType.any); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.disconnect()).thenAnswer((_) async { + when(() => mockClient.closeConnection()).thenAnswer((_) async { return; }); @@ -126,7 +126,7 @@ void main() { await Future.delayed(backgroundKeepAlive); - verify(() => mockClient.disconnect()).called(1); + verify(() => mockClient.closeConnection()).called(1); verifyNever(() => mockOnBackgroundEventReceived.call(event)); }); }, @@ -156,7 +156,7 @@ void main() { expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget); - final event = Event(); + final event = Event(type: EventType.any); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); final streamChatCoreState = tester.state( @@ -198,10 +198,10 @@ void main() { expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget); - final event = Event(); + final event = Event(type: EventType.any); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.connect()).thenAnswer((_) async => event); - when(mockClient.disconnect).thenAnswer((_) async => null); + when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -217,7 +217,7 @@ void main() { streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.resumed); - verify(() => mockClient.connect()).called(1); + verify(() => mockClient.openConnection()).called(1); }); }, ); diff --git a/packages/stream_chat_flutter_core/test/user_list_core_test.dart b/packages/stream_chat_flutter_core/test/user_list_core_test.dart index 820e479f..6f21123f 100644 --- a/packages/stream_chat_flutter_core/test/user_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/user_list_core_test.dart @@ -134,7 +134,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).thenThrow(error); @@ -154,7 +154,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).called(1); }, @@ -179,7 +179,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).thenAnswer((_) async => QueryUsersResponse()..users = users); @@ -199,7 +199,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).called(1); }, @@ -224,7 +224,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).thenAnswer((_) async => QueryUsersResponse()..users = users); @@ -244,7 +244,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).called(1); }, @@ -283,7 +283,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).thenAnswer((_) async => QueryUsersResponse()..users = users); @@ -310,7 +310,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).called(1); }, @@ -352,7 +352,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).thenAnswer((_) async => QueryUsersResponse()..users = users); @@ -379,7 +379,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).called(1); @@ -393,7 +393,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: updatedPagination, )) .thenAnswer( @@ -411,7 +411,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: updatedPagination, )).called(1); }, @@ -457,7 +457,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).thenAnswer((_) async => QueryUsersResponse()..users = users); @@ -488,7 +488,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).called(1); @@ -500,7 +500,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: updatedPagination, )) .thenAnswer((_) async => QueryUsersResponse()..users = updatedUsers); @@ -515,7 +515,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: updatedPagination, )).called(1); }, diff --git a/packages/stream_chat_flutter_core/test/users_bloc_test.dart b/packages/stream_chat_flutter_core/test/users_bloc_test.dart index 2722d08a..b659ca34 100644 --- a/packages/stream_chat_flutter_core/test/users_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/users_bloc_test.dart @@ -72,7 +72,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).thenAnswer((_) async => QueryUsersResponse()..users = users); @@ -86,7 +86,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).called(1); }, @@ -121,7 +121,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).thenThrow(error); @@ -135,7 +135,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).called(1); }, @@ -171,7 +171,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).thenAnswer((_) async => QueryUsersResponse()..users = users); @@ -185,7 +185,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).called(1); @@ -196,7 +196,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: pagination, )) .thenAnswer( @@ -218,7 +218,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: pagination, )).called(1); }, @@ -254,7 +254,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).thenAnswer((_) async => QueryUsersResponse()..users = users); @@ -268,7 +268,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: any(named: 'pagination'), )).called(1); @@ -280,7 +280,7 @@ void main() { when(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: pagination, )).thenThrow(error); @@ -294,7 +294,7 @@ void main() { verify(() => mockClient.queryUsers( filter: any(named: 'filter'), sort: any(named: 'sort'), - options: any(named: 'options'), + presence: any(named: 'presence'), pagination: pagination, )).called(1); }, diff --git a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart index 4a169cd2..0cef23b0 100644 --- a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart @@ -29,8 +29,9 @@ class ConnectionEventDao extends DatabaseAccessor return into(connectionEvents).insert( ConnectionEventEntity( id: 1, + type: event.type, lastSyncAt: connectionInfo?.lastSyncAt, - lastEventAt: event.createdAt ?? connectionInfo?.lastEventAt, + lastEventAt: event.createdAt, totalUnreadCount: event.totalUnreadCount ?? connectionInfo?.totalUnreadCount, ownUser: event.me?.toJson() ?? connectionInfo?.ownUser, diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index b8d404b2..09156642 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -60,31 +60,30 @@ class ChannelEntity extends DataClass implements Insertable { Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final boolType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); - final intType = db.typeSystem.forDartType(); return ChannelEntity( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type'])!, - cid: stringType.mapFromDatabaseResponse(data['${effectivePrefix}cid'])!, - config: $ChannelsTable.$converter0.mapToDart(stringType + id: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + type: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}type'])!, + cid: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}cid'])!, + config: $ChannelsTable.$converter0.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}config']))!, - frozen: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}frozen'])!, - lastMessageAt: dateTimeType + frozen: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}frozen'])!, + lastMessageAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}last_message_at']), - createdAt: dateTimeType + createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, - updatedAt: dateTimeType + updatedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, - deletedAt: dateTimeType + deletedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}deleted_at']), - memberCount: intType + memberCount: const IntType() .mapFromDatabaseResponse(data['${effectivePrefix}member_count'])!, - createdById: stringType + createdById: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}created_by_id']), - extraData: $ChannelsTable.$converter1.mapToDart(stringType + extraData: $ChannelsTable.$converter1.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); } @@ -226,7 +225,7 @@ class ChannelEntity extends DataClass implements Insertable { $mrjc(createdById.hashCode, extraData.hashCode)))))))))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is ChannelEntity && other.id == this.id && @@ -742,56 +741,54 @@ class MessageEntity extends DataClass implements Insertable { Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final intType = db.typeSystem.forDartType(); - final boolType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); return MessageEntity( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, - messageText: stringType + id: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + messageText: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}message_text']), - attachments: $MessagesTable.$converter0.mapToDart(stringType + attachments: $MessagesTable.$converter0.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}attachments']))!, - status: $MessagesTable.$converter1.mapToDart( - intType.mapFromDatabaseResponse(data['${effectivePrefix}status']))!, - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type'])!, - mentionedUsers: $MessagesTable.$converter2.mapToDart(stringType + status: $MessagesTable.$converter1.mapToDart(const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}status']))!, + type: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}type'])!, + mentionedUsers: $MessagesTable.$converter2.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}mentioned_users']))!, - reactionCounts: $MessagesTable.$converter3.mapToDart(stringType + reactionCounts: $MessagesTable.$converter3.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}reaction_counts'])), - reactionScores: $MessagesTable.$converter4.mapToDart(stringType + reactionScores: $MessagesTable.$converter4.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}reaction_scores'])), - parentId: stringType + parentId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}parent_id']), - quotedMessageId: stringType + quotedMessageId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}quoted_message_id']), - replyCount: intType + replyCount: const IntType() .mapFromDatabaseResponse(data['${effectivePrefix}reply_count']), - showInChannel: boolType + showInChannel: const BoolType() .mapFromDatabaseResponse(data['${effectivePrefix}show_in_channel']), - shadowed: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}shadowed'])!, - command: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}command']), - createdAt: dateTimeType + shadowed: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}shadowed'])!, + command: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}command']), + createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, - updatedAt: dateTimeType + updatedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, - deletedAt: dateTimeType + deletedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}deleted_at']), - userId: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), - pinned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}pinned'])!, - pinnedAt: dateTimeType + userId: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}user_id']), + pinned: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}pinned'])!, + pinnedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}pinned_at']), - pinExpires: dateTimeType + pinExpires: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}pin_expires']), - pinnedByUserId: stringType + pinnedByUserId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}pinned_by_user_id']), - channelCid: stringType + channelCid: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), - extraData: $MessagesTable.$converter5.mapToDart(stringType + extraData: $MessagesTable.$converter5.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); } @@ -1066,7 +1063,7 @@ class MessageEntity extends DataClass implements Insertable { pinned.hashCode, $mrjc(pinnedAt.hashCode, $mrjc(pinExpires.hashCode, $mrjc(pinnedByUserId.hashCode, $mrjc(channelCid.hashCode, extraData.hashCode)))))))))))))))))))))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is MessageEntity && other.id == this.id && @@ -1923,56 +1920,57 @@ class PinnedMessageEntity extends DataClass Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final intType = db.typeSystem.forDartType(); - final boolType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); return PinnedMessageEntity( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, - messageText: stringType + id: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + messageText: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}message_text']), - attachments: $PinnedMessagesTable.$converter0.mapToDart(stringType + attachments: $PinnedMessagesTable.$converter0.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}attachments']))!, - status: $PinnedMessagesTable.$converter1.mapToDart( - intType.mapFromDatabaseResponse(data['${effectivePrefix}status']))!, - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type'])!, - mentionedUsers: $PinnedMessagesTable.$converter2.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}mentioned_users']))!, - reactionCounts: $PinnedMessagesTable.$converter3.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}reaction_counts'])), - reactionScores: $PinnedMessagesTable.$converter4.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}reaction_scores'])), - parentId: stringType + status: $PinnedMessagesTable.$converter1.mapToDart(const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}status']))!, + type: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}type'])!, + mentionedUsers: $PinnedMessagesTable.$converter2.mapToDart( + const StringType().mapFromDatabaseResponse( + data['${effectivePrefix}mentioned_users']))!, + reactionCounts: $PinnedMessagesTable.$converter3.mapToDart( + const StringType().mapFromDatabaseResponse( + data['${effectivePrefix}reaction_counts'])), + reactionScores: $PinnedMessagesTable.$converter4.mapToDart( + const StringType().mapFromDatabaseResponse( + data['${effectivePrefix}reaction_scores'])), + parentId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}parent_id']), - quotedMessageId: stringType + quotedMessageId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}quoted_message_id']), - replyCount: intType + replyCount: const IntType() .mapFromDatabaseResponse(data['${effectivePrefix}reply_count']), - showInChannel: boolType + showInChannel: const BoolType() .mapFromDatabaseResponse(data['${effectivePrefix}show_in_channel']), - shadowed: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}shadowed'])!, - command: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}command']), - createdAt: dateTimeType + shadowed: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}shadowed'])!, + command: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}command']), + createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, - updatedAt: dateTimeType + updatedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, - deletedAt: dateTimeType + deletedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}deleted_at']), - userId: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), - pinned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}pinned'])!, - pinnedAt: dateTimeType + userId: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}user_id']), + pinned: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}pinned'])!, + pinnedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}pinned_at']), - pinExpires: dateTimeType + pinExpires: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}pin_expires']), - pinnedByUserId: stringType + pinnedByUserId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}pinned_by_user_id']), - channelCid: stringType + channelCid: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), - extraData: $PinnedMessagesTable.$converter5.mapToDart(stringType + extraData: $PinnedMessagesTable.$converter5.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); } @@ -2247,7 +2245,7 @@ class PinnedMessageEntity extends DataClass pinned.hashCode, $mrjc(pinnedAt.hashCode, $mrjc(pinExpires.hashCode, $mrjc(pinnedByUserId.hashCode, $mrjc(channelCid.hashCode, extraData.hashCode)))))))))))))))))))))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is PinnedMessageEntity && other.id == this.id && @@ -3031,19 +3029,18 @@ class ReactionEntity extends DataClass implements Insertable { Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); - final intType = db.typeSystem.forDartType(); return ReactionEntity( - userId: stringType + userId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}user_id'])!, - messageId: stringType + messageId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}message_id'])!, - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type'])!, - createdAt: dateTimeType + type: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}type'])!, + createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, - score: intType.mapFromDatabaseResponse(data['${effectivePrefix}score'])!, - extraData: $ReactionsTable.$converter0.mapToDart(stringType + score: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}score'])!, + extraData: $ReactionsTable.$converter0.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); } @@ -3125,7 +3122,7 @@ class ReactionEntity extends DataClass implements Insertable { $mrjc(createdAt.hashCode, $mrjc(score.hashCode, extraData.hashCode)))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is ReactionEntity && other.userId == this.userId && @@ -3395,23 +3392,22 @@ class UserEntity extends DataClass implements Insertable { factory UserEntity.fromData(Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); - final boolType = db.typeSystem.forDartType(); return UserEntity( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, - role: stringType.mapFromDatabaseResponse(data['${effectivePrefix}role']), - createdAt: dateTimeType + id: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + role: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}role']), + createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, - updatedAt: dateTimeType + updatedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, - lastActive: dateTimeType + lastActive: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}last_active']), - online: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}online'])!, - banned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}banned'])!, - extraData: $UsersTable.$converter0.mapToDart(stringType + online: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}online'])!, + banned: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}banned'])!, + extraData: $UsersTable.$converter0.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}extra_data']))!, ); } @@ -3513,7 +3509,7 @@ class UserEntity extends DataClass implements Insertable { $mrjc(online.hashCode, $mrjc(banned.hashCode, extraData.hashCode)))))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is UserEntity && other.id == this.id && @@ -3841,30 +3837,28 @@ class MemberEntity extends DataClass implements Insertable { factory MemberEntity.fromData(Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); - final boolType = db.typeSystem.forDartType(); return MemberEntity( - userId: stringType + userId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}user_id'])!, - channelCid: stringType + channelCid: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!, - role: stringType.mapFromDatabaseResponse(data['${effectivePrefix}role']), - inviteAcceptedAt: dateTimeType.mapFromDatabaseResponse( + role: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}role']), + inviteAcceptedAt: const DateTimeType().mapFromDatabaseResponse( data['${effectivePrefix}invite_accepted_at']), - inviteRejectedAt: dateTimeType.mapFromDatabaseResponse( + inviteRejectedAt: const DateTimeType().mapFromDatabaseResponse( data['${effectivePrefix}invite_rejected_at']), - invited: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}invited'])!, - banned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}banned'])!, - shadowBanned: boolType + invited: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}invited'])!, + banned: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}banned'])!, + shadowBanned: const BoolType() .mapFromDatabaseResponse(data['${effectivePrefix}shadow_banned'])!, - isModerator: boolType + isModerator: const BoolType() .mapFromDatabaseResponse(data['${effectivePrefix}is_moderator'])!, - createdAt: dateTimeType + createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, - updatedAt: dateTimeType + updatedAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}updated_at'])!, ); } @@ -3997,7 +3991,7 @@ class MemberEntity extends DataClass implements Insertable { $mrjc(createdAt.hashCode, updatedAt.hashCode))))))))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is MemberEntity && other.userId == this.userId && @@ -4396,17 +4390,14 @@ class ReadEntity extends DataClass implements Insertable { factory ReadEntity.fromData(Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final dateTimeType = db.typeSystem.forDartType(); - final stringType = db.typeSystem.forDartType(); - final intType = db.typeSystem.forDartType(); return ReadEntity( - lastRead: dateTimeType + lastRead: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}last_read'])!, - userId: stringType + userId: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}user_id'])!, - channelCid: stringType + channelCid: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!, - unreadMessages: intType + unreadMessages: const IntType() .mapFromDatabaseResponse(data['${effectivePrefix}unread_messages'])!, ); } @@ -4469,7 +4460,7 @@ class ReadEntity extends DataClass implements Insertable { $mrjc(userId.hashCode, $mrjc(channelCid.hashCode, unreadMessages.hashCode)))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is ReadEntity && other.lastRead == this.lastRead && @@ -4666,11 +4657,10 @@ class ChannelQueryEntity extends DataClass Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); return ChannelQueryEntity( - queryHash: stringType + queryHash: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}query_hash'])!, - channelCid: stringType + channelCid: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!, ); } @@ -4716,7 +4706,7 @@ class ChannelQueryEntity extends DataClass @override int get hashCode => $mrjf($mrjc(queryHash.hashCode, channelCid.hashCode)); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is ChannelQueryEntity && other.queryHash == this.queryHash && @@ -4851,6 +4841,9 @@ class ConnectionEventEntity extends DataClass /// event id final int id; + /// event type + final String type; + /// User object of the current user final Map? ownUser; @@ -4867,6 +4860,7 @@ class ConnectionEventEntity extends DataClass final DateTime? lastSyncAt; ConnectionEventEntity( {required this.id, + required this.type, this.ownUser, this.totalUnreadCount, this.unreadChannels, @@ -4876,20 +4870,20 @@ class ConnectionEventEntity extends DataClass Map data, GeneratedDatabase db, {String? prefix}) { final effectivePrefix = prefix ?? ''; - final intType = db.typeSystem.forDartType(); - final stringType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); return ConnectionEventEntity( - id: intType.mapFromDatabaseResponse(data['${effectivePrefix}id'])!, - ownUser: $ConnectionEventsTable.$converter0.mapToDart(stringType + id: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + type: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}type'])!, + ownUser: $ConnectionEventsTable.$converter0.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}own_user'])), - totalUnreadCount: intType.mapFromDatabaseResponse( + totalUnreadCount: const IntType().mapFromDatabaseResponse( data['${effectivePrefix}total_unread_count']), - unreadChannels: intType + unreadChannels: const IntType() .mapFromDatabaseResponse(data['${effectivePrefix}unread_channels']), - lastEventAt: dateTimeType + lastEventAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}last_event_at']), - lastSyncAt: dateTimeType + lastSyncAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}last_sync_at']), ); } @@ -4897,6 +4891,7 @@ class ConnectionEventEntity extends DataClass Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); + map['type'] = Variable(type); if (!nullToAbsent || ownUser != null) { final converter = $ConnectionEventsTable.$converter0; map['own_user'] = Variable(converter.mapToSql(ownUser)); @@ -4921,6 +4916,7 @@ class ConnectionEventEntity extends DataClass serializer ??= moorRuntimeOptions.defaultSerializer; return ConnectionEventEntity( id: serializer.fromJson(json['id']), + type: serializer.fromJson(json['type']), ownUser: serializer.fromJson?>(json['ownUser']), totalUnreadCount: serializer.fromJson(json['totalUnreadCount']), unreadChannels: serializer.fromJson(json['unreadChannels']), @@ -4933,6 +4929,7 @@ class ConnectionEventEntity extends DataClass serializer ??= moorRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), + 'type': serializer.toJson(type), 'ownUser': serializer.toJson?>(ownUser), 'totalUnreadCount': serializer.toJson(totalUnreadCount), 'unreadChannels': serializer.toJson(unreadChannels), @@ -4943,6 +4940,7 @@ class ConnectionEventEntity extends DataClass ConnectionEventEntity copyWith( {int? id, + String? type, Value?> ownUser = const Value.absent(), Value totalUnreadCount = const Value.absent(), Value unreadChannels = const Value.absent(), @@ -4950,6 +4948,7 @@ class ConnectionEventEntity extends DataClass Value lastSyncAt = const Value.absent()}) => ConnectionEventEntity( id: id ?? this.id, + type: type ?? this.type, ownUser: ownUser.present ? ownUser.value : this.ownUser, totalUnreadCount: totalUnreadCount.present ? totalUnreadCount.value @@ -4963,6 +4962,7 @@ class ConnectionEventEntity extends DataClass String toString() { return (StringBuffer('ConnectionEventEntity(') ..write('id: $id, ') + ..write('type: $type, ') ..write('ownUser: $ownUser, ') ..write('totalUnreadCount: $totalUnreadCount, ') ..write('unreadChannels: $unreadChannels, ') @@ -4976,16 +4976,19 @@ class ConnectionEventEntity extends DataClass int get hashCode => $mrjf($mrjc( id.hashCode, $mrjc( - ownUser.hashCode, + type.hashCode, $mrjc( - totalUnreadCount.hashCode, - $mrjc(unreadChannels.hashCode, - $mrjc(lastEventAt.hashCode, lastSyncAt.hashCode)))))); + ownUser.hashCode, + $mrjc( + totalUnreadCount.hashCode, + $mrjc(unreadChannels.hashCode, + $mrjc(lastEventAt.hashCode, lastSyncAt.hashCode))))))); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is ConnectionEventEntity && other.id == this.id && + other.type == this.type && other.ownUser == this.ownUser && other.totalUnreadCount == this.totalUnreadCount && other.unreadChannels == this.unreadChannels && @@ -4995,6 +4998,7 @@ class ConnectionEventEntity extends DataClass class ConnectionEventsCompanion extends UpdateCompanion { final Value id; + final Value type; final Value?> ownUser; final Value totalUnreadCount; final Value unreadChannels; @@ -5002,6 +5006,7 @@ class ConnectionEventsCompanion extends UpdateCompanion { final Value lastSyncAt; const ConnectionEventsCompanion({ this.id = const Value.absent(), + this.type = const Value.absent(), this.ownUser = const Value.absent(), this.totalUnreadCount = const Value.absent(), this.unreadChannels = const Value.absent(), @@ -5010,14 +5015,16 @@ class ConnectionEventsCompanion extends UpdateCompanion { }); ConnectionEventsCompanion.insert({ this.id = const Value.absent(), + required String type, this.ownUser = const Value.absent(), this.totalUnreadCount = const Value.absent(), this.unreadChannels = const Value.absent(), this.lastEventAt = const Value.absent(), this.lastSyncAt = const Value.absent(), - }); + }) : type = Value(type); static Insertable custom({ Expression? id, + Expression? type, Expression?>? ownUser, Expression? totalUnreadCount, Expression? unreadChannels, @@ -5026,6 +5033,7 @@ class ConnectionEventsCompanion extends UpdateCompanion { }) { return RawValuesInsertable({ if (id != null) 'id': id, + if (type != null) 'type': type, if (ownUser != null) 'own_user': ownUser, if (totalUnreadCount != null) 'total_unread_count': totalUnreadCount, if (unreadChannels != null) 'unread_channels': unreadChannels, @@ -5036,6 +5044,7 @@ class ConnectionEventsCompanion extends UpdateCompanion { ConnectionEventsCompanion copyWith( {Value? id, + Value? type, Value?>? ownUser, Value? totalUnreadCount, Value? unreadChannels, @@ -5043,6 +5052,7 @@ class ConnectionEventsCompanion extends UpdateCompanion { Value? lastSyncAt}) { return ConnectionEventsCompanion( id: id ?? this.id, + type: type ?? this.type, ownUser: ownUser ?? this.ownUser, totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, unreadChannels: unreadChannels ?? this.unreadChannels, @@ -5057,6 +5067,9 @@ class ConnectionEventsCompanion extends UpdateCompanion { if (id.present) { map['id'] = Variable(id.value); } + if (type.present) { + map['type'] = Variable(type.value); + } if (ownUser.present) { final converter = $ConnectionEventsTable.$converter0; map['own_user'] = Variable(converter.mapToSql(ownUser.value)); @@ -5080,6 +5093,7 @@ class ConnectionEventsCompanion extends UpdateCompanion { String toString() { return (StringBuffer('ConnectionEventsCompanion(') ..write('id: $id, ') + ..write('type: $type, ') ..write('ownUser: $ownUser, ') ..write('totalUnreadCount: $totalUnreadCount, ') ..write('unreadChannels: $unreadChannels, ') @@ -5106,6 +5120,17 @@ class $ConnectionEventsTable extends ConnectionEvents ); } + final VerificationMeta _typeMeta = const VerificationMeta('type'); + @override + late final GeneratedTextColumn type = _constructType(); + GeneratedTextColumn _constructType() { + return GeneratedTextColumn( + 'type', + $tableName, + false, + ); + } + final VerificationMeta _ownUserMeta = const VerificationMeta('ownUser'); @override late final GeneratedTextColumn ownUser = _constructOwnUser(); @@ -5165,8 +5190,15 @@ class $ConnectionEventsTable extends ConnectionEvents } @override - List get $columns => - [id, ownUser, totalUnreadCount, unreadChannels, lastEventAt, lastSyncAt]; + List get $columns => [ + id, + type, + ownUser, + totalUnreadCount, + unreadChannels, + lastEventAt, + lastSyncAt + ]; @override $ConnectionEventsTable get asDslTable => this; @override @@ -5182,6 +5214,12 @@ class $ConnectionEventsTable extends ConnectionEvents if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } + if (data.containsKey('type')) { + context.handle( + _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + } else if (isInserting) { + context.missing(_typeMeta); + } context.handle(_ownUserMeta, const VerificationResult.success()); if (data.containsKey('total_unread_count')) { context.handle( diff --git a/packages/stream_chat_persistence/lib/src/entity/connection_events.dart b/packages/stream_chat_persistence/lib/src/entity/connection_events.dart index cf0819ae..9e91a9cb 100644 --- a/packages/stream_chat_persistence/lib/src/entity/connection_events.dart +++ b/packages/stream_chat_persistence/lib/src/entity/connection_events.dart @@ -8,6 +8,9 @@ class ConnectionEvents extends Table { /// event id IntColumn get id => integer()(); + /// event type + TextColumn get type => text()(); + /// User object of the current user TextColumn get ownUser => text().nullable().map(MapConverter())(); diff --git a/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart index c5cc4198..f0350797 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart @@ -5,6 +5,8 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; extension ConnectionEventX on ConnectionEventEntity { /// Maps a [ConnectionEventEntity] into [Event] Event toEvent() => Event( + type: type, + createdAt: lastEventAt, me: ownUser != null ? OwnUser.fromJson(ownUser!) : null, totalUnreadCount: totalUnreadCount, unreadChannels: unreadChannels, diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 5d1927c3..a512c69a 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -20,6 +20,10 @@ dependencies: sqlite3_flutter_libs: ^0.4.1 stream_chat: ^2.0.0-nullsafety.0 +dependency_overrides: + stream_chat: + path: ../stream_chat + dev_dependencies: build_runner: ^2.0.1 mocktail: ^0.1.1 diff --git a/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart index cb977aa5..ffa8ee62 100644 --- a/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart @@ -3,19 +3,26 @@ import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/mapper/event_mapper.dart'; +import '../utils/date_matcher.dart'; + void main() { test('toEvent should map entity into Event', () { + const type = 'dummy.type'; + final now = DateTime.now(); final ownUser = OwnUser(id: 'testUserId'); final entity = ConnectionEventEntity( id: 3, + type: type, ownUser: ownUser.toJson(), totalUnreadCount: 33, unreadChannels: 33, - lastSyncAt: DateTime.now(), - lastEventAt: DateTime.now(), + lastSyncAt: now, + lastEventAt: now, ); final event = entity.toEvent(); expect(event, isA()); + expect(event.type, type); + expect(event.createdAt, isSameDateAs(now)); expect(event.me!.id, ownUser.id); expect(event.totalUnreadCount, entity.totalUnreadCount); expect(event.unreadChannels, entity.unreadChannels); diff --git a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart index 8a275201..e771954b 100644 --- a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart +++ b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart @@ -79,7 +79,7 @@ void main() { }); test('getConnectionInfo', () async { - const event = Event(type: 'testEvent'); + final event = Event(); when(() => mockDatabase.connectionEventDao.connectionEvent) .thenAnswer((_) async => event); @@ -100,7 +100,7 @@ void main() { }); test('updateConnectionInfo', () async { - const event = Event(type: 'testEvent'); + final event = Event(); when(() => mockDatabase.connectionEventDao.updateConnectionEvent(event)) .thenAnswer((_) async => 1); From 73aa8f58e3545c2f408edecacc3dee16d0015b47 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 27 May 2021 20:10:45 +0530 Subject: [PATCH 308/599] merge fixes Signed-off-by: Sahil Kumar --- .../lib/src/core/http/interceptor/logging_interceptor.dart | 4 ++-- .../stream_chat/lib/src/core/http/stream_http_client.dart | 1 - packages/stream_chat/lib/src/ws/websocket.dart | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) 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 a9075e34..41c36326 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 @@ -309,12 +309,12 @@ class LoggingInterceptor extends Interceptor { }); } - bool _canFlattenMap(Map map) => +/* bool _canFlattenMap(Map map) => map.values.where((dynamic val) => val is Map || val is List).isEmpty && map.toString().length < maxWidth; bool _canFlattenList(List list) => - list.length < 10 && list.toString().length < maxWidth; + list.length < 10 && list.toString().length < maxWidth;*/ void _printMapAsTable( void Function(Object) logPrint, diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client.dart b/packages/stream_chat/lib/src/core/http/stream_http_client.dart index ae0a5f72..43149714 100644 --- a/packages/stream_chat/lib/src/core/http/stream_http_client.dart +++ b/packages/stream_chat/lib/src/core/http/stream_http_client.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:math'; import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; diff --git a/packages/stream_chat/lib/src/ws/websocket.dart b/packages/stream_chat/lib/src/ws/websocket.dart index 0e19f9a6..6ebb7a7b 100644 --- a/packages/stream_chat/lib/src/ws/websocket.dart +++ b/packages/stream_chat/lib/src/ws/websocket.dart @@ -99,7 +99,7 @@ class WebSocket with TimerHelper { _connectionStatusController.add(status); /// The current connection status value - ConnectionStatus get connectionStatus => _connectionStatusController.value!; + ConnectionStatus get connectionStatus => _connectionStatusController.value; /// This notifies of connection status changes Stream get connectionStatusStream => From 9201ebb2f3e01c7d09ddb6135846e09da1b0bccd Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 27 May 2021 20:26:52 +0530 Subject: [PATCH 309/599] improvements Signed-off-by: Sahil Kumar --- .../http/interceptor/auth_interceptor.dart | 11 +++-- .../lib/src/core/http/stream_http_client.dart | 46 +++++++++++++++++-- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart index 19d0293b..a75b2d2c 100644 --- a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart +++ b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart @@ -1,6 +1,7 @@ import 'package:dio/dio.dart'; import 'package:stream_chat/src/core/api/responses.dart'; import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/http/token.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; @@ -10,9 +11,9 @@ import 'package:stream_chat/src/errors/stream_chat_error.dart'; /// class AuthInterceptor extends Interceptor { /// - AuthInterceptor(this._httpClient, this._tokenManager); + AuthInterceptor(this._client, this._tokenManager); - final Dio _httpClient; + final StreamHttpClient _client; /// final TokenManager _tokenManager; @@ -52,12 +53,12 @@ class AuthInterceptor extends Interceptor { if (data != null) error = ErrorResponse.fromJson(data); if (error?.code == ChatErrorCode.tokenExpired.code) { if (_tokenManager.isStatic) return handler.next(err); - _httpClient.lock(); + _client.lock(); await _tokenManager.loadToken(refresh: true); - _httpClient.unlock(); + _client.unlock(); try { final options = err.requestOptions; - final response = await _httpClient.request( + final response = await _client.request( options.path, cancelToken: options.cancelToken, data: options.data, diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client.dart b/packages/stream_chat/lib/src/core/http/stream_http_client.dart index 43149714..e5c1e82b 100644 --- a/packages/stream_chat/lib/src/core/http/stream_http_client.dart +++ b/packages/stream_chat/lib/src/core/http/stream_http_client.dart @@ -40,7 +40,7 @@ class StreamHttpClient { 'Content-Encoding': 'application/gzip', } ..interceptors.addAll([ - if (tokenManager != null) AuthInterceptor(httpClient, tokenManager), + if (tokenManager != null) AuthInterceptor(this, tokenManager), if (connectionIdManager != null) ConnectionIdInterceptor(connectionIdManager), if (logger != null && logger.level != Level.OFF) @@ -74,9 +74,23 @@ class StreamHttpClient { @visibleForTesting final Dio httpClient; - /// Shuts down the [httpClient]. + /// Lock the current [StreamHttpClient] instance. /// - /// If [force] is `false` (the default) the [httpClient] will be kept alive + /// [StreamHttpClient] will enqueue the incoming request tasks instead + /// send them directly when [interceptor.requestOptions] is locked. + void lock() => httpClient.lock(); + + /// Unlock the current [StreamHttpClient] instance. + /// + /// [StreamHttpClient] instance dequeue the request task。 + void unlock() => httpClient.unlock(); + + ///Clear the current [StreamHttpClient] instance waiting queue. + void clear() => httpClient.close(); + + /// Shuts down the [StreamHttpClient]. + /// + /// If [force] is `false` the [StreamHttpClient] will be kept alive /// until all active connections are done. If [force] is `true` any active /// connections will be closed to immediately release all resources. These /// closed connections will receive an error event to indicate that the client @@ -237,4 +251,30 @@ class StreamHttpClient { ); return response; } + + /// Handy method to make generic http request with error parsing. + Future> request( + String path, { + Object? data, + Map? queryParameters, + Options? options, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + CancelToken? cancelToken, + }) async { + try { + final response = await httpClient.request( + path, + data: data, + queryParameters: queryParameters, + options: options, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + cancelToken: cancelToken, + ); + return response; + } on DioError catch (error) { + throw _parseError(error); + } + } } From 927eb3c4518673bbf85d0191383f39b6753f30ca Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 28 May 2021 10:35:13 +0530 Subject: [PATCH 310/599] extract location outside of client options Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/client.dart | 1 + .../lib/src/core/http/stream_http_client.dart | 1 + .../core/http/stream_http_client_options.dart | 30 ------------------- packages/stream_chat/lib/src/location.dart | 29 ++++++++++++++++++ packages/stream_chat/lib/stream_chat.dart | 9 +++--- 5 files changed, 36 insertions(+), 34 deletions(-) create mode 100644 packages/stream_chat/lib/src/location.dart diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index d008e211..cd986b2e 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -7,6 +7,7 @@ import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/api/channel.dart'; +import 'package:stream_chat/src/location.dart'; import 'package:stream_chat/src/ws/connection_status.dart'; import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; import 'package:stream_chat/src/core/api/requests.dart'; diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client.dart b/packages/stream_chat/lib/src/core/http/stream_http_client.dart index e5c1e82b..d814d705 100644 --- a/packages/stream_chat/lib/src/core/http/stream_http_client.dart +++ b/packages/stream_chat/lib/src/core/http/stream_http_client.dart @@ -9,6 +9,7 @@ import 'package:stream_chat/src/core/http/interceptor/logging_interceptor.dart'; import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; import 'package:stream_chat/src/errors/stream_chat_error.dart'; +import 'package:stream_chat/src/location.dart'; import 'package:stream_chat/src/platform_detector/platform_detector.dart'; import 'package:stream_chat/version.dart'; diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart b/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart index 0f06e4d0..faad46a1 100644 --- a/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart +++ b/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart @@ -1,35 +1,5 @@ part of 'stream_http_client.dart'; -/// -enum Location { - /// - usEast, - - /// - euWest, - - /// - mumbai, - - /// - sydney, - - /// - singapore, -} - -/// -extension LocationX on Location { - /// - String get name => { - Location.usEast: 'us-east', - Location.euWest: 'dublin', - Location.mumbai: 'mumbai', - Location.sydney: 'sydney', - Location.singapore: 'singapore', - }[this]!; -} - const _defaultBaseURL = 'https://chat-us-east-1.stream-io-api.com'; /// Client options to modify [StreamHttpClient] diff --git a/packages/stream_chat/lib/src/location.dart b/packages/stream_chat/lib/src/location.dart new file mode 100644 index 00000000..86813a0a --- /dev/null +++ b/packages/stream_chat/lib/src/location.dart @@ -0,0 +1,29 @@ +/// +enum Location { + /// + usEast, + + /// + euWest, + + /// + mumbai, + + /// + sydney, + + /// + singapore, +} + +/// +extension LocationX on Location { + /// + String get name => { + Location.usEast: 'us-east', + Location.euWest: 'dublin', + Location.mumbai: 'mumbai', + Location.sydney: 'sydney', + Location.singapore: 'singapore', + }[this]!; +} diff --git a/packages/stream_chat/lib/stream_chat.dart b/packages/stream_chat/lib/stream_chat.dart index de49dcdd..991e721b 100644 --- a/packages/stream_chat/lib/stream_chat.dart +++ b/packages/stream_chat/lib/stream_chat.dart @@ -11,6 +11,9 @@ export './src/api/channel.dart'; export './src/client.dart'; export './src/core/api/attachment_file_uploader.dart' show AttachmentFileUploader; +export './src/core/api/requests.dart'; +export './src/core/api/requests.dart'; +export './src/core/api/responses.dart'; export './src/core/models/action.dart'; export './src/core/models/attachment.dart'; export './src/core/models/attachment_file.dart'; @@ -33,7 +36,5 @@ export './src/event_type.dart'; export './src/exceptions.dart'; export './src/extensions/rate_limit.dart'; export './src/extensions/string_extension.dart'; -export 'src/core/api/requests.dart'; -export 'src/core/api/requests.dart'; -export 'src/core/api/responses.dart'; -export 'src/ws/connection_status.dart'; +export './src/location.dart'; +export './src/ws/connection_status.dart'; From 644c8b67d8e122d5f49f7370bbe1b991553e67f8 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 28 May 2021 12:02:49 +0530 Subject: [PATCH 311/599] re-subscribe `connectivityStream` if `widget.connectivityStream` changes Signed-off-by: Sahil Kumar --- .../lib/src/stream_chat_core.dart | 70 ++++++++++++------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index e65eb4e2..d3ed4a0b 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -103,7 +103,7 @@ class StreamChatCoreState extends State /// The current user as a stream Stream get userStream => client.state.userStream; - late final StreamSubscription _connectivitySubscription; + StreamSubscription? _connectivitySubscription; var _isInForeground = true; var _isConnectionAvailable = true; @@ -112,23 +112,46 @@ class StreamChatCoreState extends State void initState() { super.initState(); WidgetsBinding.instance?.addObserver(this); - _connectivitySubscription = - (widget.connectivityStream ?? Connectivity().onConnectivityChanged) - .listen((ConnectivityResult result) async { - _isConnectionAvailable = result != ConnectivityResult.none; - if (!_isInForeground) { - return; - } - if (_isConnectionAvailable) { - if (client.wsConnectionStatus == ConnectionStatus.disconnected) { - await client.connect(); + _subscribeToConnectivityChange(widget.connectivityStream); + } + + void _subscribeToConnectivityChange([ + Stream? connectivityStream, + ]) { + if (_connectivitySubscription == null) { + connectivityStream ??= Connectivity().onConnectivityChanged; + _connectivitySubscription = + connectivityStream.distinct().listen((result) { + _isConnectionAvailable = result != ConnectivityResult.none; + if (!_isInForeground) return; + if (_isConnectionAvailable) { + if (client.wsConnectionStatus == ConnectionStatus.disconnected) { + client.connect(); + } + } else { + if (client.wsConnectionStatus == ConnectionStatus.connected) { + client.disconnect(); + } } - } else { - if (client.wsConnectionStatus == ConnectionStatus.connected) { - await client.disconnect(); - } - } - }); + }); + } + } + + void _unsubscribeFromConnectivityChange() { + if (_connectivitySubscription != null) { + _connectivitySubscription?.cancel(); + _connectivitySubscription = null; + } + } + + @override + void didUpdateWidget(StreamChatCore oldWidget) { + super.didUpdateWidget(oldWidget); + final connectivityStream = widget.connectivityStream; + if (connectivityStream != oldWidget.connectivityStream) { + _unsubscribeFromConnectivityChange(); + _subscribeToConnectivityChange(connectivityStream); + } } StreamSubscription? _eventSubscription; @@ -137,10 +160,10 @@ class StreamChatCoreState extends State void didChangeAppLifecycleState(AppLifecycleState state) { _isInForeground = state == AppLifecycleState.resumed; if (user != null) { - if (!_isInForeground) { - _onBackground(); - } else { + if (_isInForeground) { _onForeground(); + } else { + _onBackground(); } } } @@ -162,9 +185,8 @@ class StreamChatCoreState extends State } return; } - _eventSubscription = client.on().listen( - widget.onBackgroundEventReceived, - ); + + _eventSubscription = client.on().listen(widget.onBackgroundEventReceived); void onTimerComplete() { _eventSubscription?.cancel(); @@ -178,9 +200,9 @@ class StreamChatCoreState extends State @override void dispose() { WidgetsBinding.instance?.removeObserver(this); + _unsubscribeFromConnectivityChange(); _eventSubscription?.cancel(); _disconnectTimer?.cancel(); - _connectivitySubscription.cancel(); super.dispose(); } } From 765ca6034396cdf8927602e1540c4a38a27953aa Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 31 May 2021 11:16:40 +0530 Subject: [PATCH 312/599] feat: Added callbacks for default slidable options --- .../lib/src/channel_list_view.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 766fe4ba..1d0a4966 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -78,6 +78,8 @@ class ChannelListView extends StatefulWidget { this.emptyBuilder, this.loadingBuilder, this.listBuilder, + this.onMoreDetailsPressed, + this.onDeletePressed, }) : super(key: key); /// If true a default swipe to action behaviour will be added to this widget @@ -158,6 +160,12 @@ class ChannelListView extends StatefulWidget { /// The builder used when the channel list is empty. final WidgetBuilder? emptyBuilder; + /// Callback used when the more details slidable option is pressed + final VoidCallback? onMoreDetailsPressed; + + /// Callback used when the delete slidable option is pressed + final VoidCallback? onDeletePressed; + @override _ChannelListViewState createState() => _ChannelListViewState(); } @@ -469,7 +477,7 @@ class _ChannelListViewState extends State { IconSlideAction( color: backgroundColor, icon: Icons.more_horiz, - onTap: () { + onTap: widget.onMoreDetailsPressed ?? () { showModalBottomSheet( clipBehavior: Clip.hardEdge, shape: const RoundedRectangleBorder( @@ -502,7 +510,7 @@ class _ChannelListViewState extends State { iconWidget: StreamSvgIcon.delete( color: chatThemeData.colorTheme.accentRed, ), - onTap: () async { + onTap: widget.onDeletePressed ?? () async { final res = await showConfirmationDialog( context, title: 'Delete Conversation', From ceaf30e5454a8e12337e3eeca09471037a34bbb8 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 31 May 2021 11:30:51 +0530 Subject: [PATCH 313/599] feat: Added list of actions to channel_list_view.dart --- .../lib/src/channel_list_view.dart | 109 ++++++++++-------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 1d0a4966..8d7c833a 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -80,6 +80,7 @@ class ChannelListView extends StatefulWidget { this.listBuilder, this.onMoreDetailsPressed, this.onDeletePressed, + this.swipeActions, }) : super(key: key); /// If true a default swipe to action behaviour will be added to this widget @@ -166,6 +167,9 @@ class ChannelListView extends StatefulWidget { /// Callback used when the delete slidable option is pressed final VoidCallback? onDeletePressed; + /// Actions shown when list tile is swiped + final List? swipeActions; + @override _ChannelListViewState createState() => _ChannelListViewState(); } @@ -473,61 +477,64 @@ class _ChannelListViewState extends State { enabled: widget.swipeToAction, actionPane: const SlidableBehindActionPane(), actionExtentRatio: 0.12, - secondaryActions: [ - IconSlideAction( - color: backgroundColor, - icon: Icons.more_horiz, - onTap: widget.onMoreDetailsPressed ?? () { - showModalBottomSheet( - clipBehavior: Clip.hardEdge, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(32), - topRight: Radius.circular(32), - ), - ), - context: context, - builder: (context) => StreamChannel( - channel: channel, - child: ChannelBottomSheet( - onViewInfoTap: () { - widget.onViewInfoTap?.call(channel); + secondaryActions: widget.swipeActions ?? + [ + IconSlideAction( + color: backgroundColor, + icon: Icons.more_horiz, + onTap: widget.onMoreDetailsPressed ?? + () { + showModalBottomSheet( + clipBehavior: Clip.hardEdge, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(32), + topRight: Radius.circular(32), + ), + ), + context: context, + builder: (context) => StreamChannel( + channel: channel, + child: ChannelBottomSheet( + onViewInfoTap: () { + widget.onViewInfoTap?.call(channel); + }, + ), + ), + ); }, - ), - ), - ); - }, - ), - if ([ - 'admin', - 'owner', - ].contains(channel.state!.members - .firstWhereOrNull( - (m) => m.userId == channel.client.state.user?.id) - ?.role)) - IconSlideAction( - color: backgroundColor, - iconWidget: StreamSvgIcon.delete( - color: chatThemeData.colorTheme.accentRed, ), - onTap: widget.onDeletePressed ?? () async { - final res = await showConfirmationDialog( - context, - title: 'Delete Conversation', - okText: 'DELETE', - question: - 'Are you sure you want to delete this conversation?', - cancelText: 'CANCEL', - icon: StreamSvgIcon.delete( + if ([ + 'admin', + 'owner', + ].contains(channel.state!.members + .firstWhereOrNull( + (m) => m.userId == channel.client.state.user?.id) + ?.role)) + IconSlideAction( + color: backgroundColor, + iconWidget: StreamSvgIcon.delete( color: chatThemeData.colorTheme.accentRed, ), - ); - if (res == true) { - await channel.delete(); - } - }, - ), - ], + onTap: widget.onDeletePressed ?? + () async { + final res = await showConfirmationDialog( + context, + title: 'Delete Conversation', + okText: 'DELETE', + question: + 'Are you sure you want to delete this conversation?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.delete( + color: chatThemeData.colorTheme.accentRed, + ), + ); + if (res == true) { + await channel.delete(); + } + }, + ), + ], child: Container( color: chatThemeData.colorTheme.whiteSnow, child: widget.channelPreviewBuilder?.call(context, channel) ?? From ba0b525255a3b5341311a8a58da344768a3628c9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 31 May 2021 11:37:48 +0530 Subject: [PATCH 314/599] removed swipe actions list --- .../lib/src/channel_list_view.dart | 117 +++++++++--------- 1 file changed, 56 insertions(+), 61 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 8d7c833a..46e2ebf9 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -80,7 +80,6 @@ class ChannelListView extends StatefulWidget { this.listBuilder, this.onMoreDetailsPressed, this.onDeletePressed, - this.swipeActions, }) : super(key: key); /// If true a default swipe to action behaviour will be added to this widget @@ -167,9 +166,6 @@ class ChannelListView extends StatefulWidget { /// Callback used when the delete slidable option is pressed final VoidCallback? onDeletePressed; - /// Actions shown when list tile is swiped - final List? swipeActions; - @override _ChannelListViewState createState() => _ChannelListViewState(); } @@ -477,64 +473,63 @@ class _ChannelListViewState extends State { enabled: widget.swipeToAction, actionPane: const SlidableBehindActionPane(), actionExtentRatio: 0.12, - secondaryActions: widget.swipeActions ?? - [ - IconSlideAction( - color: backgroundColor, - icon: Icons.more_horiz, - onTap: widget.onMoreDetailsPressed ?? - () { - showModalBottomSheet( - clipBehavior: Clip.hardEdge, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(32), - topRight: Radius.circular(32), - ), - ), - context: context, - builder: (context) => StreamChannel( - channel: channel, - child: ChannelBottomSheet( - onViewInfoTap: () { - widget.onViewInfoTap?.call(channel); - }, - ), - ), - ); - }, + secondaryActions: [ + IconSlideAction( + color: backgroundColor, + icon: Icons.more_horiz, + onTap: widget.onMoreDetailsPressed ?? + () { + showModalBottomSheet( + clipBehavior: Clip.hardEdge, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(32), + topRight: Radius.circular(32), + ), + ), + context: context, + builder: (context) => StreamChannel( + channel: channel, + child: ChannelBottomSheet( + onViewInfoTap: () { + widget.onViewInfoTap?.call(channel); + }, + ), + ), + ); + }, + ), + if ([ + 'admin', + 'owner', + ].contains(channel.state!.members + .firstWhereOrNull( + (m) => m.userId == channel.client.state.user?.id) + ?.role)) + IconSlideAction( + color: backgroundColor, + iconWidget: StreamSvgIcon.delete( + color: chatThemeData.colorTheme.accentRed, ), - if ([ - 'admin', - 'owner', - ].contains(channel.state!.members - .firstWhereOrNull( - (m) => m.userId == channel.client.state.user?.id) - ?.role)) - IconSlideAction( - color: backgroundColor, - iconWidget: StreamSvgIcon.delete( - color: chatThemeData.colorTheme.accentRed, - ), - onTap: widget.onDeletePressed ?? - () async { - final res = await showConfirmationDialog( - context, - title: 'Delete Conversation', - okText: 'DELETE', - question: - 'Are you sure you want to delete this conversation?', - cancelText: 'CANCEL', - icon: StreamSvgIcon.delete( - color: chatThemeData.colorTheme.accentRed, - ), - ); - if (res == true) { - await channel.delete(); - } - }, - ), - ], + onTap: widget.onDeletePressed ?? + () async { + final res = await showConfirmationDialog( + context, + title: 'Delete Conversation', + okText: 'DELETE', + question: + 'Are you sure you want to delete this conversation?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.delete( + color: chatThemeData.colorTheme.accentRed, + ), + ); + if (res == true) { + await channel.delete(); + } + }, + ), + ], child: Container( color: chatThemeData.colorTheme.whiteSnow, child: widget.channelPreviewBuilder?.call(context, channel) ?? From f75e50d651a6573e1105cd442c8e4118af99a96b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 31 May 2021 14:21:17 +0530 Subject: [PATCH 315/599] added swipe actions list --- .../lib/src/channel_list_view.dart | 135 ++++++++++-------- 1 file changed, 79 insertions(+), 56 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 46e2ebf9..1478c745 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -80,6 +80,7 @@ class ChannelListView extends StatefulWidget { this.listBuilder, this.onMoreDetailsPressed, this.onDeletePressed, + this.swipeActions, }) : super(key: key); /// If true a default swipe to action behaviour will be added to this widget @@ -166,6 +167,9 @@ class ChannelListView extends StatefulWidget { /// Callback used when the delete slidable option is pressed final VoidCallback? onDeletePressed; + /// List of actions for slidable + final List? swipeActions; + @override _ChannelListViewState createState() => _ChannelListViewState(); } @@ -473,63 +477,70 @@ class _ChannelListViewState extends State { enabled: widget.swipeToAction, actionPane: const SlidableBehindActionPane(), actionExtentRatio: 0.12, - secondaryActions: [ - IconSlideAction( - color: backgroundColor, - icon: Icons.more_horiz, - onTap: widget.onMoreDetailsPressed ?? - () { - showModalBottomSheet( - clipBehavior: Clip.hardEdge, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(32), - topRight: Radius.circular(32), - ), - ), - context: context, - builder: (context) => StreamChannel( - channel: channel, - child: ChannelBottomSheet( - onViewInfoTap: () { - widget.onViewInfoTap?.call(channel); - }, - ), - ), - ); - }, - ), - if ([ - 'admin', - 'owner', - ].contains(channel.state!.members - .firstWhereOrNull( - (m) => m.userId == channel.client.state.user?.id) - ?.role)) - IconSlideAction( - color: backgroundColor, - iconWidget: StreamSvgIcon.delete( - color: chatThemeData.colorTheme.accentRed, + secondaryActions: widget.swipeActions + ?.map((e) => IconSlideAction( + color: e.color, + iconWidget: e.iconWidget, + onTap: e.onTap, + )) + .toList() ?? + [ + IconSlideAction( + color: backgroundColor, + icon: Icons.more_horiz, + onTap: widget.onMoreDetailsPressed ?? + () { + showModalBottomSheet( + clipBehavior: Clip.hardEdge, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(32), + topRight: Radius.circular(32), + ), + ), + context: context, + builder: (context) => StreamChannel( + channel: channel, + child: ChannelBottomSheet( + onViewInfoTap: () { + widget.onViewInfoTap?.call(channel); + }, + ), + ), + ); + }, ), - onTap: widget.onDeletePressed ?? - () async { - final res = await showConfirmationDialog( - context, - title: 'Delete Conversation', - okText: 'DELETE', - question: - 'Are you sure you want to delete this conversation?', - cancelText: 'CANCEL', - icon: StreamSvgIcon.delete( - color: chatThemeData.colorTheme.accentRed, - ), - ); - if (res == true) { - await channel.delete(); - } - }, - ), - ], + if ([ + 'admin', + 'owner', + ].contains(channel.state!.members + .firstWhereOrNull( + (m) => m.userId == channel.client.state.user?.id) + ?.role)) + IconSlideAction( + color: backgroundColor, + iconWidget: StreamSvgIcon.delete( + color: chatThemeData.colorTheme.accentRed, + ), + onTap: widget.onDeletePressed ?? + () async { + final res = await showConfirmationDialog( + context, + title: 'Delete Conversation', + okText: 'DELETE', + question: + 'Are you sure you want to delete this conversation?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.delete( + color: chatThemeData.colorTheme.accentRed, + ), + ); + if (res == true) { + await channel.delete(); + } + }, + ), + ], child: Container( color: chatThemeData.colorTheme.whiteSnow, child: widget.channelPreviewBuilder?.call(context, channel) ?? @@ -650,3 +661,15 @@ class _ChannelListViewState extends State { ); } } + +class SwipeAction { + Color? color; + Widget iconWidget; + VoidCallback? onTap; + + SwipeAction({ + this.color, + required this.iconWidget, + this.onTap, + }); +} From 56e7d45cb8e4f590316fb8f23f0274cca92913cc Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 31 May 2021 14:28:49 +0530 Subject: [PATCH 316/599] analysis fixes --- .../lib/src/channel_list_view.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 1478c745..049a51af 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -662,14 +662,21 @@ class _ChannelListViewState extends State { } } +/// Class for slidable action class SwipeAction { - Color? color; - Widget iconWidget; - VoidCallback? onTap; - + /// Constructor for creating [SwipeAction] SwipeAction({ this.color, required this.iconWidget, this.onTap, }); + + /// Background color of action + Color? color; + + /// Widget to display as icon + Widget iconWidget; + + /// Callback when icon is tapped + VoidCallback? onTap; } From 5ed6932ef20df419a3593322ca55e928eb288ecd Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 31 May 2021 14:29:11 +0530 Subject: [PATCH 317/599] analysis fixes --- packages/stream_chat_flutter/lib/src/channel_list_view.dart | 1 + 1 file changed, 1 insertion(+) 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 049a51af..8a5bc78d 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -529,6 +529,7 @@ class _ChannelListViewState extends State { title: 'Delete Conversation', okText: 'DELETE', question: + // ignore: lines_longer_than_80_chars 'Are you sure you want to delete this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.delete( From 23d992740f23e54f7144c9fe8d4a5e1c9b2a5581 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 1 Jun 2021 17:46:12 +0530 Subject: [PATCH 318/599] Changed callback type --- .../lib/src/channel_list_view.dart | 91 +++++++++++-------- 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 8a5bc78d..911e0a41 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -12,6 +12,9 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Callback called when tapping on a channel typedef ChannelTapCallback = void Function(Channel, Widget?); +/// Callback called when tapping on a channel +typedef ChannelInfoCallback = void Function(Channel); + /// Builder used to create a custom [ChannelPreview] from a [Channel] typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel); @@ -162,10 +165,10 @@ class ChannelListView extends StatefulWidget { final WidgetBuilder? emptyBuilder; /// Callback used when the more details slidable option is pressed - final VoidCallback? onMoreDetailsPressed; + final ChannelInfoCallback? onMoreDetailsPressed; /// Callback used when the delete slidable option is pressed - final VoidCallback? onDeletePressed; + final ChannelInfoCallback? onDeletePressed; /// List of actions for slidable final List? swipeActions; @@ -481,34 +484,39 @@ class _ChannelListViewState extends State { ?.map((e) => IconSlideAction( color: e.color, iconWidget: e.iconWidget, - onTap: e.onTap, + onTap: () { + e.onTap?.call(channel); + }, )) .toList() ?? [ IconSlideAction( color: backgroundColor, icon: Icons.more_horiz, - onTap: widget.onMoreDetailsPressed ?? - () { - showModalBottomSheet( - clipBehavior: Clip.hardEdge, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(32), - topRight: Radius.circular(32), + onTap: widget.onMoreDetailsPressed != null + ? () { + widget.onMoreDetailsPressed!(channel); + } + : () { + showModalBottomSheet( + clipBehavior: Clip.hardEdge, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(32), + topRight: Radius.circular(32), + ), ), - ), - context: context, - builder: (context) => StreamChannel( - channel: channel, - child: ChannelBottomSheet( - onViewInfoTap: () { - widget.onViewInfoTap?.call(channel); - }, + context: context, + builder: (context) => StreamChannel( + channel: channel, + child: ChannelBottomSheet( + onViewInfoTap: () { + widget.onViewInfoTap?.call(channel); + }, + ), ), - ), - ); - }, + ); + }, ), if ([ 'admin', @@ -522,24 +530,27 @@ class _ChannelListViewState extends State { iconWidget: StreamSvgIcon.delete( color: chatThemeData.colorTheme.accentRed, ), - onTap: widget.onDeletePressed ?? - () async { - final res = await showConfirmationDialog( - context, - title: 'Delete Conversation', - okText: 'DELETE', - question: - // ignore: lines_longer_than_80_chars - 'Are you sure you want to delete this conversation?', - cancelText: 'CANCEL', - icon: StreamSvgIcon.delete( - color: chatThemeData.colorTheme.accentRed, - ), - ); - if (res == true) { - await channel.delete(); + onTap: widget.onDeletePressed != null + ? () { + widget.onDeletePressed!(channel); } - }, + : () async { + final res = await showConfirmationDialog( + context, + title: 'Delete Conversation', + okText: 'DELETE', + question: + // ignore: lines_longer_than_80_chars + 'Are you sure you want to delete this conversation?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.delete( + color: chatThemeData.colorTheme.accentRed, + ), + ); + if (res == true) { + await channel.delete(); + } + }, ), ], child: Container( @@ -679,5 +690,5 @@ class SwipeAction { Widget iconWidget; /// Callback when icon is tapped - VoidCallback? onTap; + ChannelInfoCallback? onTap; } From 06bf2cdfe5f69016b7f955dada999421536b46de Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 1 Jun 2021 15:42:45 +0200 Subject: [PATCH 319/599] fix rebuild message list view --- packages/stream_chat/lib/src/api/channel.dart | 15 ++++++++++----- packages/stream_chat/lib/src/client.dart | 2 +- .../stream_chat/lib/src/models/member.dart | 18 +++++++++++++++++- packages/stream_chat/lib/src/models/user.dart | 16 +++++++++++++++- .../stream_chat_flutter/analysis_options.yaml | 4 ++-- .../lib/src/channel_info.dart | 1 - .../lib/src/connection_status_builder.dart | 2 +- .../lib/src/message_list_view.dart | 6 ++++-- .../lib/src/typing_indicator.dart | 17 +++++++++-------- .../lib/src/message_list_core.dart | 10 ++++++---- 10 files changed, 65 insertions(+), 26 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 69d967fe..4a5476ee 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -70,8 +70,11 @@ class Channel { true; /// Returns true if the channel is muted as a stream - Stream? get isMutedStream => _client.state.userStream.map((event) => - event!.channelMutes.any((element) => element.channel.cid == cid) == true); + Stream? get isMutedStream => _client.state.userStream + .map((event) => + event!.channelMutes.any((element) => element.channel.cid == cid) == + true) + .distinct(); /// True if the channel is a group bool get isGroup => memberCount != 2; @@ -1604,7 +1607,7 @@ class ChannelClientState { /// Channel message list as a stream Stream?> get messagesStream => channelStateStream .map((cs) => cs.messages) - .distinct((prev, next) => const ListEquality().equals(prev, next)); + .distinct(const ListEquality().equals); /// Channel pinned message list List? get pinnedMessages => _channelState.pinnedMessages.toList(); @@ -1634,7 +1637,7 @@ class ChannelClientState { _channel.client.state.usersStream, (members, users) => members!.map((e) => e!.copyWith(user: users[e.user!.id])).toList(), - ); + ).distinct(const ListEquality().equals); /// Channel watcher count int? get watcherCount => _channelState.watcherCount; @@ -1804,7 +1807,9 @@ class ChannelClientState { List get typingEvents => _typingEventsController.value; /// Channel related typing users stream - Stream> get typingEventsStream => _typingEventsController.stream; + Stream> get typingEventsStream => + _typingEventsController.stream.distinct(const ListEquality().equals); + final BehaviorSubject> _typingEventsController = BehaviorSubject.seeded([]); diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 05f2559b..52374239 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -219,7 +219,7 @@ class StreamChatClient { /// This notifies the connection status of the websocket connection. /// Listen to this to get notified when the websocket tries to reconnect. Stream get wsConnectionStatusStream => - _wsConnectionStatusController.stream; + _wsConnectionStatusController.stream.distinct(); /// The current user token String? token; diff --git a/packages/stream_chat/lib/src/models/member.dart b/packages/stream_chat/lib/src/models/member.dart index 49df170c..aa576cd2 100644 --- a/packages/stream_chat/lib/src/models/member.dart +++ b/packages/stream_chat/lib/src/models/member.dart @@ -1,3 +1,4 @@ +import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/models/user.dart'; @@ -6,7 +7,7 @@ part 'member.g.dart'; /// The class that contains the information about the user membership /// in a channel @JsonSerializable() -class Member { +class Member extends Equatable { /// Constructor used for json serialization Member({ this.user, @@ -98,4 +99,19 @@ class Member { /// Serialize to json Map toJson() => _$MemberToJson(this); + + @override + List get props => [ + user, + inviteAcceptedAt, + inviteRejectedAt, + invited, + role, + userId, + isModerator, + banned, + shadowBanned, + createdAt, + updatedAt, + ]; } diff --git a/packages/stream_chat/lib/src/models/user.dart b/packages/stream_chat/lib/src/models/user.dart index 9335f8c8..08f61b91 100644 --- a/packages/stream_chat/lib/src/models/user.dart +++ b/packages/stream_chat/lib/src/models/user.dart @@ -1,3 +1,4 @@ +import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/models/serialization.dart'; @@ -5,7 +6,7 @@ part 'user.g.dart'; /// The class that defines the user model @JsonSerializable() -class User { +class User extends Equatable { /// Constructor used for json serialization User({ required this.id, @@ -125,4 +126,17 @@ class User { banned: banned ?? this.banned, teams: teams ?? this.teams, ); + + @override + List get props => [ + id, + role, + teams, + createdAt, + updatedAt, + lastActive, + online, + banned, + extraData, + ]; } diff --git a/packages/stream_chat_flutter/analysis_options.yaml b/packages/stream_chat_flutter/analysis_options.yaml index f0a87ea5..a37a0fb7 100644 --- a/packages/stream_chat_flutter/analysis_options.yaml +++ b/packages/stream_chat_flutter/analysis_options.yaml @@ -3,10 +3,10 @@ analyzer: - extension-methods exclude: - lib/**/*.g.dart - - example/** +# - example/** - lib/src/emoji - lib/**/*.freezed.dart - - test/** +# - test/** linter: rules: diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index f59ac2e0..e2067f1b 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -27,7 +27,6 @@ class ChannelInfo extends StatelessWidget { final client = StreamChat.of(context).client; return StreamBuilder>( stream: channel.state?.membersStream, - initialData: channel.state?.members, builder: (context, snapshot) => ConnectionStatusBuilder( statusBuilder: (context, status) { switch (status) { diff --git a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart index 7e4e7f8e..cfc1b937 100644 --- a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart +++ b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -39,7 +40,6 @@ class ConnectionStatusBuilder extends StatelessWidget { final stream = connectionStatusStream ?? StreamChat.of(context).client.wsConnectionStatusStream; return StreamBuilder( - initialData: initialStatus, stream: stream, builder: (context, snapshot) { if (snapshot.hasError) { 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 a17dccf0..c13a87a7 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -690,7 +690,7 @@ class _MessageListViewState extends State { ? streamChannel.queryTopMessages : streamChannel.queryBottomMessages; return StreamBuilder( - key: const Key('LOADING-INDICATOR'), + key: Key('LOADING-INDICATOR $direction'), stream: stream, initialData: false, builder: (context, snapshot) { @@ -789,7 +789,9 @@ class _MessageListViewState extends State { } } if (mounted) { - setState(() => _showScrollToBottom = !isVisible); + if (_showScrollToBottom == isVisible) { + setState(() => _showScrollToBottom = !isVisible); + } } }, child: messageWidget, diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart index e9377674..010b2c07 100644 --- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart @@ -33,8 +33,15 @@ class TypingIndicator extends StatelessWidget { Widget build(BuildContext context) { final channelState = channel?.state ?? StreamChannel.of(context).channel.state!; + + final altWidget = Align( + key: const Key('alternative'), + alignment: alignment, + child: Container( + child: alternativeWidget ?? const Offstage(), + ), + ); return StreamBuilder>( - initialData: channelState.typingEvents, stream: channelState.typingEventsStream, builder: (context, snapshot) => AnimatedSwitcher( duration: const Duration(milliseconds: 300), @@ -62,13 +69,7 @@ class TypingIndicator extends StatelessWidget { ), ), ) - : Align( - key: const Key('alternative'), - alignment: alignment, - child: Container( - child: alternativeWidget ?? const Offstage(), - ), - ), + : altWidget, ), ); } diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index cf11ef5f..9b0d3d38 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -135,10 +135,12 @@ class MessageListCoreState extends State { } return StreamBuilder?>( - stream: messagesStream?.map((messages) => - messages?.where(widget.messageFilter ?? defaultFilter).toList( - growable: false, - )), + stream: messagesStream?.map( + (messages) => + messages?.where(widget.messageFilter ?? defaultFilter).toList( + growable: false, + ), + ), builder: (context, snapshot) { if (snapshot.hasError) { return widget.errorWidgetBuilder(context, snapshot.error!); From c10166dbffce35ce36bca5de554b9ac0dc3cc810 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 1 Jun 2021 17:24:26 +0200 Subject: [PATCH 320/599] experiment with streambuilder --- packages/stream_chat/lib/src/client.dart | 2 +- .../lib/src/channel_image.dart | 10 +- .../lib/src/channel_info.dart | 7 +- .../lib/src/channel_name.dart | 9 +- .../lib/src/connection_status_builder.dart | 28 +- .../lib/src/message_list_view.dart | 29 +- .../lib/src/message_widget.dart | 337 +++++++++--------- .../lib/src/typing_indicator.dart | 8 +- .../lib/src/unread_indicator.dart | 8 +- .../stream_chat_flutter/lib/src/utils.dart | 2 + .../lib/src/better_stream_builder.dart | 100 ++++++ .../lib/src/message_list_core.dart | 35 +- .../lib/stream_chat_flutter_core.dart | 1 + 13 files changed, 342 insertions(+), 234 deletions(-) create mode 100644 packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 52374239..0b759a53 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -213,7 +213,7 @@ class StreamChatClient { _wsConnectionStatusController.add(status); /// The current status value of the websocket connection - ConnectionStatus? get wsConnectionStatus => + ConnectionStatus get wsConnectionStatus => _wsConnectionStatusController.value; /// This notifies the connection status of the websocket connection. diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 19495809..aefdb44e 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -83,14 +83,14 @@ class ChannelImage extends StatelessWidget { Widget build(BuildContext context) { final streamChat = StreamChat.of(context); final channel = this.channel ?? StreamChannel.of(context).channel; - return StreamBuilder>( + return BetterStreamBuilder>( stream: channel.extraDataStream, initialData: channel.extraData, builder: (context, snapshot) { String? image; final chatThemeData = StreamChatTheme.of(context); - if (snapshot.data!.containsKey('image') == true) { - image = snapshot.data!['image']; + if (snapshot.containsKey('image') == true) { + image = snapshot['image']; } else if (channel.state?.members.length == 2) { final otherMember = channel.state?.members .firstWhere((member) => member.user?.id != streamChat.user?.id); @@ -153,9 +153,7 @@ class ChannelImage extends StatelessWidget { imageUrl: image, errorWidget: (_, __, ___) => Center( child: Text( - snapshot.data?.containsKey('name') ?? false - ? snapshot.data!['name'][0] - : '', + snapshot.containsKey('name') ? snapshot['name'][0] : '', style: TextStyle( color: chatThemeData.colorTheme.white, fontWeight: FontWeight.bold, diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index e2067f1b..91e7aceb 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -25,13 +25,14 @@ class ChannelInfo extends StatelessWidget { @override Widget build(BuildContext context) { final client = StreamChat.of(context).client; - return StreamBuilder>( - stream: channel.state?.membersStream, + return BetterStreamBuilder>( + stream: channel.state!.membersStream, + initialData: channel.state!.members, builder: (context, snapshot) => ConnectionStatusBuilder( statusBuilder: (context, status) { switch (status) { case ConnectionStatus.connected: - return _buildConnectedTitleState(context, snapshot.data); + return _buildConnectedTitleState(context, snapshot); case ConnectionStatus.connecting: return _buildConnectingTitleState(context); case ConnectionStatus.disconnected: diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart index 2cd804c9..4d560bed 100644 --- a/packages/stream_chat_flutter/lib/src/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel_name.dart @@ -26,11 +26,14 @@ class ChannelName extends StatelessWidget { final client = StreamChat.of(context); final channel = StreamChannel.of(context).channel; - return StreamBuilder>( + return BetterStreamBuilder>( stream: channel.extraDataStream, initialData: channel.extraData, - builder: (context, snapshot) => - _buildName(snapshot.data!, channel.state?.members, client), + builder: (context, snapshot) => _buildName( + snapshot, + channel.state?.members, + client, + ), ); } diff --git a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart index cfc1b937..9137265f 100644 --- a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart +++ b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart @@ -13,14 +13,14 @@ class ConnectionStatusBuilder extends StatelessWidget { const ConnectionStatusBuilder({ Key? key, required this.statusBuilder, - this.initialStatus = ConnectionStatus.disconnected, + this.initialStatus, this.connectionStatusStream, this.errorBuilder, this.loadingBuilder, }) : super(key: key); /// The connection status that will be used to create the initial snapshot. - final ConnectionStatus initialStatus; + final ConnectionStatus? initialStatus; /// The asynchronous computation to which this builder is currently connected. final Stream? connectionStatusStream; @@ -37,23 +37,19 @@ class ConnectionStatusBuilder extends StatelessWidget { @override Widget build(BuildContext context) { - final stream = connectionStatusStream ?? - StreamChat.of(context).client.wsConnectionStatusStream; - return StreamBuilder( + final client = StreamChat.of(context).client; + final stream = connectionStatusStream ?? client.wsConnectionStatusStream; + return BetterStreamBuilder( + initialData: initialStatus ?? client.wsConnectionStatus, stream: stream, - builder: (context, snapshot) { - if (snapshot.hasError) { - if (errorBuilder != null) { - return errorBuilder!(context, snapshot.error); - } - return const Offstage(); + loadingBuilder: loadingBuilder, + errorBuilder: (context, error) { + if (errorBuilder != null) { + return errorBuilder!(context, error); } - if (!snapshot.hasData) { - if (loadingBuilder != null) return loadingBuilder!(context); - return const Offstage(); - } - return statusBuilder(context, snapshot.data!); + return const Offstage(); }, + builder: statusBuilder, ); } } 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 c13a87a7..25cf9941 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -606,8 +606,8 @@ class _MessageListViewState extends State { Widget _buildScrollToBottom() => StreamBuilder>( stream: Rx.combineLatest2( - streamChannel!.channel.state!.isUpToDateStream, - streamChannel!.channel.state!.unreadCountStream, + streamChannel!.channel.state!.isUpToDateStream.distinct(), + streamChannel!.channel.state!.unreadCountStream.distinct(), (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), ), builder: (_, snapshot) { @@ -689,23 +689,18 @@ class _MessageListViewState extends State { final stream = direction == QueryDirection.top ? streamChannel.queryTopMessages : streamChannel.queryBottomMessages; - return StreamBuilder( + return BetterStreamBuilder( key: Key('LOADING-INDICATOR $direction'), stream: stream, initialData: false, + errorBuilder: (context, error) => Container( + color: StreamChatTheme.of(context).colorTheme.accentRed.withOpacity(.2), + child: const Center( + child: Text('Error loading messages'), + ), + ), builder: (context, snapshot) { - if (snapshot.hasError) { - return Container( - color: StreamChatTheme.of(context) - .colorTheme - .accentRed - .withOpacity(.2), - child: const Center( - child: Text('Error loading messages'), - ), - ); - } - if (!snapshot.data!) { + if (!snapshot) { if (!_isThreadConversation && direction == QueryDirection.top) { return const SizedBox( height: 52, @@ -1163,14 +1158,14 @@ class _MessageListViewState extends State { Navigator.push( context, MaterialPageRoute( - builder: (_) => StreamBuilder( + builder: (_) => BetterStreamBuilder( stream: streamChannel!.channel.state!.messagesStream.map( (messages) => messages!.firstWhere((m) => m.id == message.id)), initialData: message, builder: (_, snapshot) => StreamChannel( channel: streamChannel!.channel, - child: widget.threadBuilder!(context, snapshot.data), + child: widget.threadBuilder!(context, snapshot), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index b18a0532..97e061ae 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -449,182 +449,189 @@ class _MessageWidgetState extends State final leftPadding = widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; - return Portal( - child: GestureDetector( - onTap: () { - widget.onMessageTap!(widget.message); - }, - onLongPress: widget.message.isDeleted && !isFailedState - ? null - : () => onLongPress(context), - child: Padding( - padding: widget.padding ?? const EdgeInsets.all(8), - child: FractionallySizedBox( - alignment: - widget.reverse ? Alignment.centerRight : Alignment.centerLeft, - widthFactor: 0.78, - child: Column( - crossAxisAlignment: widget.reverse - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Stack( - clipBehavior: Clip.none, - alignment: widget.reverse - ? AlignmentDirectional.bottomEnd - : AlignmentDirectional.bottomStart, - children: [ - Column( - crossAxisAlignment: widget.reverse - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.showUserAvatar == DisplayWidget.show && - widget.message.user != null) ...[ - _buildUserAvatar(), - const SizedBox(width: 4), - ], - if (widget.showUserAvatar == DisplayWidget.hide) - SizedBox(width: avatarWidth + 4), - Flexible( - child: PortalEntry( - portal: Container( - transform: Matrix4.translationValues( - widget.reverse ? 12 : -12, 0, 0), - constraints: - const BoxConstraints(maxWidth: 22 * 6.0), - child: _buildReactionIndicator(context), - ), - portalAnchor: - Alignment(widget.reverse ? 1 : -1, -1), - childAnchor: - Alignment(widget.reverse ? -1 : 1, -1), - child: Stack( - clipBehavior: Clip.none, - children: [ - Padding( - padding: widget.showReactions - ? EdgeInsets.only( - top: widget.message.reactionCounts - ?.isNotEmpty == - true - ? 18 - : 0, - ) - : EdgeInsets.zero, - child: (widget.message.isDeleted && - !isFailedState) - ? Container( - // ignore: lines_longer_than_80_chars - margin: EdgeInsets.symmetric( - horizontal: + return Material( + type: MaterialType.transparency, + child: Portal( + child: InkWell( + onTap: () { + widget.onMessageTap!(widget.message); + }, + onLongPress: widget.message.isDeleted && !isFailedState + ? null + : () => onLongPress(context), + child: Padding( + padding: widget.padding ?? const EdgeInsets.all(8), + child: FractionallySizedBox( + alignment: + widget.reverse ? Alignment.centerRight : Alignment.centerLeft, + widthFactor: 0.78, + child: Column( + crossAxisAlignment: widget.reverse + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + clipBehavior: Clip.none, + alignment: widget.reverse + ? AlignmentDirectional.bottomEnd + : AlignmentDirectional.bottomStart, + children: [ + Column( + crossAxisAlignment: widget.reverse + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.showUserAvatar == DisplayWidget.show && + widget.message.user != null) ...[ + _buildUserAvatar(), + const SizedBox(width: 4), + ], + if (widget.showUserAvatar == DisplayWidget.hide) + SizedBox(width: avatarWidth + 4), + Flexible( + child: PortalEntry( + portal: Container( + transform: Matrix4.translationValues( + widget.reverse ? 12 : -12, 0, 0), + constraints: const BoxConstraints( + maxWidth: 22 * 6.0), + child: _buildReactionIndicator(context), + ), + portalAnchor: + Alignment(widget.reverse ? 1 : -1, -1), + childAnchor: + Alignment(widget.reverse ? -1 : 1, -1), + child: Stack( + clipBehavior: Clip.none, + children: [ + Padding( + padding: widget.showReactions + ? EdgeInsets.only( + top: widget + .message + .reactionCounts + ?.isNotEmpty == + true + ? 18 + : 0, + ) + : EdgeInsets.zero, + child: (widget.message.isDeleted && + !isFailedState) + ? 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), + child: DeletedMessage( + borderRadiusGeometry: widget + .borderRadiusGeometry, + borderSide: widget.borderSide, + shape: widget.shape, + messageTheme: + widget.messageTheme, + ), + ) + : Card( + clipBehavior: Clip.antiAlias, + elevation: 0, + margin: EdgeInsets.symmetric( + horizontal: (isFailedState + ? 15.0 + : 0.0) + // ignore: lines_longer_than_80_chars - widget.showUserAvatar == - // ignore: lines_longer_than_80_chars + (widget.showUserAvatar == DisplayWidget.gone ? 0 : 4.0), - child: DeletedMessage( - borderRadiusGeometry: - widget.borderRadiusGeometry, - borderSide: widget.borderSide, - shape: widget.shape, - messageTheme: - widget.messageTheme, - ), - ) - : Card( - clipBehavior: Clip.antiAlias, - elevation: 0, - margin: EdgeInsets.symmetric( - horizontal: (isFailedState - ? 15.0 - : 0.0) + - // ignore: lines_longer_than_80_chars - (widget.showUserAvatar == - DisplayWidget.gone - ? 0 - : 4.0), - ), - shape: widget.shape ?? - RoundedRectangleBorder( - side: widget.borderSide ?? - BorderSide( - color: widget - // ignore: lines_longer_than_80_chars - .messageTheme - // ignore: lines_longer_than_80_chars - .messageBorderColor ?? - Colors.grey, - ), - borderRadius: widget - // ignore: lines_longer_than_80_chars - .borderRadiusGeometry ?? - BorderRadius.zero, - ), - color: _getBackgroundColor(), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - if (hasQuotedMessage) - _buildQuotedMessage(), - if (hasNonUrlAttachments) - _parseAttachments(), - if (!isGiphy) - _buildTextBubble(), - ], + ), + shape: widget.shape ?? + RoundedRectangleBorder( + side: widget.borderSide ?? + BorderSide( + color: widget + // ignore: lines_longer_than_80_chars + .messageTheme + // ignore: lines_longer_than_80_chars + .messageBorderColor ?? + Colors.grey, + ), + borderRadius: widget + // ignore: lines_longer_than_80_chars + .borderRadiusGeometry ?? + BorderRadius.zero, + ), + color: _getBackgroundColor(), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + mainAxisSize: + MainAxisSize.min, + children: [ + if (hasQuotedMessage) + _buildQuotedMessage(), + if (hasNonUrlAttachments) + _parseAttachments(), + if (!isGiphy) + _buildTextBubble(), + ], + ), ), + ), + if (widget.showReactionPickerIndicator) + Positioned( + right: widget.reverse ? null : 4, + left: widget.reverse ? 4 : null, + top: -8, + child: CustomPaint( + painter: ReactionBubblePainter( + StreamChatTheme.of(context) + .colorTheme + .white, + Colors.transparent, + Colors.transparent, + tailCirclesSpace: 1, ), - ), - if (widget.showReactionPickerIndicator) - Positioned( - right: widget.reverse ? null : 4, - left: widget.reverse ? 4 : null, - top: -8, - child: CustomPaint( - painter: ReactionBubblePainter( - StreamChatTheme.of(context) - .colorTheme - .white, - Colors.transparent, - Colors.transparent, - tailCirclesSpace: 1, ), ), - ), - ], + ], + ), ), ), - ), - ], + ], + ), + if (showBottomRow) + SizedBox(height: context.textScaleFactor * 18.0), + ], + ), + if (showBottomRow) + Padding( + padding: EdgeInsets.only(left: leftPadding), + child: _bottomRow, ), - if (showBottomRow) - SizedBox(height: context.textScaleFactor * 18.0), - ], - ), - if (showBottomRow) - Padding( - padding: EdgeInsets.only(left: leftPadding), - child: _bottomRow, - ), - if (isFailedState) - Positioned( - left: widget.reverse ? 0 : null, - right: widget.reverse ? null : 0, - bottom: showBottomRow ? 18 : -2, - child: StreamSvgIcon.error(size: 20), - ), - ], - ), - ], + if (isFailedState) + Positioned( + left: widget.reverse ? 0 : null, + right: widget.reverse ? null : 0, + bottom: showBottomRow ? 18 : -2, + child: StreamSvgIcon.error(size: 20), + ), + ], + ), + ], + ), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart index 010b2c07..8f974d6e 100644 --- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; +import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget to show the current list of typing users @@ -41,11 +42,12 @@ class TypingIndicator extends StatelessWidget { child: alternativeWidget ?? const Offstage(), ), ); - return StreamBuilder>( + return BetterStreamBuilder>( + initialData: channelState.typingEvents, stream: channelState.typingEventsStream, builder: (context, snapshot) => AnimatedSwitcher( duration: const Duration(milliseconds: 300), - child: snapshot.data?.isNotEmpty == true + child: snapshot.isNotEmpty == true ? Padding( padding: padding, child: Align( @@ -61,7 +63,7 @@ class TypingIndicator extends StatelessWidget { ), Text( // ignore: lines_longer_than_80_chars - ' ${snapshot.data![0].name}${snapshot.data!.length == 1 ? '' : ' and ${snapshot.data!.length - 1} more'} ${snapshot.data!.length == 1 ? 'is' : 'are'} typing', + ' ${snapshot[0].name}${snapshot.length == 1 ? '' : ' and ${snapshot.length - 1} more'} ${snapshot.length == 1 ? 'is' : 'are'} typing', maxLines: 1, style: style, ), diff --git a/packages/stream_chat_flutter/lib/src/unread_indicator.dart b/packages/stream_chat_flutter/lib/src/unread_indicator.dart index 9c3a5f88..8b235187 100644 --- a/packages/stream_chat_flutter/lib/src/unread_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/unread_indicator.dart @@ -17,7 +17,7 @@ class UnreadIndicator extends StatelessWidget { Widget build(BuildContext context) { final client = StreamChat.of(context).client; return IgnorePointer( - child: StreamBuilder( + child: BetterStreamBuilder( stream: cid != null ? client.state.channels[cid]?.state?.unreadCountStream : client.state.totalUnreadCountStream, @@ -25,8 +25,8 @@ class UnreadIndicator extends StatelessWidget { ? client.state.channels[cid]?.state?.unreadCount : client.state.totalUnreadCount, builder: (context, snapshot) { - if (!snapshot.hasData || snapshot.data == 0) { - return const SizedBox(); + if (snapshot == null || snapshot == 0) { + return const Offstage(); } return Material( borderRadius: BorderRadius.circular(8), @@ -42,7 +42,7 @@ class UnreadIndicator extends StatelessWidget { ), child: Center( child: Text( - '${snapshot.data! > 99 ? '99+' : snapshot.data}', + '${snapshot > 99 ? '99+' : snapshot}', style: const TextStyle( fontSize: 11, color: Colors.white, diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 5a86d4ef..644ea99f 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; 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 new file mode 100644 index 00000000..bf50966b --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart @@ -0,0 +1,100 @@ +import 'dart:async'; + +import 'package:flutter/widgets.dart'; + +class BetterStreamBuilder extends StatefulWidget { + const BetterStreamBuilder({ + required this.stream, + required this.initialData, + required this.builder, + this.loadingBuilder, + this.errorBuilder, + this.comparator, + Key? key, + }) : super(key: key); + + final Stream? stream; + final T initialData; + final bool Function(T?, T)? comparator; + final Widget Function(BuildContext context, T data) builder; + final Widget Function(BuildContext context)? loadingBuilder; + final Widget Function(BuildContext context, Object error)? errorBuilder; + + @override + _BetterStreamBuilderState createState() => _BetterStreamBuilderState(); +} + +class _BetterStreamBuilderState extends State> { + Widget? _child; + T? _lastEvent; + StreamSubscription? _subscription; + Object? _lastError; + + @override + Widget build(BuildContext context) => _child ?? const Offstage(); + + bool _firstTime = true; + @override + void didChangeDependencies() { + if (_firstTime) { + if (widget.initialData == null && widget.loadingBuilder != null) { + _child = widget.loadingBuilder!(context); + } else { + _onEvent(widget.initialData); + } + _lastEvent = widget.initialData; + _subscription = widget.stream?.listen( + _onEvent, + onError: _onError, + ); + + _firstTime = false; + } + super.didChangeDependencies(); + } + + @override + void didUpdateWidget(covariant BetterStreamBuilder oldWidget) { + if (_lastError != null && oldWidget.errorBuilder != widget.errorBuilder) { + _onError(_lastError); + } else if (oldWidget.builder != widget.builder) { + _onEvent(_lastEvent); + } + + if (oldWidget.stream != widget.stream) { + _subscription?.cancel(); + _subscription = widget.stream?.listen( + _onEvent, + onError: _onError, + ); + } + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _subscription?.cancel(); + super.dispose(); + } + + void _onError(error) { + if (widget.errorBuilder != null && error != _lastError) { + setState(() { + _child = widget.errorBuilder!(context, error); + }); + _lastError = error; + } + } + + void _onEvent(event) { + _lastError = null; + if (widget.comparator != null + ? widget.comparator!(_lastEvent, event) + : event != _lastEvent) { + setState(() { + _child = widget.builder(context, event); + }); + _lastEvent = event; + } + } +} diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 9b0d3d38..0da733bf 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -1,9 +1,11 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/better_stream_builder.dart'; import 'package:stream_chat_flutter_core/src/stream_channel.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart'; @@ -127,6 +129,10 @@ class MessageListCoreState extends State { .map((threads) => threads[widget.parentMessage!.id]) : _streamChannel!.channel.state?.messagesStream; + final initialData = _isThreadConversation + ? _streamChannel!.channel.state?.threads[widget.parentMessage!.id] + : _streamChannel!.channel.state?.messages; + bool defaultFilter(Message m) { final isMyMessage = m.user?.id == _currentUser?.id; final isDeletedOrShadowed = m.isDeleted == true || m.shadowed == true; @@ -134,30 +140,27 @@ class MessageListCoreState extends State { return true; } - return StreamBuilder?>( - stream: messagesStream?.map( + return BetterStreamBuilder?>( + initialData: initialData, + comparator: const ListEquality().equals, + stream: messagesStream!.map( (messages) => messages?.where(widget.messageFilter ?? defaultFilter).toList( growable: false, ), ), + errorBuilder: widget.errorWidgetBuilder, + loadingBuilder: widget.loadingBuilder, builder: (context, snapshot) { - if (snapshot.hasError) { - return widget.errorWidgetBuilder(context, snapshot.error!); - } else if (!snapshot.hasData) { - return widget.loadingBuilder(context); - } else { - final messageList = - snapshot.data?.reversed.toList(growable: false) ?? []; - if (messageList.isEmpty && !_isThreadConversation) { - if (_upToDate) { - return widget.emptyBuilder(context); - } - } else { - _messages = messageList; + final messageList = snapshot?.reversed.toList(growable: false) ?? []; + if (messageList.isEmpty && !_isThreadConversation) { + if (_upToDate) { + return widget.emptyBuilder(context); } - return widget.messageListBuilder(context, _messages); + } else { + _messages = messageList; } + return widget.messageListBuilder(context, _messages); }, ); } diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index 01463de0..dba20f40 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -2,6 +2,7 @@ library stream_chat_flutter_core; export 'package:stream_chat/stream_chat.dart'; +export 'src/better_stream_builder.dart'; export 'src/channel_list_core.dart' hide ChannelListCoreState; export 'src/channels_bloc.dart'; export 'src/lazy_load_scroll_view.dart'; From 39d82c75ad4226ca0b3c9ef337802cc40f66faa9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 2 Jun 2021 15:55:39 +0530 Subject: [PATCH 321/599] feat: Added new API endpoint --- packages/stream_chat/lib/src/api/channel.dart | 41 +++++++++++++++---- packages/stream_chat/lib/src/client.dart | 35 +++++++++++----- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 2f95be78..a10ec32d 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -462,7 +462,7 @@ class Channel { if (error is DioError && error.type != DioErrorType.response) { state?.retryQueue?.add([message]); } else if (error is ApiError) { - if(currentMessage != null) { + if (currentMessage != null) { state?.addMessage(currentMessage); } } @@ -470,6 +470,27 @@ class Channel { } } + /// Partially updates the [message] in this channel. + Future partiallyUpdateMessage( + Message message, Map data) async { + try { + final response = await _client.partiallyUpdateMessage(message.id, data); + + final m = response.message.copyWith( + ownReactions: message.ownReactions, + ); + + state?.addMessage(m); + + return response; + } catch (error) { + if (error is DioError && error.type != DioErrorType.response) { + state?.retryQueue?.add([message]); + } + rethrow; + } + } + /// Deletes the [message] from the channel. Future deleteMessage(Message message) async { // Directly deleting the local messages which are not yet sent to server @@ -533,17 +554,21 @@ class Channel { Duration(seconds: timeoutOrExpirationDate.toInt()), ); } - return updateMessage( - message.copyWith( - pinned: true, - pinExpires: pinExpires, - ), - ); + return partiallyUpdateMessage(message, { + 'set': { + 'pinned': true, + if (pinExpires != null) 'pin_expires': pinExpires.toIso8601String(), + } + }); } /// Unpins provided message Future unpinMessage(Message message) => - updateMessage(message.copyWith(pinned: false)); + partiallyUpdateMessage(message, { + 'set': { + 'pinned': false, + } + }); /// Send a file to this channel Future sendFile( diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 98b05634..e0e29fff 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1347,6 +1347,18 @@ class StreamChatClient { return decode(response.data, UpdateMessageResponse.fromJson); } + /// Partially update the given message + /// Use 'set' in map to set values + /// User 'unset' in map to unset values + Future partiallyUpdateMessage( + String id, Map data) async { + final response = await put( + '/messages/${id}', + data: data, + ); + return decode(response.data, UpdateMessageResponse.fromJson); + } + /// Deletes the given message Future deleteMessage(Message message) async { final response = await delete('/messages/${message.id}'); @@ -1385,20 +1397,21 @@ class StreamChatClient { ) .toUtc(); } - return updateMessage( - message.copyWith( - pinned: true, - pinExpires: pinExpires, - ), - ); + return partiallyUpdateMessage(message.id, { + 'set': { + 'pinned': true, + if (pinExpires != null) 'pin_expires': pinExpires.toIso8601String(), + } + }); } /// Unpins provided message - Future unpinMessage(Message message) => updateMessage( - message.copyWith( - pinned: false, - ), - ); + Future unpinMessage(Message message) => + partiallyUpdateMessage(message.id, { + 'set': { + 'pinned': false, + } + }); } /// The class that handles the state of the channel listening to the events From 12e6b9c500bfee4c5375a13deac3960473129bb5 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 2 Jun 2021 17:56:38 +0530 Subject: [PATCH 322/599] unify errors, refactor retry_queue Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/api/channel.dart | 42 +-- .../stream_chat/lib/src/api/retry_policy.dart | 35 +-- .../stream_chat/lib/src/api/retry_queue.dart | 251 ++++++++++-------- packages/stream_chat/lib/src/client.dart | 34 ++- .../http/interceptor/auth_interceptor.dart | 4 +- .../src/core/http/stream_chat_dio_error.dart | 2 +- .../lib/src/core/http/stream_http_client.dart | 11 +- .../lib/src/core/models/event.dart | 2 +- packages/stream_chat/lib/src/core/utils.dart | 9 + .../lib/src/errors/chat_error_code.dart | 229 ++++++++++------ .../lib/src/errors/stream_chat_error.dart | 106 ++++++-- packages/stream_chat/lib/src/exceptions.dart | 53 ---- .../stream_chat/lib/src/ws/socket_error.dart | 25 -- .../stream_chat/lib/src/ws/websocket.dart | 55 ++-- packages/stream_chat/lib/stream_chat.dart | 3 +- .../stream_chat/test/src/client_test.dart | 1 - .../lib/src/message_actions_modal.dart | 3 +- .../lib/src/db/moor_chat_database.dart | 2 +- 18 files changed, 486 insertions(+), 381 deletions(-) delete mode 100644 packages/stream_chat/lib/src/exceptions.dart delete mode 100644 packages/stream_chat/lib/src/ws/socket_error.dart diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 3fd38e41..3fb63c91 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -7,6 +7,8 @@ import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/api/retry_queue.dart'; +import 'package:stream_chat/src/core/utils.dart'; +import 'package:stream_chat/src/errors/stream_chat_error.dart'; import 'package:stream_chat/src/event_type.dart'; import 'package:stream_chat/src/extensions/rate_limit.dart'; import 'package:stream_chat/src/core/models/attachment_file.dart'; @@ -233,12 +235,14 @@ class Channel { }) { final cancelToken = _cancelableAttachmentUploadRequest[attachmentId]; if (cancelToken == null) { - throw Exception( - "Upload request for this Attachment hasn't started yet or else " + throw const StreamChatError( + "Upload request for this Attachment hasn't started yet or maybe " 'Already completed', ); } - if (cancelToken.isCancelled) throw Exception('Already cancelled'); + if (cancelToken.isCancelled) { + throw const StreamChatError('Upload request already cancelled'); + } cancelToken.cancel(reason); } @@ -255,7 +259,7 @@ class Channel { ); if (message == null) { - throw Exception('Error, Message not found'); + throw const StreamChatError('Error, Message not found'); } final attachments = message.attachments.where((it) { @@ -396,9 +400,9 @@ class Channel { final response = await _client.sendMessage(message, id!, type); state!.addMessage(response.message); return response; - } catch (error) { - if (error is DioError && error.type != DioErrorType.response) { - state!.retryQueue?.add([message]); + } catch (e) { + if (e is StreamChatNetworkError && e.isRetriable) { + state!._retryQueue.add([message]); } rethrow; } @@ -453,9 +457,9 @@ class Channel { state?.addMessage(m); return response; - } catch (error) { - if (error is DioError && error.type != DioErrorType.response) { - state?.retryQueue?.add([message]); + } catch (e) { + if (e is StreamChatNetworkError && e.isRetriable) { + state!._retryQueue.add([message]); } rethrow; } @@ -494,9 +498,9 @@ class Channel { state?.addMessage(message.copyWith(status: MessageSendingStatus.sent)); return response; - } catch (error) { - if (error is DioError && error.type != DioErrorType.response) { - state?.retryQueue?.add([message]); + } catch (e) { + if (e is StreamChatNetworkError && e.isRetriable) { + state!._retryQueue.add([message]); } rethrow; } @@ -1188,9 +1192,11 @@ class ChannelClientState { _channel._client.chatPersistenceClient ?.updateChannelState(state)) .debounced(const Duration(seconds: 1)) { - retryQueue = RetryQueue( + _retryQueue = RetryQueue( channel: _channel, - logger: Logger('RETRY QUEUE ${_channel.cid}'), + logger: _channel.client.detachedLogger( + 'âŸŗ (${generateHash([_channel.cid])})', + ), ); _checkExpiredAttachmentMessages(channelState); @@ -1338,7 +1344,7 @@ class ChannelClientState { BehaviorSubject.seeded(true); /// The retry queue associated to this channel - RetryQueue? retryQueue; + late final RetryQueue _retryQueue; /// Retry failed message Future retryFailedMessages() async { @@ -1357,7 +1363,7 @@ class ChannelClientState { ) .toList(); - retryQueue!.add(failedMessages); + _retryQueue.add(failedMessages); } void _listenReactionDeleted() { @@ -1832,7 +1838,7 @@ class ChannelClientState { void dispose() { _debouncedUpdatePersistenceChannelState.cancel(); _unreadCountController.close(); - retryQueue!.dispose(); + _retryQueue.dispose(); _subscriptions.forEach((s) => s.cancel()); _channelStateController.close(); _isUpToDateController.close(); diff --git a/packages/stream_chat/lib/src/api/retry_policy.dart b/packages/stream_chat/lib/src/api/retry_policy.dart index f0f2c67b..14933fed 100644 --- a/packages/stream_chat/lib/src/api/retry_policy.dart +++ b/packages/stream_chat/lib/src/api/retry_policy.dart @@ -1,5 +1,5 @@ import 'package:stream_chat/src/client.dart'; -import 'package:stream_chat/src/exceptions.dart'; +import 'package:stream_chat/src/errors/stream_chat_error.dart'; /// The retry options class RetryPolicy { @@ -7,32 +7,25 @@ class RetryPolicy { RetryPolicy({ required this.shouldRetry, required this.retryTimeout, - this.attempt = 0, + this.maxRetryAttempts = 6, }); - /// The number of attempts tried so far - int attempt = 0; + /// Hard limit on maximum retry attempts before giving up, defaults to 6 + /// Resets once connection recovers. + final int maxRetryAttempts; /// This function evaluates if we should retry the failure - final bool Function(StreamChatClient client, int attempt, ApiError? apiError) - shouldRetry; + final bool Function( + StreamChatClient client, + int attempt, + StreamChatError? error, + ) shouldRetry; /// In the case that we want to retry a failed request the retryTimeout /// method is called to determine the timeout final Duration Function( - StreamChatClient client, int attempt, ApiError? apiError) retryTimeout; - - /// Creates a copy of [RetryPolicy] with specified attributes overridden. - RetryPolicy copyWith({ - bool Function(StreamChatClient client, int attempt, ApiError? apiError)? - shouldRetry, - Duration Function(StreamChatClient client, int attempt, ApiError? apiError)? - retryTimeout, - int? attempt, - }) => - RetryPolicy( - retryTimeout: retryTimeout ?? this.retryTimeout, - shouldRetry: shouldRetry ?? this.shouldRetry, - attempt: attempt ?? this.attempt, - ); + StreamChatClient client, + int attempt, + StreamChatError? error, + ) retryTimeout; } diff --git a/packages/stream_chat/lib/src/api/retry_queue.dart b/packages/stream_chat/lib/src/api/retry_queue.dart index e6d883a9..9d0b8502 100644 --- a/packages/stream_chat/lib/src/api/retry_queue.dart +++ b/packages/stream_chat/lib/src/api/retry_queue.dart @@ -2,10 +2,11 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:logging/logging.dart'; +import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/api/channel.dart'; import 'package:stream_chat/src/api/retry_policy.dart'; +import 'package:stream_chat/src/errors/stream_chat_error.dart'; import 'package:stream_chat/src/event_type.dart'; -import 'package:stream_chat/src/exceptions.dart'; import 'package:stream_chat/src/core/models/message.dart'; import 'package:stream_chat/stream_chat.dart'; @@ -15,116 +16,155 @@ class RetryQueue { RetryQueue({ required this.channel, this.logger, - }) { - _retryPolicy = channel.client.retryPolicy; - + }) : client = channel.client { + _retryPolicy = client.retryPolicy; _listenConnectionRecovered(); - _listenFailedEvents(); } /// The channel of this queue final Channel channel; + /// The client associated with this [channel] + final StreamChatClient client; + /// The logger associated to this queue final Logger? logger; - final _subscriptions = []; + late final RetryPolicy _retryPolicy; + + final _compositeSubscription = CompositeSubscription(); + + final _messageQueue = HeapPriorityQueue(_byDate); + bool _isRetrying = false; void _listenConnectionRecovered() { - _subscriptions - .add(channel.client.on(EventType.connectionRecovered).listen((event) { - if (!_isRetrying && event.online!) { + client.on(EventType.connectionRecovered).listen((event) { + if (event.online == true) { _startRetrying(); } - })); + }).addTo(_compositeSubscription); } - final HeapPriorityQueue _messageQueue = HeapPriorityQueue(_byDate); - bool _isRetrying = false; - RetryPolicy? _retryPolicy; + void _listenFailedEvents() { + channel.on().where((event) => event.message != null).listen((event) { + final message = event.message!; + final containsMessage = _messageQueue.containsMessage(message); + if (!containsMessage) return; + if (message.status == MessageSendingStatus.sent) { + logger?.info('Removing sent message from queue : ${message.id}'); + _messageQueue.removeMessage(message); + return; + } else { + if ([ + MessageSendingStatus.failed_update, + MessageSendingStatus.failed, + MessageSendingStatus.failed_delete, + ].contains(message.status)) { + logger?.info('Adding failed message from event : ${event.type}'); + add([message]); + } + } + }).addTo(_compositeSubscription); + } /// Add a list of messages void add(List messages) { - logger?.info('added ${messages.length} messages'); + if (messages.isEmpty) return; + if (_messageQueue.containsAllMessage(messages)) return; + + logger?.info('Adding ${messages.length} messages'); final messageList = _messageQueue.toList(); - - _messageQueue.addAll(messages - .where((element) => !messageList.any((m) => m.id == element.id))); - - if (_messageQueue.isNotEmpty && !_isRetrying) { - _startRetrying(); - } + // we should not add message if already available in the queue + _messageQueue.addAll(messages.where( + (it) => !messageList.any((m) => m.id == it.id), + )); + _startRetrying(); } Future _startRetrying() async { - logger?.info('start retrying'); + if (_isRetrying) return; _isRetrying = true; - final retryPolicy = _retryPolicy!.copyWith(attempt: 0); + logger?.info('Started retrying failed messages'); while (_messageQueue.isNotEmpty) { + logger?.info('${_messageQueue.length} messages remaining in the queue'); final message = _messageQueue.first; - try { - logger?.info('retry attempt ${retryPolicy.attempt}'); - await _sendMessage(message); - logger?.info('message sent - removing it from the queue'); - _messageQueue.remove(message); - logger?.info('now ${_messageQueue.length} messages in the queue'); - retryPolicy.attempt = 0; - } catch (error) { - ApiError? apiError; - if (error is DioError) { - if (error.type == DioErrorType.response) { - _messageQueue.remove(message); - return; - } - apiError = ApiError( - error.response?.data, - error.response?.statusCode, - ); - } else if (error is ApiError) { - apiError = error; - if (apiError.status?.toString().startsWith('4') == true) { - _messageQueue.remove(message); - return; - } - } - - if (!retryPolicy.shouldRetry( - channel.client, - retryPolicy.attempt, - apiError, - )) { - _messageQueue.toList().forEach(_sendFailedEvent); - _isRetrying = false; - return; - } - - retryPolicy.attempt++; - - final timeout = retryPolicy.retryTimeout( - channel.client, - retryPolicy.attempt, - apiError, - ); - await Future.delayed(timeout); - } + await _runAndRetry(message); } _isRetrying = false; } - void _sendFailedEvent(Message? message) { - final newStatus = message!.status == MessageSendingStatus.sending - ? MessageSendingStatus.failed - : (message.status == MessageSendingStatus.updating - ? MessageSendingStatus.failed_update - : MessageSendingStatus.failed_delete); - channel.state!.addMessage(message.copyWith( - status: newStatus, - )); + Future _runAndRetry(Message message) async { + var attempt = 1; + + final maxAttempt = _retryPolicy.maxRetryAttempts; + // early return in case maxAttempt is less than 0 + if (attempt > maxAttempt) return; + + // ignore: literal_only_boolean_expressions + while (true) { + try { + logger?.info('Message (${message.id}) retry attempt $attempt'); + await _retryMessage(message); + logger?.info('Message (${message.id}) sent successfully'); + _messageQueue.removeMessage(message); + break; + } on StreamChatError catch (e) { + // retry logic + final maxAttempt = _retryPolicy.maxRetryAttempts; + if (attempt < maxAttempt) { + final shouldRetry = _retryPolicy.shouldRetry(client, attempt, e); + if (shouldRetry) { + final timeout = _retryPolicy.retryTimeout(client, attempt, e); + // temporary failure, continue + logger?.info( + 'API call failed (attempt $attempt), ' + 'retrying in ${timeout.inSeconds} seconds. Error was $e', + ); + await Future.delayed(timeout); + attempt += 1; + } else { + logger?.info( + 'API call failed (attempt $attempt). ' + 'Giving up for now, will retry when connection recovers. ' + 'Error was $e', + ); + _sendFailedEvent(message); + break; + } + } else { + logger?.info( + 'API call failed (attempt $attempt). ' + 'Exceeds maxRetryAttempt : $maxAttempt ' + 'Giving up for now, will retry when connection recovers. ' + 'Error was $e', + ); + _sendFailedEvent(message); + break; + } + } catch (e) { + logger?.info( + 'API call failed due to unknown error (attempt $attempt). ' + 'Giving up for now, will retry when connection recovers. ' + 'Error was $e', + ); + _sendFailedEvent(message); + break; + } + } } - Future _sendMessage(Message message) async { + void _sendFailedEvent(Message message) { + final newStatus = message.status == MessageSendingStatus.sending + ? MessageSendingStatus.failed + : message.status == MessageSendingStatus.updating + ? MessageSendingStatus.failed_update + : MessageSendingStatus.failed_delete; + channel.state?.addMessage(message.copyWith(status: newStatus)); + } + + Future _retryMessage(Message message) async { if (message.status == MessageSendingStatus.failed_update || message.status == MessageSendingStatus.updating) { await channel.updateMessage(message); @@ -137,39 +177,10 @@ class RetryQueue { } } - void _listenFailedEvents() { - _subscriptions.add(channel.on().listen((event) { - final messageList = _messageQueue.toList(); - if (event.message != null) { - final messageIndex = - messageList.indexWhere((m) => m.id == event.message!.id); - if (messageIndex == -1 && - [ - MessageSendingStatus.failed_update, - MessageSendingStatus.failed, - MessageSendingStatus.failed_delete, - ].contains(event.message!.status)) { - logger?.info('add message from events'); - final m = event.message; - - if (m != null) { - add([m]); - } - } else if (messageIndex != -1 && - [ - MessageSendingStatus.sent, - null, - ].contains(event.message!.status)) { - _messageQueue.remove(messageList[messageIndex]); - } - } - })); - } - /// Call this method to dispose this object void dispose() { _messageQueue.clear(); - _subscriptions.forEach((s) => s.cancel()); + _compositeSubscription.dispose(); } static int _byDate(Message m1, Message m2) { @@ -201,3 +212,27 @@ class RetryQueue { } } } + +extension _MessageHeapPriorityQueue on HeapPriorityQueue { + void removeMessage(Message message) { + final list = toUnorderedList(); + final index = list.indexWhere((it) => it.id == message.id); + if (index == -1) return; + final element = list[index]; + remove(element); + } + + bool containsMessage(Message message) { + final list = toUnorderedList(); + final index = list.indexWhere((it) => it.id == message.id); + if (index == -1) return false; + return true; + } + + bool containsAllMessage(List messages) { + if (isEmpty) return false; + final list = toUnorderedList(); + final messageIds = messages.map((it) => it.id); + return list.every((it) => messageIds.contains(it.id)); + } +} diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index cd986b2e..e7e05b69 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -7,6 +7,8 @@ import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/api/channel.dart'; +import 'package:stream_chat/src/core/utils.dart'; +import 'package:stream_chat/src/errors/stream_chat_error.dart'; import 'package:stream_chat/src/location.dart'; import 'package:stream_chat/src/ws/connection_status.dart'; import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; @@ -92,7 +94,7 @@ class StreamChatClient { tokenManager: _tokenManager, connectionIdManager: _connectionIdManager, attachmentFileUploader: attachmentFileUploader, - logger: _detachedLogger('đŸ•¸ī¸'), + logger: detachedLogger('đŸ•¸ī¸'), ); _ws = WebSocket( @@ -100,13 +102,13 @@ class StreamChatClient { baseUrl: options.baseUrl, tokenManager: _tokenManager, handler: handleEvent, - logger: _detachedLogger('🔌'), + logger: detachedLogger('🔌'), ); _retryPolicy = retryPolicy ?? RetryPolicy( - retryTimeout: (_, int attempt, __) => Duration(seconds: 1 * attempt), - shouldRetry: (_, int attempt, __) => attempt < 5, + shouldRetry: (_, attempt, __) => attempt < 5, + retryTimeout: (_, attempt, __) => Duration(seconds: attempt), ); state = ClientState(this); @@ -137,7 +139,7 @@ class StreamChatClient { /// Whether the chat persistence is available or not bool get persistenceEnabled => _chatPersistenceClient != null; - RetryPolicy? _retryPolicy; + late final RetryPolicy _retryPolicy; // sync state of the channels present inside state, defaults to false bool _synced = false; @@ -146,7 +148,7 @@ class StreamChatClient { DateTime? _lastSyncedAt; /// The retry policy options getter - RetryPolicy? get retryPolicy => _retryPolicy; + RetryPolicy get retryPolicy => _retryPolicy; /// By default the Chat client will write all messages with level Warn or /// Error to stdout. @@ -217,7 +219,8 @@ class StreamChatClient { if (record.stackTrace != null) print(record.stackTrace); }; - Logger _detachedLogger( + /// + Logger detachedLogger( String name, ) => Logger.detached(name) @@ -303,7 +306,7 @@ class StreamChatClient { TokenProvider? provider, }) async { if (_ws.connectionCompleter?.isCompleted == false) { - throw Exception( + throw const StreamChatError( 'User already getting connected, try calling `disconnectUser` ' 'before trying to connect again', ); @@ -346,11 +349,11 @@ class StreamChatClient { logger.info('Opening web-socket connection for ${user.id}'); if (wsConnectionStatus == ConnectionStatus.connecting) { - throw Exception('Connection already in progress for ${user.id}'); + throw StreamChatError('Connection already in progress for ${user.id}'); } if (wsConnectionStatus == ConnectionStatus.connected) { - throw Exception('Connection already connected for ${user.id}'); + throw StreamChatError('Connection already connected for ${user.id}'); } _wsConnectionStatus = ConnectionStatus.connecting; @@ -486,13 +489,6 @@ class StreamChatClient { } } - String _generateHash(List objects) { - final payload = json.encode(objects); - final payloadBytes = utf8.encode(payload); - final payloadB64 = base64.encode(payloadBytes); - return payloadB64; - } - final _queryChannelsStreams = >>{}; /// Requests channels with a given query. @@ -512,7 +508,7 @@ class StreamChatClient { watch = false; } - final hash = _generateHash([ + final hash = generateHash([ filter, sort, state, @@ -575,7 +571,7 @@ class StreamChatClient { await _ws.connectionCompleter?.future; } if (wsConnectionStatus != ConnectionStatus.connected) { - throw Exception( + throw const StreamChatError( 'You cannot use queryChannels without an active connection. ' 'Please call `connectUser` to connect the client.', ); diff --git a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart index a75b2d2c..9dafc6c9 100644 --- a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart +++ b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart @@ -27,12 +27,12 @@ class AuthInterceptor extends Interceptor { try { token = await _tokenManager.loadToken(); } catch (_) { - final error = StreamChatError(ChatErrorCode.undefinedToken); + final error = StreamChatNetworkError(ChatErrorCode.undefinedToken); final dioError = StreamChatDioError( error: error, requestOptions: options, ); - return handler.reject(dioError); + return handler.reject(dioError, true); } final params = {'user_id': token.userId}; final headers = { diff --git a/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart b/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart index dd2c2535..f0e61111 100644 --- a/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart +++ b/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart @@ -17,5 +17,5 @@ class StreamChatDioError extends DioError { ); @override - final StreamChatError error; + final StreamChatNetworkError error; } diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client.dart b/packages/stream_chat/lib/src/core/http/stream_http_client.dart index d814d705..0daeaacd 100644 --- a/packages/stream_chat/lib/src/core/http/stream_http_client.dart +++ b/packages/stream_chat/lib/src/core/http/stream_http_client.dart @@ -100,14 +100,15 @@ class StreamHttpClient { void close({bool force = false}) => httpClient.close(force: force); StreamChatNetworkError _parseError(DioError err) { + StreamChatNetworkError error; // locally thrown dio error if (err is StreamChatDioError) { - final code = err.error.code; - final message = err.error.message; - return StreamChatNetworkError.raw(code: code, message: message); + error = err.error; + } else { + // real network request dio error + error = StreamChatNetworkError.fromDioError(err); } - // real network request dio error - return StreamChatNetworkError.fromDioError(err); + return error..stackTrace = err.stackTrace; } /// Handy method to make http GET request with error parsing. diff --git a/packages/stream_chat/lib/src/core/models/event.dart b/packages/stream_chat/lib/src/core/models/event.dart index d0d17aa0..6ed1d414 100644 --- a/packages/stream_chat/lib/src/core/models/event.dart +++ b/packages/stream_chat/lib/src/core/models/event.dart @@ -29,7 +29,7 @@ class Event { this.parentId, this.extraData = const {}, this.isLocal = true, - }) : createdAt = createdAt ?? DateTime.now(); + }) : createdAt = createdAt?.toUtc() ?? DateTime.now().toUtc(); /// Create a new instance from a json factory Event.fromJson(Map json) => diff --git a/packages/stream_chat/lib/src/core/utils.dart b/packages/stream_chat/lib/src/core/utils.dart index 9366e34e..c2d77927 100644 --- a/packages/stream_chat/lib/src/core/utils.dart +++ b/packages/stream_chat/lib/src/core/utils.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:math' as math; // This alphabet uses `A-Za-z0-9_-` symbols. The genetic algorithm helped @@ -14,3 +15,11 @@ String randomId({int size = 21}) { } return id; } + +/// Creates a hash string from the passed [objects] +String generateHash(List objects) { + final payload = json.encode(objects); + final payloadBytes = utf8.encode(payload); + final payloadB64 = base64.encode(payloadBytes); + return payloadB64; +} diff --git a/packages/stream_chat/lib/src/errors/chat_error_code.dart b/packages/stream_chat/lib/src/errors/chat_error_code.dart index 3dbb600c..6391428d 100644 --- a/packages/stream_chat/lib/src/errors/chat_error_code.dart +++ b/packages/stream_chat/lib/src/errors/chat_error_code.dart @@ -1,91 +1,166 @@ -/// -enum ChatErrorCode { - // client error codes - networkFailed, - parserError, - socketClosed, - socketFailure, - cantParseConnectionEvent, - cantParseEvent, - invalidToken, - undefinedToken, - unableToParseSocketEvent, - noErrorBody, +// ignore_for_file: lines_longer_than_80_chars - // server error codes +import 'package:collection/collection.dart'; + +/// Complete list of errors that are returned by the API +/// together with the description and API code. +enum ChatErrorCode { + // Client errors + + /// Unauthenticated, token not defined + undefinedToken, + + // Bad Request + + /// Wrong data/parameter is sent to the API + inputError, + + /// Duplicate username is sent while enforce_unique_usernames is enabled + duplicateUsername, + + /// Message is too long + messageTooLong, + + /// Event is not supported + eventNotSupported, + + /// The feature is currently disabled + /// on the dashboard (i.e. Reactions & Replies) + channelFeatureNotSupported, + + /// Multiple Levels Reply is not supported + /// the API only supports 1 level deep reply threads + multipleNestling, + + /// Custom Command handler returned an error + customCommandEndpointCall, + + /// App config does not have custom_action_handler_url + customCommandEndpointMissing, + + // Unauthorised + + /// Unauthenticated, problem with authentication authenticationError, + + /// Unauthenticated, token expired tokenExpired, + + /// Unauthenticated, token date incorrect + tokenBeforeIssuedAt, + + /// Unauthenticated, token not valid yet tokenNotValid, - tokenDateIncorrect, - tokenSignatureIncorrect, - apiKeyNotFound, + + /// Unauthenticated, token signature invalid + tokenSignatureInvalid, + + /// Access Key invalid + accessKeyError, + + // Forbidden + + /// Unauthorised / forbidden to make request + notAllowed, + + /// App suspended + appSuspended, + + /// User tried to post a message during the cooldown period + cooldownError, + + // Miscellaneous + + /// Resource not found + doesNotExist, + + /// Request timed out + requestTimeout, + + /// Payload too big + payloadTooBig, + + /// Too many requests in a certain time frame + rateLimitError, + + /// Request headers are too large + maximumHeaderSizeExceeded, + + /// Something goes wrong in the system + internalSystemError, + + /// No access to requested channels + noAccessToChannels } +const _errorCodeWithDescription = { + ChatErrorCode.undefinedToken: + 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.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.customCommandEndpointCall: + MapEntry(45, 'Custom Command handler returned an error'), + ChatErrorCode.customCommandEndpointMissing: + MapEntry(44, 'App config does not have custom_action_handler_url'), + ChatErrorCode.authenticationError: + MapEntry(5, 'Unauthenticated, problem with authentication'), + ChatErrorCode.tokenExpired: MapEntry(40, 'Unauthenticated, token expired'), + ChatErrorCode.tokenBeforeIssuedAt: + MapEntry(42, 'Unauthenticated, token date incorrect'), + ChatErrorCode.tokenNotValid: + MapEntry(41, 'Unauthenticated, token not valid yet'), + ChatErrorCode.tokenSignatureInvalid: + MapEntry(43, 'Unauthenticated, token signature invalid'), + ChatErrorCode.accessKeyError: MapEntry(2, 'Access Key invalid'), + ChatErrorCode.notAllowed: + MapEntry(17, 'Unauthorised / forbidden to make request'), + ChatErrorCode.appSuspended: MapEntry(99, 'App suspended'), + ChatErrorCode.cooldownError: + MapEntry(60, 'User tried to post a message during the cooldown period'), + ChatErrorCode.doesNotExist: MapEntry(16, 'Resource not found'), + ChatErrorCode.requestTimeout: MapEntry(23, 'Request timed out'), + ChatErrorCode.payloadTooBig: MapEntry(22, 'Payload too big'), + ChatErrorCode.rateLimitError: + MapEntry(9, 'Too many requests in a certain time frame'), + ChatErrorCode.maximumHeaderSizeExceeded: + MapEntry(24, 'Request headers are too large'), + ChatErrorCode.internalSystemError: + MapEntry(-1, 'Something goes wrong in the system'), + ChatErrorCode.noAccessToChannels: + MapEntry(70, 'No access to requested channels'), +}; + +const _authenticationErrors = [ + ChatErrorCode.undefinedToken, + ChatErrorCode.authenticationError, + ChatErrorCode.tokenExpired, + ChatErrorCode.tokenBeforeIssuedAt, + ChatErrorCode.tokenNotValid, + ChatErrorCode.tokenSignatureInvalid, + ChatErrorCode.accessKeyError, + ChatErrorCode.noAccessToChannels, +]; + +/// +ChatErrorCode? chatErrorCodeFromCode(int code) => _errorCodeWithDescription.keys + .firstWhereOrNull((key) => _errorCodeWithDescription[key]!.key == code); + /// extension ChatErrorCodeX on ChatErrorCode { /// - String get message => { - // client error message - ChatErrorCode.networkFailed: 'Response is failed. See cause', - ChatErrorCode.parserError: 'Unable to parse error', - ChatErrorCode.socketClosed: 'Server closed connection', - ChatErrorCode.socketFailure: 'See stack trace in logs. ' - 'Intercept error in error handler of setUser', - ChatErrorCode.cantParseConnectionEvent: - 'Unable to parse connection event', - ChatErrorCode.cantParseEvent: 'Unable to parse event', - ChatErrorCode.invalidToken: 'Invalid token', - ChatErrorCode.undefinedToken: - 'No defined token. Check if client.setUser was called and finished', - ChatErrorCode.unableToParseSocketEvent: - 'Socket event payload either invalid or null', - ChatErrorCode.noErrorBody: 'No error body. See http status code', - - // server error message - ChatErrorCode.authenticationError: - 'Unauthenticated, problem with authentication', - ChatErrorCode.tokenExpired: 'Token expired, new one must be requested.', - ChatErrorCode.tokenNotValid: 'Unauthenticated, token not valid yet', - ChatErrorCode.tokenDateIncorrect: - 'Unauthenticated, token date incorrect', - ChatErrorCode.tokenSignatureIncorrect: - 'Unauthenticated, token signature invalid', - ChatErrorCode.apiKeyNotFound: - "Api key is not found, verify it if it's correct or was created.", - }[this]!; + String get message => _errorCodeWithDescription[this]!.value; /// - int get code => { - // client error codes - ChatErrorCode.networkFailed: 1000, - ChatErrorCode.parserError: 1001, - ChatErrorCode.socketClosed: 1002, - ChatErrorCode.socketFailure: 1003, - ChatErrorCode.cantParseConnectionEvent: 1004, - ChatErrorCode.cantParseEvent: 1005, - ChatErrorCode.invalidToken: 1006, - ChatErrorCode.undefinedToken: 1007, - ChatErrorCode.unableToParseSocketEvent: 1008, - ChatErrorCode.noErrorBody: 1009, - - // server error codes - ChatErrorCode.authenticationError: 5, - ChatErrorCode.tokenExpired: 40, - ChatErrorCode.tokenNotValid: 41, - ChatErrorCode.tokenDateIncorrect: 42, - ChatErrorCode.tokenSignatureIncorrect: 43, - ChatErrorCode.apiKeyNotFound: 2, - }[this]!; + int get code => _errorCodeWithDescription[this]!.key; /// - Set get authenticationErrors => { - ChatErrorCode.authenticationError, - ChatErrorCode.tokenExpired, - ChatErrorCode.tokenNotValid, - ChatErrorCode.tokenDateIncorrect, - ChatErrorCode.tokenSignatureIncorrect, - }.map((it) => it.code).toSet(); - - /// - bool isAuthenticationError(int code) => authenticationErrors.contains(code); + bool get isAuthenticationError => _authenticationErrors.contains(this); } diff --git a/packages/stream_chat/lib/src/errors/stream_chat_error.dart b/packages/stream_chat/lib/src/errors/stream_chat_error.dart index 280ab6c7..a057783a 100644 --- a/packages/stream_chat/lib/src/errors/stream_chat_error.dart +++ b/packages/stream_chat/lib/src/errors/stream_chat_error.dart @@ -3,45 +3,74 @@ import 'package:stream_chat/src/errors/chat_error_code.dart'; import 'package:stream_chat/stream_chat.dart'; /// -class StreamChatError extends Equatable implements Exception { +class StreamChatError with EquatableMixin implements Exception { /// - StreamChatError(ChatErrorCode errorCode) - : code = errorCode.code, - message = errorCode.message; - - /// - const StreamChatError.raw(this.code, this.message); - - /// Error code - final int code; + const StreamChatError(this.message); /// Error message final String message; @override - List get props => [code, message]; + List get props => [message]; @override - String toString() => 'StreamChatError(code: $code, message: $message)'; + String toString() => 'StreamChatError(message: $message)'; +} + +/// +class StreamWebSocketError extends StreamChatError { + /// + const StreamWebSocketError( + String message, { + this.data, + }) : super(message); + + /// + factory StreamWebSocketError.fromStreamError(Map error) { + final data = ErrorResponse.fromJson(error); + final message = data.message ?? ''; + return StreamWebSocketError(message, data: data); + } + + /// + int? get code => data?.code; + + /// + ChatErrorCode? get errorCode { + final code = this.code; + if (code == null) return null; + return chatErrorCodeFromCode(code); + } + + /// Response body. please refer to [ErrorResponse]. + final ErrorResponse? data; + + @override + String toString() { + var params = 'message: $message'; + if (data != null) params += ', data: $data'; + return 'WebSocketError($params)'; + } } /// class StreamChatNetworkError extends StreamChatError { /// - StreamChatNetworkError({ - required ChatErrorCode errorCode, + StreamChatNetworkError( + ChatErrorCode errorCode, { int? statusCode, this.data, - }) : statusCode = statusCode ?? data?.statusCode, - super(errorCode); + }) : code = errorCode.code, + statusCode = statusCode ?? data?.statusCode, + super(errorCode.message); /// - const StreamChatNetworkError.raw({ - required int code, + StreamChatNetworkError.raw({ + required this.code, required String message, this.statusCode, this.data, - }) : super.raw(code, message); + }) : super(message); /// factory StreamChatNetworkError.fromDioError(DioError error) { @@ -53,25 +82,46 @@ class StreamChatNetworkError extends StreamChatError { } return StreamChatNetworkError.raw( code: errorResponse?.code ?? -1, - message: errorResponse?.message ?? response?.statusMessage ?? '', + message: + errorResponse?.message ?? response?.statusMessage ?? error.message, statusCode: errorResponse?.statusCode ?? response?.statusCode, data: errorResponse, - ); + )..stackTrace = error.stackTrace; } + /// Error code + final int code; + /// HTTP status code final int? statusCode; /// Response body. please refer to [ErrorResponse]. final ErrorResponse? data; - @override - List get props => [...super.props, statusCode]; + StackTrace? _stackTrace; + + /// + set stackTrace(StackTrace? stack) => _stackTrace = stack; + + /// + ChatErrorCode? get errorCode => chatErrorCodeFromCode(code); + + /// + bool get isRetriable => data == null; @override - String toString() => 'StreamChatNetworkError(' - 'code: $code, ' - 'message: $message, ' - 'statusCode: $statusCode, ' - 'data: $data)'; + List get props => [...super.props, code, statusCode]; + + @override + String toString({bool printStackTrace = false}) { + var params = 'code: $code, message: $message'; + if (statusCode != null) params += ', statusCode: $statusCode'; + if (data != null) params += ', data: $data'; + var msg = 'StreamChatNetworkError($params)'; + + if (printStackTrace && _stackTrace != null) { + msg += '\n$_stackTrace'; + } + return msg; + } } diff --git a/packages/stream_chat/lib/src/exceptions.dart b/packages/stream_chat/lib/src/exceptions.dart deleted file mode 100644 index 714bb386..00000000 --- a/packages/stream_chat/lib/src/exceptions.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'dart:convert'; - -/// Exception related to api calls -class ApiError extends Error { - /// Creates a new ApiError instance using the response body and status code - ApiError(this.body, this.status) : jsonData = _decode(body) { - if (jsonData != null && jsonData!.containsKey('code')) { - _code = jsonData!['code']; - } - } - - /// Raw body of the response - final String? body; - - /// Json parsed body - final Map? jsonData; - - /// Http status code of the response - final int? status; - - /// Stream specific error code - int? get code => _code; - int? _code; - - static Map? _decode(String? body) { - try { - if (body == null) { - return null; - } - return json.decode(body); - } on FormatException { - return null; - } - } - - @override - bool operator ==(Object other) => - identical(this, other) || - other is ApiError && - runtimeType == other.runtimeType && - body == other.body && - jsonData == other.jsonData && - status == other.status && - _code == other._code; - - @override - int get hashCode => - body.hashCode ^ jsonData.hashCode ^ status.hashCode ^ _code.hashCode; - - @override - String toString() => 'ApiError{body: $body, jsonData: $jsonData, ' - 'status: $status, code: $_code}'; -} diff --git a/packages/stream_chat/lib/src/ws/socket_error.dart b/packages/stream_chat/lib/src/ws/socket_error.dart deleted file mode 100644 index 00b052b9..00000000 --- a/packages/stream_chat/lib/src/ws/socket_error.dart +++ /dev/null @@ -1,25 +0,0 @@ -/// -class SocketError { - /// - const SocketError({ - this.code = -1, - this.statusCode = -1, - this.message = '', - }); - - /// - factory SocketError.fromJson(Map json) => SocketError( - code: json['code'], - statusCode: json['StatusCode'], - message: json['message'], - ); - - /// - final int code; - - /// - final int statusCode; - - /// - final String message; -} diff --git a/packages/stream_chat/lib/src/ws/websocket.dart b/packages/stream_chat/lib/src/ws/websocket.dart index 6ebb7a7b..6c36fd0b 100644 --- a/packages/stream_chat/lib/src/ws/websocket.dart +++ b/packages/stream_chat/lib/src/ws/websocket.dart @@ -5,13 +5,14 @@ import 'dart:math' as math; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:rxdart/rxdart.dart'; +import 'package:stream_chat/src/errors/chat_error_code.dart'; +import 'package:stream_chat/src/errors/stream_chat_error.dart'; import 'package:stream_chat/src/ws/connection_status.dart'; import 'package:stream_chat/src/core/http/token.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; import 'package:stream_chat/src/core/models/event.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/src/event_type.dart'; -import 'package:stream_chat/src/ws/socket_error.dart'; import 'package:stream_chat/src/ws/timer_helper.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/status.dart' as status; @@ -27,8 +28,6 @@ typedef WebSocketChannelProvider = WebSocketChannel Function( Iterable? protocols, }); -const _tokenExpiredErrorCode = 40; - /// A WebSocket connection that reconnects upon failure. class WebSocket with TimerHelper { /// Creates a new websocket @@ -174,7 +173,7 @@ class WebSocket with TimerHelper { /// Connect the WS using the parameters passed in the constructor Future connect(User user) async { if (_connectRequestInProgress) { - throw Exception(''' + throw const StreamWebSocketError(''' You've called connect twice, can only attempt 1 connection at the time, '''); @@ -195,7 +194,7 @@ class WebSocket with TimerHelper { int _reconnectAttempt = 0; bool _reconnectRequestInProgress = false; - Future _reconnect({bool refreshToken = false}) async { + void _reconnect({bool refreshToken = false}) async { _logger?.info('Retrying connection : $_reconnectAttempt'); if (_reconnectRequestInProgress) return; _reconnectRequestInProgress = true; @@ -270,10 +269,35 @@ class WebSocket with TimerHelper { _connectionStatus = ConnectionStatus.connected; } + void _handleStreamError(Map errorResponse) { + // resetting connect, reconnect request flag + _resetRequestFlags(); + + final error = StreamWebSocketError.fromStreamError(errorResponse); + final isTokenExpired = error.errorCode == ChatErrorCode.tokenExpired; + if (isTokenExpired && !tokenManager.isStatic) { + _logger?.warning('Connection failed, token expired'); + return _reconnect(refreshToken: true); + } + + _logger?.severe('Connection failed', error); + + final completer = connectionCompleter; + // complete with error if not yet completed + if (completer != null && !completer.isCompleted) { + // complete the connection with error + completer.completeError(error); + // disconnect the web-socket connection + return disconnect(); + } + + return _reconnect(); + } + void _onDataReceived(dynamic data) { - final jsonData = json.decode(data); - final error = jsonData['error']; - if (error != null) return _onConnectionError(error); + final jsonData = json.decode(data) as Map; + final error = jsonData['error'] as Map?; + if (error != null) return _handleStreamError(error); // resetting connect, reconnect request flag _resetRequestFlags(resetAttempts: true); @@ -300,25 +324,18 @@ class WebSocket with TimerHelper { } void _onConnectionError(error, [stacktrace]) { - _logger?.severe('Error occurred', error, stacktrace); + _logger?.warning('Error occurred', error, stacktrace); // resetting connect, reconnect request flag _resetRequestFlags(); - var refreshToken = false; - try { - final socketError = SocketError.fromJson(json.decode(error)); - refreshToken = socketError.code == _tokenExpiredErrorCode; - } catch (_) {} - - // refresh token in case it is expired - _reconnect(refreshToken: refreshToken); + _reconnect(); } bool _manuallyClosed = false; void _onConnectionClosed() { - _logger?.info('Connection closed : $connectionId'); + _logger?.warning('Connection closed : $connectionId'); // resetting connect, reconnect request flag _resetRequestFlags(); @@ -381,7 +398,7 @@ class WebSocket with TimerHelper { if (connectionStatus == ConnectionStatus.disconnected) return; _connectionStatus = ConnectionStatus.disconnected; - _logger?.info('Disconnecting $connectionId'); + _logger?.info('Disconnecting web-socket connection'); // resetting user _user = null; diff --git a/packages/stream_chat/lib/stream_chat.dart b/packages/stream_chat/lib/stream_chat.dart index 991e721b..66dace1d 100644 --- a/packages/stream_chat/lib/stream_chat.dart +++ b/packages/stream_chat/lib/stream_chat.dart @@ -32,8 +32,9 @@ export './src/core/models/reaction.dart'; export './src/core/models/read.dart'; export './src/core/models/user.dart'; export './src/db/chat_persistence_client.dart'; +export './src/errors/chat_error_code.dart'; +export './src/errors/stream_chat_error.dart'; export './src/event_type.dart'; -export './src/exceptions.dart'; export './src/extensions/rate_limit.dart'; export './src/extensions/string_extension.dart'; export './src/location.dart'; diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 8da187a4..13506f66 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -8,7 +8,6 @@ import 'package:logging/logging.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/src/core/api/requests.dart'; import 'package:stream_chat/src/client.dart'; -import 'package:stream_chat/src/exceptions.dart'; import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:stream_chat/src/core/models/filter.dart'; import 'package:stream_chat/src/core/models/message.dart'; 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 5336ed48..e40cfc21 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -341,7 +341,8 @@ class _MessageActionsModalState extends State { okText: 'OK', ); } catch (err) { - if (err is ApiError && json.decode(err.body ?? '{}')['code'] == 4) { + if (err is StreamChatNetworkError && + err.errorCode == ChatErrorCode.inputError) { await showInfoDialog( context, icon: StreamSvgIcon.flag( 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 efc8610b..447533e0 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart @@ -51,7 +51,7 @@ class MoorChatDatabase extends _$MoorChatDatabase { // you should bump this number whenever you change or add a table definition. @override - int get schemaVersion => 3; + int get schemaVersion => 4; @override MigrationStrategy get migration => MigrationStrategy( From d758f3df1d5b0f79b3c7e6de0ba7a2c509558a5b Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 3 Jun 2021 13:38:32 +0530 Subject: [PATCH 323/599] change file structure, replace `rate_limit` with the pub dependency Signed-off-by: Sahil Kumar --- .../lib/src/{api => client}/channel.dart | 9 +- .../lib/src/{ => client}/client.dart | 9 +- .../lib/src/{api => client}/retry_policy.dart | 4 +- .../lib/src/{api => client}/retry_queue.dart | 6 +- .../core/api/attachment_file_uploader.dart | 2 +- .../lib/src/core/api/responses.dart | 4 +- .../error}/chat_error_code.dart | 0 .../stream_chat/lib/src/core/error/error.dart | 2 + .../error}/stream_chat_error.dart | 2 +- .../http/interceptor/auth_interceptor.dart | 4 +- .../src/core/http/stream_chat_dio_error.dart | 2 +- .../lib/src/core/http/stream_http_client.dart | 4 +- .../stream_chat/lib/src/core/http/token.dart | 2 +- .../lib/src/core/models/attachment.dart | 2 +- .../lib/src/core/models/channel_model.dart | 2 +- .../lib/src/core/models/event.dart | 2 +- .../lib/src/core/models/message.dart | 4 +- .../lib/src/core/models/message.g.dart | 2 +- .../stream_chat/lib/src/core/models/mute.dart | 2 +- .../lib/src/core/models/own_user.dart | 2 +- .../lib/src/core/models/reaction.dart | 2 +- .../stream_chat/lib/src/core/models/user.dart | 6 +- .../platform_detector/platform_detector.dart | 2 +- .../platform_detector_io.dart | 2 +- .../platform_detector_stub.dart | 2 +- .../platform_detector_web.dart | 2 +- .../lib/src/core/util/extension.dart | 30 ++ .../core/{models => util}/serialization.dart | 6 - .../lib/src/core/{ => util}/utils.dart | 4 +- .../lib/src/db/chat_persistence_client.dart | 2 +- .../src/extensions/iterable_extension.dart | 9 - .../lib/src/extensions/map_extension.dart | 6 - .../lib/src/extensions/rate_limit.dart | 335 ------------------ .../lib/src/extensions/string_extension.dart | 18 - .../stream_chat/lib/src/ws/websocket.dart | 3 +- packages/stream_chat/lib/stream_chat.dart | 11 +- packages/stream_chat/lib/version.dart | 2 +- packages/stream_chat/pubspec.yaml | 1 + packages/stream_chat/test/version_test.dart | 65 ++-- 39 files changed, 126 insertions(+), 448 deletions(-) rename packages/stream_chat/lib/src/{api => client}/channel.dart (99%) rename packages/stream_chat/lib/src/{ => client}/client.dart (99%) rename packages/stream_chat/lib/src/{api => client}/retry_policy.dart (87%) rename packages/stream_chat/lib/src/{api => client}/retry_queue.dart (97%) rename packages/stream_chat/lib/src/{errors => core/error}/chat_error_code.dart (100%) create mode 100644 packages/stream_chat/lib/src/core/error/error.dart rename packages/stream_chat/lib/src/{errors => core/error}/stream_chat_error.dart (97%) rename packages/stream_chat/lib/src/{ => core}/platform_detector/platform_detector.dart (95%) rename packages/stream_chat/lib/src/{ => core}/platform_detector/platform_detector_io.dart (82%) rename packages/stream_chat/lib/src/{ => core}/platform_detector/platform_detector_stub.dart (53%) rename packages/stream_chat/lib/src/{ => core}/platform_detector/platform_detector_web.dart (50%) create mode 100644 packages/stream_chat/lib/src/core/util/extension.dart rename packages/stream_chat/lib/src/core/{models => util}/serialization.dart (86%) rename packages/stream_chat/lib/src/core/{ => util}/utils.dart (82%) delete mode 100644 packages/stream_chat/lib/src/extensions/iterable_extension.dart delete mode 100644 packages/stream_chat/lib/src/extensions/map_extension.dart delete mode 100644 packages/stream_chat/lib/src/extensions/rate_limit.dart delete mode 100644 packages/stream_chat/lib/src/extensions/string_extension.dart diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/client/channel.dart similarity index 99% rename from packages/stream_chat/lib/src/api/channel.dart rename to packages/stream_chat/lib/src/client/channel.dart index 3fb63c91..5bc59ae8 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -4,13 +4,12 @@ import 'dart:math'; import 'package:collection/collection.dart' show IterableExtension, ListEquality; import 'package:dio/dio.dart'; -import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat/src/api/retry_queue.dart'; -import 'package:stream_chat/src/core/utils.dart'; -import 'package:stream_chat/src/errors/stream_chat_error.dart'; +import 'package:stream_chat/src/client/retry_queue.dart'; +import 'package:stream_chat/src/core/util/utils.dart'; +import 'package:stream_chat/src/core/error/error.dart'; import 'package:stream_chat/src/event_type.dart'; -import 'package:stream_chat/src/extensions/rate_limit.dart'; +import 'package:rate_limiter/rate_limiter.dart'; import 'package:stream_chat/src/core/models/attachment_file.dart'; import 'package:stream_chat/src/core/models/channel_state.dart'; import 'package:stream_chat/src/core/models/user.dart'; diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client/client.dart similarity index 99% rename from packages/stream_chat/lib/src/client.dart rename to packages/stream_chat/lib/src/client/client.dart index e7e05b69..ddc6e335 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1,20 +1,19 @@ // ignore_for_file: unnecessary_getters_setters import 'dart:async'; -import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat/src/api/channel.dart'; -import 'package:stream_chat/src/core/utils.dart'; -import 'package:stream_chat/src/errors/stream_chat_error.dart'; +import 'package:stream_chat/src/client/channel.dart'; +import 'package:stream_chat/src/core/util/utils.dart'; +import 'package:stream_chat/src/core/error/error.dart'; import 'package:stream_chat/src/location.dart'; import 'package:stream_chat/src/ws/connection_status.dart'; import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; import 'package:stream_chat/src/core/api/requests.dart'; import 'package:stream_chat/src/core/api/responses.dart'; -import 'package:stream_chat/src/api/retry_policy.dart'; +import 'package:stream_chat/src/client/retry_policy.dart'; import 'package:stream_chat/src/ws/websocket.dart'; import 'package:stream_chat/src/core/api/stream_chat_api.dart'; import 'package:stream_chat/src/core/http/connection_id_manager.dart'; diff --git a/packages/stream_chat/lib/src/api/retry_policy.dart b/packages/stream_chat/lib/src/client/retry_policy.dart similarity index 87% rename from packages/stream_chat/lib/src/api/retry_policy.dart rename to packages/stream_chat/lib/src/client/retry_policy.dart index 14933fed..fbebc696 100644 --- a/packages/stream_chat/lib/src/api/retry_policy.dart +++ b/packages/stream_chat/lib/src/client/retry_policy.dart @@ -1,5 +1,5 @@ -import 'package:stream_chat/src/client.dart'; -import 'package:stream_chat/src/errors/stream_chat_error.dart'; +import 'package:stream_chat/src/client/client.dart'; +import 'package:stream_chat/src/core/error/error.dart'; /// The retry options class RetryPolicy { diff --git a/packages/stream_chat/lib/src/api/retry_queue.dart b/packages/stream_chat/lib/src/client/retry_queue.dart similarity index 97% rename from packages/stream_chat/lib/src/api/retry_queue.dart rename to packages/stream_chat/lib/src/client/retry_queue.dart index 9d0b8502..008270e5 100644 --- a/packages/stream_chat/lib/src/api/retry_queue.dart +++ b/packages/stream_chat/lib/src/client/retry_queue.dart @@ -3,9 +3,9 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat/src/api/channel.dart'; -import 'package:stream_chat/src/api/retry_policy.dart'; -import 'package:stream_chat/src/errors/stream_chat_error.dart'; +import 'package:stream_chat/src/client/channel.dart'; +import 'package:stream_chat/src/client/retry_policy.dart'; +import 'package:stream_chat/src/core/error/error.dart'; import 'package:stream_chat/src/event_type.dart'; import 'package:stream_chat/src/core/models/message.dart'; import 'package:stream_chat/stream_chat.dart'; diff --git a/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart b/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart index 272188af..8eacf716 100644 --- a/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart +++ b/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart @@ -1,7 +1,7 @@ import 'package:dio/dio.dart'; import 'package:stream_chat/src/core/api/responses.dart'; import 'package:stream_chat/src/core/http/stream_http_client.dart'; -import 'package:stream_chat/src/extensions/string_extension.dart'; +import 'package:stream_chat/src/core/util/extension.dart'; import 'package:stream_chat/src/core/models/attachment_file.dart'; /// Class responsible for uploading images and files to a given channel diff --git a/packages/stream_chat/lib/src/core/api/responses.dart b/packages/stream_chat/lib/src/core/api/responses.dart index c639a960..b6d6e801 100644 --- a/packages/stream_chat/lib/src/core/api/responses.dart +++ b/packages/stream_chat/lib/src/core/api/responses.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/client.dart'; +import 'package:stream_chat/src/client/client.dart'; import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:stream_chat/src/core/models/channel_state.dart'; import 'package:stream_chat/src/core/models/device.dart'; @@ -9,7 +9,7 @@ import 'package:stream_chat/src/core/models/message.dart'; import 'package:stream_chat/src/core/models/reaction.dart'; import 'package:stream_chat/src/core/models/read.dart'; import 'package:stream_chat/src/core/models/user.dart'; -import 'package:stream_chat/src/errors/stream_chat_error.dart'; +import 'package:stream_chat/src/core/error/error.dart'; part 'responses.g.dart'; diff --git a/packages/stream_chat/lib/src/errors/chat_error_code.dart b/packages/stream_chat/lib/src/core/error/chat_error_code.dart similarity index 100% rename from packages/stream_chat/lib/src/errors/chat_error_code.dart rename to packages/stream_chat/lib/src/core/error/chat_error_code.dart diff --git a/packages/stream_chat/lib/src/core/error/error.dart b/packages/stream_chat/lib/src/core/error/error.dart new file mode 100644 index 00000000..6f2c39a3 --- /dev/null +++ b/packages/stream_chat/lib/src/core/error/error.dart @@ -0,0 +1,2 @@ +export 'chat_error_code.dart'; +export 'stream_chat_error.dart'; \ No newline at end of file diff --git a/packages/stream_chat/lib/src/errors/stream_chat_error.dart b/packages/stream_chat/lib/src/core/error/stream_chat_error.dart similarity index 97% rename from packages/stream_chat/lib/src/errors/stream_chat_error.dart rename to packages/stream_chat/lib/src/core/error/stream_chat_error.dart index a057783a..04214033 100644 --- a/packages/stream_chat/lib/src/errors/stream_chat_error.dart +++ b/packages/stream_chat/lib/src/core/error/stream_chat_error.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:stream_chat/src/errors/chat_error_code.dart'; +import 'package:stream_chat/src/core/error/chat_error_code.dart'; import 'package:stream_chat/stream_chat.dart'; /// diff --git a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart index 9dafc6c9..d689cce1 100644 --- a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart +++ b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart @@ -5,8 +5,8 @@ import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/http/token.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; -import 'package:stream_chat/src/errors/chat_error_code.dart'; -import 'package:stream_chat/src/errors/stream_chat_error.dart'; +import 'package:stream_chat/src/core/error/error.dart'; + /// class AuthInterceptor extends Interceptor { diff --git a/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart b/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart index f0e61111..987c9d67 100644 --- a/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart +++ b/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart @@ -1,5 +1,5 @@ import 'package:dio/dio.dart'; -import 'package:stream_chat/src/errors/stream_chat_error.dart'; +import 'package:stream_chat/src/core/error/error.dart'; /// class StreamChatDioError extends DioError { diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client.dart b/packages/stream_chat/lib/src/core/http/stream_http_client.dart index 0daeaacd..3eacd11e 100644 --- a/packages/stream_chat/lib/src/core/http/stream_http_client.dart +++ b/packages/stream_chat/lib/src/core/http/stream_http_client.dart @@ -8,9 +8,9 @@ import 'package:stream_chat/src/core/http/interceptor/connection_id_interceptor. import 'package:stream_chat/src/core/http/interceptor/logging_interceptor.dart'; import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; -import 'package:stream_chat/src/errors/stream_chat_error.dart'; +import 'package:stream_chat/src/core/error/error.dart'; import 'package:stream_chat/src/location.dart'; -import 'package:stream_chat/src/platform_detector/platform_detector.dart'; +import 'package:stream_chat/src/core/platform_detector/platform_detector.dart'; import 'package:stream_chat/version.dart'; import 'package:stream_chat/src/core/http/connection_id_manager.dart'; diff --git a/packages/stream_chat/lib/src/core/http/token.dart b/packages/stream_chat/lib/src/core/http/token.dart index a914521c..26532e80 100644 --- a/packages/stream_chat/lib/src/core/http/token.dart +++ b/packages/stream_chat/lib/src/core/http/token.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:equatable/equatable.dart'; import 'package:jose/jose.dart'; import 'package:stream_chat/src/core/models/user.dart'; -import 'package:stream_chat/src/core/utils.dart'; +import 'package:stream_chat/src/core/util/utils.dart'; /// typedef GuestTokenProvider = Future Function(User user); diff --git a/packages/stream_chat/lib/src/core/models/attachment.dart b/packages/stream_chat/lib/src/core/models/attachment.dart index 1df785a8..a5f9f19d 100644 --- a/packages/stream_chat/lib/src/core/models/attachment.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.dart @@ -4,7 +4,7 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/action.dart'; import 'package:stream_chat/src/core/models/attachment_file.dart'; -import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/util/serialization.dart'; import 'package:uuid/uuid.dart'; part 'attachment.g.dart'; 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 892713e7..9e50ed3e 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.dart @@ -1,6 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/channel_config.dart'; -import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/util/serialization.dart'; import 'package:stream_chat/src/core/models/user.dart'; part 'channel_model.g.dart'; diff --git a/packages/stream_chat/lib/src/core/models/event.dart b/packages/stream_chat/lib/src/core/models/event.dart index 6ed1d414..7c1be679 100644 --- a/packages/stream_chat/lib/src/core/models/event.dart +++ b/packages/stream_chat/lib/src/core/models/event.dart @@ -1,7 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:stream_chat/src/core/models/message.dart'; -import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/util/serialization.dart'; import 'package:stream_chat/stream_chat.dart'; part 'event.g.dart'; diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index f3bf100f..1266519c 100644 --- a/packages/stream_chat/lib/src/core/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -2,7 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/attachment.dart'; import 'package:stream_chat/src/core/models/reaction.dart'; -import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/util/serialization.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:uuid/uuid.dart'; @@ -112,7 +112,7 @@ class Message extends Equatable { /// The list of user mentioned in the message @JsonKey( - toJson: Serialization.userIds, + toJson: User.toIds, defaultValue: [], ) final List mentionedUsers; diff --git a/packages/stream_chat/lib/src/core/models/message.g.dart b/packages/stream_chat/lib/src/core/models/message.g.dart index ca094a83..7d268d0e 100644 --- a/packages/stream_chat/lib/src/core/models/message.g.dart +++ b/packages/stream_chat/lib/src/core/models/message.g.dart @@ -85,7 +85,7 @@ Map _$MessageToJson(Message instance) { writeNotNull('type', readonly(instance.type)); val['attachments'] = instance.attachments.map((e) => e.toJson()).toList(); - val['mentioned_users'] = Serialization.userIds(instance.mentionedUsers); + val['mentioned_users'] = User.toIds(instance.mentionedUsers); writeNotNull('reaction_counts', readonly(instance.reactionCounts)); writeNotNull('reaction_scores', readonly(instance.reactionScores)); writeNotNull('latest_reactions', readonly(instance.latestReactions)); diff --git a/packages/stream_chat/lib/src/core/models/mute.dart b/packages/stream_chat/lib/src/core/models/mute.dart index 8c3bef89..7a82fc54 100644 --- a/packages/stream_chat/lib/src/core/models/mute.dart +++ b/packages/stream_chat/lib/src/core/models/mute.dart @@ -1,6 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/channel_model.dart'; -import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/util/serialization.dart'; import 'package:stream_chat/src/core/models/user.dart'; part 'mute.g.dart'; diff --git a/packages/stream_chat/lib/src/core/models/own_user.dart b/packages/stream_chat/lib/src/core/models/own_user.dart index 6f975a94..b9cdf0fd 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -1,7 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/device.dart'; import 'package:stream_chat/src/core/models/mute.dart'; -import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/util/serialization.dart'; import 'package:stream_chat/src/core/models/user.dart'; part 'own_user.g.dart'; diff --git a/packages/stream_chat/lib/src/core/models/reaction.dart b/packages/stream_chat/lib/src/core/models/reaction.dart index ace4a15e..997d833c 100644 --- a/packages/stream_chat/lib/src/core/models/reaction.dart +++ b/packages/stream_chat/lib/src/core/models/reaction.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/util/serialization.dart'; import 'package:stream_chat/src/core/models/user.dart'; part 'reaction.g.dart'; diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index ae1dd742..b5fdec69 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/core/models/serialization.dart'; +import 'package:stream_chat/src/core/util/serialization.dart'; part 'user.g.dart'; @@ -92,6 +92,10 @@ class User { return id; } + /// List of users to list of userIds + static List? toIds(List? users) => + users?.map((u) => u.id).toList(); + @override bool operator ==(Object other) => identical(this, other) || diff --git a/packages/stream_chat/lib/src/platform_detector/platform_detector.dart b/packages/stream_chat/lib/src/core/platform_detector/platform_detector.dart similarity index 95% rename from packages/stream_chat/lib/src/platform_detector/platform_detector.dart rename to packages/stream_chat/lib/src/core/platform_detector/platform_detector.dart index 58d86db4..cf817cac 100644 --- a/packages/stream_chat/lib/src/platform_detector/platform_detector.dart +++ b/packages/stream_chat/lib/src/core/platform_detector/platform_detector.dart @@ -1,4 +1,4 @@ -import 'package:stream_chat/src/platform_detector/platform_detector_stub.dart' +import 'package:stream_chat/src/core/platform_detector/platform_detector_stub.dart' if (dart.library.html) 'platform_detector_web.dart' if (dart.library.io) 'platform_detector_io.dart'; diff --git a/packages/stream_chat/lib/src/platform_detector/platform_detector_io.dart b/packages/stream_chat/lib/src/core/platform_detector/platform_detector_io.dart similarity index 82% rename from packages/stream_chat/lib/src/platform_detector/platform_detector_io.dart rename to packages/stream_chat/lib/src/core/platform_detector/platform_detector_io.dart index c7b4a0b7..da707eed 100644 --- a/packages/stream_chat/lib/src/platform_detector/platform_detector_io.dart +++ b/packages/stream_chat/lib/src/core/platform_detector/platform_detector_io.dart @@ -1,5 +1,5 @@ import 'dart:io'; -import 'package:stream_chat/src/platform_detector/platform_detector.dart'; +import 'package:stream_chat/src/core/platform_detector/platform_detector.dart'; /// Version running on native systems PlatformType get currentPlatform { diff --git a/packages/stream_chat/lib/src/platform_detector/platform_detector_stub.dart b/packages/stream_chat/lib/src/core/platform_detector/platform_detector_stub.dart similarity index 53% rename from packages/stream_chat/lib/src/platform_detector/platform_detector_stub.dart rename to packages/stream_chat/lib/src/core/platform_detector/platform_detector_stub.dart index b9e13c2e..9d1a7f66 100644 --- a/packages/stream_chat/lib/src/platform_detector/platform_detector_stub.dart +++ b/packages/stream_chat/lib/src/core/platform_detector/platform_detector_stub.dart @@ -1,4 +1,4 @@ -import 'package:stream_chat/src/platform_detector/platform_detector.dart'; +import 'package:stream_chat/src/core/platform_detector/platform_detector.dart'; /// Stub implementation PlatformType get currentPlatform { diff --git a/packages/stream_chat/lib/src/platform_detector/platform_detector_web.dart b/packages/stream_chat/lib/src/core/platform_detector/platform_detector_web.dart similarity index 50% rename from packages/stream_chat/lib/src/platform_detector/platform_detector_web.dart rename to packages/stream_chat/lib/src/core/platform_detector/platform_detector_web.dart index ba5d04fc..324b4145 100644 --- a/packages/stream_chat/lib/src/platform_detector/platform_detector_web.dart +++ b/packages/stream_chat/lib/src/core/platform_detector/platform_detector_web.dart @@ -1,4 +1,4 @@ -import 'package:stream_chat/src/platform_detector/platform_detector.dart'; +import 'package:stream_chat/src/core/platform_detector/platform_detector.dart'; /// Version running on web PlatformType get currentPlatform => PlatformType.web; diff --git a/packages/stream_chat/lib/src/core/util/extension.dart b/packages/stream_chat/lib/src/core/util/extension.dart new file mode 100644 index 00000000..5dd8ea15 --- /dev/null +++ b/packages/stream_chat/lib/src/core/util/extension.dart @@ -0,0 +1,30 @@ +import 'package:http_parser/http_parser.dart'; +import 'package:mime/mime.dart'; + +/// Useful extension functions for [Iterable] +extension IterableX on Iterable { + /// Removes all the null values + /// and converts `Iterable` into `Iterable` + Iterable get withNullifyer => whereType(); +} + +/// Useful extension functions for [Map] +extension MapX on Map { + /// Returns a new map with null keys or values removed + Map get nullProtected => + Map.from(this)..removeWhere((key, value) => key == null || value == null); +} + +/// Useful extension functions for [String] +extension StringX on String { + /// returns the mime type from the passed file name. + MediaType? get mimeType { + if (toLowerCase().endsWith('heic')) { + return MediaType.parse('image/heic'); + } else { + final mimeType = lookupMimeType(this); + if (mimeType == null) return null; + return MediaType.parse(mimeType); + } + } +} diff --git a/packages/stream_chat/lib/src/core/models/serialization.dart b/packages/stream_chat/lib/src/core/util/serialization.dart similarity index 86% rename from packages/stream_chat/lib/src/core/models/serialization.dart rename to packages/stream_chat/lib/src/core/util/serialization.dart index 692ad01a..1e933a32 100644 --- a/packages/stream_chat/lib/src/core/models/serialization.dart +++ b/packages/stream_chat/lib/src/core/util/serialization.dart @@ -1,5 +1,3 @@ -import 'package:stream_chat/src/core/models/user.dart'; - /// Used to avoid to serialize properties to json // ignore: prefer_void_to_null Null readonly(_) => null; @@ -9,10 +7,6 @@ class Serialization { /// Used to avoid to serialize properties to json static const Function readOnly = readonly; - /// List of users to list of userIds - static List? userIds(List? users) => - users?.map((u) => u.id).toList(); - /// Takes unknown json keys and puts them in the `extra_data` key static Map moveToExtraDataFromRoot( Map json, diff --git a/packages/stream_chat/lib/src/core/utils.dart b/packages/stream_chat/lib/src/core/util/utils.dart similarity index 82% rename from packages/stream_chat/lib/src/core/utils.dart rename to packages/stream_chat/lib/src/core/util/utils.dart index c2d77927..c2204991 100644 --- a/packages/stream_chat/lib/src/core/utils.dart +++ b/packages/stream_chat/lib/src/core/util/utils.dart @@ -4,14 +4,14 @@ import 'dart:math' as math; // This alphabet uses `A-Za-z0-9_-` symbols. The genetic algorithm helped // optimize the gzip compression for this alphabet. const _alphabet = - 'ModuleSymbhasOwnPr0123456789ABCDEFGHNRVfgctiUvzKqYTJkLxpZXIjQW'; + 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW'; /// Generates a random String id /// Adopted from: https://github.com/ai/nanoid/blob/main/non-secure/index.js String randomId({int size = 21}) { var id = ''; for (var i = 0; i < size; i++) { - id += _alphabet[(math.Random().nextInt(32) * 64) | 0]; + id += _alphabet[(math.Random().nextDouble() * 64).floor() | 0]; } return id; } diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index d7e0f765..2470e403 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -8,7 +8,7 @@ import 'package:stream_chat/src/core/models/message.dart'; import 'package:stream_chat/src/core/models/reaction.dart'; import 'package:stream_chat/src/core/models/read.dart'; import 'package:stream_chat/src/core/models/user.dart'; -import 'package:stream_chat/src/extensions/iterable_extension.dart'; +import 'package:stream_chat/src/core/util/extension.dart'; /// A simple client used for persisting chat data locally. abstract class ChatPersistenceClient { diff --git a/packages/stream_chat/lib/src/extensions/iterable_extension.dart b/packages/stream_chat/lib/src/extensions/iterable_extension.dart deleted file mode 100644 index a7d30f76..00000000 --- a/packages/stream_chat/lib/src/extensions/iterable_extension.dart +++ /dev/null @@ -1,9 +0,0 @@ -/// Useful extension functions for [Iterable] -extension IterableX on Iterable { - /// Removes all the null values - /// and converts `Iterable` into `Iterable` - Iterable get withNullifyer => [ - for (final item in this) - if (item != null) item - ]; -} diff --git a/packages/stream_chat/lib/src/extensions/map_extension.dart b/packages/stream_chat/lib/src/extensions/map_extension.dart deleted file mode 100644 index d99ab96a..00000000 --- a/packages/stream_chat/lib/src/extensions/map_extension.dart +++ /dev/null @@ -1,6 +0,0 @@ -/// Useful extension functions for [Map] -extension MapX on Map { - /// Returns a new map with null keys or values removed - Map get nullProtected => - Map.from(this)..removeWhere((key, value) => key == null || value == null); -} diff --git a/packages/stream_chat/lib/src/extensions/rate_limit.dart b/packages/stream_chat/lib/src/extensions/rate_limit.dart deleted file mode 100644 index bf54fe5c..00000000 --- a/packages/stream_chat/lib/src/extensions/rate_limit.dart +++ /dev/null @@ -1,335 +0,0 @@ -// ignore_for_file: lines_longer_than_80_chars - -import 'dart:async' show Timer; -import 'dart:math' as math; - -/// Useful rate limiter extensions for [Function] class. -extension RateLimit on Function { - /// Converts this into a [Debounce] function. - Debounce debounced( - Duration wait, { - bool leading = false, - bool trailing = true, - Duration? maxWait, - }) => - Debounce( - this, - wait, - leading: leading, - trailing: trailing, - maxWait: maxWait, - ); - - /// Converts this into a [Throttle] function. - Throttle throttled( - Duration wait, { - bool leading = true, - bool trailing = true, - }) => - Throttle( - this, - wait, - leading: leading, - trailing: trailing, - ); -} - -/// TopLevel lambda to create [Debounce] functions. -Debounce debounce( - Function func, - Duration wait, { - bool leading = false, - bool trailing = true, - Duration? maxWait, -}) => - Debounce( - func, - wait, - leading: leading, - trailing: trailing, - maxWait: maxWait, - ); - -/// TopLevel lambda to create [Throttle] functions. -Throttle throttle( - Function func, - Duration wait, { - bool leading = true, - bool trailing = true, -}) => - Throttle( - func, - wait, - leading: leading, - trailing: trailing, - ); - -/// Creates a debounced function that delays invoking `func` until after `wait` -/// milliseconds have elapsed since the last time the debounced function was -/// invoked. The debounced function comes with a [Debounce.cancel] method to cancel -/// delayed `func` invocations and a [Debounce.flush] method to immediately invoke them. -/// Provide `leading` and/or `trailing` to indicate whether `func` should be -/// invoked on the `leading` and/or `trailing` edge of the `wait` interval. -/// The `func` is invoked with the last arguments provided to the [call] -/// function. Subsequent calls to the debounced function return the result of -/// the last `func` invocation. -/// -/// **Note:** If `leading` and `trailing` options are `true`, `func` is -/// invoked on the trailing edge of the timeout only if the debounced function -/// is invoked more than once during the `wait` timeout. -/// -/// If `wait` is [Duration.zero] and `leading` is `false`, -/// `func` invocation is deferred until the next tick. -/// -/// See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) -/// for details over the differences between [Debounce] and [Throttle]. -/// -/// Some examples: -/// -/// Avoid calling costly network calls when user is typing something. -/// ```dart -/// void fetchData(String query) async { -/// final data = api.getData(query); -/// doSomethingWithTheData(data); -/// } -/// -/// final debouncedFetchData = Debounce( -/// fetchData, -/// const Duration(milliseconds: 350), -/// ); -/// -/// void onSearchQueryChanged(query) { -/// debouncedFetchData(query); -/// } -/// ``` -/// -/// Cancel the trailing debounced invocation. -/// ```dart -/// void dispose() { -/// debounced.cancel(); -/// } -/// ``` -/// -/// Check for pending invocations. -/// ```dart -/// final status = debounced.isPending ? "Pending..." : "Ready"; -/// ``` -class Debounce { - /// Creates a new instance of [Debounce]. - Debounce( - this._func, - Duration wait, { - bool leading = false, - bool trailing = true, - Duration? maxWait, - }) : _leading = leading, - _trailing = trailing, - _wait = wait.inMilliseconds, - _maxing = maxWait != null { - if (_maxing) { - _maxWait = math.max(maxWait!.inMilliseconds, _wait); - } - } - - final Function _func; - final bool _leading; - final bool _trailing; - final int _wait; - final bool _maxing; - - late int _maxWait; - List? _lastArgs; - Map? _lastNamedArgs; - Timer? _timer; - int? _lastCallTime; - Object? _result; - int? _lastInvokeTime = 0; - - Object? _invokeFunc(int? time) { - final args = _lastArgs; - final namedArgs = _lastNamedArgs; - _lastArgs = _lastNamedArgs = null; - _lastInvokeTime = time; - return _result = Function.apply(_func, args, namedArgs); - } - - Timer _startTimer(Function pendingFunc, int wait) => - Timer(Duration(milliseconds: wait), pendingFunc as void Function()); - - bool _shouldInvoke(int time) { - final timeSinceLastCall = time - (_lastCallTime ?? double.nan); - final timeSinceLastInvoke = time - _lastInvokeTime!; - - // Either this is the first call, activity has stopped and we're at the - // trailing edge, the system time has gone backwards and we're treating - // it as the trailing edge, or we've hit the `maxWait` limit. - return _lastCallTime == null || - (timeSinceLastCall >= _wait) || - (timeSinceLastCall < 0) || - (_maxing && timeSinceLastInvoke >= _maxWait); - } - - Object? _trailingEdge(int time) { - _timer = null; - - // Only invoke if we have `lastArgs` which means `func` has been - // debounced at least once. - if (_trailing && _lastArgs != null) { - return _invokeFunc(time); - } - _lastArgs = _lastNamedArgs = null; - return _result; - } - - int _remainingWait(int time) { - final timeSinceLastCall = time - _lastCallTime!; - final timeSinceLastInvoke = time - _lastInvokeTime!; - final timeWaiting = _wait - timeSinceLastCall; - - return _maxing - ? math.min(timeWaiting, _maxWait - timeSinceLastInvoke) - : timeWaiting; - } - - void _timerExpired() { - final time = DateTime.now().millisecondsSinceEpoch; - if (_shouldInvoke(time)) { - _trailingEdge(time); - } else { - // Restart the timer. - _timer = _startTimer(_timerExpired, _remainingWait(time)); - } - } - - Object? _leadingEdge(int? time) { - // Reset any `maxWait` timer. - _lastInvokeTime = time; - // Start the timer for the trailing edge. - _timer = _startTimer(_timerExpired, _wait); - // Invoke the leading edge. - return _leading ? _invokeFunc(time) : _result; - } - - /// Cancels all the remaining delayed functions. - void cancel() { - _timer?.cancel(); - _lastInvokeTime = 0; - _lastArgs = _lastNamedArgs = _lastCallTime = _timer = null; - } - - /// Immediately invokes all the remaining delayed functions. - Object? flush() { - final now = DateTime.now().millisecondsSinceEpoch; - return _timer == null ? _result : _trailingEdge(now); - } - - /// True if there are functions remaining to get invoked. - bool get isPending => _timer != null; - - /// Calls/invokes this class like a function. - /// Pass [args] and [namedArgs] to be used while invoking [_func]. - Object? call( - List args, { - Map? namedArgs, - }) { - final time = DateTime.now().millisecondsSinceEpoch; - final isInvoking = _shouldInvoke(time); - - _lastArgs = args; - _lastNamedArgs = namedArgs as Map?; - _lastCallTime = time; - - if (isInvoking) { - if (_timer == null) { - return _leadingEdge(_lastCallTime); - } - if (_maxing) { - // Handle invocations in a tight loop. - _timer = _startTimer(_timerExpired, _wait); - return _invokeFunc(_lastCallTime); - } - } - _timer ??= _startTimer(_timerExpired, _wait); - return _result; - } -} - -/// Creates a throttled function that only invokes `func` at most once per -/// every `wait` milliseconds. The throttled function comes with a [Throttle.cancel] -/// method to cancel delayed `func` invocations and a [Throttle.flush] method to -/// immediately invoke them. Provide `leading` and/or `trailing` to indicate -/// whether `func` should be invoked on the `leading` and/or `trailing` edge of the `wait` timeout. -/// The `func` is invoked with the last arguments provided to the -/// throttled function. Subsequent calls to the throttled function return the -/// result of the last `func` invocation. -/// -/// **Note:** If `leading` and `trailing` options are `true`, `func` is -/// invoked on the trailing edge of the timeout only if the throttled function -/// is invoked more than once during the `wait` timeout. -/// -/// If `wait` is [Duration.zero] and `leading` is `false`, `func` invocation is deferred -/// until the next tick. -/// -/// See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) -/// for details over the differences between [Throttle] and [Debounce]. -/// -/// Some examples: -/// -/// Avoid excessively rebuilding UI progress while uploading data to server. -/// ```dart -/// void updateUI(Data data) { -/// updateProgress(data); -/// } -/// -/// final throttledUpdateUI = Throttle( -/// updateUI, -/// const Duration(milliseconds: 350), -/// ); -/// -/// void onUploadProgressChanged(progress) { -/// throttledUpdateUI(progress); -/// } -/// ``` -/// -/// Cancel the trailing throttled invocation. -/// ```dart -/// void dispose() { -/// throttled.cancel(); -/// } -/// ``` -/// -/// Check for pending invocations. -/// ```dart -/// final status = throttled.isPending ? "Pending..." : "Ready"; -/// ``` -class Throttle { - /// Creates a new instance of [Throttle] - Throttle( - Function func, - Duration wait, { - bool leading = true, - bool trailing = true, - }) : _debounce = Debounce( - func, - wait, - leading: leading, - trailing: trailing, - maxWait: wait, - ); - - final Debounce _debounce; - - /// Cancels all the remaining delayed functions. - void cancel() => _debounce.cancel(); - - /// Immediately invokes all the remaining delayed functions. - Object? flush() => _debounce.flush(); - - /// True if there are functions remaining to get invoked. - bool get isPending => _debounce.isPending; - - /// Calls/invokes this class like a function. - /// Pass [args] and [namedArgs] to be used while invoking `func`. - Object? call(List args, {Map? namedArgs}) => - _debounce.call(args, namedArgs: namedArgs); -} diff --git a/packages/stream_chat/lib/src/extensions/string_extension.dart b/packages/stream_chat/lib/src/extensions/string_extension.dart deleted file mode 100644 index 440e2cb6..00000000 --- a/packages/stream_chat/lib/src/extensions/string_extension.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:http_parser/http_parser.dart' as http_parser; -import 'package:mime/mime.dart'; - -/// Useful extension functions for [String] -extension StringX on String { - /// Returns the mime type from the passed file name. - http_parser.MediaType? get mimeType { - if (toLowerCase().endsWith('heic')) { - return http_parser.MediaType.parse('image/heic'); - } else { - final mimeType = lookupMimeType(this); - if (mimeType == null) { - return null; - } - return http_parser.MediaType.parse(mimeType); - } - } -} diff --git a/packages/stream_chat/lib/src/ws/websocket.dart b/packages/stream_chat/lib/src/ws/websocket.dart index 6c36fd0b..4c7c91ed 100644 --- a/packages/stream_chat/lib/src/ws/websocket.dart +++ b/packages/stream_chat/lib/src/ws/websocket.dart @@ -5,8 +5,7 @@ import 'dart:math' as math; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat/src/errors/chat_error_code.dart'; -import 'package:stream_chat/src/errors/stream_chat_error.dart'; +import 'package:stream_chat/src/core/error/error.dart'; import 'package:stream_chat/src/ws/connection_status.dart'; import 'package:stream_chat/src/core/http/token.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; diff --git a/packages/stream_chat/lib/stream_chat.dart b/packages/stream_chat/lib/stream_chat.dart index 66dace1d..3c2e3d21 100644 --- a/packages/stream_chat/lib/stream_chat.dart +++ b/packages/stream_chat/lib/stream_chat.dart @@ -6,14 +6,14 @@ export 'package:dio/src/multipart_file.dart'; export 'package:dio/src/options.dart'; export 'package:dio/src/options.dart' show ProgressCallback; export 'package:logging/logging.dart' show Logger, Level; +export 'package:rate_limiter/rate_limiter.dart'; -export './src/api/channel.dart'; -export './src/client.dart'; export './src/core/api/attachment_file_uploader.dart' show AttachmentFileUploader; export './src/core/api/requests.dart'; export './src/core/api/requests.dart'; export './src/core/api/responses.dart'; +export './src/core/error/error.dart'; export './src/core/models/action.dart'; export './src/core/models/attachment.dart'; export './src/core/models/attachment_file.dart'; @@ -31,11 +31,10 @@ export './src/core/models/own_user.dart'; export './src/core/models/reaction.dart'; export './src/core/models/read.dart'; export './src/core/models/user.dart'; +export './src/core/util/extension.dart'; export './src/db/chat_persistence_client.dart'; -export './src/errors/chat_error_code.dart'; -export './src/errors/stream_chat_error.dart'; export './src/event_type.dart'; -export './src/extensions/rate_limit.dart'; -export './src/extensions/string_extension.dart'; export './src/location.dart'; export './src/ws/connection_status.dart'; +export 'src/client/channel.dart'; +export 'src/client/client.dart'; diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index 6c68818a..6bf106a2 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -1,4 +1,4 @@ -import 'package:stream_chat/src/client.dart'; +import 'package:stream_chat/src/client/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 23f6c114..f4554959 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: logging: ^1.0.1 meta: ^1.3.0 mime: ^1.0.0 + rate_limiter: ^0.1.1 rxdart: ^0.27.0 uuid: ^3.0.4 web_socket_channel: ^2.0.0 diff --git a/packages/stream_chat/test/version_test.dart b/packages/stream_chat/test/version_test.dart index 8e7112a4..29076db0 100644 --- a/packages/stream_chat/test/version_test.dart +++ b/packages/stream_chat/test/version_test.dart @@ -4,6 +4,23 @@ import 'dart:io'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/version.dart'; import 'package:test/test.dart'; +import 'dart:math' as math; + +// This alphabet uses `A-Za-z0-9_-` symbols. The genetic algorithm helped +// optimize the gzip compression for this alphabet. +const _alphabet = + 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW'; + +/// Generates a random String id +/// Adopted from: https://github.com/ai/nanoid/blob/main/non-secure/index.js +String randomId({int size = 21}) { + var id = ''; + for (var i = 0; i < size; i++) { + id += _alphabet[(math.Random().nextDouble() * 64).floor() | 0]; + } + return id; +} + void prepareTest() { // https://github.com/flutter/flutter/issues/20907 @@ -15,30 +32,32 @@ void prepareTest() { void main() { prepareTest(); test('stream chat version matches pubspec', () async { - /// Create a new instance of [StreamChatClient] - /// by passing the apikey obtained from your project dashboard. - final client = StreamChatClient('b67pax5b2wdq', logLevel: Level.INFO); + print(randomId()); - /// Set the current user. In a production scenario, this should be done using - /// a backend to generate a user token using our server SDK. - /// Please see the following for more information: - /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ - await client.connectUser( - User( - id: 'cool-shadow-7', - extraData: { - 'image': - 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', - }, - ), - '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''', - ); - - try { - await client.banUser('asdasdas'); - } catch (e) { - print(e); - } + // /// Create a new instance of [StreamChatClient] + // /// by passing the apikey obtained from your project dashboard. + // final client = StreamChatClient('b67pax5b2wdq', logLevel: Level.INFO); + // + // /// Set the current user. In a production scenario, this should be done using + // /// a backend to generate a user token using our server SDK. + // /// Please see the following for more information: + // /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ + // await client.connectUser( + // User( + // id: 'cool-shadow-7', + // extraData: { + // 'image': + // 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', + // }, + // ), + // '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''', + // ); + // + // try { + // await client.banUser('asdasdas'); + // } catch (e) { + // print(e); + // } // final pubspecPath = '${Directory.current.path}/pubspec.yaml'; // final pubspec = File(pubspecPath).readAsStringSync(); From 1626d7432cbd3d11d16311b866294dba842b4132 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 3 Jun 2021 18:06:56 +0530 Subject: [PATCH 324/599] add websocket_test.dart --- packages/stream_chat/analysis_options.yaml | 2 +- .../lib/src/core/api/responses.dart | 5 +- .../lib/src/core/api/responses.g.dart | 9 + packages/stream_chat/pubspec.yaml | 2 +- packages/stream_chat/test/src/fakes.dart | 27 ++ packages/stream_chat/test/src/mocks.dart | 6 + .../test/src/ws/websocket_test.dart | 344 ++++++++++++++++++ packages/stream_chat/test/version_test.dart | 62 +--- 8 files changed, 400 insertions(+), 57 deletions(-) create mode 100644 packages/stream_chat/test/src/fakes.dart create mode 100644 packages/stream_chat/test/src/mocks.dart create mode 100644 packages/stream_chat/test/src/ws/websocket_test.dart diff --git a/packages/stream_chat/analysis_options.yaml b/packages/stream_chat/analysis_options.yaml index f0a87ea5..ed0ce983 100644 --- a/packages/stream_chat/analysis_options.yaml +++ b/packages/stream_chat/analysis_options.yaml @@ -6,7 +6,7 @@ analyzer: - example/** - lib/src/emoji - lib/**/*.freezed.dart - - test/** +# - test/** linter: rules: diff --git a/packages/stream_chat/lib/src/core/api/responses.dart b/packages/stream_chat/lib/src/core/api/responses.dart index b6d6e801..ccf03822 100644 --- a/packages/stream_chat/lib/src/core/api/responses.dart +++ b/packages/stream_chat/lib/src/core/api/responses.dart @@ -18,7 +18,7 @@ class _BaseResponse { } /// Model response for [StreamChatNetworkError] data -@JsonSerializable(createToJson: false) +@JsonSerializable() class ErrorResponse extends _BaseResponse { /// int? code; @@ -37,6 +37,9 @@ class ErrorResponse extends _BaseResponse { static ErrorResponse fromJson(Map json) => _$ErrorResponseFromJson(json); + /// Serialize to json + Map toJson() => _$ErrorResponseToJson(this); + @override String toString() => 'ErrorResponse(code: $code, ' 'message: $message, ' diff --git a/packages/stream_chat/lib/src/core/api/responses.g.dart b/packages/stream_chat/lib/src/core/api/responses.g.dart index c1c1f302..deba55f0 100644 --- a/packages/stream_chat/lib/src/core/api/responses.g.dart +++ b/packages/stream_chat/lib/src/core/api/responses.g.dart @@ -15,6 +15,15 @@ ErrorResponse _$ErrorResponseFromJson(Map json) { ..moreInfo = json['more_info'] as String?; } +Map _$ErrorResponseToJson(ErrorResponse instance) => + { + 'duration': instance.duration, + 'code': instance.code, + 'message': instance.message, + 'StatusCode': instance.statusCode, + 'more_info': instance.moreInfo, + }; + SyncResponse _$SyncResponseFromJson(Map json) { return SyncResponse() ..duration = json['duration'] as String? diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index f4554959..e6259b42 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -30,4 +30,4 @@ dev_dependencies: freezed: ^0.14.1+3 json_serializable: ^4.1.0 mocktail: ^0.1.1 - test: ^1.16.8 + test: ^1.16.8 \ No newline at end of file diff --git a/packages/stream_chat/test/src/fakes.dart b/packages/stream_chat/test/src/fakes.dart new file mode 100644 index 00000000..e96cbd12 --- /dev/null +++ b/packages/stream_chat/test/src/fakes.dart @@ -0,0 +1,27 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/http/token.dart'; +import 'package:stream_chat/src/core/http/token_manager.dart'; + +class FakeTokenManager extends Fake implements TokenManager { + final token = Token.development('test-user-id'); + + @override + bool get isStatic => true; + + @override + String? get userId => token.userId; + + @override + Future loadToken({bool refresh = false}) async => token; + + @override + Future setTokenOrProvider( + String userId, { + Token? token, + TokenProvider? provider, + }) async => + this.token; + + @override + void reset() {} +} diff --git a/packages/stream_chat/test/src/mocks.dart b/packages/stream_chat/test/src/mocks.dart new file mode 100644 index 00000000..98e84d77 --- /dev/null +++ b/packages/stream_chat/test/src/mocks.dart @@ -0,0 +1,6 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +class MockWebSocketChannel extends Mock implements WebSocketChannel {} + +class MockWebSocketSink extends Mock implements WebSocketSink {} diff --git a/packages/stream_chat/test/src/ws/websocket_test.dart b/packages/stream_chat/test/src/ws/websocket_test.dart new file mode 100644 index 00000000..73eecf3f --- /dev/null +++ b/packages/stream_chat/test/src/ws/websocket_test.dart @@ -0,0 +1,344 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:stream_chat/src/core/http/token_manager.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/ws/websocket.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +import '../fakes.dart'; +import '../mocks.dart'; + +void main() { + late TokenManager tokenManager; + late WebSocketChannel webSocketChannel; + late WebSocketSink webSocketSink; + late WebSocket webSocket; + + setUp(() { + tokenManager = FakeTokenManager(); + webSocketChannel = MockWebSocketChannel(); + + WebSocketChannel channelProvider( + Uri uri, { + Iterable? protocols, + }) => + webSocketChannel; + + webSocket = WebSocket( + apiKey: 'api-key', + baseUrl: 'base-url', + tokenManager: tokenManager, + webSocketChannelProvider: channelProvider, + ); + + webSocketSink = MockWebSocketSink(); + when(() => webSocketChannel.sink).thenReturn(webSocketSink); + + var webSocketController = StreamController.broadcast(); + when(() => webSocketChannel.stream).thenAnswer( + (_) => webSocketController.stream, + ); + when(() => webSocketSink.add(any())).thenAnswer((invocation) { + webSocketController.add(invocation.positionalArguments.first); + }); + when(() => webSocketSink.close(any(), any())).thenAnswer( + (_) { + final res = webSocketController.close(); + // re-initializing for future events + webSocketController = StreamController.broadcast(); + return res; + }, + ); + }); + + tearDown(() { + tokenManager.reset(); + webSocket.disconnect(); + }); + + test('`connect` successfully with the provided user', () async { + final user = OwnUser(id: 'test-user'); + const connectionId = 'test-connection-id'; + // Sends connect event to web-socket stream + final timer = Timer(const Duration(milliseconds: 300), () { + final event = Event( + type: EventType.healthCheck, + connectionId: connectionId, + me: user, + ); + webSocketSink.add(json.encode(event)); + }); + + expectLater( + webSocket.connectionStatusStream, + emitsInOrder([ + ConnectionStatus.disconnected, + ConnectionStatus.connecting, + ConnectionStatus.connected, + ]), + ); + + final event = await webSocket.connect(user); + + expect(event.type, EventType.healthCheck); + expect(event.connectionId, connectionId); + expect(event.me, isNotNull); + expect(event.me!.id, user.id); + + addTearDown(timer.cancel); + }); + + test('`connect` should throw if already in connection attempt', () async { + final user = OwnUser(id: 'test-user'); + webSocket.connect(user); + try { + // calling again before previous attempt finishes + await webSocket.connect(user); + } catch (e) { + expect(e, isA()); + } + }); + + test('`connect` should throw if `onMessage` contains error', () async { + final user = OwnUser(id: 'test-user'); + final error = ErrorResponse() + ..code = 333 + ..message = 'Invalid request'; + // Sends error event to web-socket stream + final timer = Timer(const Duration(milliseconds: 300), () { + webSocketSink.add(json.encode({'error': error})); + }); + + expectLater( + webSocket.connectionStatusStream, + emitsInOrder([ + ConnectionStatus.disconnected, + ConnectionStatus.connecting, + ConnectionStatus.disconnected, + ]), + ); + + try { + await webSocket.connect(user); + } catch (e) { + expect(e, isA()); + final err = e as StreamWebSocketError; + expect(err.code, error.code); + expect(err.message, error.message); + } + + addTearDown(timer.cancel); + }); + + test( + 'should `reconnect` automatically ' + 'if `onMessage` throws error after getting connected', + () async { + final user = OwnUser(id: 'test-user'); + const connectionId = 'test-connection-id'; + // Sends connect event to web-socket stream + final timer = Timer(const Duration(milliseconds: 300), () { + final event = Event( + type: EventType.healthCheck, + connectionId: connectionId, + me: user, + ); + webSocketSink.add(json.encode(event)); + }); + + expectLater( + webSocket.connectionStatusStream, + emitsInOrder([ + ConnectionStatus.disconnected, + ConnectionStatus.connecting, + ConnectionStatus.connected, + // starts reconnecting + ConnectionStatus.connecting, + ConnectionStatus.connected, + ]), + ); + + await webSocket.connect(user); + + final error = ErrorResponse() + ..code = 333 + ..message = 'Invalid request'; + // Sends error event to web-socket stream + webSocketSink.add(json.encode({'error': error})); + + final reconnectTimer = Timer(const Duration(seconds: 3), () { + final event = Event( + type: EventType.healthCheck, + connectionId: connectionId, + me: user, + ); + webSocketSink.add(json.encode(event)); + }); + + expect(webSocket.connectionId, connectionId); + + addTearDown(() { + timer.cancel(); + reconnectTimer.cancel(); + }); + }, + ); + + test( + '`onMessage` should handle `health.check` event if `me` is null', + () async { + final user = OwnUser(id: 'test-user'); + const connectionId = 'test-connection-id'; + // Sends connect event to web-socket stream + final timer = Timer(const Duration(milliseconds: 300), () { + final event = Event( + type: EventType.healthCheck, + connectionId: connectionId, + me: user, + ); + webSocketSink.add(json.encode(event)); + }); + + expectLater( + webSocket.connectionStatusStream, + emitsInOrder([ + ConnectionStatus.disconnected, + ConnectionStatus.connecting, + ConnectionStatus.connected, + ]), + ); + + final event = await webSocket.connect(user); + + expect(event.type, EventType.healthCheck); + expect(event.connectionId, connectionId); + expect(event.me, isNotNull); + expect(event.me!.id, user.id); + + const newConnectionId = 'new-connection-id'; + final healthCheckEvent = Event( + type: EventType.healthCheck, + connectionId: newConnectionId, + ); + webSocketSink.add(json.encode(healthCheckEvent)); + + await Future.delayed(const Duration(milliseconds: 300)); + + expectLater(webSocket.connectionId, newConnectionId); + + addTearDown(timer.cancel); + }, + ); + + test('should call `onConnectionError` if web-socket stream throws', () async { + final user = OwnUser(id: 'test-user'); + // Sends connect event to web-socket stream + final timer = Timer(const Duration(milliseconds: 300), () { + const error = StreamWebSocketError('test-error'); + webSocketSink.addError(error); + }); + + expectLater( + webSocket.connectionStatusStream, + emitsInOrder([ + ConnectionStatus.disconnected, + ConnectionStatus.connecting, + // throws error, reconnects + ConnectionStatus.connected, + ]), + ); + + webSocket.connect(user); + + // Assuming web-socket stream will add error + // and web-socket now trying to reconnect + await Future.delayed(const Duration(seconds: 3)); + + const connectionId = 'test-connection-id'; + // Sends connect event to web-socket stream + final event = Event( + type: EventType.healthCheck, + connectionId: connectionId, + me: user, + ); + webSocketSink.add(json.encode(event)); + + addTearDown(timer.cancel); + }); + + test( + 'should call `onConnectionClosed` if web-socket stream throws', + () async { + final user = OwnUser(id: 'test-user'); + // Sends connect event to web-socket stream + final timer = Timer(const Duration(milliseconds: 300), () { + webSocketSink.close(); + }); + + expectLater( + webSocket.connectionStatusStream, + emitsInOrder([ + ConnectionStatus.disconnected, + ConnectionStatus.connecting, + // throws error, reconnects + ConnectionStatus.connected, + ]), + ); + + webSocket.connect(user); + + // Assuming web-socket stream will add error + // and web-socket now trying to reconnect + await Future.delayed(const Duration(seconds: 3)); + + const connectionId = 'test-connection-id'; + // Sends connect event to web-socket stream + final event = Event( + type: EventType.healthCheck, + connectionId: connectionId, + me: user, + ); + webSocketSink.add(json.encode(event)); + + addTearDown(timer.cancel); + }, + ); + + test('`disconnect` successfully disconnects the current user', () async { + final user = OwnUser(id: 'test-user'); + const connectionId = 'test-connection-id'; + // Sends connect event to web-socket stream + final timer = Timer(const Duration(milliseconds: 300), () { + final event = Event( + type: EventType.healthCheck, + connectionId: connectionId, + me: user, + ); + webSocketSink.add(json.encode(event)); + }); + + expectLater( + webSocket.connectionStatusStream, + emitsInOrder([ + ConnectionStatus.disconnected, + ConnectionStatus.connecting, + ConnectionStatus.connected, + // after disconnect + ConnectionStatus.disconnected, + ]), + ); + + final event = await webSocket.connect(user); + + expect(event.type, EventType.healthCheck); + expect(event.connectionId, connectionId); + expect(event.me?.id, user.id); + + webSocket.disconnect(); + + addTearDown(timer.cancel); + }); +} diff --git a/packages/stream_chat/test/version_test.dart b/packages/stream_chat/test/version_test.dart index 29076db0..34ecc750 100644 --- a/packages/stream_chat/test/version_test.dart +++ b/packages/stream_chat/test/version_test.dart @@ -1,26 +1,7 @@ -import 'dart:async'; import 'dart:io'; -import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/version.dart'; import 'package:test/test.dart'; -import 'dart:math' as math; - -// This alphabet uses `A-Za-z0-9_-` symbols. The genetic algorithm helped -// optimize the gzip compression for this alphabet. -const _alphabet = - 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW'; - -/// Generates a random String id -/// Adopted from: https://github.com/ai/nanoid/blob/main/non-secure/index.js -String randomId({int size = 21}) { - var id = ''; - for (var i = 0; i < size; i++) { - id += _alphabet[(math.Random().nextDouble() * 64).floor() | 0]; - } - return id; -} - void prepareTest() { // https://github.com/flutter/flutter/issues/20907 @@ -31,40 +12,13 @@ void prepareTest() { void main() { prepareTest(); - test('stream chat version matches pubspec', () async { - print(randomId()); - - // /// Create a new instance of [StreamChatClient] - // /// by passing the apikey obtained from your project dashboard. - // final client = StreamChatClient('b67pax5b2wdq', logLevel: Level.INFO); - // - // /// Set the current user. In a production scenario, this should be done using - // /// a backend to generate a user token using our server SDK. - // /// Please see the following for more information: - // /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ - // await client.connectUser( - // User( - // id: 'cool-shadow-7', - // extraData: { - // 'image': - // 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', - // }, - // ), - // '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''', - // ); - // - // try { - // await client.banUser('asdasdas'); - // } catch (e) { - // print(e); - // } - - // final pubspecPath = '${Directory.current.path}/pubspec.yaml'; - // final pubspec = File(pubspecPath).readAsStringSync(); - // // ignore: unnecessary_string_escapes - // final regex = RegExp('version:\s*(.*)'); - // final match = regex.firstMatch(pubspec); - // expect(match, isNotNull); - // expect(PACKAGE_VERSION, match?.group(1)?.trim()); + test('stream chat version matches pubspec', () { + final pubspecPath = '${Directory.current.path}/pubspec.yaml'; + final pubspec = File(pubspecPath).readAsStringSync(); + // ignore: unnecessary_string_escapes + final regex = RegExp('version:\s*(.*)'); + final match = regex.firstMatch(pubspec); + expect(match, isNotNull); + expect(PACKAGE_VERSION, match?.group(1)?.trim()); }); } From 63d391ba7aba208444c2646fd2c836c08916081c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 3 Jun 2021 20:04:32 +0530 Subject: [PATCH 325/599] add chat_persistence_client_test.dart --- .../src/db/chat_persistence_client_test.dart | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 packages/stream_chat/test/src/db/chat_persistence_client_test.dart diff --git a/packages/stream_chat/test/src/db/chat_persistence_client_test.dart b/packages/stream_chat/test/src/db/chat_persistence_client_test.dart new file mode 100644 index 00000000..0ab7695f --- /dev/null +++ b/packages/stream_chat/test/src/db/chat_persistence_client_test.dart @@ -0,0 +1,191 @@ +import 'package:stream_chat/src/core/api/requests.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/channel_state.dart'; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/filter.dart'; +import 'package:stream_chat/src/core/models/member.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/reaction.dart'; +import 'package:stream_chat/src/core/models/read.dart'; +import 'package:stream_chat/src/core/models/user.dart'; +import 'package:stream_chat/src/db/chat_persistence_client.dart'; +import 'package:test/test.dart'; + +class TestPersistenceClient extends ChatPersistenceClient { + @override + Future connect(String userId) => throw UnimplementedError(); + + @override + Future deleteChannels(List cids) => throw UnimplementedError(); + + @override + Future deleteMembersByCids(List cids) => Future.value(); + + @override + Future deleteMessageByCids(List cids) => Future.value(); + + @override + Future deleteMessageByIds(List messageIds) => Future.value(); + + @override + Future deletePinnedMessageByCids(List cids) => Future.value(); + + @override + Future deletePinnedMessageByIds(List messageIds) => + Future.value(); + + @override + Future deleteReactionsByMessageId(List messageIds) => + Future.value(); + + @override + Future disconnect({bool flush = false}) => throw UnimplementedError(); + + @override + Future getChannelByCid(String cid) async => + ChannelModel(cid: cid); + + @override + Future> getChannelCids() => throw UnimplementedError(); + + @override + Future> getChannelStates( + {Filter? filter, + List>? sort, + PaginationParams? paginationParams}) => + throw UnimplementedError(); + + @override + Future>> getChannelThreads(String cid) => + throw UnimplementedError(); + + @override + Future getConnectionInfo() => throw UnimplementedError(); + + @override + Future getLastSyncAt() => throw UnimplementedError(); + + @override + Future> getMembersByCid(String cid) async => []; + + @override + Future> getMessagesByCid(String cid, + {PaginationParams? messagePagination}) async => + []; + + @override + Future> getPinnedMessagesByCid(String cid, + {PaginationParams? messagePagination}) async => + []; + + @override + Future> getReadsByCid(String cid) async => []; + + @override + Future> getReplies(String parentId, + {PaginationParams? options}) => + throw UnimplementedError(); + + @override + Future updateChannelQueries(Filter? filter, List cids, + {bool clearQueryCache = false}) => + throw UnimplementedError(); + + @override + Future updateChannels(List channels) => Future.value(); + + @override + Future updateConnectionInfo(Event event) => throw UnimplementedError(); + + @override + Future updateLastSyncAt(DateTime lastSyncAt) => + throw UnimplementedError(); + + @override + Future updateMembers(String cid, List members) => + Future.value(); + + @override + Future updateMessages(String cid, List messages) => + Future.value(); + + @override + Future updatePinnedMessages(String cid, List messages) => + Future.value(); + + @override + Future updateReactions(List reactions) => Future.value(); + + @override + Future updateReads(String cid, List reads) => Future.value(); + + @override + Future updateUsers(List users) => Future.value(); +} + +void main() { + group('chatPersistenceClient', () { + final persistenceClient = TestPersistenceClient(); + + test('deleteMessageById', () { + const messageId = 'message-id'; + persistenceClient.deleteMessageById(messageId); + }); + + test('deleteMessageByCid', () { + const messageId = 'message-id'; + persistenceClient.deleteMessageByCid(messageId); + }); + + test('deletePinnedMessageById', () { + const messageId = 'message-id'; + persistenceClient.deletePinnedMessageById(messageId); + }); + + test('deletePinnedMessageByCid', () { + const messageId = 'message-id'; + persistenceClient.deletePinnedMessageByCid(messageId); + }); + + test('getChannelStateByCid', () async { + const cid = 'test:cid'; + final channelState = await persistenceClient.getChannelStateByCid(cid); + expect(channelState, isNotNull); + }); + + test('updateChannelState', () async { + const cid = 'test:cid'; + final channelState = ChannelState(); + persistenceClient.updateChannelState(channelState); + }); + + test('updateChannelStates', () async { + const cid = 'test:cid'; + final user = User(id: 'test-user-id'); + final channelState = ChannelState( + channel: ChannelModel(cid: cid, createdBy: user), + messages: [ + Message( + id: 'test-message', + text: 'test-message', + user: user, + ownReactions: [Reaction(type: 'test', user: user)], + latestReactions: [Reaction(type: 'test', user: user)], + ) + ], + pinnedMessages: [ + Message( + id: 'test-message', + text: 'test-message', + user: user, + ownReactions: [Reaction(type: 'test', user: user)], + latestReactions: [Reaction(type: 'test', user: user)], + ) + ], + read: [Read(lastRead: DateTime.now(), user: user)], + members: [Member(user: user)], + ); + persistenceClient.updateChannelStates([channelState]); + }); + }); +} From 7a951920938be04d5e885b15ca7234688a68ce00 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 3 Jun 2021 21:44:12 +0530 Subject: [PATCH 326/599] add extension_test.dart, serializer_test.dart and utils_test.dart --- .../lib/src/core/models/attachment.dart | 14 +++--- .../lib/src/core/models/channel_model.dart | 26 +++++----- .../lib/src/core/models/event.dart | 14 +++--- .../lib/src/core/models/message.dart | 46 +++++++++--------- .../stream_chat/lib/src/core/models/mute.dart | 10 ++-- .../lib/src/core/models/own_user.dart | 18 +++---- .../lib/src/core/models/reaction.dart | 12 ++--- .../stream_chat/lib/src/core/models/user.dart | 22 ++++----- .../lib/src/core/util/extension.dart | 9 ++-- .../{serialization.dart => serializer.dart} | 2 +- .../test/src/core/util/extension_test.dart | 48 +++++++++++++++++++ .../test/src/core/util/serializer_test.dart | 43 +++++++++++++++++ .../test/src/core/util/utils_test.dart | 15 ++++++ 13 files changed, 194 insertions(+), 85 deletions(-) rename packages/stream_chat/lib/src/core/util/{serialization.dart => serializer.dart} (98%) create mode 100644 packages/stream_chat/test/src/core/util/extension_test.dart create mode 100644 packages/stream_chat/test/src/core/util/serializer_test.dart create mode 100644 packages/stream_chat/test/src/core/util/utils_test.dart diff --git a/packages/stream_chat/lib/src/core/models/attachment.dart b/packages/stream_chat/lib/src/core/models/attachment.dart index a5f9f19d..8973aee2 100644 --- a/packages/stream_chat/lib/src/core/models/attachment.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.dart @@ -4,7 +4,7 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/action.dart'; import 'package:stream_chat/src/core/models/attachment_file.dart'; -import 'package:stream_chat/src/core/util/serialization.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; import 'package:uuid/uuid.dart'; part 'attachment.g.dart'; @@ -49,11 +49,11 @@ class Attachment extends Equatable { /// Create a new instance from a json factory Attachment.fromJson(Map json) => _$AttachmentFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)); + Serializer.moveToExtraDataFromRoot(json, topLevelFields)); /// Create a new instance from a db data factory Attachment.fromData(Map json) => - _$AttachmentFromJson(Serialization.moveToExtraDataFromRoot( + _$AttachmentFromJson(Serializer.moveToExtraDataFromRoot( json, topLevelFields + dbSpecificTopLevelFields)); ///The attachment type based on the URL resource. This can be: audio, @@ -122,7 +122,7 @@ class Attachment extends Equatable { final String id; /// Known top level fields. - /// Useful for [Serialization] methods. + /// Useful for [Serializer] methods. static const topLevelFields = [ 'type', 'title_link', @@ -145,7 +145,7 @@ class Attachment extends Equatable { ]; /// Known db specific top level fields. - /// Useful for [Serialization] methods. + /// Useful for [Serializer] methods. static const dbSpecificTopLevelFields = [ 'id', 'upload_state', @@ -154,12 +154,12 @@ class Attachment extends Equatable { /// Serialize to json Map toJson() => - Serialization.moveFromExtraDataToRoot(_$AttachmentToJson(this)) + Serializer.moveFromExtraDataToRoot(_$AttachmentToJson(this)) ..removeWhere((key, value) => dbSpecificTopLevelFields.contains(key)); /// Serialize to db data Map toData() => - Serialization.moveFromExtraDataToRoot(_$AttachmentToJson(this)); + Serializer.moveFromExtraDataToRoot(_$AttachmentToJson(this)); Attachment copyWith({ String? id, 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 9e50ed3e..74195c71 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.dart @@ -1,6 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/channel_config.dart'; -import 'package:stream_chat/src/core/util/serialization.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; import 'package:stream_chat/src/core/models/user.dart'; part 'channel_model.g.dart'; @@ -37,7 +37,7 @@ class ChannelModel { /// Create a new instance from a json factory ChannelModel.fromJson(Map json) => _$ChannelModelFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)); + Serializer.moveToExtraDataFromRoot(json, topLevelFields)); /// The id of this channel final String id; @@ -46,15 +46,15 @@ class ChannelModel { final String type; /// The cid of this channel - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final String cid; /// The channel configuration data - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final ChannelConfig config; /// The user that created this channel - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final User? createdBy; /// True if this channel is frozen @@ -62,24 +62,24 @@ class ChannelModel { final bool frozen; /// The date of the last message - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime? lastMessageAt; /// The date of channel creation - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime createdAt; /// The date of the last channel update - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime updatedAt; /// The date of channel deletion - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime? deletedAt; /// The count of this channel members @JsonKey( - includeIfNull: false, toJson: Serialization.readOnly, defaultValue: 0) + includeIfNull: false, toJson: Serializer.readOnly, defaultValue: 0) final int memberCount; /// Map of custom channel extraData @@ -90,11 +90,11 @@ class ChannelModel { final Map extraData; /// The team the channel belongs to - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final String? team; /// Known top level fields. - /// Useful for [Serialization] methods. + /// Useful for [Serializer] methods. static const topLevelFields = [ 'id', 'type', @@ -115,7 +115,7 @@ class ChannelModel { extraData.containsKey('name') ? extraData['name']! as String : cid; /// Serialize to json - Map toJson() => Serialization.moveFromExtraDataToRoot( + Map toJson() => Serializer.moveFromExtraDataToRoot( _$ChannelModelToJson(this), ); diff --git a/packages/stream_chat/lib/src/core/models/event.dart b/packages/stream_chat/lib/src/core/models/event.dart index 7c1be679..33e696c4 100644 --- a/packages/stream_chat/lib/src/core/models/event.dart +++ b/packages/stream_chat/lib/src/core/models/event.dart @@ -1,7 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:stream_chat/src/core/models/message.dart'; -import 'package:stream_chat/src/core/util/serialization.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; import 'package:stream_chat/stream_chat.dart'; part 'event.g.dart'; @@ -33,7 +33,7 @@ class Event { /// Create a new instance from a json factory Event.fromJson(Map json) => - _$EventFromJson(Serialization.moveToExtraDataFromRoot( + _$EventFromJson(Serializer.moveToExtraDataFromRoot( json, topLevelFields, )); @@ -96,7 +96,7 @@ class Event { final Map extraData; /// Known top level fields. - /// Useful for [Serialization] methods. + /// Useful for [Serializer] methods. static final topLevelFields = [ 'type', 'cid', @@ -118,7 +118,7 @@ class Event { ]; /// Serialize to json - Map toJson() => Serialization.moveFromExtraDataToRoot( + Map toJson() => Serializer.moveFromExtraDataToRoot( _$EventToJson(this), ); @@ -198,7 +198,7 @@ class EventChannel extends ChannelModel { /// Create a new instance from a json factory EventChannel.fromJson(Map json) => - _$EventChannelFromJson(Serialization.moveToExtraDataFromRoot( + _$EventChannelFromJson(Serializer.moveToExtraDataFromRoot( json, topLevelFields, )); @@ -207,7 +207,7 @@ class EventChannel extends ChannelModel { final List? members; /// Known top level fields. - /// Useful for [Serialization] methods. + /// Useful for [Serializer] methods. static final topLevelFields = [ 'members', ...ChannelModel.topLevelFields, @@ -215,7 +215,7 @@ class EventChannel extends ChannelModel { /// Serialize to json @override - Map toJson() => Serialization.moveFromExtraDataToRoot( + Map toJson() => Serializer.moveFromExtraDataToRoot( _$EventChannelToJson(this), ); } diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index 1266519c..426b863a 100644 --- a/packages/stream_chat/lib/src/core/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -2,7 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/attachment.dart'; import 'package:stream_chat/src/core/models/reaction.dart'; -import 'package:stream_chat/src/core/util/serialization.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:uuid/uuid.dart'; @@ -81,7 +81,7 @@ class Message extends Equatable { /// Create a new instance from a json factory Message.fromJson(Map json) => _$MessageFromJson( - Serialization.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. @@ -97,7 +97,7 @@ class Message extends Equatable { /// The message type @JsonKey( includeIfNull: false, - toJson: Serialization.readOnly, + toJson: Serializer.readOnly, defaultValue: 'regular', ) final String type; @@ -118,37 +118,37 @@ class Message extends Equatable { final List mentionedUsers; /// A map describing the count of number of every reaction - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final Map? reactionCounts; /// A map describing the count of score of every reaction - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final Map? reactionScores; /// The latest reactions to the message created by any user. - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final List? latestReactions; /// The reactions added to the message by the current user. - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final List? ownReactions; /// The ID of the parent message, if the message is a thread reply. final String? parentId; /// A quoted reply message - @JsonKey(toJson: Serialization.readOnly) + @JsonKey(toJson: Serializer.readOnly) final Message? quotedMessage; /// The ID of the quoted message, if the message is a quoted reply. final String? quotedMessageId; /// Reserved field indicating the number of replies for this message. - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final int? replyCount; /// Reserved field indicating the thread participants for this message. - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final List? threadParticipants; /// Check if this message needs to show in the channel. @@ -165,25 +165,25 @@ class Message extends Equatable { /// If true the message is shadowed @JsonKey( includeIfNull: false, - toJson: Serialization.readOnly, + toJson: Serializer.readOnly, defaultValue: false, ) final bool shadowed; /// A used command name. - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final String? command; /// Reserved field indicating when the message was created. - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime createdAt; /// Reserved field indicating when the message was updated last time. - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime updatedAt; /// User who sent the message - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final User? user; /// If true the message is pinned @@ -191,7 +191,7 @@ class Message extends Equatable { final bool pinned; /// Reserved field indicating when the message was pinned - @JsonKey(toJson: Serialization.readOnly) + @JsonKey(toJson: Serializer.readOnly) final DateTime? pinnedAt; /// Reserved field indicating when the message will expire @@ -200,7 +200,7 @@ class Message extends Equatable { final DateTime? pinExpires; /// Reserved field indicating who pinned the message - @JsonKey(toJson: Serialization.readOnly) + @JsonKey(toJson: Serializer.readOnly) final User? pinnedBy; /// Message custom extraData @@ -220,11 +220,11 @@ class Message extends Equatable { bool get isEphemeral => type == 'ephemeral'; /// Reserved field indicating when the message was deleted. - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime? deletedAt; /// Known top level fields. - /// Useful for [Serialization] methods. + /// Useful for [Serializer] methods. static const topLevelFields = [ 'id', 'text', @@ -257,7 +257,7 @@ class Message extends Equatable { ]; /// Serialize to json - Map toJson() => Serialization.moveFromExtraDataToRoot( + Map toJson() => Serializer.moveFromExtraDataToRoot( _$MessageToJson(this), ); @@ -412,14 +412,14 @@ class TranslatedMessage extends Message { /// Create a new instance from a json factory TranslatedMessage.fromJson(Map json) => _$TranslatedMessageFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields), + Serializer.moveToExtraDataFromRoot(json, topLevelFields), ); /// A Map of final Map? i18n; /// Known top level fields. - /// Useful for [Serialization] methods. + /// Useful for [Serializer] methods. static final topLevelFields = [ 'i18n', ...Message.topLevelFields, @@ -427,7 +427,7 @@ class TranslatedMessage extends Message { /// Serialize to json @override - Map toJson() => Serialization.moveFromExtraDataToRoot( + Map toJson() => Serializer.moveFromExtraDataToRoot( _$TranslatedMessageToJson(this), ); } diff --git a/packages/stream_chat/lib/src/core/models/mute.dart b/packages/stream_chat/lib/src/core/models/mute.dart index 7a82fc54..9166d3e4 100644 --- a/packages/stream_chat/lib/src/core/models/mute.dart +++ b/packages/stream_chat/lib/src/core/models/mute.dart @@ -1,6 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/channel_model.dart'; -import 'package:stream_chat/src/core/util/serialization.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; import 'package:stream_chat/src/core/models/user.dart'; part 'mute.g.dart'; @@ -20,19 +20,19 @@ class Mute { factory Mute.fromJson(Map json) => _$MuteFromJson(json); /// The user that performed the muting action - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final User user; /// The target user - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final ChannelModel channel; /// The date in which the use was muted - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime createdAt; /// The date of the last update - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime updatedAt; /// Serialize to json 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 b9cdf0fd..70529687 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -1,7 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/device.dart'; import 'package:stream_chat/src/core/models/mute.dart'; -import 'package:stream_chat/src/core/util/serialization.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; import 'package:stream_chat/src/core/models/user.dart'; part 'own_user.g.dart'; @@ -38,7 +38,7 @@ class OwnUser extends User { /// Create a new instance from a json factory OwnUser.fromJson(Map json) => _$OwnUserFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)); + Serializer.moveToExtraDataFromRoot(json, topLevelFields)); /// Create a new instance from [User] object factory OwnUser.fromUser(User user) => OwnUser( @@ -55,35 +55,35 @@ class OwnUser extends User { /// List of user devices @JsonKey( includeIfNull: false, - toJson: Serialization.readOnly, + toJson: Serializer.readOnly, defaultValue: []) final List devices; /// List of users muted by the user @JsonKey( includeIfNull: false, - toJson: Serialization.readOnly, + toJson: Serializer.readOnly, defaultValue: []) final List mutes; /// List of users muted by the user @JsonKey( includeIfNull: false, - toJson: Serialization.readOnly, + toJson: Serializer.readOnly, defaultValue: []) final List channelMutes; /// Total unread messages by the user @JsonKey( - includeIfNull: false, toJson: Serialization.readOnly, defaultValue: 0) + includeIfNull: false, toJson: Serializer.readOnly, defaultValue: 0) final int totalUnreadCount; /// Total unread channels by the user - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final int? unreadChannels; /// Known top level fields. - /// Useful for [Serialization] methods. + /// Useful for [Serializer] methods. static final topLevelFields = [ 'devices', 'mutes', @@ -95,7 +95,7 @@ class OwnUser extends User { /// Serialize to json @override - Map toJson() => Serialization.moveFromExtraDataToRoot( + Map toJson() => Serializer.moveFromExtraDataToRoot( _$OwnUserToJson(this), ); } diff --git a/packages/stream_chat/lib/src/core/models/reaction.dart b/packages/stream_chat/lib/src/core/models/reaction.dart index 997d833c..57d0b48d 100644 --- a/packages/stream_chat/lib/src/core/models/reaction.dart +++ b/packages/stream_chat/lib/src/core/models/reaction.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/core/util/serialization.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; import 'package:stream_chat/src/core/models/user.dart'; part 'reaction.g.dart'; @@ -21,7 +21,7 @@ class Reaction { /// Create a new instance from a json factory Reaction.fromJson(Map json) => - _$ReactionFromJson(Serialization.moveToExtraDataFromRoot( + _$ReactionFromJson(Serializer.moveToExtraDataFromRoot( json, topLevelFields, )); @@ -33,11 +33,11 @@ class Reaction { final String type; /// The date of the reaction - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime createdAt; /// The user that sent the reaction - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final User? user; /// The score of the reaction (ie. number of reactions sent) @@ -45,7 +45,7 @@ class Reaction { final int score; /// The userId that sent the reaction - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final String? userId; /// Reaction custom extraData @@ -66,7 +66,7 @@ class Reaction { ]; /// Serialize to json - Map toJson() => Serialization.moveFromExtraDataToRoot( + Map toJson() => Serializer.moveFromExtraDataToRoot( _$ReactionToJson(this), ); diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index b5fdec69..b1840ba2 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/core/util/serialization.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; part 'user.g.dart'; @@ -22,10 +22,10 @@ class User { /// Create a new instance from a json factory User.fromJson(Map json) => _$UserFromJson( - Serialization.moveToExtraDataFromRoot(json, topLevelFields)); + Serializer.moveToExtraDataFromRoot(json, topLevelFields)); /// Known top level fields. - /// Useful for [Serialization] methods. + /// Useful for [Serializer] methods. static const topLevelFields = [ 'id', 'role', @@ -41,36 +41,36 @@ class User { final String id; /// User role - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final String? role; /// User role @JsonKey( includeIfNull: false, - toJson: Serialization.readOnly, + toJson: Serializer.readOnly, defaultValue: []) final List teams; /// Date of user creation - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime createdAt; /// Date of last user update - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime updatedAt; /// Date of last user connection - @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime? lastActive; /// True if user is online @JsonKey( - includeIfNull: false, toJson: Serialization.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: Serialization.readOnly, defaultValue: false) + includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false) final bool banned; /// Map of custom user extraData @@ -102,7 +102,7 @@ class User { other is User && runtimeType == other.runtimeType && id == other.id; /// Serialize to json - Map toJson() => Serialization.moveFromExtraDataToRoot( + Map toJson() => Serializer.moveFromExtraDataToRoot( _$UserToJson(this), ); diff --git a/packages/stream_chat/lib/src/core/util/extension.dart b/packages/stream_chat/lib/src/core/util/extension.dart index 5dd8ea15..a28dc12b 100644 --- a/packages/stream_chat/lib/src/core/util/extension.dart +++ b/packages/stream_chat/lib/src/core/util/extension.dart @@ -9,10 +9,13 @@ extension IterableX on Iterable { } /// Useful extension functions for [Map] -extension MapX on Map { +extension MapX on Map { /// Returns a new map with null keys or values removed - Map get nullProtected => - Map.from(this)..removeWhere((key, value) => key == null || value == null); + Map get nullProtected { + final nullProtected = {...this} + ..removeWhere((key, value) => key == null || value == null); + return nullProtected.cast(); + } } /// Useful extension functions for [String] diff --git a/packages/stream_chat/lib/src/core/util/serialization.dart b/packages/stream_chat/lib/src/core/util/serializer.dart similarity index 98% rename from packages/stream_chat/lib/src/core/util/serialization.dart rename to packages/stream_chat/lib/src/core/util/serializer.dart index 1e933a32..2bf92e39 100644 --- a/packages/stream_chat/lib/src/core/util/serialization.dart +++ b/packages/stream_chat/lib/src/core/util/serializer.dart @@ -3,7 +3,7 @@ Null readonly(_) => null; /// Helper class for serialization to and from json -class Serialization { +class Serializer { /// Used to avoid to serialize properties to json static const Function readOnly = readonly; diff --git a/packages/stream_chat/test/src/core/util/extension_test.dart b/packages/stream_chat/test/src/core/util/extension_test.dart new file mode 100644 index 00000000..98f9f784 --- /dev/null +++ b/packages/stream_chat/test/src/core/util/extension_test.dart @@ -0,0 +1,48 @@ +import 'package:test/test.dart'; +import 'package:stream_chat/src/core/util/extension.dart'; + +void main() { + test('`.withNullifyer` converts the type into non-nullable', () { + final items = ['A', 'B', null, 'D']; + expect(items, isA>()); + expect(items.length, 4); + + final nullifiedItems = items.withNullifyer; + expect(nullifiedItems, isA>()); + expect(nullifiedItems.length, 3); + }); + + test('`.nullProtected should remove all the null keys, value`', () { + final map = {'name': 'sahil', 'age': null, null: 'India'}; + expect(map, isA>()); + expect(map.length, 3); + + final nullProtectedMap = map.nullProtected; + expect(nullProtectedMap, isA>()); + expect(nullProtectedMap.length, 1); + }); + + group('mimeType', () { + test('should return null if `String` is not a filename', () { + const fileName = 'not-a-file-name'; + final mimeType = fileName.mimeType; + expect(mimeType, isNull); + }); + + test('should return mimeType if string is a filename', () { + const fileName = 'dummyFileName.jpeg'; + final mimeType = fileName.mimeType; + expect(mimeType, isNotNull); + expect(mimeType!.type, 'image'); + expect(mimeType.subtype, 'jpeg'); + }); + + test('should return `image/heic` if ends with `heic`', () { + const fileName = 'dummyFileName.heic'; + final mimeType = fileName.mimeType; + expect(mimeType, isNotNull); + expect(mimeType!.type, 'image'); + expect(mimeType.subtype, 'heic'); + }); + }); +} diff --git a/packages/stream_chat/test/src/core/util/serializer_test.dart b/packages/stream_chat/test/src/core/util/serializer_test.dart new file mode 100644 index 00000000..fbbb96a2 --- /dev/null +++ b/packages/stream_chat/test/src/core/util/serializer_test.dart @@ -0,0 +1,43 @@ +import 'package:stream_chat/src/core/util/serializer.dart'; +import 'package:test/test.dart'; + +void main() { + group('Serializer', () { + test('moveKeysToMapInPlace', () { + final serializer = Serializer.moveToExtraDataFromRoot( + { + 'test': 'test', + 'name': 'Sahil', + 'age': 22, + 'country': 'India', + }, + ['test'], + ); + expect(serializer, { + 'test': 'test', + 'extra_data': { + 'name': 'Sahil', + 'age': 22, + 'country': 'India', + } + }); + }); + + test('moveKeysToMapInPlace', () { + final serializer = Serializer.moveFromExtraDataToRoot({ + 'test': 'test', + 'extra_data': { + 'name': 'Sahil', + 'age': 22, + 'country': 'India', + } + }); + expect(serializer, { + 'test': 'test', + 'name': 'Sahil', + 'age': 22, + 'country': 'India', + }); + }); + }); +} diff --git a/packages/stream_chat/test/src/core/util/utils_test.dart b/packages/stream_chat/test/src/core/util/utils_test.dart new file mode 100644 index 00000000..5d4c272f --- /dev/null +++ b/packages/stream_chat/test/src/core/util/utils_test.dart @@ -0,0 +1,15 @@ +import 'package:stream_chat/src/core/util/utils.dart'; +import 'package:test/test.dart'; + +void main() { + test('should generate a `randomId` of length 33', () { + final id = randomId(size: 33); + expect(id.length, 33); + }); + + test('should `generateHash` for the passed objects', () { + final objects = ['Sahil', 23, 'Flutter Engineer', 'India']; + final hash = generateHash(objects); + expect(hash, 'WyJTYWhpbCIsMjMsIkZsdXR0ZXIgRW5naW5lZXIiLCJJbmRpYSJd'); + }); +} From 473851684bd033d6dc7ae8f077b877c30869b15e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 3 Jun 2021 22:29:52 +0530 Subject: [PATCH 327/599] add platform_detector_test.dart Signed-off-by: Sahil Kumar --- .../platform_detector_test.dart | 25 +++++++++++++++++++ .../test/src/ws/websocket_test.dart | 5 ++-- 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 packages/stream_chat/test/src/core/platform_detector/platform_detector_test.dart diff --git a/packages/stream_chat/test/src/core/platform_detector/platform_detector_test.dart b/packages/stream_chat/test/src/core/platform_detector/platform_detector_test.dart new file mode 100644 index 00000000..110fb64e --- /dev/null +++ b/packages/stream_chat/test/src/core/platform_detector/platform_detector_test.dart @@ -0,0 +1,25 @@ +@TestOn('linux') +import 'package:stream_chat/src/core/platform_detector/platform_detector.dart'; +import 'package:test/test.dart'; + +void main() { + test('`.type` should return current platform', () { + final type = CurrentPlatform.type; + expect(type, PlatformType.linux); + }); + + test('`.name` should return current platform name', () { + final name = CurrentPlatform.name; + expect(name, 'linux'); + }); + + test('flags', () { + expect(CurrentPlatform.isWeb, isFalse); + expect(CurrentPlatform.isIos, isFalse); + expect(CurrentPlatform.isLinux, isTrue); + expect(CurrentPlatform.isAndroid, isFalse); + expect(CurrentPlatform.isMacOS, isFalse); + expect(CurrentPlatform.isWindows, isFalse); + expect(CurrentPlatform.isFuchsia, isFalse); + }); +} diff --git a/packages/stream_chat/test/src/ws/websocket_test.dart b/packages/stream_chat/test/src/ws/websocket_test.dart index 73eecf3f..94033bf0 100644 --- a/packages/stream_chat/test/src/ws/websocket_test.dart +++ b/packages/stream_chat/test/src/ws/websocket_test.dart @@ -45,11 +45,10 @@ void main() { webSocketController.add(invocation.positionalArguments.first); }); when(() => webSocketSink.close(any(), any())).thenAnswer( - (_) { - final res = webSocketController.close(); + (_) async { + webSocketController.close(); // re-initializing for future events webSocketController = StreamController.broadcast(); - return res; }, ); }); From 214d1bcf1dae9702d16a4716ab7803d8217a89ca Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 4 Jun 2021 16:24:52 +0530 Subject: [PATCH 328/599] add core/http tests Signed-off-by: Sahil Kumar --- .../http/interceptor/auth_interceptor.dart | 3 +- .../lib/src/core/http/stream_http_client.dart | 4 +- .../core/http/connection_id_manager_test.dart | 38 ++ .../interceptor/auth_interceptor_test.dart | 270 +++++++++ .../connection_id_interceptor.dart | 67 +++ .../core/http/stream_chat_dio_error_test.dart | 20 + .../http/stream_http_client_options_test.dart | 53 ++ .../core/http/stream_http_client_test.dart | 528 ++++++++++++++++++ .../src/core/http/token_manager_test.dart | 141 +++++ .../test/src/core/http/token_test.dart | 57 ++ 10 files changed, 1177 insertions(+), 4 deletions(-) create mode 100644 packages/stream_chat/test/src/core/http/connection_id_manager_test.dart create mode 100644 packages/stream_chat/test/src/core/http/interceptor/auth_interceptor_test.dart create mode 100644 packages/stream_chat/test/src/core/http/interceptor/connection_id_interceptor.dart create mode 100644 packages/stream_chat/test/src/core/http/stream_chat_dio_error_test.dart create mode 100644 packages/stream_chat/test/src/core/http/stream_http_client_options_test.dart create mode 100644 packages/stream_chat/test/src/core/http/stream_http_client_test.dart create mode 100644 packages/stream_chat/test/src/core/http/token_manager_test.dart create mode 100644 packages/stream_chat/test/src/core/http/token_test.dart diff --git a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart index d689cce1..3097c2b1 100644 --- a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart +++ b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart @@ -7,7 +7,6 @@ import 'package:stream_chat/src/core/http/token.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; import 'package:stream_chat/src/core/error/error.dart'; - /// class AuthInterceptor extends Interceptor { /// @@ -84,7 +83,7 @@ class AuthInterceptor extends Interceptor { ); return handler.resolve(response); } on DioError catch (error) { - return handler.reject(error); + return handler.next(error); } } return handler.next(err); diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client.dart b/packages/stream_chat/lib/src/core/http/stream_http_client.dart index 3eacd11e..4cf5b0e0 100644 --- a/packages/stream_chat/lib/src/core/http/stream_http_client.dart +++ b/packages/stream_chat/lib/src/core/http/stream_http_client.dart @@ -86,8 +86,8 @@ class StreamHttpClient { /// [StreamHttpClient] instance dequeue the request task。 void unlock() => httpClient.unlock(); - ///Clear the current [StreamHttpClient] instance waiting queue. - void clear() => httpClient.close(); + /// Clear the current [StreamHttpClient] instance waiting queue. + void clear() => httpClient.clear(); /// Shuts down the [StreamHttpClient]. /// diff --git a/packages/stream_chat/test/src/core/http/connection_id_manager_test.dart b/packages/stream_chat/test/src/core/http/connection_id_manager_test.dart new file mode 100644 index 00000000..ad70283f --- /dev/null +++ b/packages/stream_chat/test/src/core/http/connection_id_manager_test.dart @@ -0,0 +1,38 @@ +import 'package:stream_chat/src/core/http/connection_id_manager.dart'; +import 'package:test/test.dart'; + +void main() { + late ConnectionIdManager connectionIdManager; + + setUp(() { + connectionIdManager = ConnectionIdManager(); + }); + + tearDown(() { + connectionIdManager.reset(); + }); + + test('`setConnectionId` should set connectionId', () { + expect(connectionIdManager.connectionId, isNull); + expect(connectionIdManager.hasConnectionId, isFalse); + + const connectionId = 'test-connection-id'; + connectionIdManager.setConnectionId(connectionId); + + expect(connectionIdManager.connectionId, connectionId); + expect(connectionIdManager.hasConnectionId, isTrue); + }); + + test('`reset` should clear the connectionId', () { + const connectionId = 'test-connection-id'; + connectionIdManager.setConnectionId(connectionId); + + expect(connectionIdManager.connectionId, connectionId); + expect(connectionIdManager.hasConnectionId, isTrue); + + connectionIdManager.reset(); + + expect(connectionIdManager.connectionId, isNull); + expect(connectionIdManager.hasConnectionId, isFalse); + }); +} diff --git a/packages/stream_chat/test/src/core/http/interceptor/auth_interceptor_test.dart b/packages/stream_chat/test/src/core/http/interceptor/auth_interceptor_test.dart new file mode 100644 index 00000000..82dcdfb9 --- /dev/null +++ b/packages/stream_chat/test/src/core/http/interceptor/auth_interceptor_test.dart @@ -0,0 +1,270 @@ +import 'package:dio/dio.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/http/interceptor/auth_interceptor.dart'; +import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; +import 'package:stream_chat/src/core/http/token.dart'; +import 'package:stream_chat/src/core/http/token_manager.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; + +import '../../../mocks.dart'; + +void main() { + late StreamHttpClient client; + late TokenManager tokenManager; + late AuthInterceptor authInterceptor; + + setUp(() { + client = MockHttpClient(); + tokenManager = MockTokenManager(); + authInterceptor = AuthInterceptor(client, tokenManager); + }); + + test( + '`onRequest` should add userId, authToken, authType in the request', + () async { + final options = RequestOptions(path: 'test-path'); + final handler = RequestInterceptorHandler(); + + final headers = options.headers; + final queryParams = options.queryParameters; + expect(headers.containsKey('Authorization'), isFalse); + expect(headers.containsKey('stream-auth-type'), isFalse); + expect(queryParams.containsKey('user_id'), isFalse); + + final token = Token.development('test-user-id'); + when(() => tokenManager.loadToken(refresh: any(named: 'refresh'))) + .thenAnswer((_) async => token); + + authInterceptor.onRequest(options, handler); + + final updatedOptions = (await handler.future).data as RequestOptions; + final updateHeaders = updatedOptions.headers; + final updatedQueryParams = updatedOptions.queryParameters; + + expect(updateHeaders.containsKey('Authorization'), isTrue); + expect(updateHeaders['Authorization'], token.rawValue); + expect(updateHeaders.containsKey('stream-auth-type'), isTrue); + expect(updateHeaders['stream-auth-type'], token.authType.raw); + expect(updatedQueryParams.containsKey('user_id'), isTrue); + expect(updatedQueryParams['user_id'], token.userId); + + verify(() => tokenManager.loadToken(refresh: any(named: 'refresh'))) + .called(1); + verifyNoMoreInteractions(tokenManager); + }, + ); + + test( + '`onRequest` should reject with error if `tokenManager.loadToken` throws', + () async { + final options = RequestOptions(path: 'test-path'); + final handler = RequestInterceptorHandler(); + + authInterceptor.onRequest(options, handler); + + try { + await handler.future; + } catch (e) { + // need to cast it as the type is private in dio + var error = (e as dynamic).data; + expect(error, isA()); + error = (error as StreamChatDioError).error; + expect(error.code, ChatErrorCode.undefinedToken.code); + expect(error.message, ChatErrorCode.undefinedToken.message); + } + }, + ); + + test('`onError` should retry the request with refreshed token', () async { + const path = 'test-request-path'; + final options = RequestOptions(path: path); + const code = ChatErrorCode.tokenExpired; + final errorResponse = ErrorResponse() + ..code = code.code + ..message = code.message; + final response = Response( + requestOptions: options, + data: errorResponse.toJson(), + ); + final err = DioError(requestOptions: options, response: response); + final handler = ErrorInterceptorHandler(); + + when(() => tokenManager.isStatic).thenReturn(false); + + when(() => client.lock()).thenReturn(() {}); + + final token = Token.development('test-user-id'); + when(() => tokenManager.loadToken(refresh: true)) + .thenAnswer((_) async => token); + + when(() => client.unlock()).thenReturn(() {}); + + when(() => client.request( + path, + data: options.data, + onReceiveProgress: options.onReceiveProgress, + onSendProgress: options.onSendProgress, + queryParameters: options.queryParameters, + cancelToken: options.cancelToken, + options: any(named: 'options'), + )).thenAnswer((_) async => Response( + requestOptions: options, + statusCode: 200, + )); + + authInterceptor.onError(err, handler); + + final res = await handler.future; + + var data = res.data; + expect(data, isA()); + data = data as Response; + expect(data, isNotNull); + expect(data.statusCode, 200); + expect(data.requestOptions.path, path); + + verify(() => tokenManager.isStatic).called(1); + + verify(() => client.lock()).called(1); + + verify(() => tokenManager.loadToken(refresh: true)).called(1); + verifyNoMoreInteractions(tokenManager); + + verify(() => client.unlock()).called(1); + verify(() => client.request( + path, + data: options.data, + onReceiveProgress: options.onReceiveProgress, + onSendProgress: options.onSendProgress, + queryParameters: options.queryParameters, + cancelToken: options.cancelToken, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(client); + }); + + test( + '`onError` should reject with error if retried request throws', + () async { + const path = 'test-request-path'; + final options = RequestOptions(path: path); + const code = ChatErrorCode.tokenExpired; + final errorResponse = ErrorResponse() + ..code = code.code + ..message = code.message; + final response = Response( + requestOptions: options, + data: errorResponse.toJson(), + ); + final err = DioError(requestOptions: options, response: response); + final handler = ErrorInterceptorHandler(); + + when(() => tokenManager.isStatic).thenReturn(false); + + when(() => client.lock()).thenReturn(() {}); + + final token = Token.development('test-user-id'); + when(() => tokenManager.loadToken(refresh: true)) + .thenAnswer((_) async => token); + + when(() => client.unlock()).thenReturn(() {}); + + when(() => client.request( + path, + data: options.data, + onReceiveProgress: options.onReceiveProgress, + onSendProgress: options.onSendProgress, + queryParameters: options.queryParameters, + cancelToken: options.cancelToken, + options: any(named: 'options'), + )).thenThrow(err); + + authInterceptor.onError(err, handler); + + try { + await handler.future; + } catch (e) { + // need to cast it as the type is private in dio + final error = (e as dynamic).data; + expect(error, isA()); + } + + verify(() => tokenManager.isStatic).called(1); + + verify(() => client.lock()).called(1); + + verify(() => tokenManager.loadToken(refresh: true)).called(1); + verifyNoMoreInteractions(tokenManager); + + verify(() => client.unlock()).called(1); + verify(() => client.request( + path, + data: options.data, + onReceiveProgress: options.onReceiveProgress, + onSendProgress: options.onSendProgress, + queryParameters: options.queryParameters, + cancelToken: options.cancelToken, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(client); + }, + ); + + test( + '`onError` should reject with error if `tokenManager.isStatic` is true', + () async { + const path = 'test-request-path'; + final options = RequestOptions(path: path); + const code = ChatErrorCode.tokenExpired; + final errorResponse = ErrorResponse() + ..code = code.code + ..message = code.message; + final response = Response( + requestOptions: options, + data: errorResponse.toJson(), + ); + final err = DioError(requestOptions: options, response: response); + final handler = ErrorInterceptorHandler(); + + when(() => tokenManager.isStatic).thenReturn(true); + + authInterceptor.onError(err, handler); + + try { + await handler.future; + } catch (e) { + // need to cast it as the type is private in dio + final error = (e as dynamic).data; + expect(error, isA()); + final response = StreamChatNetworkError.fromDioError(error); + expect(response.errorCode, code); + } + + verify(() => tokenManager.isStatic).called(1); + verifyNoMoreInteractions(tokenManager); + }, + ); + + test( + '`onError` should reject with error if error is not a `tokenExpired error`', + () async { + const path = 'test-request-path'; + final options = RequestOptions(path: path); + final response = Response(requestOptions: options); + final err = DioError(requestOptions: options, response: response); + final handler = ErrorInterceptorHandler(); + + authInterceptor.onError(err, handler); + + try { + await handler.future; + } catch (e) { + // need to cast it as the type is private in dio + final error = (e as dynamic).data; + expect(error, isA()); + } + }, + ); +} diff --git a/packages/stream_chat/test/src/core/http/interceptor/connection_id_interceptor.dart b/packages/stream_chat/test/src/core/http/interceptor/connection_id_interceptor.dart new file mode 100644 index 00000000..9acabccf --- /dev/null +++ b/packages/stream_chat/test/src/core/http/interceptor/connection_id_interceptor.dart @@ -0,0 +1,67 @@ +import 'package:dio/dio.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/http/connection_id_manager.dart'; +import 'package:stream_chat/src/core/http/interceptor/connection_id_interceptor.dart'; +import 'package:test/test.dart'; + +import '../../../mocks.dart'; + +void main() { + late ConnectionIdManager connectionIdManager; + late ConnectionIdInterceptor connectionIdInterceptor; + + setUp(() { + connectionIdManager = MockConnectionIdManager(); + connectionIdInterceptor = ConnectionIdInterceptor(connectionIdManager); + }); + + test( + '`onRequest` should add connectionId in the request', + () async { + final options = RequestOptions(path: 'test-path'); + final handler = RequestInterceptorHandler(); + + final queryParams = options.queryParameters; + expect(queryParams.containsKey('connection_id'), isFalse); + + const connectionId = 'test-connection-id'; + when(() => connectionIdManager.hasConnectionId).thenReturn(true); + when(() => connectionIdManager.connectionId).thenReturn(connectionId); + + connectionIdInterceptor.onRequest(options, handler); + + final updatedOptions = (await handler.future).data as RequestOptions; + final updatedQueryParams = updatedOptions.queryParameters; + + expect(updatedQueryParams.containsKey('connection_id'), isTrue); + expect(updatedQueryParams['connection_id'], connectionId); + + verify(() => connectionIdManager.hasConnectionId).called(1); + verify(() => connectionIdManager.connectionId).called(1); + verifyNoMoreInteractions(connectionIdManager); + }, + ); + + test( + '`onRequest` should not add connectionId if `hasConnectionId` is false', + () async { + final options = RequestOptions(path: 'test-path'); + final handler = RequestInterceptorHandler(); + + final queryParams = options.queryParameters; + expect(queryParams.containsKey('connection_id'), isFalse); + + when(() => connectionIdManager.hasConnectionId).thenReturn(false); + + connectionIdInterceptor.onRequest(options, handler); + + final updatedOptions = (await handler.future).data as RequestOptions; + final updatedQueryParams = updatedOptions.queryParameters; + + expect(updatedQueryParams.containsKey('connection_id'), isFalse); + + verify(() => connectionIdManager.hasConnectionId).called(1); + verifyNoMoreInteractions(connectionIdManager); + }, + ); +} diff --git a/packages/stream_chat/test/src/core/http/stream_chat_dio_error_test.dart b/packages/stream_chat/test/src/core/http/stream_chat_dio_error_test.dart new file mode 100644 index 00000000..f13cf50d --- /dev/null +++ b/packages/stream_chat/test/src/core/http/stream_chat_dio_error_test.dart @@ -0,0 +1,20 @@ +import 'package:dio/dio.dart'; +import 'package:stream_chat/src/core/error/error.dart'; +import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart'; +import 'package:test/test.dart'; + +void main() { + test('should create a new instance of StreamChatDioError', () { + final error = StreamChatNetworkError(ChatErrorCode.inputError); + final options = RequestOptions(path: 'test-path'); + final dioError = StreamChatDioError( + error: error, + requestOptions: options, + ); + + expect(dioError, isA()); + expect(dioError, isNotNull); + expect(dioError.error, error); + expect(dioError.requestOptions, options); + }); +} diff --git a/packages/stream_chat/test/src/core/http/stream_http_client_options_test.dart b/packages/stream_chat/test/src/core/http/stream_http_client_options_test.dart new file mode 100644 index 00000000..a03434fa --- /dev/null +++ b/packages/stream_chat/test/src/core/http/stream_http_client_options_test.dart @@ -0,0 +1,53 @@ +import 'package:stream_chat/src/core/http/stream_http_client.dart'; +import 'package:stream_chat/src/location.dart'; +import 'package:test/test.dart'; + +void main() { + test('should return the all default set params', () { + const options = StreamHttpClientOptions(); + expect(options.location, isNull); + expect(options.baseUrl, 'https://chat-us-east-1.stream-io-api.com'); + expect(options.connectTimeout, const Duration(seconds: 6)); + expect(options.receiveTimeout, const Duration(seconds: 6)); + }); + + test('should override all the default set params', () { + const options = StreamHttpClientOptions( + baseUrl: 'base-url', + connectTimeout: Duration(seconds: 3), + receiveTimeout: Duration(seconds: 3), + ); + expect(options.location, isNull); + expect(options.baseUrl, 'base-url'); + expect(options.connectTimeout, const Duration(seconds: 3)); + expect(options.receiveTimeout, const Duration(seconds: 3)); + }); + + group('should create baseUrl according to provided location', () { + test('us-east', () { + const options = StreamHttpClientOptions(location: Location.usEast); + expect(options.location, isNotNull); + expect(options.baseUrl, 'https://chat-proxy-us-east.stream-io-api.com'); + }); + test('eu-west', () { + const options = StreamHttpClientOptions(location: Location.euWest); + expect(options.location, isNotNull); + expect(options.baseUrl, 'https://chat-proxy-dublin.stream-io-api.com'); + }); + test('mumbai', () { + const options = StreamHttpClientOptions(location: Location.mumbai); + expect(options.location, isNotNull); + expect(options.baseUrl, 'https://chat-proxy-mumbai.stream-io-api.com'); + }); + test('sydney', () { + const options = StreamHttpClientOptions(location: Location.sydney); + expect(options.location, isNotNull); + expect(options.baseUrl, 'https://chat-proxy-sydney.stream-io-api.com'); + }); + test('singapore', () { + const options = StreamHttpClientOptions(location: Location.singapore); + expect(options.location, isNotNull); + expect(options.baseUrl, 'https://chat-proxy-singapore.stream-io-api.com'); + }); + }); +} diff --git a/packages/stream_chat/test/src/core/http/stream_http_client_test.dart b/packages/stream_chat/test/src/core/http/stream_http_client_test.dart new file mode 100644 index 00000000..ece00efd --- /dev/null +++ b/packages/stream_chat/test/src/core/http/stream_http_client_test.dart @@ -0,0 +1,528 @@ +import 'package:dio/dio.dart'; +import 'package:logging/logging.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/error/error.dart'; +import 'package:stream_chat/src/core/http/connection_id_manager.dart'; +import 'package:stream_chat/src/core/http/interceptor/auth_interceptor.dart'; +import 'package:stream_chat/src/core/http/interceptor/connection_id_interceptor.dart'; +import 'package:stream_chat/src/core/http/interceptor/logging_interceptor.dart'; +import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; +import 'package:stream_chat/src/core/http/token_manager.dart'; +import 'package:test/test.dart'; + +import '../../mocks.dart'; + +void main() { + Response successResponse(String path) => Response( + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); + + DioError throwableError( + String path, { + StreamChatNetworkError? error, + bool streamChatDioError = false, + }) { + if (streamChatDioError) assert(error != null, ''); + final options = RequestOptions(path: path); + final data = ErrorResponse() + ..code = error?.code + ..statusCode = error?.statusCode + ..message = error?.message; + DioError? dioError; + if (streamChatDioError) { + dioError = StreamChatDioError(error: error!, requestOptions: options); + } else { + dioError = DioError( + error: error, + requestOptions: options, + response: Response( + requestOptions: options, + statusCode: data.statusCode, + data: data.toJson(), + ), + ); + } + return dioError; + } + + test('AuthInterceptor should be added if tokenManager is provided', () { + const apiKey = 'api-key'; + final client = StreamHttpClient(apiKey, tokenManager: TokenManager()); + + expect(client.httpClient.interceptors.length, 1); + expect(client.httpClient.interceptors.first, isA()); + }); + + test( + 'connectionIdInterceptor should be added if connectionIdManager is provided', + () { + const apiKey = 'api-key'; + final client = StreamHttpClient( + apiKey, + connectionIdManager: ConnectionIdManager(), + ); + + expect(client.httpClient.interceptors.length, 1); + expect( + client.httpClient.interceptors.first, + isA(), + ); + }, + ); + + test('loggingInterceptor should be added if logger is provided', () { + const apiKey = 'api-key'; + final client = StreamHttpClient( + apiKey, + logger: Logger('test-logger'), + ); + + expect(client.httpClient.interceptors.length, 1); + expect( + client.httpClient.interceptors.first, + isA(), + ); + }); + + test('loggingInterceptor should log requests', () async { + const apiKey = 'api-key'; + final logger = MockLogger(); + final client = StreamHttpClient(apiKey, logger: logger); + + try { + await client.get('path'); + } catch (_) {} + + verify(() => logger.info(any())).called(16); + }); + + test('loggingInterceptor should log error', () async { + const apiKey = 'api-key'; + final logger = MockLogger(); + final client = StreamHttpClient(apiKey, logger: logger); + + try { + await client.get('path'); + } catch (_) {} + + verify(() => logger.severe(any())).called(8); + }); + + test('`.lock` should lock the dio client', () async { + final client = StreamHttpClient('api-key'); + expect(client.httpClient.interceptors.requestLock.locked, isFalse); + client.lock(); + expect(client.httpClient.interceptors.requestLock.locked, isTrue); + }); + + test('`.unlock` should unlock the dio client', () async { + final client = StreamHttpClient('api-key'); + expect(client.httpClient.interceptors.requestLock.locked, isFalse); + client.lock(); + expect(client.httpClient.interceptors.requestLock.locked, isTrue); + client.unlock(); + expect(client.httpClient.interceptors.requestLock.locked, isFalse); + }); + + test('`.clear` should clear and unlock the dio client', () async { + final client = StreamHttpClient('api-key')..clear(); + expect(client.httpClient.interceptors.requestLock.locked, isFalse); + }); + + test('`.close` should close the dio client', () async { + final client = StreamHttpClient('api-key')..close(force: true); + try { + await client.get('path'); + } on StreamChatNetworkError catch (e) { + expect(e, isA()); + expect(e.message, "Dio can't establish new connection after closed."); + } + }); + + test('`.get` should return response successfully', () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-get-api-path'; + when(() => dio.get( + path, + options: any(named: 'options'), + )).thenAnswer((_) async => successResponse(path)); + + final res = await client.get(path); + + expect(res, isNotNull); + expect(res.statusCode, 200); + expect(res.requestOptions.path, path); + + verify(() => dio.get( + path, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }); + + test('`.get` should throw an instance of `StreamChatNetworkError`', () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-get-api-path'; + final error = throwableError( + path, + error: StreamChatNetworkError(ChatErrorCode.internalSystemError), + ); + when(() => dio.get( + path, + options: any(named: 'options'), + )).thenThrow(error); + + try { + await client.get(path); + } catch (e) { + expect(e, isA()); + expect(e, StreamChatNetworkError.fromDioError(error)); + } + + verify(() => dio.get( + path, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }); + + test('`.post` should return response successfully', () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-post-api-path'; + when(() => dio.post( + path, + options: any(named: 'options'), + )).thenAnswer((_) async => successResponse(path)); + + final res = await client.post(path); + + expect(res, isNotNull); + expect(res.statusCode, 200); + expect(res.requestOptions.path, path); + + verify(() => dio.post( + path, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }); + + test( + '`.post` should throw an instance of `StreamChatNetworkError`', + () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-post-api-path'; + final error = throwableError( + path, + error: StreamChatNetworkError(ChatErrorCode.internalSystemError), + ); + when(() => dio.post( + path, + options: any(named: 'options'), + )).thenThrow(error); + + try { + await client.post(path); + } catch (e) { + expect(e, isA()); + expect(e, StreamChatNetworkError.fromDioError(error)); + } + + verify(() => dio.post( + path, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }, + ); + + test('`.delete` should return response successfully', () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-delete-api-path'; + when(() => dio.delete( + path, + options: any(named: 'options'), + )).thenAnswer((_) async => successResponse(path)); + + final res = await client.delete(path); + + expect(res, isNotNull); + expect(res.statusCode, 200); + expect(res.requestOptions.path, path); + + verify(() => dio.delete( + path, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }); + + test( + '`.delete` should throw an instance of `StreamChatNetworkError`', + () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-delete-api-path'; + final error = throwableError( + path, + error: StreamChatNetworkError(ChatErrorCode.internalSystemError), + ); + when(() => dio.delete( + path, + options: any(named: 'options'), + )).thenThrow(error); + + try { + await client.delete(path); + } catch (e) { + expect(e, isA()); + expect(e, StreamChatNetworkError.fromDioError(error)); + } + + verify(() => dio.delete( + path, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }, + ); + + test('`.patch` should return response successfully', () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-patch-api-path'; + when(() => dio.patch( + path, + options: any(named: 'options'), + )).thenAnswer((_) async => successResponse(path)); + + final res = await client.patch(path); + + expect(res, isNotNull); + expect(res.statusCode, 200); + expect(res.requestOptions.path, path); + + verify(() => dio.patch( + path, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }); + + test( + '`.patch` should throw an instance of `StreamChatNetworkError`', + () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-patch-api-path'; + final error = throwableError( + path, + error: StreamChatNetworkError(ChatErrorCode.internalSystemError), + ); + when(() => dio.patch( + path, + options: any(named: 'options'), + )).thenThrow(error); + + try { + await client.patch(path); + } catch (e) { + expect(e, isA()); + expect(e, StreamChatNetworkError.fromDioError(error)); + } + + verify(() => dio.patch( + path, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }, + ); + + test('`.put` should return response successfully', () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-put-api-path'; + when(() => dio.put( + path, + options: any(named: 'options'), + )).thenAnswer((_) async => successResponse(path)); + + final res = await client.put(path); + + expect(res, isNotNull); + expect(res.statusCode, 200); + expect(res.requestOptions.path, path); + + verify(() => dio.put( + path, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }); + + test( + '`.put` should throw an instance of `StreamChatNetworkError`', + () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-put-api-path'; + final error = throwableError( + path, + error: StreamChatNetworkError(ChatErrorCode.internalSystemError), + ); + when(() => dio.put( + path, + options: any(named: 'options'), + )).thenThrow(error); + + try { + await client.put(path); + } catch (e) { + expect(e, isA()); + expect(e, StreamChatNetworkError.fromDioError(error)); + } + + verify(() => dio.put( + path, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }, + ); + + test('`.postFile` should return response successfully', () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-delete-api-path'; + final file = MultipartFile.fromBytes([]); + + when(() => dio.post( + path, + data: any(named: 'data'), + options: any(named: 'options'), + )).thenAnswer((_) async => successResponse(path)); + + final res = await client.postFile(path, file); + + expect(res, isNotNull); + expect(res.statusCode, 200); + expect(res.requestOptions.path, path); + + verify(() => dio.post( + path, + data: any(named: 'data'), + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }); + + test( + '`.postFile` should throw an instance of `StreamChatNetworkError`', + () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-post-file-api-path'; + final file = MultipartFile.fromBytes([]); + + final error = throwableError( + path, + error: StreamChatNetworkError(ChatErrorCode.internalSystemError), + ); + when(() => dio.post( + path, + data: any(named: 'data'), + options: any(named: 'options'), + )).thenThrow(error); + + try { + await client.postFile(path, file); + } catch (e) { + expect(e, isA()); + expect(e, StreamChatNetworkError.fromDioError(error)); + } + + verify(() => dio.post( + path, + data: any(named: 'data'), + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }, + ); + + test('`.request` should return response successfully', () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-request-api-path'; + when(() => dio.request( + path, + options: any(named: 'options'), + )).thenAnswer((_) async => successResponse(path)); + + final res = await client.request(path); + + expect(res, isNotNull); + expect(res.statusCode, 200); + expect(res.requestOptions.path, path); + + verify(() => dio.request( + path, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }); + + test( + '`.request` should throw an instance of `StreamChatNetworkError`', + () async { + final dio = MockDio(); + final client = StreamHttpClient('api-key', dio: dio); + + const path = 'test-put-api-path'; + final error = throwableError( + path, + streamChatDioError: true, + error: StreamChatNetworkError(ChatErrorCode.internalSystemError), + ); + when(() => dio.request( + path, + options: any(named: 'options'), + )).thenThrow(error); + + try { + await client.request(path); + } catch (e) { + expect(e, isA()); + expect(e, error.error); + } + + verify(() => dio.request( + path, + options: any(named: 'options'), + )).called(1); + verifyNoMoreInteractions(dio); + }, + ); +} diff --git a/packages/stream_chat/test/src/core/http/token_manager_test.dart b/packages/stream_chat/test/src/core/http/token_manager_test.dart new file mode 100644 index 00000000..a1a38fa5 --- /dev/null +++ b/packages/stream_chat/test/src/core/http/token_manager_test.dart @@ -0,0 +1,141 @@ +import 'package:stream_chat/src/core/http/token.dart'; +import 'package:stream_chat/src/core/http/token_manager.dart'; +import 'package:test/test.dart'; + +void main() { + late TokenManager tokenManager; + + setUp(() { + tokenManager = TokenManager(); + }); + + tearDown(() { + tokenManager.reset(); + }); + + test('`setTokenOrProvider` should set token', () async { + expect(tokenManager.userId, isNull); + + const userId = 'test-user-id'; + final token = Token.development(userId); + final returnedToken = await tokenManager.setTokenOrProvider( + userId, + token: token, + ); + + expect(returnedToken, token); + expect(tokenManager.userId, userId); + expect(tokenManager.isStatic, isTrue); + }); + + test('`setTokenOrProvider` should set tokenProvider', () async { + expect(tokenManager.userId, isNull); + + const userId = 'test-user-id'; + Future tokenProvider(String userId) async => + Token.development(userId).rawValue; + final returnedToken = await tokenManager.setTokenOrProvider( + userId, + provider: tokenProvider, + ); + + expect(returnedToken, isNotNull); + expect(tokenManager.userId, userId); + expect(tokenManager.isStatic, isFalse); + }); + + test( + '`setTokenOrProvider` should throw if both token and provider is not provided', + () async { + expect(tokenManager.userId, isNull); + + const userId = 'test-user-id'; + try { + await tokenManager.setTokenOrProvider(userId); + } catch (e) { + expect(e, isA()); + } + }, + ); + + test( + '`setTokenOrProvider` should throw if both token and provider is provided', + () async { + expect(tokenManager.userId, isNull); + + const userId = 'test-user-id'; + final token = Token.development(userId); + Future tokenProvider(String userId) async => + Token.development(userId).rawValue; + try { + await tokenManager.setTokenOrProvider( + userId, + token: token, + provider: tokenProvider, + ); + } catch (e) { + expect(e, isA()); + } + }, + ); + + test( + '`.loadToken` should return token set via `setToken`', + () async { + const userId = 'test-user-id'; + final token = Token.development(userId); + await tokenManager.setTokenOrProvider(userId, token: token); + + final returnedToken = await tokenManager.loadToken(); + expect(returnedToken, token); + }, + ); + + test( + '`.loadToken` should return token set via `setProvider`', + () async { + const userId = 'test-user-id'; + final token = Token.development(userId); + Future tokenProvider(String userId) async => token.rawValue; + await tokenManager.setTokenOrProvider(userId, provider: tokenProvider); + + final returnedToken = await tokenManager.loadToken(); + expect(returnedToken, token); + }, + ); + + test( + '`.loadToken` should return refreshed token set via `setProvider`', + () async { + const userId = 'test-user-id'; + final token = Token.development(userId); + final refreshToken = Token.development(userId); + + var refresh = false; + + Future tokenProvider(String userId) async { + if (refresh) return refreshToken.rawValue; + return token.rawValue; + } + + await tokenManager.setTokenOrProvider(userId, provider: tokenProvider); + + final returnedToken = await tokenManager.loadToken(); + expect(returnedToken, token); + + refresh = true; + final returnedRefreshToken = await tokenManager.loadToken(refresh: true); + expect(returnedRefreshToken, refreshToken); + }, + ); + + test('`.reset` should reset the tokenManager', () async { + const userId = 'test-user-id'; + final token = Token.development(userId); + await tokenManager.setTokenOrProvider(userId, token: token); + expect(tokenManager.userId, userId); + + tokenManager.reset(); + expect(tokenManager.userId, isNull); + }); +} diff --git a/packages/stream_chat/test/src/core/http/token_test.dart b/packages/stream_chat/test/src/core/http/token_test.dart new file mode 100644 index 00000000..b448884e --- /dev/null +++ b/packages/stream_chat/test/src/core/http/token_test.dart @@ -0,0 +1,57 @@ +import 'package:stream_chat/src/core/http/token.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; + +void main() { + test('`.anonymous` should create anonymous token with passed userId', () { + const userId = 'test-user-id'; + final token = Token.anonymous(userId: userId); + expect(token, isNotNull); + expect(token.userId, userId); + expect(token.rawValue, isEmpty); + expect(token.authType, AuthType.anonymous); + expect(token.authType.raw, AuthType.anonymous.raw); + }); + + test('`.fromRawValue` should create token from rawValue', () { + const userId = 'test-user-id'; + final devToken = Token.development(userId); + final token = Token.fromRawValue(devToken.rawValue); + expect(token, devToken); + }); + + test('`.fromRawValue` should throw if does not contain `user_id`', () { + const badToken = 'bad-token-without-a-user-id'; + try { + Token.fromRawValue(badToken); + } catch (e) { + expect(e, isA()); + } + }); + + test('`.development` should create a dev-token with provided user-id', () { + const userId = 'test-user-id'; + final token = Token.development(userId); + expect(token, isNotNull); + expect(token.userId, userId); + expect(token.rawValue, isNotEmpty); + expect(token.authType, AuthType.jwt); + expect(token.authType.raw, AuthType.jwt.raw); + }); + + test( + '`.guest` should create a guest-token with provided user and provider', + () async { + final user = User(id: 'test-user-id'); + Future provider(User user) async => + Token.development(user.id).rawValue; + + final token = await Token.guest(user, provider); + expect(token, isNotNull); + expect(token.userId, user.id); + expect(token.rawValue, isNotEmpty); + expect(token.authType, AuthType.jwt); + expect(token.authType.raw, AuthType.jwt.raw); + }, + ); +} From cb9a8a23a97e2073d808d5f407ac76f6e5a524af Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 4 Jun 2021 16:25:12 +0530 Subject: [PATCH 329/599] add timer_helper_test.dart Signed-off-by: Sahil Kumar --- .../test/src/ws/timer_helper_test.dart | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 packages/stream_chat/test/src/ws/timer_helper_test.dart diff --git a/packages/stream_chat/test/src/ws/timer_helper_test.dart b/packages/stream_chat/test/src/ws/timer_helper_test.dart new file mode 100644 index 00000000..ecafd6d2 --- /dev/null +++ b/packages/stream_chat/test/src/ws/timer_helper_test.dart @@ -0,0 +1,98 @@ +import 'package:stream_chat/src/ws/timer_helper.dart'; +import 'package:test/test.dart'; + +void main() { + late TimerHelper timerHelper; + + setUp(() { + timerHelper = TimerHelper(); + }); + + tearDown(() { + timerHelper.cancelAllTimers(); + }); + + test('setTimer', () async { + expect(timerHelper.hasTimers, isFalse); + + var count = 0; + void callback() => count += 1; + + timerHelper.setTimer( + const Duration(milliseconds: 500), + callback, + ); + + expect(count, 0); + await Future.delayed(const Duration(milliseconds: 500)); + expect(count, 1); + + expect(timerHelper.hasTimers, isTrue); + }); + + test('setImmediateTimer', () async { + var count = 0; + void callback() => count += 1; + + timerHelper.setTimer( + const Duration(milliseconds: 500), + callback, + immediate: true, + ); + + expect(count, 1); + await Future.delayed(const Duration(milliseconds: 500)); + expect(count, 2); + }); + + test('setPeriodicTimer', () async { + expect(timerHelper.hasTimers, isFalse); + var count = 0; + void callback() => count += 1; + + timerHelper.setTimer( + const Duration(milliseconds: 500), + callback, + ); + + expect(count, 0); + await Future.delayed(const Duration(milliseconds: 500)); + expect(count, 1); + + expect(timerHelper.hasTimers, isTrue); + }); + + test('setImmediatePeriodicTimer', () async { + var count = 0; + void callback(_) => count += 1; + + timerHelper.setPeriodicTimer( + const Duration(milliseconds: 500), + callback, + immediate: true, + ); + + expect(count, 1); + await Future.delayed(const Duration(milliseconds: 500)); + expect(count, 2); + }); + + test('cancelTimer', () { + expect(timerHelper.hasTimers, isFalse); + final id = timerHelper.setTimer(const Duration(seconds: 3), () {}); + expect(timerHelper.hasTimers, isTrue); + timerHelper.cancelTimer(id); + expect(timerHelper.hasTimers, isFalse); + }); + + test('cancelAllTimers', () { + expect(timerHelper.hasTimers, isFalse); + timerHelper + ..setTimer(const Duration(seconds: 3), () {}) + ..setTimer(const Duration(seconds: 6), () {}) + ..setTimer(const Duration(seconds: 9), () {}); + expect(timerHelper.hasTimers, isTrue); + timerHelper.cancelAllTimers(); + expect(timerHelper.hasTimers, isFalse); + }); +} From dbb98811ab2e56df1949c69dea2e1a2e936449ab Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 4 Jun 2021 16:26:35 +0530 Subject: [PATCH 330/599] push mocks.dart Signed-off-by: Sahil Kumar --- packages/stream_chat/test/src/mocks.dart | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/stream_chat/test/src/mocks.dart b/packages/stream_chat/test/src/mocks.dart index 98e84d77..be4f15e5 100644 --- a/packages/stream_chat/test/src/mocks.dart +++ b/packages/stream_chat/test/src/mocks.dart @@ -1,6 +1,34 @@ +import 'package:dio/dio.dart'; +import 'package:logging/logging.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/http/connection_id_manager.dart'; +import 'package:stream_chat/src/core/http/stream_http_client.dart'; +import 'package:stream_chat/src/core/http/token_manager.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; class MockWebSocketChannel extends Mock implements WebSocketChannel {} class MockWebSocketSink extends Mock implements WebSocketSink {} + +class MockDio extends Mock implements Dio { + BaseOptions? _options; + + @override + BaseOptions get options => _options ??= BaseOptions(); + + Interceptors? _interceptors; + + @override + Interceptors get interceptors => _interceptors ??= Interceptors(); +} + +class MockLogger extends Mock implements Logger { + @override + Level get level => Level.ALL; +} + +class MockHttpClient extends Mock implements StreamHttpClient {} + +class MockTokenManager extends Mock implements TokenManager {} + +class MockConnectionIdManager extends Mock implements ConnectionIdManager {} From b64aa70b73aad969f49575d160a530ec8124838e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 4 Jun 2021 16:27:37 +0530 Subject: [PATCH 331/599] remove, refactor old tests Signed-off-by: Sahil Kumar --- .../test/src/api/channel_test.dart | 2724 ----------------- .../test/src/api/web_socket_stub_test.dart | 11 - .../test/src/api/websocket_test.dart | 348 --- .../stream_chat/test/src/client_test.dart | 1205 -------- .../test/src/models/action_test.dart | 2 +- .../test/src/models/attachment_test.dart | 4 +- .../test/src/models/channel_state_test.dart | 10 +- .../test/src/models/channel_test.dart | 2 +- .../test/src/models/command_test.dart | 2 +- .../test/src/models/device_test.dart | 2 +- .../test/src/models/event_test.dart | 4 +- .../test/src/models/member_test.dart | 4 +- .../test/src/models/message_test.dart | 8 +- .../test/src/models/reaction_test.dart | 4 +- .../test/src/models/read_test.dart | 4 +- .../test/src/models/serialization_test.dart | 8 +- .../test/src/models/user_test.dart | 2 +- 17 files changed, 28 insertions(+), 4316 deletions(-) delete mode 100644 packages/stream_chat/test/src/api/channel_test.dart delete mode 100644 packages/stream_chat/test/src/api/web_socket_stub_test.dart delete mode 100644 packages/stream_chat/test/src/api/websocket_test.dart delete mode 100644 packages/stream_chat/test/src/client_test.dart diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart deleted file mode 100644 index 12e84fbc..00000000 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ /dev/null @@ -1,2724 +0,0 @@ -import 'dart:convert'; - -import 'package:dio/dio.dart'; -import 'package:dio/native_imp.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat/src/core/api/requests.dart'; -import 'package:stream_chat/src/client.dart'; -import 'package:stream_chat/src/event_type.dart'; -import 'package:stream_chat/src/core/models/event.dart'; -import 'package:stream_chat/src/core/models/message.dart'; -import 'package:stream_chat/src/core/models/own_user.dart'; -import 'package:stream_chat/src/core/models/reaction.dart'; -import 'package:stream_chat/stream_chat.dart'; -import 'package:test/test.dart'; - -class MockDio extends Mock implements DioForNative {} - -class FakeRequestOptions extends Fake implements RequestOptions {} - -class MockAttachmentUploader extends Mock implements AttachmentFileUploader {} - -class MockHttpClientAdapter extends Mock implements HttpClientAdapter {} - -void main() { - group('src/api/channel', () { - group('message', () { - test('sendMessage', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - final message = Message(text: 'hey', id: 'test'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState()), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - when( - () => mockDio.post( - '/channels/messaging/testid/message', - data: {'message': message.toJson()}, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({'message': message}), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.sendMessage(message); - - verify(() => - mockDio.post('/channels/messaging/testid/message', data: { - 'message': message.toJson(), - })).called(1); - }); - - test('markRead', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - - final channelClient = client.channel('messaging', id: 'testid'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState()), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - await channelClient.watch(); - - when( - () => mockDio.post( - '/channels/messaging/testid/read', - data: {}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.markRead(); - - verify(() => mockDio.post('/channels/messaging/testid/read', - data: {})).called(1); - }); - - test('getReplies', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - const pagination = PaginationParams(); - - when(() => mockDio.get('/messages/messageid/replies', - queryParameters: pagination.toJson())).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.getReplies('messageid', pagination); - - verify(() => mockDio.get('/messages/messageid/replies', - queryParameters: pagination.toJson())).called(1); - }); - - test('sendAction', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - await channelClient.watch(); - - final data = {'test': true}; - - when(() => mockDio.post('/messages/messageid/action', data: { - 'id': 'testid', - 'type': 'messaging', - 'form_data': data, - 'message_id': 'messageid', - })).thenAnswer((_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.sendAction(Message(id: 'messageid'), data); - - verify(() => mockDio.post('/messages/messageid/action', data: { - 'id': 'testid', - 'type': 'messaging', - 'form_data': data, - 'message_id': 'messageid', - })).called(1); - }); - - test('getMessagesById', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - final messageIds = ['a', 'b']; - - when(() => mockDio.get('/channels/messaging/testid/messages', - queryParameters: {'ids': messageIds.join(',')})).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.getMessagesById(messageIds); - - verify(() => mockDio.get('/channels/messaging/testid/messages', - queryParameters: {'ids': messageIds.join(',')})).called(1); - }); - - test('sendFile', () async { - final mockDio = MockDio(); - final mockUploader = MockAttachmentUploader(); - - const file = AttachmentFile( - path: 'filePath/fileName.pdf', - size: 100, - ); - const channelId = 'testId'; - const channelType = 'messaging'; - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - attachmentFileUploader: mockUploader, - ); - final channelClient = client.channel(channelType, id: channelId); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState()), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - when(() => mockUploader.sendFile(file, channelId, channelType)) - .thenAnswer((_) async => SendFileResponse()); - - await channelClient.sendFile(file); - - verify(() => mockUploader.sendFile(file, channelId, channelType)) - .called(1); - }); - - test('sendImage', () async { - final mockDio = MockDio(); - final mockUploader = MockAttachmentUploader(); - - const image = AttachmentFile( - path: 'imagePath/imageName.jpeg', - size: 100, - ); - const channelId = 'testId'; - const channelType = 'messaging'; - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - attachmentFileUploader: mockUploader, - ); - final channelClient = client.channel(channelType, id: channelId); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState()), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - when(() => mockUploader.sendImage(image, channelId, channelType)) - .thenAnswer((_) async => SendImageResponse()); - - await channelClient.sendImage(image); - - verify(() => mockUploader.sendImage(image, channelId, channelType)) - .called(1); - }); - - test('deleteFile', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - const url = 'url'; - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState()), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - when( - () => mockDio.delete( - '/channels/messaging/testid/file', - queryParameters: {'url': url}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.deleteFile(url); - - verify(() => mockDio.delete('/channels/messaging/testid/file', - queryParameters: {'url': url})).called(1); - }); - - test('deleteImage', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - const url = 'url'; - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState()), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - when( - () => mockDio.delete( - '/channels/messaging/testid/image', - queryParameters: {'url': url}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.deleteImage(url); - - verify(() => mockDio.delete('/channels/messaging/testid/image', - queryParameters: {'url': url})).called(1); - }); - - test('pinMessage should throw argument error', () { - final client = StreamChatClient('api-key'); - - final channelClient = client.channel('messaging', id: 'testid'); - - final message = Message(text: 'Hello'); - - expect( - () => channelClient.pinMessage(message, 'InvalidType'), - throwsArgumentError, - ); - }); - - test('should be pinned successfully', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - final message = Message(text: 'Hello', id: 'test'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState()), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - when( - () => mockDio.post( - '/messages/${message.id}', - data: anything, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({'message': message}), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.pinMessage(message, 30); - - verify(() => - mockDio.post('/messages/${message.id}', data: anything)) - .called(1); - }); - - test('should be pinned successfully with null timeout', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - final message = Message(text: 'Hello', id: 'test'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState()), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - when( - () => mockDio.post( - '/messages/${message.id}', - data: anything, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({'message': message}), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.pinMessage(message); - - verify(() => - mockDio.post('/messages/${message.id}', data: anything)) - .called(1); - }); - - test('should be unpinned successfully', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - final message = Message(text: 'Hello', id: 'test'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState()), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - when( - () => mockDio.post( - '/messages/${message.id}', - data: anything, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({'message': message}), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.unpinMessage(message); - - verify(() => - mockDio.post('/messages/${message.id}', data: anything)) - .called(1); - }); - }); - - test('sendEvent', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - - when( - () => mockDio.post( - any(), - data: any(named: 'data'), - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - await channelClient.watch(); - - const event = Event(type: EventType.any); - - when( - () => mockDio.post( - '/channels/messaging/testid/event', - data: {'event': event.toJson()}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.sendEvent(event); - - verify(() => mockDio.post('/channels/messaging/testid/event', - data: {'event': event.toJson()})).called(1); - }); - - test('keyStroke', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - - when( - () => mockDio.post( - any(), - data: any(named: 'data'), - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - await channelClient.watch(); - - const event = Event(type: EventType.typingStart); - - when( - () => mockDio.post( - '/channels/messaging/testid/event', - data: {'event': event.toJson()}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.keyStroke(); - - verify(() => mockDio.post('/channels/messaging/testid/event', - data: {'event': event.toJson()})).called(1); - }); - - test('stopTyping', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - await channelClient.watch(); - - const event = Event(type: EventType.typingStop); - - when( - () => mockDio.post( - '/channels/messaging/testid/event', - data: {'event': event.toJson()}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.stopTyping(); - - verify(() => mockDio.post('/channels/messaging/testid/event', - data: {'event': event.toJson()})).called(1); - }); - - group('reactions', () { - test('sendReaction', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - - final user = OwnUser(id: 'test-id'); - - client.state.user = user; - - final message = Message(id: 'messageid'); - const reactionType = 'test'; - final reaction = Reaction(type: reactionType); - final channelClient = client.channel('messaging', id: 'testid'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - await channelClient.watch(); - - when( - () => mockDio.post( - '/messages/${message.id}/reaction', - data: { - 'reaction': { - 'type': reactionType, - }, - 'enforce_unique': false, - }, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({ - 'message': message, - 'reaction': reaction, - }), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.sendReaction( - message, - reactionType, - ); - - verify( - () => mockDio.post('/messages/messageid/reaction', data: { - 'reaction': { - 'type': reactionType, - }, - 'enforce_unique': false, - })).called(1); - }); - - test('deleteReaction', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - - client.state.user = OwnUser(id: 'test-id'); - - final channelClient = client.channel('messaging', id: 'testid'); - - when( - () => mockDio.delete('/messages/messageid/reaction/test'), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.deleteReaction( - Message( - id: 'messageid', - ), - Reaction( - type: 'test', - createdAt: DateTime.now(), - user: User( - id: client.state.user?.id ?? '', - ), - ), - ); - - verify(() => - mockDio.delete('/messages/messageid/reaction/test')) - .called(1); - }); - - test('getReactions', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - const pagination = PaginationParams(); - - when( - () => mockDio.get( - '/messages/messageid/reactions', - queryParameters: pagination.toJson(), - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.getReactions('messageid', pagination); - - verify(() => mockDio.get('/messages/messageid/reactions', - queryParameters: pagination.toJson())).called(1); - }); - }); - - group('channel', () { - test('addMembers', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - final channelModel = ChannelModel(cid: 'messaging:testid'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState(channel: channelModel)), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - final members = [Member(userId: 'vishal')]; - final memberIds = members.map((e) => e.userId!).toList(); - final message = Message(text: 'test'); - - when( - () => mockDio.post( - '/channels/messaging/testid', - data: {'add_members': memberIds, 'message': message.toJson()}, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({ - 'members': members, - 'message': message, - 'channel': channelModel, - }), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.addMembers(memberIds, message); - - verify(() => mockDio.post('/channels/messaging/testid', - data: {'add_members': memberIds, 'message': message.toJson()})) - .called(1); - }); - - test('acceptInvite', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - final channelModel = ChannelModel(cid: 'messaging:testid'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState(channel: channelModel)), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - final message = Message(text: 'test'); - - when( - () => mockDio.post( - '/channels/messaging/testid', - data: {'accept_invite': true, 'message': message.toJson()}, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({ - 'message': message, - 'channel': channelModel, - }), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.acceptInvite(message); - - verify(() => mockDio.post('/channels/messaging/testid', - data: {'accept_invite': true, 'message': message.toJson()})) - .called(1); - }); - - group('query', () { - test('without id', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging'); - final options = { - 'watch': true, - 'state': false, - 'presence': true, - }; - - when(() => mockDio.post( - '/channels/messaging/query', - data: options, - )).thenAnswer( - (_) async => Response( - data: r''' - { - "channel": { - "id": "!members-0LOcD0mZtTan60zHobLmELjdndXsonnBVNzZnB5mTt0", - "type": "messaging", - "cid": "messaging:!members-0LOcD0mZtTan60zHobLmELjdndXsonnBVNzZnB5mTt0", - "last_message_at": "2020-01-28T22:17:31.204287Z", - "created_at": "2020-01-28T22:17:31.00187Z", - "updated_at": "2020-01-28T22:17:31.00187Z", - "created_by": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "frozen": false, - "member_count": 2, - "config": { - "created_at": "2020-01-29T12:59:14.291912835Z", - "updated_at": "2020-01-29T12:59:14.291912991Z", - "name": "messaging", - "typing_events": true, - "read_events": true, - "connect_events": true, - "search": true, - "reactions": true, - "replies": true, - "mutes": true, - "uploads": true, - "url_enrichment": true, - "message_retention": "infinite", - "max_message_length": 5000, - "automod": "disabled", - "automod_behavior": "flag", - "commands": [ - { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]", - "set": "fun_set" - } - ] - }, - "name": "Mia Denys", - "image": "https://randomuser.me/api/portraits/women/2.jpg" - }, - "messages": [ - { - "id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", - "text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", - "html": "\u003cp\u003e\u003ca href=\"https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA\" rel=\"nofollow\"\u003ehttps://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA\u003c/a\u003e\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [ - { - "type": "video", - "author_name": "GIPHY", - "title": "The Lion King Disney GIF - Find \u0026 Share on GIPHY", - "title_link": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "text": "Discover \u0026 share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "image_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "thumb_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "asset_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4", - "og_scrape_url": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA" - } - ], - "latest_reactions": [ - { - "message_id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", - "user_id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "type": "love", - "score": 1, - "created_at": "2020-01-28T22:17:31.128376Z", - "updated_at": "2020-01-28T22:17:31.128376Z" - } - ], - "own_reactions": [], - "reaction_counts": { - "love": 1 - }, - "reaction_scores": { - "love": 1 - }, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.107978Z", - "updated_at": "2020-01-28T22:17:31.130506Z", - "mentioned_users": [] - }, - { - "id": "16e19c46-fb96-4f89-8031-d5bd2ce3bd64", - "text": "Few can name a topfull mother that isn't a breezeless damage.", - "html": "\u003cp\u003eFew can name a topfull mother that isn’t a breezeless damage.\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.153518Z", - "updated_at": "2020-01-28T22:17:31.153518Z", - "mentioned_users": [] - }, - { - "id": "38b1b252-c9a6-4aea-a39e-8de3b7f7604b", - "text": "https://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog", - "html": "\u003cp\u003e\u003ca href=\"https://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog\" rel=\"nofollow\"\u003ehttps://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog\u003c/a\u003e\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "attachments": [ - { - "type": "video", - "author_name": "GIPHY", - "title": "Moustache Thumbs Up GIF - Find \u0026 Share on GIPHY", - "title_link": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.gif", - "text": "Discover \u0026 share this Pouce Leve GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "image_url": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.gif", - "thumb_url": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.gif", - "asset_url": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.mp4", - "og_scrape_url": "https://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog" - } - ], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.155428Z", - "updated_at": "2020-01-28T22:17:31.155428Z", - "mentioned_users": [] - }, - { - "id": "5c7d0c68-72d5-4027-b6b7-711b342d0fc4", - "text": "The carbons could be said to resemble smartish hoods.", - "html": "\u003cp\u003eThe carbons could be said to resemble smartish hoods.\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 1, - "created_at": "2020-01-28T22:17:31.157811Z", - "updated_at": "2020-01-28T22:17:31.157811Z", - "mentioned_users": [] - }, - { - "id": "5b02535a-0c3c-45fa-9cf8-16f840c5123f", - "text": "Their software was, in this moment, a prolix feature.", - "html": "\u003cp\u003eTheir software was, in this moment, a prolix feature.\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 1, - "created_at": "2020-01-28T22:17:31.158391Z", - "updated_at": "2020-01-28T22:17:31.158391Z", - "mentioned_users": [] - } - ], - "watcher_count": 1, - "read": [ - { - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "last_read": "2020-01-28T22:17:31.016937728Z" - }, - { - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "last_read": "2020-01-28T22:17:31.018856448Z" - } - ], - "members": [ - { - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "role": "member", - "created_at": "2020-01-28T22:17:31.005135Z", - "updated_at": "2020-01-28T22:17:31.005135Z" - }, - { - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "role": "owner", - "created_at": "2020-01-28T22:17:31.005135Z", - "updated_at": "2020-01-28T22:17:31.005135Z" - } - ] - } - ''', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - final response = await channelClient.query(options: options); - - verify(() => mockDio.post( - '/channels/messaging/query', - data: options, - )).called(1); - expect(channelClient.id, response.channel?.id); - expect(channelClient.cid, response.channel?.cid); - }); - - test('with id', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - final options = {'state': false}; - - when(() => mockDio.post('/channels/messaging/testid/query', - data: options)).thenAnswer( - (_) async => Response( - data: r''' - { - "channel": { - "id": "!members-0LOcD0mZtTan60zHobLmELjdndXsonnBVNzZnB5mTt0", - "type": "messaging", - "cid": "messaging:!members-0LOcD0mZtTan60zHobLmELjdndXsonnBVNzZnB5mTt0", - "last_message_at": "2020-01-28T22:17:31.204287Z", - "created_at": "2020-01-28T22:17:31.00187Z", - "updated_at": "2020-01-28T22:17:31.00187Z", - "created_by": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "frozen": false, - "member_count": 2, - "config": { - "created_at": "2020-01-29T12:59:14.291912835Z", - "updated_at": "2020-01-29T12:59:14.291912991Z", - "name": "messaging", - "typing_events": true, - "read_events": true, - "connect_events": true, - "search": true, - "reactions": true, - "replies": true, - "mutes": true, - "uploads": true, - "url_enrichment": true, - "message_retention": "infinite", - "max_message_length": 5000, - "automod": "disabled", - "automod_behavior": "flag", - "commands": [ - { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]", - "set": "fun_set" - } - ] - }, - "name": "Mia Denys", - "image": "https://randomuser.me/api/portraits/women/2.jpg" - }, - "messages": [ - { - "id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", - "text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", - "html": "\u003cp\u003e\u003ca href=\"https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA\" rel=\"nofollow\"\u003ehttps://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA\u003c/a\u003e\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [ - { - "type": "video", - "author_name": "GIPHY", - "title": "The Lion King Disney GIF - Find \u0026 Share on GIPHY", - "title_link": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "text": "Discover \u0026 share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "image_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "thumb_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "asset_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4", - "og_scrape_url": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA" - } - ], - "latest_reactions": [ - { - "message_id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", - "user_id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "type": "love", - "score": 1, - "created_at": "2020-01-28T22:17:31.128376Z", - "updated_at": "2020-01-28T22:17:31.128376Z" - } - ], - "own_reactions": [], - "reaction_counts": { - "love": 1 - }, - "reaction_scores": { - "love": 1 - }, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.107978Z", - "updated_at": "2020-01-28T22:17:31.130506Z", - "mentioned_users": [] - }, - { - "id": "16e19c46-fb96-4f89-8031-d5bd2ce3bd64", - "text": "Few can name a topfull mother that isn't a breezeless damage.", - "html": "\u003cp\u003eFew can name a topfull mother that isn’t a breezeless damage.\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.153518Z", - "updated_at": "2020-01-28T22:17:31.153518Z", - "mentioned_users": [] - }, - { - "id": "38b1b252-c9a6-4aea-a39e-8de3b7f7604b", - "text": "https://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog", - "html": "\u003cp\u003e\u003ca href=\"https://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog\" rel=\"nofollow\"\u003ehttps://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog\u003c/a\u003e\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "attachments": [ - { - "type": "video", - "author_name": "GIPHY", - "title": "Moustache Thumbs Up GIF - Find \u0026 Share on GIPHY", - "title_link": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.gif", - "text": "Discover \u0026 share this Pouce Leve GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "image_url": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.gif", - "thumb_url": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.gif", - "asset_url": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.mp4", - "og_scrape_url": "https://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog" - } - ], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.155428Z", - "updated_at": "2020-01-28T22:17:31.155428Z", - "mentioned_users": [] - }, - { - "id": "5c7d0c68-72d5-4027-b6b7-711b342d0fc4", - "text": "The carbons could be said to resemble smartish hoods.", - "html": "\u003cp\u003eThe carbons could be said to resemble smartish hoods.\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 1, - "created_at": "2020-01-28T22:17:31.157811Z", - "updated_at": "2020-01-28T22:17:31.157811Z", - "mentioned_users": [] - }, - { - "id": "5b02535a-0c3c-45fa-9cf8-16f840c5123f", - "text": "Their software was, in this moment, a prolix feature.", - "html": "\u003cp\u003eTheir software was, in this moment, a prolix feature.\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 1, - "created_at": "2020-01-28T22:17:31.158391Z", - "updated_at": "2020-01-28T22:17:31.158391Z", - "mentioned_users": [] - } - ], - "watcher_count": 1, - "read": [ - { - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "last_read": "2020-01-28T22:17:31.016937728Z" - }, - { - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "last_read": "2020-01-28T22:17:31.018856448Z" - } - ], - "members": [ - { - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "role": "member", - "created_at": "2020-01-28T22:17:31.005135Z", - "updated_at": "2020-01-28T22:17:31.005135Z" - }, - { - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "role": "owner", - "created_at": "2020-01-28T22:17:31.005135Z", - "updated_at": "2020-01-28T22:17:31.005135Z" - } - ] - } - ''', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.query(options: options); - - verify(() => mockDio.post('/channels/messaging/testid/query', - data: options)).called(1); - }); - }); - - test('create', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging'); - final options = { - 'watch': false, - 'state': false, - 'presence': false, - }; - - when(() => mockDio.post('/channels/messaging/query', - data: options)).thenAnswer( - (_) async => Response( - data: r''' - { - "channel": { - "id": "!members-0LOcD0mZtTan60zHobLmELjdndXsonnBVNzZnB5mTt0", - "type": "messaging", - "cid": "messaging:!members-0LOcD0mZtTan60zHobLmELjdndXsonnBVNzZnB5mTt0", - "last_message_at": "2020-01-28T22:17:31.204287Z", - "created_at": "2020-01-28T22:17:31.00187Z", - "updated_at": "2020-01-28T22:17:31.00187Z", - "created_by": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "frozen": false, - "member_count": 2, - "config": { - "created_at": "2020-01-29T12:59:14.291912835Z", - "updated_at": "2020-01-29T12:59:14.291912991Z", - "name": "messaging", - "typing_events": true, - "read_events": true, - "connect_events": true, - "search": true, - "reactions": true, - "replies": true, - "mutes": true, - "uploads": true, - "url_enrichment": true, - "message_retention": "infinite", - "max_message_length": 5000, - "automod": "disabled", - "automod_behavior": "flag", - "commands": [ - { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]", - "set": "fun_set" - } - ] - }, - "name": "Mia Denys", - "image": "https://randomuser.me/api/portraits/women/2.jpg" - }, - "messages": [ - { - "id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", - "text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", - "html": "\u003cp\u003e\u003ca href=\"https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA\" rel=\"nofollow\"\u003ehttps://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA\u003c/a\u003e\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [ - { - "type": "video", - "author_name": "GIPHY", - "title": "The Lion King Disney GIF - Find \u0026 Share on GIPHY", - "title_link": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "text": "Discover \u0026 share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "image_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "thumb_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "asset_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4", - "og_scrape_url": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA" - } - ], - "latest_reactions": [ - { - "message_id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", - "user_id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "type": "love", - "score": 1, - "created_at": "2020-01-28T22:17:31.128376Z", - "updated_at": "2020-01-28T22:17:31.128376Z" - } - ], - "own_reactions": [], - "reaction_counts": { - "love": 1 - }, - "reaction_scores": { - "love": 1 - }, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.107978Z", - "updated_at": "2020-01-28T22:17:31.130506Z", - "mentioned_users": [] - }, - { - "id": "16e19c46-fb96-4f89-8031-d5bd2ce3bd64", - "text": "Few can name a topfull mother that isn't a breezeless damage.", - "html": "\u003cp\u003eFew can name a topfull mother that isn’t a breezeless damage.\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.153518Z", - "updated_at": "2020-01-28T22:17:31.153518Z", - "mentioned_users": [] - }, - { - "id": "38b1b252-c9a6-4aea-a39e-8de3b7f7604b", - "text": "https://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog", - "html": "\u003cp\u003e\u003ca href=\"https://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog\" rel=\"nofollow\"\u003ehttps://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog\u003c/a\u003e\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "attachments": [ - { - "type": "video", - "author_name": "GIPHY", - "title": "Moustache Thumbs Up GIF - Find \u0026 Share on GIPHY", - "title_link": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.gif", - "text": "Discover \u0026 share this Pouce Leve GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "image_url": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.gif", - "thumb_url": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.gif", - "asset_url": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.mp4", - "og_scrape_url": "https://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog" - } - ], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.155428Z", - "updated_at": "2020-01-28T22:17:31.155428Z", - "mentioned_users": [] - }, - { - "id": "5c7d0c68-72d5-4027-b6b7-711b342d0fc4", - "text": "The carbons could be said to resemble smartish hoods.", - "html": "\u003cp\u003eThe carbons could be said to resemble smartish hoods.\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 1, - "created_at": "2020-01-28T22:17:31.157811Z", - "updated_at": "2020-01-28T22:17:31.157811Z", - "mentioned_users": [] - }, - { - "id": "5b02535a-0c3c-45fa-9cf8-16f840c5123f", - "text": "Their software was, in this moment, a prolix feature.", - "html": "\u003cp\u003eTheir software was, in this moment, a prolix feature.\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 1, - "created_at": "2020-01-28T22:17:31.158391Z", - "updated_at": "2020-01-28T22:17:31.158391Z", - "mentioned_users": [] - } - ], - "watcher_count": 1, - "read": [ - { - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "last_read": "2020-01-28T22:17:31.016937728Z" - }, - { - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "last_read": "2020-01-28T22:17:31.018856448Z" - } - ], - "members": [ - { - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "role": "member", - "created_at": "2020-01-28T22:17:31.005135Z", - "updated_at": "2020-01-28T22:17:31.005135Z" - }, - { - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "role": "owner", - "created_at": "2020-01-28T22:17:31.005135Z", - "updated_at": "2020-01-28T22:17:31.005135Z" - } - ] - } - ''', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - final response = await channelClient.create(); - - verify(() => mockDio.post('/channels/messaging/query', - data: options)).called(1); - expect(channelClient.id, response.channel?.id); - expect(channelClient.cid, response.channel?.cid); - }); - - test('watch', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging'); - final options = { - 'watch': true, - 'state': true, - 'presence': true, - }; - - when(() => mockDio.post('/channels/messaging/query', - data: options)).thenAnswer( - (_) async => Response( - data: r''' - { - "channel": { - "id": "!members-0LOcD0mZtTan60zHobLmELjdndXsonnBVNzZnB5mTt0", - "type": "messaging", - "cid": "messaging:!members-0LOcD0mZtTan60zHobLmELjdndXsonnBVNzZnB5mTt0", - "last_message_at": "2020-01-28T22:17:31.204287Z", - "created_at": "2020-01-28T22:17:31.00187Z", - "updated_at": "2020-01-28T22:17:31.00187Z", - "created_by": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "frozen": false, - "member_count": 2, - "config": { - "created_at": "2020-01-29T12:59:14.291912835Z", - "updated_at": "2020-01-29T12:59:14.291912991Z", - "name": "messaging", - "typing_events": true, - "read_events": true, - "connect_events": true, - "search": true, - "reactions": true, - "replies": true, - "mutes": true, - "uploads": true, - "url_enrichment": true, - "message_retention": "infinite", - "max_message_length": 5000, - "automod": "disabled", - "automod_behavior": "flag", - "commands": [ - { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]", - "set": "fun_set" - } - ] - }, - "name": "Mia Denys", - "image": "https://randomuser.me/api/portraits/women/2.jpg" - }, - "messages": [ - { - "id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", - "text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", - "html": "\u003cp\u003e\u003ca href=\"https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA\" rel=\"nofollow\"\u003ehttps://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA\u003c/a\u003e\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [ - { - "type": "video", - "author_name": "GIPHY", - "title": "The Lion King Disney GIF - Find \u0026 Share on GIPHY", - "title_link": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "text": "Discover \u0026 share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "image_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "thumb_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "asset_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4", - "og_scrape_url": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA" - } - ], - "latest_reactions": [ - { - "message_id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", - "user_id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "type": "love", - "score": 1, - "created_at": "2020-01-28T22:17:31.128376Z", - "updated_at": "2020-01-28T22:17:31.128376Z" - } - ], - "own_reactions": [], - "reaction_counts": { - "love": 1 - }, - "reaction_scores": { - "love": 1 - }, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.107978Z", - "updated_at": "2020-01-28T22:17:31.130506Z", - "mentioned_users": [] - }, - { - "id": "16e19c46-fb96-4f89-8031-d5bd2ce3bd64", - "text": "Few can name a topfull mother that isn't a breezeless damage.", - "html": "\u003cp\u003eFew can name a topfull mother that isn’t a breezeless damage.\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.153518Z", - "updated_at": "2020-01-28T22:17:31.153518Z", - "mentioned_users": [] - }, - { - "id": "38b1b252-c9a6-4aea-a39e-8de3b7f7604b", - "text": "https://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog", - "html": "\u003cp\u003e\u003ca href=\"https://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog\" rel=\"nofollow\"\u003ehttps://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog\u003c/a\u003e\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "attachments": [ - { - "type": "video", - "author_name": "GIPHY", - "title": "Moustache Thumbs Up GIF - Find \u0026 Share on GIPHY", - "title_link": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.gif", - "text": "Discover \u0026 share this Pouce Leve GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "image_url": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.gif", - "thumb_url": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.gif", - "asset_url": "https://media.giphy.com/media/73Sjhw0N4hyog/giphy.mp4", - "og_scrape_url": "https://giphy.com/gifs/beard-muscle-73Sjhw0N4hyog" - } - ], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.155428Z", - "updated_at": "2020-01-28T22:17:31.155428Z", - "mentioned_users": [] - }, - { - "id": "5c7d0c68-72d5-4027-b6b7-711b342d0fc4", - "text": "The carbons could be said to resemble smartish hoods.", - "html": "\u003cp\u003eThe carbons could be said to resemble smartish hoods.\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 1, - "created_at": "2020-01-28T22:17:31.157811Z", - "updated_at": "2020-01-28T22:17:31.157811Z", - "mentioned_users": [] - }, - { - "id": "5b02535a-0c3c-45fa-9cf8-16f840c5123f", - "text": "Their software was, in this moment, a prolix feature.", - "html": "\u003cp\u003eTheir software was, in this moment, a prolix feature.\u003c/p\u003e\n", - "type": "regular", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 1, - "created_at": "2020-01-28T22:17:31.158391Z", - "updated_at": "2020-01-28T22:17:31.158391Z", - "mentioned_users": [] - } - ], - "watcher_count": 1, - "read": [ - { - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "last_read": "2020-01-28T22:17:31.016937728Z" - }, - { - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "last_read": "2020-01-28T22:17:31.018856448Z" - } - ], - "members": [ - { - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "role": "member", - "created_at": "2020-01-28T22:17:31.005135Z", - "updated_at": "2020-01-28T22:17:31.005135Z" - }, - { - "user": { - "id": "spring-voice-7", - "role": "user", - "created_at": "2020-01-28T22:17:30.834135Z", - "updated_at": "2020-01-28T22:17:31.186771Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?id=spring-voice-7\u0026name=Spring+voice", - "name": "Spring voice" - }, - "role": "owner", - "created_at": "2020-01-28T22:17:31.005135Z", - "updated_at": "2020-01-28T22:17:31.005135Z" - } - ] - } - ''', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - final response = await channelClient.watch({'presence': true}); - - verify(() => mockDio.post('/channels/messaging/query', - data: options)).called(1); - expect(channelClient.id, response.channel?.id); - expect(channelClient.cid, response.channel?.cid); - }); - - test('stopWatching', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - - when( - () => mockDio.post( - '/channels/messaging/testid/stop-watching', - data: {}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.stopWatching(); - - verify(() => mockDio.post( - '/channels/messaging/testid/stop-watching', - data: {}, - )).called(1); - }); - - test('update', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - final channelModel = ChannelModel(cid: 'messaging:testid'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState(channel: channelModel)), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - final message = Message(text: 'test'); - - when( - () => mockDio.post( - '/channels/messaging/testid', - data: { - 'message': message.toJson(), - 'data': {'test': true}, - }, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({ - 'channel': channelModel, - 'message': message, - }), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.update({'test': true}, message); - - verify( - () => mockDio.post('/channels/messaging/testid', data: { - 'message': message.toJson(), - 'data': {'test': true}, - }), - ).called(1); - }); - - test('delete', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - - when( - () => mockDio.delete('/channels/messaging/testid'), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.delete(); - - verify(() => mockDio.delete('/channels/messaging/testid')) - .called(1); - }); - - test('truncate', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - - when( - () => mockDio.post('/channels/messaging/testid/truncate'), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.truncate(); - - verify(() => - mockDio.post('/channels/messaging/testid/truncate')) - .called(1); - }); - - test('rejectInvite', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - final channelModel = ChannelModel(cid: 'messaging:testid'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState(channel: channelModel)), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - final message = Message(text: 'test'); - - when( - () => mockDio.post( - '/channels/messaging/testid', - data: {'reject_invite': true, 'message': message.toJson()}, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({ - 'message': message, - 'channel': channelModel, - }), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.rejectInvite(message); - - verify(() => mockDio.post('/channels/messaging/testid', - data: {'reject_invite': true, 'message': message.toJson()})) - .called(1); - }); - - test('inviteMembers', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - final channelModel = ChannelModel(cid: 'messaging:testid'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState(channel: channelModel)), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - final members = [Member(userId: 'vishal')]; - final memberIds = members.map((e) => e.userId!).toList(); - final message = Message(text: 'test'); - - when( - () => mockDio.post( - '/channels/messaging/testid', - data: {'invites': memberIds, 'message': message.toJson()}, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({ - 'members': members, - 'message': message, - 'channel': channelModel, - }), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.inviteMembers(memberIds, message); - - verify(() => mockDio.post('/channels/messaging/testid', - data: {'invites': memberIds, 'message': message.toJson()})) - .called(1); - }); - - test('removeMembers', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - final channelModel = ChannelModel(cid: 'messaging:testid'); - - when(() => mockDio.post( - any(), - data: any(named: 'data'), - )).thenAnswer((_) async => Response( - data: jsonEncode(ChannelState(channel: channelModel)), - statusCode: 200, - requestOptions: FakeRequestOptions(), - )); - - await channelClient.watch(); - - final members = [Member(userId: 'vishal')]; - final memberIds = members.map((e) => e.userId!).toList(); - final message = Message(text: 'test'); - - when( - () => mockDio.post( - '/channels/messaging/testid', - data: {'remove_members': memberIds, 'message': message.toJson()}, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({ - 'members': members, - 'message': message, - 'channel': channelModel, - }), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.removeMembers(memberIds, message); - - verify(() => mockDio.post('/channels/messaging/testid', data: { - 'remove_members': memberIds, - 'message': message.toJson() - })).called(1); - }); - - test('hide', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - - when( - () => mockDio.post( - any(), - data: any(named: 'data'), - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - await channelClient.watch(); - - when( - () => mockDio.post( - '/channels/messaging/testid/hide', - data: {'clear_history': true}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.hide(clearHistory: true); - - verify(() => mockDio.post('/channels/messaging/testid/hide', - data: {'clear_history': true})).called(1); - }); - - test('show', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - - when( - () => mockDio.post( - any(), - data: any(named: 'data'), - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - await channelClient.watch(); - - when( - () => mockDio.post('/channels/messaging/testid/show'), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.show(); - - verify(() => mockDio.post('/channels/messaging/testid/show')) - .called(1); - }); - - test('banUser', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - - when( - () => mockDio.post( - any(), - data: any(named: 'data'), - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - await channelClient.watch(); - - when( - () => mockDio.post('/moderation/ban', data: { - 'test': true, - 'target_user_id': 'test-id', - 'type': 'messaging', - 'id': 'testid', - }), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - final options = {'test': true}; - await channelClient.banUser('test-id', options); - - verify(() => mockDio.post('/moderation/ban', data: { - 'test': true, - 'target_user_id': 'test-id', - 'type': 'messaging', - 'id': 'testid', - })).called(1); - }); - - test('unbanUser', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - tokenProvider: (_) async => '', - ); - final channelClient = client.channel('messaging', id: 'testid'); - - when( - () => mockDio.post( - any(), - data: any(named: 'data'), - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - await channelClient.watch(); - - when( - () => mockDio.delete( - '/moderation/ban', - queryParameters: { - 'target_user_id': 'test-id', - 'type': 'messaging', - 'id': 'testid', - }, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.unbanUser('test-id'); - - verify( - () => mockDio.delete('/moderation/ban', queryParameters: { - 'target_user_id': 'test-id', - 'type': 'messaging', - 'id': 'testid', - }), - ).called(1); - }); - }); - }); -} diff --git a/packages/stream_chat/test/src/api/web_socket_stub_test.dart b/packages/stream_chat/test/src/api/web_socket_stub_test.dart deleted file mode 100644 index 77a67421..00000000 --- a/packages/stream_chat/test/src/api/web_socket_stub_test.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:test/test.dart'; -import 'package:stream_chat/src/api/web_socket_channel_stub.dart'; - -void main() { - test('src/api/web_socket_stub_test', () { - expect( - () => openConnection('fakeurl'), - throwsA(isA()), - ); - }); -} diff --git a/packages/stream_chat/test/src/api/websocket_test.dart b/packages/stream_chat/test/src/api/websocket_test.dart deleted file mode 100644 index 11270a9e..00000000 --- a/packages/stream_chat/test/src/api/websocket_test.dart +++ /dev/null @@ -1,348 +0,0 @@ -import 'dart:async'; - -import 'package:logging/logging.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat/src/ws/connection_status.dart'; -import 'package:stream_chat/src/ws/websocket.dart'; -import 'package:stream_chat/src/core/models/event.dart'; -import 'package:stream_chat/src/core/models/user.dart'; -import 'package:stream_chat/stream_chat.dart'; -import 'package:test/test.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; - -class Functions { - WebSocketChannel connectFunc( - String? url, { - Iterable? protocols, - }) => - WebSocketChannel.connect(Uri()); - - void handleFunc(Event event) {} -} - -class MockFunctions extends Mock implements Functions {} - -class MockWSChannel extends Mock implements WebSocketChannel {} - -class MockWSSink extends Mock implements WebSocketSink {} - -class FakeEvent extends Fake implements Event {} - -void main() { - group('src/api/websocket', () { - setUpAll(() { - registerFallbackValue(FakeEvent()); - }); - - test('should connect with correct parameters', () async { - final connectFunc = MockFunctions().connectFunc; - final ws = WebSocket( - baseUrl: 'baseurl', - user: User(id: 'testid'), - _logger: Logger('ws'), - connectParams: {'test': 'true'}, - connectPayload: {'payload': 'test'}, - handler: print, - connectFunc: connectFunc, - ); - final mockWSChannel = MockWSChannel(); - final streamController = StreamController.broadcast(); - const computedUrl = - 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - - when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); - when(() => mockWSChannel.stream).thenAnswer( - (_) => streamController.stream, - ); - - final timer = Timer.periodic( - const Duration(milliseconds: 100), - (_) => streamController.sink.add('{}'), - ); - - await ws.connect(); - - verify(() => connectFunc(computedUrl)).called(1); - expect(ws.connectionStatus, ConnectionStatus.connected); - - await streamController.close(); - timer.cancel(); - }); - }); - - test('should connect with correct parameters and handle events', () async { - final handleFunc = MockFunctions().handleFunc; - final connectFunc = MockFunctions().connectFunc; - final ws = WebSocket( - baseUrl: 'baseurl', - user: User(id: 'testid'), - _logger: Logger('ws'), - connectParams: {'test': 'true'}, - connectPayload: {'payload': 'test'}, - handler: handleFunc, - connectFunc: connectFunc, - ); - final mockWSChannel = MockWSChannel(); - final streamController = StreamController.broadcast(); - const computedUrl = - 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - - when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); - when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - - final connect = ws.connect().then((_) { - streamController.sink.add('{}'); - return Future.delayed(const Duration(milliseconds: 200)); - }).then((value) { - verify(() => connectFunc(computedUrl)).called(1); - verify(() => handleFunc(any())).called(greaterThan(0)); - - return streamController.close(); - }); - - streamController.sink.add('{}'); - - return connect; - }); - - test('should close correctly the controller', () async { - final handleFunc = MockFunctions().handleFunc; - final connectFunc = MockFunctions().connectFunc; - final ws = WebSocket( - baseUrl: 'baseurl', - user: User(id: 'testid'), - _logger: Logger('ws'), - connectParams: {'test': 'true'}, - connectPayload: {'payload': 'test'}, - handler: handleFunc, - connectFunc: connectFunc, - ); - final mockWSChannel = MockWSChannel(); - final streamController = StreamController.broadcast(); - const computedUrl = - 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - - final mockWSSink = MockWSSink(); - when(() => mockWSChannel.sink).thenAnswer((_) => mockWSSink); - when(() => mockWSSink.close(any(), any())).thenAnswer((_) async => null); - when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - - final connect = ws.connect().then((_) { - streamController.sink.add('{}'); - return Future.delayed(const Duration(milliseconds: 200)); - }).then((value) { - verify(() => connectFunc(computedUrl)).called(1); - verify(() => handleFunc(any())).called(greaterThan(0)); - - return streamController.close(); - }); - - streamController.sink.add('{}'); - - return connect; - }); - - test('should close correctly the controller while connecting', () async { - final handleFunc = MockFunctions().handleFunc; - - final connectFunc = MockFunctions().connectFunc; - - final ws = WebSocket( - baseUrl: 'baseurl', - user: User(id: 'testid'), - _logger: Logger('ws'), - connectParams: {'test': 'true'}, - connectPayload: {'payload': 'test'}, - handler: handleFunc, - connectFunc: connectFunc, - ); - - final mockWSChannel = MockWSChannel(); - - final streamController = StreamController.broadcast(); - - const computedUrl = - 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - - final mockWSSink = MockWSSink(); - when(() => mockWSChannel.sink).thenAnswer((_) => mockWSSink); - when(() => mockWSSink.close(any(), any())).thenAnswer((_) async => null); - when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - - ws.connect(); - await ws.disconnect(); - streamController.add('{}'); - - verify(() => connectFunc(computedUrl)).called(1); - verifyNever(() => handleFunc(any())); - - addTearDown(streamController.close); - }); - - test('should run correctly health check', () async { - final handleFunc = MockFunctions().handleFunc; - final connectFunc = MockFunctions().connectFunc; - final ws = WebSocket( - baseUrl: 'baseurl', - user: User(id: 'testid'), - _logger: Logger('ws'), - connectParams: {'test': 'true'}, - connectPayload: {'payload': 'test'}, - handler: handleFunc, - connectFunc: connectFunc, - ); - final mockWSChannel = MockWSChannel(); - final mockWSSink = MockWSSink(); - final streamController = StreamController.broadcast(); - const computedUrl = - 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - - when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - when(() => mockWSChannel.sink).thenAnswer((_) => mockWSSink); - when(() => mockWSSink.close(any(), any())).thenAnswer((_) async => null); - - final timer = Timer.periodic( - const Duration(milliseconds: 1000), - (_) => streamController.sink.add('{}'), - ); - - final connect = ws.connect().then((_) { - streamController.sink.add('{}'); - return Future.delayed(const Duration(milliseconds: 200)); - }).then((value) async { - verify(() => mockWSSink.add("{'type': 'health.check'}")) - .called(greaterThan(0)); - - timer.cancel(); - await streamController.close(); - return mockWSSink.close(); - }); - - streamController.sink.add('{}'); - - return connect; - }); - - test('should run correctly reconnection check', () async { - final handleFunc = MockFunctions().handleFunc; - final connectFunc = MockFunctions().connectFunc; - Logger.root.level = Level.ALL; - final ws = WebSocket( - baseUrl: 'baseurl', - user: User(id: 'testid'), - _logger: Logger('ws'), - connectParams: {'test': 'true'}, - connectPayload: {'payload': 'test'}, - handler: handleFunc, - connectFunc: connectFunc, - reconnectionMonitorTimeout: 1, - ); - final mockWSChannel = MockWSChannel(); - final mockWSSink = MockWSSink(); - var streamController = StreamController.broadcast(); - const computedUrl = - 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - - when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - when(() => mockWSChannel.sink).thenAnswer((_) => mockWSSink); - when(() => mockWSSink.close(any(), any())).thenAnswer((_) async => null); - - final connect = ws.connect().then((_) { - streamController.sink.add('{}'); - streamController.close(); - streamController = StreamController.broadcast(); - streamController.sink.add('{}'); - return Future.delayed(const Duration(milliseconds: 200)); - }).then((value) async { - verify(() => mockWSSink.add("{'type': 'health.check'}")) - .called(greaterThan(0)); - - verify(() => connectFunc(computedUrl)).called(2); - - await streamController.close(); - return mockWSSink.close(); - }); - - streamController.sink.add('{}'); - - return connect; - }); - - test('should close correctly the controller', () async { - final handleFunc = MockFunctions().handleFunc; - final connectFunc = MockFunctions().connectFunc; - final ws = WebSocket( - baseUrl: 'baseurl', - user: User(id: 'testid'), - _logger: Logger('ws'), - connectParams: {'test': 'true'}, - connectPayload: {'payload': 'test'}, - handler: handleFunc, - connectFunc: connectFunc, - ); - final mockWSChannel = MockWSChannel(); - final mockWSSink = MockWSSink(); - final streamController = StreamController.broadcast(); - const computedUrl = - 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - - when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - when(() => mockWSChannel.sink).thenAnswer((_) => mockWSSink); - when(() => mockWSSink.close(any(), any())).thenAnswer((_) async => null); - - final connect = ws.connect().then((_) { - streamController.sink.add('{}'); - return Future.delayed(const Duration(milliseconds: 200)); - }).then((value) async { - await ws.disconnect(); - verify(mockWSSink.close).called(greaterThan(0)); - - await streamController.close(); - await mockWSSink.close(); - }); - - streamController.sink.add('{}'); - - return connect; - }); - - test('should throw an error', () async { - final connectFunc = MockFunctions().connectFunc; - final ws = WebSocket( - baseUrl: 'baseurl', - user: User(id: 'testid'), - _logger: Logger('ws'), - connectParams: {'test': 'true'}, - connectPayload: {'payload': 'test'}, - handler: print, - connectFunc: connectFunc, - ); - final mockWSChannel = MockWSChannel(); - final streamController = StreamController.broadcast(); - const computedUrl = - 'wss://baseurl/connect?test=true&json=%7B%22payload%22%3A%22test%22%2C%22user_details%22%3A%7B%22id%22%3A%22testid%22%7D%7D'; - - when(() => connectFunc(computedUrl)).thenAnswer((_) => mockWSChannel); - when(() => mockWSChannel.sink).thenAnswer((_) => MockWSSink()); - when(() => mockWSChannel.stream).thenAnswer((_) => streamController.stream); - - Future.delayed( - const Duration(milliseconds: 1000), - () => streamController.sink.addError('test error'), - ); - - try { - expect(await ws.connect(), throwsA(isA())); - } catch (e) { - verify(() => connectFunc(computedUrl)).called(greaterThanOrEqualTo(1)); - streamController.close(); - } - }); -} diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart deleted file mode 100644 index 13506f66..00000000 --- a/packages/stream_chat/test/src/client_test.dart +++ /dev/null @@ -1,1205 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:dio/dio.dart'; -import 'package:dio/native_imp.dart'; -import 'package:logging/logging.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat/src/core/api/requests.dart'; -import 'package:stream_chat/src/client.dart'; -import 'package:stream_chat/src/core/models/channel_model.dart'; -import 'package:stream_chat/src/core/models/filter.dart'; -import 'package:stream_chat/src/core/models/message.dart'; -import 'package:stream_chat/src/core/models/user.dart'; -import 'package:test/test.dart'; - -class MockDio extends Mock implements DioForNative {} - -class FakeRequestOptions extends Fake implements RequestOptions {} - -class MockHttpClientAdapter extends Mock implements HttpClientAdapter {} - -class Functions { - Future tokenProvider(String userId) async => ''; -} - -class MockFunctions extends Mock implements Functions {} - -void main() { - group('src/client', () { - setUpAll(() { - registerFallbackValue(FakeRequestOptions()); - registerFallbackValue>(const Stream.empty()); - registerFallbackValue>(Future.value()); - }); - - group('constructor', () { - final log = []; - - dynamic overridePrint(testFn()) => () { - log.clear(); - final spec = ZoneSpecification(print: (_, __, ___, String msg) { - // Add to log instead of printing to stdout - log.add(msg); - }); - return Zone.current.fork(specification: spec).run(testFn); - }; - - tearDown(log.clear); - - test('should create the object correctly', () { - final client = StreamChatClient('api-key'); - - expect(client.baseURL, 'chat-us-east-1.stream-io-api.com'); - expect(client.apiKey, 'api-key'); - expect(client.logLevel, Level.WARNING); - expect(client.httpClient._options.connectTimeout, 6000); - expect(client.httpClient._options.receiveTimeout, 6000); - }); - - test('should create the object correctly', overridePrint(() { - void logHandler(LogRecord record) { - print(record.message); - } - - final client = StreamChatClient( - 'api-key', - connectTimeout: const Duration(seconds: 10), - receiveTimeout: const Duration(seconds: 12), - logLevel: Level.INFO, - baseURL: 'test.com', - logHandlerFunction: logHandler, - ); - - expect(client.baseURL, 'test.com'); - expect(client.apiKey, 'api-key'); - expect(Logger.root.level, Level.INFO); - expect(client.httpClient._options.connectTimeout, 10000); - expect(client.httpClient._options.receiveTimeout, 12000); - - client.logger.warning('test'); - client.logger.config('test config'); - - expect( - [log[log.length - 2], log[log.length - 1]], - ['instantiating new client', 'test'], - ); - })); - - test('Channel', () { - final client = StreamChatClient('test'); - final data = {'test': 1}; - final channelClient = client.channel('type', id: 'id', extraData: data); - expect(channelClient.type, 'type'); - expect(channelClient.id, 'id'); - }); - }); - - group('queryChannelsOnline', () { - test('should pass right default parameters', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - final queryParams = { - 'payload': json.encode({ - 'filter_conditions': null, - 'sort': null, - 'state': true, - 'watch': true, - 'presence': false, - 'limit': 10, - 'offset': 0, - }), - }; - - when( - () => mockDio.get('/channels', queryParameters: queryParams), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.queryChannelsOnline(waitForConnect: false); - - verify(() => - mockDio.get('/channels', queryParameters: queryParams)) - .called(1); - }); - - test('should pass right parameters', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - final queryFilter = Filter.in_('id', const ['test']); - final sortOptions = >[]; - final options = {'state': false, 'watch': false, 'presence': true}; - const paginationParams = PaginationParams(offset: 2); - - final queryParams = { - 'payload': json.encode({ - 'filter_conditions': queryFilter, - 'sort': sortOptions, - } - ..addAll(options) - ..addAll(paginationParams - .toJson() - .map((key, value) => MapEntry(key, value as Object)))), - }; - - when( - () => mockDio.get('/channels', queryParameters: queryParams), - ).thenAnswer( - (_) async => Response( - data: '{"channels":[]}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.queryChannelsOnline( - filter: queryFilter, - sort: sortOptions, - _options: options, - paginationParams: paginationParams, - waitForConnect: false, - ); - - verify(() => - mockDio.get('/channels', queryParameters: queryParams)) - .called(1); - }); - }); - - group('search', () { - test('should pass right default parameters', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - final filter = Filter.in_('cid', const ['messaging:testId']); - const query = 'hello'; - final queryParams = { - 'payload': json.encode({ - 'filter_conditions': filter, - 'query': query, - }), - }; - - when( - () => mockDio.get('/search', queryParameters: queryParams), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.search(filter, query: query); - - verify(() => - mockDio.get('/search', queryParameters: queryParams)) - .called(1); - }); - - test('should pass right parameters', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - final filters = Filter.in_('id', const ['test']); - const sortOptions = [SortOption('name')]; - const query = 'query'; - final queryParams = { - 'payload': json.encode({ - 'filter_conditions': filters, - 'query': query, - 'sort': sortOptions, - 'limit': 10, - 'offset': 0, - }), - }; - - when( - () => mockDio.get('/search', queryParameters: queryParams), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.search( - filters, - sort: sortOptions, - query: query, - paginationParams: const PaginationParams(), - ); - - verify( - () => mockDio.get('/search', queryParameters: queryParams), - ).called(1); - }); - }); - - group('devices', () { - test('addDevice', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when( - () => mockDio.post('/devices', data: { - 'id': 'test-id', - 'push_provider': 'firebase', - }), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.addDevice('test-id', PushProvider.firebase); - - verify( - () => mockDio.post( - '/devices', - data: {'id': 'test-id', 'push_provider': 'firebase'}, - ), - ).called(1); - }); - - test('getDevices', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when(() => mockDio.get('/devices')).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.getDevices(); - - verify(() => mockDio.get('/devices')).called(1); - }); - - test('removeDevice', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when( - () => mockDio.delete( - '/devices', - queryParameters: {'id': 'test-id'}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.removeDevice('test-id'); - - verify( - () => mockDio.delete( - '/devices', - queryParameters: {'id': 'test-id'}, - ), - ).called(1); - }); - }); - - test('devToken', () { - final client = StreamChatClient('api-key'); - final token = client.devToken('test'); - - expect( - token, - '''eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidGVzdCJ9.devtoken''', - ); - }); - - group('queryUsers', () { - test('should pass right default parameters', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - final queryParams = { - 'payload': json.encode({ - 'filter_conditions': null, - 'sort': null, - 'presence': false, - }), - }; - - when( - () => mockDio.get('/users', queryParameters: queryParams), - ).thenAnswer( - (_) async => Response( - data: '{"users":[]}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.queryUsers(); - - verify( - () => mockDio.get( - '/users', - queryParameters: queryParams, - ), - ).called(1); - }); - - test('should pass right parameters', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - final queryFilter = Filter.in_('id', const ['test']); - const sortOptions = []; - final options = {'presence': true}; - final queryParams = { - 'payload': json.encode({ - 'filter_conditions': queryFilter, - 'sort': sortOptions, - }..addAll(options)), - }; - - when( - () => mockDio.get('/users', queryParameters: queryParams), - ).thenAnswer( - (_) async => Response( - data: '{"users":[]}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.queryUsers( - filter: queryFilter, - sort: sortOptions, - _options: options, - ); - - verify(() => - mockDio.get('/users', queryParameters: queryParams)) - .called(1); - }); - }); - - group('user', () { - test('connectUser should throw exception', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when( - () => mockDio.post( - '/moderation/flag', - data: {'target_user_id': 'test-id'}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.flagUser('test-id'); - - verify(() => mockDio.post('/moderation/flag', - data: {'target_user_id': 'test-id'})).called(1); - }); - - test('flagUser', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - expect(() => client.connectUserWithProvider(User(id: 'test-id')), - throwsA(isA())); - }); - - test('unflagUser', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when( - () => mockDio.post( - '/moderation/unflag', - data: {'target_user_id': 'test-id'}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.unflagUser('test-id'); - - verify(() => mockDio.post('/moderation/unflag', - data: {'target_user_id': 'test-id'})).called(1); - }); - - test('updateUser', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - final user = User(id: 'test-id'); - final data = { - 'users': {user.id: user.toJson()}, - }; - - when(() => mockDio.post('/users', data: data)).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.updateUser(user); - - verify(() => mockDio.post('/users', data: data)).called(1); - }); - - test('updateUsers', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - final user = User(id: 'test-id'); - final user2 = User(id: 'test-id2'); - - final data = { - 'users': { - user.id: user.toJson(), - user2.id: user2.toJson(), - }, - }; - - when(() => mockDio.post('/users', data: data)).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.updateUsers([user, user2]); - - verify(() => mockDio.post('/users', data: data)).called(1); - }); - - test('banUser', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when( - () => mockDio.post( - '/moderation/ban', - data: {'test': true, 'target_user_id': 'test-id'}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.banUser('test-id', {'test': true}); - - verify(() => mockDio.post('/moderation/ban', - data: {'test': true, 'target_user_id': 'test-id'})).called(1); - }); - - test('unbanUser', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when( - () => mockDio.delete( - '/moderation/ban', - queryParameters: {'test': true, 'target_user_id': 'test-id'}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.unbanUser('test-id', {'test': true}); - - verify(() => mockDio.delete('/moderation/ban', - queryParameters: {'test': true, 'target_user_id': 'test-id'})) - .called(1); - }); - - test('muteUser', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when( - () => mockDio.post( - '/moderation/mute', - data: {'target_id': 'test-id'}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.muteUser('test-id'); - - verify(() => mockDio.post('/moderation/mute', - data: {'target_id': 'test-id'})).called(1); - }); - - test('unmuteUser', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when( - () => mockDio.post( - '/moderation/unmute', - data: {'target_id': 'test-id'}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.unmuteUser('test-id'); - - verify(() => mockDio.post('/moderation/unmute', - data: {'target_id': 'test-id'})).called(1); - }); - }); - - group('message', () { - test('flagMessage', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when( - () => mockDio.post( - '/moderation/flag', - data: {'target_message_id': 'test-id'}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.flagMessage('test-id'); - - verify(() => mockDio.post('/moderation/flag', - data: {'target_message_id': 'test-id'})).called(1); - }); - - test('unflagMessage', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when( - () => mockDio.post( - '/moderation/unflag', - data: {'target_message_id': 'test-id'}, - ), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.unflagMessage('test-id'); - - verify(() => mockDio.post('/moderation/unflag', - data: {'target_message_id': 'test-id'})).called(1); - }); - - test('updateMessage', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - final message = Message( - id: 'test', - ); - - when( - () => mockDio.post( - '/messages/${message.id}', - data: {'message': anything}, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({'message': message}), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.updateMessage(message); - - verify(() => mockDio.post('/messages/${message.id}', - data: {'message': anything})).called(1); - }); - - test('deleteMessage', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - const messageId = 'test'; - - when(() => mockDio.delete('/messages/$messageId')).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.deleteMessage(Message(id: messageId)); - - verify(() => mockDio.delete('/messages/$messageId')).called(1); - }); - - test('getMessage', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - const messageId = 'test'; - - when(() => mockDio.get('/messages/$messageId')).thenAnswer( - (_) async => Response( - data: jsonEncode({'message': Message(id: messageId)}), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.getMessage(messageId); - - verify(() => mockDio.get('/messages/$messageId')).called(1); - }); - - test('markAllRead', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when(() => mockDio.post('/channels/read')).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.markAllRead(); - - verify(() => mockDio.post('/channels/read')).called(1); - }); - }); - - group('api methods', () { - group('get', () { - test('should put the correct parameters', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - final queryParams = {'test': 1}; - - when( - () => mockDio.get('/test', queryParameters: queryParams), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.get('/test', queryParameters: queryParams); - - verify(() => - mockDio.get('/test', queryParameters: queryParams)) - .called(1); - }); - - test('should catch the error', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when(() => mockDio.get(any())).thenThrow( - DioError( - type: DioErrorType.response, - response: Response( - data: 'test error', - statusCode: 400, - requestOptions: FakeRequestOptions(), - ), - requestOptions: FakeRequestOptions(), - ), - ); - - expect( - client.get('/test'), - throwsA(ApiError('test error', 400)), - ); - }); - }); - - group('post', () { - test('should put the correct parameters', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - final data = {'test': 1}; - - when(() => mockDio.post('/test', data: data)).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.post('/test', data: data); - - verify(() => mockDio.post('/test', data: data)).called(1); - }); - - test('should catch the error', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); - - when(() => mockDio.post(any())).thenThrow( - DioError( - type: DioErrorType.response, - response: Response( - data: 'test error', - statusCode: 400, - requestOptions: FakeRequestOptions(), - ), - requestOptions: FakeRequestOptions(), - ), - ); - - expect(client.post('/test'), throwsA(ApiError('test error', 400))); - }); - }); - - group('put', () { - test('should put the correct parameters', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - final data = {'test': 1}; - - when(() => mockDio.put('/test', data: data)).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.put('/test', data: data); - - verify(() => mockDio.put('/test', data: data)).called(1); - }); - - test('should catch the error', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when(() => mockDio.put(any())).thenThrow( - DioError( - type: DioErrorType.response, - response: Response( - data: 'test error', - statusCode: 400, - requestOptions: FakeRequestOptions(), - ), - requestOptions: FakeRequestOptions(), - ), - ); - - expect(client.put('/test'), throwsA(ApiError('test error', 400))); - }); - }); - - group('patch', () { - test('should put the correct parameters', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - final data = { - 'test': 1, - }; - - when(() => mockDio.patch('/test', data: data)).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.patch('/test', data: data); - - verify(() => mockDio.patch('/test', data: data)).called(1); - }); - - test('should catch the error', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when(() => mockDio.patch(any())).thenThrow( - DioError( - type: DioErrorType.response, - response: Response( - data: 'test error', - statusCode: 400, - requestOptions: FakeRequestOptions(), - ), - requestOptions: FakeRequestOptions(), - ), - ); - - expect(client.patch('/test'), throwsA(ApiError('test error', 400))); - }); - }); - - group('delete', () { - test('should put the correct parameters', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); - - final queryParams = { - 'test': 1, - }; - - when( - () => mockDio.delete('/test', queryParameters: queryParams), - ).thenAnswer( - (_) async => Response( - data: '{}', - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.delete('/test', queryParameters: queryParams); - - verify(() => - mockDio.delete('/test', queryParameters: queryParams)) - .called(1); - }); - - test('should catch the error', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient('api-key', httpClient: mockDio); - - when(() => mockDio.delete(any())).thenThrow( - DioError( - type: DioErrorType.response, - response: Response( - data: 'test error', - statusCode: 400, - requestOptions: FakeRequestOptions(), - ), - requestOptions: FakeRequestOptions(), - ), - ); - - expect(client.delete('/test'), throwsA(ApiError('test error', 400))); - }); - }); - - group('pin message', () { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); - - test('should throw argument error', () { - final message = Message(text: 'Hello'); - expect( - () => client.pinMessage(message, 'InvalidType'), - throwsArgumentError, - ); - }); - - test('should complete successfully', () async { - const timeout = 30; - final message = Message(text: 'Hello'); - - when( - () => mockDio.post( - '/messages/${message.id}', - data: anything, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({'message': message}), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.pinMessage(message, timeout); - - verify(() => mockDio.post('/messages/${message.id}', - data: {'message': anything})).called(1); - }); - - test('should complete successfully with a null value', () async { - final message = Message(text: 'Hello'); - - when( - () => mockDio.post( - '/messages/${message.id}', - data: anything, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({'message': message}), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.pinMessage(message); - - verify(() => mockDio.post('/messages/${message.id}', - data: {'message': anything})).called(1); - }); - - test('should unpin message successfully', () async { - final message = Message(text: 'Hello'); - - when( - () => mockDio.post( - '/messages/${message.id}', - data: anything, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({'message': message}), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await client.unpinMessage(message); - - verify(() => mockDio.post('/messages/${message.id}', - data: anything)).called(1); - }); - }); - }); - - group('channel', () { - test('should update channel', () async { - final mockDio = MockDio(); - - when(() => mockDio.options).thenReturn(BaseOptions()); - when(() => mockDio.interceptors).thenReturn(Interceptors()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); - - final channelClient = client.channel( - 'type', - id: 'id', - extraData: {'name': 'init'}, - ); - - const update = { - 'set': {'name': 'demo'} - }; - - when( - () => mockDio.patch( - '/channels/${channelClient.type}/${channelClient.id}', - data: update, - ), - ).thenAnswer( - (_) async => Response( - data: jsonEncode({'channel': ChannelModel(cid: 'messaging:test')}), - statusCode: 200, - requestOptions: FakeRequestOptions(), - ), - ); - - await channelClient.updatePartial(update); - verify( - () => mockDio.patch( - '/channels/${channelClient.type}/${channelClient.id}', - data: update, - ), - ).called(1); - }); - }); - }); -} diff --git a/packages/stream_chat/test/src/models/action_test.dart b/packages/stream_chat/test/src/models/action_test.dart index 22e66078..d0108948 100644 --- a/packages/stream_chat/test/src/models/action_test.dart +++ b/packages/stream_chat/test/src/models/action_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:test/test.dart'; -import 'package:stream_chat/src/models/action.dart'; +import 'package:stream_chat/src/core/models/action.dart'; void main() { group('src/models/action', () { diff --git a/packages/stream_chat/test/src/models/attachment_test.dart b/packages/stream_chat/test/src/models/attachment_test.dart index 1342e72e..a433bedd 100644 --- a/packages/stream_chat/test/src/models/attachment_test.dart +++ b/packages/stream_chat/test/src/models/attachment_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:stream_chat/src/models/action.dart'; -import 'package:stream_chat/src/models/attachment.dart'; +import 'package:stream_chat/src/core/models/action.dart'; +import 'package:stream_chat/src/core/models/attachment.dart'; import 'package:test/test.dart'; void main() { diff --git a/packages/stream_chat/test/src/models/channel_state_test.dart b/packages/stream_chat/test/src/models/channel_state_test.dart index a93166b4..8f8bdf45 100644 --- a/packages/stream_chat/test/src/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/models/channel_state_test.dart @@ -1,10 +1,10 @@ import 'dart:convert'; -import 'package:stream_chat/src/models/channel_config.dart'; -import 'package:stream_chat/src/models/channel_state.dart'; -import 'package:stream_chat/src/models/command.dart'; -import 'package:stream_chat/src/models/message.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/channel_config.dart'; +import 'package:stream_chat/src/core/models/channel_state.dart'; +import 'package:stream_chat/src/core/models/command.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:test/test.dart'; diff --git a/packages/stream_chat/test/src/models/channel_test.dart b/packages/stream_chat/test/src/models/channel_test.dart index 1e69565c..1bb400a6 100644 --- a/packages/stream_chat/test/src/models/channel_test.dart +++ b/packages/stream_chat/test/src/models/channel_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:stream_chat/src/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:test/test.dart'; void main() { diff --git a/packages/stream_chat/test/src/models/command_test.dart b/packages/stream_chat/test/src/models/command_test.dart index 8fafd192..3306fd0f 100644 --- a/packages/stream_chat/test/src/models/command_test.dart +++ b/packages/stream_chat/test/src/models/command_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:stream_chat/src/models/command.dart'; +import 'package:stream_chat/src/core/models/command.dart'; import 'package:test/test.dart'; void main() { diff --git a/packages/stream_chat/test/src/models/device_test.dart b/packages/stream_chat/test/src/models/device_test.dart index 5cbf015d..f4700b7e 100644 --- a/packages/stream_chat/test/src/models/device_test.dart +++ b/packages/stream_chat/test/src/models/device_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:test/test.dart'; -import 'package:stream_chat/src/models/device.dart'; +import 'package:stream_chat/src/core/models/device.dart'; void main() { group('src/models/device', () { diff --git a/packages/stream_chat/test/src/models/event_test.dart b/packages/stream_chat/test/src/models/event_test.dart index 96efa844..b05da962 100644 --- a/packages/stream_chat/test/src/models/event_test.dart +++ b/packages/stream_chat/test/src/models/event_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:stream_chat/src/models/event.dart'; -import 'package:stream_chat/src/models/own_user.dart'; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/own_user.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:test/test.dart'; diff --git a/packages/stream_chat/test/src/models/member_test.dart b/packages/stream_chat/test/src/models/member_test.dart index 25e1d78e..54c258e1 100644 --- a/packages/stream_chat/test/src/models/member_test.dart +++ b/packages/stream_chat/test/src/models/member_test.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'package:test/test.dart'; -import 'package:stream_chat/src/models/member.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/member.dart'; +import 'package:stream_chat/src/core/models/user.dart'; void main() { group('src/models/member', () { diff --git a/packages/stream_chat/test/src/models/message_test.dart b/packages/stream_chat/test/src/models/message_test.dart index 1219e137..b3f28ff0 100644 --- a/packages/stream_chat/test/src/models/message_test.dart +++ b/packages/stream_chat/test/src/models/message_test.dart @@ -1,9 +1,9 @@ import 'dart:convert'; -import 'package:stream_chat/src/models/attachment.dart'; -import 'package:stream_chat/src/models/message.dart'; -import 'package:stream_chat/src/models/reaction.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/attachment.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/reaction.dart'; +import 'package:stream_chat/src/core/models/user.dart'; import 'package:test/test.dart'; void main() { diff --git a/packages/stream_chat/test/src/models/reaction_test.dart b/packages/stream_chat/test/src/models/reaction_test.dart index 795e2967..b69bd986 100644 --- a/packages/stream_chat/test/src/models/reaction_test.dart +++ b/packages/stream_chat/test/src/models/reaction_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:stream_chat/src/models/reaction.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/reaction.dart'; +import 'package:stream_chat/src/core/models/user.dart'; import 'package:test/test.dart'; void main() { diff --git a/packages/stream_chat/test/src/models/read_test.dart b/packages/stream_chat/test/src/models/read_test.dart index 66efe809..47c252ac 100644 --- a/packages/stream_chat/test/src/models/read_test.dart +++ b/packages/stream_chat/test/src/models/read_test.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'package:test/test.dart'; -import 'package:stream_chat/src/models/read.dart'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/read.dart'; +import 'package:stream_chat/src/core/models/user.dart'; void main() { group('src/models/read', () { diff --git a/packages/stream_chat/test/src/models/serialization_test.dart b/packages/stream_chat/test/src/models/serialization_test.dart index 9e64caad..605b5b58 100644 --- a/packages/stream_chat/test/src/models/serialization_test.dart +++ b/packages/stream_chat/test/src/models/serialization_test.dart @@ -1,5 +1,5 @@ import 'package:test/test.dart'; -import 'package:stream_chat/src/models/serialization.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; void main() { group('src/models/serialization', () { @@ -9,7 +9,7 @@ void main() { 'prop2': 123, 'prop3': true, }; - final result = Serialization.moveToExtraDataFromRoot(json, [ + final result = Serializer.moveToExtraDataFromRoot(json, [ 'prop1', 'prop2', ]); @@ -30,7 +30,7 @@ void main() { }); test('should have empty extraData', () { - final result = Serialization.moveToExtraDataFromRoot({ + final result = Serializer.moveToExtraDataFromRoot({ 'prop1': 'test', 'prop2': 123, 'prop3': true, @@ -49,7 +49,7 @@ void main() { }); test('should return null', () { - final result = Serialization.moveToExtraDataFromRoot({}, [ + final result = Serializer.moveToExtraDataFromRoot({}, [ 'prop1', 'prop2', ]); diff --git a/packages/stream_chat/test/src/models/user_test.dart b/packages/stream_chat/test/src/models/user_test.dart index 8f56f7e1..f4ff5ee0 100644 --- a/packages/stream_chat/test/src/models/user_test.dart +++ b/packages/stream_chat/test/src/models/user_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:stream_chat/src/models/user.dart'; +import 'package:stream_chat/src/core/models/user.dart'; import 'package:test/test.dart'; void main() { From bdfbc279173b4b9ec8eef7c062e57b441f251793 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 4 Jun 2021 17:09:41 +0530 Subject: [PATCH 332/599] add stream_chat_error_test.dart Signed-off-by: Sahil Kumar --- .../lib/src/core/error/stream_chat_error.dart | 3 + .../http/interceptor/logging_interceptor.dart | 1 + .../core/error/stream_chat_error_test.dart | 126 ++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 packages/stream_chat/test/src/core/error/stream_chat_error_test.dart 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 04214033..a78e9390 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 @@ -45,6 +45,9 @@ class StreamWebSocketError extends StreamChatError { /// Response body. please refer to [ErrorResponse]. final ErrorResponse? data; + @override + List get props => [...super.props, code]; + @override String toString() { var params = 'message: $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 41c36326..1f5687a8 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 @@ -1,4 +1,5 @@ // ignore_for_file: lines_longer_than_80_chars +// coverage:ignore-file import 'dart:math' as math; diff --git a/packages/stream_chat/test/src/core/error/stream_chat_error_test.dart b/packages/stream_chat/test/src/core/error/stream_chat_error_test.dart new file mode 100644 index 00000000..a32e0d71 --- /dev/null +++ b/packages/stream_chat/test/src/core/error/stream_chat_error_test.dart @@ -0,0 +1,126 @@ +import 'package:dio/dio.dart'; +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/error/error.dart'; +import 'package:test/test.dart'; + +void main() { + group('StreamChatError', () { + test('should match if message is same', () { + const message = 'test-error-message'; + const error = StreamChatError(message); + const error2 = StreamChatError(message); + + expect(error, error2); + }); + + test('`.toString`', () { + const message = 'test-error-message'; + const error = StreamChatError(message); + + expect(error.toString(), 'StreamChatError(message: $message)'); + }); + }); + + group('StreamWebSocketError', () { + test('.fromStreamError', () { + final data = ErrorResponse()..code = 333; + final error = StreamWebSocketError.fromStreamError(data.toJson()); + expect(error, isNotNull); + expect(error.code, data.code); + }); + + test('should match if message and data.code is same', () { + const message = 'test-error-message'; + final data = ErrorResponse()..code = 333; + final error = StreamWebSocketError(message, data: data); + final error2 = StreamWebSocketError(message, data: data); + + expect(error, error2); + }); + + test('`.toString`', () { + const message = 'test-error-message'; + final data = ErrorResponse()..code = 333; + final error = StreamWebSocketError(message, data: data); + + expect( + error.toString(), + 'WebSocketError(message: $message, data: $data)', + ); + }); + }); + + group('StreamChatNetworkError', () { + test('.raw', () { + const code = 333; + const message = 'test-error-message'; + final error = StreamChatNetworkError.raw(code: code, message: message); + expect(error, isNotNull); + expect(error.code, code); + expect(error.message, message); + }); + + test('.fromDioError', () { + const code = 333; + const statusCode = 666; + const message = 'test-error-message'; + final options = RequestOptions(path: 'test-path'); + final data = ErrorResponse() + ..code = code + ..statusCode = statusCode + ..message = message; + final dioError = DioError( + requestOptions: options, + response: Response( + requestOptions: options, + statusCode: data.statusCode, + data: data.toJson(), + ), + ); + final error = StreamChatNetworkError.fromDioError(dioError); + expect(error, isNotNull); + expect(error.code, code); + expect(error.message, message); + expect(error.statusCode, statusCode); + expect(error.data?.code, data.code); + expect(error.data?.statusCode, data.statusCode); + expect(error.data?.message, data.message); + }); + + test('should match if message, code and statusCode is same', () { + const code = 333; + const statusCode = 666; + const message = 'test-error-message'; + final error = StreamChatNetworkError.raw( + code: code, + statusCode: statusCode, + message: message, + ); + final error2 = StreamChatNetworkError.raw( + code: code, + statusCode: statusCode, + message: message, + ); + + expect(error, error2); + }); + + test('`.retriable` should return true if data is not present', () { + const errorCode = ChatErrorCode.tokenExpired; + final error = StreamChatNetworkError(errorCode); + + expect(error.isRetriable, isTrue); + }); + + test('`.toString`', () { + const errorCode = ChatErrorCode.tokenExpired; + final error = StreamChatNetworkError(errorCode); + expect( + error.toString(), + 'StreamChatNetworkError(' + 'code: ${errorCode.code}, ' + 'message: ${errorCode.message})', + ); + }); + }); +} From e3513741008ecb92d048e9e6a8da7e93b1a40e49 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 7 Jun 2021 12:44:49 +0530 Subject: [PATCH 333/599] fix: fixed update tests --- .../test/src/api/channel_test.dart | 12 +++--- .../stream_chat/test/src/client_test.dart | 37 ++++++++++++++++++- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index f92b05bb..6d15f4ef 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -430,7 +430,7 @@ void main() { await channelClient.watch(); when( - () => mockDio.post( + () => mockDio.put( '/messages/${message.id}', data: anything, ), @@ -445,7 +445,7 @@ void main() { await channelClient.pinMessage(message, 30); verify(() => - mockDio.post('/messages/${message.id}', data: anything)) + mockDio.put('/messages/${message.id}', data: anything)) .called(1); }); @@ -475,7 +475,7 @@ void main() { await channelClient.watch(); when( - () => mockDio.post( + () => mockDio.put( '/messages/${message.id}', data: anything, ), @@ -490,7 +490,7 @@ void main() { await channelClient.pinMessage(message); verify(() => - mockDio.post('/messages/${message.id}', data: anything)) + mockDio.put('/messages/${message.id}', data: anything)) .called(1); }); @@ -520,7 +520,7 @@ void main() { await channelClient.watch(); when( - () => mockDio.post( + () => mockDio.put( '/messages/${message.id}', data: anything, ), @@ -535,7 +535,7 @@ void main() { await channelClient.unpinMessage(message); verify(() => - mockDio.post('/messages/${message.id}', data: anything)) + mockDio.put('/messages/${message.id}', data: anything)) .called(1); }); }); diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 53792764..431b0c04 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -745,6 +745,39 @@ void main() { data: {'message': anything})).called(1); }); + test('partiallyUpdateMessage', () async { + final mockDio = MockDio(); + + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + + final client = StreamChatClient('api-key', httpClient: mockDio); + final message = Message( + id: 'test', + text: 'demo', + ); + + when( + () => mockDio.put( + '/messages/${message.id}', + data: {'set': anything}, + ), + ).thenAnswer( + (_) async => Response( + data: jsonEncode({'message': message}), + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); + + await client.partiallyUpdateMessage(message.id, { + 'set': {'text': message.text} + }); + + verify(() => mockDio.put('/messages/${message.id}', + data: {'set': anything})).called(1); + }); + test('deleteMessage', () async { final mockDio = MockDio(); @@ -1094,7 +1127,7 @@ void main() { final message = Message(text: 'Hello'); when( - () => mockDio.post( + () => mockDio.put( '/messages/${message.id}', data: anything, ), @@ -1108,7 +1141,7 @@ void main() { await client.pinMessage(message, timeout); - verify(() => mockDio.post('/messages/${message.id}', + verify(() => mockDio.put('/messages/${message.id}', data: {'message': anything})).called(1); }); From 9f60af5e166b094165f96ab06ea63b3ee8eb336a Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 7 Jun 2021 12:52:57 +0530 Subject: [PATCH 334/599] fix: fixed pin tests --- packages/stream_chat/test/src/client_test.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 431b0c04..2450de7c 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -1142,14 +1142,14 @@ void main() { await client.pinMessage(message, timeout); verify(() => mockDio.put('/messages/${message.id}', - data: {'message': anything})).called(1); + data: {'set': anything})).called(1); }); test('should complete successfully with a null value', () async { final message = Message(text: 'Hello'); when( - () => mockDio.post( + () => mockDio.put( '/messages/${message.id}', data: anything, ), @@ -1163,15 +1163,15 @@ void main() { await client.pinMessage(message); - verify(() => mockDio.post('/messages/${message.id}', - data: {'message': anything})).called(1); + verify(() => mockDio.put('/messages/${message.id}', + data: {'set': anything})).called(1); }); test('should unpin message successfully', () async { final message = Message(text: 'Hello'); when( - () => mockDio.post( + () => mockDio.put( '/messages/${message.id}', data: anything, ), @@ -1185,7 +1185,7 @@ void main() { await client.unpinMessage(message); - verify(() => mockDio.post('/messages/${message.id}', + verify(() => mockDio.put('/messages/${message.id}', data: anything)).called(1); }); }); From 96f65ae6190677b1d6c9afdddd6a9a7f68c252dd Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 7 Jun 2021 13:04:38 +0530 Subject: [PATCH 335/599] fix: fixed pin tests --- packages/stream_chat/lib/src/api/channel.dart | 3 ++- packages/stream_chat/lib/src/client.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index a10ec32d..212996b3 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -411,7 +411,8 @@ class Channel { /// Waits for a [_messageAttachmentsUploadCompleter] to complete /// before actually updating the message. Future updateMessage(Message message) async { - var currentMessage = state?.messages.firstWhere((e) => e.id == message.id); + final currentMessage = + state?.messages.firstWhere((e) => e.id == message.id); // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index e0e29fff..6c3b71f0 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1353,7 +1353,7 @@ class StreamChatClient { Future partiallyUpdateMessage( String id, Map data) async { final response = await put( - '/messages/${id}', + '/messages/$id', data: data, ); return decode(response.data, UpdateMessageResponse.fromJson); From 244c77cbc74d333e69f799d9adf90545b153bbb8 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 7 Jun 2021 13:45:03 +0530 Subject: [PATCH 336/599] fix: Timestamp alignment --- .../lib/src/message_widget.dart | 277 +++++++++--------- 1 file changed, 145 insertions(+), 132 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 342f17c3..f2f08d82 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -490,152 +490,165 @@ class _MessageWidgetState extends State ? AlignmentDirectional.bottomEnd : AlignmentDirectional.bottomStart, children: [ - Column( - crossAxisAlignment: widget.reverse - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.message.pinned && - widget.message.pinnedBy != null && - widget.showPinHighlight) - _buildPinnedMessage(widget.message), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.showUserAvatar == DisplayWidget.show && - widget.message.user != null) ...[ - _buildUserAvatar(), - const SizedBox(width: 4), - ], - if (widget.showUserAvatar == DisplayWidget.hide) - SizedBox(width: avatarWidth + 4), - Flexible( - child: PortalEntry( - portal: Container( - transform: Matrix4.translationValues( - widget.reverse ? 12 : -12, 0, 0), - constraints: const BoxConstraints( - maxWidth: 22 * 6.0), - child: _buildReactionIndicator(context), - ), - portalAnchor: - Alignment(widget.reverse ? 1 : -1, -1), - childAnchor: - Alignment(widget.reverse ? -1 : 1, -1), - child: Stack( - clipBehavior: Clip.none, - children: [ - Padding( - padding: widget.showReactions - ? EdgeInsets.only( - top: widget - .message - .reactionCounts - ?.isNotEmpty == - true - ? 18 - : 0, - ) - : EdgeInsets.zero, - child: (widget.message.isDeleted && - !isFailedState) - ? Container( - // ignore: lines_longer_than_80_chars - margin: EdgeInsets.symmetric( - horizontal: + Padding( + padding: EdgeInsets.only( + bottom: + isPinned && widget.showPinHighlight ? 8.0 : 0.0, + ), + child: Column( + crossAxisAlignment: widget.reverse + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.message.pinned && + widget.message.pinnedBy != null && + widget.showPinHighlight) + _buildPinnedMessage(widget.message), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.showUserAvatar == + DisplayWidget.show && + widget.message.user != null) ...[ + _buildUserAvatar(), + const SizedBox(width: 4), + ], + if (widget.showUserAvatar == DisplayWidget.hide) + SizedBox(width: avatarWidth + 4), + Flexible( + child: PortalEntry( + portal: Container( + transform: Matrix4.translationValues( + widget.reverse ? 12 : -12, 0, 0), + constraints: const BoxConstraints( + maxWidth: 22 * 6.0), + child: _buildReactionIndicator(context), + ), + portalAnchor: + Alignment(widget.reverse ? 1 : -1, -1), + childAnchor: + Alignment(widget.reverse ? -1 : 1, -1), + child: Stack( + clipBehavior: Clip.none, + children: [ + Padding( + padding: widget.showReactions + ? EdgeInsets.only( + top: widget + .message + .reactionCounts + ?.isNotEmpty == + true + ? 18 + : 0, + ) + : EdgeInsets.zero, + child: (widget.message.isDeleted && + !isFailedState) + ? 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), + child: DeletedMessage( + borderRadiusGeometry: widget + .borderRadiusGeometry, + borderSide: + widget.borderSide, + shape: widget.shape, + messageTheme: + widget.messageTheme, + ), + ) + : Card( + clipBehavior: Clip.antiAlias, + elevation: 0, + margin: EdgeInsets.symmetric( + horizontal: (isFailedState + ? 15.0 + : 0.0) + // ignore: lines_longer_than_80_chars - widget.showUserAvatar == - // ignore: lines_longer_than_80_chars + (widget.showUserAvatar == DisplayWidget .gone ? 0 : 4.0), - child: DeletedMessage( - borderRadiusGeometry: widget - .borderRadiusGeometry, - borderSide: widget.borderSide, - shape: widget.shape, - messageTheme: - widget.messageTheme, - ), - ) - : Card( - clipBehavior: Clip.antiAlias, - elevation: 0, - margin: EdgeInsets.symmetric( - horizontal: (isFailedState - ? 15.0 - : 0.0) + - // ignore: lines_longer_than_80_chars - (widget.showUserAvatar == - DisplayWidget.gone - ? 0 - : 4.0), - ), - shape: widget.shape ?? - RoundedRectangleBorder( - side: widget.borderSide ?? - BorderSide( - color: widget - // ignore: lines_longer_than_80_chars - .messageTheme - // ignore: lines_longer_than_80_chars - .messageBorderColor ?? - Colors.grey, - ), - borderRadius: widget - // ignore: lines_longer_than_80_chars - .borderRadiusGeometry ?? - BorderRadius.zero, - ), - color: _getBackgroundColor(), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.end, - mainAxisSize: - MainAxisSize.min, - children: [ - if (hasQuotedMessage) - _buildQuotedMessage(), - if (hasNonUrlAttachments) - _parseAttachments(), - if (!isGiphy) - _buildTextBubble(), - ], + ), + shape: widget.shape ?? + RoundedRectangleBorder( + side: widget + .borderSide ?? + BorderSide( + color: widget + // ignore: lines_longer_than_80_chars + .messageTheme + // ignore: lines_longer_than_80_chars + .messageBorderColor ?? + Colors.grey, + ), + borderRadius: widget + // ignore: lines_longer_than_80_chars + .borderRadiusGeometry ?? + BorderRadius.zero, + ), + color: _getBackgroundColor(), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + mainAxisSize: + MainAxisSize.min, + children: [ + if (hasQuotedMessage) + _buildQuotedMessage(), + if (hasNonUrlAttachments) + _parseAttachments(), + if (!isGiphy) + _buildTextBubble(), + ], + ), ), + ), + if (widget.showReactionPickerIndicator) + Positioned( + right: widget.reverse ? null : 4, + left: widget.reverse ? 4 : null, + top: -8, + child: CustomPaint( + painter: ReactionBubblePainter( + StreamChatTheme.of(context) + .colorTheme + .white, + Colors.transparent, + Colors.transparent, + tailCirclesSpace: 1, ), - ), - if (widget.showReactionPickerIndicator) - Positioned( - right: widget.reverse ? null : 4, - left: widget.reverse ? 4 : null, - top: -8, - child: CustomPaint( - painter: ReactionBubblePainter( - StreamChatTheme.of(context) - .colorTheme - .white, - Colors.transparent, - Colors.transparent, - tailCirclesSpace: 1, ), ), - ), - ], + ], + ), ), ), - ), - ], - ), - if (showBottomRow) - SizedBox(height: context.textScaleFactor * 18.0), - ], + ], + ), + if (showBottomRow) + SizedBox(height: context.textScaleFactor * 18.0), + ], + ), ), if (showBottomRow) Padding( - padding: EdgeInsets.only(left: leftPadding), + padding: EdgeInsets.only( + left: leftPadding, + bottom: + isPinned && widget.showPinHighlight ? 6.0 : 0.0, + ), child: _bottomRow, ), if (isFailedState) From 6df1e111cbdc232f181638543cfe96001c25c09c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 7 Jun 2021 10:59:35 +0200 Subject: [PATCH 337/599] add skippush to merge --- packages/stream_chat/lib/src/models/message.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index f928ce3a..327845dd 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -366,6 +366,7 @@ class Message extends Equatable { pinnedAt: other.pinnedAt, pinExpires: other.pinExpires, pinnedBy: other.pinnedBy, + skipPush: other.skipPush, ); @override From bc0d0d5705c2a840c2aaa69f1e0aed153f985664 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 7 Jun 2021 11:48:26 +0200 Subject: [PATCH 338/599] add usernameBuilder param --- .../lib/src/message_actions_modal.dart | 5 ++++ .../lib/src/message_list_view.dart | 16 +++++++---- .../lib/src/message_reactions_modal.dart | 5 ++++ .../lib/src/message_widget.dart | 28 +++++++++++++------ 4 files changed, 40 insertions(+), 14 deletions(-) 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 5336ed48..6d42f9bd 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -35,6 +35,7 @@ class MessageActionsModal extends StatefulWidget { this.customActions = const [], this.attachmentBorderRadiusGeometry, this.onCopyTap, + this.textBuilder, }) : super(key: key); /// Builder for edit message @@ -97,6 +98,9 @@ class MessageActionsModal extends StatefulWidget { /// List of custom actions final List customActions; + /// Customize the MessageWidget textBuilder + final Widget Function(BuildContext context, Message message)? textBuilder; + @override _MessageActionsModalState createState() => _MessageActionsModalState(); } @@ -222,6 +226,7 @@ class _MessageActionsModalState extends State { showSendingIndicator: false, shape: widget.messageShape, attachmentShape: widget.attachmentShape, + textBuilder: widget.textBuilder, ), ), const SizedBox(height: 8), 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 db14924b..845c50ee 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -156,8 +156,9 @@ class MessageListView extends StatefulWidget { this.onMessageTap, this.onSystemMessageTap, this.onAttachmentTap, - this.textBuilder, this.onLinkTap, + this.textBuilder, + this.usernameBuilder, }) : super(key: key); /// Function used to build a custom message widget @@ -259,7 +260,10 @@ class MessageListView extends StatefulWidget { final void Function(Message message, Attachment attachment)? onAttachmentTap; /// Customize the MessageWidget textBuilder - final void Function(BuildContext context, Message message)? textBuilder; + final Widget Function(BuildContext context, Message message)? textBuilder; + + /// Customize the MessageWidget usernameBuilder + final Widget Function(BuildContext context, Message message)? usernameBuilder; /// Callback for when link is tapped final void Function(String link)? onLinkTap; @@ -857,8 +861,8 @@ class _MessageListViewState extends State { } FocusScope.of(context).unfocus(); }, - textBuilder: - widget.textBuilder as Widget Function(BuildContext, Message)?, + textBuilder: widget.textBuilder, + usernameBuilder: widget.usernameBuilder, onLinkTap: widget.onLinkTap, ); } @@ -1056,8 +1060,8 @@ class _MessageListViewState extends State { FocusScope.of(context).unfocus(); }, onAttachmentTap: widget.onAttachmentTap, - textBuilder: - widget.textBuilder as Widget Function(BuildContext, Message)?, + textBuilder: widget.textBuilder, + usernameBuilder: widget.usernameBuilder, onLinkTap: widget.onLinkTap, ); 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 5f3bc5a4..d331d726 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -23,6 +23,7 @@ class MessageReactionsModal extends StatelessWidget { this.showUserAvatar = DisplayWidget.show, this.onUserAvatarTap, this.attachmentBorderRadiusGeometry, + this.textBuilder, }) : super(key: key); /// Message to display reactions of @@ -52,6 +53,9 @@ class MessageReactionsModal extends StatelessWidget { /// [BorderRadius] to apply to attachments final BorderRadius? attachmentBorderRadiusGeometry; + /// Customize the MessageWidget textBuilder + final Widget Function(BuildContext context, Message message)? textBuilder; + @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; @@ -157,6 +161,7 @@ class MessageReactionsModal extends StatelessWidget { ), showReactionPickerIndicator: showReactions && (message.status == MessageSendingStatus.sent), + textBuilder: textBuilder, ), ), if (message.latestReactions?.isNotEmpty == true) ...[ diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 97e061ae..d519d29b 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -102,6 +102,7 @@ class MessageWidget extends StatefulWidget { this.onQuotedMessageTap, this.customActions = const [], this.onAttachmentTap, + this.usernameBuilder, }) : attachmentBuilders = { 'image': (context, message, attachments) { final border = RoundedRectangleBorder( @@ -265,6 +266,9 @@ class MessageWidget extends StatefulWidget { /// Widget builder for building text final Widget Function(BuildContext, Message)? textBuilder; + /// Widget builder for building username + final Widget Function(BuildContext, Message)? usernameBuilder; + /// Function called on long press final void Function(BuildContext, Message)? onMessageActions; @@ -721,14 +725,7 @@ class _MessageWidgetState extends State child: Text(msg, style: widget.messageTheme.replies), ), ], - if (showUsername) - Text( - widget.message.user!.name, - maxLines: 1, - key: usernameKey, - style: widget.messageTheme.messageAuthor, - overflow: TextOverflow.ellipsis, - ), + if (showUsername) _buildUsername(usernameKey), if (showTimeStamp) Text( Jiffy(widget.message.createdAt.toLocal()).jm, @@ -791,6 +788,19 @@ class _MessageWidgetState extends State ); } + Widget _buildUsername(Key usernameKey) { + if (widget.usernameBuilder != null) { + return widget.usernameBuilder!(context, widget.message); + } + return Text( + widget.message.user!.name, + maxLines: 1, + key: usernameKey, + style: widget.messageTheme.messageAuthor, + overflow: TextOverflow.ellipsis, + ); + } + Widget _buildUrlAttachment() { final urlAttachment = widget.message.attachments .firstWhere((element) => element.ogScrapeUrl != null); @@ -882,6 +892,7 @@ class _MessageWidgetState extends State builder: (context) => StreamChannel( channel: channel, child: MessageActionsModal( + textBuilder: widget.textBuilder, onCopyTap: (message) => Clipboard.setData(ClipboardData(text: message.text)), attachmentBorderRadiusGeometry: @@ -931,6 +942,7 @@ class _MessageWidgetState extends State builder: (context) => StreamChannel( channel: channel, child: MessageReactionsModal( + textBuilder: widget.textBuilder, attachmentBorderRadiusGeometry: widget.attachmentBorderRadiusGeometry as BorderRadius?, showUserAvatar: From 877de576696870204968351b3b6486d48468e262 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 7 Jun 2021 13:04:23 +0200 Subject: [PATCH 339/599] use skipPush as a send message param --- packages/stream_chat/lib/src/api/channel.dart | 13 ++++++++-- packages/stream_chat/lib/src/client.dart | 10 ++++--- .../stream_chat/lib/src/models/message.dart | 10 ------- .../stream_chat/lib/src/models/message.g.dart | 2 -- .../test/src/api/channel_test.dart | 6 ++++- .../test/src/models/channel_state_test.dart | 26 ------------------- .../test/src/models/message_test.dart | 1 - 7 files changed, 23 insertions(+), 45 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 2b6bc0a9..1a74a674 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -351,9 +351,13 @@ class Channel { } /// Send a [message] to this channel. + /// If [skipPush] is true the message will not send a push notification /// Waits for a [_messageAttachmentsUploadCompleter] to complete /// before actually sending the message. - Future sendMessage(Message message) async { + Future sendMessage( + Message message, { + bool skipPush = false, + }) async { _checkInitialized(); // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. @@ -396,7 +400,12 @@ class Channel { message = await attachmentsUploadCompleter.future; } - final response = await _client.sendMessage(message, id!, type); + final response = await _client.sendMessage( + message, + id!, + type, + skipPush: skipPush, + ); state!.addMessage(response.message); return response; } catch (error) { diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 6e7caa9e..9d11e7f1 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1327,11 +1327,15 @@ class StreamChatClient { Future sendMessage( Message message, String channelId, - String channelType, - ) async { + String channelType, { + bool skipPush = false, + }) async { final response = await post( '/channels/$channelType/$channelId/message', - data: {'message': message.toJson()}, + data: { + 'message': message.toJson(), + 'skip_push': skipPush, + }, ); return decode(response.data, SendMessageResponse.fromJson); } diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index 327845dd..6df17497 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -73,7 +73,6 @@ class Message extends Equatable { this.extraData = const {}, this.deletedAt, this.status = MessageSendingStatus.sent, - this.skipPush = false, }) : id = id ?? const Uuid().v4(), pinExpires = pinExpires?.toUtc(), createdAt = createdAt ?? DateTime.now(), @@ -158,10 +157,6 @@ class Message extends Equatable { @JsonKey(defaultValue: false) final bool silent; - /// If true the message will not send a push notification - @JsonKey(defaultValue: false) - final bool skipPush; - /// If true the message is shadowed @JsonKey( includeIfNull: false, @@ -253,7 +248,6 @@ class Message extends Equatable { 'pinned_at', 'pin_expires', 'pinned_by', - 'skip_push', ]; /// Serialize to json @@ -291,7 +285,6 @@ class Message extends Equatable { User? pinnedBy, Map? extraData, MessageSendingStatus? status, - bool? skipPush, }) { assert(() { if (pinExpires is! DateTime && @@ -331,7 +324,6 @@ class Message extends Equatable { pinnedBy: pinnedBy ?? this.pinnedBy, pinExpires: pinExpires == _pinExpires ? this.pinExpires : pinExpires as DateTime?, - skipPush: skipPush ?? this.skipPush, ); } @@ -366,7 +358,6 @@ class Message extends Equatable { pinnedAt: other.pinnedAt, pinExpires: other.pinExpires, pinnedBy: other.pinnedBy, - skipPush: other.skipPush, ); @override @@ -399,7 +390,6 @@ class Message extends Equatable { pinnedBy, extraData, status, - skipPush, ]; } diff --git a/packages/stream_chat/lib/src/models/message.g.dart b/packages/stream_chat/lib/src/models/message.g.dart index ca094a83..8792035a 100644 --- a/packages/stream_chat/lib/src/models/message.g.dart +++ b/packages/stream_chat/lib/src/models/message.g.dart @@ -67,7 +67,6 @@ Message _$MessageFromJson(Map json) { deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), - skipPush: json['skip_push'] as bool? ?? false, ); } @@ -97,7 +96,6 @@ Map _$MessageToJson(Message instance) { writeNotNull('thread_participants', readonly(instance.threadParticipants)); val['show_in_channel'] = instance.showInChannel; val['silent'] = instance.silent; - val['skip_push'] = instance.skipPush; writeNotNull('shadowed', readonly(instance.shadowed)); writeNotNull('command', readonly(instance.command)); writeNotNull('created_at', readonly(instance.createdAt)); diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index f92b05bb..65e169d1 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -52,7 +52,10 @@ void main() { when( () => mockDio.post( '/channels/messaging/testid/message', - data: {'message': message.toJson()}, + data: { + 'message': message.toJson(), + 'skip_push': false, + }, ), ).thenAnswer( (_) async => Response( @@ -67,6 +70,7 @@ void main() { verify(() => mockDio.post('/channels/messaging/testid/message', data: { 'message': message.toJson(), + 'skip_push': false, })).called(1); }); diff --git a/packages/stream_chat/test/src/models/channel_state_test.dart b/packages/stream_chat/test/src/models/channel_state_test.dart index a93166b4..df078583 100644 --- a/packages/stream_chat/test/src/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/models/channel_state_test.dart @@ -902,7 +902,6 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "skip_push": false, "silent": false, "pinned": false, "pinned_at": null, @@ -919,7 +918,6 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "skip_push": false, "silent": false, "pinned": false, "pinned_at": null, @@ -929,7 +927,6 @@ void main() { { "id": "dry-meadow-0-53e6299f-9b97-4a9c-a27e-7e2dde49b7e0", "text": "test message", - "skip_push": false, "attachments": [], "parent_id": null, "quoted_message": null, @@ -952,7 +949,6 @@ void main() { "quoted_message_id": null, "show_in_channel": null, "mentioned_users": [], - "skip_push": false, "status": "SENT", "silent": false, "pinned": false, @@ -964,7 +960,6 @@ void main() { "id": "dry-meadow-0-64d7970f-ede8-4b31-9738-1bc1756d2bfe", "text": "test", "attachments": [], - "skip_push": false, "parent_id": null, "quoted_message": null, "quoted_message_id": null, @@ -982,7 +977,6 @@ void main() { "text": "hi", "attachments": [], "parent_id": null, - "skip_push": false, "quoted_message": null, "quoted_message_id": null, "show_in_channel": null, @@ -1000,7 +994,6 @@ void main() { "attachments": [], "parent_id": null, "quoted_message": null, - "skip_push": false, "quoted_message_id": null, "show_in_channel": null, "mentioned_users": [], @@ -1023,7 +1016,6 @@ void main() { "status": "SENT", "silent": false, "pinned": false, - "skip_push": false, "pinned_at": null, "pin_expires": null, "pinned_by": null @@ -1041,7 +1033,6 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1053,7 +1044,6 @@ void main() { "quoted_message": null, "quoted_message_id": null, "show_in_channel": null, - "skip_push": false, "mentioned_users": [], "status": "SENT", "silent": false, @@ -1071,7 +1061,6 @@ void main() { "quoted_message_id": null, "show_in_channel": null, "mentioned_users": [], - "skip_push": false, "status": "SENT", "silent": false, "pinned": false, @@ -1090,7 +1079,6 @@ void main() { "mentioned_users": [], "status": "SENT", "silent": false, - "skip_push": false, "pinned": false, "pinned_at": null, "pin_expires": null, @@ -1100,7 +1088,6 @@ void main() { "id": "icy-recipe-7-935c396e-ddf8-4a9a-951c-0a12fa5bf055", "text": "what are you doing?", "attachments": [], - "skip_push": false, "parent_id": null, "quoted_message": null, "quoted_message_id": null, @@ -1118,7 +1105,6 @@ void main() { "text": "👍", "attachments": [], "parent_id": null, - "skip_push": false, "quoted_message": null, "quoted_message_id": null, "show_in_channel": null, @@ -1134,7 +1120,6 @@ void main() { "id": "snowy-credit-3-3e0c1a0d-d22f-42ee-b2a1-f9f49477bf21", "text": "sdasas", "attachments": [], - "skip_push": false, "parent_id": null, "quoted_message": null, "quoted_message_id": null, @@ -1155,7 +1140,6 @@ void main() { "quoted_message": null, "quoted_message_id": null, "show_in_channel": null, - "skip_push": false, "mentioned_users": [], "status": "SENT", "silent": false, @@ -1168,7 +1152,6 @@ void main() { "id": "snowy-credit-3-cfaf0b46-1daa-49c5-947c-b16d6697487d", "text": "nhisagdhsadz", "attachments": [], - "skip_push": false, "parent_id": null, "quoted_message": null, "quoted_message_id": null, @@ -1187,7 +1170,6 @@ void main() { "attachments": [], "parent_id": null, "quoted_message": null, - "skip_push": false, "quoted_message_id": null, "show_in_channel": null, "mentioned_users": [], @@ -1204,7 +1186,6 @@ void main() { "attachments": [], "parent_id": null, "quoted_message": null, - "skip_push": false, "quoted_message_id": null, "show_in_channel": null, "mentioned_users": [], @@ -1212,7 +1193,6 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1229,7 +1209,6 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1246,7 +1225,6 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1263,7 +1241,6 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1280,7 +1257,6 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1297,7 +1273,6 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": false, "pin_expires": null, "pinned_by": null }, @@ -1314,7 +1289,6 @@ void main() { "silent": false, "pinned": false, "pinned_at": null, - "skip_push": false, "pin_expires": null, "pinned_by": null } diff --git a/packages/stream_chat/test/src/models/message_test.dart b/packages/stream_chat/test/src/models/message_test.dart index 1219e137..84ae616e 100644 --- a/packages/stream_chat/test/src/models/message_test.dart +++ b/packages/stream_chat/test/src/models/message_test.dart @@ -133,7 +133,6 @@ void main() { "id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", "text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", "silent": false, - "skip_push": false, "attachments": [ { "type": "video", From 7e463a96633505e33fd75c4a8ac7378367fb41b5 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 7 Jun 2021 19:29:00 +0530 Subject: [PATCH 340/599] fix: Fixed trailing comma --- packages/stream_chat/lib/src/client.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 6c3b71f0..0d584c92 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1351,7 +1351,9 @@ class StreamChatClient { /// Use 'set' in map to set values /// User 'unset' in map to unset values Future partiallyUpdateMessage( - String id, Map data) async { + String id, + Map data, + ) async { final response = await put( '/messages/$id', data: data, From 89f989003d12598c45636cec76bcc7554c57db51 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 8 Jun 2021 16:09:17 +0530 Subject: [PATCH 341/599] add core/api tests Signed-off-by: Sahil Kumar --- .../stream_chat/lib/src/client/client.dart | 6 - .../core/api/attachment_file_uploader.dart | 51 +- .../lib/src/core/api/channel_api.dart | 2 - .../lib/src/core/api/device_api.dart | 3 +- .../lib/src/core/models/attachment_file.dart | 35 +- packages/stream_chat/test/assets/example.pdf | 57 ++ .../stream_chat/test/assets/test_image.jpeg | Bin 0 -> 4429 bytes .../api/attachment_file_uploader_test.dart | 136 +++++ .../test/src/core/api/channel_api_test.dart | 492 ++++++++++++++++++ .../test/src/core/api/device_api_test.dart | 84 +++ .../test/src/core/api/general_api_test.dart | 211 ++++++++ .../test/src/core/api/guest_api_test.dart | 44 ++ .../test/src/core/api/message_api_test.dart | 291 +++++++++++ .../src/core/api/moderation_api_test.dart | 190 +++++++ .../src/core/api/stream_chat_api_test.dart | 49 ++ .../test/src/core/api/user_api_test.dart | 77 +++ ...rt => connection_id_interceptor_test.dart} | 0 .../src/{ => core}/models/action_test.dart | 0 .../{ => core}/models/attachment_test.dart | 0 .../{ => core}/models/channel_state_test.dart | 0 .../src/{ => core}/models/channel_test.dart | 0 .../src/{ => core}/models/command_test.dart | 0 .../src/{ => core}/models/device_test.dart | 0 .../src/{ => core}/models/event_test.dart | 0 .../src/{ => core}/models/filter_test.dart | 0 .../src/{ => core}/models/member_test.dart | 0 .../src/{ => core}/models/message_test.dart | 0 .../src/{ => core}/models/reaction_test.dart | 0 .../test/src/{ => core}/models/read_test.dart | 0 .../{ => core}/models/serialization_test.dart | 0 .../test/src/{ => core}/models/user_test.dart | 0 packages/stream_chat/test/src/fakes.dart | 57 ++ packages/stream_chat/test/src/matchers.dart | 19 + packages/stream_chat/test/src/mocks.dart | 25 + packages/stream_chat/test/src/utils.dart | 15 + 35 files changed, 1788 insertions(+), 56 deletions(-) create mode 100644 packages/stream_chat/test/assets/example.pdf create mode 100644 packages/stream_chat/test/assets/test_image.jpeg create mode 100644 packages/stream_chat/test/src/core/api/attachment_file_uploader_test.dart create mode 100644 packages/stream_chat/test/src/core/api/channel_api_test.dart create mode 100644 packages/stream_chat/test/src/core/api/device_api_test.dart create mode 100644 packages/stream_chat/test/src/core/api/general_api_test.dart create mode 100644 packages/stream_chat/test/src/core/api/guest_api_test.dart create mode 100644 packages/stream_chat/test/src/core/api/message_api_test.dart create mode 100644 packages/stream_chat/test/src/core/api/moderation_api_test.dart create mode 100644 packages/stream_chat/test/src/core/api/stream_chat_api_test.dart create mode 100644 packages/stream_chat/test/src/core/api/user_api_test.dart rename packages/stream_chat/test/src/core/http/interceptor/{connection_id_interceptor.dart => connection_id_interceptor_test.dart} (100%) rename packages/stream_chat/test/src/{ => core}/models/action_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/attachment_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/channel_state_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/channel_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/command_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/device_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/event_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/filter_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/member_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/message_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/reaction_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/read_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/serialization_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/models/user_test.dart (100%) create mode 100644 packages/stream_chat/test/src/matchers.dart create mode 100644 packages/stream_chat/test/src/utils.dart diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index ddc6e335..9ecc780b 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -38,10 +38,6 @@ import 'package:stream_chat/src/core/models/member.dart'; /// [LogRecord] as the only parameter. typedef LogHandlerFunction = void Function(LogRecord record); -/// A function which can be used to request a Stream Chat API token from your -/// own backend server. Function requires a single [userId]. -typedef TokenProvider = Future Function(String userId); - final _levelEmojiMapper = { Level.INFO: 'â„šī¸', Level.WARNING: 'âš ī¸', @@ -583,7 +579,6 @@ class StreamChatClient { } logger.info('Query channel start'); - print('Query Channel Started : ${DateTime.now()}'); final res = await _chatApi.channel.queryChannels( filter: filter, sort: sort, @@ -594,7 +589,6 @@ class StreamChatClient { messageLimit: messageLimit, paginationParams: paginationParams, ); - print('Query Channel Completed : ${DateTime.now()}'); if (res.channels.isEmpty && paginationParams.offset == 0) { logger.warning(''' diff --git a/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart b/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart index 8eacf716..d6dc249f 100644 --- a/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart +++ b/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart @@ -1,7 +1,6 @@ import 'package:dio/dio.dart'; import 'package:stream_chat/src/core/api/responses.dart'; import 'package:stream_chat/src/core/http/stream_http_client.dart'; -import 'package:stream_chat/src/core/util/extension.dart'; import 'package:stream_chat/src/core/models/attachment_file.dart'; /// Class responsible for uploading images and files to a given channel @@ -70,29 +69,10 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) async { - final filename = file.path?.split('/').last ?? file.name; - final mimeType = filename?.mimeType; - - MultipartFile? multiPartFile; - if (file.path != null) { - multiPartFile = await MultipartFile.fromFile( - file.path!, - filename: filename, - contentType: mimeType, - ); - } else if (file.bytes != null) { - multiPartFile = MultipartFile.fromBytes( - file.bytes!, - filename: filename, - contentType: mimeType, - ); - } - - final response = await _client.post( + final multiPartFile = await file.toMultipartFile(); + final response = await _client.postFile( '/channels/$channelType/$channelId/image', - data: FormData.fromMap({ - 'file': multiPartFile, - }), + multiPartFile, onSendProgress: onSendProgress, cancelToken: cancelToken, ); @@ -107,29 +87,10 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader { ProgressCallback? onSendProgress, CancelToken? cancelToken, }) async { - final filename = file.path?.split('/').last ?? file.name; - final mimeType = filename?.mimeType; - - MultipartFile? multiPartFile; - if (file.path != null) { - multiPartFile = await MultipartFile.fromFile( - file.path!, - filename: filename, - contentType: mimeType, - ); - } else if (file.bytes != null) { - multiPartFile = MultipartFile.fromBytes( - file.bytes!, - filename: filename, - contentType: mimeType, - ); - } - - final response = await _client.post( + final multiPartFile = await file.toMultipartFile(); + final response = await _client.postFile( '/channels/$channelType/$channelId/file', - data: FormData.fromMap({ - 'file': multiPartFile, - }), + multiPartFile, onSendProgress: onSendProgress, cancelToken: cancelToken, ); 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 7407bcb5..761f10da 100644 --- a/packages/stream_chat/lib/src/core/api/channel_api.dart +++ b/packages/stream_chat/lib/src/core/api/channel_api.dart @@ -59,7 +59,6 @@ class ChannelApi { bool presence = false, PaginationParams paginationParams = const PaginationParams(), }) async { - print('Query Channel Started 2 : ${DateTime.now()}'); final response = await _client.get( '/channels', queryParameters: { @@ -80,7 +79,6 @@ class ChannelApi { }) }, ); - print('Query Channel Completed 2 : ${DateTime.now()}'); return QueryChannelsResponse.fromJson(response.data); } 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 2d5e0fea..69f509f8 100644 --- a/packages/stream_chat/lib/src/core/api/device_api.dart +++ b/packages/stream_chat/lib/src/core/api/device_api.dart @@ -10,7 +10,8 @@ enum PushProvider { apn } -extension on PushProvider { +/// Helper extension for [PushProvider] +extension PushProviderX on PushProvider { /// Returns the string notion for [PushProvider]. String get name => { PushProvider.apn: 'apn', 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 8dbb70ad..7bff5806 100644 --- a/packages/stream_chat/lib/src/core/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/core/models/attachment_file.dart @@ -1,9 +1,13 @@ import 'dart:typed_data'; +import 'package:dio/dio.dart' show MultipartFile; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:meta/meta.dart'; +import 'package:stream_chat/src/core/platform_detector/platform_detector.dart'; +import 'package:stream_chat/src/core/util/extension.dart'; part 'attachment_file.freezed.dart'; + part 'attachment_file.g.dart'; /// Union class to hold various [UploadState] of a attachment. @@ -58,14 +62,18 @@ String? _toString(Uint8List? bytes) { @JsonSerializable() class AttachmentFile { /// Creates a new [AttachmentFile] instance. - const AttachmentFile({ + AttachmentFile({ required this.size, this.path, this.name, this.bytes, - }) : assert( + }) : assert( path != null || bytes != null, 'Either path or bytes should be != null', + ), + assert( + !CurrentPlatform.isWeb || bytes != null, + 'File by path is not supported in web, Please provide bytes', ); /// Create a new instance from a json @@ -95,4 +103,27 @@ class AttachmentFile { /// Serialize to json Map toJson() => _$AttachmentFileToJson(this); + + /// Converts this into a [MultipartFile] + Future toMultipartFile() async { + final filename = path?.split('/').last ?? name; + final mimeType = filename?.mimeType; + + late MultipartFile multiPartFile; + + if (CurrentPlatform.isWeb) { + multiPartFile = MultipartFile.fromBytes( + bytes!, + filename: filename, + contentType: mimeType, + ); + } else { + multiPartFile = await MultipartFile.fromFile( + path!, + filename: filename, + contentType: mimeType, + ); + } + return multiPartFile; + } } diff --git a/packages/stream_chat/test/assets/example.pdf b/packages/stream_chat/test/assets/example.pdf new file mode 100644 index 00000000..d736dedc --- /dev/null +++ b/packages/stream_chat/test/assets/example.pdf @@ -0,0 +1,57 @@ +%PDF-1.7 +%ĩíŽû +3 0 obj +<< /Length 4 0 R >> +stream +/DeviceRGB cs /DeviceRGB CS +0 0 0.972549 SC +21.68 194 136.64 26 re +10 10 m 20 20 l S +BT +/F0 24 Tf +25.68 200 Td +(Hello World!) Tj +ET +endstream +endobj +4 0 obj +132 +endobj +5 0 obj +<< /Type /Font /Subtype /Type1 /BaseFont /Times-Roman /Encoding /WinAnsiEncoding >> +endobj +6 0 obj +<< /Type /Page + /Parent 2 0 R + /Resources << /Font << /F0 5 0 R >> >> + /MediaBox [ 0 0 180 240 ] + /Contents 3 0 R +>> +endobj +2 0 obj +<< /Type /Pages + /Count 1 + /Kids [ 6 0 R ] +>> +endobj +1 0 obj +<< /Type /Catalog + /Pages 2 0 R +>> +endobj +xref +0 7 +0000000000 65535 f +0000000522 00000 n +0000000457 00000 n +0000000015 00000 n +0000000199 00000 n +0000000218 00000 n +0000000317 00000 n +trailer +<< /Size 7 + /Root 1 0 R +>> +startxref +574 +%%EOF diff --git a/packages/stream_chat/test/assets/test_image.jpeg b/packages/stream_chat/test/assets/test_image.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..aeaccec269c66219d2abb3352e50eb959a1961ce GIT binary patch literal 4429 zcmeH|c{J4h9>;%UjKPEkgT^w}OtKV)WP6aw@E}XJETIW`>@;L6Q642F@{B!1i4a4n zWErwF${uPkD6-91Ct~h&?>)~w=l*q<|L*PcJ@3yy-{rhtpU>xbMmqzt+Z2FU^(a1a;{ zVvzw90DvHDw?7R3JwO~_PPR*K9$pyx0BJwK0Rn?L*#6l7?9;LAeSi}V5tKP<#3f|o z36%{IR=;xRKDV54O$)-dk1ns_6`H`qDhBCubM$bNKT6zKjU*_h&GFI2tH#WDJ-?o2nfdKH| zu>K_bCoVXfi-X-3PUsIV5C?%RFq{)2bCgTa$Oh^eA|$JRgIZo zk5@!qb4r2n1MRP5{~B1re?;~t*gv?&fPG*PyLn(ZUPqkLVWP)28`jP&E{E!!LsxnfBVBT3rsZ0|3)(`C_{SgDy;Jzbz!X0^B%3t{K08x zkJU%2<6hI!MVXP(lHyqRHzhW%Y3&p6ulXW$)80o#e9~Xte+wCZ@+d;`vjC z<*3!qBlGSR`=>WWR4gc+ixAJalid-n)S`_h1<7X6Ht}PJMF!fbLN5oe5&dYff;47* zUjX8}EdI?L}(3M5({s@<&e#8|D7x@M z7EJbIw~!h-HCg3Fuwt+~hs4N(c@JnD((h8gzlgg~-K|v}&SZ-Gt>oqz1lH>+v=oX6 z>hOSlhfMQX>{yTuBokKGeC>|+$Xhjscco?>lL+%LaZH)FMjUu#ks0l{p9G;ShftgM!P4;&|*TI$tsYrlT^ zrKIeH<4vdXfcjS`gHd*6{;WIy&#= zH@vdwF_f+rRI&8%ySHJB28uA3RK;V@EitinDXA6XnZpeiw;ENmw2BjRm9Vz8Pa6V) zqL=ngX2%Vb?GV~u3&ilgK7IjN{p`85*b-OZVV zs7*0F{mB*n$au%0jaWG=t*x=QO#$CKq9nRUy$guR?rzUq1v>4g*0ztiO8D8@+NJ84 z>Kh!Q=;%^e0548|r7uuta@Mvuqw@T~H-$1<3=5!pw8jZD6vnjn5ZLXc!0 zF)6M)*yGD8qi)Hxlk;p;W-5KcwLeCI4kPa!9M*JJQ_(Z26h~_^ef-1m+)jM+-DTU& zAtQmuk=qt+`Cxk4^%zY26h+*8N0s>S0Nd6|oY(xf4M`B|Sb>P_#< zxwfRcbxm8VV-xf@J@G>8ZpMR5!?xG--`UyGajQn$dXbpzW@IE|-=n5u>$)E&pE*hQ zf3L@bFN&O)-Xgq!I z)=9{S(vmwAuF5by##b(56Jb9y>88+%V(Cy|61kOjnFR>Q(+%6Q-jnVJL=fUNvqTwJ zn$+@)zs02e5pYUCtV_R~nn@>2m1b2Qtiey|nytv)HoMtxHg}WX$o@R~$fdI(&a}u{ z0SmaZUBDQw19vD4-EOK#kb3lD;l=ylo-b(ZF6oTR@@_$yNjbgp!S3~>^~aJ9vwUlf&j*ng7}x~lhrWv`X>qa_9W*`gFBE@2x};HQ4p;O7kdf6qWX GYxFNZ3vhY> literal 0 HcmV?d00001 diff --git a/packages/stream_chat/test/src/core/api/attachment_file_uploader_test.dart b/packages/stream_chat/test/src/core/api/attachment_file_uploader_test.dart new file mode 100644 index 00000000..27f4ce6a --- /dev/null +++ b/packages/stream_chat/test/src/core/api/attachment_file_uploader_test.dart @@ -0,0 +1,136 @@ +import 'package:dio/dio.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; +import 'package:stream_chat/src/core/models/attachment_file.dart'; +import 'package:test/test.dart'; + +import '../../fakes.dart'; +import '../../matchers.dart'; +import '../../mocks.dart'; +import '../../utils.dart'; + +void main() { + late final client = MockHttpClient(); + late StreamAttachmentFileUploader fileUploader; + + setUp(() { + fileUploader = StreamAttachmentFileUploader(client); + registerFallbackValue(FakeMultiPartFile()); + }); + + Response successResponse(String path, {Object? data}) => Response( + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); + + test('sendImage', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + + const path = '/channels/$channelType/$channelId/image'; + final file = assetFile('test_image.jpeg'); + final attachmentFile = AttachmentFile( + size: 333, + path: file.path, + bytes: file.readAsBytesSync(), + ); + final multipartFile = await attachmentFile.toMultipartFile(); + + when(() => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + )).thenAnswer((_) async => successResponse(path, data: { + 'file': 'test-file-url', + })); + + final res = await fileUploader.sendImage( + attachmentFile, + channelId, + channelType, + ); + + expect(res, isNotNull); + expect(res.file, isNotNull); + expect(res.file, isNotEmpty); + + verify(() => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + )).called(1); + verifyNoMoreInteractions(client); + }); + + test('sendFile', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + + const path = '/channels/$channelType/$channelId/file'; + final file = assetFile('example.pdf'); + final attachmentFile = AttachmentFile( + size: 333, + path: file.path, + bytes: file.readAsBytesSync(), + ); + final multipartFile = await attachmentFile.toMultipartFile(); + + when(() => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + )).thenAnswer((_) async => successResponse(path, data: { + 'file': 'test-file-url', + })); + + final res = await fileUploader.sendFile( + attachmentFile, + channelId, + channelType, + ); + + expect(res, isNotNull); + expect(res.file, isNotNull); + expect(res.file, isNotEmpty); + + verify(() => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + )).called(1); + verifyNoMoreInteractions(client); + }); + + test('deleteImage', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const path = '/channels/$channelType/$channelId/image'; + + const url = 'test-image-url'; + + when(() => client.delete(path, queryParameters: {'url': url})).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await fileUploader.deleteImage(url, channelId, channelType); + + expect(res, isNotNull); + + verify(() => client.delete(path, queryParameters: {'url': url})).called(1); + verifyNoMoreInteractions(client); + }); + + test('deleteFile', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const path = '/channels/$channelType/$channelId/file'; + + const url = 'test-file-url'; + + when(() => client.delete(path, queryParameters: {'url': url})).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await fileUploader.deleteFile(url, channelId, channelType); + + expect(res, isNotNull); + + verify(() => client.delete(path, queryParameters: {'url': url})).called(1); + verifyNoMoreInteractions(client); + }); +} diff --git a/packages/stream_chat/test/src/core/api/channel_api_test.dart b/packages/stream_chat/test/src/core/api/channel_api_test.dart new file mode 100644 index 00000000..8d5f5e76 --- /dev/null +++ b/packages/stream_chat/test/src/core/api/channel_api_test.dart @@ -0,0 +1,492 @@ +import 'package:dio/dio.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/api/channel_api.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/channel_state.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; + +import '../../mocks.dart'; + +void main() { + String _getChannelUrl(String channelId, String channelType) => + '/channels/$channelType/$channelId'; + + ChannelState _generateChannelState( + String channelId, + String channelType, + ) { + final channel = ChannelModel(id: channelId, type: channelType); + final messages = List.generate( + 3, + (index) => Message( + id: 'test-message-id-$index', + text: 'test-message-text-$index', + ), + ); + final members = List.generate( + 3, + (index) => Member(userId: 'test-user-id-$index'), + ); + final reads = List.generate( + 3, + (index) => Read( + lastRead: DateTime.now(), + user: User(id: 'test-user-id-$index'), + ), + ); + final watchers = List.generate( + 3, + (index) => User(id: 'test-user-id-$index'), + ); + final state = ChannelState( + channel: channel, + messages: messages, + pinnedMessages: messages, + members: members, + read: reads, + watchers: watchers, + watcherCount: watchers.length, + ); + return state; + } + + Response successResponse(String path, {Object? data}) => Response( + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); + + late final client = MockHttpClient(); + late ChannelApi channelApi; + + setUp(() { + channelApi = ChannelApi(client); + }); + + test('queryChannel', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const channelData = {'name': 'test-channel'}; + const messagePagination = PaginationParams(); + const membersPagination = PaginationParams(); + const watchersPagination = PaginationParams(); + + const channelPath = '/channels/$channelType/$channelId'; + const path = '$channelPath/query'; + + final channelState = _generateChannelState(channelId, channelType); + when(() => client.post(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse( + path, + data: channelState.toJson(), + )); + + final res = await channelApi.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + messagesPagination: messagePagination, + membersPagination: membersPagination, + watchersPagination: watchersPagination, + ); + + expect(res, isNotNull); + expect(res.messages.length, channelState.messages.length); + expect(res.pinnedMessages.length, channelState.pinnedMessages.length); + expect(res.members.length, channelState.members.length); + expect(res.read.length, channelState.read.length); + expect(res.watchers.length, channelState.watchers.length); + expect(res.watcherCount, channelState.watcherCount); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('queryChannels', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + + final filter = Filter.in_('cid', const ['test-cid']); + const sort = [SortOption('test-field')]; + const memberLimit = 33; + const messageLimit = 33; + + const path = '/channels'; + + final channelState = _generateChannelState(channelId, channelType); + when(() => client.get(path, queryParameters: any(named: 'queryParameters'))) + .thenAnswer((_) async => successResponse( + path, + data: { + 'channels': [channelState.toJson()] + }, + )); + + final res = await channelApi.queryChannels( + filter: filter, + sort: sort, + memberLimit: memberLimit, + messageLimit: messageLimit, + ); + + expect(res, isNotNull); + expect(res.channels, isNotEmpty); + + verify( + () => client.get(path, queryParameters: any(named: 'queryParameters')), + ).called(1); + verifyNoMoreInteractions(client); + }); + + test('markAllRead', () async { + const path = 'channels/read'; + when(() => client.post(path)).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await channelApi.markAllRead(); + + expect(res, isNotNull); + + verify(() => client.post(path)).called(1); + verifyNoMoreInteractions(client); + }); + + test('updateChannel', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const data = {'name': 'test-channel-name'}; + final message = Message(id: 'test-message-id', text: 'channel-updated'); + + final path = _getChannelUrl(channelId, channelType); + + final channelModel = ChannelModel( + id: channelId, + type: channelType, + extraData: data, + ); + + when(() => client.post(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + 'message': message.toJson(), + })); + + final res = await channelApi.updateChannel( + channelId, + channelType, + data, + message: message, + ); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect(res.message?.id, message.id); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('updateChannelPartial', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const data = {'name': 'test-channel-name'}; + + final path = _getChannelUrl(channelId, channelType); + + final channelModel = ChannelModel( + id: channelId, + type: channelType, + extraData: data, + ); + + when(() => client.patch(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + })); + + final res = await channelApi.updateChannelPartial( + channelId, + channelType, + data, + ); + + expect(res, isNotNull); + + verify(() => client.patch(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('acceptChannelInvite', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + final message = Message(id: 'test-message-id', text: 'channel-accepted'); + + final channelModel = ChannelModel(id: channelId, type: channelType); + + final path = _getChannelUrl(channelId, channelType); + + when(() => client.post(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + 'message': message.toJson(), + })); + + final res = await channelApi.acceptChannelInvite( + channelId, + channelType, + message: message, + ); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect(res.message?.id, message.id); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('rejectChannelInvite', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + final message = Message(id: 'test-message-id', text: 'channel-rejected'); + + final channelModel = ChannelModel(id: channelId, type: channelType); + + final path = _getChannelUrl(channelId, channelType); + + when(() => client.post(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + 'message': message.toJson(), + })); + + final res = await channelApi.rejectChannelInvite( + channelId, + channelType, + message: message, + ); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect(res.message?.id, message.id); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('inviteChannelMembers', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const memberIds = ['test-member-id-1', 'test-member-id-2']; + final channelModel = ChannelModel(id: channelId, type: channelType); + final message = Message(id: 'test-message-id', text: 'members-invited'); + + final path = _getChannelUrl(channelId, channelType); + + when(() => client.post(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + 'message': message.toJson(), + })); + + final res = await channelApi.inviteChannelMembers( + channelId, + channelType, + memberIds, + message: message, + ); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect(res.message?.id, message.id); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('addMembers', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const memberIds = ['test-member-id-1', 'test-member-id-2']; + final channelModel = ChannelModel(id: channelId, type: channelType); + final message = Message(id: 'test-message-id', text: 'members-added'); + + final path = _getChannelUrl(channelId, channelType); + + when(() => client.post(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + 'message': message.toJson(), + })); + + final res = await channelApi.addMembers( + channelId, + channelType, + memberIds, + message: message, + ); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect(res.message?.id, message.id); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('removeMembers', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const memberIds = ['test-member-id-1', 'test-member-id-2']; + final channelModel = ChannelModel(id: channelId, type: channelType); + final message = Message(id: 'test-message-id', text: 'members-removed'); + + final path = _getChannelUrl(channelId, channelType); + + when(() => client.post(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + 'message': message.toJson(), + })); + + final res = await channelApi.removeMembers( + channelId, + channelType, + memberIds, + message: message, + ); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect(res.message?.id, message.id); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('sendEvent', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + final event = Event(type: 'event.test'); + + final path = '${_getChannelUrl(channelId, channelType)}/event'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await channelApi.sendEvent(channelId, channelType, event); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('deleteChannel', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + + final path = _getChannelUrl(channelId, channelType); + + when(() => client.delete(path)).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await channelApi.deleteChannel(channelId, channelType); + + expect(res, isNotNull); + + verify(() => client.delete(path)).called(1); + verifyNoMoreInteractions(client); + }); + + test('truncateChannel', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + + final path = '${_getChannelUrl(channelId, channelType)}/truncate'; + + when(() => client.post(path)).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await channelApi.truncateChannel(channelId, channelType); + + expect(res, isNotNull); + + verify(() => client.post(path)).called(1); + verifyNoMoreInteractions(client); + }); + + test('hideChannel', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + + final path = '${_getChannelUrl(channelId, channelType)}/hide'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await channelApi.hideChannel(channelId, channelType); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('showChannel', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + + final path = '${_getChannelUrl(channelId, channelType)}/show'; + + when(() => client.post(path)).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await channelApi.showChannel(channelId, channelType); + + expect(res, isNotNull); + + verify(() => client.post(path)).called(1); + verifyNoMoreInteractions(client); + }); + + test('markRead', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const messageId = 'test-message-id'; + + final path = '${_getChannelUrl(channelId, channelType)}/read'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await channelApi.markRead( + channelId, + channelType, + messageId: messageId, + ); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('stopWatching', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + + final path = '${_getChannelUrl(channelId, channelType)}/stop-watching'; + + when(() => client.post(path)).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await channelApi.stopWatching(channelId, channelType); + + expect(res, isNotNull); + + verify(() => client.post(path)).called(1); + verifyNoMoreInteractions(client); + }); +} diff --git a/packages/stream_chat/test/src/core/api/device_api_test.dart b/packages/stream_chat/test/src/core/api/device_api_test.dart new file mode 100644 index 00000000..f12f04f5 --- /dev/null +++ b/packages/stream_chat/test/src/core/api/device_api_test.dart @@ -0,0 +1,84 @@ +import 'package:dio/dio.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/api/device_api.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; + +import '../../mocks.dart'; + +void main() { + Response successResponse(String path, {Object? data}) => Response( + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); + + late final client = MockHttpClient(); + late DeviceApi deviceApi; + + setUp(() { + deviceApi = DeviceApi(client); + }); + + test('addDevice', () async { + const deviceId = 'test-device-id'; + const pushProvider = PushProvider.firebase; + + const path = '/devices'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await deviceApi.addDevice(deviceId, pushProvider); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('getDevices', () async { + const path = '/devices'; + + final devices = List.generate( + 3, + (index) => Device( + id: 'test-device-id-$index', + pushProvider: PushProvider.firebase.name, + ), + ); + + when(() => client.get(path)).thenAnswer( + (_) async => successResponse(path, data: { + 'devices': [...devices.map((it) => it.toJson())] + }), + ); + + final res = await deviceApi.getDevices(); + + expect(res, isNotNull); + expect(res.devices.length, devices.length); + + verify(() => client.get(path)).called(1); + verifyNoMoreInteractions(client); + }); + + test('removeDevice', () async { + const deviceId = 'test-device-id'; + + const path = '/devices'; + + when( + () => client.delete(path, queryParameters: any(named: 'queryParameters')), + ).thenAnswer((_) async => successResponse(path, data: {})); + + final res = await deviceApi.removeDevice(deviceId); + + expect(res, isNotNull); + + verify( + () => client.delete(path, queryParameters: any(named: 'queryParameters')), + ).called(1); + verifyNoMoreInteractions(client); + }); +} diff --git a/packages/stream_chat/test/src/core/api/general_api_test.dart b/packages/stream_chat/test/src/core/api/general_api_test.dart new file mode 100644 index 00000000..8844262f --- /dev/null +++ b/packages/stream_chat/test/src/core/api/general_api_test.dart @@ -0,0 +1,211 @@ +import 'package:dio/dio.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/api/general_api.dart'; +import 'package:stream_chat/src/core/api/requests.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/filter.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; + +import '../../mocks.dart'; + +void main() { + Response successResponse(String path, {Object? data}) => Response( + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); + + late final client = MockHttpClient(); + late GeneralApi generalApi; + + setUp(() { + generalApi = GeneralApi(client); + }); + + test('sync', () async { + const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; + final lastSyncAt = DateTime.now(); + + const path = '/sync'; + + final events = + List.generate(3, (index) => Event(type: 'test-event-type-$index')); + + when(() => client.post(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse(path, data: { + 'events': [...events.map((it) => it.toJson())] + })); + + final res = await generalApi.sync(cids, lastSyncAt); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + group('searchMessages', () { + test( + 'should throw if `query` and `messageFilters` is not provided', + () async { + final filter = Filter.in_('cid', const ['test-cid-1', 'test-cid-2']); + try { + await generalApi.searchMessages(filter); + } catch (e) { + expect(e, isA()); + } + }, + ); + + test( + 'should throw if `query` and `messageFilters` both are provided', + () async { + final filter = Filter.in_('cid', const ['test-cid-1', 'test-cid-2']); + const query = 'test-query'; + final messageFilter = Filter.query('key', 'text'); + try { + await generalApi.searchMessages( + filter, + query: query, + messageFilters: messageFilter, + ); + } catch (e) { + expect(e, isA()); + } + }, + ); + + test('should run successfully with `query`', () async { + final filter = Filter.in_('cid', const ['test-cid-1', 'test-cid-2']); + const query = 'test-query'; + const sort = [SortOption('test-field')]; + const pagination = PaginationParams(); + + const path = '/search'; + + when( + () => client.get(path, queryParameters: any(named: 'queryParameters')), + ).thenAnswer((_) async => successResponse(path, data: {'results': []})); + + final res = await generalApi.searchMessages( + filter, + query: query, + sort: sort, + pagination: pagination, + ); + + expect(res, isNotNull); + expect(res.results, isEmpty); + + verify( + () => client.get(path, queryParameters: any(named: 'queryParameters')), + ).called(1); + verifyNoMoreInteractions(client); + }); + + test('should run successfully with `messageFilter`', () async { + final filter = Filter.in_('cid', const ['test-cid-1', 'test-cid-2']); + const sort = [SortOption('test-field')]; + final messageFilter = Filter.query('key', 'text'); + const pagination = PaginationParams(); + + const path = '/search'; + + when( + () => client.get(path, queryParameters: any(named: 'queryParameters')), + ).thenAnswer((_) async => successResponse(path, data: {'results': []})); + + final res = await generalApi.searchMessages( + filter, + messageFilters: messageFilter, + sort: sort, + pagination: pagination, + ); + + expect(res, isNotNull); + expect(res.results, isEmpty); + + verify( + () => client.get(path, queryParameters: any(named: 'queryParameters')), + ).called(1); + verifyNoMoreInteractions(client); + }); + }); + + group('queryMembers', () { + test('with `channelId`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + final filter = Filter.in_('cid', const ['test-cid-1', 'test-cid-2']); + const pagination = PaginationParams(); + const sort = [SortOption('test-field')]; + + const path = '/members'; + + final members = List.generate( + 3, + (index) => Member(userId: 'test-user-id=$index'), + ); + + when(() => + client.get(path, queryParameters: any(named: 'queryParameters'))) + .thenAnswer((_) async => successResponse(path, data: { + 'members': [...members.map((it) => it.toJson())] + })); + + final res = await generalApi.queryMembers( + channelType, + channelId: channelId, + filter: filter, + pagination: pagination, + sort: sort, + ); + + expect(res, isNotNull); + expect(res.members.length, members.length); + + verify( + () => client.get(path, queryParameters: any(named: 'queryParameters')), + ).called(1); + verifyNoMoreInteractions(client); + }); + + test('with `members`', () async { + const channelType = 'test-channel-type'; + final filter = Filter.in_('cid', const ['test-cid-1', 'test-cid-2']); + const pagination = PaginationParams(); + const sort = [SortOption('test-field')]; + + const path = '/members'; + + final members = List.generate( + 3, + (index) => Member(userId: 'test-user-id=$index'), + ); + + when(() => + client.get(path, queryParameters: any(named: 'queryParameters'))) + .thenAnswer((_) async => successResponse(path, data: { + 'members': [...members.map((it) => it.toJson())] + })); + + final res = await generalApi.queryMembers( + channelType, + filter: filter, + pagination: pagination, + sort: sort, + members: members, + ); + + expect(res, isNotNull); + expect(res.members.length, members.length); + + verify( + () => client.get(path, queryParameters: any(named: 'queryParameters')), + ).called(1); + verifyNoMoreInteractions(client); + }); + }); +} diff --git a/packages/stream_chat/test/src/core/api/guest_api_test.dart b/packages/stream_chat/test/src/core/api/guest_api_test.dart new file mode 100644 index 00000000..6c6386fa --- /dev/null +++ b/packages/stream_chat/test/src/core/api/guest_api_test.dart @@ -0,0 +1,44 @@ +import 'package:dio/dio.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/api/guest_api.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; + +import '../../mocks.dart'; + +void main() { + Response successResponse(String path, {Object? data}) => Response( + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); + + late final client = MockHttpClient(); + late GuestApi guestApi; + + setUp(() { + guestApi = GuestApi(client); + }); + + test('getGuestUser', () async { + const accessToken = 'test-guest-token'; + final user = User(id: 'test-user-id'); + + const path = '/guest'; + + when(() => client.post(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse(path, data: { + 'access_token': accessToken, + 'user': user.toJson(), + })); + + final res = await guestApi.getGuestUser(user); + + expect(res, isNotNull); + expect(res.accessToken, accessToken); + expect(res.user.id, user.id); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); +} diff --git a/packages/stream_chat/test/src/core/api/message_api_test.dart b/packages/stream_chat/test/src/core/api/message_api_test.dart new file mode 100644 index 00000000..59319eca --- /dev/null +++ b/packages/stream_chat/test/src/core/api/message_api_test.dart @@ -0,0 +1,291 @@ +import 'package:dio/dio.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/api/message_api.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; + +import '../../mocks.dart'; + +void main() { + Response successResponse(String path, {Object? data}) => Response( + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); + + late final client = MockHttpClient(); + late MessageApi messageApi; + + setUp(() { + messageApi = MessageApi(client); + }); + + test('sendMessage', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + final message = Message(id: 'test-message-id', text: 'test-message-text'); + + const path = '/channels/$channelType/$channelId/message'; + + when(() => client.post(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse(path, data: { + 'message': message.toJson(), + })); + + final res = await messageApi.sendMessage(channelId, channelType, message); + + expect(res, isNotNull); + expect(res.message.id, message.id); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('getMessagesById', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const messageIds = ['test-message-id-1', 'test-message-id-2']; + + const path = '/channels/$channelType/$channelId/messages'; + + final messages = List.generate( + 3, + (index) => Message(id: 'test-message-id-$index'), + ); + + when(() => client.get(path, queryParameters: any(named: 'queryParameters'))) + .thenAnswer((_) async => successResponse(path, data: { + 'messages': [...messages.map((it) => it.toJson())], + })); + + final res = await messageApi.getMessagesById( + channelId, + channelType, + messageIds, + ); + + expect(res, isNotNull); + expect(res.messages.length, messages.length); + + verify( + () => client.get(path, queryParameters: any(named: 'queryParameters')), + ).called(1); + verifyNoMoreInteractions(client); + }); + + test('getMessage', () async { + const messageId = 'test-message-id'; + + const path = '/messages/$messageId'; + + final message = Message(id: messageId); + + when(() => client.get(path)).thenAnswer((_) async => + successResponse(path, data: {'message': message.toJson()})); + + final res = await messageApi.getMessage(messageId); + + expect(res, isNotNull); + expect(res.message.id, messageId); + + verify(() => client.get(path)).called(1); + verifyNoMoreInteractions(client); + }); + + test('updateMessage', () async { + final message = Message(id: 'test-message-id'); + + final path = '/messages/${message.id}'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {'message': message.toJson()}), + ); + + final res = await messageApi.updateMessage(message); + + expect(res, isNotNull); + expect(res.message.id, message.id); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('deleteMessage', () async { + const messageId = 'test-message-id'; + + const path = '/messages/$messageId'; + + when(() => client.delete(path)).thenAnswer( + (_) async => successResponse(path, data: {}), + ); + + final res = await messageApi.deleteMessage(messageId); + + expect(res, isNotNull); + + verify(() => client.delete(path)).called(1); + verifyNoMoreInteractions(client); + }); + + test('sendAction', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const messageId = 'test-message-id'; + const formData = {'test-key': 'test-data'}; + + const path = '/messages/$messageId/action'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await messageApi.sendAction( + channelId, + channelType, + messageId, + formData, + ); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('sendReaction', () async { + const messageId = 'test-message-id'; + const reactionType = 'test-reaction-type'; + const extraData = {'test-key': 'test-data'}; + + const path = '/messages/$messageId/reaction'; + + final message = Message(id: messageId); + final reaction = Reaction(type: reactionType, messageId: messageId); + + when(() => client.post(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse(path, data: { + 'message': message.toJson(), + 'reaction': reaction.toJson(), + })); + + final res = await messageApi.sendReaction( + messageId, + reactionType, + extraData: extraData, + ); + + expect(res, isNotNull); + expect(res.message.id, messageId); + expect(res.reaction.messageId, messageId); + expect(res.reaction.type, reactionType); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('deleteReaction', () async { + const messageId = 'test-message-id'; + const reactionType = 'test-reaction-type'; + + const path = '/messages/$messageId/reaction/$reactionType'; + + when(() => client.delete(path)).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await messageApi.deleteReaction(messageId, reactionType); + + expect(res, isNotNull); + + verify(() => client.delete(path)).called(1); + verifyNoMoreInteractions(client); + }); + + test('getReactions', () async { + const messageId = 'test-message-id'; + const options = PaginationParams(); + + const path = '/messages/$messageId/reactions'; + + final reactions = List.generate( + 3, + (index) => Reaction( + type: 'test-reaction-type-$index', + messageId: messageId, + ), + ); + + when(() => client.get(path, queryParameters: any(named: 'queryParameters'))) + .thenAnswer((_) async => successResponse(path, data: { + 'reactions': [...reactions.map((it) => it.toJson())] + })); + + final res = await messageApi.getReactions(messageId, options); + + expect(res, isNotNull); + expect(res.reactions.length, reactions.length); + expect(res.reactions.every((it) => it.messageId == messageId), isTrue); + + verify( + () => client.get(path, queryParameters: any(named: 'queryParameters')), + ).called(1); + verifyNoMoreInteractions(client); + }); + + test('translateMessage', () async { + const messageId = 'test-message-id'; + const messageText = 'hello'; + const language = 'hi'; // Hindi + final message = Message(id: messageId, text: messageText); + + final path = '/messages/${message.id}/translate'; + + const translatedMessageText = 'ā¤¨ā¤Žā¤¸āĨā¤¤āĨ‡'; + final translatedMessage = TranslatedMessage(const { + language: translatedMessageText, + }); + + when(() => client.post(path, data: any(named: 'data'))) + .thenAnswer((_) async => successResponse(path, data: { + 'message': translatedMessage.toJson(), + })); + + final res = await messageApi.translateMessage(messageId, language); + + expect(res, isNotNull); + expect(res.message.i18n?.containsKey(language), isTrue); + expect(res.message.i18n?[language], translatedMessageText); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('getReplies', () async { + const parentId = 'test-parent-id'; + const options = PaginationParams(); + + const path = '/messages/$parentId/replies'; + + final messages = List.generate( + 3, + (index) => Message( + id: 'test-message-id-$index', + parentId: parentId, + ), + ); + + when(() => client.get(path, queryParameters: any(named: 'queryParameters'))) + .thenAnswer((_) async => successResponse(path, data: { + 'messages': [...messages.map((it) => it.toJson())] + })); + + final res = await messageApi.getReplies(parentId, options); + + expect(res, isNotNull); + expect(res.messages.length, messages.length); + expect(res.messages.every((it) => it.parentId == parentId), isTrue); + + verify( + () => client.get(path, queryParameters: any(named: 'queryParameters')), + ).called(1); + verifyNoMoreInteractions(client); + }); +} diff --git a/packages/stream_chat/test/src/core/api/moderation_api_test.dart b/packages/stream_chat/test/src/core/api/moderation_api_test.dart new file mode 100644 index 00000000..fb2a1f29 --- /dev/null +++ b/packages/stream_chat/test/src/core/api/moderation_api_test.dart @@ -0,0 +1,190 @@ +import 'package:dio/dio.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/api/moderation_api.dart'; +import 'package:test/test.dart'; + +import '../../mocks.dart'; + +void main() { + Response successResponse(String path, {Object? data}) => Response( + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); + + late final client = MockHttpClient(); + late ModerationApi moderationApi; + + setUp(() { + moderationApi = ModerationApi(client); + }); + + test('muteUser', () async { + const userId = 'test-user-id'; + + const path = '/moderation/mute'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await moderationApi.muteUser(userId); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('unmuteUser', () async { + const userId = 'test-user-id'; + + const path = '/moderation/unmute'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await moderationApi.unmuteUser(userId); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('muteChannel', () async { + const channelCid = 'test-channel-cid'; + const expiration = Duration(days: 3); + + const path = '/moderation/mute/channel'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await moderationApi.muteChannel( + channelCid, + expiration: expiration, + ); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('unmuteChannel', () async { + const channelCid = 'test-channel-cid'; + + const path = '/moderation/unmute/channel'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await moderationApi.unmuteChannel(channelCid); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('flagMessage', () async { + const messageId = 'test-message-id'; + + const path = '/moderation/flag'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await moderationApi.flagMessage(messageId); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('unflagMessage', () async { + const messageId = 'test-message-id'; + + const path = '/moderation/unflag'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await moderationApi.unflagMessage(messageId); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('flagUser', () async { + const userId = 'test-message-id'; + + const path = '/moderation/flag'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await moderationApi.flagUser(userId); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('unflagUser', () async { + const userId = 'test-message-id'; + + const path = '/moderation/unflag'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await moderationApi.unflagUser(userId); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('banUser', () async { + const targetUserId = 'test-target-user-id'; + const options = {'key': 'value'}; + + const path = '/moderation/ban'; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: {})); + + final res = await moderationApi.banUser(targetUserId, options: options); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('unbanUser', () async { + const targetUserId = 'test-target-user-id'; + const options = {'key': 'value'}; + + const path = '/moderation/ban'; + + when( + () => client.delete(path, queryParameters: any(named: 'queryParameters')), + ).thenAnswer((_) async => successResponse(path, data: {})); + + final res = await moderationApi.unbanUser(targetUserId, options: options); + + expect(res, isNotNull); + + verify( + () => client.delete(path, queryParameters: any(named: 'queryParameters')), + ).called(1); + verifyNoMoreInteractions(client); + }); +} diff --git a/packages/stream_chat/test/src/core/api/stream_chat_api_test.dart b/packages/stream_chat/test/src/core/api/stream_chat_api_test.dart new file mode 100644 index 00000000..f61746cd --- /dev/null +++ b/packages/stream_chat/test/src/core/api/stream_chat_api_test.dart @@ -0,0 +1,49 @@ +import 'package:stream_chat/src/core/api/stream_chat_api.dart'; +import 'package:test/test.dart'; + +import '../../mocks.dart'; + +void main() { + const apiKey = 'test-api-key'; + late final client = MockHttpClient(); + late StreamChatApi streamChatApi; + + setUp(() { + streamChatApi = StreamChatApi( + apiKey, + client: client, + ); + }); + + test('`.user`', () { + expect(streamChatApi.user, isNotNull); + }); + + test('`.guest`', () { + expect(streamChatApi.guest, isNotNull); + }); + + test('`.message`', () { + expect(streamChatApi.message, isNotNull); + }); + + test('`.channel`', () { + expect(streamChatApi.channel, isNotNull); + }); + + test('`.device`', () { + expect(streamChatApi.device, isNotNull); + }); + + test('`.moderation`', () { + expect(streamChatApi.moderation, isNotNull); + }); + + test('`.general`', () { + expect(streamChatApi.general, isNotNull); + }); + + test('`.fileUploader`', () { + expect(streamChatApi.fileUploader, isNotNull); + }); +} diff --git a/packages/stream_chat/test/src/core/api/user_api_test.dart b/packages/stream_chat/test/src/core/api/user_api_test.dart new file mode 100644 index 00000000..ebf0e343 --- /dev/null +++ b/packages/stream_chat/test/src/core/api/user_api_test.dart @@ -0,0 +1,77 @@ +import 'package:dio/dio.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/api/requests.dart'; +import 'package:stream_chat/src/core/api/user_api.dart'; +import 'package:stream_chat/src/core/models/filter.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; + +import '../../mocks.dart'; + +void main() { + Response successResponse(String path, {Object? data}) => Response( + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); + + late final client = MockHttpClient(); + late UserApi userApi; + + setUp(() { + userApi = UserApi(client); + }); + + test('queryUsers', () async { + const presence = true; + final filter = Filter.in_('cid', const ['test-cid-1', 'test-cid-2']); + const sort = [SortOption('test-field')]; + const pagination = PaginationParams(); + + const path = '/users'; + + final users = List.generate(3, (index) => User(id: 'test-user-id-$index')); + + when(() => client.get(path, queryParameters: any(named: 'queryParameters'))) + .thenAnswer((_) async => successResponse(path, data: { + 'users': [...users.map((it) => it.toJson())] + })); + + final res = await userApi.queryUsers( + presence: presence, + filter: filter, + sort: sort, + pagination: pagination, + ); + + expect(res, isNotNull); + expect(res.users.length, users.length); + + verify( + () => client.get(path, queryParameters: any(named: 'queryParameters')), + ).called(1); + verifyNoMoreInteractions(client); + }); + + test('updateUsers', () async { + final users = List.generate(3, (index) => User(id: 'test-user-id-$index')); + + const path = '/users'; + + final updatedUsers = {for (final user in users) user.id: user}; + + when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + (_) async => successResponse(path, data: { + 'users': updatedUsers + .map((key, value) => MapEntry(key, value.toJson())) + })); + + final res = await userApi.updateUsers(users); + + expect(res, isNotNull); + expect(res.users.length, updatedUsers.length); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); +} diff --git a/packages/stream_chat/test/src/core/http/interceptor/connection_id_interceptor.dart b/packages/stream_chat/test/src/core/http/interceptor/connection_id_interceptor_test.dart similarity index 100% rename from packages/stream_chat/test/src/core/http/interceptor/connection_id_interceptor.dart rename to packages/stream_chat/test/src/core/http/interceptor/connection_id_interceptor_test.dart diff --git a/packages/stream_chat/test/src/models/action_test.dart b/packages/stream_chat/test/src/core/models/action_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/action_test.dart rename to packages/stream_chat/test/src/core/models/action_test.dart diff --git a/packages/stream_chat/test/src/models/attachment_test.dart b/packages/stream_chat/test/src/core/models/attachment_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/attachment_test.dart rename to packages/stream_chat/test/src/core/models/attachment_test.dart diff --git a/packages/stream_chat/test/src/models/channel_state_test.dart b/packages/stream_chat/test/src/core/models/channel_state_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/channel_state_test.dart rename to packages/stream_chat/test/src/core/models/channel_state_test.dart diff --git a/packages/stream_chat/test/src/models/channel_test.dart b/packages/stream_chat/test/src/core/models/channel_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/channel_test.dart rename to packages/stream_chat/test/src/core/models/channel_test.dart diff --git a/packages/stream_chat/test/src/models/command_test.dart b/packages/stream_chat/test/src/core/models/command_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/command_test.dart rename to packages/stream_chat/test/src/core/models/command_test.dart diff --git a/packages/stream_chat/test/src/models/device_test.dart b/packages/stream_chat/test/src/core/models/device_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/device_test.dart rename to packages/stream_chat/test/src/core/models/device_test.dart diff --git a/packages/stream_chat/test/src/models/event_test.dart b/packages/stream_chat/test/src/core/models/event_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/event_test.dart rename to packages/stream_chat/test/src/core/models/event_test.dart diff --git a/packages/stream_chat/test/src/models/filter_test.dart b/packages/stream_chat/test/src/core/models/filter_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/filter_test.dart rename to packages/stream_chat/test/src/core/models/filter_test.dart diff --git a/packages/stream_chat/test/src/models/member_test.dart b/packages/stream_chat/test/src/core/models/member_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/member_test.dart rename to packages/stream_chat/test/src/core/models/member_test.dart diff --git a/packages/stream_chat/test/src/models/message_test.dart b/packages/stream_chat/test/src/core/models/message_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/message_test.dart rename to packages/stream_chat/test/src/core/models/message_test.dart diff --git a/packages/stream_chat/test/src/models/reaction_test.dart b/packages/stream_chat/test/src/core/models/reaction_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/reaction_test.dart rename to packages/stream_chat/test/src/core/models/reaction_test.dart diff --git a/packages/stream_chat/test/src/models/read_test.dart b/packages/stream_chat/test/src/core/models/read_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/read_test.dart rename to packages/stream_chat/test/src/core/models/read_test.dart diff --git a/packages/stream_chat/test/src/models/serialization_test.dart b/packages/stream_chat/test/src/core/models/serialization_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/serialization_test.dart rename to packages/stream_chat/test/src/core/models/serialization_test.dart diff --git a/packages/stream_chat/test/src/models/user_test.dart b/packages/stream_chat/test/src/core/models/user_test.dart similarity index 100% rename from packages/stream_chat/test/src/models/user_test.dart rename to packages/stream_chat/test/src/core/models/user_test.dart diff --git a/packages/stream_chat/test/src/fakes.dart b/packages/stream_chat/test/src/fakes.dart index e96cbd12..b7e431b1 100644 --- a/packages/stream_chat/test/src/fakes.dart +++ b/packages/stream_chat/test/src/fakes.dart @@ -1,6 +1,18 @@ +import 'package:dio/dio.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/api/channel_api.dart'; +import 'package:stream_chat/src/core/api/device_api.dart'; +import 'package:stream_chat/src/core/api/general_api.dart'; +import 'package:stream_chat/src/core/api/message_api.dart'; +import 'package:stream_chat/src/core/api/moderation_api.dart'; +import 'package:stream_chat/src/core/api/stream_chat_api.dart'; +import 'package:stream_chat/src/core/api/user_api.dart'; +import 'package:stream_chat/src/core/api/guest_api.dart'; import 'package:stream_chat/src/core/http/token.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; +import 'package:stream_chat/stream_chat.dart'; + +import 'mocks.dart'; class FakeTokenManager extends Fake implements TokenManager { final token = Token.development('test-user-id'); @@ -25,3 +37,48 @@ class FakeTokenManager extends Fake implements TokenManager { @override void reset() {} } + +class FakeMultiPartFile extends Fake implements MultipartFile {} + +class FakeChatApi extends Fake implements StreamChatApi { + UserApi? _user; + + @override + UserApi get user => _user ??= MockUserApi(); + + GuestApi? _guest; + + @override + GuestApi get guest => _guest ??= MockGuestApi(); + + MessageApi? _message; + + @override + MessageApi get message => _message ??= MockMessageApi(); + + ChannelApi? _channel; + + @override + ChannelApi get channel => _channel ??= MockChannelApi(); + + DeviceApi? _device; + + @override + DeviceApi get device => _device ??= MockDeviceApi(); + + ModerationApi? _moderation; + + @override + ModerationApi get moderation => _moderation ??= MockModerationApi(); + + GeneralApi? _general; + + @override + GeneralApi get general => _general ??= MockGeneralApi(); + + AttachmentFileUploader? _fileUploader; + + @override + AttachmentFileUploader get fileUploader => + _fileUploader ??= MockAttachmentFileUploader(); +} diff --git a/packages/stream_chat/test/src/matchers.dart b/packages/stream_chat/test/src/matchers.dart new file mode 100644 index 00000000..a2be680f --- /dev/null +++ b/packages/stream_chat/test/src/matchers.dart @@ -0,0 +1,19 @@ +import 'package:dio/dio.dart' show MultipartFile; +import 'package:test/test.dart'; + +Matcher isSameMultipartFileAs(MultipartFile targetFile) => + _IsSameMultipartFileAs(targetFile: targetFile); + +class _IsSameMultipartFileAs extends Matcher { + const _IsSameMultipartFileAs({required this.targetFile}); + + final MultipartFile targetFile; + + @override + Description describe(Description description) => + description.add('is same multipartFile as $targetFile'); + + @override + bool matches(covariant MultipartFile file, Map matchState) => + file.length == targetFile.length; +} diff --git a/packages/stream_chat/test/src/mocks.dart b/packages/stream_chat/test/src/mocks.dart index be4f15e5..411f06a3 100644 --- a/packages/stream_chat/test/src/mocks.dart +++ b/packages/stream_chat/test/src/mocks.dart @@ -1,6 +1,14 @@ import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; +import 'package:stream_chat/src/core/api/channel_api.dart'; +import 'package:stream_chat/src/core/api/device_api.dart'; +import 'package:stream_chat/src/core/api/general_api.dart'; +import 'package:stream_chat/src/core/api/guest_api.dart'; +import 'package:stream_chat/src/core/api/message_api.dart'; +import 'package:stream_chat/src/core/api/moderation_api.dart'; +import 'package:stream_chat/src/core/api/user_api.dart'; import 'package:stream_chat/src/core/http/connection_id_manager.dart'; import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; @@ -32,3 +40,20 @@ class MockHttpClient extends Mock implements StreamHttpClient {} class MockTokenManager extends Mock implements TokenManager {} class MockConnectionIdManager extends Mock implements ConnectionIdManager {} + +class MockUserApi extends Mock implements UserApi {} + +class MockGuestApi extends Mock implements GuestApi {} + +class MockMessageApi extends Mock implements MessageApi {} + +class MockChannelApi extends Mock implements ChannelApi {} + +class MockDeviceApi extends Mock implements DeviceApi {} + +class MockModerationApi extends Mock implements ModerationApi {} + +class MockGeneralApi extends Mock implements GeneralApi {} + +class MockAttachmentFileUploader extends Mock + implements AttachmentFileUploader {} diff --git a/packages/stream_chat/test/src/utils.dart b/packages/stream_chat/test/src/utils.dart new file mode 100644 index 00000000..b17e35a9 --- /dev/null +++ b/packages/stream_chat/test/src/utils.dart @@ -0,0 +1,15 @@ +import 'dart:io'; + +File assetFile(String name) { + final dir = currentDirectory.path; + return File('$dir/test/assets/$name'); +} + +// https://github.com/flutter/flutter/issues/20907 +Directory get currentDirectory { + var directory = Directory.current; + if (directory.path.endsWith('/test')) { + directory = directory.parent; + } + return directory; +} From 7091ce5360f4eaba74aa5f46091df0d168e2670b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 9 Jun 2021 15:33:51 +0530 Subject: [PATCH 342/599] feat: Added docusaurus --- docusaurus/docs/Flutter/basics/_category_.json | 4 ++++ docusaurus/docs/Flutter/basics/introduction.mdx | 6 ++++++ docusaurus/docs/Flutter/other/_category_.json | 4 ++++ docusaurus/docs/Flutter/other/introduction.mdx | 5 +++++ docusaurus/docs/Flutter/stream_chat/_category_.json | 4 ++++ docusaurus/docs/Flutter/stream_chat/introduction.mdx | 5 +++++ docusaurus/docs/Flutter/stream_chat_flutter/_category_.json | 4 ++++ .../docs/Flutter/stream_chat_flutter/introduction.mdx | 5 +++++ .../docs/Flutter/stream_chat_flutter_core/_category_.json | 4 ++++ .../docs/Flutter/stream_chat_flutter_core/introduction.mdx | 5 +++++ 10 files changed, 46 insertions(+) create mode 100644 docusaurus/docs/Flutter/basics/_category_.json create mode 100644 docusaurus/docs/Flutter/basics/introduction.mdx create mode 100644 docusaurus/docs/Flutter/other/_category_.json create mode 100644 docusaurus/docs/Flutter/other/introduction.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/_category_.json create mode 100644 docusaurus/docs/Flutter/stream_chat/introduction.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/_category_.json create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter_core/_category_.json create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx diff --git a/docusaurus/docs/Flutter/basics/_category_.json b/docusaurus/docs/Flutter/basics/_category_.json new file mode 100644 index 00000000..76d33569 --- /dev/null +++ b/docusaurus/docs/Flutter/basics/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Introduction", + "position": 1 +} \ No newline at end of file diff --git a/docusaurus/docs/Flutter/basics/introduction.mdx b/docusaurus/docs/Flutter/basics/introduction.mdx new file mode 100644 index 00000000..c8f90b0c --- /dev/null +++ b/docusaurus/docs/Flutter/basics/introduction.mdx @@ -0,0 +1,6 @@ +--- +slug: / +id: introduction +sidebar_position: 1 +title: Introduction +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/other/_category_.json b/docusaurus/docs/Flutter/other/_category_.json new file mode 100644 index 00000000..acf56fce --- /dev/null +++ b/docusaurus/docs/Flutter/other/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Other", + "position": 5 +} \ No newline at end of file diff --git a/docusaurus/docs/Flutter/other/introduction.mdx b/docusaurus/docs/Flutter/other/introduction.mdx new file mode 100644 index 00000000..fc8f385e --- /dev/null +++ b/docusaurus/docs/Flutter/other/introduction.mdx @@ -0,0 +1,5 @@ +--- +id: introduction +sidebar_position: 1 +title: Introduction +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/_category_.json b/docusaurus/docs/Flutter/stream_chat/_category_.json new file mode 100644 index 00000000..900c09b4 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Stream Chat (LLC)", + "position": 2 +} \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/introduction.mdx b/docusaurus/docs/Flutter/stream_chat/introduction.mdx new file mode 100644 index 00000000..fc8f385e --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/introduction.mdx @@ -0,0 +1,5 @@ +--- +id: introduction +sidebar_position: 1 +title: Introduction +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/_category_.json b/docusaurus/docs/Flutter/stream_chat_flutter/_category_.json new file mode 100644 index 00000000..18f33a2b --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Stream Chat Flutter", + "position": 4 +} \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx new file mode 100644 index 00000000..fc8f385e --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx @@ -0,0 +1,5 @@ +--- +id: introduction +sidebar_position: 1 +title: Introduction +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/_category_.json b/docusaurus/docs/Flutter/stream_chat_flutter_core/_category_.json new file mode 100644 index 00000000..748e3f4a --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Stream Chat Flutter Core", + "position": 3 +} \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx new file mode 100644 index 00000000..fc8f385e --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx @@ -0,0 +1,5 @@ +--- +id: introduction +sidebar_position: 1 +title: Introduction +--- \ No newline at end of file From 3dd197c188a6272536b2fd841f10392c25a04eee Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 9 Jun 2021 16:14:14 +0530 Subject: [PATCH 343/599] feat: Added docusaurus action --- .github/workflows/docusaurus.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/docusaurus.yml diff --git a/.github/workflows/docusaurus.yml b/.github/workflows/docusaurus.yml new file mode 100644 index 00000000..03fc3d5f --- /dev/null +++ b/.github/workflows/docusaurus.yml @@ -0,0 +1,17 @@ +name: docusaurus + +on: + push: + branches: + - docusaurus # default branch name for each repo that docs should be push from + paths: + - docusaurus/** +jobs: + push_docusaurus: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: push + uses: GetStream/push-stream-chat-docusaurus-action@main + env: + DOCUSAURUS_GH_TOKEN: ${{ secrets.DOCUSAURUS_GH_TOKEN }} \ No newline at end of file From 5485ec3c57fc6702cf14cf89341214d7a2995f4b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 9 Jun 2021 18:42:45 +0530 Subject: [PATCH 344/599] feat: Added basic files --- docusaurus/docs/Flutter/assets/sdk_title.png | Bin 0 -> 577110 bytes .../docs/Flutter/basics/introduction.mdx | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 docusaurus/docs/Flutter/assets/sdk_title.png diff --git a/docusaurus/docs/Flutter/assets/sdk_title.png b/docusaurus/docs/Flutter/assets/sdk_title.png new file mode 100644 index 0000000000000000000000000000000000000000..4d92e89142f4127a70382e87ab5bc360d517295f GIT binary patch literal 577110 zcmeFY^;^^L7e9`HbV*8zib#XfDIlUCAxO8pl$fJNj{%Yj3Krc;*FYEzBLtKhJsdH* zd-Q;PH|G0ueSY}<1>ft-b;-7;7x#Vc6OZ#a=iGa*ud7Kz$xcZ^LPGQO$s+?25;AfU z5>j#UYxpN7e3xADFBEQ1%s?b0RE&gwmq?OR+3*i9febX&NJ{%SSMVRMI;iTXl8}_g zQk~k7l92bVJ$o!R9@WkavHVn6Mpe9+Q-)^7QQ)>e_+U^ z7PHeTV1M}ZsZQJna`|k@kJOp;6pu*X&~!W}v)O(5e)!Rw*6w_bC*nr7?f1$5yZNiB zTEThQbC8vHrGFCFiJJm9Dk4yT4A8mQ;S}ibI4~KEkOM;-N0pa?^rFV$f$2HBS`RMa zg(Trj%{%=)bd~Vd&29Y&;g$HqNqt%EfA2}AgX!rn|DR7}VZrqO=Y#Cct6~4=L-Qrx zn-^LU z^NC@^z*^D%TZHGUTN7)D4#*Ky+y@j`k49D(Ia&8T3&>{eOnf2-*hId`{fdTJW~DKY z+q(Fs1?_iK+NIN7OHgYHXs8|?446qv@^XFj9W}j!UdIa-MJW8YbnhYsjy&W z=ONv&{G5+@VaR>aRm**?lt{+~f52k&&UY20wMAcY+}I#=g6d9N994-DBy*8x?Ktr7 zltHw?U$)1q>vewhhJS0Uq4MrcHZPUXrHy-mRWKP+{FcEM6c_jw%$$$Kj@-UNBO=xd zo2=}+w>G@(R3NoJaxh{9lVeoZpfZU#NT0E)+>gMx_47mvjjcm+WVp?93XfiA`~BW+ z3Q4KVnt1WHT2AqSOexKF{N=`jrg%Y8uu=FKHhvTQcbEm%R60ih&uL?g3*4NJpFX78 z8+j{tYOrIM65AW!R_YhbBy^e2=yau(>$nCQ@!oIBx+L>kd0>p&7CSmz8t|lAYrtke zF_}(cO_8to&A}O9{B4y-4eOMi;f#!x&?EqJ6!51?t3e0LJX#x|F9)@pU^?nmZ&wse zhi*K7wS>JmGK9Flt?P%kiPF$^OZz_Q^w4Qs@)l@ z+OaHNtdMpCT)yq3)%BlslJRKD)I&vM^xr+eDct@&gFQ86UhSL0bE>QtX`LSAPF|fY zPH!M-2#aKZzYj~uAHMo^kwjO+Z1g!u+S_g_09{{f4i{iP8@xI zdn3gNZ0k+Y>C4h;KV0)dROgP@QnG&9y`S#z_MOHW5HNbcyJ@PC;2X_N|C1+aII1P* zT^?}phTweby|oW(h@?1)(p^1EQhVByRW@y2gOXoGZ$AU#dTD3Ev;&vNLzCO}wq@@_Ak_Dq$8AnFZy;5%fg-A}nk-5Ig2 zWT?K+Kq@7BjV$=%_(P%f$@(WU_tFsj3NX=&po@Nd;qsSCRX~?)8E2nx9KO^LFNHC#t6v_kWlu7nxbSSZvrC*1{KqZgzmRJOtwcB5A?5~7$&m6fk zI=9n5*t9TpwHA4tJ;Xjcsvru;UzqNB&Fp49^+4SCvI}jQM>dkGUF#modQM%=EYXKI zf3HO6RaS`uwtoW zF0lRAg?%Y8PN2pVfk?|&#swAe>%G-YAJ(!ru+B#gqW+w1^>b#vc@lCSG9s}1{iLCO z32Jdw?Dx?HUQ-!#I+(4EJccA}BaJQ0Y)HDtkiQswZL==JdMvVxBWmSkv#FY_3c8iu z9nr?#pC)C#cFp@I=gB_mgG*862jLO5f4lW}pV>bg>ZrkpYrpPO83#eHPWOL7s+`>#lp z@zXwg9g3C$FmBZZv{exxjtZz7YW!A}lC#*~1>T8Ok|?IpPD`R2tO|EMZtRPQn;vXc z+As+BjmJ(NG&mSa&FnSAaGqf-W?1C_WoSSFXQQh)5>!qNse7|1tO?3HJs(lbDVPQH@XCYGUc6|*YvIH!9{${9&Nu}2oHnqSgYqC$jnU?5;`ob>w|C3G z_uqNLoOp1OqRUlJ((zhoq_D+ocHTq_$ZKEJchVHuIp>eyM?f-@LLYAwi#ipjD;9Eg zH~0^p=JB3a>;M)Af19TncjEJGt=YXe^Smwr!SPdlw1;HnRS|^Dq;{%(!fKrvc<8kI zzbdG$^$%7T(G9Mejl{YX>#oC7Y{q$yGl=eLC_#^ZiEF-o9)SQo3$4l%HlC$2YC5I?B{A(p=Gm|I?+= z+bW}E?>7_$l}|7jbMpPw@{=37pI@c6;G%pCHenTH=~6Am6v+R4#&=7q78VUQ9rDaP z?X2Q@4~I&h|9qWfqFjZT^OCyS3%+&Y{k^f=PiZaeJ|1!dacglN4g(eIhFVJ2eM<8*7_jj zq6TfLutw*J73epGuHpqpZa}$zuMMLn@^FT31j1C(lVGD~mk%&c zK9-}8j&rT$S}=gw2K1<=Q~6h3bMq5@UcF{bOQp`y!#Pw7*!?_cKWNFE#`BrGgoboT zsYa?;&b~BIyg~0lHQI{tB`er(D78|( zwXT%$UhR_C`lc$fcp)#Np7FcYBPADR&;Vv|d2koICpQ+JrsOr%t=u zkYMvMV$IxqW3T8(z>hvthXe$S*`w3&_iKZLBcWGzq7S?C0;H^I#!&1Xi%40dd()a* z-t4BtRJj&M9Svxh?S8o8>}BPz?9B`HPwf_EtD62Kd)MA{r#|i2=I0UG|FRz;1qt>=5F_|cLXMK$m&+c2$0Qcq!Nx3PmK!G(Im zx!tZ&Mn`3Ccj-lr!eS**U43D_6>>o-q=}fWtC131Di&^aOLYF{-z^tM@&pgI_%@il zbVNw0OIgD~tlWQwJc+e)etl!!o|K}X92;RCkgBcRcUYZ{(JZMB_7Bs2En4b!vLE;L z#{;%TD)w0am=nHH*=p(y@ zfoyunvV3p2{%%-Y$l>)dd$+1tdj0zwC7e0}=I^_UC*q`AtZ1iqxg(Qf3Wjewao4P@ zD@D)`CA;;1ObTf*-*q@w?qUFNWB4<(avV#oh2Jii-t}Fds*js?r!0(FQ!+gr?igxr^fF6flli|21Io7rCRFP^YMURSwE>%E`>7kL?@|~MP_=tZ^pQ>3^UtGTRGdkl*D4Ji0aPk`o66;w?>Oui(wF%!@JB2h*GuKUBZH^Q!L z0&@(66l`^CcuDmrh#uP4mgLWu?f80c%`WyDMeYPpD~^Nq^?sYgmzA1FgsG- zz=}Me*|5_O!TMl{@{f7zRhzlNl@tfkReP1^)*&!kklaIHXs1;Op6&K~FcDCyl$bW( zEVRjNC{~!2*!n*MFF9=YPUT-;m%Nnq?HE>TJ~3%-npepJlXhb|RPKdno%?ZFY%TkG>z#A=M84

J30 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ⅅ5s`__@i7lcTZHqP8WhqeS!mUrSX7^CqHeN+3Ra{H{ zQ+_>5sZtA1e_{XD)Pu1Q0HBT>ni>|k$2GVdkhtMqeaGK&lZ9m5_xUC?KJO_oWBK-P z_a=~-TN97J`2a6MBu7BfG{=UTz4n8bCM(`xxz%i!d$NmQBU`h`-stobxM5NM+StG% zt#=+*!@%_A_9}1ydvs3la;gU0CLDatEn<=1;GJ!S<6Wr=s9W0g%!Lj~3i)jSpKla~ zoxhz0p9_e)a)0PIET$dfEkd!p145!0-9SGno;0+nA)YX6dGj`D6iWMXxbujmuN?f= zpB>(lyC`f8}Glu?wG4N-_vd{09pk9THRkTg-eXz_j`7<1u6D57lZC|Q!t zD8W(c(Zdu~4q9`4Wr>gHDUz%6Ded7Fi?@ z^OgTd?GLEvCr51#%n6EWYSP;B*`3+`6wcE*sx9&q*O}amsH8TioUrXUkr@BP*Bn0$ zMI2`WtpD8*5q<-tEl5rk_ml>kHS9MC&zWpR+84M}W;dz|c<}6W#N!iwAZzAT0s5>- ziL|DhQUbf0iGacs%WWH;Fil9BTH>x(!r;lXa@tDevatwN56xtc)m^qhz@qY~e(H>k zjbp3PX&d`4>xNXNO`kAsipv7U`O4;|o08uUc^`Ts2{)a@8qljM95Kc2`4yCZ+Uc zW=1E(4l<|KGNnaBQ=hn2qGgehz)D&Yu|{8OT{8UsJkaL^?9ihB;U(SAUW6VlVXhvp zKK@o**P2}%7#eqswJpM#{-evSWP7|$guWNWKCz(GU{^C)y&NAuvQy-~Lt)#ql(aFu zwC3@!MC021klY+OdCyEXM@q83Nds=ldqadOXSZc03zws$?(ux|qr;V0wvuccf5XC9 zskW)5iiy9cp=H{M^~X`GFGPWEfq*Tw)aG;wG{nMoDnBz!RqFSfJ3m_Tig!Nf%xn0X z2n-41j|Uy4zd&9JNyOU|=da$HSEpjGs_zi~&~oyoQQ5oOHPdawMRw9)Hfp|x+_c%& zkCS&SMjB!})SjdOm%Cc%Le`BtuXCaR!vwYLElULlAf_o1d^Xu#K1PJ3`QLJ>ROn2V zZ4_-!bVd_g<2NO{4icm?4{7=ue&+J{XUM@sN8U59?vwryIA>!$abmd>+V3r!4ilc} zjmqt-)MTG{Ha4!9R}KdF+Em)L@e%jN&DEwi(nLp?Gnl3_ZLh6GcH?(?HT6yQ(Xvv1 z$eW~bl@sckx8lSq&U7-trUPO2^dvp6Zs~28dCDuc|$;3{`G|j#42LZk-~SJ=y;Fo=W?cs`Dy%V~pVw z+)aO?Na#SR*RGBk7$KI=r1WVOj0ap_H}HvsD~0f-Ez<{~M8h;W?Tz==$r2p|7ZkyOTS z(zugX?`?5FfvNI4V^O{Fjr{mG=~42t8XtSudwna{2(tc&WEm5L0Izm`Vm+RgONChk z-qyMPfL;HpJw>kPg{CcrMt6zQmg=LT$!l}MB~j>A0Zfd$Ms<95RhH}>IkSnG^fLqw zwnW&Ty4D1m>`N&o9k^!bzigcXM_AOr_ba^{#obzr(~7RcBqE z*Rp5%&nIg9mF=6X?BANDr`o@jQHS3k$f7ezUGMBXu4?(Ro*{RlCflL3p;YeW-`C-! zKA9KRazAZR>a-jy7SKB>UYdiGIcmQ`Y+JsmZPA2rjIh}ygIpae#vJ0FxlD7Mn#~w+ zrXd!S=2@Mz$C;Xk06;fmp_$ru0QR8JOvE}7>1Tz^oHAMaG8iQ6dKA9H&A<&Teznp- zJN@@P!ZQlIZe}U8CbV`UNnbchHw->c)TE8FoNiRdXXfmaDaYbbVR9XMdiil>|vp+n?LoL z-^^<`weXwzh!FStJehGMg$}&dTJ2{gzo1aqascZeA~=mGcjPc4hGp9NPhpu?omCwI z5^r}XQ_7Aj{p;4JjLG(T=PbRfa9K``#rHr(`-FsG(8i-2&G`E3YcK~hj@`AI7f36{2a!m+6keJ<=g9xs?pt;C<|jv@7jhQ zYPUyK-}Pyz@EV-ALK|;VDUgDuYldFe+&Y^4g;2QlUQZ{7GUh5oY-_nW)pBwuxUGWS z%Hu1a*`&)A0RDa(Cwvqb?OB|FY4|^~pY6z>(q)5=e zRICmLjk>Iol)NdCEqKZ1&6MPdoMm;)H^8eC*gnWKapY*duxPfV>FHmCN7*2Ww<8?W zky27Nk)0VlApr^~QVyJyWJ&F-4XaDMx%zlJ^OPt+RI(VrW7n%pXyL8&&YL%2!gG5* zYRsubBbuo&)tV-r;H0Yx+&nyLKP%2zlH2dw<;wOIGwxw#->n(xzQd3|E5sRMtuV>1 zg()86#vSYAQsE8ut+M-Yx;AA16H^}iQ1QG%L>cQU&tK4fdlQWsd=Bnb{;heugQ{M^Wo|(p z)MYkXl|3rYHwc(_2_M*~>x-O3xEYJdcsIgs3(d1=8BuD3riF25MX0XB zTZ4x;=EWe1k1n&}q|f|uO@*&@YBnuxQnHDWmWinPn)A4&d8x}_16&n`+N-bwf9n3qI<%v_mcDmk`|_PY^77i`mP-XV*y?-3AxYj;mus=2labth9V#`X3!&=pmg8o4P2!I=KheGV<+4ncKu+On$8$(1tP0h#5?u z@Z9^Ik3w^UddniD@VlXA(ZV*<#o8kcgFv4rMUU6Cz9>jQN9Fn_f`y4RNNk_YE_MT=n{(CtRZH#31O5?4RAenW+PO8&k3!WwSzr`Eht zE!v1>nTH{?!q=unw3-SY=vBGmV=%lH(2NMaA}29db%tM$p};!+#4haM#bFQ$D@8cs zZMloImYhYYUTrbfG4@SIz}3Gobn(q)DmP!=JiGCw*Wdo1x`qA|x@*n@3#z4Q2Lc*U z(9;Dzl3kaUU4M4;MOa!oZIGy4LrsmXAT)M9-`x9*En{8d0YQyS#gY^bYnJQd9C;GS zN3xQVUXAcRkmat(YrFowxwRCSi{sAO!s_;`DoZ6j?_J*c$Nu8a9^l@&ee?0z`3@Z} z&c84J_s9P;e+s0RDEMrlpQi%O+!yw{O%(9c|Hhq|K8Mngx zbyD%HKZcqjhosB#ah{&1)~n<%$wrj#h46QIqq{4;wc{;&YuWlW)4)*UY0oNneqvMX zpP5H+lV?v<9-zvU2dR5eCUa;~)j(7)R zJ4tyKd7%05-MEND34nryS!o=dt-t>A{gcmvEn#^Hzf;Zi_3IVG8zSY&D3YQwm_Sy{e)9Nlfj-IG% zD(OUO$!$qzS!xc+FS+`=Le&}?gzZ_7!b(|*%D=`8g;MQ*Rw#e)41>kU*M#j`puumk z{Y@1@$WS3o=#JN(Xr0*XCbY)dG;alnG2WbQy7hl#po?khzNFT0zdzaT6cHuCmb^v? zpR1a$uHUSOzNd(k)wIwlH0V~i#fNos`YLmZ%0tNH&-^0YIUk(>;zC(|iM-ka=rqw| z%DiF{Eb**mDd|+)q!Uhl>5t+&u9j`7-Jw{IwU9GwSp8ua~R-(6HEx;anZmK}*}G*z9$#~XNx|U-A?b0MuG~imhb~9CVv=%1PL}xVM;vF|D)kg zqO5_`Q+orz8Hwls!SBxeM1&+A-Rhw;`(cq}=|s%~ZWA0xX1IPIEVumV5uhaSRjFIC(Yv9z>V;AKhoj_% zw9Rk7VY&%-H|RB;kumt~2M*uMyL@_Ds@`D+X3pzWPsFrlC46;lNnvD3Q6URabjN2Q zomPFql3xE45-Rj5G{*cXXUtSQzF^X?%fgw(Y7qPqksHq<{WC6Pj7=+*m*~tAm+rWg zVY;;mjXCU;KbS+N%^tk$@DY4oQT&-Q<7-Z&+`nynNa8E5NEBYvtW&cUCim>!V49c7_hVq1ReSLx<%`c}OC7FF+!jr>Ccw%^~}| z`+)md=tcNNErt6__&cs+y5qsJsC%e*B#2F64o%PY>rw*=KC*6!sL52s-D>a`QVz<_ zDr$T?@~9_6K{7|MYTG2?9XMRPLirA9;9lVwQS`IORpvbhrne0F*`);dq;7}5=*~Hn zUT`2kZZJ@AFbM3t#r*&-$X2Y64CP{rC0U#9mK&G*`rZt z4tk*k)!@)#IFPJngy-B3tHz!8jES(cnl;FwW{$O%mPmLDb}vh>?mSyqSw8N(BnA0nAo#pT8r~(F{Sk2z1D+EmF}9ggG+LhN(d>+vFDPsKi_@wKrg-r zQo?dCwrAgLB+yQl$m)IF#CF3A9gVjx()sV6@W)snK16wn@(K}sh|kr~Vw~gXMZ9-t zT(SNls_CAntQl`9$OaZavez6es{p+&xvbUWu|AODvDq`fpSDFOyQ^kwWi(ZsiV;U~gF31y& zZI0GnRh@2qfh*PhdR!tq0cNwifnS^6-IwgJ{sR$7+?f;5nU8$z9AA{Opvk>a5H5>j zCPGk_@hH_ukZ!*BtHQwe$1&=RZ!GgXO?l$>o5EDYTT!l$U$F@pcMJ3P$ljA!y-%9TnkSH@D;!ZrwP(cf-Q8yD&FC}stp(;S`ixY94_+Z=?ZQDw3b*!QR~sg704nc7c%q}kP>$IgcG}OxvtH*vkZe%s zX@9m}saiUEZ{TT7s!USno{QTUq@_>Kxsanzp!I17kJ9G5PLFeUi`5Y5e_;_AX1ov+6t4BfZuVjwPD5t~<{`iJ=9$rrXlAwKCj0${4UM}6?spR`NAbuC82X1&uYHb zotpj^^*UD?{>sL)lnF!PtmIzC{<8kq34hoQXf&0%XMVEN)9jZr2;On#!rRTq#!h~A zsO_J<`^cXudn|b*qHT{aS5$~NB*jDI)X9X8Or^#B^ zI-qt*5PYSjX|5)K^>dYncxJlabj{JG7+V;R6tm+MJdZ`TPPI;f+-utrH5)J@+^r^k zrg^tKo^RM0)rdc7`KeBw24E#RDM5nW zAnKX`|K5P(N`P?^e8`So%#A85HU}(N5Ng9aYw6nQYbR6jwXQ^Kg|iaPxDEKF+AH`0eK!Jb!ma1O_ZL_(c9~otdUd4y1fJ;l z9J*b!^9MA5BD143Oqh%e@PavYyr8lKyVG-DAsws7ZD)w+ljOq{Rc9Pq@FRs0`D{;c48a@lZK&DW1W^}W{RMAt+~~(hMER}t>4kaU5`#tZ$3+D!5iuln`ZCklv(L)LhJe;7AV!BPgAW9_UKHS zJ)6-jGM0x?!P-BYD=ml)%)jmo6R8va9Qrv40Wl8O0w*d-qtwoB`Ioz&8)oeZM&XQ5 z*cFV7PnSoq9&_%3exuUkVw=#NmwHdbE}~tSK1gI_xo?Mau9(LC$*in?mxcWx_N7~n z!V>sLd4E>oNiOw>(l4b>6@O7Lb(?7mA+JV!E$Y`1rL~_8X`e^`K87=mPxJsyg?L9c zwAO?b<+XwtsAr#e1s-W@&-71xrpWkF=-PQ{5YBmQl18M`+e;IAfl}T$@tcd&RJYX9 z7yasxaQQLBZc+jzH{FW5BnDt#3h{c*Ph>`SxV>~NV>nK)V?0(_=|Q!!QG25ES1?`@ zw+aUaN`|L64M5!J0m2`!$?05(M|ECB>)(;0+&%oX6bwkGs(#Vo_UL4QU`ugL*<(02 z$J1gD2|;FSE!eXr2Y%kIU~vJgVkmC=JpI(%nk4G}7H2(hU-etO84S z!-CS?-6ElcNO$cL(z#Nyl)L+`-}m?Tp0ji2%zS3?Y6CrP#qt8ZC7AWJ3T`5gN3-mgfJDwI7p?trkzMldu4=92e{mdQ(SP30E>q_G=>9_6f z3zUywr`jg+-Ok9*UW1q&Oy>DD`H9JnQ!sBEN4nq+=N~MIUKXykKv&#In9XD3E z5F0;HbwRiF9$6o)ITh<}iO$HwGT-`c{w|A~hslI}63X?TC9h)s{_U4U7f4PCI7o++ zYg83%`O)VwE2;a~6wJy7jBt}n7b$OBMmIt+K(neBmH7{D5`&o#$xZI)@d(wF#)->6 zIgdN~S}p2=kcOjb5#Hrm4K_F5C6t2lv$ekhSUyTBxiG6pAlE(zMq#-SP-L_)6W}BQ z*lB>S84$yOyw7h{pHtRH%Da*q7%$7Rs6+5o7OJY_mLAxm>)xEmKr+#bZWIZ%c1vW} zBkYmLAMZ}V5g)4T0X#`a6wXev^0W_UJyu`UtCx)D)PcQ}z#juGpYu4Dc(vk}vgEg& zj&)@J{Yf{vhT#<5Y5_tIFXtjQD4mpsP9I*BYD#yTZVM4dWq-{9Gatw|ebZfN#V<=C zo;~xhfhEzw0$Sl5SNun4W>aZs72cU1d-AVq$g!-79}7!-S4SpO?SKT=QH$m-pB__+VC9g73C3w z%{WL;S?-fy+8k;6NcZKFTm`r7C7#T0AKy6^4G7XaPyK}67M%!}A?ElGg8qZd^iv4! z%l6Q@#XvHeCk6f_Is$1zmgyYg(<7KJVwA=&qvf(e@M^`tq{n z282s=b1QhN`XH}PZbS}nra`D&mP~Rs>6O<_nQ4jxiP0X2(u^2U9`RLc;a@DREr>9h-zrwLT(QN1=zbWJPd3j}N4affHc9G{%OD+wOK zfARgXVsujNm@{v$jc>!3K+Uj~7s9=76k57>nOwE@Pkbn6-n&OG{sQpgiF+@p&e$ks z=ngFrtv+SKE;I-40o}a*s zf4Hp|MviU47Uq_QdveXb`~{_HVyy$wOx6$WI+1Z@*Vo`HUXU5sq@n`a(P4LUD4So7 z4a<#O9N1+Rm%0YDL&69+R|5h2j8s#bI`dL`scMVLY~k=9x+h=DqxOf|9pt}uaZMi3 zE`Jf}u9wH<%J@{&N<^NAVYwkbB%CwHN;c4TwX8AbywQk0j^F>Pl7dQ1P4e@8Bdv?FHufi-vpSN%2zN=L>YISQ66-{e5g`z1f&1nP=4JZqQZEzJ=Cv-M$2%;ih>0gcri?d zD$cyt70b=ODouGFnSO|#84PVBs`GEA@S7L^MNx8I7kN9rLbCn0%W}RV#d`I99%bZn zKo_jMLhtEGv-ZU)|06{w`TXjMtmJV(p$bTsyE#v!{>=bz`im8VvmQG~J;(c#u=aIZrv=X#L+lsLiE#wwt_XojR&l zuelJ{7zasba{1;=|@4;^oy&Rkb&&m5$g4Y`O50tIxR>TYgw1a<<&*4)GB5 z7wXkq_YZ(t%v*@B1m1^kwe!VksT=dPdPJH1@c5aDru0HX)gCUX`U=F@FO$3$8Ea*| zdBKVEW*Bw=!@}G& zp`QV~^YNBeMHK;r_n8aMbcy}dY3~vzYwQUr0e2o+0-&gaqgU0qFLsw=cn=LeIUYwx z=|4C5EJ_AgqXg<7LfQs|4D`=kXiH%Qj>;45w~j}JpSxb-{40-Z?1gfSe6RKII-tRv zU#QBdt%wF9BdFrU>QWr-DDowD`<@!^lJiZumXsOgyk+50di2;Uo(@wQ2vC=?o}&GD zKyfb_^h;C_Z8k|XiO0z#?UPE;$3Y2?Qbia_tZAzq{+t$oK+0nERm zwgHkq#f6`WKu6bY9O_2;7|ETl_30M;K_6eJtw4Ev#sN}(Z-0Bk7~2%u{KQMfxSNT5 zGqHDJaa)!wy`Im8#^;E~rrv*Az5oJUwnc6ln?Qfyq6gmyQYuz>onn5T=6)Fgh* zwI9wceixr8uyw+5C)6x^9sCQPD_QnMiZR%>9KM0lvmH>^(RdC@I{JpHV}W}xgT2l~ z_94@&j?PYvtWPYkdzQg%n3cyAev46UbL^t*c`1s!RJ&ZK;55-DbdwW$bL~bmrcgM1 zS&hKbCl}yF-k=u!N-MxJ%fh%23kVF%n^w+TuQv-7| zeZlN4<=oI!OHjGme0Oy{h#GD|Mt4OlRr_7I5qO9g)goCt>Kx3C*jvH~}mN%{~I=lnj?9>v~jUGE17*;KNF1&ID2a=FZ?!GpbpNc1D}6QJ@hQC z1;2I4q=ct&Y<+7f8C>J{bo0bKT1gE&)HT2ow#dicMKNy*c^zsHuNB_+ng!Qm*R`KQbO@tunaG>iX=xa`9v&1$ijydCiul4 zkBkvJftdX5NmSdIs7h56rk7>x$=LSJo+-B@yK{kc$^*{(r9o8N6dAbolKlZEq2A;l z>EvCwHM@MW%)voNKT17`C$^t8b-#oUs=mnZ>-1~;W&x_*%!KCc)I;f(ALcnql|eUy zB==s6sGc~h?0=wgs&;(@Lnpp8KLMXWW!m)DpBipieNIqX>c2OV{^{Lph~;g2A2VkV zD+Y+6Tvs0=-qm@?l{(Rx_>O!j+&{F^7WYa!u3q`o$W4%fTXilCZk()c zD_i#4#}B(t{+%xvAoZcC_C(&a>Z66N+Eg=CO9af)>w&WZfhMO+Qo3jUg3jU$G!p3T z6!sXi+OZU>yOke_8~8WyBrIc~#=sftRh0_>b0qdrt`q;k+(h!kZ%g5QiEM)sQh|@B z6gaI^xz2|vIx2#NJmzgmx5XxD+4AaptDX@;v<;~?rJMoTYbAM>{+vwkdgfOoNA>sT zyDYvt*1?A9;BAs)X@a2qN+x&R$1L4Q(adf2sN^K0oi4m9d;(ZfOgnnlLujE;A7nW4 zwUr=4Ju>6KGRF5G{H<}vIZVvYd zIm*9O&fZDhQE(g7#$sqj{|c_=jZnWYgNM4A1`f*#58y38YNlThMyQ&_*W|kYsUX&D=-AqN%O|)G#Ic7SVEQ-c9ZFj zNeAZ674ILkfVqm;j$dBFCn3C=OCzOq#;Ht;ESiJfYjqiftbec<3SMG+gc3FS`jCgY ziSYcCs(us%eOOC(a@E|f1|!gnS*Tl5R(5a&8d+r~Q3HybOKIQcrqGihwo`DudU1j&=rCeZi1$K0e@&>n8y)=vMPap3p*qK9P=u+aE|O*>3E(_M zo6aYlA>$w#eXXXJPTAWa`l#d49P1b{La`SNUu0QE-lr%e0mZK9EIwLRy~(8yUN@qq z9uac>Y*3Lk+weS|evmusbG@}Wx>cjUto+;L_novZme^S$wmfHg2hc^K2aDm9cVI_? zJcC@;DDPx*fbbyVO&!OANZ4*2DLkhFKZ~!h`0k=eXhz%c=y&oE3IwPQkcmKSJnds~ zSp@)7nt@7bjV#ZQtbm=R=~oTiZEdz_oBL&f@9+Yvsqb)^*4lsKQ@}9U=(j~flx#>1 zzFwV;gid`>DuT3(YRSSKjiK`rzOTsaAnAWoJ-aj(b%*ZJ|e|IDvk3L8)Yz*-hmFpVWWB z^OgI3+{t>d1y~qryVnsNx;zBrTUtzzF6|i4HRDmJ$X(uiVM4WYTj%uls9fnN1ab_F zEP5ju32M~+m=4MJ>nBnIBWVLq%s(A!rm-z$FxdzdPuVSzl<69s|IL?QwRvI{)y;e3 zU()aOKC9jlF3al2(4h1_7W$|kA+K*sMfyT>Z^P~TrTWF=Y-8DzjbX}r<&@OI`c0VM zEK1dE9aYEwQiwF$dwP62t~@eN4`BHT;BC5cHSBoO;SLhR3@$FJ$o;&h zuU&W9vRgyJy5G3uXrRVuI((MCZ{1TbsO&IE#E)Y&4ZycKGp%YL%WMf4=HkVdsJJ!X zw$*#DzV6q@JI!+YDX|}PIGyx@K1+tba>VRKm4I=mWrTPc{KwIRx&7*wlAlss#t;<| zLlb6?88zKAS^!|Q!r$RxxuAzkc7}yXxfSHgX~O#~h*pQgnNIBt4RldgZ*dhiP|i-GV9)7f zGDkF^6>L~c;^CBijQ5X!zVz?6E6pdYs=!zgF#4<;FQPAUSq7I9(t=kiHmrBK17r$B z0sI!U?(O1@Sx$MH4q;+(83~ALD3*I5Mdg56cc+&`l%sT}MtfmyDC%)=khFVU zla?TDc}e(^>%_>SjXJL%k;I-5ze+(;9kX%Px;*fc)7|UBa3ZhS50>odL0r!8dRJSA z+I*LyBQt}1P)U92tMw5i;eDOkP%+v>_^}1-uG9hdTXHbA zYBab;%xmFZ<>W?>7mVwM>xkLnUH0$iTNS-yQ@*B>a!KRckrl{ zKD{$R$-gl9^yWrT@z4RnTbQ?tFSj{1m3ADt;U? z$a@rN`UUSq<|brxPN%U=#f5}W8pcxA7gix3Xu==5B3Jx!5H;ZU3?|bg1wxZq{`+px zTLvfe=Cw%Q`z!HTxuAF0<}-zIf&AKT{M>XS$5X2%C7mY#N@or;zLVQ+v_VltR<&u* zSF@#FX&?zU3vB}qH(~uVUeOvsUcsCwyixXNr*rHgxJ+#B11zU;N4XEY1Py}Vev?q2 zw~|KeyQ5IQUT1Liq?j=(ebiDJrEyEpbPwe1I>C>(2mUP0;-aB84d^dk) zA<;Ih=51;idf8@q6&qIs$-C;&K8lv*OVztcL!bp zxn1RIaVEx;p+u;V%B?jL{O9~Ki07*be~QS`OURpx)DAA2(7xlNuYt9Bdb2MCcERS;FIi?>)> zA3d^hS6rDSV`>rWaH%RbECrZ*#ELVAI6~4z%{E+lbLyQxr@TUKDqMcb0%oe6%Z^-$ zhIRi&*w|&Q>lyV<2$GWv2Bg{5jfCgShhIe1)wa#Gzm=dCI(#s1px|9h@!CiRu)f1= zE;80(W({TlYFz0Xh`5{?DNn!(k?-bzj|mc2#JITBiTj=&F*#!!@Ocm`IOXI^bexZ* zgh*~=x4OZPC>3d%nqM%!ssGw;-Aer4V(0sdR}1F%7w_JAF=^7N08se$iEj5!U1?cw zQ}t%3Oa4OjbWRA)j2b|<_DgP&`O=h$Qzsjc*U06c1!UtWprp23YT&d5r_lgcOq+vj zx*4vyK%F_I@|=j(U?V*bOIn?&h@hg2o6kDx;Vhm`tGL;y&DS%tBL)Mp`&jhBAn@X( z85={+V7O$iQ*^P02U``+eL~tl(sl^d!E*n8LtIhuY^A)khw{1#sej`|V}>mP@>=W8 zTX_`u{x|T<604yi%JH@gw4fncz}5neaIo8TS-kKFcBb<~12^hz?`~p@_@p6?6P^1tdegBt zfj?@JMl-ES(9nw}kFfR^6!x<+MPq%BTPW>3u;L&umK3RyS*wk0AUiL1bE??#WJ~j0 z>e)R6vrARoGWI8iSr#{8kOj@TzpfF26+@_pX7#q{38;)p&JKcpp0Ad$Q~NQOmiL4O zzFv&M9>AFz&?)*VgHNiOTwY$2n6HW?d;u0jl@)Qhfz8Rd>yS?HvaIyxsxtIIrKxeK zOIv-`2w4%}j9&M|kfvj(9*pSmA1!0Ifr5EYE6aGEaxaebTZar0UXvUBb|F}QFk-$T z@=&p1rbCZGI>Njw@y{8@O_tzCxi6yOYJHt)OBdkUaJr)(GH(qof-|CshR7HY23|xy+5Na7N>U@<_s~IC`7QDEz`3n5Chk^gD{hj|Lb36tuy* z`s2kNO)4Vly3tNEY^&jeSL9}@r9ai64kccUL!;^Kx;Fqu&}=MX8+hjbe9a+lwwVBO zMO^<=qjEB4Y6$fmZoNV!EdaAeczI0X?`(aEY{6-_(^lSutA}7>Xv3YtTF?yIl6kTd zq?IbkO-bMAxaE~5QmLIymSVXKHwCHuGvDT_ST|4u`uiK{ab=TA4tMZP0IOF7Bh5S@<$q z7K3f8{RaL-DH-u#>gNm8)YMjqSa%yZa_K51m?RI_6;PfL*GLphb0{`F*?RFYSm+B_ z`=0o*$2Pe@m^4MH^{Th>`xPKMu`pFcq+0!^>6dc0^Wa*C>|dxatzM-7@dx&W)j4|Z z33&)F~wY<@+Hx7}(*1uLI>})q$PSN`fqLX4KUj;gbj~BSSf%t}4e(BD6F51q^@Jb2&NPFz~!Y=fO`d3U1;lz zCiGeFGT80~DzP{RkE%C%J0%31rQBT*gr24H`IG5il!_{^Lj6!bI-T8>UNU)8Dazdx z=asWJ%6Sg-I38WsG9aN0D?{t;;%3m?NnyV1&d$m90EPfomC z0Y}j2a7(2TKHxy4>$UQ>&Z$hp0h3=-AmlXWOu+JkV!)Go$r*(xjNR=x+gTngth%y(fM2XoSNv@?#^6sw%p9cjMYcY z?Ujx?v``5xcj0CV>gR=-?M9(1Lq=dCG?O^z~xML5GhT_GPnv^)2*?3 zl3tt;xbKvjyv6S&+Df>5r@ynQb(!6ae%4!RnU|Ji(vX9fFSY*EK%UaWygN~U8bG@i z1Ap|Lj8SImwv=}+tC^RoPvb!xpnb~M9MLYtRjyh+J_~GM>WLqeWuFeXR~GUa6_GKC z)~297d?i~HxeQL>$5_-ty3svG|K!f=qTzJp{rqU*z~j6wQ_8Bp`R!<|{VtxQBn5*} z?WcY&6u1z-KSv4{Mu^11Xcm>+Er!t?%UKF<@@#qhvKbYf3o{p^ww{ebxxr$ReqlQa4DO{Sl@%UbOio_ag#%Tt0C7l>DOx0`@aF z!bRC+1qNMV3>ejwnjJsnAY}R7`7E|u-{+Og5=*yyi{O=rXusj}{=Z*Orx+v_u4+`> zgFmMRBaBHTh^sZIru^#kpVo%&_juOo4M}Ojv`k+$q1Arg%aCB0{Dq|v3~&@BQxc_e zi8<+;7{+mfy|1<>G(Y~P+rb#bjt#saDI2zaxdXwIk&p2g*Xe+j?+6#4%L$e1)W$!l zo%=q-$U3lLy@=$?{W$GOUSKqOx~mYLIlyJS-)JmOF+ zsp1p^srpPL&02sBB~1PBU~aL{GCB@kd_gN#+=?Lu(hy#_+!V`wcyMbnOVCJ`X>M}WE2ETlFpF>k}-H#%jea4lDtqps1AXZt^>Spk7E!8soS(v~c7wXSPJKdeqGe z&GZvCfx#Sz4+?r-0Qsz^j33v39hgoDkP`Kqc*>$;>kc5k+`G6h6G5IyZwGF*VCEmu~gGxw01{ zzOuWF{1XO=cUkS76FQL{oTbn(He~}J-hiF^MsEUs(!E(|LuWk6>73$n(ZW?Gl~Vs~r3B~H>U=EEvVPK~oxg2Aw* ztJpG8WO-j_d7a{m(;GH;-(Hr$_!tlWx4+S1#lNPksjQaWU z%WUgw5<2->(93+?^{@ZA`wQAJbuvb8f~72@0~)~v?CT)izbkFSC$FW59U>;12v282 zw1@vzGw?y==8QJK$h)wIYKBKOiL-Sb=){y#D z2;6Ela6a3sKWxc-?tFVL8Deq$A&L72-@I*A35u+I>20Qc^0<*K+FZ&Q-2B8WRpq6xi>Qq>Zmb-6q2_RgS4U{ahY z;FaxCy7b%1E){mqvbCXh%`S}-!mmqmC0PN#4ix0cnTa-V!~U8%`nUysU^h5MQ~<2{ zQv;WAzUF*xJ`H#!xK3T{q}uE;$Q!%=<^*%X|HBIZ**dN0$$|yQRI!qR@>{{i7iC^9 zMC#efk6+!21X((&p9ht{U-{Usid>!xsm|!4KhlP?+5l3!Y`R90G;gFMBl0yF4=h|n zJWYOk#}n;D+@<^G$SnEy3ou;^sS0w60;Ogg51cGrD!j=n6dYBhrhN+g6f#8fBCo28AC>u pq8R+_O@z|S%s=Dz^;bPur zJ2@&Bt}avw9Zc@Vv(AWmT$emQ$EkE6Uopl5H#WY5Zc4BM`2!qThh+ava`(=tH(Ko| zH1jc%w>YDm{S>X!aMa~j&H7ugX9z`xE9`&<61QqSyTBF#tx%548Z&#Gpj=DATiK93 zbYcUiPlH&o7v+Kd<`2NGvEO{$^251-eaRDP22S!&y*%AUhs3kcUqJ7qfYMA&?u5@r zPt}Cg>7ZGbt8r;v#vxgC+Fxt+b%q_~?QgnQl5ij3isQ(`v{WrC@ ze^{HhpbAVCe||3NPDiD>u9RX~el2Dlo2u-M%V zdeBvN50~(3wGE5K{0;Yo({z%>fwy)LW<6?s*@VN8zmicTGpNfCrm}DYeaW7C)}^IgVh6 zB!*{1XXMM`BN_rWo!=9_!(xQ&kj|pI6`Bsrhl7n>so7|=R%u0q)W0QSMP<&{HkL+w zzdKF(D=)@U3*ekp3Y+xcYHQt=z%efd&Z5T5h69?>&HJD^#D+b!!_d+e$1(+T)CG0C^*;=*5HU2a7Jl_G4j(`?w>3@d#1RZuwS_|^@UvgZB8eBq{G0R-xg&$Eyi)r)d*fxa*@ZJ_WuDOvs^1( zjE&gd5c6>tz+{SHd{W;!2&m&as3+>@-rJh6{?TS-Wr`z_7OW&kIkGhgc}W;h(Y}n< z!KqmWKW=u*-iJitjp4_rCyqYi6gOJ>bsGnMvmz&sB_2EQ1288r7kgGEQbGo}gwCn< z)OH(4Rmn!NE$KNZz<3@>0ne-}MG3LfqzSV=Oa-i{SQ(DIf#{Y+{Z^MC63 z@be<{CL&yzreB4XpBl@nMX3K!#J$n~o@Ha@v_sa=C!39*zMSnIeAFi<9cew(NNKJ6DNa&E!Beq1C@+|dyWf7xeA?QChrg9+aT3Ko2vXXDstSK zRy5CC>hDPDep0f-6f>hh2dZ+Q+ZfZjeZK^apxYibN%P* zDv1sa3QisHLqDu>4Cidm*@%afMT|Evf!rBv!r~AnQ2#|<$o!rYH0sT>7 zTuE>i6QyMwr*C`6!h#7VhKd&NrA7Nkq-T+q7v?N}y!;N6@i^SwTyAR7w9-0fP(+RY znx)RbQw@g#Rh9wwP7sP^85eVuoL5e~$BixejU!s2y&D~50z&JT!Wc1I4nds2c`vG- z0QmX|MnPj_Kkhh=>qV^<4*=Z*g|U2mLVxhSZw5aTJR zsu{f;*hvp#*)TNNKe+G1F*f_5LB*evMUfp6H%9zcfaD;pUOjjeJC}7m-WIKqzYHcr zoNhg+5+N_o9=GLc)uRv{SRE7v$`kO*M0U!8WQ7oP_CC`^@|BzkZEk<)ig^w&@y< z1U--2)WujfrNr!kF=4Rqdq}ig5v$ibm!Pqf58q11=u(pm)VR=+?)GCbbEEyu#Pm5} zibE`hfL|F4#?Rx+U%Y>M5x_OQ)?RIpcaCk_EF}wxIv>L+miyZb*ksMc%c9jz<-oW)z>%GauZw>1%imvk zg@bixazl4yJmE#wL#})`&%7p45Pe_4dntle&Br#k85yJ#A$aZrUx(p8p3Jqc9hyA0 zY{mb`%Ei3?p#~n)?pf1J6hlL*z9KY`G+~L_WX(>Y zd_gl_xy_8ir|RZ9S6K(m(h;9zXjCGlf96*aeJt)8WvoNYIrT+L0#SUSveqD9nOrZX z)z;T6l8(_MW@!!{7pBq~+6al8%pQ3dp-WAwE*p6HZo z87ZwE@F-N{D4R>QK^Mu$*z!WEx<7NFtQ*~7f4@X)#DrfFq!#yAy==#+H5@AOz3YJI zLJwH{2KPidMpBSEt3qYAc1#y)#Vl^)F<7)Vx9u1tH?rLkxU(z)J9E$XhjaB>@sNTXjgAH=>nc6Bp zEsEq43E_UVCJz`X1*q4Guw>!D)Kp$F>KgwgGI?ImkmmCibTxYrFWf3+MJh?4op?+K z`x(#%E{-iHjU}IeRpVxXpB)b>YZLC4m^lw~G=P_+g#tmHsdB=lfSw*dZ5@I8is~;} zT}-0360$nR-40a3HQ{184_hgQ5ZY5WU%1~u8qvibGZh-{Xd}utb6S(9kD?PMTNy88 zcUe#RD#6*FR*_u6(1ROQi$Pg~VU?8_jIZlXWekI!yU{KL7+S~{Z61NF@MI?Xoh4Lp zef$1VO|gLwPQAls3|;J*O=}_U5Kc`w5Av*wE44^{R)=-z&Fv=8vgnPe*jiXESLs&f zE(u}(ETB2OM##mub>fxy_YV7#3!PZI!sc2(!zck^;I@CptSa^}j(tSCfl>4X19DoW z!aCDY(1Q)4B%ibleh2P`$}ChklU22U2V}&%E&leoOpfiJ?k1<5c1^bIs#aLI5jUU| z(4vr2iWd}l^UQhtt1!VU_rfu65s928Z!(--3zfD9-B^cIX3bSxcQxO#erT8N>Ho4W zv7(|GNtI^FJIP)KgD`@ED8>Pa(Y4E)BB5XrKM~)q_^*G2HJ#p8?$~vW3-sGXHWIJV zbL!E3>F&PNPjy28Q$#h^tA5Yv|Mk#UF~(7kJF5GvWHT{++{GkqA~__)VdXgLX4i16 z!e?PMP#@APGH{IjU0v{Hhx?VfdEc=bE}-P#guO#qMCZ?lqH@wmc<995>aBS|r3}%=-nx`21iPImI6MEh1oH-QG zCPDEzZD}p((o6E#nm4RLFKJh&vm=WZQ=I*GeTwfjIBvOT3L=*j{O(?Shb&53YMy36 z6gkwgHi+qc&5U6MQRqpzmrG;wrQgeEiD6QjAshQI3{s_k%a+aJKnYdf%r!5S1|1o6 zLHAi9QCbn&BPuAts=hIW@4CKic=lX-5l?G2V+)6NnpBNmeMDge`-ht z$Lj1q3Z`VmIH;f%uh&nkH`{3Zs*l4K3F?@Q ztUV86_#d=4Q1G(cBXs=SMbHzwG}moUWuSohN_YQgF35+C-ibQ_)$lI2hD7F` z6F%clMU!&r9+P%Br5+#H2IM%3E0n=B+x8`$G*8PfSUIf7&G z=J{Tjwc2ZqsHm2fni~!3233x4i;+yEfzMMuE`YQ*s%e zGyfDmR_}Otq>vMT$%4R8I8Qu{TUjpd@I6Ay-Y82#-*HWPXNFxc9|XE_{H7e*pCd{q zydwC+L6De7wbG<6DddH=>0%~^Qu-A?i9CBBZ>I756IeNOSDMHABDYz1??^}4BkO3h z!NpN~KA3!lwHe$oZ*xo54@p>-iDmrftiUwGO{QadIAx~Kw?CgqVlBj1)Q5GcmL(Pb zcFtKExxI56C>7`j?^H(yh5vBMApdw_}abErPGn@R7R}-$FDgl*4XeIBtke&tzmkZ%p zy4B->U4L$wF|8r=`(>#}-<}67P%JVUBcFGLme22!oX%Ue|9lmmjC~Yl4Rzm5UfX--r@_6p*;$>%($Z33{A&Tp`xR^TO;HOFtO(VG6=9+RjGMCZ6;04jz zs>K*fm-b<<8b-FqbbWZ=H~5C5b=h%RqQ8PUXD73GGTOIlQ7HIZiL6blEf%+~luX_M z>^`XF!2*(8nF|P)FHXiAU}$O0r5Kh&Bvf54V`x$E5l9Sg<5DP_aX#Rczn&1 zq|rj^c(&*m_y#Abs6()pOgIH@*)7Okof8y?tkJdIEz{CDtAh9tJ*!qJ;I1~{Y5}{Z zFXyU|M2K%WD#|M%k`)-E%d}6TpQxAC#2~|i8&mh6x`g-Q1yGN--{Q0V1glEKUSQVc z6nu7(c`d_miZT2&p$YP2R`_^Ovs`MHb|Cda0XphI+zBlb(-Y7ZiNdB~Seze_Da|!k z*)YvhOYLB&^Hr+Z#nO*6O zxHqO4>$r0@$=>04P||mqbc4@N5_ky+#XnU#=qa7t_eVN2CCVmO3mT)lH26cVzg7Nn zU444k)*Afm{<=DIuZiLovgF4g$tajG?1yt4p22LWgQ16j)|sbVdHNT+wlsv>MRK{a zx*#<1SLCt(%bU$)i9|X2LX_g@*U3 zR{rkMQwTQ*@{+Omv3>|sooBtpKhjE70XYAR6{#?xep%Fec(3xa$SXwwrsv?}S4h*w z`cX<)#477R_IDYb(&xBAqj1Xn;fY-i^UN|$Pt5%9htjELg=+ z8W|YAjLOF_)1tCmy6foHag2ke39ajlhF^o%D;{1XJp@gqUe160!JP(6@o6_uF({&w zPS=u-9aCVQE@)$P?J%I5NkvdYN{2tBWW>$l!$od3g4P_8I~3lK3> z!X}*J>)uu4wM7&AvAl1d>UzK57J%2DV&!szkwe+BU7CJ8xc>s1A51AXSBi{9Mi}G# zW(V#qslye{*>(&)r+WuJFv59Et#wf(Z7(RwCuVW7>z}%`#I}aaw!59iA}{|8qSQ}h z)Ue0x)oVY~4l#zC$C6R}80F$5=~G}xK5nS!XDbs@Q5P;7n;t07X`6a@tdpT-B->t! ztox-VZeq^K$CyWrU-(RNf8Z17!4T%HIKBJ@Y-fhHHDuT`mT>3TI}NA-RK4Lig5$=DcDJvvP4c;o@SqabfIs&&J#^8*f$QHKIx>k zp^Kfeet8jm%C)f+H$7rdEOfL9g~DDH)sWG`;Ri=YKS^EnbG|nG8{-E>>$3>VsITze zZRo>q_N>`{RrmR)*2EEa7L`=CzLzGD!+B7pC@e45Ok&ii{e5G*>W6@+jNJ1ifA1v{ zy=VaS!OnJc6Y$-5)oku3LT1>z-oF&7-lLmRVr{7R*m@A%f3L?$UnsXNhpXp09DivxoC3RCSZkBTc5;11-)YCPGYkk{NMj@KdpD(gRM8yw^9=lEs zSOQ<@o(pG8h-r`%LHR$XGdg$9ea@#M_f-~2cI>1zNg|jy{Xw% z)`{7ZL2id-l$Z@d68ZnDN8RYXtyF8>C(TSm*bCam3f$}7)|Nm<6*xp1kWcK;>NW%S8B(7VH&afs@zk>|@C+6W12_>UjAo3thH5r9b9X zsp-YM$<{CU*H$RrU}@z`zDQ@&B$e0sw>L{j<)7hUX8pFcxWuxkWsP6ri?_b$;!>=< zCc;QBVJTi!{%CEbr}b0T7}mFXia4Wb0&g*kGj9B1>P0EnLM{iTS}8BhxH5FJ_b+ivLFW?%W?mNFz`@Jn*dAeh6|fXnoI)dnp`&};JZ1J)m!!O-A@!1Z`R-L z`lLX%?{-HaD-Dde*52nvD_%)~{?;$Nv#2>oa1}}QIj30_Rz3Ey%BVzXH3e~Q z)2iv*lf%!r7;!@1fee*cDTyF2Ak|1&gSrw{)BW+v%?dB`99~XWM?%k*36YhC|H9Qu zX#ckyJl}3?$s}OSD-lU7wPc#^WtndU-m`uIn24K=7UaU2B~{+VnwdQwN*iQ_PX{)=#eEzkOYTm-+VS;NT}t z#n^hIw0V~R-Nb>obZ=mpPf_s%jFYUAckTH`JAO`Dr1>jqn=HQbjPmzhOcg5oSPZ<1 zmVd|m|BtD!3TU%ky8T+*y*QNO4#gcxv7*I2xKrFE#ogWAiaQi{cXxLQPVkfV+k5}# zid^v~?=!O2nt5h)X73`$@4G(cm&_CJ(u9>@3GkR5%M6PA--t9kO*}4Zr3}4abD)2jM1}HWLtUxSe-4<%f($z6PxSeM=WG6WbF@^cQ8RsNi3>bDS-qrrJw4@Q z6OEk6uBAwRS1^g#E$u-)z{^RHW10WGq3%esEAFQ;lAmJg#5fBwje@N46h_|ihH9T~ zBlFy$1|877|}Byko?8{;Wx2|Ouw_U zhtV$CFQ^cQ>ni?_%YV~1#Y{Ek`Qx43CQ*W57!E9o6vF}Ki<}=ojx;Svb77!Fi*2=# zhy>~}MwU|XP`XehT+xFz>fxbOd00$-`dz*UwxSyh=wRIIJ6+>x@!U3t#LZ@z`&u$@ zY|6iW@%MMIv{tbpjDhk)uqKz5L_i<(!42raiIa7L8Q-A4cQH#ywk|oynv6%>Ab;6^ zSyC`&Yn}2oY=c6 zGBJB2`uivPfo!LW9&i`<&V86^8jxwgOdQe^kggu1_BBb20M(6Wz!PDMyIwm2zVK1q ztpnY=bA%e!q8}PKF!G!h(Z=7xmi%A~BmIdWe)pe)@ZAc!ymhyHSijP;5lCrr+R|iY zSA5K!cQhd)8tT(v?3FzY8f7A$4HWezGyvMJ)B0E~rACS|u8yP)iKRgTi z*N%q&V@LKfnfyHDS)-p5i%6z2lG`K_?gXx6ejAml%A>)-zb)qR*`I#* z37;GaX0P8}aT@)cE_Bb}R52UT^XcK{RFYfc7Y*OK{xVy^iD`+RrOs^F}AutCbzsDHl}{Us{Yo=%CpkT z!>X{oc+%o`$H{UuF1s&ks?_~K=VyOYJ#-%7zrhsmCuO#rKr2@^+z}ktOYVY^pFJ;N zoSDobd+>(005dlJ6LeZspE*~6xZI4e2$#lv9^T8B5@e&gBDv8t<*&t*c(m5X#>WaY zJWRn-Iz4G`1$=ysZ2bSa{%<{6s!h zUfV!+{G}3+1G$=%;ALLkcsvLQdprLuYPc@A`djyv zaex;0^iM5#@D)dKjWL|i1qt{ygGDrv`y^xht=tq~oYUvK5pL2P3!fI~{Kk}R#k+NzW zt`I**$o>v9=T1m(mKfw<=WD3ZIMn-L;0{gguoj=O1`oPE_k;9l)aCVz< z@5CKD8>)~^W_kDjM;$k#ugcZ9TmA?cub&L;!?A!jzfD%mCZ+uK?68v(en#bTjp+JY zr6`QSt$t8@v9arUST)u`>T;7ro%WPF96iYf{0+!>Z0Np!GJQWhg9x`vk!HJpG2n(Y z@h-0p_n?n^tZ*@w_PKox*yVldVHy&6PkYJZFM0q^Q;^G=%CFMl+}!ks$@ohZgC1Ku;<$wMc=v?dHMDlgEPNthbp*%p+Ce z3(1%aPPMQqtTc)~zqxb9RezDaJhKnm(kk(;d`9Cm6+aJH=KViY@!b-~2eE73wXP7+ zfJGdgoxoutc3%Z8EnvnjJ|fOEg)WukyJ{Ba}|_eJ~nzAwnnfNFJ1GOdze? z7-k-9ed@m^qWT{b!KgDO|Kwwig%leuvxam}cyo_*73KvFB7NyoX_)xJO8%YpqCgBG z1uwi`SpkELOTE{=o5!C*B5`mu&??Zsg!1+9JVI=oT_;q`!5+uk(TIc-t!ad_nb6|o z>mPUe*GdAhz6B_)@Ru>;Vg~H^aLvNt&lO_-+MhI!E?~#n!$gjg9W*=Lo4y)vJXPuu zUsr7YmE63D$Lp4yVR1a8)y4DS2rih4*;mRj8e_Qt#-C zdCp9e_Gc;Z9GCco`e<0O$w?B(5jfJU@f5>DA>+vOgT`SLVWf^`4%n&eWDye;aHR{2 z^)&Es6w(s~N8y#Jve2t2U>Uz?!R@%^SN!Xl5QD+fRCdE18~uP}T5%LR_odlKYF-a+XK8I@7P?6f%5++YmzI0AqgIQ+A z;h?qSY|rD2QR`|D!(ZHo@*m6b#d6tozo@T_9Vw-`5WvSyDmyCfw8j4Q$q=Te*^_4K z7YB_5yJ8R>-Wa?*uEoh*biHz|v3Querjo&THtR5%X7+Te@*euGe^J@r7mkcv(iIrN zAF(Pih0-0bq_Hy`X9s2CmGaP4PLntWeQnLLr0NTe?42g#?XO<5KfUFqU=%=>1V{*u zjV?F;YOm62^yjyy{l^_CLh-%dbrcK=w4{eTF6l@^Tk_94?wQv zqp?3+M>;Sp%(#b+7UwI{#0(RQ@|`GH{+uq+EptC9YFrDb1YVu0hgH%7?~sCif8)J2cr?d&^?JL$V` zlRnG@6WgK&Oxu$@qQ8wEaE~*a6J%E_?ML!^S-oiYl2mr-k7&fX*_8gzdZ*34q8=Se zUYGVw#}o7>k^AehK8$TF%;rp`e}zuX95cBkDb=EdE;zI)?$;&S05Zd`8M#fqtN4v) z#7ktHvh47=Y=^tUNZC#pbGr2!KapT-otQvG3k%{MUCH8bf2^6!VC+}D`b94$Luv>2 z6UPBkmU=oepY^h8GOw5A=#uq^{Zrmj?^c|B;ySKCC`${%%N3OSZPGS*eW$m6{+Bzc z#bz%;A`Uy|(~WvIPCoVrA~kOY3R2Ny4L0kc?(JVi&|yo#OC@x>%Pl-e8v7RctJG(& z#7*SbSZ?eK!clMTEpMz22SzpGPB@nXGMqOMTgK8Ni4SB% zB!a@FG5-?^teVZdHh6~TZMTcYqNS|^^(;oj?@JqMY|JAx8RctLOZN^^xx-S+ieuT- zX`m>{t#$i9CA*ZVuxLd>*C?z;scZ$_$$g0B*K#JAeHn}glwKi~Cmg)enqxpbpltm)u!`KJLw(fzw*Q&1w_L{jHTR#K)2UZAUMIjPkcwCwOLw(XbnTxMSm%zslZe1uPM>*^bxY zZmVbs5T>EUmnohizHp$%D6UWC`DcB~*SI@*rf#vxG8z9)QUPwXb;WMsa^$I%7>-Ty zG3=I-Itvy#*fpatiDc@`Bp%^*#hhJ`8ohRvxig# zzPwF-T1{Cz%Pv{GzBo#*o}qA*u@d!Y>3*PI4Q|wNXK`LMq`$enGh7SRWq2IWT=g0= zbUo~$|2%zB3|zqfeG4k71WJtK)#1nldxSQUYo9~GjfkEdt`_tOxixPr{M0R(92=?M`(>4Y5 zlw#he5I<$~ut+NbAJgL z#k#uU?pWrE@kr-IeK;#A^|l;B`h_3ep^qDdnY!A*rT42BFEERHKTEjxb~YAS{;fGp?F-R&fIYV1u)pX2T+JSR?M5`B zBh~?8)|6X<6$^t-bzv|uqnVOi@eig+O4&3&Vt%4A0E%yb%OJcq&IVUfLt3+}b9;LW zsV`n29=}b$Gxv{aY-Gw0H@DjzY79L%mTG^Pxu*YzKUfVJX;h|5y2D=Ojt?_m51&B; z{2G<1!CPzleFHJQ;l^?aBzzfQEl(J;l$7vJNBSmyC|iB~M0)aVwdD)o+o1-*lqugwqFVgu<$$~UkUKIFbs>Pg z$a-NTv~N{QBdL7l@=r?o`P|CHgUc8#watMnclg@7Okdm%OZn3((|RbwL>vLoh0vCe zV2co{_jjk1GQd)p(45qO7Vru?lFy4X^L}4vYAPivb@j*c?y0=Vhd-QmP~=~H!E4(M z18-V@uUJ)@$CkyK{Bdvpyn{57_qdpstch)ZG$kWL%f1SK^{o?1U6~pg)baL?|Dsc9 z)eU|J?)LURZy-9LJ~eF&FV@5mCh?=Ed&=bz&K94_MbC@!x5#PzO1hh@G@3YUjWThZ zu3@+>_clz#*fzMcUm_S%k`?M3?oVe|J~Lm??N1dOA1bXoW}>nD$VS7v(U<|L7=e&v zKS}YIu!zEmLhC*zkZ%0~y|_yzq7qy1w?1<%kpyCkilAzG8n$q4!Nj3;+ID04Q;{OZ zqa#a*K0c+!_&f993;I<>L?l5YonSDHd|9I=ZqfAn8JTV=c24sdYjVxyVWGJ;@7XJp zD|#!IHyTBPcE8bbHxfmoCzd(V%Xf10gLa)2yx~-&7E=4Y}6&XAo z#gYAiM_d1D4wL*}Fb5x0!>wprks9R+e#Q9`9~Qp_NCy#IfdZJhF_=BKLh$jqosN}} zIX*CI`fL4YL+N-J108oXinF1NUFEF_2pm?<`Fg@Uyc0kBoEQF4c^z=8(DxP^L4V7i zOO1d6zEG2+2sBmQP{T*AtN94p+Bvy=xnB!%d;3j1 z{(3HWm^!;&H%d*a9hTA})8Fl}z%KC3xnB!&3p>fu{|w$h?O!? z-4VjA?9JV?^r!i+gb_~QPa!Qc3ieu#0L^Jo!|?I(Ep?8RC^hz{n6@KGkUAONajmjY z?3MzIUv*P5-u4?iMh3e{-*$_Znp4KO-KO)cmZmZ@b#HEOG42@fSa?6|<6#RjkS>RM z2Il+nH3ZFap~AI24*AjPVV6pEZ&AQz!2a+_>o?!6s5EGI5X@|#Al5%Nxs5ivw)hNw*rBnkd(Xg+NrZzso(kvh@-%Ubd+_{0?L^0?!|SLaH3mO>gXd_UH1XaW(PNkwa^wcj1?y5=S(^pz`3Nqfe$md+`rc)wuID9C9mRCE5MO7+wn|()#4|jvAr}FwDQLdz`n}>0~fG-!(FiB$8PxStoPrzkfXWZF78HffWc@ z^VyEy@niPkTfm!Y`o}%4Bp*iRV^l|UG0HuKOc^ZVz!oclqz+XIMpn#nB>IvV zu177Y*}hKCxo(E+ncH8i&w;msNIH==+6R6Pc4>C>lQ&j&`@H=YD72ah3Ais8DP7c$ zqIDhMLhH*|5Qxz7#stL}gmm+f>ckTN;hulVA-PZC-OkLzFnzz*p_xq&M5<)Xk*iy3 z3dDP!Qb{rerlmpN9UIcdb0ODuV%lps5;NGd0hHsD@}1lGYB!w0v3CZKgytw(C1|b6 z_hULvr~wYa0orl=M`gG6J-Z;-P-K&Wqj)Ygkj~1CYbR^5;iy5)6AbO3>MaIKvauK! zHILclQVJjHQ+p8PI8H@PAf*w=%-=y_Hp9tL$fL9TK`&a=-q)3DO*<)^&wEGvrsyeX zBTz*P7l5Ff+)D4O7ohXz2wH=FAC=L86Dbl~%TH6;(yh9+$IJGYN82tT-pc_>YuT9Z z4v-4$ZxhMZ98FF3UJ&PcSRU1Rz*f+0PDxBU3+x5o7M45I(Q8~)ixk0MkQ|=Q zUFUEvY|ce(Xh!perQ8vLqX==LpHI&=o^&qa2WMr1$rHm@Hq!bCc~iAxgOZONh2jzw z>MaOwo)%)_n1xmLLsXvks~&dCMfU@O5)*g|>!T32h+C<4T9ZWMbCX!TnxU>WqMZapJ}+erhp5)x-vb8i=1W~v7gu~X!v$a0&ul>bR4e~-*ccb7yoZOOE%mQ71; zwj15=Ak$+!n1P&EJ$X-JY@@N=Atmj*nTd$=?9i85A$^^Dd0>^!_HWG{6@0LHXFHHh7 zIKY#+rr(CTUe^hjJnjWtmxAuP1SL?yiP)RYOy##OlJ-%fmpW{_1T3o_kA5IS&p0Rl zWNYU}>v+BrVtqTGE7%&1&HJ48zu2~FR%eyhyf$B0qv4lG#O{Hd34E2P#rHGx`Qtu_ zK<$N+qon71p6 z68X|!pH=B5QRr2Jhi4OVI=4jG8hV(FvOD+t>l&Ort)DXf;BDi-;s!g6D54;!ifa>s zcT12>O;BI=OE^96qW8tA&-m&CD~YDUl-^bYviPT_W``*|+LXI^v14ky@~y;jg(RDd8!<25^l9cmqQPaT zQ^J8$_HW>VM2q6=^bMsp|NKkp-@LpmZrl01Qpqlr_+Ku#7G7)Q z>e}8J;+PzLuiY&s<36j&3w%!JH^)>?%lcd>O_jN)rD;_mlJI+yQCkJh%1!2+74jsf z1xI0@Kr=yLk4SdGFjqz-V)tYmSWxPxbnw>}+HiE&R@bX}Zm*LgJZqMb@L|Qg_deOk ze<}mMfhu69gxd^7&ZC*HLX5bEHeB@J%;dOB1?@L6huMrLw;j{m|!@uS#*)eD~ z&f}gM9g(q%D^9Fxb2vWOCC+w2(&B(~{GFl#ZD=!Ik1){wLOP6-e#Hh>kpb1{0K3MgBfu87FM%vArbNRFiGN+t3pNmWfE3B| z#_w{Y%!d*1m}7171>ypVe?1_+8{PLH{o!buQf#EE8jTxNAqTO4l2dNlm#tOZx5(Db z3*G09xHIrlw~uwuqi}}ub3ZvTc%F65OxiO^5U467x71(3<)yeJPQ zeLA7zf4!drC%q;2R7@;uY`KMh2vo-8*a>VEf_I3U^ z|DTM2M^jvBoVZpe9h$1)9Ee9WQVTo6;&eP~#kuux0Hok+l#Wb&`S7wR1!|j0@ksMJ z8v5Dlm=rP}>R+qiFLND9ehRNN=@z&r<>BkdZHmrGg$SjSFcC<6W7)X+v0EVQaQirx zQj_ej^dZV6Z8MMB>9PJU{ka!d$J~%N-uea(*D^SlHR}!>aC19rd$vG2AY}^j zkmFg`R_d?Y&l3)H4a=Ly_1)B|- zPRTmf9-cC^G(Ul&81YSPx=$+F`Wbna=jIY({Ec=CkO*VN$@xagED{Jwdr>iOiSURs zzdajEApWMeRXV|o!7EyLWa!w(h>hH%ppeyqj31Ap`o-QGQkY-pLD^3(m&NNZ9hUj{ z>)ix)S$7z<4GiS*@PF&4!PVZ3>yFb)DEEX`{*}*pSROwLE|=H9f(9*eQ?4(WUffT2 z6;=&fdU}E~8yZR1scTPmH=~{!V%OHsKD_$iq@oquk(vhHCHy$-CzrGEtzO%aD2W>l zOnJUf@;JLGpLVlTUJBt!&3JmFm6eQ44CFX1hWqS*xgY5tJiBxLacTO}EGR4ciX!zz z4r@9l?xS=m3oVnRFcB511Qaq)li5=0{`-ZKsZ%uYbFK4$-t+89#}jlz=2Qq&R)@~j z>+BiT=H8nP>GOpsT6TxcX5@D3!H*ya$y1{yNLw+r?J3kHL-=ns6v|hJgbg8MBTJbY z?#$b-()*UaHpR~*an1E7nFvtkj*^{X%YVGWpdLS>=E#VgFHRvL9YaFZ4OA5)y<4+K zB*B-)BpX9H5k&j;tM3Hg@sO)1SXd{=p<6T(t)DV7joZH#4seYKd>zcgqf2j{1G}ms zK>N=BttjcKw^=Z%dfqTFxY^q7W_SJOeYhXz2t?S zIi)z{9e&CVzpO!xDuSb}EJmbFWlTAekhTvJ#*vFplHE4Tkg;Kr!~**FGQ3VnC5hwY z8#ifx<9Dc{k&2~4E9!j7@DxG`C#d_1I?wu+$&t)oj@3fKVmq*D)1e*O;{C#|`}o?H zvpj4KUSiG)(Ak&mgXGe;pZDbHEX~A0Z%>g;M-uX!#1j8!-onwJ#7@24X-rTx?Jrgx zHX@KZz;5AO$uBrc6#B3ywgwK~LDx4tm$#ogi5Xa>gGX~7rYI4nqMZ1qng*YZBzHQ+G}cMEefD>gLm@}LQ4;zhMc6`j)$}C*qS$z z41rV=jQqbGNv90l@TfF0$biq=-SpUSK<%ejq|jHBfLp0;NA3a1+eJk^6{n7V+yy$) zqo|Zo?x;?YO=%e;207T$GL8(}&l%xloTd*7V;$uO?bCvaY64g^N6`y@+07V(JNU6e~vNQDzd1HL=3)npA7%p{`riCyW@97 zL}KET&331Pzz@n?aDBLF1!QeDMR+`T-<%@#k_ihkK!=nIq4u(fgmz`>EhWbGRjSm7 z=Plkma-J*ac<}ohZ3G)08*SVtv3}Jr=VLrL)@O*O+AL@rt}7dtD>kD8AvSB}>($em zMV;Xp)cTJQg=ld@&2Zhpl#|$IEbzxDpoXC3j2q=os{E5NMG%JyusL3i7o6;E-}8nF zz)kR9N9?2$)m2e!mC6echkXzTLBOKQ1}sc&0Lbavdj};Oe-&uMp)`1-^0%ol&{{Ot z_);)9A5;xDCzutRO7HlfC|LVqzyvaOSSeT~c^8^ys42M~FjvT`@eA!d3nmn5rJ^MfnBy@xv?6utNu-!ZB zIZaFgc~a#S$$?$G0az}_1Ls-~6N_`D^AJt-jW_f<)10|iVJAT;vhy?&9}hu6f+`!n z))3U&8wQ~bQ6?={?+|F2D&~FsXC=E8ru8=W89uxH2+W%Qp=?T5fhs8{R+E$Rm40C& zBtPB2onn9g*7LW=)8#~wYPJgh?If>E>bws=U;Sb0Z0*w~AhYX!11Pj^f7!Ko+c^*4 zSRr-hx(1x+6MAhW>Ht5JA1=*qD0(WvJP`>%p>3?XXW~|B2ldgXu5|p5TD2?io_wQ~ zKuOtS;|nW*0)Tz7E-$0JpnZw9WV)rWsB$CXb@1!i(>*XPxJD|usqe8{#??}WpnC6n zM1IZV)?*UtHw|{}n;JmM{0_l}KtXl)_&r5Go)PCN#<3OdeYhZ4le_EyGT5=J+@60l z43t?&Sgn_ppD&)0wvyD|ux)@RT69AqesO^3Po6KOxLfCZHUms8qg{!SjJ_ZFxBRiH zp3YlpI~*ni8jrv>o;!qjU}oUM2pM`fZZah|iU_y1StXFR{aT~#FCJoPL?iCG*mK<- zC6HSyN2_egLE3O_f=VKp^DF0l9yo^7iX5;9Mn8G(%ujSFbfNo^b9h9P>E7ozRbYh~ z`$hy(glizw0d!|{Y3C`&%$r!)vkJHsX!;I5X^x#~OR6AC??uLZdOQB&^2vvhl3?0@ zc`s}_Uvoimii>c*@~98kwnJ40Sa`Ni`w+F>@cxO2Bd?$UFkgAxr9%1ySMicHU^`C< zr{CxY%SkxFAn_PEf*OEd=DJl1>SPP!Y`_L)~4KQXSDSM|DL%gFKT zFnHpWWh>>E>*aUCL^yI$)eLP}lij1(o07pCYPeE#TzY_7-R0-1;qrN`@N0@37plIbkuOG zfsbCg;TBfdWxrZ_(3bCmxaWwj7t70|O-nh_J*4TG8uhqK!61z(VuE-%43fHi=GqX2 zT+5))D!;p)k$4xzYVdMs33B`-ySW1`y9zD_dKV+{wu({B2C3mcDIUKcPhf;;y4iINN+v}a z)LBYOFBhoZ&|J7?>UNNvEZlD)RkjjGt^p~(x8Z%xb1CbkG+oPJM0lLH zAqpqHCtB5o(ZO1|1d%SnO?X^afUnG5Hsv?8e!jNv4?DK-#8p+xMgCBq2{NfZ4M72) zH{Wj+mVVe8s<)g{TY0{v{x6FlHoLL5G3k~~kFIRh9~5Cz`eEUR54VHkaKAx%kd-x2 zW~F4kR5s$epBg~qkvD+LI2fr ze`T+E@9XGfn9<)IfA&C4O|kd2Z+(!p=`97k44*DC45fmWipl#e^5NM-X>cT>F-R98 z+V@zIxIWvy;|S4-+#=FDM(9em-4pd2amp682+?y+CD1oQA#~g&FdIsXLAph{ngW)RK#)2gK~KPu4nY#hnjf>kWXQn$9%=Z=$;Kzpqa`jA zcPnmXTQw3rTr=qw{r-*N`3-@thZ63Spw4fpEnoNFxTH&dag|v=BfRLyzjRy-fi{(J zuVnX7q+E?NHlMD3o!Cwt&(FIFS#)QDyGLgvAllsID&ZQIpLVAy4xypVqh8>Ej4xPs9_{267&B^lj#A-&T5 zH{lG{6I$voKwBAM)M^bT@a=4x7hxg7w8QFy?n9Q7f$j2zCzVy>2t~TnWtZ#jJP!C8 z4R`Yq#P1_2zmCyD%8OE)RqvEBxjO+fSHa@c!c^u(wKY2HcXw~W@BW0idi`X)q?qqP z+AP~ZaU8xBr|rb3)%I>hJ}taNiRyptU>BRO(h?GUykV436Z|t+0AGvb7kr!MJyizH z8wx6C+0Mt;*H!s?L1mU5E7!Yy`sq(SfU{q$443~rbSlGucpY@0T(Hy9&_`| z5uF@s!`Ypb-YGd5s?FPNdvVJ?eds4sU3g^uIJ*4Kb&US`p|M@)v=$DotW=l<)@~hc zX5=>WrQ+TQnyk+^0LgAgmTTvVzGB#v7?uq-iti4O$YqqZ>2g13gmZQvF1XC7QdTQ14MMZy@1r)-0NGsFS_7%AfvIn z9kQOZ|JGqsr0)CVkvH1?_W3Tmc@e;AMa!3QFrE?D9Sqhz(g!rp9d2#lNgW+1syGw5 zS?)@vomnCI&d_r()XRW;-OeQbOVh=qVC&i4_^H-?8U%Q?oHmgEEx}!wG*78n-=2ok zw3a_*sn4pl`{$9tPLu=*S)j9_!TBZ6Up-yN$UBmz@~}Gd#BR&bFN$BNw1DSY(C zaumiwO_1-rlky+KEkJG~acnm#ft`0_twmWfIgV#=isfle)~Om#T4t!&%r&WD;nz;0;vb^dFu~$!_~gL-U%8Io;qK!TPaaPj{zjq_#Rs<%|K>7fpcE! z*EdxB50ktMURDAmt!^-?49mf$#Epz{u00itJ5~EhWW>la38xM>;ioo3c_(%Y_0qNv zL8QXv@(Ur|M~lYmvsM~2)+ZDj`6U^LsjHDFmhRQ&UVsdm(=Ik7PJ=Bd%RjpK?Q>=C zxRb%0UiG6NfJhAh_-Wmq+7g2%y{ z84l8u5p0g0XXogn^cczWTxDh0pa`NUc4|t1$woK&_5PT3=*M&=`33Y)6HvFgk()jF z)B4l-fY<#-R(c6ucXYgi$u)nOMlElgzMWOf_&bMdFHE&c4W$ks>5tzJiG|ZF!SG*1 z*L57H#K2cc9qGhsUFhy0w+Rp1etgqxe7xMt9nv>$@5?ZN@-Sy_9)cRx!r+qE{o3~c zkoSGd5-CiY{e~VblxSq2J_+#o@?Bi+`!2Jd0>Hym6%*s*G|x*io5oI=)^Etxcn5iE zZ%z?UlK3iwkva_t&h{=-I z)dN>fQISxoJ(kT?om}hYS%lg_gBq)nsr6UEoBM@Tnc6K~RrZ5M#7fph zLqYXCXj?ypGfD}pPd6slVvdKKirFb%9Nh)NL5R+T;R+R$$c8}e>ZbWd6u5og-U-n| zh_teB_)}NqhCW<=bz#kwKAfUqJ(Lvq(%o+-$7sU-cFYC!2_8gTT=BjOucy`RI(7lY zZ5}@GCmV%~@`!e3u)kr%;QD2~X;t0*aeiqCok# zlMbP`K86T!J?Na2srx^+H-=Z{i|`i6)k=m0e0?}j3*_)mi&+bHWM{bw*A~5ac|H&t zxH%$cOB4Bs$IQ~3z2srrRMqn}0;0c!DcKx;0Ss|%hmr(Dy|!WDlB`EibICv&0#aSD zM+Z)W&<^`%V~N#{?xLwJC*(HK1w;}r3&?D@-|J!LfUvX*P5-A#KxBv#pDkG0rT4By zwrl+9M}W17>{pMZ?jl`Vw$}{~2~#(XW>>6CjXK&iFPE8wu#1&2(w7owwmMtXN}GSlx8F209e=ulnnC?hz$+|BSm8_)YsvwV!=8{fyx$*KR?)+5GmI+T zYD|Ww%sV<=)bJKXk+^_igI~+^e?`pYw-h2uyv16?aY0A!tnbY#6a>C&7(E0Bq==Z^ z-K`YVV&&l@J-Wj-0&<0`U^+(`tYA&>r&TXf# zZ7@-UaS1Th9UT@ZX%&yVQ2Rr&Srrl@*;XbPP#$ZN+8Eq6&_k%xZQpAKkm1O-ww(V) zf)wNqS<-hm<53~X=y4Y%n#<nY+fvd>sDTV#ZxR!|>1!tXRqEWp%jiLK zSj12T{X5N1>F)s~b;5#JlbLt}xr`s5uGp*>a8)=}?IJ#My9<(Cp(ACinS+;Ku^bciSNK7`p{0Y1+R3IF?gQ1#VDA zD-`P6y~fh#!X6m5mdmWh%3c&9?PQF^pjKPB=K<5Zfm1gn48~nep|z%VG_A42VH1JW z4&u92#A~_a5ciqBN0l~wDffeVX}hr`l0NT&A7KY=#*J1&{pemV91PkGupVc1DF2le zcZj`(4RDWExhRtK^jK<{COl`-E((|d{&#oIb&d6T9rICUB@0OAd0`>;Kj9jE+Wk$`FF0d1W*s@-gA$?@s=rJbqTR3X3G!RP){)THq$7Trf z*m%;#9z2CJOMGOPwp?z5Qra4|&!hb`fgTySD~%B)-az)Damggj>S3JlM-v(TIC`{W zpaGk7c;VzQW*+SzW}YwOyFhe4gNdt{=zf_Wyd{J-O(jTC71nEVg3fx(&cEVZK}~IP z>vO4LY&L`uHpFWYa3^2axsH3cH&khEZl3DK5SatfJ-6guTu*dOq^3EwFNEPZZ|Vy- zCbCu{gcz&`7D#sU2kMTrj6E^^&_>FZ$b?=ZkbT8e<>PiOLn5nA4!MHNOG@H2Y9l|Q&XRzsL7q@1oRfh-Vnce6uy zQCj)Twjy!i&vokSC%>FyKc6*q5?1*es_1ROdr8;Nm_@AWE=BkGAqdGlj>4Aj+9GV7B@?AH*Eu5!o$ETJL$7!5618WR`3*N zLF!8t-}2MMzFE*&!`S$|%e`UiR+Chm-I!d9{@~^Q!ou75#R13_^fU1T2lru!#2*l- zt^b;bmN1UTo}Z$zf4#7vyMW1q+l_AJ@^*GN%|42mYXT6imDgLc&o-9!@jiqCg(c#l z=sQ)W;+rQ?qUf)a0pC`vmv3VU?V0?S02k7LM@&dYMj-ginvP)x`Mb?TF^Qa#{OKiJ zn>?#~+o}`vQts^rIIp78m1Kj-uRLq zb1{~y%~A^*up-VDmuP-Qag|T4ZY$sY8Fvn{9aq^^ufBWuH+&ENX$?-*y()oH3pkAL}K3J5+$(;v7 zU$P5wcYtkW#13+SV~InwJ@tg@+_vX8?J}D^ylKC=#AtPRtQE$4w~>i z4KzaE5t|-&x?q;*b_I>JL@M&E_OK(GXpwvjK+Mr3Qhq6fZdhvulgPVleUdoFFKzzT z_z6kS{pnllv!93I#*PVlK@@Fj#$ASQ5s9Xj6nZMB{$M|s*PbxH{WV+D75Q7I_r%f% z=9f;v!$ZiXbXkX%lV!@Lb0En?B9zw{^}VGr4d&L|QL-j_2R?--y=FJ?P9ulY<;+Oma ziTJlVPF|dluC50#ZTZES(^4`2zV(M>s9vG3)(qe-m@Ap7- zTzHA}MlZTzRGSaxPUSJ_JyNJwdtz}pr#g%8x`$i(gCD*M&L&NKyn(y?M>`-!+uW4B zdvv_TZs(ErJ2y-ohBy})9Lx?1F2J33{7E=fkIg89@^rM_-$ZpQf@>q(1Jp*!H3v)! zSfXa}CxZca^rz*%ppKPw2m#)nf=BsY>Xg!!>7%=&y{RG}>T86Qsj=jNvSm?2MN(`O zU&b;+sC4J)O-*16C0mo)Zwg9o*seySoySh8&F(Oe^-Z7xce8!SHY++_AFZ4}CRj(J zOqof3A!tAhlqo|L1tpx>(fDWUN&AsiRXna&vJxe$QiI0RR+5O{APq)0bDD449prapB7|YlkD* zsH+0AD;)Cmf|P}@j+XYmQr^SiRHVra-^OqR*Qz&x z^aZYPBK2pZwmnOsHCGO$8{c`e3a3LQesVg$SacsHQiKGG{pw=f6NH2}4 z{zT7<)!<$!m)^Lu($o~^mo}{b)SP_BU>mv8UMgHxXm&l&oSk8JytO~pN*ySf0MO*lk7sz(rUJv#q=ljHVNjPhxTRlmu|n5 zP4_-7Sz_p*BVw`9y>qz=1H@;f;R8D!Z|j?$LrFO(kZ>8a$41QC6t6b1VG^Hu*kPZ=^(bI;lpAS^6hmbxTPkqAmW*ZbC_gvz%M+rbt z%R@AW!>HRaW{hmPpF$4X-Fs4(d1Y zw9#Tdu!3%1`PR+93ViwOc^lqgGSEW$nYi4b=U7+Ub6;oCe52fMokK#TUUFE#a|Q9# z4(wQ0k5^f)v$Z&5V)(Yx*tf9mh#Tp-$B+(AT&wjkVeammC>#5~$~*Xdf$Q?h%kgKe z?$a(h`$+=|$!=TbFMMV3j}F*9tTZ#stluosf7ji`N9#Bq(v#XEeg->V$N6X8>@V=x zNWjaET+@|<1Wy?2k3Dk?Vy<<`dSL5Ki>b~wlJB9fXhkgV991S_824WA_y*GP z-iwNb&T*p-;yz!@s|DQS#HTWvO7gO$fpWu`hT(s!#%MOoo~kUGnPe^{v3zZUhDiKo z#DM4C3kK%LM}eoRYoiDd|Mc@|<`~R1ipLfd@uKMoL1wL2TpC3KeC_F>B zLmGw->5y)uhmHYen0e>-f8NgwpUye=z4zMJT6?DO_UscoDr(PGFbgB&#%J+#g7((0 zhzfk@_`^gFO*3=7D4HVImxJCdd_h{HJVU1=$PQuH2kb0og;UpsjB9$Np^^)$Y3 zov3Ae4g`~??cMXPabk0F_Qpx?+Q0Bw1UbaaOEsB|BmY5OxTd#9`FGonc%P8CM!zV+ zU*!Dh@B9j34avCPILq1p|FZW8+G)SV3v`HeHrgZo1phMfC6}>9Z7fv!FE*kqhO(yv zLdDO);6WF>mW3C^OU>Nr7!{RTIc_StQtmJ9!ZP`(o3hLw-z`}C{k+T-ldWbCyB_dp zm6*sDCLN)1|DbDu^k%S;+_tprYcem^z9Qki10HmtavYuZ9qMs}%J7(4VDucqPH&p_ zMbI0U%v(5)Xo2}>^deteEQhLxvz_dmd_N)i$K3WKznjVG?gSugPuraTRU#L)VnP_Xc0RArb9JC(AgqLA$; zx)53N(ZJWveOnIgFY2$cAdI)}ugCh?n1a7*?qZ1eR3Y;!rjZh+`cwxp1lCU)dba()ywx*^L(&zo+$Z_7O<=X$A z>f?YxceQx7Y&%3dbR4m`=CyOSCHmT|E~UoEc`4jdD>HX`!FVgKQum8d?dSg(0!v!! zw^unU0Zdm}f>o{;fe?A5qu{YVzx2%U%*K9vWGZ`KafI*JXy0+l4U|5@pQSLf!8~dS z>FKG_9P}!RBxH4Z)JiBsp6#MPe)vu(b}&PtxE9LG8U+{^mC_#G%m11H+lg7>8l(wN z-jEEVqfl$M4!Y=e72}yfPtuzV70|55WcX7meta`9IUTW2=jzW#(rmNem$(d>{PErl zOU6!SS;=ts{L{&S?WSDrrkJJ@1Wit{3w&D@)JJeIQjA*@ewnCDaT>AmhfaFEUT?OY zcay8l%M+!+$q;tV6Zb;m9;BBiRdr4xy3-R(0zhlM!eDw5jrE1kQX*JhXv2*;oFu>ons+|1P?hadfG z2LFH%IBQe^WD%r~v7)VEDk{tERw7uD^y^wL?iIqN-eMM0^NxDG<9M?*@58J)_?_UK zCCdDOvG87?8=(B-M2<+Y?!fBf+cXwimlY|Aa*RswPAEM(j_YqfTkjU{O(YCx$a>#0 z@Nqjs*K63*%%3U}io~Ks}=` zNK4}CwbzEHek*sIp`rpE8Fp-wBbD!|Ii)i2?&R-E#Woa8<|Gi=Y`9YgkqB>kxHsoV z%Fr#(73{QrT{;3!fOv6|?oRX*OrUiP=-%0w#z30O4a#6GW9lthr(`6$HQmQqo|pId zGa~Vz1R$L@J@f-ukdI^57x%1FpLWD**#J*Txbq)7+ zF4dC05ZfBUaOVmyUFSxL-J{kL*M&(tzG-Ii`Yrb z;!qAwc6^_VZd7j2^)K*rFTM{tRpK_| zW9MmYXQN?Q{=*>a3`Y-itoo@G`cdB&@``n2cEB3Y`xzlU#wK0O;vnK4KfH3Ht|4-c(Vo;AmH}h%4@qW z#@o)_l6*^j=C~XfIm;WOPDd8QQ&O;!hGfg2rf$UeSn`Bff0h5BAuXV6WXdo{KsRc6 zzSl3x;Or23Oq_NA)xA=&AJXOL6A59RkdCP5^Vz}D95hhm|5xz%{4joPZ^g9JJk~po zJCta74u}55T&`)bp8VqB;ZXo8?>yHhv0umLts1m`X?MZQ@-o;z39e^XVCTWOLln;> zY6HL#LzqSOaD2uN`d&B3rE865q43I1j4a~;stA8#8oTyeus4khUX&#m(frq@lj z9xNL(71E>E;Jyr|!`YM10GT~Mb%56&D2Fe2;E`AIavA*_VLvS?KBlU^~M>AZzJEIe=dj(yuAo$+HNuq^}`84x6ev`M}1JDx@TUS zt~73j>3}_aPVimFx6WvaEwK*tC2rdRgv3Jhc@TnE&0@;m?u~Rn*Q+IN#}Q`bd-LHz zsQ~arZUxS*d>7ZPW2@&5H}`7Tmc&Xh`Vw~Shx_%8i1ag^0dir_!m}|k*@tk5`NQ@G znZ|>N+I{%WG0W-WM5mBQmKNLY+ZFQA)K474AK9fCc(d98bpTwfh__kCfIC-9@m$?+ z)FN3utm=})YN{GRASHKWZb~f$K33Zdb8CDlpS?E{lE7EUAu;r;N=ntZ7_3x$7jG-a zjCBK-ifqX+-L;?6yYIg;lLj{KCUM%9shvoy<=9W#BS`yS>G~l{$mRNZXHCz1aZ{Ja zzHWFtvn3=jJ}0={^ZG8`49=58JT)5>1G<{**n&iG1>(nRX~onRdo|;^`wZb8Y`XMx-ANmGHg1y`g=- z{6tz@3Q}qB3_rh(nB;7JZE!%D5?r3;>R>Y*U6gVfVTnqijByfjTlI+r>v)d@Q4w|P z(XYBtMCIXdCxup^>Y(ZRTEA>@6 z!Xzy69QDcC)>Q6cJa#5`H7>;FbY_|DYd|t6||DUPdU|OL=?>;4V@LtHK8K z=eYD^&U&WQ;)fY=#m+wuT$l)L@B2>7Nev$hsfRlqH(^Q}T_j?RtEQH`;G&&g0g!C zs&^R`axE+h<}?`b0!@%l_0C9jsK8Ruvy8l(OUKWzC?cCOsTZ*_ou6};Fyk<5qsX`_W zU{hIxX3b3e9j^_|(GCqmX25JV0SWKm-H`vN88*h(@;Q3xq7)eY;e%Rsc9YyqfKk!I zoX~s(?9c~xHA0T5LU3;8Gb4+xXc1eyH4&?Tbk=&bos$Bmf+kt}SLvKIcnF~wZVWhFjY)Da`hPes@%_vh~7T zv(Z1YQ1ZwDNiH`eqPy-h!~kwv(nC5lf|I=Ccx`NUzjUt!)t5D3V(j~xB3RY1_&YT zK>~RE?k)w$Nkla8y!}v)4#Ps}6xZtlm%l?;@I)UOtLN3*k7pKO zK*#BUt@)H~!}fi<-jY8=SgeN5^rkkTGF#vL=8B9r)>)6^2-r%lzt?Qmrza>Jvwr)l zz4(0?~&pvj+yW z5Y)hK5$JLGWsV|ZFphG`vclq9&%eq?LyG_M3PsO*ZoI0ETaaW9qQEdCa9X#ZB;Rn7 zF`UnhCtX|&MpT^7EY`FcD8Ed;%LRKMYN)vYrk}&8W7%B&teTx{Y5j<1nscRR2Y4q2^G7l6JNY6GJLinW}QACxv~Cx+1J^TF<_hK zMeS6-Z!qd0_tS-ds#`ItnWv*nGnrCy3{xpPk>&chqOZ`9Awuo2B&U3?RuWizRTUjnzp-Hd6~9owXbx3>}f}RYaLC~1lA;<0E;w%r&lig@T6c#30C8X zR8D3)6*+z)ZDiYrv{`*kC*1%do_W(|kWK#8Rh6Ww%~;=KwANOr_lq76u?UbIsi=ypv$wZU_GJMp&6qWm^XLrei!5j*C9U&gVML3Y zpcKa4dutwSj?EyrwuCJw&;-xwdx3e)ZJhh>dwVp`P)~#v8CYhtFf5bJHnICBjz#PL zKxHI1RWfIxwSkrXR!|*Lq9WH>{gHekSQkfE}FI7&7xcP|Qb2A+;o5D$X z(lvPMl=EHG=y5k(wjaOB`cUOLZqh|M?w9@>bV}&)6hx{wWz>HAG&nPEQV%RK8**&zAL9ri}ML zqgsF1Q(U>OMS4DY|MZMalDu3p14^3SH>S7mOUF5zPfa@XHFBbTc^)QpXn$u^G}0@w zXT9>_2GY)L{C&Kxi+#=h@;@$eT6E{}?&dZ~X848ldB*Hs7#1?E8fSk|4++>#0gIZ} zl*B2{L`4GK0yYxE4oyupaI?DK=(nPX=ui^p&IV zs7Yd9i>g=v-qJL0&&F~DoF#{^%oQNIzZxo0mBA+@iH5@jU2(r$Vo1Rzbm-AgLkKg1 z!`YCk;%B5OeFu<-XPvQ&+1}&tE5{1R8szUEX%S9U zpRpv#hi!Ow+cqur5p}O=h%k}{Od$2x=8wGH#u3)Ac?e#s6_5&$q3waYu$~(eaJP0G zMGui_zH63EVV2s3rZMn$_-aKt3e<{BmXCoRwS)AH03FM%Gs$5KzAg=^6tP+(Oi#~m zj|HnT88ovr2Y-uxR+Ku!(8d>)3JBuPp~qyEClGdRyIiA{iU$VH*OFn9U7s~0N4POt zFz*km9Yp>#LiLcfhPoB z-^E&B{3;r#Yg?PctmTFHg+NPM8X<-ddOs&B2Y7Seis#kqv%G`1`euQGYdyUI18YBJ z{-8lKGox?w9;NU=vvGMlKNwhIA8T8WGnzHp{#NM&UZH8wfS=#mPkjj7mv7;5GuM%sD6942iYrjgD zO0+#SCHsjA7h4T~LI>I%VH~DLFv@ZWgRX$oUDzw?{(b34TfF|GFScJ6)5~AngxaLw zPWmWt>t5M=L~8>94qr>g`tTz-1d!e~a~szoBnI8p0VLo$w#De`M0okP5JuC(CLpcB zm;f}=|M`4`1iP^-d|5uO>o>90JBr&Ru%_zi7Xv~&JgHgj z1Gb%W$9SsrYX%iyYPD(DA!MY0(#fTOkCynrDSe5-1Q0$}r+$S2&{wE zA%jpm6VJ_TV6XZ8d9jI}gd$J`;NQs;ZD5Hrb-YrFKj;K@CQwxOxJv@)v*J|;9gniZ%*S}u`?f)Jq2lJK=Njv_ql<-scD>@H&PGqoT&Fv+ryi{ z`y+Kw*(MwS!B-!)rbLWtO|8fj_G0?qKSMCu)y4+s!g)ssg#4BgUW+1A<0J46D4)UJ z`MMqZ->(1@W=%ZPVVC}^6FLH~SO+)>u`wU->f+%)-Sld{r~$$=d#GBewO=9N|;Q_tLw|H%p&V}>g2qEE^tQB6usywzH_ zRg4GKoPXGTeq}`H@t^V|)qyVU!@-KK%R^hK?7WmvmA@7$4_LS?1$u06)#iUce0F-N z`F!(wy}pE+wu%V2Rh1IU&r$-CnEWu%x~SUU-MaWN*cW)@VE>Hu8$`1&@9njNyf(hJ zvh80%s?tXc{ULkBz>Cq{)SVu}5UT~`21JGgR$l^L6O`-?4 zlSTxG+QgAebt1D}{4QP_WG55!7|gv)1A$*1i>WTBBstdfb9nk)WqR*^ zC$ipsSQ2iffqnd1$Jo**?_Go32|-5o3ynaB?0Vt>`*s?vEt1Uhx@5L^<@rw>-nS_E zy#xWG5sq@Z{6mjJ<{%zTlvOY-7pZ z^`~S6S3J(HKSKs=l;8g=kmVoTQb`F#b3c4)baK9V1;iqqx2RWl`*kVOm3S$f%Rn!X zK|2;$s;1^-ARD-JLY^7!Jw1ZVmFS~0Mu~>Mpvco;{^mWrNLYMe0@N7lG(_ezn~#6b zWVUHxCFA&&he-<`eY7F5raKVd{JS33o)kiUdF)5eIkv=}1jt!HVvSYwvH%E;ZQ3Y* zaWqbYjfb6p{q(x<4jNKmor(@l&OnyO!~JOE#_#}Haj`YQ%^oeYYeuj9O2ID2EvcM9 zf6~SK-|!(o8gch-8x8Da%qsvodrj^;o&Prh3z?J?j<6Z&JV#=H@1?r6Ej$zYL+;+_ zPhE_T_DAZ!Hx6>}W>anZMFU5kt6vk?1Ov>Sw!Wuz1pLy>2|CErG!FRWea+}yg=@n4 z+27@A;LOKHLok!*93f&n3FipPKWZ zPIFarBmIfG5gB|XdLo1^*k$f#&B-gk$s&i_^E!155MPFs8yAQHZq#P3JBT?gKd=X*v9FsRPNOR$HR~b?!za6Mv{Yex~Y~ThnI|%OhViYZBx@ ztve}zsCMP2wh~2k>!t{7(NIv`D=VqaD^rtV7?x=zIyom%!_jX0k3w$tTreZq(Hc8z zB_N{OUa=(_`mmP(XuRlwo`aXcta2B^KeTT0esGV+ycuTD6Q-+}VBKg3p7rCMl&qP& zUp*PjVeLXj`jjf)ZTF*#U5#cfO4}WNSbW$4c>vTsgU)uja1JdthrR=*lK|2Uqe z_k6kaNLX~S0m8$p`TU=29A}?{swEJkQoiVhS-7~)TkDRwq7T;L{s;C!jFu@s{xA*u z^WY9cNr8p|`YH}?YVS?f?Bs5ZZVCTLiPH5z(z0RS&sbLNoo@z@wcsNR-BO;{lpHJ# zK5#K`z|n!xu3M)G>t1yX-Y~-C5`#iBLdWw-@2QaFFg1ebQ%zG-#t z@L81Dh&w&`_HX`1c-u2S--wl5*z9nknVQx7DkO>i1~sH1exc&rdTRL)AfYnz}rkok4qs{01Vp>4pAw|}f(WV!J}|8H}MY0NOU>aqE5tIlvL znPanOSSER^U!0lHyvT6XQgtoURF84WgbxP$9;(zLxI02uWL{bVeyFkx1 zfF0xzEz{WUwhOOpY#>0i2)Y4JXU)H55Oz=b$^&J6^Lmf^kyYY~?Jcd1jcWEvlkx>T z^c6GuA@}rN<`#Z-3{uy8y|YkhWI37jJ4`@#hz24>MO(j00p6a)?l4f&txP24ST}0? zt#owMzoj+gh^@JegZ?F1y%SgFEI8H6tAf1nS7EVXq?1))J-WG1qi6}f8*)II&$G3O zPJIua0Pwz#S?0vhsfX5N2mi1xpudaxV{Xl!)s21DtNLUD+=~+i__vCK%HU*zZrQ;r zd-W^znuv~z^cJ(iHk_Rx9gy_ez{ zay5GP)Zimed0tJFK}xk5O8RW^Qb>Te_`2FVKSaw^#AALWoy#QadCG4~kD##pQ+7F8 z6JF3y?x9LzayAvuDP?rO^zR*Cf8=)(HZJ7V2hvR+=Dr@wO2B zYhPfpKwAAPMYlti7hOqkbhg@>gJa@q=en`_q^3a0w>CA^Oot{0wI8?d$7AU62VTTl zHR8lNbku}#Zt)S$$A$=gqhvmwsBUQ_ckev=eeoT_VBF3D*ApzZpWO5v2(Jsg8#iQ? zeZw+sEy|GXEsy_Si&N?g98))orgT0~$XoHgOpj((nH=4Ddu7ZD_E~hSvN6R8k~A?SoN`*u+#Q{r z-Ol6_N?4d1N*Ov2MC$oLENoRG45ZKPyTJq@=YWd0k~Xn=H?walz$YF8&@ps4+P?y< zU!ev}PO@}Q^3ujXK934(3D=FeeQeLYI+SlcDG16+eWz!?XiqMXp%yfd~JDB5CaaRVy4fO zN^EEK5lrAuE%?(*)P~{tj`jV~4?HOuaT=Z^TrpR)Gq-I8JDUl(_rlDlf--F!0oUQE zWXswh+xHv5oA=E}fcG}%1iHsL`CzS-R@%RGVxm!ov1PS(f#zRr-&VPB;ch(X%@Zw3u8)11Y0VSp#M@J5#L$USsEYO+tRWU~} zQRRAi>boi`OOnxBZAXbP;w+m8KZr-4FREUvZM#J0g=67P5jPsN`F(q~T+mf(zwy&W ztDwlZmfkcjg7z*+aXq(G%W1kd3L&ij*2Bai|<@EOj4D-3+Hm zr|5)myp~0A!{{%Uv)E3#R^$CNBpX2;_MyyE(VVfmtl|LgY$P--IV7L(ulqZLiht`J zW(xgXLR4ZN@JiSPz8dvWU}OO-@cBfnBPtXH1ylT$|CgF?6)%JFQSb*6+|oJh zOY*7bG}q!d=F$&{j~O9?Bl}|SOUZEh|6%|GR46w(8iQ+9{c+R$Kjes(sn{D8(^V7$ z8pi|cNRLuFJ#}>?g4JcRh=keh3J!ikml4hB~PJ=~U~1A7Q=R*bf<@%ScYPs0n(|V5G34kho7J zD5gBFK?p?RKKFXv_B!j$I_gU)gPx`}SNA807zN0*E9_ z%4m@N@yT4Xts{C(fEy8~Sw|iX{N}V7{#VFv9&`R!S6b?OOJ+(CKX266QEkghmDe(* z^<=~GojO*0>V5HoB*POi;dStc>9t&uQ5W0kF3a)0UBEm1#`+P6+K;>dxY~jFQ4JY8 zo1iyMw))Y(_8GP457Lh-+-~93S9`}RHf&r_@&TI5Ks57uNyEJf)#3_J zXW65IEKu7X?hb0d*;l2W8a8CJ5+xSzA*PIj&SBODry4YkMuxZa z8QgU;6TZRoMR;A=?`3)U{o}5iciak0GX(~{uT)axEPgXvl_b=)skDW7kYAY9E{U%( zS(yD&a*%}MG2;)IvE9m3)@`LVx{;EV_VTp1=tZga~ z^=TN7A=srNCX{?GV`?z(ubRM}(#N9J4-^m9yJohnEVhCDEqRkfa%45*hx-6~9 zg2K<#R%!Qit}Nbi6>rd&0)$UVgYu!@QZ>W#+B$BQq|!Vuf5*{5I$E0<1;LmUMk<%p z6P6AIkI3H-2heE7frjvh^d8n{98Z{f^6O80bZVh!$%P7AA)S{;E}IRB$}ElJpSgxq z{>?GD_mCwr*tM+qiDaL=uJL`_X=fBv#$E0IJC3INSk;CRUE zYN+jn+bHtTgXLB1s++~wP@iiW=*8a+TVV0KJ^y<*^4VFrK6W&KE z*?3BTGQ)3hyG!_!<9oy2`9Zcb7hCOq-@dRf_W9`&?I-p*PO}~f?`LE=quyP8-air8y1Xz^&vfzUk~?9Zw`h-2JhEcOFy6a zN9$Cfe7D2*(9#dR^!0Cx4kgJ5w6}r@H48)eOft) z$?&wTw(&}Fh|WapS_|!MX9^??m3`j=mtXtm*c)Wi7vW*t52_!QbYj|sFbJQV&(in-Km z{{$h?m{x?|F;QjkcEp&jm)NX4ohy#=dmopyI?OAkOUx=XDUhFp-_!dyy&DvBW#r2UF51fj|t}2y}S+Bs1f1OONDipHxb)ok+z@MV+gL=)7 z&)i?u$T}UDLl}^OZpZiSd?q_-3DZ+)rxP`^*T zwk*5G&yQ%sbKs5NLGW*++R@+vUJ;)IcaP|fAEANXXx-*p002qD8jgi$_bN|6z`h38 zIA~iC{k+u=cpLZZ&liWZ#oBw>M{Vw2Ww~1tgS+uM9UIj+eI6T-QI z8jd`dOuT;*w=JuV*7lq~;oh^_FV&X9zhB^`+n&X}a(~xXD;X4SB^LR^yoHAl6?D0B z<38T_62&eu-RJP`)BL78SpB`;j0``=P~QitkCQSgEaU9lUcV3ZG!vV53uup?bpJBs zamkuenY_Oq=Ax~#OYRnMfLBj#@IUL($9=a#ag1;HV}I<5Mr5pXn}~|#pc5aQFW4q* zvF=UIK#yXc5#sj+1>4nnGS752h+oO}=?@I|X*bz=b+z(ncZ9;$gK<{b>l}RL{Q!)9 z-8?P5{6U{~UDH37J8i?;gsEAwNqcs7p1uA>+2z1tr1w$L7HGXY2hruIdP=96OE3ZrvQ}SPqE$pN%+t3gw|ugKo)BrX|1uemQuHs>%LDCc&fI30>}dWbVxq)QXRQi( z&P6OWrdh&sGaJ^(#E_GdqbA|*Ok8P4|EIH3t$E0_u^hL=r_AvRX)@8GXcUyc!5ePt z2?^98)6$3lFdxTX<%Eq04i;Qt12T!-D8`6x5gVVYQgG`o2ian$%ERMyt`;b+$0tk# zLk3YCn&-x}{tf0~#(^7uc)r7eTf4RW=gT;m0NmHskhhHcMJpFn+o>KYDHnwuTIjzw zlr4wBKDgNdb;R_DXPYq_YeviYI>(lc9^cPh5WPE;nl0es{e#=D!%#qgDSw5Jf)at{ zf;@*S==~C*JF;gU8%=Q@h!ZAf8ReHV$`|%!*?7(|VGU@09wyUy_fAHcQQk`m3_Fhq zn=-|!n;Qb3g{pj~5_R080(7`>0S@&>{$WO@N%Qm|LvA3@eJt={l+60PwQvlEm7xS>ew&&E2wp zTyjt_z}( z5%(nG#@>t`MaB_hk8A+Bwbr(PEPhJX9Td}XgsIkpqTKDZWu?Gg*B{#oXfzCR(?IJU zh@-PslYNQjjI_Xkmw? z;G^*51j}Ib;sX|QY;WhT_0^mX0c|(;;DR`6g)QJJ;=P4NcF~2@iUR4ylI8`dGgHXx z-k9m1p5>NUL9Wyl#M;ltv1g|K=nm~^{ts7&m#lbhLq&Uu%BXwb%|8Ycia1E2|Cfr? zl^?VpRvvv|)l(`|4$(t>d3?@tNmD;w4|{LS$~6iaDtl;>NFXkv^g0gjUP}Hh?DF9Y z>L>e!6Z}fk*Us<>&?m^v3Q{iE3YUNUTCjX@NAYPjI{u$H;ct;T%BG8qCcw+qb@xa5 z%=fIny7Iy^dnc=YCjUghWR!JlUib47WgYKZQKEmK`$rQ6g4dNFt)MT>8ZMn{@q5+5 zyuJ5{vMYSQz3Qgw*8f#FB^mJM?D`NT!6C^00 zEMy@Q?u7N}s2n8k)$XwVo7t<2(fhybM3?tjH$yxvwI3cp`4>?8{m;7-r0_BA=*xsZ zmQq3Ey=TxhC*yBA*PisLzV%$T-|(M4sE^>~1}SkwMO>z*;A6a~pjJzARF+x&%)Ih( zV6%9itX2|Tzl7N&&VuK3s)|o-_FOEt=%s{{^sI=RO_M;$} zvWLc&Xnp9Hjz=Sp&MZ+3#irW@avLdCyqw!%+s@DXL;P$%!oD3`v(Fy}ZY8P6d0?A_ z1uBj=rDxb(tM7(Q^&Yb(hdJtcGAnyQ#=eEKQ{Gtx#4x8-jd+PC6d#h}O_08&c2w-R zUstwyws-W{!4YkQgDBC?TS9z8th!#4Z~t(|3bs#B!}w>#AtMU z7}+xKiM4jDAK5y~soXsKk3{35rzxDIX6iz6RR8i=QtW78R$}Wpo#abH=^=3F>eb!h zf!9pY64vcFsk-=&pnEw+$u7OwrTepY(03B{nrCXUYD@X2j1}F70hB(r?ypy_-?`BW z)2wKJz%9T*rg+Qzhzw>rOsm4jtla+c5@^nV@uOW-x`dl!c&QZjVJB9dN1LKsDyFoq zT&Qn4{$_Z7pcS-u(M@Ua76zA=R!9NFmCS=A6DYF%?zeEZ=2_1T<*~Ff$!R;UD>Wp= zdJ0u%lK>NCkN{Wxj;kuIxh6bhU3D-kZd%?=qREVW)@qml4l2Eu>5CJV_u!QUB0J6u zUrR=cd@?D#f#+b-?Bu8fj zGCsDW-+k?W;94L0KnLh9qa#_^S^kvvWz71^V9TG*`g6cn_7Xcz>YrbQKQo958E_&f zbp?OQRpBYfp`f_@-;?*wH&Jtf!qzqYzIGRhn>oG{AEr$5c{m0N%#I*atrdqhdo|@~ z_5hJBv#{t=YNePhn<(V6AU|)cz_T5tr*opX+s7dd4=^>S$LxT{?e4)GULK;+2)*0B zlS_KH(NvUA5op@X-xBb-SfHyX}KBjzY_DE_G53n7FEA{Ph}Ty z0hONcBxr0pKL^1wr4Y!%8&qx7<{}GIG&POZIVhKgirxux|9q!$9}S%HmcJHEJ<&Ia zzoTq~#DJD1zV*F)Y2KD{Hf^20D;;BEawQHyfa|2f29W9pjX&76)A;Q^d#=zj#HPxK zwFr~%=B1L!E(&=DnyhE#d?kG1shL=&6R+PyRo}frL&Icd;gG78?fA zAig#F7Zhe@u&$DRzNI;`wUX>=F_jC#ihIAejm%?0Qsa%V>mPwnAz7C@Y(S2sxR2*! zvPo(b#9^|*UT(SfQ^};Bci2GOmAf&v=I#K_5HZJaEvxaO44SaMcT>KZ$D|f~e4i;8 z_tjE97dJG>MCLV8OS+7+`yG~h7s0n%E+k0Pi2|XZbCqyYvPfXHWhn{G+;=9hB0qbB zqg|;G-w@(D%boZkDH1{)W1SRr_iKNe^86sNpCLm6Qh;}8*9J&P)Y&*WrS&Gr41S$n znRVG^@PIuYeC>HHwvv!|IIs&a1uAZ8>6~Oe7K{w;hH6G{raF=B>Z zHl!q)fiVQ+cIhKI3a<9l(hp#&Ot|oW08jxu0_5>eG{Qf1LW7Zh~0eOPKFNr&js>tt}=r zmW`Jp-CNMrX!UK~m(^sprpoJB*UawcWnm!UpJ>~TW}|5lQct|W4!gq64GQB7H;RHK z%a051a8g{|rNbKB5hiYbWwek7%lA)=`rPlF4ePv5^D&9ZG)u$ncG_i*1$y_Dgi3@r+AO8xc3!J z-C_vGRLiRiz1iYckmkkH1lK2FLUFQ`< z?!1i?lSpGqy)>aS>2m-o^+VpVfS&@<3zQCIGRYL%afszktfJg;fhEA0%{39V=X~9@ z^y&Jl-9TpVW*^f%FkDgM94iAo|3>n{SBpdEI;M`_88;yRx;8;^QD=|Fb&%jHW<+?` zJkQ>};#ztOVZmYjGZacl;|=D$s>%@=H9H8r+Ttz#0{l5-i{r@_E z04Ch2?2A9K*8|&25XbTh$-gp??#(99%ObKLQUT8Vfo@1e&d2ToH5OEDS5PN4rywm= zl8c)&2X%e;GPTtds+qN+c+uSyp0}|K1_nDIwDS!th0!tYx9?X&!;4i z(6rG0eUlBSg#B!sD8tmr!6-|?@2%!12wNZvb^lj~5#xc@&vcjD1=fTB*jG`5^9lO_ zBCQ4#pRI#_@C7GRFVIJ2vD{oiyBUshH{*aPd{ zpPZWK#50>!8y+3lc+mztmEXR~*Y3iyvD!_E;ZMiAN~d5I3>31d|4IWg9%p#1*JE?8 zSS9{*Is!BesrA@8(NjEpE{^WGIq=F_9uEq`hA_d@n1)TfWW=c#u#ys|VY4P;#~I{E z{p;M?ueckcRSd3>uq@{iJB1&ZT0J|SF+8$=pv_BROsUfchQSE=GCq z&X-y)cEOrTamA&wBbH)T!TI@6)MJZVe{NNOwL)(l7FT{0mEfSY2403b6EA`mm4MGz z>bEUg;O&~N$M4Y#0!6_zomCJnIb63b#Ba%?y!^U?Nqg}M76s|yt$t{At!E>B8?V@ zmtjxF1S-?dK#yfD$$2~^w?+!`kcBaC$@~bl=8{M-8gGM<^$b6#kC~>SJpWrc#8n~x zjBwKZk+WpnwR=}zpg{~t+L;Sg2# zv@sBnZjh4hE@_bNmIjdq>7`4$LAtx8rDNd*>F(}aazU1k{jR_7{sFhn%sDg9JoAjC z)q5?0<4h*Xa&7Xn1!Eee74u|KRjiy7CuOH>qCu&dl~rXVvyS~hceNY*h!BJy+wZ-x zUUQ%UT172Wm^F6-XP?3pqur-2JsM$0+p#_zcKn8+BG%r}{u)W?FNQo|!bUPhl(cV~ z{SpGyw^kg@v70j+ftT&pFZvI((R&>T&(oj@rFFM()1;amw-n2@a)L}jS3I)6FBza! zkK7W??q+)JEGu|@sxqI-UI%h3P_oq-aDp6B)X({?gjJKW#WcqP?wX}6x_DvOmOX!39yltd( zX*lRUCmDWv5FtRReuvyggf(M$hcp`Gn_HroHgKrh;cnk)ztb1V6Y%mh#_5}@Hg{4z zWf>wUDwf8(FAxGoxi|EUO);j3A2u82yl+=h?RR?dDp9zqDbv!Mby=L)(SUsRflUu8 zoWnI>h#ycrDd*3fZ{>Kx?;pDb*iyWXA5{w^2}rXa`$pYs8NLLKF8jLt3fa=Is?Az=WN$i=_msrfYF zoa%mA3UU^-Cjg6To@=*`unm`0gn8LbzmE>{a-Vl`u=%F#Kk!6 z`veZ$?h~h8raFt#Q2F4GBDGL6k)Ygv1}O(KvIQS{A|SMzJ+~`^#@23UZV#&q?DFjM z^MG*${6F9_t+!{SJ2$`sv2V}++5`vvfpvGiuY_X zmEQx!xcd|P*jc_vA?IXkZAO&E3ty*`zYzF$iTGwfI}E(b=Wtr(>unZDzPgXxeqI_~ zvC{c)TzUD}cqOZuI1S-?*Mwa4ExC<5q+lRfhL!U9?~e0?i=q(1E;f z&T1gq*6>-(pQ)3LE|)G*&tgZ2nv%1FK5>a_fB81oc)XgmyKmpJp89K?o0<9*^jaAR zLX8wqfjTX;sHMQx<$4)&R@8Bm?)-YuAh+`ty?uXGb=FJF50|%j56uD0MzlY)DFl{AYqW{}AU;Zi4o^ z{z5!d<3APQ#v)wKw$D#wLxqGuLqZ9_7Oq+8r9t?LY?g$w{^uxNy=YhW*o;CXnCWD?FmTgo>1hX zu?cQW$?I)}<(4%^GN4_mG=hml@qw{w`Oo97!2%#ZH}peL2~el1U;b~WJNcu`P25~p zv{P0^(r(GPy^+>rvMbt`B0NC^1T-q1e`plimS~7hZsVJExJ;n`-_LF_e{!S?YlYXO ztYp8fPnbR4y!E}UME{qeHm-hBzLnnD(>d^%nx5^jUUZgT@o$V9b$7|@sl;?BgOV-D z1o(i(={MJBsGT|DTci&?7pvGF#)6eC`g;_Pq6UD>l}^xpP(|=0*s(zW+h|znzLeX~ z|H{pezcD3N7&`xalk+*!X$%S>MEzd;r)lp!(ElzO-i0&r(*@k-Fl(nC`E_E9vh7P6 zYX&4pP^XK9Rw0X)o^gSX*X604!1g=aREJmt_w!An)vl=5{I_vM1Dod)`+D&u17nk} z6o-d+ddRcMa#8W8q3k=jdnu3S%;-hHLF15PT;TyWC9RnTpCTqvDLKCT-{a#JW z8H`7j2%{{7AICj~ABB#Vo-N;gM)-x;@kS5Je37hM5~!@g-s(~G39k{(iiQN-?dDm3 zBp%uMm_}&f{PPHMtn}rfJ+VZPR$%`}&*b{sM82Hlz2R|Vz91D*)CBc30;F+EgXOZY z)bnDDC)H6C-uW4E(ILG}XHpcaB}x9CYDmvJtoE?jXCX+0 z+*rqR%4_gp4MF?Cvdl5WYxQ&d5jBs|r|zT|dwE>&#usqM?;7g1xHlXAMDsXjDI=gy z&SML_l!QAW0zZdy)pfHQHu}bF-loW43%YHFE~lz+RZW1$;tIK48%~t!3hlAAM&y#| zBea`;TRBqNq7Bf3mNhy1YK$`rzE`sBYGBy6I5(^NDe#idb)dcR&g-d9ZnI zYOW*Tg(TE8Nfmcp?lCmET1w;e&9?Q(P-=|9>- zP1l2d#~4QM+Sp|BF`mHHJD8#COHe5X<(KnsMOI>BeBxFU-{V_oW$Xx*Qz;Bad@WVZ zjZz|A_6b;})b;+*YrPHs9H}m!D>p>j+kN|7i40VAa3B)M|8bZe8(`3j9KSETQPxtf ziS28DrpG^86A?Pvk$t?;c`v5g)v7mbF`6mI9{m!eK*6C$u+=wVE8_Im^_1QnTD)1s zFlAKBr)Bh%;XEc3DHC6^LgnkL5E(^T0V^>qw)jo4-{IG%Gcs8YOOi<=zIUDOnQyJDmFtz0E8w4s3TaOlkk#!fS#VZ0lMEb)btvHHYo6%^ z>M*P)f)FGMlh0O00SK`lHq+n(JR-0`oug!Ss027LC*1G`v}x8{A}QACCoQ%YIV(Fw z@6VA}BL-K0*jhYvXJ$I+8N3h)xQ|o@3vNu|M+UbT-IIdvhGYbH|AOVDsaQ5oDt1rq zRtf$s27($v&ed=!Ig>J3!q@r4=I?iZC|>5+{g9!)y?Kk${D;xOW3m{O?OvSMB=n~` zew;JNwJZZG=CfK=DJfrnNpR(Y#=JBLml0+vyJpw(4}R`>(8Z*PsB?Iz%tyst4Z5Q= z6r>UxcLWG=H8Alin@CrC2@9ulP;FO0#RS-#U%*f});uZ#f+Pk|Cr86=9fLfo3dQ+5 z^-or00@!ghxkI5HVe@Rk>dpGL*;e=jTkRaOc5ITB$IL6-Xg~sHoY$*WgtmRMN%m$z zae;xUl~jVYn5*Qwc+xeNE#;++>dw`^=ziZPTGSM45|avFs4;u(M4lN@*s{HicyYhm zCsU5rFZ-K>Vn0W-nnsorqhfh2ABs8a#fRtvPUzk9vCT-19^p?|evlmzo8!|B>C;M+ zl@HOet7prum)_%}21gI7VhTvPpK>s~;8o<$KdnyrQdQclF(3NV(xxdBw!F3?xqV=p zu9CrpA{$&8!!R(Op@a|+ErxWJue@lW!p%F;$pUR7^ zMuo%2&pRF4-5ELY2ovCOFtkq+X=A7CX4+oMQ|orQYx%{D^;e{6Uz$2lE#vEN84wA; zJ}jAkuSiNnKX=SA%%kdl9+22(v&vToGgKH?d)&m1a1qI1c3U3aqoqZ?bY(~(N~!x8 zksFNgqX6ySnG*v<&?~ThpC;|;v{asc@Eek%ZfzMJtk~~U!RP&cqgL??plMMu(8~@T z5S93wbJ^YS=sPWS&6%`*SG@fSZF`-9GkG64t7^0Qhh48yj$j^!!`v_&?oQDn=uu|8 zJxSwe@S$KSod#V?%d?mvo9Z=LN~$6)rDLXK@vTP5;PA$KBNsn*5`KDMFidg^cQ!Ol@ zSlXF9iS-2BUiO7n#sWpt$P{Ic=0!Di)WolX5)^q2+}c|~(Utq`laV)=sGR=*LyZWYG}3W3A)wu#kyw1^Oj1I&I4raYIE`LDM3=fcj9}$ zw`X1nfKDKV#yvax5bGy)kvS>Vs#<0LJ}b66qHFq#UIni9iOI}pf za_N?qH~gCPF@^ZGj*WSkqF!{$oAzkWP%DZO`g6bFI3X)~TjF)KOHXF<)g@{q@@UT% z-z#GMhq6To%yA-Gx~q%BY>^WdNpY&k|9qGP6SHC~xD9Yq&E#p|LfULJl9_#2Tq(EL zq;HC;IGqj+*iiI`Gd=GV(nBt0TgqDez9Bh2wyKZkA_E%(aD~xb;s&V6NM+^;cC_#4 zm`T1d@*8)nkH&(Lv9a2YL^n6J_^c*B7dYaF7ts78Q^~P2 z5yRZ|d#EqIDUP=3I}*;5L-;0sD-_WXmd9P#l1;N1#_QiJ0B`_)yeAg2RANCs=dj{y zYTSzsR29DGS@+r8NXT6J;&4i3=MuASE-aTy3rogXzG3y>x zj|_b7M35<-;V$qx z+Q(xGV0IZ@E7`vso84|PmfgyFsRv{Kj)^%u4?h8KnG1=1Ex2ggY(F%tC)>AAeGHXQ z|MK(1pfQaQmB`ERgIL67Q3-6+KtXq^(a^$hphoj+`%WsEBzsVBU?S0F7 z_WkZrL$}~he~*4w*m^u$=^vFm=??aEr6M|>6tZaOB(thxAviu)VST!*B#FGk2mSdn zAh+!7hEz81@j8&l<-6NU7O_xChDvnbK!>AeWY=STf8(Z5b4l<9e}h4RlM=|!&=&OP zS*aVmM|`}rA`N$$@8Yp@_(HU9*=zm3jSfMQV*N~k%nEbMP}`_<4`t0G@XDHgBS<+A*)EqsR9#<@{GO8K|a*|!Sa zDb#9Zk{Ex2@v(c*MF*>yuFvZ1TLbp?rhYdC)s4kvx0)>s1cZDZ77TKDjhW5b5~urm zSS(2exH@M*=0sm#f5)u@+Kf7$&6(ryG7h!gEmhW11o(IyW922$91q6106&_0{p#jR zdw_Bvx$ZhxF`j}7ZqcIR4bN72I0ouMX*whfRidr+Ns#_=bXY*bMe7-|GMpU6Ps~e^ z-tTUMz}v#kLZZJbDGJtnWCVIL(J%i3tM#c{b1R!k{jwoQ9c95qB(1eaey2@m$F(l* zl@Yd*rPo9nqA+F$gF0fzYqxJ7DI;!>eVpiS|?Jhuut5g{+%V2Ii zycco0V&<_2{B&DxD64)o{g(+x+9wOy&7dT!nViLLxy{Is2_wu*IV7+Lojtz?8ZVC} ze8bwBX)3)!x$&myOJ@6@^0kxIc2OKYsJ?PQ$C1UcHS3Hzz{$N4bWw1Sr*OwmAv5Ve zVEUgE=c`~*X|EWnNY|^RIbj>SEaQV_(KcA8F)JQPz5+mh!GN~?P{E*rA+1A=v>MBxse3OXw z>uGoFyScHc$yA|ZNVax63pe+y-Qr4KWR^kh1ioR>5+k*86tdj)eLEBVq_lN<^Od#W z?hM#wz99>;kyl;pm;(}1UbW#l>vF<5^S&_86t<&@C0fTqb?H}8&M}DQu{AXFos2Td zTxkYP9}~9BXa)oX=e9%9z0IWUKx;IDyT8F^fptnj-?gpDm$A~Gg&}}j-gv!4jlPMuD{WRb8h$EzoSrVVSs$r}&FqpKU3S-$rcrE4 zX*N^HhvzkmoV9=D;*Do`z8%iCfBeFR|G(v)!@OsGBc+X9VwO-Rg7f=>Y+NH4xl-L0 zG=WzbPx|>!+|>l!@QJ(wNk}M>XyljE-bnA9xVDC|MGj4A6@;ZdYktKrD2DBsNz_ug z`nqRi^(#@d$-}gw=e$72@qe`G&tJP_)u`@u57Cf-jKZo#4#vH+^#+qI(u9(P-=R?G z{K@W=Va=zutv=dmAjYQAWuNI6z6b&GVzmKbw`%%S3;UwJfx+%#1UB^!AwoVJaQC10 zqrYCSxBUtf1zXVr>F=i7yZYM&^c zbK943f_Bo}OFaC&>HpC#8&A)90nmdzvP{6imOFN}IaDUjrub%o>Ul3ZUKW$ssBydL z1~N}T(LD+5(4o-HIQJ!w*RdH(QqSmG*A6AkY{k556W1E#T zITK}Mdg*S})>=zrUtUg98joTz^a0Gpetk+ph>RoQM$r2`+0`(a%}AB{y8j@D&1u9P z;{R4X4C(%}0v+v7!45&NaR|_>Cc5I#k@41h;9m&)rAikN#&C<+kf(!~D|L&}hBW=U z_HW_)4XzUF;Jvq->HvKI;^gG-EU4q64 zPZNS`B7*CiHCI)^b|~WHEgZhaZW#)EgKwkN?)n^8yuNP**{oGbf2y6%FQmG3&%0{g$JI94*74gyyBe zm7I`-R`@Koewq2O5a`EZ4Nqdp)Hp*Vt)kyg5?c$)1?K&@LsTkx-BP6(Hz?*_uY ztkjiM?}?sP`9LshXIwFBic(Y^qe6Zs@c2M4>+kr!eW&K-C4^kceOkM!W1O4UiwS8` z(Qo+141VcNy(4lba?sl(zGbNT(%q9=P~=q=dw91~L{jdh6?g!&;xuP)N6APRT8s`2 zw8FQ>)(56z)@3#AK;Wzm>$0@GCAkx0`&g-d-sz_fYakUZh+Lbj8^c67m-wF%Z0#C` zJQ>ZWPZyaHZhIXDr21c8wtn%Aot=6mK@khA z%dN`;fjyt-=y*hs?XE38vvYp?1@21R$rlf&aEp^mr$>FDSX)GEU%LPvGlG@2d2aa# ze}NHRy7&OlHAwhtx1Y_(-tgQ^B9V2g`4OO|^ZAzT!AJTTmBg|Ah|ZfFDY0}1>Xm)Z)mslJ;(+bjqaw>WRl*ui_|w z{vrELeulGb#)U)Xfjc1$g(3T~&X>=kq)+qjhVU;DpC0g6j((sdh&Qw5T-+P&%w+r5DT78^^5PK1 zH!Qbia@IPa!_s!v!}hh)&>GLw8^6J3SSsds!Ze0zx8y~9l%(6ur}xj>0lCqmdE^&0 zN3eq0$r5>P8PBW3&F+!_?^t&+bDVJz->4<=xn=$0v{d`zk^s-xF29jy9=r2Tf)8d5 zHf8+EW7&=i$@R|g|2Gi?Uv1i0+QLcuGyA!jHJsb2j)!BrBQpx2U~vmWm!tg!kyeY7 z1@n?R$a3G8U+B5HY4TyTBn!*M`c$h6y{9z&oO)9Ogs>kO90yGEqSW3?jR z4Tst^KO@`Bj;3vnzdohcCV8CD@azjW-!FCpRqKSaVW*#vd!o`~J zCEXhrIDWTdwDswC%>f$)ba9_w^hEGb5?hR4SjN}<(ELuLz%~n=&NmBTz(B@RB2p1b zhQJTij^7!lFdk##O$Kv1s99guD?z(DdBu#Y@r52aEx7i;AU97Pf_E)hf^q-bPY^hU z4g+wp>rbjH>LcHWzBU44N7zkEH+qQ-^N)TlHujOaA^kaNjkeeNXFFGQS7FVO)t4Dy z@g=(HcS!f@-+q=Khfe$&BTW_yXTnW2Yr5}lD|1;aPsU(EkCeteeU!~KH00uJHPW%e z#{-g0qDgSwS}MBV;A&hP86d)Z&2#FT!1jHGU zxb!p2R(WI;gJu`ep~JYU!qb98dNk(K*a&Jq1FA%li!aCDe$lc362UUC02!F?FYPTS)6e&@Q|Y8zGN`otd0m6PBd*j3slMv+?|xC~sPLlbB;U;nkSD#n zw)BsDRF7@OEJIhZeN)4)Jiw12xFB?Is>~4#i$BjVyHkkkTg+;c%-OT8pw{S71$#mGu zG`ri8a-5ep>Q8F3&sUb!i_t4rZFXY*4ofPtqJy@4ET<0r{XU+x!aduMlN67DaT57) z3smDdA}Rkfi~lZYwb^p*R@d4DIqB7SLiYgkB$UWAe=+ zQ3YPGu7aj-F}|1C<=Nzy@!^&0gQPLb$~IS(tj-oBTxIM4I!c0Qn*qI_yR++f;>v*Z z&arq3fyHNR_DHKMZHSr4a|Jy%U(Ex^uX|mCKFIQmpl}L#_U-9p! zz8c?+#jl>ji3~8K*LDYozLS>C<;)Vj-hH!UW}?@KQX>88--?F`a0SjpGOrNIYT+E# z4O!r9Q}{eNw?hhRv4_I@dy-V{#XHAtJ+2m5x#H(#z9?8Q_i2%^yK|rM&Txkf2&6>5 zYQQ$N<;CM{!kTAFSNv<7Re84Ds^>fxU)0e0X@y)I>oss%IIcM_nt&7Y%%~F_Zr9!A z`+706mFV+4g~?nl0xkMKGoC-c#J3!t{}DlhwqDg<{!L~~+DR-;yuZSsr|$TGdU`w> zJR0=DbTau3t909$jJdh{nzJlZI5zV=+m9aSV>dWJ$mmHI7u;Fmj#-;WRyR|qr}Yyh{ipCL{c@a)2DgQr zVQT&tufS03v0{j2I9_4pZ=3>V@Q$L5U(c8p)0}=E69%Xw^e5g zp52z-Tm=ZG|JQ96@U(FSJERa*rD*qLJA+Ty)JNzrvWvC{mYxZQy^j@9WFZo&$auLo zc${L)TLbz~|6Lr`tg|T3kbW%^8|Z0%+{E7f?Jv8USVij~meV>OKcv-_l^L$&k+-hH zD!mnqu~tn^i;NkK$J#-|GGjMJ7NKGWLCM7z3;~X>CDbj_?DSNce%Ch?8p|7TPNAM% zd&8^KUCZ87d?f=AJO|Uji-3TK=sUmbW8RHs#w7Yd3pqU&b1TzawbtG2vt19-X2ZN* zUb~Hp0p2>F1Zl~GP`Qua_W;tPTwqOdgzm6A8?o&T<32f$+$-n(k(BjwOoVdsG?Qmq z!H0rbNI#+NY`l{jvwHAMW1FVBUHyl@>ZzMubK-f+MM=4+ z@=S$ybEY|fPl?+PvP(b%`#x4auTle{TTr%uxLn=Fu!(kufDimlq2437w^aq}7&T+| z45o`)D%^Dc4}T+s)dd*TrR-@2*cmYM;>q+sBe#Vdv^wuXij!1memYG{o5Yf(qTP~x zheCc5r=lnf=yZu|lXw`#F%VP@Kg)dHk4=_oW$K`9kU3;ueD4JvxuHlN)sJ4{i@1x0 z$i8?0^P8o!xJkcbK70hgzj8E!8{Ln4I*N+idg|*e6tyO_207zj65o5oOY2UjdB-WqMqW71wX$3(2k3oq4CXFHTy>?1(jw^ z86`sggOgaV$@(Q(Ca={8ZIT5lwvF!K32=90Gl?`TA;yK&L14Xdi`?N&v zb}VTD^*Miyp7P}P#S=%?IZ>B<%l%VTMG}N(W6SQZ?YaH6;^S&DffVG+T&eu;CT zC0%hQ=Kpz+llf>2&ixsl)&pEhZtb_5qM=K&05B)9=<(&o`1NqP+U9 z{r+2poA?U;IV*gm0OK`+xT7gH^nRaxfL6ls?kE=YnB%ef5@;BdOct7WAa|kI92<++ z>r$Hv+SG7@O0EXik!rB8NHbEZMftD4QpfrcXt8j51X{0UD^1vy(QMip>;t;nkVHD0 z#k-BCjNJ>I$=@}o?SR`o*50x>_!8%TDl_{=i#D2N>r9%l zfy-CNDQiddXyti3J10PW zWJSnQ6t{3LeTNj#5k>HPY9aV+aAkeEXL*bsztNRxc`BGmH(vGR6(lV|fHdv#4~RfO zNTveS5|8(3|52j)`K@ttVWHYRfUV+wX?N86)1RF!+B73_b`R;DqXoW8KjQ%rh^FE+ zOZE?_5lovsXIaRK6jw!u2(q%I&gc`{bSj~H&evY9YZ2sDy-&`TuuxI3VeoP{l4h*_ z4G2zu{Yg?4k!*RoxxL|z_VlVUZWw6C>hKYP_x@eu%=J9BVN%M=-IP?UVc+MfGVgGv z_(H{{5Lkz^NCmjXSXMXc_){%~hGvFkxyW_|Kqt!K?tqAq+r&_a>DX(-Li?3rCXdat zkbz@ihO1ymNy10Z-M{bjyV}IN4VJ|fH`HL$OoI3%9!s&JPUE-MYa#VKo@%*OXBT`o#SV)qM-R`9Mk10Ra%{Qv8r6?N!|yAu&fs)W}=wRBKzu(R|P z=oAEv32n#&OfOt?+19FZJfxQI%R9GZJRbH4Z>jCGU2a_OmslO`h!o#32h*%wJgsys zDiAeljvQq{#%Y(M&6~*Jp76&XYEomwV{8K=#1@g7RLMk$2tSy5Y2{3eGprrP15usE zGaDTXO39H!*jIKf+B66-6jO;T0w-GG1nkC2`<}wp{JG)SAM~DlfFa8jq<@7=LKG3i z#W;NUAE2B5{5XFe=x}}q$A@;PZfr`C2(ASmR8=Bb zG})gQieOY%b(Uiz$;Lv!A5s42C}EiTNa#4pzdxJm28N8rILJ$X4jYX%_CDI@F}fc50C8GJgu0)tJ)|u9U->6L3X)fG)#kcmN@FOSADAD~;#;xG!vY$f$0FnATvb9^NWLYj=w}QZH=Fomg%hT3+0BN|gHS!fs zO^Vzac6I_oRbk~k zjDQ2xC0*Pg)t2cs_$($lNd=d}{aulao|ULsz1KbdUbyZa67tV(uM!;XE&Ji35GoxN z&UzddtG<9U1f$L)VX=JDCIQ6>H3F?UGB-UHty^`^5S^BQ@BvJw!>?P#)QpDmX<2x; zYkVbA97(C7>Ko4I49tZ#Ba^vfW0WW&59H?p_iPuOD;EP}tC9W_@N(`F?b#v`PW9-v z6Qz+Nj16te(XIa?LXrlwZlDLIW%jC=n=gXkhbMw#)+EcgVnq7MVhZ@GS=%Z_s|CJt zrf3j`urt8g`Ic4+u*4C)2bYBgCi>4gM35+f{U5k~bnPh6F(s&`3!+e~kHKg<_kJ@& z3ZuJZBy;{jc`e<$Ik0fC7si*h`T2~9k`fG}eD5SZ9m8}bTYQth%#-G~DW*lg!;epO zla;^nT4>E9tcRnqy-6SWfcYkYCqOpm%5^cARp}kV4++F*7uIoB9*0xi6`xv>j`e`k zbWR>unQ!?9VDH$Lv$jS#2=O4`ve(KeI4a7fo zXx$Fe-Qs?1p#CXl;_0OQTfgl?P%$z&0J1cv+I zyd1|tVY)PXhrPGsDz8fca({Zqlk)q8arzwAWAYs4zUs`u^Z_ zQ!>E2B=Dv47o|Jr4V}d0Zl0h34pJ?tIbgq9DA;TbeE4rf7+F~1-$;X29Mp|DO8%61 z4uUX^RM=*rA7L`^Pe$w&mZgdghk8Cv*MDlg?X3DZY#}@DkN&UAB=nSuRKUfY z1zEv!eMxTK+_bi#At~4Y!A8H)8hagBM$qlL$6%h9@&-4o(u||h9NYE@m@&xR=F}T+H+*mrzB>Ik(Wa>)7MJVg;`Q_^Bxk_3 z{VUY{VX>Qk_91RYHL{W_!AU9*0?08;O{VeR$Z6O{ggpmb2_1!wODzg(>f^Ou&A)rt z>BR81h%PMSCn(qWm#V`uP$!Ce1`fuvc5fY@>xW$tHKsJxP+IcYQ}Jg8j;PY3^A1pR zd4?YEE%>z436^8k78W7@<#`a;mrD2x;L^q|x_oLWf8Udlc$qvS&=7Gmt_RO(@#uq^ z{hF(6&BLTF?#&cG|-1+2NiG@HXH$SAe);D%#C6WA~+P+#|j|)FF z6YsK2EedD7?q(z*5|Ph-Ba_*ik?NLvuE*P@gqyJpR4UL1)v0CFK`41C`E8Izhki1* zF889XJA!qCAkCiw#BWVz?56$h+0al1b?xx2wbgbMU# zb#9LGji@O9Dk^5$*kzb4eY_S2#Z?kE&Z1B9y9Pu33hwi!WOpGND1!rm+SAPABc@__g|#)zzba2@jR2qGB{Y(tW=fKObM8*_z*^A8hG2 zz02U&gambFotn7;iPk}KA2k8pC9^{c=x9*xDefH<{sO&xOpqlf?m$p9^(GjZDV;4< zE_%M*r0_#}RsP0U|K&aSc1TqQTFJF~S8#Cx@c8d!l;Pe1lbOZ-=4C1mr)zP#eUH-( zv5nsvOmag8$q7+{>FqbA;mtZoe((yJhlSmgG~B&Q%xwMQ2Ka_j4sEWss=%}E!*RF%doNp!!GO5Sl9N87mODx(Mo}v zeDqXO-=A;09q;Y%MuNExT;Y^pwL=X#Qt%VsgX???Zh`x%eM?D?f`KeywWr@3aB~-^ zw#Nd%4zjv#XmYN0-vD`0E(GAl=XSn4g{Ulnd&xfFlqVV$OzC< zu|EhjN2+u=-6lRd$xpAhWW^*F0=!d1I+Yw1Aj}vkaQ%#_Ba^tDyIdp_7ey8tb~W*g z=l3vss3y|0a{v?&`EZco4vTIekeZ!O7Wm74ppq)JxQiFuQ`e|Wd1-SukQi|O+tm>X2_Lpgk0=EK$%Ee9zW}U9js-n+IJ4>|)?wIGmXfRs`m99mz%ZuxI zJ4mRUc-2?5Y7qWQ%C~8#!REu4PT$k|!Il3JDMx`qq1#hpr<*t7^PoQ&2u5LGEh#&R zSeQ}7^E_HZ2Ajx`_I3si3_b?^O>#tiyN}Kby$}P1_Q?&8?Gu$LTfIg9x>q&w= ztQ%-6RW-R-*Es#q`L8S;^yj9iCk`KZl7uYzj-mB;0gZIG#^-!y_RM5O^}-ni%Gg&@ zZ})Z>toj-Fb$Ss7rB(H_ zyG7rA{&SKbey2f$_xo%5V4nZQ>wmSGgz^^l*qk}nggQ4xLcg=>+bP5VV>z*x77L!+ zI!4&RE$n>sVAJ=JSpWLe@*l&vyq*!>eW|B8IMXO^R#88tgZ%;cy>9>^LIfs>4xE@3 zld{ksy<`X!?o-UU-hy30t3OoTpp}WC1SXIMB)M(;sgfP)l zy3Vc6(WBdzHJ}EO;CZ{iCkPLudw9&;NMYuQaV(TWW0_Ggi{@d&L23ap>UYUc1Avf) z&jXcoIa2(1%ov04_ti?y{QKTwze*ljWP_vcU!@Q`x!jCo-7X&&;gPFY5teL zrL9tcI_>0n@XZ9qjPzM_K(AjK%N@3^@rY%>7HY_m(re13Jq-q|m2p#?s>DBcW)UY7 zB&De}!?^;uNnY*HSJ?xMi*4&oB20#|-xP5~?Iz`s{~673+~3!fx5%Bxzbbbu2%c4h zFQrtUb`y(OFxd1P8D7R{724QB&JLOg4n=-Wuq(&Ulz2%@~o95p2@<3G7LrnM;IOAez2s)pYaq#=ne7P1tr`}pm< znfst#Lidt&0;OYhbNC`fZN5c-k#g38`_z56r)GT6_B^{oWM5C2_1v9|H(c6B?TWz! z!Q9koVb0mvQ5tZQd8B`~x|eh^gY$)}`!$M%0&ocN%tWLT)Sfcj()vEGZYa>_z>Ed& z@`J5Eer?(E3+RXk+9m{RlNn|=rX}nRRT|5m_tdPKTmw>>hV|YI8hRvyuzumZHa+JX zX`{op&7U(GvwPdGj~cSRxvQklp0V`BK{@q#M8Lo56chhEi-k_U>7NDUpF_I(7=S3~ zEzi(K@bCDozDQiNwCxZSTx0|e;rna!mKnJ5qmVvm5$8ipo!Q0QY<$V2?*ZNigEAE> zYAlubx1SeDrpz%jE$C}AT-fzJp@xQ(-fIbbAh~A z>kuR#mi|e7PgC2293nMg1cBQWl4Y>6^nI=dZ&es0ewJh`xF{LqpF+>`i#Z?@NQo15 z#fXiO>QKZVr zd$BqpP%5q76%Dp0JIm#OP<{uo<(?X7So1P226M=#H&Og%8v^Ccl*e4v2qn}lc&g}> z{LT5nxlQU9IAwqn3H1)A(rKNLYneOc-alxBFIXk~%$= z1bwueMg%DAXY;vomHd7Q0%NAeL>x|1js?tp!Bc)=L$zJ~((9lugGNKm4LPP1qTolSZwaAjtT>#0AY0iP!J>Yv*x?CLZ2<+(i8 zSS%=7r9{XBw0e=1rKR+v<-v*GHlK3|$LP0z+FWlVQV(TLyeT?rfVMh!B+;9t->q(H z5IpnN!H=lHXDnz@*S|}oDsT$DM39hr1+0f$u~zQ)P(mtJ2%~bkhAek_>N4ZjX1g(s zJ6DRh@)L?&BR|LwdVs3OfjHD|Lq)dT#UFS;$vZ%WIDvS&#LM7E+M? zhuuCVKN+X@o4X?`7fTbvSw>5@@NY-R8uqwwk<&tCa-nR#tE$F_OS~K(jZrn#1JTmJ z9D0oQ+r9Say>p`k;|fi7*k7GHqP2djkpM#;q3=2MFTluEB5JX>qF zJ@V1peN?1PX$tUn_Lcqr6eqy1mFM4oS(Ko|1dHa+s(*05H(54?qu&BS7v zba5oa(SYP6!UHD2S4WtV{X4?7f+p24z}CmZ&nx}eRPvNbi+0UUhG);io#aE0(?ZqR z&h7OHzb^ep)Sy!w-_uKrOTi;7SXpK(qL83aMP+4DaZ%imwD1Hc!&|Nt_9*msBPm4J zeG*XCLqUc2dHd_ZU3KXmzHZXJxsk!|Sef@Sd1>9}?^oUG*ciCq|Nj=Ai}LSX2z6;X z&r&;%rb|?Hz-Wx9fbKCcF>v$FZ{x|xnjw)MOW!dEedqP1N(CvW>RWCBKh!4VcTGms zovaQ`430bUj(#Yl4H_17Y24@$Ls0=EsUO_Th9I&?CAtg*FTOfddV~cA&>$~YLK9*t zK-%xS%~=HEpQfx8>&u)<2$a9Jn2^F)b@iMp?0{S}^x#w^m(Ecv@jW_I8+(Go-TohF zLBPw4D38d>U>(3y_$LmW+vatX(Z+Ln6)FlhV(PL9m?rQOJRds0Ixx2gd*HY1o z+PaExVT^bTw}s60Frs~U!YTTUJjba>5Kt zwR&G~-CfEL?5+8^7hFyswk04B?Mv#RW%F8j`nKV}55Zu>dKairyumQ3!yqM1$Qzj; z1^rxG`>M^t<4bKw6tmt;%a%Hd4I4Y@^=nsG6e$|Nz=MW$iPO{b@GF&v5{;n$j30OQ z)CiF!bvb;^+?Yi!%5)$Z{dP9@zpslk@nW;5lqbm4mf*u;C%d;oC?upc7hHG7F~y&5n8D7#N$=SxuH_Nkss3 z_>tzn46~1^4&W4fSVC;3?&rAU8Wg;As#-5LT@`wwk5D zfJ@pJV}cSloDV%?(ReYZ1PAyJF2&0SYe5+d%}hi+NiH->^e@<6_pvLZZXmOg#|&|0 zRQduG-?jTSzu(`AYWneC12+V&W5gqJo*)gG;)Hptp}uS+$3D4+uXbVTx0`q)Vk(J;BnXCfAiK$Xe>n^?|rugiILMO{Y=M%^x6$@ zxEV;kW~lEa%%@I2?Ue5s6$lkMeexd6MCjZyjL+F)0C?l`0p(%s!DAxNh*9J-`Ix`5I#=hVTyLguH@Ixk22~kzQ!>nZq07_}Uv5h26!=@TGG!aWvVPuO z#NH479+9b}6{_NtqY>|-TDQ{ch4z2pPDbJ&S3jH+gX^1@j0RZhJ?xUqf;m6^v?4pZ zBfLqIkzRXsy&6kH`~^TP+7lbj#rurq|nvi zx@0NH#5f&;Z)GJMj`_KTUYAD%?*F97F}Ds%K}8)M;s!@%UZca%rma=V6?@x)n|!)DFe7}a0bU11esDEF84BC*1ct#4gd7)WXA(Hr18N=^ddlB z%kJX#$$ow-k$9>aoZ7JR#ikKD`n@Zz5YqKRoueL?Sf5OtN&p=4HCK(MHtlCakGN}reAMKe<|?u6{GnEnNbQmJbH+^0$DI-4?4)v!;BAp zChdOUl;O@pAnDzH_)MU)4;#k>n?P3Dp~@|U({SfSlEixi{d03obnZXl8K$PawhqaX zHHA@mmPp!vm-M;EFW4CWeMI{3S#40AYA8Je>(RJ5ERBF+p}kLM#?!R7)Sukt66L%4 zt#TLCo1erf?l;#&ypA?&KOP4P`yFVCc8ry-qIP3loBKX5OoSd>FN18p-nsgY zv560F_J#=aO%Z%AF1NU@Ps)?2KyQ)sKjuRy$AXEx6sPJ{!qg#W9l*4~JJgn?a9NgF zwfLG0w)4SaU-|V#X$I;FQd#R;I%TZCyyY&raU$4_s0a`@K&Hjk$^uJRdohnm>9KK2 zM>Qgd=HJw)L^MjWhB~~OYrx|tAS+?GMp;NUCw;9+Q;GXtA2;h@-T0veMmcSiL)`}uMfvevKaEglXF(Mo~9z*V(0sN)a5p&ksPB;(=PuH0$vCxu6I&=pU0B<~<0 zzE8dR>(roB3}H{P^ivs6;Ye5tOo_=ls}4CRmyke9d9SA4$1B~s-IhVC^qV~~KTqeF zFiZqRg6JKaY_yp9YO13izF-6d`hSn+5c$kI5mJJ74*TcX)DI*6yHpBda;OY4HB{K| zJarHJRaG#(-w9B%DLwn9d&$`y5=O6lg4&;JyY$xBM>%>&c9~>$`lckOvYZPZfNQwF zqN`TyZ25ynM-CjS$~N0ITzVym$W3>vVlG!XhQ{~n*Fc4@8zr<%yNo3k!$pr`kFcYdlYHmE2jHSx{>LahH1K2y|AFEh36I5%tz6ZhQ%b^Lr z2lsLP5UN~Gjo_0LS0(C1qfnKZs4GfY6)vC{7D>+M&8}}OdeuNSlW^z1I|H+%m-?6e z*Fp(eRB|8MtM5)BJsd2<7~Ssa614sOQ(5~#Rr0o`aTmM%8)64(c$BqyVHqN&a}|nl z>R=3xPX1D`bfB*9s%*BW`)& zP;p$X@whn>52|@qBaFN(1P*fdU;VaCJZ0{u`WeiA;H)m+VfG`-(i*gYOsV-%Z&GBw zzz2_``~B1QQ6`3)3W5DX_s0xl@fEmA`AxNN`3CRG)2(QPGwssWkaoHLV--YjlE8vt zslMe63H+75K}bfb=Qv&+rVHncNKqy50x{qKl5%EE%$d>Vj^8}b`lFcB$#}a@ByDbWC1(XN(Gz@sqrZ~?OKG+3<*<1 zwQ+Iftaf+)?h3Z!^Z({>1c3uzDSU#SKDIff<@>El_#hM+=G#{NT5TUENsJd&&);A#G(6bw$blvxca z?T>^D7dNIDtns8V9{~TBKYNIZ5HH{1BZBo6iNHJjAphsLTr`~vLy1dq5;B%Jfb@R} zw>Vr|Yrnr#lNmw4oh~|awvsK^xwOB|C0$gk#vqoffRoQ;$tj;Y8KR1;qC~rg*(mr2!Jcrv7%e5Ed*P7i+nx zEKV05gl0CuQ@l8fa0CwuunnogsW9@U;jKkiy!9&NFLX-VpA!l!HvTQ&Thv5WL7f7B zs+7bW?K2^Eo*Rx{BnvY84^AO)^=asLEY^p=4S}YKTdz&deRnJ4ZaCn6ga*5+j3Qy8B_YLS z!Bs*Q3%+N3r{-*hEMcnVdYYu7@s|dur7~h9_t3^;Bj0-Bxe}D$X>0|wL9n4Ae(Z&An5*SVyxQh;nSFjNtp!CE)AB-^Su(X5J)n99!Yi%0MSR=rtZVHjJDax& zs=CYlJ~(0M7eo9i*|i+lQ4+vlQ@&01OX%G=@X+|Ircl>7RK+6*p!&_orU_o&kIW6N zD&uI16mo1BjW~LD$U<((NZ1b)CHHRQ*ug5E0g9_~soM;)PA~P;q!Epq8PO!NU9go; z(JH)YNCaH3IHf$<(AbFn_e-=R9#OZ|s*kKilCFZ12v+~+PjWTxySQ%Zw-=78kPSCv zCQ8$k2FdmPbO|e;}^qwXCRLzlgmdx*^(0SEi*#PNfmThmXqT zDTn-~&x1WuJoBlb0rhZlwx*B}G-Wx}7FGASsLW4bmKD7FY-4Xv@;ZzNIWWip*2#BA z1!yu)6fSOVwZP5XH4Wt#Rgh}Nd6L@@A1Iow6Q&rES}Ob&^;)b^FbkZa;~^b{My(5m zke~?A6`6u^8kDdOm2}JNhtPp3m_SoQn0Ao}L_~w-^j9YH{= z#r};o7aaHHYSb#Syj>hu{}{@Ya%%(KSNUg>y#7Z|N3aqJ6(!vzDrnl}h>u~nI$7=D z;D7`e<@(L*9eO~S#qCg!vSl_HTk@Rcl!+p^94Ei~De z!TZPK#9rMExCtjMYP9&V_&IPZp@n*(+hBYzO(=Rns%XK%t~DEFBZef>_7G6&6-bNE zRk6kgE?DF6c%J9_vk%R|&9uMYEMo8Wlv3I^HoshaE;db{jT4`gGLeBO)seZJ2@I$%#F?7?ir-TfC#hmjLv@X zWewLFRc$$!2oTo&mp*nSIfxvL8nBX;{zgWG)WOk8(z@_4XaU;Tt;zLQ* zT^cnRAMaFn$fw=-U28od}AaI291+v_(c_^b66avfteJt{>~v%)0o&+e8v=%aizw`i~qWX6d( z2)lhlqzj(XSonakpU{K#I+0AuPPi4!{(iYLtGtjvR zHKZEgzn-60LgX?Ru+4Um@sKy7Ns1_0Vhsw>V1(v8M{FHhvE}3(V^lB&ZJIKGYMv?u z@HS^!U{cW8@|_KU5H8@yNAF=n#L{szF*pONdb-Yh(Er5myDVU|D3$OW5kUJId4pQi z7_c2SL;`;9cLl%I(pA4d=Q>j9!KUo*s^Xs@Jo-8vbcJN=0Nr37NL|~CX7EQ$gG~B} zwyocao4HlQYIFqZob5&Cfd;eRUah;JQ_(9gF(3xSr0{p@z(+9LBuzS1f7_z=S^jDs z<(jjNpTnBlo>)MJ0%-l^=wjs=ls$Zi0}U2Zw8PP(cWUOiPYwBLdo9*K{1;)-an-+~ z&f+-!(N92LLHfgTOU=MSd_8^^=!hYSHl9~<*bthjXcV5O(%G~1vowZa+v{^q@Y^M? z__|#V4Z744k*J$&C*6}T9cJS}^s%Cb?@Ac=Y##8#$K*<~NvsK@&J4n=4tE>xCdM-gDf0QCyB}y&05OO673$5DQq_uR9sh;d0;YA@ zBzHfulJa|JooQu4dt2MWPyLu^*ZzL!#pV2*ql$6@$C*%7#(&c+$K zFKu%lnqjTNBKEis(LU}_t2BZZ%i_q63a^p)SQSFgb}~eBdtETWE5q<4XJx;W}wq$CwEAMeKJK|Tza7GS}m72#4IH7{@+#1 zV*LwjP)Smv88XdQ$V7>}?88o_slIf#va$gir>iEUfqN<$t={{t++q03EHA!(tpv_4 zeLLo0-=W++R^-F653pi&oMc=gb9ckFl=j@m?u>QsZ)K>0zAiYQdPbnnnP#Aetlm!q zxYrieuBzta{GiCwq3GCBqVqCh;aP06Kxk}i92kB2#gCgEjEu|!FXNe4n!h1($@KnZ zmV6j+JFW&m5BgHya*z*Ge#>*g!pZ;3{d-P!7_U zB4BSl-5OIw`j=x5_?)HdM04*MnlnN<(vw+@S}%*3H0Mn7NbWhbioY0;(8}e2(TgrJ z$2j^wXCEV&-HkcKgBmHB>iVkzuvUfDf1B zwLg>--kjfq?zv{W@W6PXx#H23jUYuybwCwArAtnAnC-h$ar+$8c3rut1~K zu|1>mJ;iykAbFL}TLYlV^!C&>#_!SS#T^mKPA}PLY0a_Gv}+$v|HvoE(vfBNJpm;w zC{kITmj+x?!`Pbf-}?Dv_g&(kjOE$=o`8>!&ACHcOEcCo=;) zMS8GC-zA}6MATQV63MGct()+%)$!F{sEsj7pNu}R92J#HigmsZ3m`xT1gNlKcnt9_ zC<5D(9AAwGCz@w*EdPjDktU3q;ODls(0_P&%EPjc7!%MU&pW37qm}T1N3JW z2eaK{at)nml&Rgh3o**PjB#ojcc4B9@BQQz?Ed#4A|tmzw!qX_BRM148Y@u7hY+Jy z%C;LJ9M{qK%+~#gY>*Ihm(Y{j{v$2zt=oAltyMdEg}}?vA0|fkv!~W~$Jqk*+zQQs zHG${8O4e&_oSsOTVJm}!gW81Z!BvKzjkab5Qbwgfr$SX_6MXkcKMwO0rDsL?C;}O_ zd)1hRXVz&p(RfwVP*}-_mlnU3EF7&SEf2aHJ|&(x5r*iFpgmJaA2O-uuEIt9&!jzi zLq0YTQq2OnJK74a*?N$(eP^hZDpx*L?ZN>y!V>Ro!9*L(uEbqJ7e5W=YqaSc z_kjpcw03jG!GROUPNc)A+GhMf!`aVm(1z>tl{Vpyn~TnP$8&1g)}~?}^IBLObUj8b zP>qngEBBy<{-tW`^2_cZ&$-q83dr&8Gz<&ob??>H{xh@vFcv40<(Z^4!_cL4IzI_M zGbYTo!{KDkxth}b+_Z$#L`i@mgEqcPJg>v%Prt~MlY@Mu;{`RKP$v?NWGub}phOY5 zZ;_b!l6tdcVs_00<$+HKN(Cb88cwyf`MjoJU2Nb>PR`2M+`hbHA{?2Bs7fr0Hnu^T z&2^^xMy#L)qLfU%DBzAx#%sJ;4e`hN@IPprgOJGOvDS5^us&y86GXZY77Q$pba);%=r}(R#^GS=#+{}#V z^(W6QgRkqyQn;8C(F7U0IMax_C?A0fa^1v ziy?Dd3hNg8gA`j6lR8ik6c}`OQ*O*n6eEN66;&fe^L)O_CMSfJ=5 zBaJHBm2JGdzYH+Ev_cK+DnX41+kGUJ^*(!Io9NL?-MLjup6J!iKG|Q71N{>7)wBDe zC7Zly7<_{NdP(~T-#wm0nj0)T8YEy%xjTC1CHEx17nZh}sC4jy%ba80Ce zpIDMpad?ieZLTJmcWl*##nU#@tv&2B}7e7XSR>q76qK#_Y)D`oBS>uPdpMNzDZ||^ze(``7 zM$AU@6kI3Er9y_n&W7)n2PQYy9+6kY$btH;nC8ttA3TojX;nUl{#ArqF1C*)3C%?V z^Dq>vn7d*w4-9nX&HPJg4#IcJzzwlP1@jqPnQRkr*tX*;BZz>N!tF>TNJm4SlL) ztq92sP%I=`2%IX>%7jv9yb(VD4%}Y~MzMHnAp6!-FL*G*A86$1Di6nPNJAINxF!+G z)l_BDj!p|}sq18Zd!rx1H8qYbn_(Gh`KO_(Qob~WQ^C%=0vN>>mN29*d|NJ@U6AqK zyQh7$b^a;9Z8tl7=)8#BWwZrWXUFx79s3(qG@VebYwtS3muG4WCM8{4#Iq8dRI*b9 zeeiXpke5+Ya!&H*bL%7AX46WU_T^$>c!;(!W}k#8z)ND^>P?F( zI(PZwQ=puB$Aq)jRNAk*0906o8+|P+D+YK^4-XY7DXFtQwls7iR#)>U!`k%HEtz}y z?Qpew|J`k*u4tnkQHCO7AO(?A1*EG^E>ZpX4Y4e zuT>J;4L55koYo~KD`{?jcc#^Mlm5rNk3AtcIMjCb?$)w)V03|tp&3m+0R4Dzi#XQ! z9v(ho@2UxgQK&=|YcfwMARN+U@norQlV(}ibBN|-L7m)OsCwD`@jwB5SNIU0oN-yZ zWEETCisYOSLR*VNEMAgk5$skUcI{lfB0`zfQfE)P1LW!r`DR5I>!>rj{ST>A?pM^h z(W4Tb#`*B?GmBfmg@-B|!+BndUe5QKY>=A)>WRvTCpCE{uz9o0>_-S83sJ5WCq7k8 zdP#@TA>SFZxQ9kd!;E>g5$)}5BOehe`4nB)nYf8Q#Fu3RUx;;KqdaD~i(tr8Y!%w~ zm2qwiiFV|n;VE!X#{JkCb(t|JKUw!DMU>1IC_5Ez7)4j0uI!zmc5P|#GC_xWaJxCL zpoUt09&bAfocxUcw2``}MfcrKJP=Uc%y9w74sdhe;{S4Mi-_XYX-dQe(QNXtfZ;4QJnD?P*+)+{E%OrzMrO&0#}o(Pt%3YvXu+3Y8n$fCD%FM zQWK;Y!acuu3>mX*pw|IqE8O``eh{rw0ujBW-# zSw^Qj6>7z2o^m$w&vx6!QN1kEQDYJtVbXN%ydul5rYf@nqq zE^)t6A8SvUQtyN$M11^#@unX8tWiKw4;Sv^P&*9yS)bQ;2_#L3j;%@PhdI6Wwj2}$ zrZ*E>LMaYQ>SOadE1VXr+hRpVhIh{TBiY@JkL zB^YFKh`D>CX0lx541*5J75Y|&H^^Vc%cOmNqgB!yONXTSyH zLdeqKj8?6PC8ji?M)%F=hoCeWxReA6b32K@qx-^4505rY;JfDF1!2%7Zca=|pULUk zRfu@ZzbD>T`JrUk1wD{5xT?e%izq|}Gb9^4PghM1lbOdYnk zBkPEpc@<-#jRy>ZhQ9LPp3M@ptR9)Pr>s*Q#)N~1e(5`8Ob?9Qx2%>Il=(CHiZYS1q3Mbr z;g^`cI}A{We;7ys1u|Q{PZj1Tu3wf`Dw6th$MRt#-E$UNi0gBPqXYDzpy|#?GKiI5sT9ZpoCD{XQ@HoY7 zg*J`bhK)MOAsG$M|4`)znM!WjBo?F0B!O*G=U@(HGD%WmnfZ3zotr8pM^3k5+Q5l8 zNTme6!rQv(vxMqy>oXpjh8D!BlQ!-Sg9$baC1DF!b3=a+NjHkeEhBK$U>?A)FN;NA z`ZrGNlyL>GxJxy>w)($w(8dpldSW;_tl?9m{~)g~2|s?5jy0!FgPhtgZGHBQVTRY4 zrgo@cn&~R=gv>d$t}{GOY?X;FJk|l;<%mf=;QG~r=K<6821+9on)wjhj5M0)6Da#u zO@cqL2DSTxPt2nR;v+8A^R=J%*dIOBhfmS_Bm>;JQZxdC$A60HnADbWyqQkOIB;=H z!aKYKYE|8~&Bi;owRpXw1<;buH{;H(M?c>DI2`INZQa;gmfslYibepF68hQ1D9}HZ z3`==a4m4<^%LU=@o=cQImHQsxm=>iXoY_w#?cWL=5Cr@vL`pRZ0sXe1 zM)H*{6Kp~9M*@!#1V}8YP|*jisi}Dg`SOP}Z1PC3{%7~qMHddB--v55UMr=za*Wm9Y#_ zBc{_T8}{L&iujA;URx=l;sW|bTWOf_$VmMZ*eLh7?8H$@Qh`OJvqH=im^-l#lBC#_ zAckC&IN$ZjhjXDfo)@8o7Bsn^AAk9qx=iZZ=?7e6V@aKH$%yo>r*oCVpzsH z#bjv9T-i>L7fxqxSYvMstOc{EVb@-fgi~JD1l^!%=~CE0XIu(y(i%wMk864!bUSpQ zyOUV$&|&QMZP8e#g|cA-SGs_R#V&jOJDxN#?XR7%>Xqft^Wuqur?cH~Y`xB+Nz^?h zk1P4&Pdfg7Hd7eCL-eLoQN}Y8JR8-9nRP3(ucn~_mW94};M;;$e}?U^w9PXk^?!OU zm&UcnQ;Ey?{vW$^3)HMgyYUGKDtLWdp1zNSBvC7rGU_!=rB>^`3)Hc-`1AEs;ic>r z4Z-7H+qyWab>)!s^t5_;0sIxc9g)z3Ao6IA0u^pjHMaQHn);!PV?yeiMt?0+M7(h7 z2f9Q>ATEyhQ!wfr6m}L9Z=C=DXB`aUS1>J}WBvOd7h<-4>6_yKgdfg-`B19cGsZb-$D42MaS0 zN`i98RG1_kwM5B&eIu~ZO_ zoqg&BngCD5BG|XNdkRL*uqIU_{D?9IsHZOsSOXrt&_g&j_eVQMyBApSybM32y_O3s zLhxHRo=%^i_ngk1@&sG!1Ahy7G?aRs&q{bnK`!P@N9BAJpw1PF@?{uHK`-%sKFy;a z+*)Fc!m3`$tV$blX;t8@70?y{s;RE>oXi}LU*q*YVyt)nYIEfKGWx=RQUBUdGsAG{P}~i=Ec>`jXj1~FcTgL>3{yY zlwL>^DGKZ+4$grZQ6_%d)sU=a`_i7sg`Rb{x8QVEbw~q0LNc>ib8dyDakE+F|O$~ieX(iafMus#0oEMzpX zbst9uny>cp4?;Q5uGn$B&YhtI&90A%U3Pz8NhOVc#^mBVd!H`QcdS09YWmI393}Ns zt;-1m5%KdDtOUp;cuewS#VKb3k6p=IzPY$C2ScG`huSlS)`va$nKBvHd6m|kg<9cD zR}NIaS}9_zku&l;9b9`Sr#KV8>p-2QWh zUM>Zl$UY-M|Hf)|Fh-MbXKWn~gGv}^sPdQ8p_%yG8mq@s35llJOyg4t=XaYaNlj)V z`$j)r7YU@`-1}@9-(J*2e6gF(Qi2a&roYZ;_j0Z}_g8Oa8tx!(Z1r!|5?WkQ1N7Ko znAZ^I&b@fb_DhK=elLx78kdJc80edCG)@oVM!vnwbC7 zHu5za8k>in;qb`Okv~wI1fdt^F-9bPmZQvZZhc#_LWcWVMv)U4=gPT=M-!eLER^7@{JhGp*FAajQ4kw;AK| zu2k^dc9=I#at5+*jbFHtDtkq2p{i8=1Yw!Ph9FyB34&6qj<$+&N$vp9zio!{U~jLf zmFj2K1OrA&lKJ0eK$LSzlGr=i;;So) zQjUIH=Fy?(+%x}{xtJajHDx?&G~4Vtk`6}D)=-e>C5~}8kC3TSVA9ZhkN&pooXqrx zD~XWhkBlo|ta9$x_oja1ePaH*hqoUlp@+QCaB-{b0lUpPR6GUe)|_~iRu8p5<}idq ztx!CPrqL9#$QUZaU%L}MJ5!%jg{(WPP;o%%bWdZ(t^6dr5$7;IT@WOajY9!_48wP} z+4_HH0g(bkwz=$K_c$+@7KeHLR9{ddBp894Al^@~b>qA%cXgYRw)UR2h=CFRhIEe7 zA-+Odnkt8@yQ3Tf!h`CrdBy#u8HjFzI70YaN`{a6kBGqUk&(_u1gK=N%%j}18ySnV zr&?1+I{_)?+)mSa()bEu2(N-?3_J4C&yo=R%y%q%^&=>l3Z50Jm&i1}5}Wq#5&_@F zYx|?Wgvi5y4t6ct9*QaZM3KZp*UvKfjKgG`lctkp&rMbYCa#j5F#BmbxT;phC{d50 zx%52qgN&+B@yCi#zr*F1Tf`P7V1oHtfL#cvV&ZUt`B>n~n<>Q_H8zczaHFQ^$m6g3 zH(x`!X0=6E3`1Stz3AlRFXCK=C>RYPlJT8J`LJzIAB9zXm!TM~DBl0(Pe4On1?EqT z6@4pVXGiZ*{D!ArfD^T*RNaN~$*!WIt}!Da7+p;c~yGNv1c7u6L*b%_pUw%1z}qrPo`Az>B4S%V-XuDLQl?oCtv%vy#AA* zUmv&GJketbS;=A%hGK}Mj=8JKUZgZ55;WZGY4Pg8c`+FlTonlUje+KteqO|0)&~b) zQj)$tvP#(JrD}0*8A?j0ge6CM&qj()$kH(53cjX+82+~*$dSRBVg9t9BSJC36F5sx zA1jg{Ct?dqQevC2!+VQ7yg#pRn}eMl#SN!SYXqxNKC|Bzg#R{3_<4#LE~xZjWRcYj zCV$SFPg28yFx(cD&- z^{hz-yJhAhZt!$YImog_G^}P84-AP(s?XYGfp7Dgnz#t`!frJOb}Wjkf&3*M#OUqzDNN(*JXVQk~kIi^PsTY)GW% zv&$*|N*pQiw3xV%sTJC=&_c$sTHx~Gkm zaTWd@@JNP3lZRu_N@&?+3IP@}e@+{uOe)R1;kyF*_of{_I=n0lKUHJ;MKngdZOnT5 zB@Xw@TuB<4mMz?#Wk=f4y-_^)N_l)4cx+FJiYi@0*JkNfgP$s~7=Wgco1v{Muc-Cf zE8XUFxv}ofYl3%w>ciuSnkxhLJJ9>}G>N`hC85rpE(Yn#*8lBU+$U$W%Ip)3q!XD- zgLY4JzQqlKxNOHgy_~;yjXSA zZCUd(%JxmYT^MRBn2BIlMm`=5-%{sOD`by;Q=G4j6tpS5AqvMTsm=DeiBL<<%*sX#9l zxVc_1Qu-@tKqs*w4O_FjyjCCqbJKzwdg#n}+dk;>Om~2~y|t?u38*_+o$6scqW7HvK_8w`#w5l{KX z)+VD{R?I*|Cs%!G^#^>y!?kr~5U)#UCpvVN9W(#&5rxg=vxC@MjV%824)*5r_Xu9p z+0Kyj_}Fk)gpmMN>C{*QZw#+gZp6cMENc+*JR7Dwn$%v%(({1{csA6cPl9U|bvmJ9 za34C%)F`ZDbsgE1qiCh`)f?AVXHE#)FzCMot!m|4ho4*TXp>@$zCEOhV~K{dYL}BQsOhvaY$u2d@8); z?7_c|rTe|<_0P*fDsM*{n*!ZnwrqdiGj>` z_%-)gg_}^-roYR1j$>f2w6*3OC0d?zYRv28S-jr(g~BO#CJ|6cP|m>@qhSZC_M}?N z71K}ldfpR6RUcWCqU=^y-g(bJ4>ur611zXq3?E>QFc5IR~&5-aSTBlrsESxfe{+qs-W)p`)s;oyIAGLM=MSwbIVkQ{khG>B_>mSf_d{rPE0WSKHS7E-i7;_D`UK<)uC7_ZakBC5ho!5+Ww7AO%it<&lE+Y|%6ds@pP+_91A@`ds$c#ZaMcLnz7|}BRLn+t4 zp{I-IDm))U@NHtlt;xFYJ|whS)nu1f=*rTyG&P9$a*b6XNs?4)O!)zuX7MA(ZGx9W zIXmFtFvEj;!;c-wBARFTtVg!>wH3={`u~=Y(}ZGzF%Q2Y8Kk24F zY+%RcApVM?V}ga8pNbL)<&5uJH?O25KNl;DKrq`UjrCUBun8UT+GtkDC=%ACQ6^G` zJ+0P9i5tP+V$x{^6}Im4K;(_>Cdrr^%4f|tc1sf?rH!LMCUh(J3D z7oEnR;*Eg*DQ|PUw<0dM$nn{L0@c1pz!U&dcH)Uq7+}P*b&XYSQDypoI1+={*gejN zo+3@y$l6q6BcPA0Q?Jnm=XOKEJg?bWymHs_3%((bIEx{Q8A3Y338mi?80LQ&u}>^g z5yX%qZ`*E&S5t0v0fW*y-vq~Gc&0?o zhURkBZ4#8H1CioPu{vCU{ESLe*Ntw479E zfqz%J;-2YEPo^~Kn2v}&n93GAO8-Wev7;+klwoX%-J5zPmm(tOZ7Z`QW1&NULoV(C zI2H`08~X1dKEIy^IB;(oa58v$CQ~8mlr9{rk>IyT%7nYfS^e6SY`@TKR$gs<$>O$# zMAI7Uzs0as;TlYa8K-sKx=AjvJmHZ@t{4PKl*#7*h%a73yrT2LA z8Pp+rX13nqiu}~td<&FQ0LVQqniT=5@q&I>KrSWHpg39X>pWSidL~#SjFqgQ7S3{8 z7TvDu3$uaUzOqFxt6Uy+PN&TzsV~O^u?V|!rZItisDmdm0NnrTUqE<-`;}`o zsY`1fzzgvz<)C%V7($6MjSA*@o{6_NySkD`+$&@Ci*C9!SSgEsRlSg}Rv}?9 zbjw$iq6{A2edj|y6+-@ z_;^Gx>Y7UQn#-i_y6ZJ0Kg-{c2t5tq1<59pAQ6RKDuEt^^^26#zc8__qDiQBa+*jM z?K4p9v&aV#Q}&B*IN&-d$dyyPNjU6Y6nbM=4b|HNc1Aa8p`ea(dxzb$Na?kcAT_uG zz9wgYLC`(9Nh-wQKU$=<9zik za{Zb$72OV$hU{M$bB<7=&Aq&Kwo!#UpLFAVbQw{@)*iq9#ro9BZ&geS0&kyo|9PpC zi=UiapcrDOlx`;gLg4^XSM0v6m}PQLvH6;dGBJvYG^?UeOoT|6D5LOVzg{vwBY04` z?~YR>YoW}~ZQiCyg-T7oqg`qNQ(_@CaQn8`0pL-7z(0dlnr7sll0}Sna^&*`7b9Uv z2NM=dieQ(pD6-r{sfF|Qs=GL7?T>oiH~4Q_KYwJE*rO)zW4IT|Y!?}&a~R+l8EtJH zca8Wy_jJC9;*bUsAAAVulOfr;%zR1|gw|O!<$m8ic0=T~%8Fe>hlSWWR@M#~ZMKEXJko9)-L(Xk)O_hD1$ovqvmU6xW3WcPM zDo5}7fQ_p&#?eWn)3D<|y2ee+O&Axdl#8-aL|@x6lbZ#t{Aw2hbRGQT(_on&D?`5q zqQ=cPnWmVbqHOeR3bU;le4W5#Y|hyuPZWMHP%>FJCk)!5eWC+Tsv?ROII6V5Y2$SO zT$+FRpIiOyqIc3t1>)%f-;eFpF`?j8<%`^xWaFufRWI%N*a^`>@+qS8;xzdF&m?ewZq1?)2K-rtFdjXv6Jsg?>_JG&HufRx#pai zQ)|{TnOM@g^xtM-JVn+GQTLJ<_0qJs#sR5l zqhzAi=L<*<{eC9w0bJ~IO<089c2u`1yrGk^u7$_d+jdP=6FDZqk1+uuwXb(Euh}w& zIGN4J@5|yd!x@?qfmGXteN-$Is#lQ^G}pbAdSo6WbsjXI~CCXc|~jDCI~017I_g8x?L$=Arzcs5mt3iD?B=YZt90fbpOa zCmT(J`|^NIG+^UoFG%|kR{{pKA&mUVyG1hkcv$`8ETjvRc6x-WUeS91jGVZ}0e7e81D(=dieWPuTFs#itd#}uFszeX;_fMQ6PkY3$2RxAb zDEkA`Fk!vBfRdYceFxh5P>uBr-I+5j%h)gH*JBGo8$tKImgCc><5f^c0)wUq(4zoG zG4W3+cC0Ja>Y(_NED2PV_LXraQEL&KgQ~jk1A=BW=`*X+nqD3(26i^^1vQ^Z?83iO znE2peJZhXqqrT zikK|Vi*%F~uIeb22k8S!i}%Go>LH4W>D}<}5-N+042xAj^%}=ncW!(oRsa&k)W*|M zzO#HF)cp#Lj!i2XS{pQOnC^SLZUVDjYc{-Trlmb)I23!qm)s93*KMAB{N1mie5w+y z2glFY(PWUNbvo#oA`|@MkJnH$k~eFmQC}q9za0?zf)+1k0$-qOTq1o(QR|pP=TqFV zcxcNz4KqBJFM-1#F8MiCrgwvpHi>-!T$EG)2Yn(DHP6tui0vvad(qn*gQ1eQP4F>_ zKpO%0Pf;ip@mj_4>0=@w&$(r@7 zq(7}Qy296{%p&MbTRUCc4CYAcDg9#+IaQhf+HfEjVI-eGB3}LJeH+a~u2KOL*kO8K zv~frN8IZZYPVT&}%%X;cFko_Gg-_$Q;T%3!eI&^Fwn?LJa!o=LpPPZ45z0&cZ1tG} zhs1I!K?rIgH1*OHqVT=Gp}DjdfnEuAGE2!%pOvR)N;X+C_hhmAnWk4m2R~hYvpppQ zc%;D~vkJms2JqU5SaD$p1jRW6zi0nuSf-zUx_-Z_l-0X zH>uveRWJEXiI4m0|W+6Pt*HAswt)Zk$%^{32I%Zz9@@6nZ{J&8+ECLGO!^*X&&XZs_}=?&}!2K{`mcxUOMcWv*HN;x}dkfKhrwzc7c z8QN=XKJiX!7PmfK3_t2o0Qr}b=fuV-38Il>bVzgcaxp0;L=;3#qXVAGAaIFlP1a~y zy>?}yCbgQO)lT34%}s=!Nw&V{Rz?X~UKok8jOR!0J|1p&I>~2yc;e-U%TUe@jWxKT zSgWiQZK@RiW~qe9A)I53BbGU;-2zvPsg|uSGt+)T{^jsSdSR%k5}9eF!-p^tQCuKA zHBe}*2Nyz>r&J^y&X7!-5po`n1pc&0U8`$J1HH(a`BT5(EN#+>q1{07vDPSSoKmpJ zSq{`vt|&!Oxe6Dazf4q9^N9|*MN@JwT?1xI`BQGyds2wq(NP6!+wvE|35ND`@6%>H z$7n%ffUmbkXpXo#f@Mq~g&H3jtBHMIo@>QakQfls`*CLIKA@bH^PLeBqS?>L0J5u} z*l(Hftfv3HS#Ps8Y%rVo?>`?euU)87FWNY+)JXn&MA?u-!eCB#HVtnuJJkh61KAdN zSEScf%&k7`Q(>PH<>8uAZ7uSt!iEk#i9m-IjUHhxU+YY!NV_0b&aJ#`HBY8z$#=V| zgmVr?=tBTAtdNM_y&2#+lImS)wby2aboel5Ao^Hv6K=>6iGL?Plhfy?l1TAoWw_U4 ztg-XtWz^!O82J_m^o>^DHZcYaQa66o%M)W!)4`HJ3)_l(q7L~~AzCw`=f}WJ_xfZS z^XXP&CRXrrSg9F$fZOL!;K-+u@5ixnn3}1=OQ;}paBRC z!lSq1bCpRD{oEKrr6>NhKc42DuD6HoVkI#o?!gcHgi0AzYX9+xpR2Q+$gOQd^?YIY znWphU?7s)?xkU%ux!It1D3`HIJ-$N$iHT@CpLIkS@Q%DW(1K%!hRxZh<~Nw zo7ip*n)HEf(`(yW$sN^#`L+HPsxx|mub=5#5ZGHa91O)5b#+oHyFrJbSi%miRN~IC z(rMPJ>>E#xtY>HBsOQ5NPXdnB9eQdZzf$#Q(!f!vAoDi*fZQEZTWnJ=w}RvIA3sZ} zCT>Nb>NHj>90ah%-ON(KO0{w43?(IDX5g_?+JT(PVpi~KT(m|?Q&yAktroa>8~c`O zv4b|0vy~~fCsUUJrAeCM$ESz0x|<2%-x;BfG|gj7 z6)X$}{V`oP%kWO3~SLLk&obVH zJ)-_-RMF7TkPnh(Wb+#S(AZP(k@CrN&F8iN5OkPRW8%_QAT;f)?HKxwOwmu|PN)Z{ zsN7rvQHkYDsalQ0Kv$OIXw9GA)1Tuj{Xhn|=9}sm1E^_xFkoZixviY&ijfL;iTT-gHIn=erffwRx^py>*}De0z<}*fuoxSxmT{F%u@4 zpJPYVljw|mY|I%&w)$^x&p$v?UDoQ%vgkz=O3hwb(m+2Y(aO+Lf<&-`eP2*YY~iE7 zvG|90IawZzx@XuC{uMpN)j4ostXxA~BE-u2Z{uwVR;5b&@Iq?iNaQ6Z)5!Soyno({mIPLBGv-{&kTjcF#E zenaH>evjBCAOn4h`Ege_q8K5Mjl9T*79dxqyDbXCXmDgy7yQLG&>m!GXUBoXrwM7w z!i;%5FFhdgp6{WQa&hI$<%px!1V7F^(Dh@>_ePP?T;>J58laQT0F5tPO~`Q7cII{zs0iSdC_NL#6p3&=aIhleCpoZUk9djO4t_ScQ(d<`TO^EHM15xHjL*R zE*v27_%ks+ZU|^2exU4RD$}C#ngYlbiJj=2p~;&dRTGv69ZS&`Y6JDInaOf65$g7a z!c$*-H^DG&wdwPzv^x7?!?!6nz?tt3tTScw$P0UsMZkox|DuxY_rrs8knDnLwu>>g z<1Z)|x>A358pgtw&_t=GOOHOP#AR`$Tv3xK^K^z_klWBAw=7sT&+6A}5pdH?z1DDB z%T#*|W>I*d7D^7+vU@tJeAxK40BM@RaV$FEKs%^RMiLQv%wVF#Ga)45rEmc3Tz_2DVaDkOyOv1k6ma%Kn$Z22jIF!h5Y+yprgx7+!QN|y3y@xsnHPx2 z>-WT4`Ztpm5DlN6!Pw6VTQ%ZHH#a!qVF~h2)*mi?-!dtZOBI&YzhOeA-m-|qUj;HufbOXroC;h-(2NW1EV=G!!sC-;vz*%ioHyC23N7RciP17_8!H6*mj+%dV)2oZeDW=Owq>M+S;1PluGlkog_wCMWFm<^i9B@Pu{BE~gA6Gg83ftPe zozpi+sFY4HP)pgQGp1MKo9?N9>8q#xnq@>c6sayz;*{-EQ0pj=I#zLk?c6tRbk?Wn zS&)rsh5qSt>&W)qtbaf-RA;3W;jwk57?^*px5h{Ql1LB8*E#NjT9bTLmdgTu(REskTtyum&hjBMVk4G6unt^daM{^Q{(j&Ll@=yoGkJN;Lz^R<`g|1`IlJ38!Hz$PIAtozF2a zO4+2I$_{{)pnThxf#f2>8~!FqmZ=RiDsa2JY{$Upq@{w5^0lt+za+{(N5o{&52!xQ zw!CJ;B3%CP8tq`7;E2e=J@WfAi;T;vFnZ>WTRxBiS2tmol1|?%e9Nm^pcre1-&= zm7DeqHB?R#%HQG2{B7;){~GHeAO`|5zxwY#@lfI7M&04iJvUQ4)HPNie0f7}Bxg9+ z(RxM2Ag*4=R=OzO6co!IW3>zq7h=~c+7i9*We$EBSrHToI zibdoQyuWXNt0mzA2GtV+srmyFa)DGhmV^S;E&gY6tiajJqv}kB_i2UB%8cyw(e1_a zZK?P>CF5EulinrKx#%Ucb1TzHA`5&Rgyn~)Ic58hLBsabxE z8KXkqPsKisdKL@htXCU#PtKKTBoa%>0tM@)V(29`4Lz&^bUSQp=;m1x!HWmB+-zxi zZT21D@yy%EcE+O`qAb^MNE?#)?$7AO>S*}zX_2aaU(I++r)FDqFri7Z)D1EWT;(Cp z4M~L5oOiQW=5f&Zr9i1Ese_hcPCR53LTIhleqO!acYYTVU=sU#;7QsNnFc>s9UQ$z z_BFiIa~yx%tlCcMaZRBKpMaO8>?S@TVv?$h59dg>a==7APa_djbY4o)6fdHc-xTgM ziq*O;#wg0a2^8IlT831}yi2<>${$y6BDd+B^RQGdG69r)3osk1dTEQ&(Rp6(9A~vG zW+<_1X*^av_ryiW5rUrD@v|P6_gGyKNYVes9{{~>*m6+N){sP*dPVMa+fj+Fts7x& zgl@)Dwt4b*lYX558Q&8DN{s~Q2j~9rXpgvb+=(Zu*ci8Z5z1PK1*)9;;@@BD3B|lD zAGg%ND30-U1;5aPgH6YHRlvMr0Z$XnaX)HKNr*0)Py)dLSKeMlM>U$&J(3`)wz#Ff zMUU{9QBljl@C|1&!Xl-t(N}B1g`?+S|TN-c&D=re+UgS< zt7>~ur}HhKNz@Z6dp-fx^wQRBPgBe|)(tnIn=FS6!Mgj9X5OQDwJ-oF6!+O2|w+8oa;*hm?^CD9Uf5UYZ82Bd`YxBT^^-Qcb#n^Y=6EF2*`jeOjI z^bkbD-{+^A4LEn-F26q@nnILlblkdKmC6OgYMtLK)tir`?O!*^bLLc52G~4(l>&<9 zJ`is_VPF-ZTfI?0OIE+m+hO6tOqJ>$JV%D3Q|?Qt*ze;~KYQ}aA)NOR;6}@ppT#-*CMJE6UA$y5U2 zT*QgtxuB2R?{=N;Nx$Q;sS&BQ;_Abd%F8703=bidKH|F(?at7$A4oj2=f53Oz-ptS zv+s3ljG-Em*%e)g{8-Zyj17c-J8vo4Jx*xX-}Z`5QG^vI$`GIQMUA*h%&87K|M?}h zF6VqgV^K6xUka*2xxpC6DJO1$0}$G2e~B;a{jafRtY%tne9n_|lP}QygSUAq8tMeS z5@D@44JV8{vdk$221=AF1(Gx6)g}pfi9&9AWZieg0(x?__h2Kfd{vcSV5fSs=ma9# z6r?*!#uwWMz7hp>hoZ#jSLwAT&6exSL!?@hZ46Rp>AD{(|Mw|E-W&C~R1Nz}^sDx= zC!HkP*k$8*q|6u#VL}>3adwl+mHhHE9BWF+$2T@NiRI}bhbZP2GU~l@zRcqX$31Qp zZFz0YO9+@_2)QKtqV@PTMnc;@e1^sQ#09C^DdVCO;rl$iUdF0Ecs4ev6_Af z-~HLXR{PB50qB`pSx@W?CrdWK!LX9X{Q*GCc>o|K>%T7o%RuPQ5XEq;c7J~O=z!w& zjTK{-WuL#7Mmz^IFH>5fo@LlcfL0+NCyMP42uCmtcg^$xE}__)Y-9c4Rbj-*aT@vf)IRmC_^XtzxjN|0`KPlt7mkFI(^Ki8L@ z1XtY;L~-VNJbC>p!Z*v8g8fGt?Emtv2%95e(eEV@6|VOuIZ6 zXPf21e*Vi>9*q~CshM_ZzB;vfclF-IHPqUP4otX{8$ur}|4BomYWFukV42wo3Ah)t za@{%cZs@9_%J>e#R%zy`C@UWU9iH(@Tg0&7I0kH@xx~0OksOf9CsO%VX}si&th;)i zT6#8D{uB!I31g*vyqF_@_A}?zm}s%5?pZlYFw`9MX2f&In80R<(f`OiU3hyc9b_ZhH;U_12Rw(GS>#Z z+{<9J)Hj2n_T;q-4meNwBU3ei!tVOAz)GQ*XL%-OVHJev7J0aLJi*gHNRfAX<535~ z6V1uY-KA&XghmLYTHjR_fM|P9`rr1+_G9)K&z2%FovqNBb1xCML$&29_*_?Gi^&1K z98~D=m37mwFiZ!rdm(T6S4vD>9V0CVt>vF zJ{JsduzKjfj@Wo!eEwtb*qC%y{IQz)A14(*suHQ({ocxG)h=`SOm@(NlTq2%0WYCsz2_Y89^c4y8(3F|k3P|84ty`W8Cj z$?6qrGl z8QXmM5cD)Hc-xaCdpA;@#$dpA9MNx*SfhZC^ZxBxMQof6*vvIJ)ot=WeMQS(TQTDOncl7%WMgNT?O4*3u8xuw_L;984p|i}}hhJKQMGg6In^f7o#KifEYtT274R_hgM=BSCpX>dpaf06sJShV*ETC zOn3GlpITR-wY3X*NfUNe5m=ZVC1k`^G6$Qm1O!-kBI*iat~$hN% z{cqz`I4pDIk|C!dQV(-?4?eVyI2g0FZ!>_@wtMOp1r*uq&JFPOxKKBp079{#@t!Vq ze?%n4wUwea|0Sv&C9Uf*xUfqmaOKEAw_l^e0v?zFvre-I#08X})?~&69Uy#uT(rm` zulUhrICUvaY&GQ}la1dZQUTWgU?Kk*@TFAb!T0YX+$IB*7iD1mw+qLnw_A?#`ItNj zI3gqB`h|nke^6V5UaACq#V;j^$lcVvVI=k-TQ+hG1PmXMYU<)|<@@(sgL&Wz1NyF?3|#)sA1 zS)tP3cib6;>yh7^n_VX(c{fHk*$NOe+aNR4Y#=!$^uv{IbtF3VD5G2io)jz4^;wJUku0eJ5#|=yd@<3b%rSLUlc(whATB_hKVEWpj*Ws7#-~p3@ij zQ0HLD<=uvfDh}s0@PBq=@EM7fiT!AOW$ER8e=#84luL$Msnf`ajx0Y!{Lgfjn9(Dl zC`1xL(7os4XAjrYD^pW@957|Za)G(|!K%<1^I8mnGYXMqKPmV*!`YRrP7iv)mue4D zv5)4Wrg*53y;z`J!wVCv-`#p==c9KhEKYecpQKQK0={dg_ zk*6aB6d3Yd$;eC$(aW0-;e&1@u?EJJ=g)zcvIi9Ubp=$s)J6EnH zdX&Ui5JxUKS-sKy4>}EoZ{$EZi<{I0o!VeCH(qO&*p%lk;L0vz-lViEUds$xcJkKl z>33=!hiIrP`wUeRDkgbi<+a_ra0;oXmngE-(1=AwN`te)5h%n1c=n^EMHK{8m-p3g zPxGb!-0rNRPetPALyZ398XNkIR~(TIk`Wi6x@*wa8Dt2j0}XjRY}2o}IIOFSxF~0R z^X!QhtSgi9VM$ant(g{sQ2m6&pg*cri>|c|Jnm0a2QfBGv>RNECYEJw=5Zzn-qMz zCifdX_9OR>!qcJGb+)yq(_5?G^HD)C&P&aoJ}O*hdPM5$-hoF|)PL7K*!vZTtXCP!0SycLO4+}9ea649Xk_OpLvMA6l)_uXN0HS@~)JV2*TWTdOQQN3Z9U`~! z-sc7O91*2_>piZMVN~euy=goP!`iQ`=QTgHl-Z_`6zId68|+Y@-y1^MHX4A*h)t0! zqNebADqk@_dCjH|Y~XAHE+;ptzo(0K4r%)#fV2szk>k!4I^xEJ`^vw>WjPLJYJP}i z>2sqRdplI(maor}ZOc*nVgGLDp5T3t6}xMrv{LC*!OHMKP0{YuhdOijFY-O(*B)P1 zpN3q&)Sn)07{G*9o#Z9HoK{|G9BJQWWogrz+^?d1JX%vIV9Im`1gbMGYEaZ51>MIj z{H}ijip`sWIF?>JO^>_Fi3cvbXMXa@s+o=7s|8k`9{H?V!9HBo`P$?-{IX zXa-QWWTtetzRU~_@XRxImLY=f`&D|a;Y{hONg9*zsd_tELqs%wP6s%Lu~@hEGWa*( zeT(O@o*3s*VvXtXEja-@PF1B(Mu3Rz&PF2dr9lwW(0iD#_3OAj=FX>ro~!>{JtK^k!y0=--DbHyNRq!h za(-pWs{|ELXE8@HKKG>_=u#xBkGj9yR^}qm#V7=YbY6b+v;N~n=sw-)@;(D!FGfZ1 z@gyzR=F8exSag|0g$-K9EkaS@Ss7gL)7oj>%T69=@BY_l9tLvc(3Hm>ldILB8~E@b zbT`cR`d0lXfDDXi0Ykv}H~-OO2eTb|a>o*XO{xqxM{Tqt{NF>?wKjGduX#8Pi5S%>8tV0I0rIQ{ANF?dptm%PDU0Mga|a96Ip~QisuX#6EM1&lW`9@eRez5S z>BNNF6ZleIB~^%Y>o~*%aZpkk_G(| zJ~mF9j)H(gn~f0`6fQ&R7QUPRc^_@^r~0|um@vXsS#fJ_l84HObof^wR#u+Aria%R zipFku5VYI)%9oi&`rI`o)%vwE`Yuf=LiTh6w4eF=GjP?#o_kg1m@V(!SGj{e>Y#lp zYuK>SktH;})8_Yj{8Xl2eGd!KbE> z+UZ-Y%N=E)My-q@{gSg3)$jcyz+Ecqm zQ5)Lz@Q11u5e$HOoJ~l%R=dHOryYnEu9P3J@8YnT6G)4l=^sw8ma)-a zO^MTQYSHF-X!<=CusZU~KBwTdacxrYp+z~kTJ2naP@Bw?0A4RA-V7tR8jQ6RDB zB0J?sv>DEV*ln_cnewI+sj*^n9U6^W`XgH86QwOofm+Kw0KUr6(bQ~x@+*RgR}c$5 z6;H684jq>A%ToYWK%7b}D+Bt=wq)|e!d|*0utM5Fu1A!tDJ-o8-gb&2(M^=gzC*V- zGNejH&;2E0E%8`i7p=-5Ou|MhR;qJP?^D&%cnSA`5yj>G`E8(Yhg8ElWU~8`*@_}U>~-UmUi>ONg{+9$nLx5Yec@pFELhp zv;3$mXBtmn2ioR>3d6j2or!2{?Pv?S*ur*a7=)(c3I-HdiY&fJkNSX|zDCSJ(|j|24v9@|FSdVKaN#{VcsznR)|D zum$w-Ikj&6O^yb81KxRCT*UsDaYv7i^w=~mGkR(Xt>IFfd-*($>x672g;YKpsRYI) zhLu+B>M!`jZ1vm4IQ*g|%zFuDQ2%*@*a_f6a5WvYc>u#ez|=J{Q9#OYOKA5ZK+TmG z0)gA4e-DE$hbK#(9mS>rp2`xHLVB5aXnLAV9)E*d=8|wFx1|P#xA*2zwi!%eA!*ov zX6wn;>m1Z55}0K$kQ`<39!2tv;RHrHVDn6}BZMUc8t2L}4K1(uXZQHXRt}cf^qcU@ zO*4JjD9|XuW~kf=(=GBnCG&a-AUNiBS_T32sv^v*dGSr?-rs9BhN7NYZ~kPKOt}JI#dNdU3$m@PL1Mn5R`JRjL%% zl=lb9Qi_5lQ=d{FJj`2vNykklo_-Rqu7}RKTBD6Wf*jPMEH<6v5y>npBn&=?I7S~R zwy2Q5!6`e6)9R$@6}L-Prs_}qD6%)rbGGb=m|hUqM>|kWGa$0jG86;BfPLW|hKUM< zjfjNMNL^hhU|{p8`zLzDX6Rat_C6G_l{y{K(D>~UA4VuJ+RA#uwBgVcn-T02D#T9X zmuxxl2*@>vWj#lQJ3c2a5{mBC4@XQX6{Y29D3D_ul|`ajM#toy(w%dFWjksj8avuO z+S$oS)8ddD_+A#Q9V zXr@LOk2rP)B(HF%m%V*DbX^V&kKJ=gMA;3?1=r}$blhe!B^(5!-WTUr9CMW0X+bmd z_PMQo_B$SbYfAWX%qFY7uW41cyF^>FloSV1vh7bcl0xh!Vl?^3-cub}!tAbOg|D1n z>jEe6k~Kq0#A5R{7g5a(S--`RAdytO;v3V ze*^4QIqDKG=q$N_`oSaX4ik-*13s+#j+ZilMY4BWlvMF?{bp}%D#wpO2DJLPpGdEvT~0u6(J;o6;%3hpq7Dcqw~8SWGXM=$f3f{V8}VPxD2#A1%=!tg>9U6 z%$SOs$+pLsINUX3bCRk6f;Fb37s<2Y%J{99PK>tP zKbciWjM&tXyp)+tOiNL`VJbF9We}L5QHxMi+Xbj)cZm|D;||L^KYyc@)_-L;}Uo7f?F(A2n+vWZ7`v)qz!!3(}}{2aYL{cT7nrm!-~N@2RyYRIVY96oeWW~2pN(Vv%hzT z!UNiuoh=DI>oml{vao^`IJk>Xv*oU3w)R$bZWS)11;#{7$1`C2z6P@8&8Z}?=Y4(8 z%(3Dp8+0J>3RFN^`>XY+w_%{5rpaxl}LpJpHl8~WiQ8#HynO;l>Y%)n3=z_zESl{Dk=~jQN4YiRC?WDT}xRff1%sy?0EBf9jD5MF(Y+c5W&L zcw(HsQx_>8SSw{g)@v2I; zVxd5O$%+)D_HbNisFpdJR+p{bw>%E>jt0sN6+cqZ@(`t#T(R(Rd#*5arACs7P~ePq zIkdJ)y=(9J+Rk|&G^Q`cckCUwg$oMY5ZbV3x;|2^+RclRMzMJxkAvNEpp9~EEg$MJ z_M(z;+i-|7i_X%xW`E=b*(H#1JNNvQme#+_^;mf|p@N15Tz&P(?rp~L`0Vm$sb5Mo zc(qoj&3@*SW!0Y94&>YJ0>w>?cX73yqWX6^rKcYx2)E~Jpmi)s?dc8@5zDf}M3Sa0&b($Ku2{q&_SPkP z>FXCvlHNG%So2+~D81>A=>p$)2SY3aL+E1p@Dfg}C*zzoFsA1{D2_A!md*OOh>`$- zVA6&;7fVhw_Lz1#%vFp`AlN0Pflk)kzlPb-*QrgPRxl*>%>~pRnOaYlqVQM{I*8VzkbzKEP zD|*&4X3eqIVc4?rvbVb&1SFBxqB>BkfSJ5mZ&ahJSGzG& zDQ}uARZyP#KPEVT1+gMB{lIux$q?@1pZl7iV^kO&wBHj@cGCyx-)r?~#0v@u63O&l zv~h?{RJlbJGilg@y7Kpi}>MGIjkX%=6^iDX+1KeY@#1kLFRviXdV&1Q78FeGRv z91&f(9mFEQX+i{yJ@+H-J_#`(JW?HuR|@!+IByI91)m5 zvT(s`em=4gUbh#S5w1Q(X&`dTdP(B935 zKY;h<@8)BhTgXOzfSWhN&V6qhBQm&q=wnCH%9eaSju~UyX0j5UJh_{EMy)f2@A-X_ z8&h6XfAnY*&;>R!SNw$!>oC%bK-ZP&vX|ksS-&6GZdMJ4nR*~-umGPJ~5_hbbJb3Wmz7|^<_BQ7mB+ApI>3z}?RQ~C# z>wn-mCB+nFJc-G&YMa|^JvV2$Bf8LWGpZ0)S)6c0E&0EzI@`nneYyBgB}M+Qm}@6t z>S7csQNwstV3fZ3*6s}>;|Q`KVWTygM41pl9t`iWo!So+Fd=Wl&i+p`p3Yd3>noXc z)=y%DA_q$4V-b3P%7Y-L<{DS}8p- z;=)z^Kz1^6F?DFVOt(sLRO5j`1#U9TsVCB9R}653e@8Y3%grx}O+j6&(x z)h_Y%R4_4`@6d43d1zGOL2h+yRKkrw{W}*Q;mE}^+1y@IxW`SGY?T_34jSuiVe7p& zAQRL2=PoV%%jN`<9tvOkeSLR3wb+4JOokh*UB`)f{rSAofo3yPd!xHr^X<$0opv3D z;~0>S`J6X#Uza(uIT2iIqs;mMm$4QiLB7>=wZ1jh=k|N^tDiGCoH9YF+)QLXF@UIhy3|&8>OR zhn& z`Z)G0o8&@zmO;5c>Sh_=PcNX#MNEIFVU=-;=5#EcLP}LFOZpB`LFPkYqJ!x!>6==3-}R{qqWGKXg_2IluH<7T{?xZ|H3RYt!xvW!9^mods=M>6WVW<3{+wzQ>rM87`Gk7fb_E>;7jcGE z)!;{RkIb9cldiuzfoO$?d}7{aB^f>;QWR?@vW)_*Ajv~iSZ1>V)qE;G;fYTVtPnB` z@D2;?N@a4DUV_zh5ysMXEpUUD9 zIdeGG6ja(dH6>lFebKUAPCiq;#7ymZ;04v2h`Bf%M|?{FdeYHkY1fkP!qNH_}0(BfIgA`eAgCnO%%Dre)h3gjU-R>ccwK#Kdv&kN=cJ%AnKuxU(08x(2sq!@UH=`6bSzfX+ zruLh`-t-T_gaj!Fjs|}H!sr9&MJq{MtXT&*g4GXY$tdEDK=c)<7FgWgCdH;7E4Qh4 z(F@q5jnNYm(W1OrU4`(4r(^sk{Sv1-oGY&yM!zNf&uY97lv5_~Xv2LDte5iK?UW1Z zOBdKT9+l^9* zsx19sCLii|i@A|Sn?f7$s}~NC3EA6}Q2$QpGm90!Z3fuIq=ih}Z`a|4=P*2`h41P* z4uLck4M;9e{zC_AkvR+dLJ?O5_OI%{>rmKWQYmJBQ#a)%JfTIX(u!@*s)7!b!>2v| zw@%~sB3-&t%g$3ahyE>zOu-98bS`4}eBYaD&bB0(%wpx6w|d*oITt8e`8n4bU~&FG zn~6P=rC)~V^5n!|_iLg}+`cSu_IVbAI_$@}VOPW@u!ATD6uAU=n)#iI?rUDDW zt*P3INFK*#ij>0*VREr|PsX0; zg7yfIdFkib&&=bgC%5yGHX0ZOK`VW=x8HtL#2xeI2B)m2nJZ)*HA3INZ3}FQiVZept7s?Naje6=3fb9%A z^z}*}Sbr*rv`vPzmKi+GQ@@c@mwt#Kwwv=^5tT7murc!a$L|Exp1x*lF3))_Xr-Q(qD4xZ$BHA-Ngbyz&R_17wpU^&y3EMM;me}*`%;KFDSm^KN2&%` zLr|8ks;4u_kQ-skKejAjDo_clY~n|y@6s_E!hguX8(FQTl7DkbZg4~2u;r_KlIAp2 zcbl+#%IpcY%|Af^754y3^Uq``jtLn(w1VaA$@xT-b!3zEs(AN|m00Uvq3M=Z%c)dn zmvDw1UJj+@D@ued$Bss2VS`Fet;|01Aam(OIzaMPO7ju#{7ldoIQaIzjqLKC_1YY@ zMm9njBh^>zw@vg^bfTgepUY^Tj64f|R+qsxz9j|+iHr?{wM`0Q60~#%Ncp{*LQEPe z76i~+1OLxrk*d2NYwaRE$IJaeFRk2k*bHSnzBN+5|_-?UMBs@ zXA_*y6qTr@A>Zhdg$wab;=QJOJn5K`orB?Z-y~!Hj*P(H5_!uN0hqbxqZGJTB5l*s z#TkmVH?Z*!H>F}Z<2cZwdtq}^bj_9X6e~H_7>>SN%$vsFY|YhypFbo1-Qb^;WL~Is zDOUw-Dr7q@%gTT6a=-HXDDiBjS9BD$r9p<(Jev2ELE+}O6?(|hmpd*}c2IeX8ZS+mxxFWhS63+*`f8q4xSvEt=gUq;JC zDMWjY4;Xjy=W(#2tk2-I9$LfAMQ3F#8wsVL!M?x$dw_)XZ?1|~C+;F%oscwVcNQe% zM(#11c8-%%!zB}-C^le3G*0TP#mvc8f&Le38~`^?&&_s(eH+A?MUyeFS4E{@AoxtX z=h}b#zlSnx(ojm~r_?tk+#O=G8H-DpAXM+;b$%o~q(?2{JHB5g>Q zl4jJZTQu4SyGUz)p!r=42aST_0L@& z4GNXi@kq~%Hg#yAk4g55ltwj;j(|35M)eyVi@B)rvv>rSI1VWBJ7D9kSfd7^CbEMp z^iodHPOC&m_Yn*0DemO+F>bsJ=3! zJWWK9(^$$+B94*omkrJ79=4U?E2v?TyZ+CU`9;d0rQ3w(n1JQiH9$rA6h)5$I_~De zlxCYg7uO$F0h3mhva?;_10j;3otSb_1H_qVDjGZhJLo$K5q(T}Im*D^6bNReg1Q0U zfBV6p%VtfFUn0(Z>{(_Lk{f3C+^Z*FAjZmhHC}l(a8aG0crjKAYM;dBFh(~1DwMq; z{(FWg_OeRk#~8Fv6);DT@+61bRUS;yz^I^0R8SW7tB9M_R4l>Qo|ei;#oEvbOmF91 zE2Y57Do~0pC>2wYq6;~d(OO+qDQVS9#K=L47M`vs|2IisSrTFh&P@f@a)~AK86kD) z!_>N4W(SPAn843tG>Rz4+&*kI-D*}LRNqh|2{26}70+~23L@%;HPDi^oI#hM0-pcQ zluYYyM%Dq#DzQL7RRl=*>8Ke^JJDRzd)mkvubEQQr6_h$6y`lrg7_lMAX;c8mmEC( zX=Na9)H1_H<|RIRz@;hlFU9Mh!G%%r_IsLEJ)RF_D_xv~HfUw9?ja8#-5z4HMjkvE z9jCKbiK(=Nlouzd=O+o*2e0i|$-zg){2P+M{s~FO^(uSilcpaFW&^pXX^Qo_lt)nL zTFo%V2f7xn`Mh-XZnMd_IDPljQ*mT?q?E2=-bv)pMHMD&NxwfI-GAk#j#x98cEq$= zQv5A-^{>Ru9Lw&bWrC^fF)3;P6z(*xT0+&K9h zHED4$rr4@&`-<=>d3RE+KkKuFon`7($91h`E0Tr&mzvarn~#*-V~!}1B}s339f|I} zvoy*DnWCYE?g;5q%$ZV~|4q1y9hqTTt~xAsD1GHpR?cXZ;UEn`9hqC{e>2A}oJWFlMopj|QDnpKeaB9S3fP@7V#-WMhW4%59v!b5^S`+CE za6zHah9X3W-2$#6tKR#659NZYRjNO#Pm(c#p~YOQoX-?yyfB83OR#`aX8%A)X z>}Z8(>!-(2@|B_fIzRuHOXV27Idyu=o8tP~$t9Vzp}eSMBa0_emUe=!^p?1czc4VwM$O2T#J(K^F*AW;M&{BU+ zuef?q=~Q0+BSE$^&-Eh8w@$89S8&9c_-%+c#d}c=655Qn$ZawX?&(N5UX{|X=-HnV`T$y4s|e+7p+yx&FB=3ktIJ8GWAlSwt!Qd=HyEg zC*reGuKwRs3BVZt)P9jG4U{ayHnhgRq4Kwu_|D?nNClW2lBZ86dHWphGU_Skhka?< zXK*k%tGV}((hi^$i+2E>rT?3-1dJo_7gZ&7dChoPBXGRJwOm5APp(9xs@byqTqqN} z`8~I=dmQgt0r#7f^p2$($!SHOWT&chp5&kM;$g8GGLjZhq|QbX_!R$toJS!ymbq0T zn`3F@zERQ^q%En8c$}-Ob$vnV?=)?JF58m!I>*0JSRN(jn+(*NQQ5^|T~)1>ShfRo zl&1;)(Oq~$W+zds!J2-_uR5#ktuI{t4sMjQ>O5b8f;Fe*WABQWrRmo`%W9?@l0!0`pP;D%Q)9!$oQfi)@sW zhWD0PykaBgiYov6fLwqkoooOid%TJAG7Xc6(Um2`8XC~V)v-o?u+p>uw*iFRzB_|Y zql&hd-6s$wN|CE&(14F;h<*}9J99?>U7jX8l!7tHFnP@FjbvzW6gh z;LZ}eJUN%a!OGBW`L|YgzM0;Es}l>K3{=_Q1;4KnPVm2RooKyu9)2zDx!Ll>>g0$% zJpBp8<9;X{HC8N)ZQ6b?78|@tXX48?Zrgg90-?jcOD3N<0}wpY-(-UMa+DjRKSCd| z=7S``r0&PK{bw*6K8=s^#nZ5?W7;xLb{Ftdk5JBR1Vd@jb;xM9*pf^@336T1A#U4v zDJDbH?-uY}sY@0SM{j3Vj+W_}$Kp^VqynAHOdEgg{$D|!C5(IX^viWvlfr7loP{ne zT+D8o3V~6zd8eDhrd{htkivb|;Gi9~Q{baXqEvK}UAwdtk0lOTF?H}>O{7#BCfHkc z2R-qMN!8=s_7GKIj|(_nH=Q}E?5oA=^IjT8z*he88F`K{i=Wo3R}Xze_R}W>lW)ju zu<)dzW}b})6} zO`Y^237Z_NMEdb{X`le*S5gE%DLKZ?K~3&c#$ViXr1AR{W&Nb6=~9iB zDC#YCZP$Ge^xqwE1GIwq8TY6dSBzoBDnq2>q3ow$U8vfHIdXy*FkdI!G`V znxEv$OI1^q>l@+iqN@+iptlT1=Nd(3$<{H^S4V?cmSulCSO`g}w-t40e-C(c+`1Am z$CbiEn2Of*5~HYqp8B}F7zCeNo`ZFPrMb-ZcHU!l_djd&$yv_w7^$dXuIUQ@?Hl&R z?orx1B@Q~;por%pcQoA7a$0EC8nfz)OAIUZw}H}L7BC6$?C$4&va1mO>pekn#Dz&W zPUEcbl4ZrCacivGLmSxOlKUS4c-a+RT}70bo#=B2br$$KF5Qd`;J z&PrDPC{6F3Ke2?DG+@*UB}W`nyA6dIESJQMYoSx`m0~7gsI-ZIXOwF{5}#QCt0tNH zaiJA)Et_Dxbq}ZCHAvt$7>$nPOz9Vxt-Ibl4qK#AJn`)BgLX8244FUXKqeV)V)^IE zS=2>)6SPi(WcmCTHwS~;s1Whh7a+x-i>)=$WnUkHWvsEsm#5C!-`;aB971JzZ&kOM z;37q`dF3Vh&~W)b0_Upk{&1F2W5mlaQ$1K$RSHx0=2TyjgD0C*I@M3kD3;G=v>O{L z*H3df&~dt5P(?J0+vmb<1wzr!CCa}h1Fmm&rWD-W<@es^X81PtnGLtuMs7SneFv7a z&Y{Zsm+ZFb;LkgjvAphuYS-Pz2Zw`A1o>U%Y4W@$>PoSC7`B_X!)*1|dsm$(%K87z zu7N=!oR?sOR4a_CTiHCuT&hxW)jHe2E`z+VHD&AMnc3;D&~&tQs$zmOmvmQMRKN`Q zG-83k@H}coXjJJ(YQQK5A)?dDQo|UqN&lL{7FOV!4OnHK8HWLCa-a)jD5L~&r^F{) z8BvF)+dR-(0(9NKWcXFAW)H9cEV*VRJ+5Tj%V{Vk9z`CtbR02fTG&?}c&Z@ClZn>? zw==z!)n0d|Rw7=^Lihpw^qmVE&VVX@ohP4XA?z8&7oucZ7OlBLGazX}?hxn;a4-eI>o)%ka)exDTg+}711?JF^o{8N@i znm;=8I-4rrCSw41PbbSDx;2A-o=YX>cZM&9J zn#08lit;YFJ4Gu?_UICrtrLp(ch(U69TX4&)t?I5E(+dC_9Pq~u02j5lXQ5fsdIIG z#Y8=@2+k7+YQyNl2bckyUtaxI&a=pS$9;M`_gmI|)E>DQiV3jc^J%_gH;a)#_IB>H z!mM?nnGFt6ZyQW*!UIFa&rpDY&iau}H4?))s(?_zuhgk&--hxbsIh&+aw>{*@lvv> zQCo-Fsyb9i3bd$DTMw>-Ipe9sV)&lbi{B;Ke}BHufMl5*l~YfRGVas6NDUZb?x>o% z@OOvNeLli+g%7+#{S5MjjP>`Rjb8U)!F$hpm_A~j%!Poy(75@XvPfG*Pv64lga|O5 zc_y7<1i@$A*COiA{>Xwd_P>LnI^2lMbeogiTCSZM1u}Vbuydr-L(!(4KRPQ*gRvfr zJ%`B(S*dJ0V2yGJI^ zht)x+94s{q^vb0Veb?N&nk;UTLo{EdJqI<)9}k+8O^hWH8w{W5Xppi|npYL0zJ3~8 zQtW2spnVGs!<8%ZLy?5680wDgg8AB?%@c|}>mVYVvLW&h#Fj3xb4A+9OTHZa%pdfK z*dDl2?>xUG#kFqRT7DOnjN2~%{^e;W!GOrtdMZ8{ zqQPGUt-uzGzfYhm|Qpg&WGz6a{11k_h}TozmR}I z1pYfi@qd*s`#@;dUC`8;y=i4xeQp!)U?kba1Tl1a6!k|Yq(!s}mlCRYh zrD%n<^5AB>PPb>%!BcOYILzphKZ)P23Q%cpfULP_P_3 zg_XD&JB2p-*_(+@KBup z73*z#MVlxf=3wN*xo)Jbk&v|+c-yLHMZOce!r98MvwDfyv>^mT(2nGLnt1+EeRo@F zBZbRyd9^lW-}K58ebjpl+A)6zG@K=sqIrDz1byOSvID9`dQs(Nu}BgADt$yq-PVot zdgT|#YV76oFgua6@|TL)h*_phIWfBN0yNtPo==DB)dH+fxJzkf8m|_6Ve`OVq4^$m z4M*wPxnFj^JuS&Q`q|5mjN@#{1i{6b#+#dd60ba0KSj8+v4P&G_!rLVExx-wJ873Q zkGOvPclWCFcdal@DBA-@Rd0q(D_1ufPuQVJb z9vj+Yd1AsQ;Rw10dPJ1FOgCR1RBInh@4X6q)!tzYFQk8s*^hw_d3WIyXMg#OrkF8s z<>&V7@WP|9%kdPc@w;sQ&}hc!;zn077vQ4@*cN)be(dx!rU_?A8IRfw+wZgW^eM8X zE#Oe^cO7^;h-Uu%Ua@k{&F^Zgcs0-+7>$m5dKbU2_QR-La$^DQ*KF!M1spTK!q~qgXnw7z? z&cpX{qe}m7{%d3E;0S~9vfq7m=rPU<@z8JZ zKV=^KUbcI@)1-$y$NScNSFJ#}@c-bg@ld<O(0wdBWU zad1|FaX*hqhp^{4BR3|$dcz5m)@>r(4=%4W*kCb!qG9vHDq>gZXTQE@iQ*IGvD3+ULZSg68NLF7op9k^h&wC z!L-9N?{u_I-CrFyJYbaVV!pRPQ66$3(_ZyCAP(yH$%T8Z!v2?qHWIlpW^cEFsyDs?+D_s^ba&6G%W}$;1lrM4qUOb}_xl!SUIVsdoz@6mzQPP=aSX z;xDJLB`enkEd^>WGOAP5ri!}M3D5=*VFQ&N=KWLFDMLrJnEXFn;cFFG?pBv~)H|&R z5*o(VeNTR{y34@2;b61l5sdBZ!*uxt<_=7B!o<2i13Q;K>vT2#b9IfBV?7{K0`GKxlde|(caA8!rjAT2?M(F{(#iYPxc z72$z^ccsx=K%LdFG1?MXB;Tpgp%D=SaMMZl3(B2QSm!!w1LO*Rma0(+E4NK3^a~CP zjir*<5P&3 zX*=BTe>UBF(S0ST@qg{!N^>)9d280M6#p*~thV8?K*p<`u~;#Flt|}r(YR%x(e294 z)_7v$t+9gQ)$^p$s0-zSPJ1Hj(O<-7n;<*~8?pddj*{Si-iicT*Fdqht z??NQ_ZK;4}VjdI1t*=7cW)1gwE(JVpl%aNh4s2P)^!MxtVRWNFNNdOsU#vt0y^k`UKjA8%4W`vsFXo?PA|ezJlGCa*xx{YMXG zxK&}tc%M6@pQF|zes$_tPDZ{1!TVQD*qtc~par)-r(D=bueM?n4!mn87(O<2&O_1B z`4f;s+cPHbUE}nY-@3Ezharkm41HDC^y8yF0|}SDdzZJ41c(57Luu`H%~47HZ9g>5 z9|RuzV+S?hV3fFz%Eb?B*9pwf3B3L=aqtU=rop(lET!G)P(HX$dO76 zNN|;)BJ~YM*ZGv@mOE=s{fxr7Byuu7?z++sslTPdlm*)c&6#z`kMC(k=oqJ2XT!?t zdS$b+)81!!*u14e=EF(uzlZE)d>=UKp7WcC&+YUp*I}G*rkjp;^}@Lz1UGDp|G%+` z%|!$K^u8O3XpVfy77!7#f7Qc?BfjeS!Q6H#>A&phG;g*& zoj1Q!LbNJkW9`z#@?)M!2z|yDCL#!$7o zbv_P*YsvP=!#aTUtMEZoNSZbFEp8$t8XuRNk+==bj7OjpJt%gseJ_MTMr9bo%L}lOC zY7K@P9)I7do#9X3ig3E+sBwQ3BcV}R!c*Wk?4LJ-LG0X$_F27MJr?8+<-qGt-#{ta z>7Ql45H;pd)uRlu{Skl^jklClp}CJwBq8;~xR5ZVeZsv%_3djC%6Dpazn8i4KA{>P z4Nur7WzpoG;UZ?WG)I)|E~+adi4IeE@^;baR@v5X8~9Pz#Hdt8GVNrzWKiLfZ;0M; zn^AnUx0+Mv>+gj7i*T<1dv9vJK=~oxcB2bz;%$HZ=zjfSjdiZ^lqFY!`CrNln~BC( zlb(ws-TPeW#%$z>?|0pFebkNXgsAZJmAIQJLWV`qxyt5zI2W9Z6$#+!I5(Dco9)zx zsXL5Gia>^Jtwq7qMUrv+EnJ+Rb@ARs@@Ev0MRvxodV?!)tgxS86(<@FfcC^q`7vA3 z%L~d*dls_%FZqa(<47)n!JBf*^)(ennlcl1oS*CHm6ChA1&mxHoaV zffr(Bs%2A{cN+60sz}u(&M{E&jI^kf=M`J$@F-Gf=|iqa^PB$qy+?9|V5%nLck_wN zbW<6{OMtZKwm97o=A7KME9}U5JDxX%2WCBl_b*Gw_CnEb*EgyrKAqHy?%mImqgoF` z@-SRgg6fnO>|lKV^7rpdNmr4RuLqGKRR=92^JiTzaq@a4mx`LLYTa zE}~r@J?Vr_26JX*ePas*y@1yztQo7eYiaXW@C7nUna+%_*qV6By1hLe(G`|OKV0}~ z`tf!WRWl4zs%TP_yXj)_#bO~fRjB$`YqiQppkHf*CmxA-#)#b-r);3cFV15syqzFp z>&HGC>Mx43q&nAFQ1M&8x4fzp1UPe2iXlW7Rmi+tA1`D465MY)1uQz;G_0oe8v#*h zZ`OPXkFko;U9Lq`ySlV0hpdKQ=EK8}+pvHfq)0Xbk0yq|klrx5Z_jfnJM|gjXW19k zyU#v(^*@yaGNyxXm_*`F)GN%0Vznc;MF$Un37rfV@S%6_c-N`vPd<#%i;a*CiZLZA zO;T|tnqW>)jHXT2ZB7cDojp<+EUd+xRNnbM5m4(Gsr$G8>OGwxl?R&kqr;Z88PYTe8rAOSO-M#E=n>< zF`ezt;nMAAv!WE%c%AW?W8o<@Dx)V<%JKW*Icnd~yJDj&h)mFXFk`S$6TmZyu6dw=yFOv_<`kgxxaPT^GeYsPPZY75#sig2^_<0MzGA!QAr<2!vs|ay zkiQ>vpnPgw*_4@ie?6I9@7~PgQ7?YOLn4_)&m47K6zx8EfC3vf()se|s=zSHym< zt8v(Div2iW2^VAHXhngZW+4kEOI z#LZs}hH^GB;k7|VB6~8_U*-77PvWehDdQG0^y<^S6As!>moFWe_CQ~V7Q?P%BBV}! z5z>b^PLHMD-X-R%WoGCs76sHWc*9rafa*EOM7f{gZBqrAu<(?oTI;1cw!)PD9o2;l{n7;&=YK+uZS zx=np16RoQFpwcr5v2#mg{-DN?Ovk z8jp7Eq96uT!nqqr?X-@2{j*o}KijnZ7%5b9vG*afPDP)=4pkQE%GN{i+-XpqqT2^J zE%`JVaQUODK1xmnOwZC}bz{b-L{~CoEPP zpDJ9QQh5?$?@}L#1cfa1$<#2B|ByWay3QQRm4?TUvLt!yTP7)PB>7p&=42Jp5xk-3 z)yXX@6Zz(Z#xDL3)uouBsY#&-`oI{edkERkXRPog}JuxagOit|zI zMMnZO0*=r{)*U6+;|HJAs-FWUcH_wKqw|b*3tz{6OA0gsflLMg10gE<$5n)Xv2Dy+ z+WFJ;hiWJ0d-h_LMuvAbd5#BHo$YGRPKnG0qsd>_EYL6z{yrDkD2>ZfE#cufVkas* zSYFILW>VM4F@<$F3}R0ZRpm@-vXn;dN|Gi=`u~zRJT3l{lxf-!5tlyM**9hn29|Dc zS+Zg~=wZ}5s@{91(eLG{MuWcL$ubj^d~i?+2Orud=dSF?7AP|?a!{gKHEpdAPuZVl z-3`8zD!Kf|R9O5Afx;TImbfPU8&;_&D1OmlZWBG2PEPDF*$%;yYfZMy!u6j%GZOVm z1C8@?71i_TRjL*0bfeR)9y~3rxkqNy=GT|zIs4zrNf+@rMqCl~{M1kLso~(Wm*8hY zMRUchAf6xQF?WS{G;=5R+_eIthTH}XRl-MD`Iu=e(iz!~+iBpH<)>c}09XW@;*^I^ z4}BlpyL{HZjk~;2gYyCvgNL}i(Gxihb+L3l9xRhc6V(@892Fm2I%6VIo>!xSM_4X+ zZlGi3h)UQ!<1Bs{9-K#WJpSDVMOfT#ek3(rb>4?OpSiQ(Tbm?8sfs!gY63F^rH|NZ zJ(=u#r2JXGM?l%(-5i#ZVOzgo-!Z%TG_ms@m64Y-DUWeGS!Ekyy-s* zji7IB11p2eRexKb@0qEILgv3(7kuv7_I;-ygrcrm1=@$HfRxb966-7tR)HmI#qwjA zuaD!==O8!3N(DB3WNe-{btEF)_lTxy!7cWFCF2Gh3>uauy&_YyV}e4SL{mR^e|tp) z85mqzW@H$x9F2Vy1w~s4)BGo8o&ie9ic4Z?d#q{E&i)v*8|_puQt-PDynuO)4TD4Z z`cppC*H25YtQR^yx|%$@&QG>CR(J`>u$qrdl}rG0JKuaSDp|X+-Xz1pp^DT$K-o37 z75pb(QAFM?)O4QCI;CuY>jNtsJdxO;p<=OGNAnzY;POB=Sl>fw3H;?+#@d7U+05^| zeaCA@vs0fetIuOgm=1}KIK(P75(7)^!;e0S6Yd<>cZ_e>=IuWW_WcLnSc)n_hm0zb z;6oY)-K)q;U;N!)*~y3e?W5lxFBJ6pMiA(zcAYQ#ZU>yV93K&-+kbJ?{TXTq$-Mx3lis##TDwXUYt8_Qkx!C|1r~= zY<*JMG_$^r77>g_rZp3z{QaEUhTO~G&UxOddD9jqC8HN4VF()E<^J5`xfON>D@UJm z`{UDsE+j^KL%T+5r}DY9pW8x0k$OR)C^%jlI) z?xX8uea8}|-!)SNW@0GF75#gJG$dT&@>c@c<#SgvB{IM7S6B3hMgf*zp)wDeBD+8x z*Ra-A4He3S(Yq7&mOyfN`@7~gpMvo5{8fK7lod@9s_m#mBc}>JgwE!@Vxzkk(?_jc z-$72@($}aa;cu5vPr$NQ_tXRLh-Nw-eSbe5f68(_NA!@JmWwAXsTwAv zPV(z6sMiNKFvwmiiw924!GmJ3=&sq-QSVB3xwbS_rf~Ee#SZ>ej$HzmSPSTklKJHG zOtSkef(o4)hDrZl9o;g(GNA&dBN<&JLeHfJ`j*2C{`}34eZ*tyTP|5@eLb}&xofx% z$4wzGkUCxc>l10c-$H9>)!bCoo2%LC-)=6`&Kng;)mn>8Uaketk_&N%^?lIDLzQLo(0O8RQ=yvPCKM%XeSKx6yziq6T3F^frz;oi`H-A$OaqH+QWfLDzE80!dPcpoVrt#Xb$?J}MVl&Uh{n~uHKQLziIWIlV z=R;VCEeC|V@SMPcEJgLVm4^saX=C18i(Z|o{4ySlsMwy%)G-QO!ysXYI(wT$g@%9h z15$FiHR2pw*3pc1$Y!?Og30KY6S&CDWMuJjr!P`IUi?|x#p4Vp<=uQ*jJ3$Ue%XAd ztc%X_d$l0Fz+nuD*|Z=4Kidf>j>&Di=;TJtcua#TDU?o8S{_ejB<4!-`9jh&7Vdd{ z6>aYmgdK7*ay3Mdvr+c(w)s)$|9;?4_BRUL>QAockJLYNt9JtZ-=xotUEW;to1wP{ zgP;{Hhh>=UDrKDtXmO;Vf2!L8Hbz-nVz?P`(C!zwl(2HV#!M*D){A>B*x(h~`{>{% zwFPVt(y5#&K**MY!+!9z3&fkP`K0TwcKRn@b13zrLj)-QTv^L~9$h8XQC8^f#eg@Y zQMw9R4a*Ez%h^;Pz8iAhD8BBy@~9Mcm80PbjXUol0)cl~DQ7pniE^L!g(ncE;BNv= zr&g}8*bOB9z!O4=dHQ5?&qf7QHMQWg;${c!;Q+FJPnZS65OdW~3~oboJ@~Jg6A~tI z#jVS+8rg%D=Je{09r3>>X=Vd7r9mo04Q$2y_6XvQS$zwWs z;ZOj->Ys^K#v86?&5hbK&wieloR8UA)cyQ`^hZOT!gcD?r>D%6@p4{vKx3e<-S*_! zhIyScvoTyx1NI)PaeVcndFI>&8}W|?h}hT=PiFG>*Q=i^k3~^a?g6z9!-2Un51z*u zj{5r-)0lcgBsmpkTOZjDwu;MLEwTHrJoryf4ly~4njf@>JmZmKQaQF<7^ZuyH?d5X zToYUZ7_gD!jv#m@!Q_zpfcJ7p@vfE#b(tmmFY@HN^x1J@K4MNRMHeo_Rk}1Fvb?W& zzFh2#rM_8J(MBfYEdo3y4fgc*x0~Iasp(8_{DL4v$APws818t!tHIc#!aL~_%|<+y zm`=9)pm{pGmOa4n`8nOZ+v)F``nUVlxoq!qNr=yYbRR`Kkc;??F~|+8h{^C%^gt!k zR?9lbRy*y?hd-A0UV{n^aQpfso5>N69a5FzUlpY>oFn_s+M`EYws3Ji??I#Hul6}- zUOxM9mfZ^*17|IUq(SsE+L<^Ev+ocyMGosDd#qD5%0+2WELh)@j-;w5Mn-FN#``6S zls@>e5^K8GP0>LnWvOUN3}OnPlCoXPZF*eTb}DiMzyv6(zt$Hb*A@9u1ELpc%fg-2 z!{!2NDNG@WDkar-zd~FXE5;0P`<45UZtxkM*k}V_goT!z)ltv5*0cRTsCPW8{|w|W zUsf17%EHC@Y{ZpKx-(z5KXPnV3?cFggQzY$!MV*AWEi1CQ*Y9O2_tA&o2iMmZOysg zEFr}{9+#YzLTiCD?wWK}*|Re!us0&bJO==HG<9{yN1fDg7N!P+sZob?Nx+xRr>*Ln zGWvMqEVbU>CU`9n$1*iz+n+b--AtdBl0*yIT@n z3_~BhEtX!Ha0?(1uDf68#X1t-jOX8VX7iO;`dG4N%AOurK13Xj!3 zRJI@MwMId6VXBHuZxT2q*F{@Hy<5yF6USGJR~3k1{R~xq<}9pefLZQQs}dLhpYqmI?mA(_EakA@gRmJ0S* z0*dq>D>w*iDrOi~y zfz_AlY0Y{>V~H<59RhEH!V(7KIQH`woac?$4Tx>82sB@KTm(-|(i4>J=}`@-UOsM< zSA@WU!R+cHfj`}lF4&D0`Y=sSzDa4=gs&NA%=4M^+M2f25U=}owq0}w-hBdQXT;xu z;Yb=|eb$@Xc~O)$NLp$Q=g^qa`Az$S9s0&|D-6qdgZ1HMcZvue=oy&fj|ljPTyws` znzE*TDI;TBbglwUW<1<%E`|`;+4(?s#_m5K_==622Mvr+?vy>KPGz=qJlPBI=E#Q! ziAoeu*%tDg6wSq;Q>;77AxZhvkk18-W)7JtMogKbxs}R{)ffL5gl1}HzFD~P*yS_r zOIAovHZkdavuK39qsQP12~xU6Z528TJVB(>xnxQXIjAN+Vu1RtCHYzYHA%Mt_X-UUf z_MERX_OAOuhmXV5g4^)n`1WWn%hsprFQ?-#o6!wr}D4(YwM}6)s|Hhrecp73k%!yI7RBOYw zzfPH>rRuh{ZLUxN_bQs-*$Eg)!<2LD3MtyS_ze)mju=~8SGKHU~4ibEtSxLXte^}+vO+e zd#`GXg>q-0uDMUN?6FvA6(f+(bYA|8;cPn7gr6SCDTg+Nl1=R}U?Bu|bM2sHA?HeM z?)d7-Qgrpn-C}a0*IVD0Z*`(tFkKAcq)@F#4&AJb-bTdkYp%#S+6ylC`!3vYrAuKh zLTLFnF_8j@-W<1irsV8N_dCuV%k=VLbK{m~w)~2w!^SE6D&Cn{NdFF!>%wr?{1x7m zJ2bJ6hvSEh7&qHS&7d;JlVD}wdFM&I3c$y9!~MbXb{Ny{=KvCYe&?Uz7k=HRb1yVk z{yVW-zti6{w=b)e`Tz7l?h9RkN5^}0KgAV`Vm+4O%Va`?MULHeZc8pC5j2T#TPZxNA99@U9|h+EaBcpr4^ zyp^XEXCtSi_1CDU*=<;gYD6(XD~Bs5LO5)exsB!9wPSRTal&KJT=;waweE3$Y=5Ot zUl^a*t(37`_ru4VW$VG&qN&)j&06Z=(;d4E6*U@?H)EZLCDQ?gW)*QW(fr`zy>Fx8 z3c_7hO65w6DYgPg=6;sa^2IygOMYT~K9?`0ZiHif`@sHHY`Q(&5h0isC{2{I7P}D3 zC25qjdciWw`2%Nj%7x2H3CL0FNT=Uj2t3(Fki(6Nz8$cXT%t!JV1CMM79&)iSho0m zqs?YZ4WCCpm5+eV+pGo;AkxzGuHugXn_X8I^O||dzU!X_m~dOG@=q8Xk2PSwXDQT2VhU={{u1BIPp+#-z9nK#H4CqmHil4UU;7+G#zN zvmS|>Uf4Y*+s*muB|SFamZ8Q7bWM4r>;9rvBIi~%CY4OdE5w*J?{Ki1VcHsNK22+~ zIHk+Dj}YK68E3%O@+|im@EbWf*ApY)cJv}al}kP3dgtIhY{4PG`RwPf!I|>jY=zHT z%|)Gi%5778IHTTc!1}vzkGr<{2kw$Td=DLq-G~0io&J0dR=ALI$$f4?*WyJG#oq?Y ztv?x}W&ZuQn_*)SOg$YGaQ$egUO>&zWjrkgEJA*;-fMO{EL*g*ZGrW+AeIZ&rsSc^ zR}~b=(7hQ(CN+8#Vu$qRWgLuU4ozHPHuspinblp~f{qlX0SZO!eK@c~VvRWM+3#bM z=F2`-k*caX0Wm?<0=`{dv+;-yR&c>MOb?ORmZq@S%sCWI{vf(wn64umMiJ^Sf!YYx zi7wrwP(-oeo7oTz=IjbS3%Akve6Bja84(dKwh+X1ORaFO zREVS5NjxcaT(upqujXBJtPLfGN9{`%<4Lq7^j~jcCM*|l?B`Yr%QDY+>;dA!M1*}p zTfcrHMc|K1pZo~-%{mJ=%%dmiu8!-c6cx6lZ27STG8)!SaUecfZqUkQ@~v}Nj=4=V zoh-znYdd8+;N2wHWE_2%ldowd?9ONBPc`EsuO%?cEhzdcvN#$V(j02A^r!Gc)@5B* z1$q_9R;hVhkW5(T=UWJ*2MV?RgS4y__w zgohwclIG_f^QUF$-NE;jvzZ00pB1dugdn{M&1&X&Yd9v+S@)O>D{IDdmP{N5WLW3R zbzf7C?Q=BnUC9>^J8?cls=reM_sdz6rKl;%E;_b4VR0sUUoZ}1B)ALemuA*y++WqC zh(bp)BvTa@VrGzX`X%+oYG1@X(k0w)Q~U< zph{Xy$^z0bIl+zc#D~0ayg%+_*{;3sCd$W%w1Na*;T1QPsu^8Rnq~2ev)r_jYG=ER zW}?|QBo_YMTxKq7QckJgW}5ab^||}jK8`sjuUZ$TzP%ps=cMK2wY6~{Pj%-+iyhch z79(xKP;NX?N((GNu=KYTC=^vUgcH-St?>Reog-u!31 z`V27@IQPcE{K;qdhOW|C34akeFLZOhqZ!*D(5nadCSKWoS)oz*Vn06&m9_4hno4wC zKo{Ms_%DdhW9EJttD#jrLvkEIzWWj5qs%H_vO_W&sfSto=XLvw+*to>j$;#zTZ4e$ z0lo;7eIYp-fU-vW7+U(*^Ep_0_H`F!lG?}1tIq1ad~_g-_eF<$wf}h{lq?@z)jeME zc9Ia%QsY?ARL|SG(-^IaE!|ouR8@OR5&}>iH9HZ-&^I8C!1H`64%Y0C^%l1j4$45Y zf2ZENr{D8jjv1f)`y0}F+4Mm$6nCe2XSWT*LsZDNRg6?;F(Kg`e-jFiCQDe^KU8F= zUlfWGjY%=NV)G6N`~w-coB99)%3cfSCH5qx}d;18Sc49FZm(!oB0H4O}b^Qy#Mu5zcUo&LVKsMkNpy{1~6-zth6KX zUodsbbX*rjs6SXzmeXD+kJ{&a)qycYE%+r;qN3JaM~yY-UMu`kVB38-PpvZg8Eg(e z!GFAA#x}}lWe-29WZd^X8EKB2&s#t{dD@o0n!3Wr6pPlcq!IZUn6r-B^0~|D-kmDn z(6<3q^J{yF6R*4fQ2cEg^JF0>2xXOp2v0#s$Uu^Q#RkwTjR-;oSo|jaK`s@-0dl^! zx}OsIL2tzd9$S&lLBZ%eQnsl<_O25R_7xS}x&?MD_=0(p|HJRbygV6^rxTM#X(>>& zqy0ot8uv>UvwYlN<|e2ed#(S11Aaw@5&}ghD~EeDD5*-?q7+|KCbhl&8c*TAUUfF0 z9V{&punA-0JtTE_s;Khqg$gWJS+i-pXyr8ah>d^?cz7?oJv3Hl)ztODvg!v>#XtNH zZ}QXkAJpJK>7Pd(;nI2eMoX+Lwf$#uV_Z2%SGjpP&V@zUXf2$7qgm3ANSZa6Q2iMx z5f^b1!$&9t5>hq{3g&DAS1Wz%=XfnNZrjW)=6na|_FwG9&jb+E>QBrcipjei)wkfs zmv)Ev+kd!i-VIC8y#Bows-rgA1i#R_df9xtm!I*oV>dj3ION`ZRTQPr0i1P}WNljq zRdWo>vE;-d15iyh3btKhXuw0B8RHmHEiXtlA(xo&aAq|rlAl)fUKw@-vNS8E7cML=rjnt4vYIKKmcgMDSet-Yt z-ox!U_F((G<2tYNd|l_~pCleu@ij$PdG3NT2^VhW@7L@(LMCKQ9&^3MYiNGUxZ>kN z3zNI_S5d-Mp`&Gi2$@6ZA&!^#n?CAVYX49JzkdV6F@(4Tgvw3an70Nm$ahBOH_x8D zvRnnf7)bvXxh@jo#g$F>a{NR*V2kl4)v3*0)G0vj1U;Vdjt&ZE$nXpDv;F)2R)GJ% zc{_J+cy~ z5#>sfb`f>^>nS@&k#28PY4VlHohmf@lgST~`n=z!46q3-jOl+Z70uK|Ma_1EMDzVG z&}c?+c`4@&ugdKiL*2!xF8H*ITd?SN4R)0}OP=smp7d3|3Uji6=JcuAY^1i#!;9kQ zTzk#yWO6wZ(99de$1i5j;!GVk*j3SS7x~SzD1i% zg!^{|HgA*+hT>Zskq@`K`)D-0<(=PA({`!4R8To#?BH)yR$6lk>?^rKD%G8s^Ghu+ z)ut7E|C8W!IPWzbQ73u@f5lD5w9(aM8E^9!PYKi)qs&j@&rrB+{!FwSHBRZDr|2HA zGduqa!ws@AVhG(k^HdhZ3QhC389W zGGM0HX8k6mOQgAWFTHbi{jvq_`tc6O8EMFn*0gQ6zZt!^-^h*S5lWYmu&27N$w5ZD ziQOSABkhF@PF4E7M-9Sl@62i3M9|{`{WDaV^8U!lKbQm#ldJuL7Jy}&Ky3D>vo~T4 z9sAXNl!_CC&U4kXB+dn)8dZ1ocLUu}zz!%pUT?}il`6Us`QCi>yOF2)YJkeka_Am4 zFKm{E=s83sJ7ccb?=JTiJ#{eI;o7B_7&QFW3BuDZ8mjEULwedkeS>_qh>?&I7 zP)``=I_Ju;rL?Tza%ZUWumk1Nk*`w@>y*I?f#(XS{1v`p(*l;)d!Dgp)^`|It@RK6 z+mICmg`LNNsMSpEl}(Fh$;|cm`Zh9fx~=W|RgM!*nIsqOhs}bchh2{edyq)P(QQ9! z?8nSXiF@a@S1qEk@Cg*bYUIDFJ-O8OO@x9BW!+)=0-N`2%vw)tK#_cI;QSUN;dme^ zM5C&q9M7b`emCw-vPE4shDHApxO=1KuMNRn)<*H*WzW%Q^}lP#3GB>L!+(p)>F2+S z`AK%4ZbhETMx;ROASv!SiA+kNV;R5i1-lE0SfhQk{HwQJtF{Ry?>3`?HC!*^NUb0h zEy?JD=>rObO%{z*qchbNrIgE3KwiR%GQS4eYAEkZXnph^?T$&k+AK`uEIH-jB`s43 z!Zd7Ay;uqo4(t4Gl_?)tK=QGju2|pPbrv^0`whH3I;C3Xb&(P!si>%t_d%^G!rJ?i z*bMvyn6_~K&;_2vTemt2^%&dh;V3h@s5~r|Yj>q;yYHjRyB-lqNBYhW*O{7lOt{eT zJHKmN|M>*GU&uVxM%NPfeHf&4D1*CK5J@)|Q{unxmmuogf(|^%`G5+fcWEZeZZH(Y z`>I6;#WL+;MDBk~C6+fpjisd2>NCP%@|M&I71Wx~0c}<}k9rrrELekO4PWGTc?eND zz1!HZA)S(Gt~Tq_;1Y5x#SdU!zO3{Bu3HM`6FwP)h5g$nKYZ2nl7OAr&AW8VPO@=U zeA~@w95gK2?(KO!9=pem_2;X4LL!CjcwN@ytfzt!PX>#GHFr*o;NAK2PsI8QYA_i$%~CuE z?JAnN(qOXnciiklVB4|7z8fM22wAG^;1olFEt<@+WEcEmu9aHxHuvrGy1!v8 zyA@cVk|QIOQus)7ln5yRlPQViB<;?R6Nglp${?9>O{KQVa|^ow9c#ldi8wm4XV3IG zYU=ZX2WjtQ)JAMZM`l^n**b9?yJ(gO=|wYc3xI8;ud}wh@zYCEpiDE~wz^9n(LYum z;A|nMnC1<^cQ!m;m0v}+BNQo5Vd*!z)9LW;sR8g>RLs;9KErM3<%Td;Z3Jd5HQtP? z=G`E8{o3L!^W8}A1Lc2Dc<%q8fec{wF;_(aLW??Wo3e~Qm;P(=?G6mcH*f{Fo<1-W z>%dpm7nJg0MDFAnej+GQVT*#4XbD^@%XySM?UC5MhnzE3?w6u`cKl?mbfKsX@;8HEav-8W{eSMsq z{ZLMOTMF{|N%s#(nGYBrop;1>V?20z{-T)1+~nju7bSLSN$IBeeM*%!|DvGPZoKGY zXzVtBVg6mDd6$Y9x;69@F#A#op@dYLX-@TB8V=r`O2uGB|IDLh54O98u1DY3%QSE1 z5(Ud>Sefuj_NA3&sTnCIfGVvwUzO;_V@hQ{(TLGCB^15&G67d3fxYzEbhmvpQCk~G z=FcO=UnY+)U!cc^{eQ|q_cQh_Y|%G?3avTt*D{3ZANutVUNvp{=H~poN~N~Yc^Q@R z-5?rSRIcj2!bmuLhm!fwxxS+;`@=S#Xg}3`SwTj~Nvw3KC*OF*o#W%5(HF5b9B&Na zS#$Wp$20EGf_Bh3Yn$h}=EVp~ED;Yi0Uo<=T7gCDw2$S?(+r|uA+$F`4pz~@%;U7= zw34EzeTj=5a!c)yhKIlY{b?o_v2Z$hQGaK4@!N`)-6jsY68GA5HlMMa6``!@)~2AN zw5#^IgSnpcIOZ98-?K=|o&W9^!NAh^4?8VzJ;#@k!C4kb*>(p=z9SxYP<^=ys4b+M zhD>_jEu6G$VHO9*RAsDpvF0;M&c>_nx?Ts#&dEta^QWKHXeL1gK~QT`1{Che_EN-u z$6XaVVUU4#c7)^2l^&CKpX&LeogM?gx8Jo@4k(Z52}P4gv6emv_C$PBa3y3UkxK=Z z9`9BEVAfY;LIY2Oxk433vGay`%FZ%Y(1V*g*x#z%#_S>Q@!}Qh95-jAU zV4drcO`RO@V*V~7+v>>UC_l74OA&2^(A?ExM%^7?x^5@TjK;F476%8o5pn%Gdf|OH z4wz^rwXv&JXgTdWP&&R_=J!VAVDj&O9RyF=43^nxjR4%w$S#iP5N63GmofKb<5|R& zkov)y4gc2x*9!nQbgaBUPM+knCwuU13+F_b&2Xp;y~^0^9}EV-MwarxAC*p3?)m4O z-YHz;)rGN`7U_ve!iRMeJFDSzN1lx|*!~Zn^W#_HT!5qI{mLEMo3|=t0EYi`X%pD( z{GjW7tRFXl|M_KVnQyBezaT$p{YyKf8rB=WF+FUCDeAyRp?aE)Tm{aUDdbL%!=nK& z1@bpGEWcG4t&bs3ipVk1|BE6!fxp$WF+X@O6hBSI^<00F+hpuBI+ACfX*yZRv7D^C9A((*6C7~IUH$zY=T-L)|uJ_=GzC1BbX1lBVvY1Y&i8KJ}r; zuv}UDcAh1VGv$lvC*1D~iD!mKW5Uta>$dO)(JPwCmdZv(HP{D_fDM#9v@B;lnIBOTfWMd+WpFk@~<2y$)c?f*h_rg zvwB+z;JM?-4IV}+%qRyy8Vhz4OESN{9CK)n~y6v#(;*i`^v*bDL{kf4+e4!jh^y3Y0 zhOiH_BX-Up?w^Aw<;_XXp{MrFQ>^Bj0Kw5Qt&$Hy+c{X+_2^-ba#kA+W>)Ls4F$KP z)knX}~4tz0VXh61jqG5qN}b)1Yt`)bkuyY z%y=}ixatskR{dswnl{v}gkCb_3l1xFaA0vBOvvggv!^UdJ|t-F1B(E~_e_I2N|Tua zayW~Fwho^wb%+3x3_H?|b?-ZtG{e(J7mwGkYM|tRXHHd*MRe#}j{;}el1;v%j)&XM z4{h9gZmT{I?7qYGTaBf*Qd$;p$EMV!j2=Z#qOB|Zi=vfB^F3zjmIN=fswxNLKH5Aj zx;N?=414xt;MyZSXyTa$bJe(v3g>H}oQLc1T9&vH5yHwGgA`8A`Xuek`_dO_2leB- zO7%o`wJc1kC7hw5*in#0ue~*&-Kv#P&|m1v7;mW%^p^Nm4uY50KL*!1yAq&wjFo%j9i8&|j- zv~XG5W_NVOKk5k4jazqgSRd-WDiY#*=;2WKpIL z0`fK&l`cZ#9$rn)y4~e+su^?UzkJlfsVp+U``5*JUiZa}=+8HJ8oX~o7n?9CjQjw= z?fVr`7l2nlf(zxW@<89+_O{J+2R_=%LG5wijx)7%0qj&0g^@er|Eq9rU7;XVI-_^F z_A5@h^PZ#h)+||T=uN`hY`}CDcPsbj-J6DIg8ja{15P-5?AzC${Ir(gX|PumD&Rd~ z#-22-l#aQd1*T&VGVH{*bysjL8kDMdiu;zb`?rZ&H> z{owgW>Hg6$pSw)rKs{qWtu|qHrNon6plFPKPEwlY?~$7?obe9*j7_cxu$^Bp@6!k! z!rn|a#C+xAZ=em=U11$Ll~BZIg}IyGaV#5NhU&o|yj)Hn0-Z{kdo2+0_RVQl#4Exx)`++ND9L)IcCP0Kxz7f&#oaIOkty~4lBxyG_II-VqPvHYxt1S zEiC1tn!o#Xw~vW;stf^y#S}&@zVFcnG>kp}y_l%ajeKnZBfE87ERaO@UBhR&0bhX0 zVmEihR&eaNJhV_nzI6WwG*)vP4W8kqmWj6aW2RIT7gO)6cfgeW8y{!AMvtxJbvY$f z@CrNrz##KKuNqzr@^8aHxp2Sf_w>FNbd_Xf3=`6GZ{l#CgUi8z$SHL{$xj8kGf2Fw!tg94~&%uD$^tPG*u`uXvsPxqLdA%E?&qb3` zkbYKpaxl2#@%YU|dBfj-FdC~6<)UB$3=<~Tq7R;@oSA0{OP{DTGKPI}>!At8b$*T_ z>Xjy*%0TKY-kcBNV_epFHSvQeqetT)mN%k`8EdbRSN7K;VD-h%AAQ8-7P783&?1U3 zp*ly+534h2Q72;uP-~SsMWGq97|-VGWDOEL58F6*|AE~PgejC)oWA);>gH%Vuh8YR z+IVycmbxPjL9|{c366u>13#AyV_F1mDxv0q6Gm`!ew@Rjr43cuED6PvRLtYYQ#uKv{p<~;xGp~s3SD*Iy* zLgKD>iA5N?O-#uG4-TD-uj)NVEnmZQe*|+T8`sH4sfC5gplCy>BbD^8cY3KDaHgR+ zQ_Tk)HzJ{1@FN2l(zi_tPnzsc^fH**+Urw6FF`~}X4q>o z!X;xP9wi7~w~|_(o$IC-d71v%Q(-~o=sptcU<=Y>vhxHy={IWvOsV*@*?C6roF}9wH~(bj9NBPC~d8;>j5%qY(_~ z%dD%|-70)EZAqX|G`2M(=e7e;XS%8}zQ4BL7gw*SJE7R_)y%kP1ms>m}ByOm3h`we5e0#3_{&BF~`w&JTLc>uIRscEt6QI{Uedx<9eG84BFUJ80eB)TS2q zlHxv~MClvVQGPh>4}F$cAdkJSGPT)i@zA_)McDR8ffZo~e5gVxkX5Ta0duARPl|bi z5hpTs(Q>S5M%q{$pdwC5v~>59mMMA-xCN5=@Nac|AR~LRf;ea$ANO}2?wA1i!YbD2 ziB)RT$+Nv9d4eC8Dm3o1$7pMeFdjC=xIK4k^Xd7NwOQNgSLV|4{CSjx&wUA+63jC$ z9&u5CJK>5eMRxH^;FYMakTw$_!wKdmpDZWrv~t5>v_;iG1NK-gO=zTt3-0uY~Hl*0H*+vZ9$5@uCu&ZF{93ku)>rE09@H&2n5# zVc2r4ymLObV~MM$l|jINOGLNps`FDb(k5PpQX`#73(QoSjz{_jW1|5w6d5`*!jkl; z4*b(l4QXQuW{$_5{-(Cm{3z)YdqDtK?|$s;F;F+%i$m{MUiIIm`?8sjxJL!*3l2(y zpsLCr%kTGhj{!d4jA4Efhfd2Tu9LwiSr_isXycn6VmGXx3)J;|%7|$~{x9oBCD}54?SA|$s!-?8jX$k-{76(ZmziR|P zx_O>~NI(_7iEE?8S#G=^wRcrD4`AZ_q_-SL&goh8r7}PUYhsND-&Pzp}D1H;#v%5UB&T@&O+tD zJ!w5%#Le2Z!aCA*F?@x2f!jM*U>H9Eb{1q>|#P-g|Qy+Ph$ikNxu1+Y>4tU zAGwxZJpr{echCKZ?R`~ucej<{&0BB9VG~{5-;Y=!C!o>~K_hHo#uEX{z5s?Vxh#h^ zAds((e_IC^ybgMsA@2Hks%W0NsHGp*Ux|ZQ zDot4SacbV#RboM^He3rO&I~|TkvFtP{<^*a54SEWXkb|Yt#t>+yy_7iO5AC0Vgf$~ z-m{*BaVNdK4O+0y^(dja4g*d=S4aTi5_Hb?3>)GJi-Id{|wPMu5^f&G&Rr?qh)E4OHZ^qJu%?NH7q6>S#a+#!KhB1r2Fi>5vA zRSQS(-sHEP9MC|}{C@SNE4PtsOy11pc%mFAe;6-g=Xpq&Hk1p}?z`1kw=1y@h8|N> zG@a1i&(oFqEK|KVGj&HOBd+ePCSx;4lSrMMplGE9>3hfZzDM$p=1QitAq|XJ9ChAT z;93}tTGjI2IMBb?qk+#1i49ctv4H@b&hjIO>7|Q_Q8MDGjv%=r4+v$y^gp{OK z(Q0vnKlSxOUIgG32k4jQ|s@fzjlEy#Pqzc_?`ScV&!JR z)}3PEH#TRhvO%j_AZFB5#QKyokdF`gi`k_9`+Ng@;<~_M?w9W7HWMR2l$E=vRyL4r@l%es!!m-_v=mkqA>3}Q@vyt z>X5kl<3(Tb2H^qm_Alzi&~}ivQSzq5zw!^wAM*8M$xS*GyzolbOCOAdSZr(V*Y*_b zNyvUKeGv4$isVlV2x)Kf-eSih3q{xV&4EF7WPJrVs9ynSV<>@I5s1tHr$UKq4_hW5 zA*KVvn|exY?{O4kQZ0SeBPHgxWr27-^VP`H#Pi{ZyT`ZK-v_*pW_af51CZ6>Q1 z`+_0wHpG8h z0HARqwewi4tV06_ip}A$kE*`+_TKl$ioAAmw(q0dkux3>I&uf{a0I;T;(F~Kefe;S`y<-;2Q^A42H;Jeajq&jTH+HIC)2ki5kN2Qw3>af^(30NW8-g3*m!L2 zd=hh36;_-7_R8+H4^pr~zoupn+iXxtc>UG-@ zF)wMZ(L(e^X3iTwcAPDf0dTi^R%9@nk(jk|AhU!!c~=-_{Th$SEu+a|0Mg5KtKySN zW@J-n@TknV2(tQXjb#0O^An}yN3PE}&y)}iZf=|PCQepsauKv5Mx z?3)g|sFx-<*uf9aJN!;zTzEeu@a-F~zwNE_`Bgi*qrmmhNRlx!z<2K6DW9Y8lI`mr zQ)+BaLuw^$3ILb?iQ8N6it?BVU*nqxY=8axoc;#PS3{joTK+`+R)NH)b+BQx#Kkq% z_M6Kv`J+}9?g4&H4VsbJRyFO!*Tvs|o4q>{C8HN=V*s4H3*-9_yPRj6?;;yJgY=Rs zdBE@y=A&@TNydWG4dc}>;=k{Y`(_h#I5)KtoN+W{D&CZyRg1|k-y`C4ll*V+sUJQDBxHF~Yo9qnERkaUaMA$00++YW$M_&@vcVA{{sp<^ zm872l@ZGwNJSKJhjQHQEU@-yr9pz<#qj2bp=$2z=5hksd zS1d>-A=3VcC(4Dqyi-{xv$J7!&d{=xE6`6L2oY~+j86k^9}a}9PG?KQSC+qx+e`;- z*Pm>)k8^m4siIEOJtj=r5l_Yac60w}$INZUnlqyvbrWEpu}YK!zWnWUv7Xy}Gpc#h)oKeHw$MbYmXS}~&`%7`ldt_xkvfa61#K$&5xBd+- z6@dKffqfXMf>Lx(Tuve38bI&Tz?4>s_G@L&G1PitZ+wD*2*Bnk7~7F@q}Pf zy1!94jaxG3{E{Yodaine0}(LqP4=%(GKiFN+44axAJ5u!1q4(O zJ=Yi7JXH`f=Yklz&3onIBMmp4`dtgp+9`OKSW$;|5p2NocPmSWL2^xZ+$s$0+oMPeXy5e9(5p{)0}A>1dHvqhpmh30=TvvFD_BOT(_S4k4b=xxtjnexGusrzO$Y67kE3&PG480iuAP$w)%;u3IYG>l zo6Ec>yke&;`nVX#haxLhI&HUu{e?ZxY&!^nZm@s9;GnSOnxh=t$;6P`r z1aiqidc`Ilj}t2gAGS4mQdj0Ebqb!i4a4k}A@p|eOgqx>-0l}&pj@Vu;5MGLysiw!lHxa z_I}I#4P_e-q=CuV_l)B2zS+EjB@K27k+C9V-ROg2|ya!KGk^5B$nO?&AH1GVvZ+^tX=H*d z)gUIJR8sXAn=wFp!E=F2vT@SnFTe`VO85Grvf)w;CY)z3Ybngmn3H%?(BtAtNzUgp zPMf6M{NB+=sMO3dk3;YIYdN~Se6z4I1O%1wT@0yU@nR=0KNx(tU{gwX(R?{+Ncg-Z z4A;E@S{@OqniYW6cA1)L6?eX@NUSoOO^ceRW70JdU!$LR?@Dl42|a%G-wugx<4eEI za69iT3Vh8*xRSoaoKr{W`d}P6K&D}gou!|4s1^CWd99!jf%&^t`w!EvllS^wKYP^2 z#D*x>HdfT$(iXkKq!J5G67hHjI^dD%90yk3^jscL24V`cJ+Ce$B?`Iugzr^`v8h*1 zNOX}F+8%|;8%ffOGd`y?+|+Ld;vbX<)BX5@J=&GJN<{`Z*SG0Kb=33L-sfrC2maB zD<}gGp03p;ZB?ICIEu2dItEyh%NOc#Pu*>}D9mVhB!;Ow+t9dd@BfJ{^ZtCQ9;F}i z{2TCeDKc?FoB<+AZ~gIlZG*Dxv;IK_WA{k9s>bfkd&B?{me&#IyKR$kFy#Xru;Rj2 zbn5m~pqHE0$guw$*nh;n*?i;6CqV(wU?aC>$vm=};@4XT;+= z)x5u3gWj|S9!?9h6h6=Xb*%MS+k&H~_QYDn!*=5rMyOr}?&UcBJG;yT+N7fL$o+BQ zZzAeMt`v1+1O|HjqN3?=!SMrs+Bn6204$D=vGS?S-y;R|)xTH8a~& zeT)#(FDa`xS*=4VO{A^lfxch13%<0Ntw>HF&m8LPHx2HFD^t;JLMWt-TQmdj;Z_b@ z7)hlm{?o^Y3%Byq|7|(H!@fmC*13OjIl|7?C*a5|8mn!QnC^m`GR1l9mx`*2A9{kr?U9gv zM9Ot|Pm}eLEX+bGG8pS1b7Vq()1=k_8FP60>&D2X{vwq7_qgA6|=dyn7m`cHSZn}HRL$@gfpWCy$(dd$KbU>bzDl2yX zg^JI~iq$!~cmET?4S{gq^HqSFVz)p@f~DIyKw*9+4x|g^_%a6dw{q9bnD^L?@_GaV z^cLgWyvce$q17Hm?2?^}$i??Kv>{6(?MQS}*88TG4~*7iF6Niu6Lwgi2!&s*c+7zR z={943343JP-z(~jN7(iLXYECcnqA$KWvYZnf>aC5eUmu(b$XRQ&=X93HSH&Ur}3u% z>*RIja-Y@=dM>?b(C8lgrl9GGQ5iL#aQf~MO`xkyb?F3OySER3YwK5ADbgj14wOpn zXyo`q@d>tZeRpdp<{rs_M7onPSF8D&NL8kNJ4dbE9&nm&2>n2;#90S1b-6Llky_De z{H0n)^tYADis*qRIh-p}XKq?wSr>;^VAG&i)+dncs~5oX>437IlA|@@vxt@V({%TBypR22gGj7(*W-zUJ^7)te)OsWKXLJt<#7VpdDUu$pzfwIxuu(Biae`}3Oh^8=4=cVd84fAdv2MUk%xwRE&{1?7Uzt3UT&pBsdL(cx$bhT% znBZ0&>~#i0vscrdsit}p1*Hy=vh;P`LrN3-CF3AQC6V#EMp!e!BiEPyB7J|hwH%JI zWu+Lo2e&M?7TPiNF@Q`bN+4KwMN@2tOCEG3a;2cQVot9?8Juy@Q6* zK$f(0?GOXs+?kJ}6eMhHk{TBJMT|}=wR0(plp5gi+LpGRA=t*^E_LE*i93MHU|zXQ zDD9~?8gMy678$XXU3$!Fp|7*Z=YC*iom_o98)SSp4C)f)ctTq}%sRiCq9^cZWkZ9?4nP4zo zMNeF-QYOrXN<+GNuM*VH&ouh4BZ+>A1oQ7HubGlo4GWj?J}IiMCRiE2;B{N;-sX}i zc{hB#Ld;j(RI_(b&yQRsV~B+Rfva_EOplDprT*|6mL5qJtc)3*92*3$H5;&7tbI7|+0(xQ zJZX22$TDW9zTB+Uon;xvj84cVtQj*@IaT!XcGv2xMi+`rSJMcfS#(VSx{E-5lL^!J z0$4)kY-_5tI?cd7YDYR@bF zqz}B@@uhUDa$0JOf!IpM1svk1`&m2czJ7Uhb^By@swEfFBH?J46;(&yRz5TFkSjsd zbSp%&Q#C9YwMYCmz@M$n={P8(&*jHB0=fOGH0Lk#{Wr+H#3g#3>$R-OVOl6G)lys$ zx_s)9p_bm)F_SA3?T^A4ZrPzVSg4%`53(t=aICFPA7{#N!yl-o6!|dzZ-QhVpFEQ= zP5h&Y*J6zRB|;P52vNMkwsmJr_AlR@yOI|wSQ<4|7m>bXb0fYxt^7n{vjv$zq|;{4 zIO!7s`r-i3FdoToQ%6hh@41Fl*l2WHZ)5@QegX+il7OvqEChBgTAS*(tKLajB!!$m z&Vp^&wx%?bcY)`7_;(liqEJBm%MkG?@-tB>wOS0yXkv}JUk`5!_ECu|FrQGLgYTB} z*PQlR^Ymah!Jn}KCiY(1{xex&wb!^TXVir? zhOk^D5XDg5^<}4xSU*-@2EDEB-^6+bF7D8xNhXZ$_Y+urUwguaNS!-{Y~q0Aaz;r%x&zgUEZ*lGz>B^vXc2mko=HrpEnhMCi0{AE$ba9x zZw1l3DhPHEN)X6tHTOP7AJgk~x01x*3xKWcWe{b^|NiicfG>Kf6~hp|Y6oT5R*7q4Kz zL1UmiJtw2e%~8WXZRv6jW;hx(7FQ6YUv6?*_+4lN-UEH&`StqxGD)<|A^`Z5O;~_T zR%CdSOe*&D4y)8GzVpq@s;K(pbo3|Z84bBrG<80@fwC7d^HMM3YNE11>cIRum*i%a z#&G}mH_Aa361|ZfLInHm8Omf{K#;qe`BuIiSIsI2FNhOg~Q zL+;Lscq`3b);HfjmbhVR>uKR!IONpNureuIXB&_kGJFk7d*|f4^tSU51@;)X@d1w= zBV~=GvBoNMm*SQ^W&jgWz$Mpv29)$U)HA|+=4YW!fsdP(>PbWJTgc3@SKw9C#Ve7m z1o$YAYR<<#G32r9?6Jg0X+b(Ptqhg=uO|POa&6SnuUOnhpQchCttd(ZPef83JPz>T z9&sT=*|yF+*{QFf>^?|y$VrXYv$EtyMiaVO0%|Atj+(XpY+;V^ap@uR9$df;pKshCWK;`2M( z`;5?L9qgn9lF~w<)xx8D*^qZ+BHP9rB^bEqF@oZH9Bn(!DE|ZPHgRmUx@bXnW^m;R z7?^2(%PBuleP&r0JduTWwf}glBbKEdw(#+O3j%EQEa9+HA~@80>bc{~J~ZP{BZYxY zz)Q0>n@}HT>9n&q(RHu)D;bUJK8_}txcb)jxm3VIrCe{rn^%Jxto3U3er%*Ubi%#y zI%Ua=(^k9uv~R77Fjvp&J(zoNt8;zHHN&^UCZ;~SDy{c=@slF$c9UT9k=zqhj6Xv5 z#|!L9(9Abh0n!*<20Fi(Da8FdWyV#_d{0@<}3_NbRmjq}1EC-qFC~$jV1$)Cr^CfR?czx4tYF2O4qDWkic) zmw?F+HK|{-z5K~zCrr$2E3NwWkZsa^!`%!Fcc7K@pmxKxbN}sL2mcmOWDaiwdIa2% z*F7|yaV^_?uPlIYBly}*0!S0=d&lZ7?>$PU94hnXSd3MnD+sCG1fBo^vIlem=>bh{ z-{!k2k+#2M@xD640wY!FN@yftEQcqN;ybsw;>N!KXb~pwSg+>GiBFhT{hdDjuEk>^GS2E2{)(>9EmqhxW7Bfq`;e?0A6x*FW zPIrdu@|)GViVJ_^qhDFUNM^<$#eAJCqao{N~Dau{QK=VNQGfJ9015 zX>^JJ@-|1B8?gnR-)}jF&;aVm#qUvw;;oT>?EICn9e;S}Axs>Kpk3!zd;m5;=()o7b0Z2Z(*3(|sy)wx7FQ@f!KR5s- zwuC0*jM)Uxo&O^rjJYt)q{IKJKijFgrj|0)$_dA%BC~5*8?Y&bKxTu| zkOQLE84AbqHma0-%p-UwXfxJ03i*I4ZRZ0+XgIF=ggW`@?`L?B)=#W5 zZ0^4gZz~4kTFA{q0q*68jNRQmaZ}uQyquq$Xe8Ek%&O!2RR&zX02w@y#~3o*Z>sb(x~Hq2o&nrDYt*_Fav!5I6OvQe= z{=8hzg$=_$Fiw&pX^*u&nPTHj4J=FbtaQXW+uj3U*mo3OMF2D{wLhi|bV5jKZXHhB zR$>5rNqsB)+P1;p*E=lv-@30=x;a%s`9 zgUrdyKdAvs&BdL0?oVhbUVrS%`lxGN1R(ZG|JvD%Uk)wdRN#NTN}C**&Ez3+mp?$r zT%0@RhC5Hlf+}IN+Gv%sd>g)mJ;^{VX3g4mo>+)BXsla>2h0bPExwDhI1CW309AjV z52YLjF0?ujDc{cfly1*87Kj8pv{xnP+^#<1-<@o;LX`uQxR-CVfWLi1ra8f?gtIx8 z%SA1K3+(x;CoSXlATjiyOumdO)JoiFt}2q0E8EcMHK!uA@5Pv$rc)uI#6>>-n6b|s z_is+2Q?tzhJdU5(yy(SXJ;jbO#luJgf})9s3``hjS7Jjj*-Ab-%?gK6NIr<0687xUHgx1>=^X#%&qKzF@-PfTQC{ z>zm`w2ai~ISDeM&=M2IX1WbyA2J+zJ;Hp%yQ^*>dld6xS2-Zt|`{NB0#$H`Ql4x&O z(Um6=4dz;xl&Mit+OPUqaH3AS6DDB(GA^}N1Xd3K~VYsjN_obuI!!;_n4lKX$D zObO#c3m88BP-F|K*0fu0EHlyj`ek+S;!hAQ1o7$0)>nlLChM~+=XKwSp&~)n&InmA zJMEezr>=Hu;DU?44yle{4izWExTUPVj|GZVZubS10VF`bFEriB2(@k(7aIvtH|fV% zAS&3=!*VY6^<^u){vkEb!}rI=2yco)b61Y(3}gD@|2uw6L6oNWiAwl-E|jd6iF zE1`YFrBu4znm;%Q2Z_LnEZSHxNuv?r2Ik!jLZlJ6wlznhDKDMCB1BWb#kdymPpEJb<#=kii9-k#NmEa6y``zbC;<)DxKSs@?Twg zcf>>I$^BUEDVg7GN7*~TRdpPp6)joE@cSkI`@DQB>LODLAWiDgSaid#?hlYVx{p20|G@FzD!nhR1f(sVrVJm%;y($ zH|@f5ZK#O;!@aH%W3tktRv-64iS-40(VPp;1mEZ$O120?rLu2IE%0p}`!!;O;OZkI&A6A6OO{H4?;#V`!pM} zk;_urEUr4*y?Ki~b+6iA%QqthJ=sO&itAFr@~!+GFrOm*Xhr5{s*U+O^pEuQPP`v% zs+p8u8f0!vwwtZ)R6_+==ufM!b7kwUaX3P7DN+Jd+&OCK^^2Hkv27RIoe0B1B*Q{$ zTPI66DV+f&_+foIHog+PZ98j}msq&IfLR_^C@!B!Us+El(@LC}Qnt)4!BESz?2Wle zbr;1@Y;8ZLVCPGvsl7XjaDtXZy{9eQ@zyE#s|H^zsi!mMEu3wEGVj`PvFSOjtDmo9 z+lawb=?rXtatcuLXiqm{Uv|w@&8Jzu=qR!BRu67P9z0JP0p|8E>HmcdT!mF@S7ODb zL*;f1-udF{xtg1l|4hySqGz!ZgIJjGGD$LPXiXAeSvH#M1_N+guAB2eap{qwsY|6p z$%kw0h2F@pn=E`If`Ae3cD}5{I!Ciw(paeTS-+Rva16f)3*fo34?9sn+GT(Be+I-)FzD3c!zTwJn zp2N6X&Gu(dH;a?`yZhfpPYXN6S3S!1FJB;C-Y21k!Z^`>fgh{RzbnQ27}0-mKj?1+ zxyKj1w|ZD!`-&~I@7t!f$yOp)w3VL=;TK4TT)h(MDVirdF_F0&CjYw`|7cEaxh4ZJd zW71e8ie7sg<)^V=(LBhvRn=mh4Q~DwdXpxr{D5al5YAfmjf;VO%9}dr7h3a?IaK~4D3TaJtzka3G z9vGXvduw=L*ls3RY~v8N&h^z*mfQ+vWSkU7sDmHOqh}`ViKr7Fq=m{}TG$U<%730s z-|rcX1<}&CX(t&vET|}HezvCd#2rju51?p9p$5qLAMca%XLWCQgG9G7AWP(0HZ|s# zT}$>qI{!@R+Dv6EPPc)|S~fXp8 z3-f`taE=oQFow=9Fx@8UvLZwB z==)Ky9UlDKT5Qx()RIyIX?^mnRhlBauYS)@Cwt~~0Z_4oYBkLGYt7vkNc;VbFEuB87WA6H>$We-H?Xv0E}Fu$ zJdJv@QlBwbeD_?yGC@V>{1H5zI$OUNQXgSPkk3n_wJ;J?G6I;dS&K_DPy>fvFT)i} z%hx-e&x1?tPSGflBe~I?T8T>LoBg7C#c!-LwKx8}E}s-5v+)=5eebkOLyu{G!n4e* zER<+@4D@t-{y3jWHJ?2kkC;MBsTJfICHJe1o;5b$jvLqpIB@XJz+j_UrX?Z3!1-N? zyte|4xwPY$EQ(#tlixS2SJsJEE?{3|IsYX^^_74wRntP!pI7OGFyFY<<76BNpnA{q zOKz*U4B^7(mY#F|zQ4$Q?q_e3vGiHpeZjI^us$1+1;-siMDNRyzd%BWNoMy)#ab^boAyY^<*(8CO$w3tw_}6) z{ZcAhrSO007~*fPZHKE=W=G=Nl1$N0_*M=*FLB*PI%KLt<19%ykFc#E&}724B#@S* z?stLGBR`oKq{U9JTP(tYoA>8m0xoc@+q0pTH#E^7@HFE-f70NL=0091(|_9*Cg$AM zLab)xbjtSjtL8@eT`#zpB9D!W6mjS7R?Dh9@66fPL{@PlUAQ~E_s21Ef|5rdx*ml* zQv$cQA8)!>{J>HkN)4x|VUM{k{erz+R7*YU{HO*9>w|DP&_=n!_EnsCgvS-AD1fq)&&7trNI9`&6kCwweJqb|SZd z1q#Vl{T0(b#)5YL&fLw0_+PgN`L^1{JJ!*o+C_YIDqxX6LQ9I2ZDXqt5nHG!us0jS7-@LkgPF|_TdR0VqF@1;XoMyhu zj#g7abDoc}-c%xP`HcOz`ZJO-6HB|_2D#e2bA8^<4U@uqyf4wm9-M}UYg3VHTD6p+ z>?&!wPrEh;?OShHo>vt@W;K5vksm_&V^#h&HSFJQi5e=ub>$}Kz8q!OKdwFR3l8wmKb*kud%7@FjV&j7OYs15PY{;aPL? z_=3;Z;P_OYD%js=du#e!{d)+GjPU+qgRlCo|9Je-_R01%{#5q+rP`50I^|~p;=d1Y z8RY1oYT<4kio-7(eG()(Nx^?hl(0sdKtLjVZ#n-}tI`oHy*UWn7))EMFs|9w#aWu_0yJI~RQVJjKQuN0p6*Fr2JF{%hKm=wam~=G zS?7cGp&vRf%c7DJHXnZpRQa@Q@pj$me)%^Na3S4cwCu~h(fmnlA_MUGE0rMSdSU2U z2Il4~({IjWw)@$D2gBKf5Y>j)+^utEsCZ!M#vYI(2x8l6a8AhZhYDNPSrDwzf0i%L zRU7%oBSzNuCR9Dy^0QCS?`@NcAmKu%nq=i$M1h6!B{D*RbZH&UB6BGI+CdaXR?UcX zB^sn;y8RpyI#oxdpUB0oLBOppeAHPw4)vW*>Do)KKSV_y)#~}jwBiCmlGnvz*3#F3oa^ z62A1DK2`kHXn=KR{bcyEXc+2V5?-lBdAdjlw-?ea^E)`DlykmLLWsTR`R0nG#@>+? zntF?P{knNq!Y40!>=-IE!#h|>*&sf5>}a-@*Gy3)QV0h{zJ)X4N%rK4@{Zcp-xId! zc;L)oG)Zt8JN3V&){qiV+`5?F-@{oQ7pX z@Aus}aoowVQv6{=dU3jq^s`NUCm*QocYbQb*+E6jr`*v9PlBMOtheT3fNB7$UhISB zUEKW{@$=Bc!_ohTY~S%RH4`U(svke>-qB2ULSlmcGWt4Sqb~XoI$lhbqx*j6wCcJu z*V}#sZCP~@-Vwhe5X{gRxVKa}t(XGIxq!R`a1QBHeD)twn0HnkRv|M#Yaiwk9O?aF z^59!eV<3;&p6x!Kq_?&h41Mi)t{@X5&%rx-Fq*}7kM<8hwu?quTiD4QZ_!T)kbJ{6`c(9OQZ3p z!O`zHn(mj|vb@pWoNHu=#bv6Up3bx82ZcH&kZ05PR({chXpP|BAL(>1&%MZ{JHQ1F z>=-~6vL)_e&>*`$#{_l+{74(j!DeV$ROcR@tE~v*#kTt0@WY~`$lLDtW7E7NvaLrW z&C^MZUyZDEzKt6nxYfs%nF@nKd%wuJYWk-noijL0S;5X6pazohhZacX%89iHT!UkH zy^^#FUE-7~evYN7p-Z=MW(z&9-w%)SfgF-*Mcz#6Z>LzfKsuiLq@J4uJ=CDri=NT0 zHJf?aO`7H)t3(7Iq!_5$+T3<*&*um$t0iy0r0+eyPHb+k0l5a8gJ> zIx(?8e7HbF^+F6q#I zYN`OwB|DM)I1fnsL*Rev6*1DNpHyo_?d){+hk_o42-QFA1Llm3$W3>Q5J_aVeaeaR zE2_!Fwo{}i0tH{jP7TQjr+Z$qFWp%25DeC+zi3H&q;d|MtAp%yW|PuhyFaz-Q8Z2g zny$~yFA>Q?2yx7SDOwzmvlTLO(;xgy|YTh@r%-F zJce9p#{G0~5?c8zD!WA(+P6YJIO+km4|4!gWU9PiXAi8tyKptnNQ(`Cg5&{DKw!XhQSd9Gqs-pin1%B?6yl7@R=wD(vifeaQgNjhdt@V>~Wyh$V`nt4z zJ?jISyDdF0F?yI9%!Lp);tqKwClx$L(9bx>>HfCL5ercyf)J{7X<>oZdY?uZYq^1f zBCe~P$>4I9El2V547X(u3u$G04;}LmSDH9Jt79Z~-RiU$EH&C}P|vOb^;oG?j1Q zG5mOqChiD|_LK8Owypyi2>l2zvH7%ou{KSNWW>k~2 zETCRUc*hbD1ROgw`nK0dJ7Pf1@%P?mnw+p-Vnl{?VE;{t9O^VO=^|4w+))jA2|j3| zPN_{9e#yOiKI$@nJ4hzq&l*_weU@iHrapQm?m)A@5rNkAoGO6D48mPh%f#j75SO~s zDaZIKE`a2tH0@J1@EZ_V06h+03<4d#ID5rSfHk6H6|dduf&!sc0R|+f9TD8aHAM)9 zCqO4^b~QMmR0X*TIyS#HR6InE5|k#KUh<`I$F}3-^OO3<5HWVLRbKCsZLC4#yOBRH!+3 zFU|&%{^BdmrK~M42bXXjXdUC3=Pyt@#_y7a1@)??-rx^NTAZOMq($a@xIB^6h0VCy zSiq$sIPyP$5FsY1iqb?lV2Q7MP&JCNnB$puaTc`wi@~(oRe%f|cl05LpjFiI?OsEh z`##U_aEe0zzPygkz%66gTitj5^Uvh-<9BesFh+dYKtRWKv-m@T-GiWXLn5zP8K$eG zjA~SRv|4a42dSkK_Jy!J7V+LR2giFNH~Aa79}GW~;?uZW(6++g%=q(Ukmn?_H3op< z4Qnqfz8gC>$b2LEK+E}wxix0V`GEaqLN`y{{?pUO<%c~cjVby3wP@rn8YSLeIF16N zym~2(W)?Lfj(M?tY9U4Wru$Tp^Fs*Kj{=KDv5yO1sTJ=qhBKhfpOJEgm|MOo1D!%_ zlQ6hS%QEg4MEw(OyDwlLM-2_WsI$2|sZb?3bnQ?boV`JCAE$}@B!S)85Pr1yKWUMtdv&)C}G$h+sAm_e!?N4Awj`YzPDe41#lfUCQ{ zdzaHyG`kwHL>02c07SPW6+71*w<7ZVIl&0EvFqgfBKepgL7VDhI3k2@Fn#w2w4 z+u3PvHu9U()ssUiox87~A|Ia9Pr;dsIC)!}s@g+%f<}+#AKky4eVSn+l?$A(wf`0( zt#vf1tcf*q13&Ug-Z{NY9Npwy$c2aM9)LKEx}&)`iqn*n8CvKH2eZ!Wm5_`TA4R;W z==4b(3ab>e6&hP8(hS&p@0Gr}vukV;QTh7)x$ZRKAV!xQ8UnkoYv;0r%^H10N9yl{ zvzPY2lBLe|Xc*C(a)=P@1Zu2IUREsyu=NzhT|el=e7CX;-#j_qZOLk%>!47a%u^BF zgv80PXOBr}KOcj>TWw@(Kt&8%cK*8hjA4+0tVUYzb@w?*erx!cWzvyp7fx)l)Vs1> zT|7L8P4Hj}@1Y3nda5LlZ9YH*tPV>Y91^PeL>L)5{VrbYdMi5}T{5s`p8VQQ4tQXKf*v|={G8;1$m1TsAW*SwN`X=~Bq82p>G`&6Y|MU^+zzA;V{xrouP z{#l8rYpm;I1&7)LPw1MI(ucP{(M}&8fn-NC5n(5BBnE+Ql~i$WZz6X_kK=C;kiqE> zOIf8mU-^5{C$8&CB;=B7|FDj5pw7Q9x$cEW6xJP{X?4fZ9L z5vO~sjqqk8U(Jl$jJ7bU{VE4e_o#o~%89|TBqu4=D_yjF$WHCOi;=DdlCeJXzgF)d z+42qXkDgv46EZi$U{_~YEoDigbIM`h^>SjQMST~!mq!NIX(~>cf<@B>-YULJf zw$;?)&%P5C}Bk(OcYf8BQ(w*n5eVNRBY|}cjDRE1 z&fjz2-GxB(e$FpdUa6JSshkufM`xQN z7pjm}s#P-)xBlkin$$XUjQ;*t?l;X6Od*w$xYxmqyHiPAHd-gu4oGJ^nn1kg!$a=t z*PpRyW*2{8gUDS;4$Y%~OFW=*L{!e}IrMz#G@+5_+sz^4Yft=B7~<@8TY-OX$e*yO zAZ_sbTV^bq0a)lZX$qstgAkd>{$GXr zp%e1cK(00JYBx@qXR+gfD;IU^eyP2c!Fjlt4nStc zsn{vgDj>GNCCf_xSI4;S_U-l=R|^?UKDEyYXp7M~kd9^e;Lnk;a`*j>%DDdSj?G0{ z7uI=Fq2H@i+k>G)V%uNiZ&+~S7G*xOAtWPkxJY(+LH6_tg4o!qx>h$KH4Nnd_|ZTQ z%d%j%v=JKxiRgDad{BI*@&e!~m&ZX7HyNUNhL|KVA6kQ)YPb;~0vlHBb%qpI87Ikq z+?s^17jYpS=(kriDjI_!-`@6a-4N~}SSdDe@woWPC#u`d&hWAl*roDi{eSB+s~E}p zLc4$Zbx&uJQlbYfDMZhHN|Uk@@+G)t)j|bCQ0l_d-CGZ@C=pgRyPix%W-8Z#M{*)I zJlbS;TSAPYm#Mw5W`z2U)K~*$2=cJ*g)tf}?m6tAj}*BxKp zvJ4zY#c9taZlU#CT-_7j$tKX~v)GEqta(xLWY&T+#nO-0VpQ$?`mtMWx{~S8Of`y1 zhvj2}VAVy&YP2gGXJ0kGR3l~FrA+JWF)o&mkl5b$a3>^-0epUc>Ptt^hXJ4KfW*vg zb$eWVOg#C&vVsQ;Z%ux`kJKSH=}yb0QkcA1%~jzwG-2w8dvS!*zp0WKBItt5V_t)@ zvrL+Verc+oO{dEOcXd*KFmt!dn5;GhGxs_8y*zPQle=A9*Ps6ihx^V~S|)f`Gp)}) z{BmREblJ1c#ejbH?gf}=!AZ8X1SRkZVt}MZ!2|>x0@L^7+5<2~uzk`UwVxJss*tC! z6n0Z9gkc~OYZh}gZ6P&Vc_uv8KlPj7;zBOI4!eE~7#jiVv^kkUtA@>MhOH9X)y2z> zdTJOh#?{-R(|zpnqKDLi(B=U&9g8Na6D`a?>4`&%bYsp*@l}H<-OqlIxqapoMiGPS zvpatjbqA&!wo*E7<&bV1%@tq7^1Vo*&-edRy2LAw=pDraQgyr2pstPd)+ueJGdNul zSYf`bX}_6lu6v2R9a$R_++H+`R2&)Y91w_aVnwj>zZ-Cxb%E+bU$U}rT`-^sZ6}t7 z`ukHp{kwKslrC)gXrg6lOaV?2KFHcr^C-#4c4~=afw$l-k&S z$A%S$NF>?`dR^-#Q$$D~qt`|bah*Ap`vJ|I-->asyDp#Rml~bh{-9}l2jlW=fO)nY z5i#avs}IS(5_w#r;}-yaT*0BD@)fFnr64nClF)+e621BW1VkP}*+nWMm(^gH#q!AH zmt`>FekuuG-(eM>$|_;PO;^eZRD9dvb*YJH!(B!Y2Nh^fH`u7b&@x?!3wxJV{tZ*K zSQlz-$s=62C|8ML^Y!O<0E>sl4DMQ_bz4^o?7a%!;56MXV0Gr#sZnBX`4!zb03bhB zdG-Ar+KXFtT$N;SZ{Q3)7H6&d+jtWHFWX8Hi(9@|*=THxz#}ebt=g-AbWU50)NN?< zpQ5p%J#W4TrtY%%GXL1GJAI_>cu80;OVs3Eq!zjg9D>R<4_|Gy_-b0Tu{&&t_ zG2v9PyCHmA|Hr+Iji~aR{+-866MFUYLx0?Ou*2g!G#&g{X$pENT${x(zN&tM7<*Pp z;Nb!6WuzH7H%awXe$gg=OHfNhQpdFN8Z0+45a*Z-Sq~|Q-QWIhkxj5}*xW>QLI@{b zmTFu`UU?!G&oYFUTgqL7ruGG8=Eb~LWN9FaGV?vPila!B%vQ)6u&*1s;3z)tvUf=w zqUkUW_kCGco1!HIEfHH_&amAPay452#7>(Ca&g>xb5u$nIsi0qYVDL{~m zCDPglq=csXTVz90JmV2th-YJI$$Ku`&2~xNglLe(pC92V(~8rg5XgB1JYJ{2Xt&)x zLpzydnuIo#dhM<V6dLuK@T#3BzQ3FR1vUyg2-g4fw_CahkwPdfK z`|bZMI82Q99(5d_L_A*dRZ*h$qxiO7gEu%)yz5Z8h&h>h=riw@3K~yGycwl*B{7=S zQ%AW*t>#J~3*1XG8S%{G+M2O0KQ@=M#3#HxFJKqf+Tlx&bZrF1E~^Cnvd`B4XdS{Q zcifM^+ASLI;2@7MUSB+Px)!~(&g2ugt}5*_iltKNs|LCBS85`uCnE8X%Eba5!E&V3 zlV3G81vhsek8-zCM@U@Bd&KjH6^5O%945^5#V6P_?@mo93{@*H552Oy1~CvhDD5uv zNiu&X2p^JW8l#JR3YnIIjgWR=%jjP;VE+_$;do!DTV_6iBCxQ^F3U1UjAI1)mpU%$#u^cEX>TLEBD6G95D?xBy{ktzp3FuoW(9aX zHU@iWYrWsut2*=qTiT(#D{!byZ5J=&#gzzNLRJ82ZH%a#YN8BoY&#av5~!cXCfF8Sr#i}Bi|TU7}>ZU{FCXs-G|LgGEFZiDQtBHJh#D)>6Y zLHV?+@-x)&KvVh@E!8&F6E15@Ezozzk3ATgTieB2JTO|!F0LSqpWOcr`CclX$4=Rk z9*6Q=MTCaN;q9{}TO!6~zsjt8J)Q4#89qT;Rl=9gc?>_xX$$qPQP3MPam-J*j*RwXTwbXN~NGhip zz30EMK@=L4cheaq1`Q$mZ$HJM?CB~JDN%Rn&Cyi?G7zJT@47ecua$D@mNkj8-)!YS zXHc;5DXFi#ABgYZom`VtPiV}UM(wJRc*km=FL$>yMDN3kxMkEd8c9 z`}%ft@tMC$;xS;*it|ap5C6xObce{YAA7iJ>pM#GFO{Iv?hJh1SXT{-*b!K$jHMO- zo@U_oodvZp6=-+9+w_7TLL1!3Wv`h>Hlh~4=mJAP3VgRx=r(;5+03xv^DdE4T-t4c z5~2WTFT`hc#No>&C%svx>^U5SJNNOc4W-Vq^jF?bRaEY0>jT!snSw9}B3d}c2!y%f z7dyg0@B54Kskf_DDmuDlM6H*#2x3P4WAi_H&zu#|xM)tWOr#A`6*nqo>uDyM^prjm z*uL}RaG$G5@5%9ur?!(LMgVFo$cCFgx9<}eJ<96}Hu^|u^kuq*NxMl9tAHpHm4`Uu zXv^nRPxpvD;OT?a6?o|zHfThWWrPrKRj%(whfKazbyU21lLG0g>gm7c)7LN}T>e=S z@_V?2Rg9>=RkkLUSwfimUyQW=>|?D%4ZdyYW%d9Or5TbFdqvVTA`~zF5h`Z)#s>@p ztC9{P>0Cy$aJPIJ#M!zlybgZvFaB5>aiU<*3^p538@@W4EnL)y)*8^=?1SC!+f?x1 z`wukJr~9aEKQ*c{Io_8Gp*s(ec{n%|W;2t231agCmJK8B`@GKkcjjpN>+`>Pg`kZz zTn9U@rghkrgLAL*;LJ9L@Xz$Tq4zTM>3?_4}OuQd=mt{jPA%SlKo;r<;2jG|+I z065DXihmzScLb1_?-AmHB+4Uv#F?9z;MI8LXmL4cf303cX?IG9ypN-WHd4L)8-_m_ z5Ege6Ta+=QS}jM#ErORv;UNQuxHTJ~!8H*@7Dmb}!1{)C_q)=4V=L<;`YS4&%oJN1EgPPUUUGyxiYT07L#qnbY8-|kY^ojq z6Br7q?7oi`Bgarbn)cms`i&;0hO93201LkB|JY$J_4LLX^TZ;!igoNb{J!b@pFTWXrs|Q{ zr?a^0xepsFc?$-+-z~N@=NfjX?*}pGx__IF*~3f>yb2Pnj2t@IMSf#_i@VqND4+O{ z)?;_&MTTA|Q2<*QHPcoX^k>go@{A{NIkkeV%^*@>XzRW^6u58zEy!B$JhqFtb09m_ z_dk`MSwu>dlV=n(Agjd^Y(lNir_N3pphZdK6_+O%qVK2O4LDOaAHvOOn_;&+EUrDwcuZ!ddiI< z8^L#0;_s_uhfAk;fg#oOBL7Z~*So?W*@fZ6mnl&=-#E{8J2vASkzkQU)L zzo{6(hq>&$s79*1nRy-mRi?$@d^;EmU_xoLgU5Dhy~ld3b0^)u#oDSy9T;aPiLve20g{l4`H7JJ z9E5oGbl%b#d&C4Fe6xGwmxhfyxO;C@G)`cpoy}KK#ta;}uw2`S;ax4QQ*ypKp5N{IS$RBTas1gF|(aF0cm zkk)-1YkVBL^=~rzz~J(GdX2O=mr>gv4RU-}Uo5z86RzPSSx5$S3d?PR{F<^s=$ zmp^{F$!|yZwE0;DZ7SUo&I8-fn}pt4D(bzM!{2e5&l5aLGt zdv5#ym!3#(B^Ip?Bi_Bw2HV{dIjzXYhlIYt_vjJ>yI!3$%8X==R6fx9=@4lkW6{OQ z&Jb=08sTJ%vPpgxNR?y>L&xXs_#L48G-NXf05{c9tCVem5kD#HAtK`JPMruto0LGX z2ULW6OHGLaF9A@$V(QfZUte&H*G?$c?ET4tr{Dp4)H?^@N?NquB_0W-s`fnY)Y)`d z6Gh2G@7yWL6q2)O#PfU@&jm?BhTAusFJCCEn!lavb^Cm;boyZTxL+ztCReKD$d(4k zuXj7o@YFVBHLoI8N0+U}9=x3z>D>%a$Fu(N13weh9Ns?6u1du0Ep;DYU}LV(*I3zz zO$}CRe8e&Q#qJw1MnYYVggmA9{l(Jz`}gb^j9-0h+6czKYxK3=A5=*FyMf+6GtaL% zJ_nG^7`|A#A8aAB5w?{L(|7i?7SI#&1nFLlLM#|zYP(q(NaKZCMdeWzx8`4axM+m` zf%?az1DG8@GNQ0QYAQA2n(1NO#vdqlRslum`uilY7KLKspU>foW>+FV6UKdmm*EFV z95R^0k5c9E+zp}wbgNnYmLP^=dS!+}B1tpyi)-Xwd+j-AuNxCpDzrI%>{ zn$)i<@-){|th6Q-VX1GFUEhYZ*;7ZmH0s^=FjpLp%suyIvBtV5JCF~f-p4xlM&h@; zQ*EJ3U%1c#=6hzjnZ^3+|G^_LuUA-a9G$_~6rn6D@JpD6FV^qh%^4%1HJpm~W$VyR z>Uy=zoYK@;;ydriQx`Ywd3Y)oxTl6XD{uw(oEGH0SZYYRQM0mCN{0ER%x(T@dnlaj z>6iZd%b{Wm91m5rZ=E4DuW~D`g=C*7#sm?c0e{Jb6La`#Fnq54O>sZwbAMoklQTV+ zW_P>D4?;7;`eHoLZq#~ZGH$w+F~6@Ci??OTCWGOOj!aazi60Uc;-FQuy(XI4fsfe6 z7h#PZz+6jr3;7W%!*1t)-j>cE;(q z$lgDInaNl=29jmfADU!K!v=@uDMdd#*du%Bw+(^e#uoc$qrG!DQF7%Eh1N4Him}Zx z-$33fK(xWIB zuE1?|Vb-VGA>`@Momdu2<`D8)5j*809ySwXiZD@*D)o+)8hQ2sEeJ_v%2@$0`9H++0kL7 z?>}yI{Em=e&m0oo_4E`_&wpDi+5x~smR!f7tr`G>emuRSkSS;ZKK-Zb3<`$ZQ1Lm} z{`S=Q4$A$aZ<^hQzXf)zI3|#Fd0%*h$mtkhv~;fHY;bYXDI#kWQpGhYQA~hiT8f!y zsYc6)y0hApHp;EU0l*Y)Rkbw+d3)#(M)^W1DIDi?+XagfGc#*(vcMXwAmb+Fsp)A# zEjh$w{MQI-t$=~D@33e6!LVrZ+S{w(}#FRomDZb`xis=@vn64JN*7V0))}whx zBN|M%$ISVVf6XuZ0+!qJ7~!i#h(*rx*gR#?O2@UUBwBwxFWEJr z$!-68zXxL8Y>$sabsqL)t$uiC(tZT`CNfu>Em=$m{$u29u=Q9WNhYS6?*Ma0Gjwog zVpcZec$V;1Oa!7`wdfJ@pv;hxq3Sb@devkM`q9~m=GbE7ob#YBftSnpRA4bW z!%^`xb#)xh&dV%V>Q}}<@Z)90JB>s^9*;QwaYBFYLw{1hr}Due%Y+zBGD^99*Td5f zSd3K;sjHRK>KB%GqpCK>I75|P+6PSU_nnLQvnna_=6lQJ`?Q8@lN;V{;J&we{nmeB zF-(y4E7F&AX$YtJ-3}>Ha(baMU=n?qdDw4s7nTKHEgB}D`n_!(bo;kS%fQkg|A#L8 z$BKmw3o2mf7r@eW5!}1*@!P>PpBtZRMdOggW#rjNINY`V<-rwSZBLR@-4ag#<4=lO z!{3yVs0R1W{5Y{rB#@1&5|B4FmOZa`ezN1D0pvDmiR@LLcHV2(%GTiZqX%;KqNSfN z!w{baPgM;gi}?xJR}NdzZ+pNS40(~;)4lCRU2qK~_p2h)UrZNIl@SBlkjs{+il?(Xcyz{&~Hf5x|6hA|>y4@5st zb6Z&E#J+xfYZeTYg6riy^RseB6-3#l*X*FDkjh7{zg^L4XQ`>}X?R;3TVj(rn(Ja1 zTjYG@APN^(EtqVTHFw(D>4rwLtkw$c+D%$4+#>tahRL$%zj}A(4-6$CDHq;a2*t%j z#_@;=vhoq`9<4SgRHHkyrOG5#zt*spz_QlBZj&i!WNgF=1g?}Zbd=F;=opyPMv9cy zJh+N^ZOuhhGloexu&ou9M)0hrgY& zI^Dljj12LNAJnKAidHeb};SAP46vQhuD=7k)&c61U) zFpEfYl1WXZ>vTx>MBe;G_S$l=>-Y16)L(Xv3kR8YihbE(i~Xtt`Ig0Z#sKj zTVbfEeRL#7zVYgq_*9#2AOaIn7`uhtwzpRpPvo(AB=t?E11W^m82_HSCxH-zomt@p ze9D>Xe|Y=hfNGNS$~)$_Vu~RE2!Vc z`$3(1AKI>c4RbjK(p4c_L$zCUhTbcs2a#_N^JXs{Q^`gzH z67FW5zZ&D+@M6thSaZcd>L0mojN%u#jyfWTXcx*t_Q5U^1gLAotArY?g`Cp67oPJQH|)0NUvn-mGladx*;UQ)3JvzO*?7 zDCH2#VyAD6h{No`OW+^e!_NVxn?=NZf4*kLmp~ba3T;l{KI8HNOl!Vb%Oz+90!|8b z-cbhmuuer>v>LknkU6UaJ}obRoQnwK+X}-Z+r2(&f?68>HDu~b`8S7rk&kS^S>)qF zv?wPXW$Dfo6JtmE%%lUH0$W+3&-Io<3ccC@NGwE@msrOXw?I5Xw^fM*cME+el`c>lA7NQ!%ZT~!mg#eqkH zhA%7b^55IFk+yz6Yhzjh^YPz1TCTMT&2+&2U*dkx z-&e~rcJ<9=P(j?odthc%LcWm4veLYoG7G1GR6?tBO{R^S*2*#I@RQHH-8ac<`V{kV zwljbE8dsOqf}+s2_@#G>4JJ~O8m)qzc^FfKTgClm%^ISgsi%l7W=Bhq;yB`9=Ax%i zfVPzjEPi{XYj93pL38&PVQ9}hDVYcC$3$bO7W_yp;cui2R2d8?p_nfLezx#aX|%hq z$54FG$6AgH6SvUxcyR$IXsCU&a0Q^u>TQ_I*$j&=M@*M1(`F%zbndSo9m+0X4fOgX@`k3c)>>$E4A4=wabJp`__BfjBZ*sg3sB^!ye;le5 zWtn~h*N*mY^{~g=@B^?NuIi_K0ga&l%KQJSy!AZqYC=t4hh~P=c*& zZs$+seoE6v8dZF2zLJdwc2csl1A5=^R7KH>?(cjf_38-5y8c(ON?kMOrol$Du(Vim z1!S+X%1eRko@3t5uEZ!MynUzg4&$SA`_3De2c`s5dtONjfK@SFS7(rbUO*9CzWm%p zI-4uQ=QeFzw>1-6kt8owvS07h=XK2{bXKL~8J3ZthY-F)PjQUMb{nE{{`gm+=gz|? zsC&x;1Jr;;OpPkW{X3g}XimX`5iv`OLA!aepeno+wSF?V@qO7%;h{wHe%U7H<4q)A zOJ!_c%zTkX3$ZU9&__neCuKj4UP6H{)9zqzIrrJg^_2v2+8S(X36ozk%9t`Q1J1|7 z)<}dAcv!jIw_2jgY&mcEvL0hilwo&p(m5M%$0iyqG!uQ=juWB9pDUK_;vEFNS=t2U z<=QhMuo)OsFcXzfwbl2a>7`&eb-4HnGXCtMcMi{|99ZAc$9&k(TTLyA_)?~o@&cW` zS`J~^Jlz6|@Y(lqDm}N^_8Uf>9|>;6(T3(**3X#L=;(X#Ovyt^`i%Y(G`W3$$<&-g zt)1dkBOq~CO{wQ{m#k=rsL`JHZwqWBCa`-bXyKar(_%tW7yu6hN^SpDjqb(UM_1^Mm-K z7~xBv^l11y`lO=#k9-ri57Bg%lgQ;zJIOf4tZbAI+YC9IiKf5Afj3uN&W#1_d@ic{ zDN08laNS5Y!wbq^l`g*+Zx#_!UEE8wxplUCiPQfgD zV~<$(vAXpY9uoU>9YW~zhn54jjHA|D1uE!vLgrLZJ^#Cu zZjD4f_~UOL(g!^ES&A3H#xRc37oVCxcf!-oz%CoTGJv{8y`D?pwyT4OCtwcfe$9@N z;`e?vb#%}00l)3=?RO?j*#pq;eKByAVYvG&C2&skeJx1){IRw3^5wGL<3EDpe^$=t zOPOP1+D;0Z;(3zox~yMYjK-yP8v*NEI9?mrZBQ^pF4^a{#}K$u2$!DzHtfDmCky4S znMt)l0zU6@rDBAI#;=Jk$)8~Z@;`Gf5gygVY=Brq^E9t1oC9Of9}H%5w-Fk>)f(Gk zh38F8^h5-)%5Ecq^-zSW$9(>%viu6knySPA81Rg>r`Aa3qpWZAcM!@+Y*^UUF@W%` z8!-&l6X4QCIVtz=n$NHTAj9Y-jdRxA39eluMQYn%5ruBt3`XgR4qV-9#A2OH@M%M+ z-eOYvA}A%QlUM&L<@u>aW}nnUHNbSy4o>``@$FkY>xfFbg!IIKw!UsImDKN+CcwYE zcC>fI!A2eTC4>k?o1EqJszrOOQMH2VJy<^zhhY)93tNxHm})=cTvqJi|ofRP6-uh|ZvY(_A9yIp>EnnoJic=7w5z2i5WC?FT8 zpiNOr?Zf0?lOis22bS-C*Uet$n^Mm0dg;U0r&jIiw5L}H;fm|!x8Hc02A*rOegGA( z)A>QxlhNiVhSb-M#N*{R+B9&e~!7} zk}s7ToAxv+$|qh=J{za!m=4P*fB57^h9dQG!9C6QCU@WW@+HBxwc}r*2G3Ol zQ5LOs*V&~RTipp5quzGo!*|Dpj5ucilK);0@RP&KWV90Jf2jEX0uMecgc5|*x`aXWqaOJg}TS(9~|g7T?>AH zrE+gqc^D!J{2pP2?Cs=Ac(rypU_&Y7xl#~`vNBxy-3`L+ty`Pn>;qh59%Fm9 zr>zT&Yhbkq>6^=Pfp@!kasH11)T_bjg}e|J-TKmA|-pxJx$~xT(_pfwiJ5!-kxsN zM)YBYxx;MK?UiO$Y$0cwv2ixJt6#jN>FqCU`MC)qap5^4?T{`;{5M|qKU;C2a3v{p zJfgqxevq)ko7elQ@I9k0@&BRf8^i1Bx~`Knw$a#XY}>Yz#i9jS;vR9a7o4vy9#%NSZ67DA>zRcAMmtFXbv+_+B6e0#l0 zVWXZ?N0emiuY3)^oVOzn7elqG*5hiqsl%GaL=ACakuF)qprI!ot`m|hJ4RJGRUZig zIq^LnXq-hl|5#MO4Z3&|vAT(US8lV$$HdV%m=iy05*iZ23-!86R9JFuMR@7zduuhf zTRK9BLdL<@(D`<)>70GiBj2l-*t54wKfJj4z zM!|N9kX<69xKzR5;;IW?dxMoD;2tWzU+`mtssd&}tI0MH1i=0KZY>UqeEap|70UE^ z)YWOim180*@Uc6^efZCB-!9#&-rIdP|0f@3(!X`^2YJ@y`$c+C6o&24D$?k9;piU-{Ogkp2RnFRUr6HwTZ^FUGi2UixR-j zfv~ugIFJ{T9W*AJ=n93w1lkBUsb6;o(t<=T1npiDT_(v+F^~DzSS`1>PxeYmFU6N8xSzd#!899)>JH;xCM{Q>af|~bRT0QwT zT<>`c|I&AWwX)svi4YYO8y9pT_ATJNE4cTH0e~D_Kq=`_L3*uc`I5L^2v)9=BVjbX zAaLtjm9R?=*9jQzuitY^%VQCd-+UG>DMyd5R&mpdDpE1PWkSf%JRHxqICZtDlwErL z_Fe=-EWRzViOX+r8(B6g;om0S^&CU!(nf?2nvdvn%)oDu`#5Vp#sGNo)Vk-98TfB- z$cSwQi|WiOg*(94Ig221rwgKJoX*)@w zlg4|5VYM2UQ06+qQ6@e=kp1KGh2TuQJzsOiaXk4pCbd6t&nC@OP&C>(X=ho=U$6xd zWMv)U$rD2KF}6%RF2u&z=TCl{VHGLx>l^(UnJ8CTN-Zv+GEyuHRN_=CwzKG4W4s}m zt?#>03i+kLt2Ox{DTf(E6!JQA>hJRF?P;i)Or!W$&vhJ+cCPaV%U*O@_BRFoU33-L zzqR~-Ii#?H-bV1gq7+I%6n+hcV-8^w4lt)}50n=FO{B@Pd9C!iF=7%Bs;AamruyQlL--AP zP;Rq&E{I4+um3{C)AqgLHLx@{_`w`!NwMh60H3E0ceL%IrN;ZCgydJFpR3<4(W)14 zt2@D}ZtzL3!Kswu;%Y;iLQ-L&9Zqf0sVsVzQKGc@a(3ttIKpMm8v4dUv(Ix)-sRZ*bF1u z;!IsJElKFT)Ck++rPvGXm6t4w9R*f|=`pqYhQRZ``K;q!S;f9Tz37;nrrOpU_W3$J z>OF=}=`>#)Ila#~Z3S8j^KZMpDPb!7oi+ZqA;zhhM`>3>D^X_z$A<^ZZp(|ZW?WjX zGg%1hpkUg>@u?t*ar1Z&(U;KoENk(6r7*+%7@MZQYbsK~l_x$NIy&;O6o03P15tx-yh+JH(zS%fHXhJZa*KgGP zl2z zlZXAgs(4x8o1A%@O{)&%=rp!JHy#H#`KjzndVJY#ZZbbZ3j8BDb8wkg5#UEZ;7?! z0CH9LGl@BtGks0XpFY=qq4dbSGVN%RJQVMJ_7Uz3rx+4B-M$sYotcA{M{V5aWj3E& zpTUvqB|m%K5lNH_r} zBQer-US_56>KQsNWq~HeguJN2oX)!fD!c{JDj+Ma&wHG1u6w@g3ZjJ&b>~~19$x%H zSka@t!8TCwhjd&n-7v-zPHr_AxLNhFeSFhW*t*+9d!Ec%f#KY%ngcV%6PuT+BDh9e zFlc@t5jbae={w|}vw0Dpb0=cTyJJtl-CW8gl$=_?s~X`R~bp`Of%U;!|ObIzQM=X z^Mw1Lp3NyPa$GsfX~^$^TPZ{^dp~`7dEdRt2YxYMwr$uu<+B_eDoF1MoV(^+7D0iu zV&?(^@KJX7-7eZw7-dZ7#Vc$$|3tcx+w8+_}EXAW#F;>b7 z>8sGOl#HT{f(obG6_`kc4LKjE zk7me3Ac+uMW2!B@YmA@QTTn%CI3bbM$g{=b_XU3|<5Y)3bXHTOL zS2CVO;nkkav*{yU8v-H}L)XSm1y%x?qfc2PFgh4kF^Nerfmj~zb$hT95tm@{G{Eao z85B$#hEtDxl}BHh77@o8>GB9y7mgX)Ge~sSRags-I2Zrr&b{*Gvy0?tx-46#{4?fp z{~8<=?^0sNusZj%Os>7)XJ6DiSODv~I+YQSZth=i510?e;$!)p=C$r#MmVQqDj86{ zNTVEI?}6_wnQ|DZ9cbl|+{*$k_)&ys@q^)GYsro?MX4x=imW7uc7(G{_1aa5jEvk} zsd7YYNvLL`*fZApV7)%eKrHbPbbDys;8b++` zikgCTBQYb#g@;2Sir#8+wk0DMM9;ac%lvL6%ZKld+}(QWr*MEC4Fhcf_A45H8II_2 zYw&ifxASO_-jitYyVRhoGlTTn+fw6AYzVFqeC}gwn=Z2R`pd5@YmN8>Y*HFRp`F~1 zi%ewET%ucbyp(nt2)ovX90yUtHT{@N>NM6NLDZelYeds(%NvnD;dvTpAs|1hnJ;jL zF_4k5V!QXhK@;|4&WA`j40-x`d(D>+MzHDy=)Ux46`?XTb=D9<#|QiSI)3^HB?rRO zU>ITXsX+E(F&v*|*^$wW^e9nnm<4g=(?Txk3Ew`nmvX}g9ohO zVs1?W)fymA{5~~Iw5LT$XXH?@fAsa%SP2NDx(a_L!}hT0sTwNc2icr2@T|% zc<^`&A|F8;p9h{CR}euzVs#>Pz2HHrZ2Eldkf76dgIGIJ4);Y+GR#+F?q{p|q&gU= z9wJ|~WIGI|yVS!N_5-|=!Xf{45K|!t7%V`VPkrY__sWsHEBE~nXCVB5W@7~e44?li zsBiYc#prg@H2E|K(Puv1H;kaR4U5fuaKd3X0ocnh&`gQ={6yykU}z2!-L_|6{%w`G07L%_M^@zXc=lM6@$H8S|VQ|4lDVY z=EZ+R)bO`)h|H6%L8GJ<TK_Tbf&JcdZBmB-kWI+M49oe>vKPnu3d<}&Zeg#ngmuPDp-5TCu;54{% zoE7S@i6j2c`71v3jkp)!{MB{FPUgDzjG|OShBHnEMIi!FnXiRg@fZwC8ji;PANg^& zv|GaGxxcel|GJ>Q znc@9>AY%G^T^Ta%P6scBJwkEtuceSh8!%(!t5~jV8NCeBnfs|5Y>3BCU<1{I`; ziIWdlY11(SFYnL4@NNJLnIoz@Z8KElD34?7Ha%>%OTyU&_Cb-hZPWduBZ+vKQA6aD z%(p+8MuK97mcdGl$GKwz`Zqr<8BCo4MJ8o#2yNd=7<_*+zE@9wlu>7}tFlHipV9~` zCL5}9>)c1T9?3`)@+*bPS%j7%b=D5mvcH^oL?gZJPdbc43;)ePzPbIIZxS|uh#G>+ zp%%q~-&AhxAyh&z4&9raoMn|Y@vDb6KO z7r&~Dz_A4-By8rp5L$?$MGdF2SxCOoYboymsC&+jeF?=9^K`f;N39S+o8MLD*jOl) z-4ZN9&^LDsfOwof=w3HLP%cT?Zo8-M0kzGqLKfJ}%F*Eamm;5ck0Vc(leeQ@SX-JD zBsT+bQS#9}~}xX^NZ0X3fmfQ0Yipq7Cd#mgw*;fhKvWaDw{_gy^c=moh@X5Ji~ ztefL66%K-_)xSwfoF$&G(0e9~U~^Mi=7hbTv39RmNHKqM;)76w;3 zjy%H1a{pHs>d&?4ui{bv3$uX5CTSNhtchblfXNX}RCDOlSfVSz1i$z3x3q^RDkkjb z@}mxR^6$57)U;WeAzkFPvC)aPcEF703n)D+l0dCnQN$GY!}lNIPZ&(&_?YBEPm^i$ z*RbY6u5mY<_E@|Fx>~Vdr*SoE0W1T*!jYY4zE4{WcPHl4{-`g0p>2{D51JB-8Zu{s z9$Q1lmn5VBjD)WY7-oXUu-92C%!>`~O4F6EbxV@Hv09GOvQlGPon^iC(o0K>vaG^o zybQEb`2Vlw20UjrWE`1|LRshFH5_xLA@|-;>o9u?Ep#m5lbqiA^RYJ?3o9oMW&Q~%D+G)^}@Q2rN-(wz6XCP6q6L+{bER>P7n z`hyt7GAQ~6?cz8|6q4<8xD3C%9DeUJ%0h1Lw*%5P7B%g@fAgZ^(Vztzszg$$Y)YWI zQxR*`9|bx4Z?FzDfu_f_i9sn5x&~t);JLZ#>m>C?^<$XQ`2|bpYxKfAUCz;aq}0j(g|#+AU$r8xZLf9chcpzi&rkHS&C#uGWC*gFqxNjzXXH4WJ4oB8Y8TdZ;%*7%b0~>y~z)1~H zf5oR4_qBV^`j?uGad%_o0FEx>f0&HijOzNkj3p}e8lSPXgtzzw5IYxAwd`o@7ivDg zqLkthH1ym&j{k2Kw{BJG1K6D^7y(J=ih>~@QY={$!(}OK zgg9lj!))5|qHQfGuQ;=Cu7gqK`&7k};$-Y7rm<$mv&75vY&%(itQz3|GxF66rt3he zwyC&vR2wc=`rE{`x=YT3dundK@tP(N){&ezH~E*lk-hu6SkV)fP0eR|Uz+pnC#Tl% z!d9!=zs0C9xPKGblt>1r;rcafeaLcjNIq@|kNJzBEsq+`yi_Z)lHZcsH)QF#e8(pH zi1T&Gz8Dl+*x;6};H#y|m$**c;~&Ihx$3v7Ix@?4&HV!|q=L3shy7|phs6YUtrbfH z2T`#uCkh+Q6fQMq%SYHOSR7Z2`uw=4zdFA?$$do|iKKCZHG73ESr8k6U)4xE)4@w? zJ9;S2N3tYJU^PWF?AuLWIj_h`Jh<8@W6o6dg{SzZiFvq%tvw9DYqnsHc{4A0xB0{bLx_3ol<;Y0 zy*$cJL75j!YNgi#>59b|RvZW^*W{ff+u|BnTMkZ3y!6QpW(R5o6eVyoX2V3sLKSvb z=aXvMZw0)yN3yCoK;fV+B0g6ik$(X88`%@$&X{?V;beqmdkX@+H?3RTLny^nSj=pS zrMlfKMoao%pJZG5dY{?e85Iz%FsYRKE89WU0+0yVKWRuWcc*9p|l} zImJ)?HWY;t`hSk*r(!|p8LN&2n%WWfoNNC&+DOOQo|#FMxIIBj-CF1|h+8FzOcw2^ z`0JXjMT>krK$^W_jLy=T>2H@Y3hloxFlqK);tOGfaIN1O2clFKr&q-H@gKd}*s>*@ zou|`SuGBz15D^j0mssw_H_Bb{pZZLNg=5}>-dJ-^mll@uT&A?Mf24UlkHS{|l|lW- z(D1mE`!oXE%_bd~sd@}yw72CdjAmcv4VN8iI`nmNl1nBV1IIsr`#X=wMOUBoJd^S8)JF>w0QjOnI5_S>vn%<94Bwkz!?O&>(Xfx zvRRsKj%mUQVjtDSRVyEme)%0QEW3Mp-E0Q0WeO#kNoC+HpLNMRe;P&^I}zB|Err zVjC8J4WXI*UxcVO^oZjhb*}W3$z{lwrALoo@wZ27S7Es6SD~kTLkqRL~9O@Z6?88c$u02cAao5808XyNxl(}db?kKVy`|2m*^SJ%^2c!=ON6GBC z7PE(&i=@|dE2lUxi8=PzJ;^zEZy{HDGX3ZA%OcVXW2{UhzD%yjA`YPAfNL_LVL}Tu6Y( zaawj}IX0~9uJ%dR3hD@-Sm;4<>6pxYWk<0vka_+;ck@lsf+YTeBMIeDEfc$4zj$GB zUxE@49D;_k#)#MWo<78Rrk1&)cN-P9F~j*3FNu91@Xk|Sd!($ggY-5FDn)YWqi&;)qz9(wno71OJ6&h2q?$WZ zc}&E!kb;9>IiJ{iirvZMQmLYZJ$3Zf$bP#OjD-2G$HZi@5t{n@xN(?!CWPk(jU%jh zwE|u_8?CAEUln+?RxCB!`!@Jj6Mma9sEiNv3e&s6aNvgLXj4+no))cVR-IdML}P&b z{jYJT!A+NIhHwD_nKb&8k&%%rPa7NA!J#43NXN_|r@|Lq{JiY+__~vx-@m;izT)L& zW>y2VNYECT_P)r<$%Xnjz+cATw!A0|CY}Iydcp$E9?n;Fd<{r;7aCR6@Z*O>UHMMX zZeXyuwYq#fQ``)Oph;8mylqG3xCvH@NL}HTHR! zBD$(hv0q?)wefY-OV%5$H7syfI(+^xXH?&azCWD*A(g4Rr24XKkRxG0q>zj@8f9BP zWW;=T>#-2_UT2_a4^$tGIn_}0jlMh@11fiG0JZ+r^5l!inE zhOQq@hG1r&X>w}!WhK!8Rqhn6ip>$r1ZgLH7%P`*SB!oSj($?vd0vKJhL{+Hb9&#h zVc*|s?EO^ix}MFnT)ROHqi4*kG-^}Ts?oe=gcUbKD4T+x3ff)o#D&uIz#u7J?$6lE8PC<@{}Xx;RZ(wNwpr%*eoK@22q# zirtQ(_R+zin6~eDkzf6#@}|#M%~yQY5wUqJ_v8g*p|P8Jywky-u1bH`kf63&-!ND8 zT#QEvawXQy37!k;>6_pO8d}o*`|n}`4PBL~PAhr!$0=1j_i@W&F<^)+g0EySUZkgn z+oa*^Hx-6ZAQ0GSy);@7j*odT)=ze@gvxcWJT!tP&#gt!X_5Vdy&;d}Gl+bYxrQU5q zTT7+p-L7UaFyU}swR3|T=NYEuOiurxFd6XF4O>CxX}#wjv-A+om=dC74Xw}T^2KgguLFb z4ig&R@x3)HG&HMfV;6js=i)SVHrw3|$`5C+O&6T5G)K+xtS2y*L@uCQGpc7_E7~Tv z1XJmCw8OP3uYfX$s_$I_KIQ`Rmoi3#k``DmprOX=c-H&nGV2wNaHR=*3l~$h)XZ$ zf<*zz?R&Yw7pMpmqPu_y4|&rg7@ywH#u?^5kMSIwto3z7t}9@FOd|w0i$i_=a^W5$ zu#G{UVk6|pT$Ug<1up72>9`;O%bqH+AyBra%Q$GKu`-jDC{-~pMeUEr^iKuCm8Srk z&6l6mn(B>e6e9j|YDhwaQeqtHn`bgtI>vbCd6Gz3OuVclf5NipdG|^wdhMa`hOEcq zLJ5(H!V-{PJUX73n!pXGE17XbstG>Zbi;#8X%ge^gZK#LCj)INXXO^&Ba6|JUiSiD z*@qF93~*mUZ@+SUBm6N;$g$SudMa$-NDwdC_cquE#=&97ycdj%6oU8i2slCNRg%ER zxLNM<^={E}xvsho^H(Y&z`YV?86(ANI+{D`bFv zi<^~^+5q5E2C2>gN$y+$Y1MS${fB^w&r@Be&(9r}gOd#_eTB0=UzL)TO;bHBUAQ@kwW?{*PEa^~gi^IWk=e;) zS(F(8jX$fze5*R8G+nT&ZU(@fc0&QaRxR??h3PteTi|P)*Dz6bJ&XU6rg2(kT<`Vg z#0sHRzg;4iR%6lnS@Uqvy}CqR_8Xijy}P%`E6bPH{D{Tdb8>CQ=}Dwmy~8OqV)w@yQz&4jf1Milu(t1+HM_3&Ddqt4=t<#MVZQ* zRCV7Vzn)u!UFq1d_Dpl_Utg_1j16PFI!JCQ9xfA17w*GVi`VPIYb!ii@2)Gg$A6*w z3`UkLynIwf25VvVbB*b(1F&>?o}*F zamEqV>;gnZo9zMiB6%V?hq+sASrAm_Cq!l#9_*8shCOqzW}nXAo`7WS zv%s1`waY>yFJyCU9%SQcQUZ2ugvh2~lbu)yABnugmL0q;dDz6m7OCs!NuEpDJ-Sg6 zE;yqqK%NYeVmYL{Du-LANdAUW0Mvc51D52w1jKIWHQ#m!ny_B{BXAoIt{Zp2{1D*F zUPMzqUVLsjLo6v@?7Ht`SUlEX78(1^fcb@TxAj=6L@!rI{w!`(26Yr&yi~ zPS3Sn0xW{uWt?CiMf*KD)5Ax=N9Nn%Z+|U$J^!$dgL`|Q`EuCK56j%>+;ZPr;BJ)V z_4IHQ`{(0ac8!<2jzecsjk5$B7rJ{(DvqT=s!5QpvGziyBrXxabaQbbY8NreDT^zj zBNlS43=f6#QpML_)+zs&p$_3G51LKTh!kWykG4nKBT8?ViF*~OJ|dub@zDw7 zvIR?s)q+r&i;TwFPnFFhJy?aFm$31!Y|$85{^{>?n!Z_gS{@a#>Y2@D+m;XTh_sGF zR{0)-YMrg=T>1oK#d-q~gY}zSspKHhox^7^lXmf#I4mGC`t(y*VW5rj;*spA91toQ zV%-izC&7zgtlDu8+}2ayRS1?R^mkoPzr9R$02dJr&J1F5=$-CwG0#1!7R*f#lWHy$ z-6}CXRYX)aM@|Y_jRjbaYEV)H4EJFqTjC+rC=0g|HcJ4~X`_GY6gSxgED4r_;x&p~ ztToQOpNt-AB{BX=IM(7mwZqH?+jB+-#Eb8@$lvh<))%UG+dPZwb@Fm{@W;~xSJqcP zP=Rx^*DoGd8#u3bARdg>fF2)VzbzL|4iLbI^Euwy3~=AxS5bf6tV2$sxK+D$x*z6@ zZeuhTw(gDUai_z^+juvWo9tcIhZo(U z6)uo0D-+(|^g-+%4~i9Gr9LHsh$KQN2=s&u)f!urhLDX(DimAlo1EzJNdHSB6BJ83 znS6k0!h98_a8fLpxKoyPO7P`bIEG9r$I<|91_vV{cuPcZ`_TTFQP=KvX{c~48Nq@N z5ubV(e^w;{Ce%`8FQ?RW+AtOxge_7Mppuk;hRju%u*4A;j#-6jJs$GDuuy4Jk&K9~ zNBslD(EsFgxz@5w@N7(X@@#h5qE`WJeBb;)M|eq0z~f2Lgy2b@i+)XOry%6xP$Z7t z%3VSuk7(&=(aGK=FlyIh`j$Ar=Xy(&kccq)%cROc_?@AhIzuvW5iO{YFKo#XFy-s5 zr5yI6+~EtbUM@RZQmeA6hc||p-@A*v>K4Lhw_C$r35EJbK#(q?h*?PI@+h-|W*iw# zJ1XJVf@pg3JlGa;sa*A_`1pb}Fd9!eVv+beJ&$wBJK|k9qJbhxDz(r+YVsxy;c&b1 zNfF+}U_n1VxT9HWZKn-QffpFTwdJoL!QFkPd*vr}48}&=>JX#~yzjb?{mOd<^ZXN9 zn;MM7C5y??&?HZIKbpSXCHTI+Po_MSOO*Qqt zNHN`ASm_|FT@*jPcSm))GSTz~ukPatB-{JSx2U|$XK!>VimJBf6Gb)|OhV)re)J`G z24pV+Yf-7rOxln5if(~TH)>#oUPK^4Qbb>qV}P4(QC$j z;V%kA1%dawpNk0e+dxMa|5xctsJMU+AE>rWsR%d>_xo{PnwV(4MSQ1v1{Y~@gdZnL zv-BkLUWBVO@eZQv59q6oDPc38kc!9C#q6`+<{i-h5L?$~#H80?wlYRzb>P@-Hg&3p ziHDN6fLAvc2`E8IaspCns}Ck3C8JM13|p~_Ub54S7+Y8ae*`1Pd5sTn1P#FnJ+SW(eohyJxrL%#W z-QuCZPD1$&GM1G9qjNL4@0<@03IfyPOoz~uttS((y_zUzOl~cPhI0P zWQ%-l^V>HW3@JHFMq-eAAVxTutt*la>gO6?$kbiM_ah55`Jyt#+fo#C8;69set{tbIvT#T zan~*WO(YuShEl5H70TqQ-gGQ!1;vtyeHe)Y2*_r;KVK#LUOtu$P)y^B!(HR~Xu)uf zk8{!PK`I7wXwrwgcQ0V}!dr!ca887&blt@lj601DEtS=0)#w2|yb^vEXnHj}NM9oa zKI(*9)1$HveY#2MK8OkthQAb<)TnOuV=>xh$zev!VJ@PoeqZM~ z9ojYzuWnpJISm&?Wstexe8 zHUCuMzL6jNn68lXsQN<%x+&ZfJP~lhk{JJ5$RSfL zkc68*ibj-I1YurI$R~2K+yz`DE~XGmq*&*NuvDS)deRgpVM^A?m&gGYA)$=EUqruW z@$&+(kO&Vm^uPj-BT|U(d#AL`JBVX_>`me+yHBl_c;_U^%;g}4)>uw`$4CK9NxQpb z*_;(!1{J@Y?-iFbFqLecs7qEbWPG-~qHr-3M!6&u zNifHkd0irK+|3@^#1m-+;b@dm*pnJ$T0mxtS{GWvA2VcW-grsQLipsECQGbb8@Tt` z6RfelKIu=*Erg-0I1W;n8fB?Uca$qowSyulp^4dBubSC;-@-WOi!0CqEst2xmA+?j z=2#@`I}|CQwvys{L?l@Jp#;CIp}_}}6UkUgDb5Gq^f4v!uRp4K^{hoaIGm$TruQOU zH1rrM&#t5-}=q+<_kW#_Ja;RIxsN zi4bTD#ICszna4f;BS3{_yMu`H5tyO+IJ)k>Np1h!s4=qY|H^l>cK9CjP5x@AH469b zsz|TGBBgyQjNQg>d~V&j2Ko+viS4nAnY{saimg$y7$|SUYre|8sJv9eb=XkQySovU z3~q5dM7zIp?`Ub!cQr2gytMRc^hoylS1#?pK!bx@!=t^PkPwe!FHWW?Vr_E7eNAin zJ=m;|d#q>n6LGV~LcR8QFXyG7GM3e2J0bfj3TMdOl*v0s$&TeU)(P0JfCFdbnYOJy zxL40m1|vZg;&GcVB&-fJs=+_3gN%GdInCID`?(>-;D*k4mqZj zUBoRim{NMky~+)&u7Wt%7WPl}%1%DW0vY&c@U1n=vK-8=O`qEdoQm+JmwVvmuv*_5 z-jC}zq|-9@byp3lFZVlMbVe@){Ef5rvh*ES`^4b$LjoT#e_F14RPE{33gW{K1FnY; zEg`QCiamLU4Iu9FrLWt0rzC3A)`(=_GWa~q!?fxMzh}1^&av8 zp$y>fe*?-GB6hdfuG}dqN(RlF8qA*4>W$ByQ|qM~eSlxY`h1h+IwL@bs+2gdY_>H%PA*aXr(CWSePVbHYEI~#5gBLER+xi?gb#G zg{|rW4VietgLh~m>?;MJi}TF)b)k*u2}F0k2d(--I*yw~Mmu_FQdDUr7ES)6ZQdkC zVGpeI*kl{aCR0-MN#DhJ zlrZgd59Cm}(=cD}zRx-%9WqgP(eJG6ZE|f02#u7^H$Khv>A`ilyBJ(;z|ypy#33Jm zbQ$wr2XyOhi3o2-xkyE}8GVGdediE9)6pIBl;kK;&AlNW;u5++`L5-~0q@Q(IN|Yj zJw!R1a1klxWMoI^JkC^rK!%gSF}wA34}U(!0~vCb0QHzVmLjF!dzS+IZUl%c{E>(2 zpWfl^LW%?@!EcI$qorI7ijb2zX~fT(gFXU}JC{t%vtH_Z+iKvgYzmCtMsCkb>w~R~D z#AdDk<1T~4$ebVR93o3(BpB!T(WK=i7nv!bij`LK@>Z!NQX93e-*nTq*BD7w;J{PE zz!GB(GU`Vk{pm87C?y=n_5)zT(q#@RV_u71uY*h`M)m( zx6jo2?9)mWlrAN;>I0r315W#OU&~fiVfm;scRT}nG0#0?*FyE3&9wtg&&bXtXO%`G z{0gb)BBE01syh&H-p#-hXqiAkwMc_=qDEXV7`-G zq`w1P{h@;uo4qeuDIHZ*61+)f!r9k64|9~NS#v!I26M<5Bp3o(QuRrX-O!h!lYDD% zlHlq2|KAJ1kZK$s@3txzGPm&OE=cB14KjF45BwL7k-}(9R z+UHozbFgeYM*;RAaI?)9!HklIyhI#_L5m1)T1aOha8nAWUx@>nE%Hni#q6|&Z|@)F z^Mv+EB!TZ+{Y~8kHGm{@)QPTQ4soz0n(06|pFg%-3PJ!twt}qGjnWep! z3W;HCBV%0PvbQsTpD{zmedOblDz{Km*VX6O=Q4IMNlRQ=?qFMD_ zb>$HwKaN~`c@^{zwQUaEbUKoFvt&Fr_lam$X|rZ3}PO~Q|gT^3Ny!-k0l=Rjf(_qx3WG<+@y(; zw}1kB91kd?2W-(XT!4kZKoEzjII_XC%UCz z8kC9vHR0uNVq>hqsEZIX;bY8^+9F~oPm`=Jz-vP!_YA=ezQ9UA{DKcAI0z-F=tm4< zx{zwS+6?j8*pZQe^a^vVge9w330m=DqBAoEg5lL++2Dki_hCssAd@dA35sJST8n0Gh-qb{S$6g z-;?AOhzgkLuyGk_QLI%Plhtw5bf( z(Oq+GGHLC1e`tf-yE+U}e3!gfP4>%&dzrsUrm5Bmzfo?ot~xd*5?Xn;@h+XD4B?1a zXl8Ssbd%qNkfuTOpkf56f+<5g?nF>Rl5Z&Zzs!jSDLH`EBOG7jTbGg<{CtRTo!O1$ zp-=t-=3r%|qsaj=6K4HWJc3Q8I2aXSpUD4;Fq$x&O8{(cv`A9zX@*tDf~bZ(bUU^LyJBLJ@dBRs;kusOH=xZ@D0u&x(?r zJl@y(@uO<B>4!3^07Pn~OOKQ-Ytjf?-Xol|*^^0hWM73>Am<%;!XW;<%*NxTmcd zW&&y@>3u5Ulz+bk2hT)#u4Kw+M+Fk2)ONvjsvMV%mELR=b~5%cwOie$3~;0J8>fM4 z6tnbH-KO5iyk}RSyFR@A@c1G8w zS}e{I3i`I9dme`E(j%n&J}kVUsvpExpV$3|6d)uNzco`soKKMzi6)Jx5&utPm`RC! zl!fR7o`WCC7%D({A!bZk#(z5&RSfZf=(MAhW_%D{4m;b+)c>z%ouxGWY7-?*GZPbz zcSSK57mgX&d!%PxR$fbV5l~;C;2lw9OxoorKMZ6}IcpkNC@)n=KB6XI>6UPJ9@SO! zPn_j|CzM#*0nKscFP1V596P*JJkG(h0hJ%uHFzFo)IMa0&0dVyjwUS?{F5Y1Koj3k zqS^aVZAK*L`9;#+PApEDzHs0iliwK=Z4BwV2t{I&cz{w%5uqUHa-_(bz}c zOP{7V5Z49v(5tfpB*aahk2E#MrRkePKd=AG76A1H>=@Uec!DsYU2aB7-O*+#xm~Ojv1bTczA$0l=D(HABBD*#iEU>*icxG2 z^-{+0l|?h^F;0Wc{IxsHh2*d<8-JpvQ#8$tZNL5QO9S$>3O7a}MFFie7*g)zc!eG= zR|*9ArR;dYR%V(lwO*)^{q{#CF`Rh^R&E;>u&nN(v~C24uVY6mx~R|(tx zYhjNUjRuD$23Gk9+-6KAH%{`WQ?!UUV=vAwBb-|5QXcoSw8{4kfCAHij3u7H9NS z)=83FnQ|Xur7^kbnx?24_WjB{NK%JYTN}9G&nMbLZWWkvF^a4%)0n@|7zNI+N&Zz8 zf<8=1lRdvz97X^IM7q}Ma^?)EflOw8?+HvZ)a+ZurDI&qyHuY3^-iTXqzKa-0KT9s z&GG7Ql~nL3gEjgUJeY-Fi+tMF9Q0u4^XP1?`e6= zCgosI(6N*xH<-DyCsmrFTLK%aprp|;*toe--%d@KxQ?}d(gQ;>QTz<1Gq@ySvLngY z;6_A;87j$hn{^eO>D~qt!}F~~ii*9_hnWL}E4IFyX0b@IfWJ5(`5IPQNqtw+7tgIN z!(RyDn)-#71O^$@Fjo{lV8q{6ik1{Jl@8*q^yF5sXr+PX#vdVDeHCcu|7C_mq5q8EgoL_-FP37Qp8e7VN=Swk0kk_{k{|C1@Ilq;rtGsSQ!%rlpi?S#!4 zaj#q1j_bu5>VY9CkMd{P8$t)D;(1a~X4yEf&7UJLmROj?PLfwzDG5xHCs|%I=czF4 z0oKu>>S8lbuIhjl$YU&ET+>f)>0B>cOzy^(r|$w>s1O<(*HjB8Ngf0BI&3;oBF=8b z;S3?V2;Vc5R) zN$o>Jib;+XR!y=G7&s!4O6xD9_{u}|Q;25^a^k;GAviN|LhHfOAF989Um8PpqpV1t zPc8aJS37@aVDcVZqf(SFkYUOvP;A|O@~W2qztKpxX@&%QdFT)qk=dWoPFS$kZ{1IZaYVpnPM8-Of@0g9qCc6g4MjY?)~! z&V*d`{vKRL)njf$)nA>Z~t zLsdv__=PPk3=9&&MH^`i2YrGfjp{yK3P$%{1d-f4A}aiMa1~UmW{jaNuBBb-$r-E0 zQ8J^eR!uKo1R^wb9e*O4mEX?SUwQBRl<*^x1*@zbR@sldSSBkeq@E{#ID> zd%GI4{>?9Xt2}|N2b=^xhkAszH~1LS6TK;bOvgWp;{ka>F{i(G4|876n4X#7 znU<4Pc1RoQ)Dj%}66W*flWmEcJESUJ%3hpo=}>8im5?j&6q<=<>r6mOQXhHsI`5$o zkvuy?=KLtXswqe*RsAI^qAl6yBkcuH!W{xlAWV&pKb%iBn7Ra{g^w#an-Ge`qREGO z3O0n-F?UyI{*dm1Ak^-zzYiO+ZV=lc&I33J?a&?waeZM($+-_r1XVf4mU3`jQ2-Fh z{bAsg@fz0G68TdrSJORhdCC8NQJ`x3Y*>tnmqY^LETBn7+4#3z@O&8-{Go<*E}^FP zawl^)+3(y4f5%7ibzWADSY9Y>tra#TvDj~vf$E;7bE3^?^fMhL!3{uh&4y+d;r_Za z&{8i2G%FqU z$d=iAhFSk$(e-OsRHryVfl%r&EQ>4QbS}$koFM5DFO;phkp6|g@s%1{(TFB&aWR5+ zyH+uR>b&v7n7XtJA}I#IoDV9$WIq;#4?${im#7aYJs&o}AFdW(oqp)u%GyPW+2DP# z_4Ht)yAKq6lzv|LoU$9)0gp+2Nw{D=mGDvi1@n_&|N8N+MCeb-^nM3S_7sO{q;orA z{+J!t_rNJC5t~&h=jT~IMH9h+p{Jcyo}jD&>yynUn7vNtw$RXo459zmzV_BB%~T=m z@8!8NX8KUAjk3WsP1T-_bW@kMHSJAE6oUw&2_{@eWhPIbA7tCLI~{f%$jIlBT5y6x z2g5{>_2^6aKTw`mmu%rqgR~S8BRfYU>T=-h-_fno_lUqepLOw`A-=^^F^~qpBtZQ6 zo6u-<#8wsH_&0SAv|tTFtG7iUjXRx|D%5vPyxY!Xycv99h^UJ%sX$)}!u3QrE#IS8 zazhDLf=qM^V1nGCL9-u$J#EtR8+7=Kn=dzydJfSY%(4}WxzBfBu9)KYQtJ0jjAUsE zJZpE=8ZBS_uTGb>hmP)&3EQ_iDusgIvdOZVH5fh)Rb|t=t<1?9=c6?bRjo7+KmKr> z^tE1w9d@;bMlYSPR|Z+4oRcmMG=E%U5+g4yo{lMJk2ua4qId-S4)kPoFO42x5p~J$ zN+%1`;9JP~;{}1*PF8d8WKcI5tvu3Dr+^&y%d3)4HXoyVZk|*QV5%{2O56j#;E~9;LvKdRoD!3M7JmZ-#k?^o zQy*q$=@HEYKs_d0k1uNZdIi95gLd&x0{RA46gio^sz2S>C%ARb~$Y{9V6t zKg(io59VwIm&DU5k7$!cAEQBF(rhXYQa7Oh4mpFpKXkFiksUY@8nF=?2#$Q*qqBP| zP#LbY6{7idPB>HfHQDhRd)(yVGnCO5D}Pfc;_f1tWX3?YRW`#lzL&_(;SidtwXX2h&Q~Qq)^+gH$N6KPhvPXaONZRawYwQ)Fk49 zt|lM8esK|l=P}JO+#gw}7ao!PRQBA*MM<+f42x;P7$UgOzx`f+JMO1wQuLxjlWF~y zpc6a*4@9$pM)jDJ=L|U{SnN;+41FKCAHjbX`5X4(|3fOgWZJ-9)Iv~NJ968f=PqqL zIVH||C2>H8Kr>Ri?jxEfpd()h?q{n;tnUgTzmmzhucIT41C9m$;={3UylLR=U7d8| z)kVdJ7A)X-*kOR33j6Ztbqlk>a_)kvjHI*>T5nGV?%T-hn}68&vg_aGXj75=LhkYv zV>5G?FlA{ebqt{$e>36~9eX#pcs>$gp;T-n3Rz^qDs*L=kxu)$VD}(Q2gn^L8-xq- zQVMk8&{N@}?WYr|teI*u*xc-tqAa7%%E^Jh>K!WjNh3{pwpm#+P!N>R|614@ZaSHd zV=?z2ZDo>SZxTRA-t9yV1KSZ6B8GFN^lsP$N-0Q*sTFiIW78DvH01E23p?#Jz`uw= zVESK#!$|IXp0vu4j~I}N0-rKdNop`Eg|raU9Kz^eA<2zq@R7_9Pj|E11VL0AL-W z2PH&=u^iyKNo`hXyZR#Cdr^ilCCXx>SUUOv~1RQo-2sX zZt9oN6P9VAzWpBBvexgVMjhETtX{s|K7vCr7lSXNKWA8AA!LxWR&zqM6D#$9+)Mud zS#N)z1m*Q%@!X1nYgNel>_g2d--?T3)spo#-1fr3@`qWz9h9F6FRaRs_KOJ@%SMN= zz&coM3nn@k0;emLwvW}0*>)0mu0$3sz>p3pU)s9YpCl4HeSTTN7?`zkqu%Q!N)c~I0j`@%DB@fF4$RQD6$h- zJI5`%#acr~Y<*{C3xL7hi2Lf#(vb8}Vz}eu_#&E4`%dRp9vb2G;=Au3Smn;Pse z%cO|3Yr>wXMAkqi+>8t`XNg6^=<~xT17DusqkOv0L2m1ha?QBlcPiR=NKA0Z?@lxu zY)5*@jV+}8)Ai9oL-30O(r4(+<053cfrU*khrjO)g-ikht! z&hrK18OmPsAfgbx;4E>ayFE?lWg*9Z>7o09Y?K8~datS8VVpRH>DdiZDI#*1grskp zeJD`%(A;?_jN$n=oug$%(0wxz=@zQ^rU>D6*d=Bye#UFoe%sYUH&r!0n~{_%jhL~Y zV=O;-1ieU$WC_qb-NlUDc;rr}?3;OmDRXNIa)Dmd3=+LHb_ou6M;!O!!XOWji2~h^ zw&NgUSve>#^!fN449_;Z+1mp7^E`zZadpw8zN478!Z5A@-L+U@sCKXz47&sekN;uq zqVEvedJGSzfyim!Wvp0mouHp*ADP2>X`Hw>rIuC6M?6){Bi;hGc-tx%b99JT95(Z^ zg0{z3+nS<+9|?#ih+#>TO96HhHpwo2E(8GHwbLWuTY!-6jNM=4II3stk)^bd*Epy5 zA)sML8Jq~^iDs8np^xe!AjLO2w{!yTyUJVg)M$g)OPhr9q58!!UNQIGAG#87U5)vM z^1`Xn@sdbSQ4t@l-)VoZLyULTWKVufsMV-VSQD~(|JNF3TSPa3o}=7KTKa)b_CTk$ zTz~oQH~e9rrFCNh?~K;q+hRf=dyC7_H^U;?QzRYcGHK~AF{+jx*Ur{LgWpu_>c8d0 zOpJkc%0>C>Sa$XHYO^<<8s@L#e^I2IbA#m2DzvGbdDN^DMnb1O$qbII0(Azf>i#SG-0MmK6-LZJD(+rA zIZB`>^K6w$sn4xyp%NIF=S5!#J)$~fO=n>g@6~c%} z3TT3~TK|#j(~{*dlmwX&$q{gvPaI+zaQYRW!#izQHkWzQOxgakn6n36fr}(UaeBBk z$J3ISl^I#dSH-#Cw-Q>%D=0nuLh~9?^oYS8OiOWyYteyRA0>1CbWNa@I6DghFjpp3 z?`w!Kqp?Kux8iS7LM22UmO@tmACkWREINobpuPxSHQGymY5m%HPb>@MR_MHR@-9XS z%wVv`*h%D)34`DB^i$GKd@mwUe=S?6sJr21HUAD3A$lH*GHnJv%2%|HBw`N0j7pwu zV7Rjz$ES*?UH~H{q(HPK0z)US+gsQxp_LwTFYMM!!~{7CQOsKww%}u1Lro1yxv0|8Pc)i16P^4W|w}jmNf4 zOi|v5gdb+!nuL%MWyVr-B@t{d0+q}5#q+{RX&i^)o&4>DwPB-LeN4v9Q%Eb#JPqtga+V=*Gexf z0}x$sP32YMSP=j^zHta97F{F8juX-o6V~%W4dBPl&@(I)OaS;1{Km7$vIMhqpE`>x zM3q3XqU78eC&AuKjOAQra;n}#YbFzNYp_SOox1>}lTim#&$G84AhgVu!>7q3l)+PB z2n+6&8|fnE`LCP%p!SO-{dDnBBt~gz8d_;*N6o3VQ!2*$K_HRAW z;a@R@76V3-(r^2@s_xK#4tY7jrlBLa&cluCDXywt(SFV845r&T@3GIz-*|a$ zU=SGoXcEb;9d}Mqkdyu&NA@p6!i_X5@l^)3S>XhQk}6{Od#ry(@Hfr~8Oxy`6*^- zZV4>eybay9+KIoD4)-cq=x8LzUey!?j+Gz+xaj4#_c=xtM`(C(D!aJRpX$QE9N_oDiw5CSzkjZa_ zn0O18-5DrJd^QQcJFol-UxCr14@E$ML ze!=k_DB6;vbOp?TDR;8mfzRe$=v@wJfv?h%{=q3wR1!E8sphMGX0Cw5^^Lu)IH;{- z=oXA`qvDI)>I~rq zZln@|Z>lR#k@zI^S*_p5BRA5{P}xsR+?~m!lc5)oIMMT0!G*+tn7=RPH=<1QYBNdf zws6pCV)teR(MS~Yu6^O$0?1Yk>^W#=6U}D_Mw`)lNCsUrZdSehr}?x>viK~u{6v`zUuFJa;vfjq2ymVw&3>`C+Too=*0J<$DNyE=aNXvz!$EIZMI44olP3aw@={BwXI{~?m)Jy< z#v^Ti1)0hK`hQIhq2HAn-=CskNxSCG8HOBwzV%}z*Ur?k1Mh_K3}P5RER?dx1kpO} zT2IMaO`z)uAO~*ZKo=y7&`u$|X6}Ml0P+I^{?z*e@U{M3he^5e&9-le(O^GzhFN$2S6bKjJ-JC#-p=o^i#namA~tkZn0N=HB7YQ*cJP}_dVp! z0zbLmUa)gom_3&&k^fWOp#5`N@XqSlqp+&bL1?FL2JA&l=tWG*bK^IzPss^8DO-;G zO?vnlOm%D((l6?K)Gt>Z_HE7ijU*kp?|I=W26+7cbrqu^yLMHZ9QA)CPBt4%p?tYC zLMa{=a16zv&5duCzIg2uQZ|OIj3jc+d&M=o5d|5en%+m&4+u~1;zqDm0Zjg&T3rW7 z1)|xVXA;Nu=#VYqw7Et#i+gencv9U2_gQOe`SDThiuAzyHx=Ln?IG_j8Y5vwGKV@9 zOhYUsl=Zu4DHh|vyAbD#U4B_{QBeqIx5CJiNTSDDX%lB1m7r$@MePynypr%QdXwk` zXqn2pyj}Gp15O)>weq1rKp{K-Bi@0H!dLmwP7$4fZ^17p&kogC-bnn5`FwjZu5-F% zr$~9WTwyIFjNV~&P%e`EBoz6?u;xVS&D#^KQ-~nO5DuxZcH^~px2-x8&-VEH6K<(q zoRg#{jEP?r3Lrw-s^Hku zA>{lHxhEd;t{AA86);siPK9_4sk5Ro#6Esysw5}+@TK~Pz6?FeLc z4U6Nkg~w-aPvj!P+bb{!O?zYFKOWam_XW_6ranSQ`|@c|;gWyT*9-A9P7iqx$toJ_PA8Z%(MnZ>9+o z!G5BkA~+FqqJxsW#^0ZfSjPFzVH&HF9cFbc(p})qXBtmro{$ACg|ov`eFajE@Cx;N zqjDJrg66T$g?JqdcML=Yw|E8m|1!DW*LmA|_dNT&K9sODouv9PC0u;GUL;UvduP%) zPb5Y9G=;4uEiuFRI1A1pI@c zB1B{i+R`WC#T%lo*GHP>1R3x}Q&4?w66?s0LnG?i@kaF34;#E?Jjz)>l`=aaa$ZMy z#pAQ`flldW&Vy3GTC=gyCzZr8|3c85&`7*^^={jOqXwE^x=MD$6k+vX<2>YnUeSRl-AbJO9jU~2Y5=ahIG!xHJm6&*I&P1ENDOO$>BQ7+1%1`cp zlU%>*gG2@}Ot7{OkSj+yfFD8PK@ktZJHg+gIza#=ib}ol0amTcAlwN-yvq821uy+U zcV(J{LccfI{ilqFW!EAC-=4yv!1c=2tk;?s0eY!1jzW9eD}7KNf%gmp7-=qw->MFcn9MHaog5JSB$BQ1$vC zT@M#IJot(gZwk;=l{OJlPhMlkzbrV!b%aBaBO z(m*DE@(;C3Wq(Lw`hF5+;i3DX2pkq0bwo)HOmWd>3uD7?4k2j|I}*QB!9@82(7Dd) z`uja8TM_3m&HZ1u+G+avl(|s*M5t8~iBRi9|hd5Du>|&S^P3k9ByZ8gQ?0d zyl~zh^%cz)Ix>03ecsY(Cm&osUv1_s63);9ZnQe6${@ssikU_JEnR$~ww)znbNMnh zn;g*L8xuEFDiRm}v4xLPLq{VRw8U!Ym2{^}9o}rwRNk)Us%f=?!mto*Bzl%Gyrsh( zA+ktb&Y2cl%41a<3w9{;_x4%kGOGHMN0u@>pYLNpF{_4S>XJWBO)OOJ!niQkvW+>F6Laef^)1$ z+I6RcVx0|jcytg*+NtdxWREtNQo^7q$n>;D3omuvm;CeIOrxNziwC4^O zX|ZV;T$5I6Rx4OFXsJSJyRSJ6hq<^V@*xIDBq01LpLwVJ^NX2q!64ck4mr?Z31K<${N#tzt_zK3iK|{rx2ru76$q*jt9TkAS2lkfpcGK{y$S-399R-! z-;>OQvs+&gZLznuOg;s;ZxjgDT|{|nR_kPNAyv^A#_0J;boAzXUwUmK?B}@eMC2nM zc{RcN#5pcYROLI_;G4#t;zGo2+)zCggvo-PURCj~DkLDpD$~Q|)#9{A24Bw4i;bm$ zsG&W*`gOejO6Ch#75K*%5GDrl=3OM~ph~$_@w@@s(uj%h@c}u^qx;tchYY7eUBP-o zTq$m4bV0m3jo*75a=MJ#>J(f6>UMX!GQSJ%@*}6xw_2s+u`UUo$P?^E2`R{Sr9*NU zx;2c?0qFioxJl4-*Jft>2^3UuJ1!tX`y4r**Z@8uf!5Ko28eiUb$9158Nv4GzmnK- z=|WP9D(H~Sj2pqAuy|Msa!B6-fW2y=qVLOGZm1)(uxOo1d{l*@@~2IJ);g9gl$_o? zu7|3OYzjGiW}b7DkK#VbZjW;4SXemnh4f}iCR0A6&b(m<x6@5E~!$eL>UpcdRtY#ZBh55dWz)VgM z?bf}NS7(C$?YhSnx$InO&mj$Aj1oL1jO;b|IZy9JUczGp+bQcdy+WW}WS;?&hVd9V zc|V`%Ni>|zy~eurFlVbYlR-n;{kAvu#gSnf0Cp1VdL^8?_D8~nG2FEYc2o3ISO*BK zE2F#_giN`He}3!H61Eu?^lVL&E;A{G{s*1tmmhS1D7e%;WOXwoxo=oqgI=6vbr*=H z8jk(*liV;JarO1PrizLoJMU@Fz4D3qVfH|4VB!LW04m5PHaaG+FaGe35Dg{ybnQEBL z0n6-G5QN|oCZ4vP>^N8mB_)=ma9?B4+K{mrX6SOnfp)qSPqnQCFi=GBE$AZNw^OH) zZ$DXekDSp%S}Uyct7a7OAg;ELGdWMAVX8S-^?-pu+!v7(5eQy`X@iLbQfU=zL7g^~ zp%2om;*;^SnVdVx!TVe(?8NR-EM@*@f%&!E>83Bu_4U-+MGDpvk6)1Kj4@tMxkhs! zJc)JIAr5Y~Y_(k1E9Px&$GR8dSLE7u5Z(&B*Pe&3M;y%si)MCzzh4UTCi;9DrUdv~ zbZemP5c)*%O*57O$H2-X+Ks)XMo7-Dhk?M^9j?VO>5cQ{wnEaghwo!N^I<-L0tf9} zCrY=_R*Apm3HtH;j#rcsE~Ygiqx+V{7{FcfuHa4|xQ4F*r}qio2dk~fn?rTlU`y}Z z4||3q<5zaU$lH>3iMwj3$}#;y8Scg=Nr><@kd@HRt+#8qUwp1#3&_$QIa|(!2-jjS zY$$$IVwV}^h#(Q2*VP)(%`Cku9dpdTcO0LH1>{Hb;BYJ$WBES$*BTXk@etJ*RnE6S>N*BVAD8rh7LPCh;cw z^RmqklLG}UNB-@Jf0{Hn`fPl7HhB!eJ4196Jh{pRG2F_3wg)lKZB~_A@^Am!t`Li= z6}N?LTkl1Z{B$gnTzA&PT+Aa9C#$FL(+`Tc5N2@2A^n0C(drg%1| zjVIMpWk{{zJh}_m&w9Jfj}w@+1ehpa2Vtt2T85r5$kot~J^2=AYFGGqx@*YCO(29p zK@L@#SwbxKNH$pXWB9F99FIvsZ~)A>9>0aqiQ~;o3VBLavZQLLY+4#a5kT#e{ct{xt?eogVTqaVN;!r-x6QXYz^&7fwMPC*=~^6S zLOem-agU}yu0WxlmouF8D@!*y!ZZx_fZz~^C>;l|i2uCpOX5(Wo|>FMrufziXDPnf zI)!M)C{-hMXPmREGlVe!gqlE2JiP~YxI~DV^5Yy{oi$F?5Z!Q&eDNRY$=)MDj#e;+ zgWiWgezYQxV0ovg&rvGYBY*5RZ_RM$F#8DLa5>HbJGs#tZbx9?Js_J6G%PK&;kN1X z)BE&rOoqF`61zrNhi?n`v;t{-3vu)3yyf5Q``9{Lw9m*>jD_ma;R)*t8K3pzIg+1P zpB5b&TG<;lFvQm5e3x1ND-;d``RS*7J3)DI_(_%tVbvLd~ zGFTO{U}ub&HXUKBVkQ-gH~o(ygHh4vL{)W}+0(GUxiu7oxpYi@U`!_*uU`f^X>5~Z zTo#BhkHZ0f+Nl*qAsl>D?<XMtHQH74E(SjYQgjEJQWx z9wCs?8S9(qkc;W*vc5C66k3oBsRkN4FewxYb3qY=R+e0ND&Fq62b3OTI!4!QTvzK* zCLfH5X`tNU9iwPmdwOz_9vvXgq5^<6yL@>2iDSi~u}38;FuvB@BgP9*2Ww8Ji8II6TD_ zazQWzH9%uxpg`$?tzb)rP#v}ULxC&9fs1IN6N8`{5>w8kOL_meZC=#)y?0d@F6g>U zp5M9huGequ-SL;6RMvLAXL;scdKkZ!8RLVJz||AFvC;*6Zm&2C}eoF6p%3J*Ki#OJTiA2WvAmRA_c687nt5 zeu|bdf}aRQ$qD=$xsu^Z^14l_Tn4$x!WNi0BVrn0hmRtT7p7DRpzbktVrVdJ9rtr}vP$ zfX$Qr;hCSUd_J?y;2nRQhrWsxpJW%ePvmJEfp}RV$}rb5Ao~^n+-ZH>@Ro6^y_b@- zFTjFGZ5X?$C0n4PN&e+Xx!hTtf1!BZr_kfI`J$3eZ?V*8R_?>_4Vc-cI}c!JI8+g| zc@;thW_E6t!9ux?&Hk~Bv!17w?I)sJU5`xo!=)`;a#RNv1xN8hqE-W;7!?|UbD5*U zS+Q*tgv7D@Umw_5dGF*nK=?K@rvPkRWPBZD^1>L8>GQ=~Ig>3@u53J0nwhiWHWN=r zkS=D(}i0R@!50;qn4&bPe6Ga%Y-(GMZ&XUg4?Ug__oO~?!`_PA^G z_bE6=zwk@>h6(%w@nE*uO19oS^LScQV2brfYxhJYr%{m21XzVCVOCZV1)_&Ah_$=k z;t?g7dj3q}SV5Q$Fq*N@;Ey6iC>JSG(?SYGNbkPEOK3C{(&F384`o9J2=AtGJdZd+ z73nk-B?_|pxuK0{Bx8QxSf~m4HWPN!?z44o4mUP+Z!a7j54Xt&>ixs2KoE24=qs~^1yC-O z!kT|oWlJ55ZgG=CZ9H8%4yrVY*a(}KCS6pfn&$&*2`ZDDqGOR+E1>BXFG9K7I(-6v z{Pa((uR0m; zZYZBg$;w=1;pCq|7!#>ZDgSBhBh}bReT8h2M0x9Jp~&*IK}EaWRK}(|J4>|Q#f*jY zLfx`*({pv`R=&)<*yv*zw|lI9ji2jty8b?S{=HEMuzCg=rGI)ET-DQ0+AKFnVcw;Q zt@m|HdSUSY=X2K6()CTld+$7l_Pa3lX`^ZBFy|mlXH`eSJrQ{4pT=~zp3m8V!$lLi z8M+J@pzxG@JY$@ZaRWVUynS8J@b~X6Y+d?3k5543U*Mkk-+qL%c;_}Or-KhLs{KUG zy@Diq@^>B^Ej3f;@TWC7+y?yFOO$ooSQGUhLa~Ej({0RDAEaYL2AcOWc68lY$>`16 z0j>jIcW%`hFb;?wNeRJlyOg~v{8Rls8rJWSf(VH#BMq11jg8RaJm(85mp8S64FHpjj(qF z54-eWFT`H)iDV1~e644dec%3!4F=Xu=QQ=67jORRUL&JH72!z&qr?&yJna7WLq+*D z_NUdV@`9AlhzaZy8m;zJ##=T@gndhx8Oe3!*`osHWaww&m1ard8p8MsgJFFqxG(kZ zqMAsOFBiivc|F! zA%qgut`Vb+Mepo-Fz9c;mtq?71SCXNlnLHd7-?Z<b{a$vd#3yOjKD?ZtJiX5K0jDRKaN$uxsf!Gc@%mJ&A<%YKrA#=rkbB zR3V2(IVKPkkg#~KNvW`Vdwe^)xq99L&?qB*7SjDpW{3eZGlP8)NGY85@r8l?pZ;r=$4+k-Y8e#p9*=20kq>1X+O z#Op?H)q6L{qbx%mEu6aGnBDZaD8X^TiGH;P z!?wl*d6usXV)wEIG^FZU%#spZivBE2-(Id6E!Z$w`GF!)v%VpKuSVA;&Gcj3J(~Sz z-Nbwd9k01{t{D%xg6W}KaCYKFxV0>+R0ZPydI^IF!x#NXqklk%)=6a_j%C|`XGi15 z0_k|fI!$(7+v<)!Gw&6|-|OfS?B^TEbW!S#^0JhrDG6|_>t2HiohIs6F5|j21vx*2 zrJ$cY$-TCKFm64-3~l|}631mGpITfptyZd6;cF5|rbHtD$SJ{VbissC@(RR?nvzX4 zK?Rnv~|^ zSnQLCF)zdo|CfSu!dLG?@mYcAxX7FSL;tOD9-TFfVNL>1j7^svcpA`!MS) zX+aZ72wU6#WxJemKXpXx-=LOPLK|G!k5<}M>|CD1>lGE(dW_Y0$CHjyfCATcu=Df2 zrrES|CwXA(QwaF8S~8mk$@v8Hh2$q3gzaC%KIQKi3JajG$Y4KQCOc10Npv78)0ACU zg9u(F@L*FE675eEfNUwyk{`=nW zQdE(McEf@tsZUJ5U6&REitMOD)@rNsR@_A(4b401>RFsvto|#6>fe+P-N!VR$CsZ}B9eza-?Y9j zPauvrr{y$eT(Mngz;vf^R8nMIfb)ZSIHEFudrXv&;E90+vB>3=rd$FxBPuv(0Vj~9 z1gEtrf=<>zru+N1pBY2G71OUV<_5Zsg8w$sSrUi-7J9dcCWoA{zVHYNL^`YlQ$gbW zBa9Llb#rQJ{j9-L6@G3ThN&dhAFo@-j|J&T$!H{=8NL~ zoj|2Uz4AgP&G&M)(}Kup;(acy`)$ATY38#EQdwc%!}3h_#`Q;x+z#B;Xbq;jYKyZ| z^|l-?7((?AD45ZI&y##RvfL;w zRh<5xS`{`B^S;seeqvbw6l;l@b<%4)-}@+WK4@xTbYF#o9ET3e`|O;PK>0NNkRmRq zw`8~oP4g=;Ch`E4n@^w=`6;a(n{#Sge*GU(H(DxTF;=C8!F&xs=1!vL4GZ(uGtuljqP-%Q`h4Bty z^AE~>Kl{O|1)mpET@#@(*HnqNwiF>i18?X^m$aorE=o`VCOciju`7PqdTv+ z6DdE8PRjgvb`)R!3|hAShN{M?kVQlGz3aHpNn@se$!NC+Ui8XdX?*hf8LLGa!sn>d zWFX+NOJ5^6fMHUrcPEi^)zS_Be_Xv~SDeuntV?5!yL%u6cXvo|cXxMpmmtAi0tAA) zGz52P+}+*X9WHyEJI2}j{DFSI^cri;npID|=0)-}<%5TtR7VT1|26hu`Ca_}N;+UJ zX1zQajlZXBhT$DEDjE$U)wG!^>V71da8qW2V|uGmpuWijsyF?%bhujVeBEf1r!t;pDVuLZ3-eA}43Q$7hHwTTp%-3urq%a$q|)oEo3ts3!GG(XS)l3m(9H z#62G@0d%J=COC+N4Yzlnegdk9MW`t(8`EPIFk@@-Ly5D7{)x4LNNCzEj^P0Jip^}4 zKuG1yAV?3VmzHF!0XD+LDMjQ2?@Is<&z=Ro4@ByH?G>D!CN-{?8hx)dK+G7c3uU+| zmwf@BZjvb#FNbxYzhWS6llZ!0rBMShI+yZEQ9~@|4XwgcRHvTm&2mw!_!IauTBjc+~*Ft7Y&{}0V z+5Jt0UniijemeNRCpWZ-x3G@A~dV%1_7i2mX*3*lBouL9}!Gg0fPRL z00Q5;M=T>p{0C%%uK@|2Kh?qwuncxR7@y>qwINvoS<8ogN$xMhxtw+`rM7+g8%q^D z{pE7E|2&F!e*SUH710`qawrbJPJLrLXc$vvtXzi$$V$n`2Ha2=%2O8(;|eC7+*LaG z(GB21i$b3IUMJ;b0e%ce=isAFI$z-$FbeAU)^1bR6EX?9knV77hs+I>IV`ZsROOLg znuYw>dVnn^4;Y_u$Hr^?cePYTR&0>vIvX-E8)Vbf)P#FxnW-p0od265J;^#cI6Fdn4QMzVmEu`8!7GWayLNaPw2^hiF13!ov0bu80yMjKbM!{S8C71=aUxXT% zo41&m+h9VIG;96Q4zZbCuD<7;Yk!ztWM4kQQn|5y#UW?0lLg!|XO-NuU2+}y-HmN> zqLxywsmNLpeMWWo%~=NUh{R3JP4OZpERB{3YuTOHdTr6oPx<3am(Nhg($=H^A8Y0s z5zbKna{}yas4Oom_l+y$lYo_AeQtEhsimB%)t7TCiwBFwZZ@+06E`pcwtTjrn?`I_ ztcQm0;b*frFD&ar|EcQr9{-vDj9iLuzH=E8L`fz2ZLPtz-z0Mpv60ri-F(_6RzcLJ zVZfDBB6tWRt~J7o{@u;_Rx3hDn|=B5yhJIy`LOAbDYikR%EZkK%62Av=BC(%e*kYa z$)B%4mq_yz06fxo#x0c%2~A9WNDdM#-Cks{cMWc49O(hoq_{+}z#caJGhd!APVHtX z2xE0g*ayTVBM?YBiHL8hd83FKmuJ0*cJQ~V<2`~}X zM(O;ygNtZ3WYTWVVjkwy-rDOnB9_wWdjt`w?melKc_#u}6)*=WbXn=wMblF^PR+lM zm3BjCUiaOlx;bRjJ*HOU7GX-TUe&_plQ5l#m*5C#06P4h z$Y8lxWs&a(;ZI@<_BC_qd+2y*uVA~dc;`)%*h9_ox+#x=_(aq*Ewo{KwR;Oc@r7&< zpz__#zWyQ2{(mH$mt|T~G45Pc>+j1-aS~hz4!+jWTbyMQCwVuT0wnGxADSCX^Flbv zMYBopc6~0+h04c=aC4cC`3Jc*<8H7!1VS&;KP+py?{l_nCaNxsya`cTJ>~P_h1cr7`23yWutOd_ES6ADJ2j1A!S33kF6Q5W!zd>C#y@8u3jGSF2PayX&OI2ku1DC4N z97N}^^GRz89L0UK|7kWpYGaM26@d|45urfS8wnZ~^p} zLA`j@udY)A$;~~iJp(u zC=2ZHegu!uVj+~68+c|EzqSn1;APP`l}0%F-ZF7 zG-V9hL{`mFI83xudkGoj6{nOT5jLxLWIN2koM56=5EBYL(x42MZC9nHfN1$0)nR>S4crGgcDr-j$d-}SaCUlSdP|i%c_@6ZEL>gFRTqIMdcT~Y>S-cO~5ouu}t#EqCESd&x0gI zaxu-+lLI~@$jqg_i~$F|Sqv9v^6=AA#0MSGHoLx0#+w9zz8(_vmEG&~h`L3E7o#gq zRF31k5_~o1L#9RTJmYRrHfZm{jf_a@LN(BebfGSK#OS2)#{+@JSW=!t9>W#;7ZrJs z@G!6^2N8~POcV7FU0aWLYl{KddeLHt7~brl_tha=X1g_u}1_J;n!2c3{KJU`| z7?`HAHPjV;6s}E!wmcDtkYQQWdjhw^Fg5eHgN>$N0%74x3-hRNbxFykIz3pN?>O12 zVt&rAOI}1s^tPilLHFy1vM8L3f0jR4@}M{zAx0+OU!yaW*Ff$>{SW}nK{>BcId9L^pIM8@wrdj3y)O93)Eml?PF zh)zI?0N;`3YHOi7w;~}>ux<>45~tr%a*&Or#D}i)5GB57vOt7^mM(&rxUh1S*Ctfl z2;mkD@ZAeM^3N>5zRE;g*?*gNI<+2s4@>6@1Jfn0seYsFl5S9A7X6G2eSTac?xy6; zwdbOGIrcH`1ntk2a?hVL9JMj`s{NDBQ9@Vx*SOy#HP_seUzqAlzHCoEn%38#JX2!^ zc1jZh@jLa95TsurwlC zoG$LyGVK8kT>?PZuh}7aT=jUW4d%*J_+JtY2S$u^DM5HVXb=a}FyY@}SK8G88TMCK zr&=U%k4s+DiZG73>8h6<JAp^7sXSgD2c&UvW{mgm_ns>4c zhHNLa7uLK4CA=V!F>mlhQR7GcZi$!pzL6>8^MZTA($P55G#@UWP_zpU!aL;8+H__r z%f`V(mAcH~YRb1l|-J?6q_~ z4KCjhEd8sAeUx3|?ld1;<5+Aaw+q$MB+0i;4$xv?k?uI3bdq%_EexmVI{KaWT4NU3 zG7U_^z8d=K2&eJ^^yNN2Peyn=>HkLb;$&b|9v#dq^Kj7-ItEQ50l1Goh$JJ-=RbUW zgBs-`2q%K#s&Ub{m~T)(4#YWAdl1_hSjU}sa@VeZQaUOOxJ|D2K)X0?a9>+?z30gp zV?t8jLKWHzpOGL%= zF2gp?a`P`Cyo(ten|c@6znd0QYp^!I^TwKtl-_DSd5L}_7`SDC5>;c(L1a}VfERd9 zL#+87Lr9RJ458px^{jL?unm>w36S)@Wtsb)8z&G3G18$4XYg-BeNXy)=njNL!(>=F z&^+;J05U)yC2LA%s!C$e=s#RZ_v=eW&0%>^DZ2%G_wwbGW{Tn;S0IqRd#;yn1HX~E zfO;aukxOGiO5%c1d?NJ0Mni98U7tB%nbz!2rystn8T}RoP;u8knW9|9Ic^oGl95GK z3X=sdMC`V%j=6Ulf^CF==~ z4lQ{ZRorb-2}6cCos0=)6bxh6;EE2?ox^3}PGnw&zSoW7d1Y1yu?&Sc$IEByo-od^ z&Scqa4sgdpawTX>Z`_GAH|6J|uE&@^QKI42OIcA#Mc&UeF@v3{=~gzfu{|Vj_ast~ z-MU%Eb4GbYXu&);fP1os_A4;y?k^dV`We+25aDSuZjz#-ykW{tayUzWFqvM)4M^CG z3_;ap{4qyYMoV(U_cI%_SNYsv{OCklMOB%b-j(MP-DcLUEMGdF+}-@P2^8YKFgLnN zSsIM0zd2Cgx{{#vlVHa8OA^WEq#oU1n=+%j5898b*lYbc+*Mc1jU*-qt5;ylU9;<4-8lE; z+y@r78^`6I`Y`$HY>s1R-Wtnd0f|_V{_(1r18I;iT9j-*H41MTV1(Sw1SpO_IcPDO z6M0U&%{aI(Ikm6W9_Kr1|NIin5+L}xg<3q}yU&(E^B8ZUpwM~yx$zTHUdpJaJVR}* zeXV^}__*D=?V|dVRrLyfn0}R`Y2OuNA}Emj-wRcsA^WLJSwT$V+khs?mfVyS$c4Sa z%b#H%(8{&+lr57wrgu(B)j|Ut#T#2ZnZw`k2g| zDNV4L2*MW0g;eD=3fNbrgVy?q0uAG~V=4?u=EJ`G3KI^(!fFqbx4RjZQ90{|jUh+N8aFH?F4U$#A{R z_OQ+S909tX-pFPNVHJw7O}_5Oi7Y zP}<|mB4~|d_j`ye-YlG>qMD#De(f7b-o(jCY+-elxy6(=qZq{x;fPE%GG2O0^LRaR zeKQsWYxew^M(ZNJR`aic^m1p<%@-z%ISw>rF7X;^Z;E|U26mIVnPv=4e?$D))eA{!kV&S*MjL5q zM;vB1s~Z}}NUmK=(JJ&^jcOsV8Fx+;7f+XEsf^*v02c^Zuw^7ok*Cc0zOAN__P?@V z2ML+!zJf^?NW3jM5~K#2 z&<|a{C@HHyX$OgXYrd{hI$6rCT#aMdOaG#fk1R^eO+?B_&X$0WUtpYC^dvXg~rrbU_(-evt|AZFJ)JIfJ?4vUk9 zIm?qs!QuJh>G$M(_7#$eao7;{-(41-=9Kyh!|GjnNDqpg+g?eMXGZj3peQ_H% zW5vf>lf5SeYXU;$XQQR*HHxUKL9sF-H zGYSh)mDIR>D;`PN$m-p`K^>r_e-<1jka(^_v9%&9qNDwKpshm>cYJ$xL#DQPESz5u ztp2i5vRyg$rus#`zZ(t`p-6159qYguUa*IKrkariO9V{3 zLN}Q}#*a37nZK_1oC+^?Q(+-M_mUo?24@-0PJLm8I6hVMm`R;@omvm`zz$1dw%`ye zMA*snuH!r0b_twPz;ON~M%=XZu|bR-(%}b%6|!DXoO_72?CtAJr*+&GW|3|Z`lan$ zrT8I}N%5rkrnvs#;j~+wt?%@A{w4V2Z}!go+BbjLn(5HYBXEr#CmZK=YT~jUogfy4 zV=kQf-qFmT`Xf^Wt32i5wi8isIu&izlIwM`>;aIh47~n}QlHRQgAi@|qd|%5bx-$4 zd$LXCb0?7xB^ke3NX?9EYVr@>$=G359}drc8P&A<>91z`OOAmSc{7mC!`SNHZ6}$5 z37p?PT~Htrw*3r4*e84}%p@Ag{Hfewnqh8Me=^~pvr+)mbBebaJe%2Y9{>qlmKrGh zhVUNaY`Xwf^lv57qvR90n5JX*!~9VFE*0W5UW^kOA|7K!T$HHFw>xi`W`fNsN#eK7 zCOmFMXzn8!6INbJ)6^+*ddp)yhS$5&CV1b>+JOZZSokXWzj@t~@Hqd-clSz;XBXxSW zd`b&e*G@H(_Kb71{|F*xErt!*ry;##hXCAQfO1c06&*n1czTZbqMAw*H4jgf6_4w3 zJc>&MP9tBSQ)+cH*ZCa17eHO$ms>zZZIwl`V!NYc`RRT<#CM!y;MV;3l6eg7T-s z#>%JP>!Uhe$8K}}TqWNof86KH90<9)!@>h|9skF+uwp{DEU^IR>*ncr^;8AKX;4^h zm)(I~c{XyHR@v4C0d?-Oe7wbD1~<9k7Nx7?Em0@t)7-y`L#=%N4hpAEi>s7&-EjjF zXjKc6J5xG`(qC#x^O1<<3L1N?$R;m-7txgRO6fcMXQa6i!@ZB@cH^Yj!7&X2P1<-` zf#`>|R8?7Ex3*ovrR9n_!AC=SAkLm7TP>XUwk+XqnGa8~rlTSbrb2o^!=^JpcuOU8 z?*33hh&ROX(J|+-kIFpfwz2c=47MpLnmo7J^#}ypo7=9MTDPwI<0a|iv$OHH2#4>? zV~W?OqfbR!bxGy;`w}#_(azNQ^S0W~j`vCmKMkFqn3ko6+WD;;;_8qomgL@VWDaUc zFB;(9*syziZ_&ZQHN+@-z7i`w(pY~~8vEbf)D*KCroM7jzIy#+D98Jv>wksn2bg`| zKEee~jgE%tsw^X9J{P`vj7grtBs0Ghg-)D(m<5sR8l8HY40d;MtJO0 zhIUALs2sfV2yiUDMF|JLE_p7rt5(i`HOJY7X&!34yUk?)_~aNzi+7P1mNJxEKiV41q=Hr4IOxzX3AGG*0f9tv@rbTqb^gJLI&tF2fXA#>f zs5m}nw!%|Q#6-U-O>pp7y!U+!7w6z(M#Y~!`+73G(9dYb>>sY|MJwr;{ix^3QU#@6 zJfqvLymrHyC6<@#6|h%T`OhrY+!BdT%|I3*hoSKUixt-Jv_RbpXUo7&5{Y=ER#o+h zxa1hQ&$dv*FeY~*xp(jMRs`JL3*+shHCSg{Zygo{&knTzQ#3>Cyml5^V zrvdM`aYZN9t-K>qkmsA>eF_mwm;t;U;_zzQ5RcrXx0}EN&5%bFDPS|;o=Eshhw0~; z56;_AGq6`3A8~>MycZp~3AdKiP|?sN6<&7tKFg>f^&bTyTV_~kV#bhXgq`rm9pzUF z;+oL9J&|yzZ#tzMUdou8>o4h-(QdQBa$`01UX&{k3o)P3M#bMgy5$?_USfT4ouOC_ z)HpmBN`+70lf(Ok#jP-_gYbtY@WKWozk4sW{6lCL!&j|km;WN$E?w&+ai2gE@ESs> z#=DZudZ?i8q-2P*!TAtw`-9+hDzHCMmE>tqV&9jxXd~744@(b6Jz(Hz#OYYld-xY> z)-`5lqDHDW!%B`jxq*H`5Hh^G$)Ef7Xi2|!j7-m8=y3cFbV2)8-2&O6!B%;VtS+B6 z$ys)sSkIvEwDJcs0|j-ayi;1_6|4o++@X6NVE*K#{WoaAcu#1vy^bv0K-GJ8*jn9| z3S*?HAr!%330~L)EOj$kny6MNZYny(WZj~qh`Q+h4$-16l58tK;$84)T{NB-oaYc( zecf!0+m_>ZLK#MDAYU^#*bM}RmTXIXo=P2V87`t|KQn7(bwYw3zGEa{j9~nlForvk z+30q?YAf6M{_oA5LyuH5dO9DS-3^`H4Gb=m0>spPdT9`knV}TLA$8X`N%MDz zbYCM!ea;-P#k8VE;3R|FrLwTmF%nvf(@v3Wlm_s(HvrB#U^FzWc|6L-%H;Zw!NAMN zboXg+39g?zPt79@#c^5@3(%^3?+yjwAcBRJCWOyi)AK^xPs1Lw10oqvz96@0-h%QnuY}-Yng6y&yh$|9;4nzV(dO7F=+v`UW;F&RJDxMXrS_3t~dNC%b|h z;o;)=eGCzdPBrM2=5>FZs-QC0rRkOST05sfNY2R z2!aM4H%#dFwPAfqhuN^WkP9){*N&r?jgQ4sU;%1%({|*k5V$Y3Db}0IP9^+04+-Cp zZpD1AtaX@I-3fc>;FkiwfZxURMbUq@i2_Z~DKsM-3;HwNNqrq~l|U(=0sli0^1?z{ zx(Wxg94`xu6YjMCUAe(nAcyStKsk)r5dDEPA01frl)< zM1s;Di5cP(7o^lH2MQ6gG9YJ#po`l2!C6Q|oFIwS1r^`8);_NW0x4O{9peE_t+72i z>06y*jU;S(lBRVvL<(zu@IST5>Bu~~5E=ChTN#?x^N8FtX#e&!WtX|0hQhz~EsuQ- zbtvfdQ9^+gQAy&2A6JtpL@|7n>^HHwIZ@W89N^FfR90`H75O6_a2q`vm|`8J#57MqA7En>C=i`wSzR7ukpB62( zp9t#OC%rJcRNFJhNBs}Pt&T6ubyT4R^yFPe;jmmM|}v%Ov|{ zQoOD4&1(X8_;>3<0rJU(bq76?=;^m$qLK%QM!X9{jD^Z4DIwU?e)coeHzFjSyFyVp zA%sknC^9jObKd*SIu8Y4t^5~jV~95bn8{~A(MKRADPq(!4^@@O6=T&82E4@tt1(nX zvDOvWSdz>8e=J;v$mCQlt@p_@`tc+I!wMJk9BFHw_RqUdJ`>-YkA9ODEVOSvOw+AD z0goc`<79K5E<3*sV7httlD4zFy$QYjcwSKO?T@I9aJ7OvVSi-~q;b9yQ0jCzUYs)f-NkLe zegEY*U8cZ`;(DItpNeve4U&3%0vf@S#)-aCDmem1;;!y)6jqA+O|vw;)VGLXO>#M} zm$OEWf-mqKcrKdGgGJK|a8nQ;H2xCUYk8yO6F+?Fc^gK%yaM8 z?ADhse&aI6 zywy`m0)=5}fSzGff{&?`BJj??Y7cukzY<$HTT=VBM-4ZutLja>LD);>lM_9~cUt>1 zpN|WDydpvVM@pP{x9$2)DUN(ogjE4S;U}-3=&?T$F4@@prMOg>I#9Z^+|x>v0Y4~p zFHz=`60NjYI17Y`dxc2p&l*f)-M#@5%^LV0WS0+L+Yk2TpWQVjKo9(TT!0BIO&c8> z;NP!(^8RryrP*T|Al@l}2kB+Lzm?d+DhtiJBMu3Pg%sW#5{_$`e=C|10&Y$kqw0xe zTkk1H9I@$-+-vT_ioJDhF$}z#hITgYk^^FfV2B!g)7YD=?vm;POfyxOiOXGlRbo=# z<>E3QQ-uutD_;+Dx1k(DXg!#i(vB%?#g%<@;7%n=PMb{5(DR_UWv03{!+Cj z`FQ?m(A0#=koTvm?V=A+zFiZOB2!$F8YU2t5gfV4YKj)tF?|*Dn(`E6WJKqWCNmzT zQ!cJ-G&J~-NES9qX1aS8EN2PMh($FKGNy->jv-GPG5`m*BH)=M@Mit})#7KiITafy z3t79HgFDue!y+UhJSo=qN<4Euy4l3)O@td*BzUg`9jGPi>{l$F&7I2VNiQ62<&G^Z zq^_P~lTjRH!`pNN`OJC{jXU^VR-kUzvRx|hijrG;rS>sto*02XeU~az$=x8?)=Y_F zgUG{)qU&XtcYQyVze6J>s-;x|DPrVJ^VA69yab>>A64nru}c{*xz?<1WnvP}sNF;j zWz4niYQk%}_XT+*{`orJYo=)INVrijT|;05&dM!d64xulFr+AyZ7h@qjCKmE!?f&5 z1AdTM!xF>gJ}q7%r0eNCYel1P_g5@G)@G-qwg^DHHp35FT8evn@r;AZlVASjol9`D zcM$M?;E}+_ZPD*I~aidR$*^dq?b1$kl#}d}QX}EvDUSZuh*r zWMDncpxHzw@Y3l2rTymPO^i2BGvUvtbml?ue-idN0HA$OXhfHOriawY^fI`(U`y0gJVb?wx6%#0c8foZbj!-hgXid z4~hdJ=XOt}5bR71+ zH;P241AeFDnZkMbjnzscaO1VD#n+9uhyLudM)zA3!#Hcws(4_cPkh+w*ACOX!5%Rl z(q)u*Y=b)gC15fcZimq;sGd?#H|61SAyK`P`{>Fi1QneWwJ6JI<+gWcq(82rn)rj; z-$#V+8GaRkl(WO`J&|fJ6n>DSjAeXO)s$^orDLvq!SqtekgM_K-cByl5On%Anb`f& z=xEX`+`Q9UeYE_?&6MPT(BmW0H4Ecy66V77AVu8pC*|SG*kE|W)WJB1pV0IESmtkU zx_s$&OTO0=4Sg@OmaS*PVdqqnzpw~bE2Mt6V@E%yFFwW`eDm>rjK2r=C1(G6IVsx@yL0^p&;Z6BE1|iw5=<1G&B}O4dJ6VE6i&;BpI8X%Z z%T`j%a#8SQ<($CA(HcB`eMf2#|Eb1{7TSDQ zQLsgU3L};Uw_BRGUGyaViH^+Tr?|~aDd;hq*e319`mwlX6t6LD)DQydt`CXU1=AbE zM#Br%p_Ky0{P1^<6_S_^N+j;Y>e~TIF*C4a|5BfWQMMf zLHjyE9kLcnjl6j(Xpg=}vA?(R7`QK4@E8~ZxZ5szqa51tG46(d0MsBzO{et<0O;H({xrv=ke5{#4}KI^3T!$ z&k1hhrmV3jt$i=$m;U4Dksm+V8d<;gIl9lZmkYSr?;L&Wj_X=zJ65y$Z$<4q%3bn_ z|0NBTTd)om)zoJ)IP|D`*tz3*@TJ&7*T)MU&tC2!)(yRFe8+Fmh|lQd^pr;&cFXdW zWWD&KiCbeL&W}{{#^3n3rRcA(oLb&&2{s&dM*UEOC5UK+lYh|=7*na`;&K0Rv~wWf zulY*NQorE-8nx%+7mE?Sm|O`mrbN`Dz|sSaP#1P!7^uScMQZm;fAFSC zvGGMl7n&N@-Zzg#&WFd8N^@5y{9yz!&EKH~Sjv+eA)_80_LY(f7z>JH$dba(M9{gNGxAET4qNgq77)@x!(~n}`a#fUtdK@&1 z5B_S_wSY{Ao&*yS$N%zarS-(H9i?4WBi65i*0z#44r2QAnb~d=zax-+;!!X4BT=8f zIMkh(Q4kVH11h4aSfOs<^^y7R?V6C9r_vN;pAqunNt@!y&pevSLlFum%7T&&k@2R{ zRHJW(=^-Pga@px&HXWi#zoAhsTRX}tnRWA>%e<>1w5t^g(P)?!s36G0~Fy5k`Qs*Ug(rVc+N$0V_;wb5zHcbK%n1oFy-YjG}`Gg8!QEL z$(>d(q*sFAn!*OXdw!K{37HY;W7HA3Pi>CJ$AAVj#*vDdC5)04+c(st1pmfYqGAIn zi%t1uc`x<#8Sy|hkc7)=Hw22bK!Y!&xilD@{RJ^tC7YKS6<_9_=K!W3Cf+4yK1eqo z{uB|?MIqzguGG48WDk+9GB7FyedI29M5$*%^H;Kk%Q}pcDPt$7!dGNk-?zxV&Eg#D zoH*e1Dn`~awiZsN2DT1HXJ{lnW;V%0dCW^eA0kBg;E3%d2?Sw$O@hGXGQqUVciu)Z zfCYU@*0F!Dt-i#RzZ%Qoc=DRwm}AU!n!=$#EkZs`wu8 zx1_V=Q?0V^t$m69RC~-}2G+&wO-7_qqPOIi8(!BuQdtWdQi5NtYbpM5M4<{f;qI-^ z+SnsYDe3!|*z!NEyXDBl2_8|+JUBdiw{V@^XFAASc-^f`S9k0;PJam}{Y~;Z8D2o@ z`|*5>dhu>px1RIV-6>_zx%qmFd*NsB9`rw1q6h2{2Dn4mNB0^$Gl7pj2~*B3pYt}3 zvw$eUA~-bnpqe}&hI|9(tZ1gjqN27?l6Q5ypU&^8cZ&tNDh&lX%T!i5{y%J|g6OF6 zn})zTGmylQjc%K#)P4B&OrJtofZsr@^-qO`T@>GnFxIl~)M}zMOPUxiUq#@Mj3HHH zj@R#+L5p!{vC4RyE zWfqpCa(=x-`r&zh@qX~}n#1NP!AE}fYs&!^51OyXz}NH_m0h*p9JtuN<(-ehv!R7ahfOYn&n^t-=4Pr6!(&+?5fMD8PG61hwy__v<}9NcreF5si2nwqR>ya%RQwYCbd5ua<}cQftEeZKVW(qg{bw# zK0P`L6hOT3iuvdAr|Y`c!5(MrgwkvNI#rIZ^jQG#>zeKZkKjAG-D1<1kkNFbt|oVF zW>l5!MA;@lX#I>G9wCE5BEc(~W5SR*eRp*HJWC%lAU$=RCn+^`K?$(rzB=KOEW??Q z_|qC{25)!3Pin$vT8V|KFgGtrO-pe^BZxkqU@#*MNaSVVSSVf(Lzl_zq(f^M6Vxq0 z8*ec?OUx#p4>cGI5->gBp@tSKE{cSGbg9LlUTd&Zl2D)S4#y4>JM76brOW*Vic1fS zi~3Vb$zy)XM^$8KzFLWv;m0Cu4Sh^b!Ax>e2YQAu)NLYvsS2=zMl;DE&zx%Da5&kA zR#{2EN8`RFIs;HHcd!Ml%(=)wd^%b~wW1gxzd<}?R>bXy`z7FSR{Gv1;vtxMK_qbM? zHO&(GOq>-Cfi8R6b+=h0utf%C_s+XirL6Sd_+U{AK8lJFJQ6nI}^K^+a)@LZ9z z3edvsl8Nb&m)?T^$3r~xFOn^PoTIX5Tyx!O77DW5V&JWxyyBb(-!}{9*(S21j{K=@ z4TLN)#^ymgU9q`&*tI6Xw`gdh{uB%U<6q^q$$T{1TRb6`nsDS-)VHy+ul7qVJZz4i z)R_i-O0H!PRKDt=?Xc%?GNhX9n2nU2A&Q@nXRB=tK@k~_6sqjf(I}&>2;)q8PEk&q zvefFbQs;~Y1KV^P`DTQ{eecJu0?+vRR`^D>Qa;5y$veJaxgrhqxAlUFj9XT2z>6W& z@;NH~O)mSVQFr5czVX^;*zL2^HO2ppwvXTnwVA4m_zvtK+Yy#Ky+8y|f6>@ZzAnbF z|C*(AA1wz1sgNp9scQgh83k)GhzIphT3Z{TV~S&b<^lA`6$~tWbtBqCvqHpW@2dYw zu{^~&*w>swSN0h6k16{xKKbr2zx+NV6kaBgt1ASf5LB)yYJ4i(-6ok9 za|JMiK?sabtLPX_-gY6Ti1XjV^AblZh2d0zs*n8XL+Syki1H$(7})~fq5LpfQf4AV zpqX-S7Koq4272DMhof%@fPNuoo@e!Y)*f8R^zL6n9D~%ph`rPn;kdcYic06C5&Zk0 zLb#kwM9+(8jq@j($AbxO6|9Fl9z>hEANj4uV$})VOq1f!+UxZMSGa%)@sMG>RTGCR zNVhWIxpAECMsazj_6P;?C8@(z&;%m-wpqEEF*UXpmTV`gFtzBesny)Hg`h(Ha>Hqt zJLyiwj~cjFF`xOQb?}>|N|XhWar3VWd0h;?%N_2g)30;7NRIdcChbe5xf*-{?lq(8j!PpO#{7>PoXn?SA* zYRG)JdX-K8`uMh1;f&A`t@S_qs-6A0qmmyA(p>9KQY~C|OObm=OP$X`GcX2^-n>WX zRK*fPdrn86;x|j`IXKgvK={8MBj=dz%poCD@r3>NxRQtdi-LHEsZ&F3EuK#N(>I8& z?(wnHwwX3N2hHZF>J!&|z}FT|g4UuK5xg6_^}_Rf*V-9hAmOPYi*L(}N{Z{Z_qhrr z&6l7$SIc(CO`tl1Jge&c-_*@5%d)#G^Uk~2RIJ&@{maaW%=5=^9kN8Ulk+mw~_|7+O z_)EY#HaGfV-=GzQ9!}RN+7N|<@bc()K z_UUz`TVe>gIBFSa7Ct*s6a9ADmM`*%<#GpNLA4-U2%zV!Cb>X5M+nnhiwb;W9=bY zGrS?L{)9o1kuk2gA`kO)AF@Y-E}b2n?Rh0p6GSgSwy&HaBxFW+@WAK_h8mz@`|$>Yx3DSeg^KvzkUys7Ev9vL#@occahWz&%aDl-uMZ31g4 z=;aaVQc6(r7ef6NITIK<3gHP;bB-AGWT9HdM!kB-Z(JTq#NxQmPnY^&BFDbKrW7^6 zXrl#g*mt+xCd6xx>6bSPbXT}B%E+8oH^J^V=!0IPe>io4Eq48Pvp`t#ha!1tqMN+Y z{gLm@c7&&+doi}9h!xJm^FMHIx{c2*>5s#csnhT?LZ9?*&=g%!;27QW-!lqQuXZ0x z-gOGJV+%)c>04^H^tD({4qBqImAO!`imf~9rI>eYZ>iHB5 zTz#5E#2&KG zYaXv6>!lBsOnS_z{t)p0Wz6JTJX6|px%C4O%|gIeGx-??v_6m53soSVUH*_NpbGA^knY|5QJLn-5WLVtVixi`0PTI%bc&4`j24KON|LJ>Iv8_oKl7A7!r;0KOIIz<|%k@Yd1!S zwWcDxAun4)Z;AHwPK+@0Fe7GsTW3Zs2EVQ9SO5E(VqL`DGlNAD>JqFwhv?G5{TpNrSn6Fx9oQ6JNQ4STS8XYy_VKnJq9Kbtw8>o zFOI-k(%0I85T#MTG_bpiNctHxr@SHFAG4 zxTvAXQBB#HfL|ir6*`E2{?+z{?rVHy8b;*1-a-xVG~WX#LW(Ne2N?r?Oe304o8Rm+ z*gxMiVOWaRVzh||bf)77Iv!Im6+_w$_PV)gLZ`34D2@SI4!GRdnTa%|M}o&|6Pzk$x?Ia-xwL|Iv3Cl+VyaqqEE)_RL6(eD>78AP72#7!MGf=PN+2?Bq);po)W2yOWz`sSf=5&1| z7E_a;HcoUpz?&&-Vp41CtIj*?}x@d;d%J8R1vh4W$VWWAI+m#6JnB`+Iho!-3C#=YJcA{gCm(m-CPVey3IHz0fkH4|MeJG&oet$;dj2O{bZbA_s0tNCgZ2#0U%l#DaD8Q3p;dlzWM)1Zdg{qHLezDZ_TTDQ~@>v?uPnFmDG_*0E z=9LR&=)a!E71niLRnHk~|GgotVc_oe{Fp&H`;P!*L zri^w6lfLfwi*e4+xwXr#3jgPuPs66E+sCn1W56D_O--f)m6c!3i<2+mg<%LW|9$!Z zKj&HcQhE^W_b+NrFm(I)fvB5kg7R~4ln({JiKY`RmBV*2g7}AcmT;RLMPfag+U`ZR z1E6t@q>`Odt!orN+(wyRnmtsFT>2!g>9DiW4BzCS`tS415PBLyG8*zJNu=Dn^G-`I zU}lV|i|_=;d=wNW6l`RW_UbHhvj5y<9Tcc22yncZb;qiwL1g|NBVVLFg4tks*L5QH zot*!SGjWY^;2QWk-JAVz=Q*pFu*kXo$jY&@;&PB zkgd$CM!Zo8=DRo6U038ZTs z=#nE>LypM{NQ8iV>N=Vo@MEYhlny+zF!SF$793>50jH|a(mqi#Aro8@_rYSWpRn$X0L?N}~5pe_uDfz3$mrYPAwzjWm*A(=n9VdJqY=N{lng*cN_)}c$kK+bA;Sz=Wdxn zg~F$&Uk?t5mI)$NIx?|{}&itP9Kj(9v zm;3$R_rC6Rt@T~mt;z!F1VuZbTYdGCpQ6dtKTjw(=Fqm_5vU{y^XuBx6}TWiK1=AM>)StlAMT_aC>C;vH6 zW&d$_l?7X4D!8TaYnp9Ws!x(=B6o+Zhw>Eg_4RUA;1x$RO`dIx1k&5amLwHHMr5y| zKUU$vallX1z(gM=(Tc$)~Rf^=o01&2gG&yGQ;oREg^M8keSAgq1lKqTEu}O z*&_vG1-${lzpe50b zby1HIr!iBEv3$^Z^O4iI*nE#1)q-Lsg^g#hki}?#YwO2lH1>2gBasG7Dm9t&ZfD3J z=uRWHByCwyE2A5|2;R`)e3$=qq;_Q%iN|(2!Kl%@CG4rKhDLJ;_uoZ3GBl*6u zK&{4^YK%ewk2h~Sm6Work)Zr8$HQ~;qo$e?Bl6toQXD!{RJB~D4VghhYu9elu`)-W z^Rh1b+zXa1zZLg7ll^$AR*DhL74dm#H2$fuM~I#pf$R z#45>2Iq+qoD_|XwTn=1@UjLe%Zz3k{ep$`~OiM>v||? zZZ^QPxoz$~RqM}*B){#sn`)cV9pl2a$IH+r!jRoia8yRTD#?@u zmcM{JR**yp3q&8v((cR9RrhnU2$L>m5i}5S?8NEBu*`xSwhXf+PQVFSIQWZY`xeqnOVg#EoJ^{+5?jg-T~DwNHCOkC->YYMfjTOMePq6 z1Tdg+Y^jOhCOzg|%4p$bU43+Wq!%uhNpcT4VSu3j=o^_=J(XFJfdNg9A>mpgRvMof z?T5^G=!&YzQ$~4sxDxJugp<`3F_Id?>Reg6&xQVdBbX+)(4{~)Q?2qEw%G?%2n(tX zvkGH{nUMP%O23i)`S$OjDW<0;o_oiif(=-|Mb^!*rRL0CFQ=GyUzb@d-+mNDbQ|BX z?Ijyldtxmo3MHfB+NvzSU|O)!7kteYHr4Zbx!V3j-(^v-C`F6bmno?{a<#eq6~pO# z{uzQ+R*XdIE*w^@zfHTL$BSTK+nc<6WGs_CDM{B9|fnix^^M?UI}BxEif-Aq9Z{ zvT;5u+1-QJS#`>RV{kEdxXo7Q)_S@sH_&WsSZg?n^{mLnwI%(p%yb)R=Nie?XphuU ze?{4KB>7o_&%TtWy6Z~Ca;0O<&X?OIL5A_+PA2eS25L=ZNbwg79c|?MZ7UYF>e1tt zI*9)}HLlo#{Kq!eDge61yHlVzgIW9ou;^kjy?8)gzpjB~YmA3Td_ZWPJ@7sR8IYWy z@HN(ikOBSD$?Q*yMYt5|emwMdkJgB;e5IrPCZWc=H9k@#UWf4+mnzyo_#m71R%Bvq zV^E#(cE)1cE6S=phtVMsXBrwWOKHFX>}$fNnF6K@j|(zj`a>C!r^-7K2381>l>Mb$ zbtJDsVZ)gjZr_9ZeO#Cj-=L?sN}1vmgmJ*Nia3Nkal7nkey{J-A{6`*)GXvVvA9>% z3^{QqC*>=w_>(9dUSy$onii|$<1Yw{Czy`3kS(={I!k-Urejz|xZ|Y?1V9kXmky@Y z+TEpw!_y<^p$A{FmYtKFo+WSe>e0=tcIlb^<-t|-U>7%dli5km%>i@d9^O0OMH3oI z39r9ByD}k(KW8(1IlW@8e!Ollo(j3@R}FAHe%P2EWmP=$zio%w8D3Yo8*x(@uH8>~ zdB#4$*j`-W36%E<+zZAJONKA-`8|VtlCJk_H%(8YtQ7y_PI*8aAS=ud)svNm^ddAV zx%-C;ZPVJ$v$KrgL#|mnZF`>stiXxDYM9_|naHG72wt4?DX>?4Z_-}%tu)_tA8w&; zouivIU;Yi>@4aoapjAkHOKqoC35j9~+}M)Oprw1yFt0rFgK#hrc7D2GmNP=7+h`zl zY!Mx|H%tSP`~P65_-BQZ*V1_vyk-Drp%ocA-&zS(e1pDjLC5|kvQzw~X*; z`e$i$lAzcQimUvsx=L!%5iIf40M+gf&l=wmyx206L=$kAAKgzS`AtSw;XiNvrW>eg*Pt`O(rMdbTCGeEfI+{$r|LEewkCh8k_MCYH1N z*WNjB-8DAC!nQ(7#?8{1mm-@ar8D0?uT<;*n@QqpZ?B-HXyl1qxo-F z;74Mdf50Wqtk8w&79{<=BjdYUNf1HN-vzZ0&YPo5?u>FlXpON_o4^lEsQk$-QURNa zQ1>5iRf-85KwC&7*7%Dwk|d}DYwyD=$1)P*f((jd+VV~24}$gvYMLJPJ;)@8o4NA~ z!e%qrs7#3u{I>=iRvI`yW_?X1jqb2v0RlgWVWdyQf%}AGzjImat3HX7fq&H#w|97$)fWQcQB ztb4@D+Z#}JG)CN^yUN(!vg;P(TO{v^9t)~eG)~#_34&YE?QQ4EDP`Fqaf1ylWmka< zSHvOp!HFD{ACc^zU<;}@KS>5d^f(+#@)KPSX3)MwCf7=n3gOw?f~#nnA^( z_|?-y2WP1)6RkXGd(azmGMm7`f6$7se(E_wL^^jHt8ESwTl03}`UZ2apa%zWFXQfaFtp+r zy)UJtPF*yskljr8@$tRV>#Q#pHr>uiE<|=v0m!m+H_B!Yd z;z8xicP9%`tqFg~`2EsaFDjC{JBItLEZFo2yzao~Z*M;SZ@pYEU}`ns_1bRDrZi{H zetI#Zc4||S60Wv~7;*q_aCnJs`q=_Fy0}fnumJx@X*eNWX1<}KT6-8d+ct}(5JOYD zTYy%~W9DW}V@@(qGC5|6uuXR!Tz~B5XQTuSd3CUzM7+;?opj!T?N+F=NilsW`J@`i zbwX3JZ2h5_UD-Q6*L(8tj6ce;s=H^Rewt#%ytI3#NV#!5%hZ*4dS%w5rf0Vo5P+kn zTMNVv{YHtefj4*_6Y^fXr0nf1uVqa*5k%fUj=kv=5hyqz2>aS*R@wlTzVcB$A=~C(s z71!ln)9)B-K$`S3I;3aS;{--50Xti9Vxsd#ACd!{9zk`R-XAA}<~}QiU@YKJEqFmL zbLfOq7UHF-1Khj$MuJtpMn@7oTW{j6b$cwMT)@sc zixas4-s{ld--F8(N_0t7uuj}M$-+q3zLdC!k8nhMVD%_Z186T9r<&3;TAA?1|RUfh7JAqR?W&KJ}SyyI4 z({ozmU6t7z%vV}Kx-@PdO`NK<7@mqi3+K4+xw@gGARDseko=L@5t1u^tRFV^Ja{Kz zJbL32EcyH9TVxjbfv@yoj^V$#$QS==(bkxWy~YyAu*gxrVPGUV7WRm#iKKI&Oq>vnJ6KM| zHQ>ink5b^nL9d6g6dCgxKp%zscXE|5MTzkW7UP2#bJA%$Yp}CdE~xS^bEWD=2rRj1 ze(+Ug4y$MNzgTc8Xj@pw=M|5|`LxV7Hu_K+!EJmDp8Q>5Hp}NOFgDd&|3D4`{X6SAKDqWt)r|?@LyY;^l-_7qiHgjcWV+CLZL-;Or2<+-@4=KM6i{JEVPunm_ zD8-`v#)j6q>W<_mlzkqXxTeU?)qe7b>+pc6K*a40_<=NuOw%;{1N_U**)SG zA1+S>&;g-Z(0q4-#gp0Et8aydu^9Uc`{y#f4G9D>tLY=PCiX;A(Rl(4S*-3zzT3F7Ugw&lKl6{*8_}~80{*A zEv}l6%;JnwHpm?IeAa%cFw}nYh0ZlFo?skE1%1XsHsZbBB`b;@Wo@s#x^Ns)tO{_m zFrWrm3w%_>O0w)MjH@)Un4=-ROtRj5U@BTIk?+W^nMD1i4$@j7WF5>p-f!fNO^hD z{Y{Q#sc{IwyU47O)PjvcAZju(NxjOU6^eqrljCtWZ4S9?sH-Z8vb=aK*PC?x5U=I> zzP+C@Z|Si0mFIS5w+$NDp-gGBW6~0ABZ<129_NY)dUTZcZpSTKv7^W;KFWeD$O#?- zCPEh(yujkTwJ(i?gRQt8Nm`6SB#bRuqWl7S?uMSaSoV<^R{{xTY!+{Kh!$Mc81N1e z^JK`Uo6t~1It=shL%n)FRGt7xe<+hBgXZ??v0>=gu59?amxi5?-saVGO}f|P3g4Ms z0Fr$I(~1>HrggGoXbzU5gnl?!6fveep?Fq&Vh&9#E%P;xgHc97Y>Mw?j3*!j+*biA zeROl?Ip0MShOJ)OfAw}(PT+NcDw((R9IO`ow$FOMFAlPNinf6zPAISbRDqtYq}7rG z4R;43>&Ayd?PBDuGnpL!J!Mh-3OXJ8C@wHqUyC8n78Or{c$o;7*erLuu#A_|)2=_L zE`{BXUXVT1*hi&UbTZtk%aUirCD5_o4wraxS!rr%9o1n4^BfVZv&@$??<6TY|r9fTes8k z#~#$$U}$?hHeM)X+~n8mEj}Lb)5bjItftiUuJGJs2za)u#46```CyRpMehJg)bSB~ z;un|~u%B#I3RQV@I@ml56smthMi-?Ax9bn^ z$5veYIy$_#b4|(@DM1~jrgxkLw}h$h@KZVp5C=jYDJbs$x_Sb=ABrV$RsGE4!0tq3 z9XoT$hIg<-wt-f@E{N&V{k?!#+Vq1McP`O}$tyIr4u<09F1BJlC&hzuRh5HcHv36x zvB;VIFrw1c#you>$k$Y^4^pnZSQ3Y*@G zvM;HK@T%?aFGYlP|JPRR#X34T^$E%o1bTXbPjdhnd+5M_*NntE7k(hHvi|3sJLZ&|XDP{2 zoiR_PPDXDC#4)>AA#b{?GctaYpHH}|d_{gRnKpe`%!SXJW%s|PB;{#QPG%s~&R0sN zT<3u}?R=U~eYO}tlj4FU7*k_>clUqyCT!(H`ba0uld<}+dh`%1O5`Hf?*|{q5J!yf z1b#mmC>m$s!al+x`K3~hSTh)EBkIgSXcDw3mDFhYC`LM#?xknD*h|Z^JQPd^4VSSq zO`gqyGL5k_J}Eov8!)P3 z;Cqg^#f8v|wOwkvx=HI}y&~PLI9LjOuD9ta@<0S;2Gvg=jPACiZr8RWYEFVJ8eX=Z z6*@z!oB_`3<>8~E+9X3|i?i{CQ6V}E}!#tP)ZRk9}#jz{qxSWK& z?tg9Htd8v1NM)j67Qbwr{GpN6^;LV2zHN6;LGXgF_}7VikQwN-obcb-9bApOo$D7< z1FqZ0^!d~Y{upWXtCnCPjWXA>s_S(!Kj%T6)|=Abt#)qkc3gj!rzO<@hRLK!gS)*0 z*io0x^Tu*+?$x=G+{1K}<8m^@29Ce4zLO51Y70(3jh% z6%DU8-PwtcB_Y)l!k1$RDdYd10^rFnsguX09}P2ah5&~JJ%KAtwFFpp^15 z%8tn^g6=E~;{u42QXWr<>5R4QOagY;*i4Y$C6W8gMFFLlsVKbU+DleiLgM!iJ;0aX z&TQYq@|Zk5*pItVQ1r8*1z$MiBdIdpZ|(KD0=sF1~feAXgyG5L6FRVW~b()K|s`GJ8{?*5+T ze3*tO&=lWQOf4`NYro5#*8S}{fqAfH^hy>MJF!jOq&Fg0*sYO^oXoD0 zV?m^JT@NV6{5B@PRjuwhvPD%g1`ru35_Bm~`cdOrTa-2{ziuHgO|oA+5u(2$`?*UD z!<3Sr;zQQByUO2ZbN2%2EM=VPE_7cuqHuH;6~WVxJ@XO~U}W*}Yk$S%llnIX`Po58 z_IPE^1p=q$w-~Ya7V(3w;{{QP1q0U&Px7NgNGExVF4xzb&vHi-n6X?<7Q;*@qp?Ip z^h69cFjh`|CneQg;ahY2huou;1S;^FGEy?s7=bnuogoxQR2TPK3jAvN_NlAkr?K{> zoAlzTUM8D9AI^COhvU6&=`r$%j6t!i(42(`3FfSK6jI2TM=bK|r9h-K*0hJ$qPqkt5};J}t7Ea9GB+`5P8RRGjvB zaaMFx)jLdPUfoSDJFi`*JTVK7!su|I%IY9 z1e9vi@GUlx+Ap{9s7@7rF>Gl2l=lg*Tl|bCOV$_>g!ga%_x;EX>(gUYJl56t;rhV( z4>r8OpDg131sebmf~AX{9+Evs8F@yqUWg?%eWXp_3BoAB&$qQykiWs;fdB3%GX*gz zz0~OhZO70>6SMT5Fy4(_n3TriQz?BC@AK0rSW*wSj4YyBm!*Qj@(?|Ei}Oq+QhpJ? z&U>v$B?N$6O9Xi?pKaW^TBQh|E^Em`er$52@+4-~1eEbq6$W15Md^um_zX#^3>};k zYJSmT!xoY;Djbl_=`$z`y?!rtsH^C`>!*V@%E}&bCj9*_N}MH-oN}9ajf!`dUq?Td zann%rP(5yBhwumw?akg#$jl_n6zUm-ZDqm;Vt$T=NtXgd|KqveFPk~A@)PQ7qlG1l zY>%f5yk$?&U{(tXH9~{1@ZtFZ399fgcrD2+kuk7-Bo3s9a#9M&LS_z%l2k?jNc)$B z+WJ#{v_-s<^l?PY?zOho1oW?$wpK4v@9du(KrlF?`Rn$bdKH z_AUFS?>pR#kmMAM9;Bj)D)FeLKLZSJ$JfYUp^Yfw)_&b|)PW=6+aKe%>wf%=5+?6TR32?_eWQKO_Ad|mloe~-SHIH`w`Ii+ii zme_$cpo#u|T6J7l`C~mkLS4)W-DK6xUUVN<`Iely`g+0*=uqVX{5_!8PbOUC?@&n_ z*6M`%e1tNPo(>4-q+F#1T2HuC72=G1|1#atca`nfEeeo?M5kMg`S4;b>}FIL%<)ty z>U${)?53sqxU4K>25qy1Y!C96I;C3?8G4(n&Hh0JjEwO+^Sy4@l~bhnJ6C)+neu7~ z(s)SkhwR}&;jR&>X7oE@k?dmyA9eu)*I)e|TA2m*3>iQb7iHn&5F{8XW9D&kVocW0 zo$Mrw>HA&C>xS=LNbr+iBduf4oLU;H=CSaH6TbH6_Coh-Exw|zr!l7zgt%Dj_Xpy3 z-Bo(Fesz6?sTwHj+jlW3_wpqB!vEAY3E_5FUK_a(g@;q7;RtM=yHCi9f#dEioX=GQ z=vQ4vPY$SGyw-z4mkgS#6`Xt)Nui2sSCQPBRnzh%S6 zO(NCgxmPs)i019w7a3$4;WvO!0gC6oh`#ddQ-}H(oNskwDeAJc4$p;)!JKm}gMl*v zh`yfx5Ok*}5<6~(6w4{=)eC_nEZw5UL|bo6wGBjaRmH~b^FJtkGJP{}BC1Zak570zb_u(lL)ovv&XI>)`rSW3jU< z53LMUKDBFL;rIAi1xjV<-}A1w6#o{8aXITijC5ns;0dlze{rAGe5aETx3}l(>Qd(F zjymQzA$3RrHBsAV|y*=N>=-+vG;9WSB zHcda#vGqO`)txYKv;|;Rd1@rD1kLUS9piA5y0lpW1o)|;^!2s2JVRk@u$=a3_BSmP`Kp>n{!K3sSE|`7CYJ<%fc*)1Nz?LwRlkp9(ML=fpAI ze(sL6mG}z%8&@7m@VCWoy9W%=m-}_aa8TZrSiX`4b5WYhCmBePX9yizh`s=4b0f(n zMKSXr-@@Qi{S-dc?*C<2e0i$%~VGT`a2a_job52M6p+ z8h9Q5!X59FAKWA>mXXITo5;2dsP);X!cYGlG<|ZgQLDb!S}H=a=0Rb&a`ES9IlK&4 zWOXpVHt#lUQQEi;q^R+fbE0lHZ-0Z<=pC2No?H73SeMMG>ke&vV;#hUqVfl#Q6wkJ z9_W{D~4DZJhnsE8ulRUn{S`5yE>^vPF&cO#GdcciYUQj)x~a*`>GQCc|%_h z-$lK=q~#Mg%BQho;uP|W0x|fByB`Kl!1>2O0s0_es(*;YIne=1HT-wS=*goSWIFo+ zjyLH zp(V10#%xPcP$Ejxx!MyZNqjEgp<;KTx}*I(`gY{-n;zVN8aJ{HciuMxg2Z_oBs&1- z9r!9RxeU>5;<%FKA8=S2P+?PuK>L_QFUlYH1&GKKjeC|nMReXpsQMSQqRngWLe$wt z+z{!tnBczluY-FcXFfifSp!d^@ToYJF|k+-*qSDLtbe%~8MWUued=UWqz4t-2*!h_ z)m)yC{vhaN5(DOcq_-~U!g@6*%@TehEr8nnKl&%w1UIvb$aqmKS8F($2^XHZ{(B)X zuOcuGdBa61)MUX5Ep>8~+-J(;h@K0tQ*>#V{uEEVg_A$;wpc66@`(Pc*A#D@Iv!2s zM2r|;7N;;d(KS~y!@n}nXtmz=schjb_p=1LyUoa)pk#9Q8Xw-+<&dnK!!?^@!xL)n zD0~XCtV%S}%EtRk=D-q@o^$3yt8Gx=b=syI?eA{fXI1~aJM|cj=KQsN2~oHK*F}N9 zA=PgkG>HCV{0g!W14Q3HO?jn9Fgw${Y=0}3jerT-PqFIZz;Ni8<})9pyMuM);Q`kP3uMVT3^t&$j?VS8!At!``Y64XCGAI0E> zVwuBM>+oGZ=n1U~bt<)*m5zXfN{?!U?k4hGeKT78bQ9raq_S@ElCWG6&f zDijF^2|vi1%S8HLO5(Rq3Iaf?^bV|JmO&jAe>(J_ki>)$?g~W~Ma(Zr8;lmzaSBMT z%1LydO_eMY^>WT94t)u~@XSHZNQImmIOPc^D2<+V-34zKLFd!P6-b8A>&*}OOa%3- z7#tICqUX>KH(x~&cfG`=I;>ks4`9C=;$F;3=GVzGJ1vx4Hfckk6i-ZbKJ8~pt1@~n5c zWjYh~DpcGC^M(gk`Xn?yyUp~Sx9gcha-<6RtBXUlYSh0+GZnXG17s(uY`du%3*-m_ zyBxD|P1ezK*uj4^nzW4#zne<~tL76S%jv~+Z>zS)?`$P|FY{cNe_&Q!*Ar6rj_vnz zV=I0KSw;zbME;whl;jVmI&Jg&| z38;S9w=Tb7`O+3Z0(a3M9g#aq3wIZ4pRAgz#bt$oBc=kLAV17P+^upv=A<@Ut#}#N zbqtpP;_g;vX|8`M>u1GoFuTdR*X-}FFY^tPc3=6(P!VQ1oc7t-vmJKOpYZ(8{q12U z9z^?vipn_kQMk>QQbOXSH%i0zkKTZUD6M4UVt0>!7xwDpXeuIAHo#sbpRav8HUG`# z=3@niGzUl0O|38YZg(E$d1ao(3kAWibPcr^d7H|KLIb~D`>J)^YlMfjmGd#=?hzZq zvW)f#4rewQj^Nqny78T^DEr<;AkFRHQ1QM3#6XO!28)+uItJvG1!d^`NGw^7bWpxy z`dD`tNx_~6o+qw$+FHcm><4)fs-u3=@fr7wZ4~uev?lj7R3LX&c8$Jm5>|ixxm)r6 zb8g;e=OQJ*_dAmd+R+AXd%^?j2j;34&gLkT(-nynJ-) zV`FnlGrj8$Egk1pZt28_%20Z$a&#EizwdahMy)-Q0{pja5nwXYvgI6(% z`w>dY4}amLUK&UVqTUZd+F@EON~L)LzHp5LB5OjjU)bcAB}2H2E40e`z1Kn69TH2b z;>yly6@@s_pp_{J+o1959o%nU3O!u@Aie!qZMuZ#SZ2(mI=QnC>m#|{vyXbT%3jxfhGNoWFp+}Zi}fY7j82bG9^ z2TPoeQh6hqw^ojsYMxcz>O@F?Y_`n-5NDv{aO!4}5v?RF4uCCm$n`=)6m8|DCD@73 zmgF!LB_J}AO=~}?bT+@G{-%wm9@UJ;C;qIP?E-6ewtniDkD|&yBmb+ex!7U$>{1{U zEb@*h2OaBljH$*(k-&>{;J!W}$d>Su#nu+YdK?__Lxq0fLkZhd;t2w9DAka;xE2yIylB1bLb`mQ_M zr8nwp*qi9ibH#;iy5$dbGhV)j2oWf4j5HRR6{#1zC$v61uZA-qR+sB2_$L4D z_DY*a#c@)5mZ(|quHo30tnen@{phzk;?Y{0QFzqCT|y-;r_*AXDcR?TF>KVJm~FR% z;xtcC8Cq52-_-V(X>0s;b9tsF$A%mEjXM?VIkwP- zhCZ0(u6iuz5X$MK?jh(VitB=BA^wlzR(Phz8d-1SoVD$(0O6=DJxe(4c8S1G*TgPD zTtT8Em~R@Yb+zh&YC?*tPm>6pHe&A# z<77S$Wfa+4+^T)m_zPmh@kAFK*oPNA7iYK7H7{XNcyT+vl|A{V5F<-v*`btaK3eAK zalj#w;9;$X1Ckdv@vNx?xE3Uh^E>YLAKz}ESdjMr4}XY`pcm_IDfaLEz||60Md3Zi z1k(E_-PvAP9s13mMNH!91K&glpF3@Op=eX#O2|Ey-T%;F|t6>s__X^ph>S0_cPseb!jrO`o7@LmfZ)2AA%CW$<8^Hu6HM!*t1 z_d(KbSRM%_=aUC)n0_yypo>wAhJ!c4)zGMQO#JCAB#eWVYAeX>sO78^2&ri{}Vv#h%>h%>36tYn0#VxA^*>6}1GbCIV@REnexe zRuQsuG=VJswbTxy_YI|`^@wo5noXDnHp`BW3rTnhC&+<{!Rn{T%&!U1I}6FU0V1Ge z=(PpJc4(X>X;UI2yx&404d-nSpknQJgx`p0{7)WUxV_e!A?^}ieeT{L#QNAlgY9o$ zF^yXW@qoX~OiZgFr32(Fku7ck#hSZ$iq>Q7AtD~lyJf4h-`;t=~6~Qs6 zR5*e3m&iKHn%zm9#|mO9OmT^skC1IIbdTfGe{GAPw5b-+CX=q%DSp(XTDG5pdXV4c{`*jcvVEe{N>;OHl6JcFdh%G2zToQw>uT?s#c; zx_BeufVOBTXP;4D3n7}L4^O`TRxliMHHqvt;g~a&KD-A-KJy@U!y8#jEa1OLoCAeP z&Y@=%EJ0S3{xJi82}a-O_tHl=5&Vr4_KsDr;zn|~6O2I_+1kn?H(;o6I8iSu)Np*is3XhtR%gMa1JH4xjzSjd zST^Xs`w~d9)1*i-f;ZqE>Yv|EiibJj=TbS&YIAM3+80i-v=$_5N zx$kKtLVXwWgYpVD@Wax-@1z0oq26ElP9;f=JQpp-jfbb>b+_>Tt&|tX6$Gc~OfLOP2xaNBZaj9#u&qPcuIRUvU$dW#rtf8cof_Hp?Ti+sXPRd?V>D*^>Ll3H{W}=1OxPxQ_w7tS z;CHeJPb{59()Tq^9d0EYoIP^0Z+6jk2VgF{ z#>Y?YD*e9FmNqNn8xL}3UQ@o?)RY-SlTLvT%Zokl%y4AH^}7GYV91htn6oY)l#10R zV*Ms^S_-Kh_5as~@EJl(u$XZ3E1B{R9(ZN8mMrnKAPYLVb$+|pNj)O7sNK^f=4K#Jqczm9mLbTN05yZfa}z4nDL2 z-se-fyncGz#aBb1YqCTpcZ7iGW|h62HAl^-(m7tUk%t#mz$mslK)nd%ai4>yLzznF473Q!KdTK+A_50 ziN>62g3)PA;gCYr^{yGBMD&FpqUVU)eAcLXO7n+58(k?yw2RD%+GOk>c6scnrY3BSKeC}6DNzjZ7GYBl<-4qoq%dd7BkgMq6S7Va z8W}zV-p6R9$x^tOV+?5Jzb5)v-56jYvLXp%&m0NeKgRJ_g*s?^h;_*2i4YgIBH_Meo$u!vJsY)5ZoH4 zN=gr&;LIaX;s0sEGm={{PW+Z{-Cr?dzQ8}$9qHET=8X7j%~f-{zyHbZqrDuW zdw21Ga!ewgplT1dH|WP|mS$?NcV)h7y~s}3&!IiR_bid`v#An97R0KhNgTTDpBs;n zdKL6vi!Q!&c#UZTh4^^TyMjP655pnUV6ycPyKFB!&RsLqy+hX)%>FRln{0b^Pc2X& zl+!sU|6hfc*R0XItV0wl305&Py!hJA+ooN^ftDht#bNwGS~ICdLmHW!Um8hVn6+bE zp|jCRZDTGRu^ZJ<$(Rl81p4tYVnf%*iT9^oU0Hi5Tl4AhtiBx#^(=Tgp~pdL1S_tt zxep0MW@(Vr%o7}(r5~$jy zmn{N`3yIz_+9rIS#C$^gerhT88+Z8x;-NsSFc-#QA*#|A7~Q?FYxO(wudR@v5EZr5 zQYhZ-vgF@+$W0@1rU>X{`baGA`97*ziJts6dE!FIu~nq=I$BJ@>2bNaWV8!x13!Ow zW?jwlgf@!FA+T|J&|fcSq)@&|5iu}K(yExc^h?Qq;Ku`TF7XC=tY(84kGVAAZ4q&O z%7yM0)mKV)!i+d8MIhIpPe}m2Aks_on0MbY`rNk`?7Qjjua<+)LeX))ifv2X|0Wup zxK+$+M1+RN>e*u2>Uo#Rk@K^a4`(=Uh|`4qua>_}viS@vPG9EUc1&CZyo_7UB*4db zS>aUL$A5Abi{LKN>rw#Usd$tVM7RT@Ok#I)u|;v)BEm#}y`DjHP;}3$2Z`=7usHZX zoAs&%uaUh65zZiqU@s*9rVWHJ8S>}ek(8)&88I#Q^R*_)dk zxmz_?cjfreEX`kDeeh##az(Rrqb-v&k<*yXKc_>1oAr)P?T6Mc!%+wtfYaWs4Y2dG z7|e?g<2Kht7QFsxs@&Gf$l(>uUQ1Odc2hNWJoZabew1xk2oIi<@mKL0_E#wosmC-X z;!UeQs|?BTaZ;Xd4E|}majJ=C`igBs1Hay`F{%9!=A#C;=4*WIGbQUT_tCUjo9XzD zf$B&wdqLybpx~*A&B%acuI^ipLa9eJ-9StUYLto*WoAQ))_H}&*yUj9=SCLDC#oN; zQ!IMc;il!7#bEc_+t@%J@r zf9_(Q180gElxO$-y&iR(8wWim8h71~<)A4wUHO%HVRHc$RW@5xE}OYo&b*b&|77a3 zs3asC)gEHO->PS0RGZV0S`AfX4PI4WQ!a}?0(Lf)W z=rRyl#3NOZQJ+W}ZrfxL^K&5wWbyG)(GAYj_qPUfGXgLiBm^22>RVQcCAu`c=)>JL zVO9WjPc)b84VTRU0~9=(L(N39C5A@ZTn@0(uj?KuX!OT-j4`W&T~~x))WWL7s+DPg z5@C*K+hW4|A2OW)cVg1N9k?GKJS_y~-laGcRk52*N`JvHBr@6Ys86<2f7%||A|PI_ z!)-+acehwg=&%+W3QTkow^O(UFb&%2tI!GqSFxf;I?LR!YL55`yUo_x)i9?SjgFA$ zTT;95ba6+*m>m=erQ0Yl6MEOq^mL-`L*S#~!o;`yG%nGcmhz+gB_iz4Osxu}h-SnI z#jqS2ltjlVF__Xp~H}X1F=+gyW97HJq2M0+J+aT2YR$1ud&cr zWmUf$F6f>pJj2~-ahedpxqDtUQrrW>+z$F=a}!lwE+|$0zY;ctQ?7t+_q5Z&KTg$j z(WLzYZ3NJE47KHAs|MHeIyhDyKysV=QC0kxJm4QBp7?}|faftNRP_kp?cB{wplkAcT$bGbI)B2LRmH~4w81yb+EJGE>{99hHS+NAqsWiH zB~mdS&hs~ATNg2#4z&5xhJXiMs20sEie~ms9XjChM9I@p{7_y0cl=HH*v%paD&#>9 z+u}W}q4;ent5q86(=^#tXxZ+N<%Egfggac=8YqPbG=go_F>sBF|K(i`Mp>Ze|1X~2 zvLUXm3)bz%3GN;U!QI^n?t$PMJh*G43GVI?+}+*X-66QUHg_SUrGGBS-g^Sa*R6XBY4xB`aJdXsr2@pMk_vj6?Ei14w7J-O_27G}>r|MU;1h zd<9Pz>?L(8Il;-Rs|D@d;P*MqC))RQ_m(|bHtOeB8cy4i9yN#$eal;QH$7=RVBnq+ z%A_$GgpulrK^C!{c1ZDXG*lD~e2dCB%%rml@JIpt-Ok?c zbz|gLK9gt=xnQ3BTAA~m?EKRT<1VH@t))(XwOuTL3#WUUTHqhz@w(HL0%U|*8fB}0 z3oSLjccY4rD73NIqX0_IOLJ~!LkCFy-DY{iWzbx%F0((0SEp5hYsv|p1?dhQB52Ys z3=6tf=+>~!{a_^uo(#brmd4s=_6s{ImS+?d0x~TJ;0G#CXoz6>rTpOBPF@MzAL9$# zQRSXA4;^Gt9V@5%xayI+BQ7MYW#YeK~A#RX`DOVtkYF z8sXyLqU^SY;md=#&u$E3OAiAh6#T{Tq$>OV77A3(7Dl<;=E=`i@=9)v{Qj)~p^NE^ zI<%L^t~n#ql${2M35~;#epOhhx!^;`(}~*O?r3H- zA{-#K^)60O!i3q>`Y(xJvdfh?dMq_K%r^c#n;VDoLT|@PMS&w0HFkQ>H5^r0eMoZl zSCD{oTdKqpx83uY^WIyuV%KvJJLdJbspmsqrI)qBG1Bg?w?&PDOMQC>@R7m0v1d2p zP{Ee>E`i6=c0saUnxgN{v86B3S!w6pzO^JDAWN?%v!=uq)-Z!2c^O_hmmt0x4d&WI z$@p==8!V~Yvx3Y!*LkmH6@Rm?mF`e!(51&Y+FE3@?Ox6J463PPbyRAs<5T=uctIzA zku|tBxjJ#VZ{eLqLC>Fl4q1MxUm{Mk9~fu*j|%L2g5z%M7b;nB{~v+O?d;+v^}Zd& zYECuzV!yD#wCxMZs?r)fIyLZjHN4ShNodV=g+M{~>w`#3#qy63Vv%~=S^!=YUz=$9 z?XxHM6&~0G_5)PqX z`z6e}bV6T!q< z7(|6#?u^qXUHRUhcGEQjcq#Ob%eW1IMw3-q9aK%QyJ7=!Q5v$p?=E{4Go;2SqbPmI zdWz>JT}Zbd2MbS}f>^^({1#=d8cS7i_(E$xdYNCvCgB%kM$JG3s;Vv>g%$0Ff3^)v z`GM0S)Sd(O^n`;6*4z#14wdF~HYm$1M_ZD03!EcFa&Xw z_XHougW2U^&87oBxogpopgalXU+%f$s5E@T=k=eeBHx#n zFhSQ{PVRiS_8o=ekM;{n%Y~@><4u-=A-91Tj&^E4V}lh6^ph+RCP0FW$doSNH^1== zt$8c^;%Yv4Lv>BDbgMF@%f)TMu>4C4qI6r41DbhV=7LJpDGQNKg5yotnW~6bN_HJ9&HsOAoVU-aw5 z3eXqe{l08C6le=G#|L)f-E}?+-KWZ?Idh-KTWJ*WvS*|0I@!dgnXhnZjnR%zwHmL~ z>qzN5r>6Ye={Xs<@SX#Anqeufnh48{?$tS{$TQgdn(4L${jxJmHr4F9W-T|xz_ldR zfb5zM=GCh@>0ik2!$098z7j;E_Z>Dj+-aRSCCgKgQHY9N!FBB(X!B08NP}_|$)sJx z#D)J`4ah9%d)Z9FZ_$)m0TL3?Jn67Ra|yGJk_&%fM!0MXB^boSIHUPK_01WRzAVf4 z8g6tKy`J0o^6R-Q{7AJH<>Aqf6Jq(`%C8{7ksj{oCx9ttE9T_<1?WdM`HQRt{@cS$ z7i3a^Ta?WWow(n@8$6ZPYBt?aq*k=W8~2A?oot%^RHeMT9x6PGyFJmU`Uy!r5H0o4 z9URCu`#B-C^RN0`>N@AID>_R4l5mR8m1AGpPPn$;WoK;BZdL3X94$M>zXCO^A0igdwcOIik(0Am>TmLymJqFDDfbn?WerYHUfO!AI;*BH=*V z8*8A10%Jz^EQZwQRQ|51oA^+P&rl4=>V(nE#8Gn?d zRtm6S836dhcx3&}w#%iM2tbxR;%X{bs&fFpgyC-|+0^g0Jt!p2#6{xIWBqs|I59Qr zY^bou!(%<`q%ifA5maznCyp4^1_?6xdXdcLhBBLdQQGY$4lVXJHr=W>ZTCq!j#>2u zXEbZ0wNqu(jLhA+(gO=%YcV_uYC0|=bo|>evb=a>%opZbw3oxL_MFF?Z_e`=+abxA z%>jN9fqQ;EC7vvT{>|nu6`EbSD?meru3=Y75@WUiRo2a~ zk6#5rWbRvV77CHpw5Eeb4ed$W9T0JwkX9d0kM?l%51Wwb2q7TRkp)*;uC3bQMS{wG zBRTYsf7=}?>TR721v3Wzq0Jf-a? z_XW^SauZbRjW&gSx`ikuSV#ipx)(CmO%v%&dtO@Hvvs)DSL{qaXQ_2{rl%?xw*Ttp;v|J8Vi_c}uJA{f(sM{6|1%T@{znIuUv8u{nE^zY3f zy*oapt*4U<$4I37UbudC2~k29DR(KPKHi@5$I&v6{NRvwUdxxpU({=_5=e84j;IE{ z1DwYY|6p9_TXL=6}RScu2|*{)Z2FT*hNT z@#t~Qz-EV>@&EODxh`#XoHX8@Qqj4d3-D>C$S zW?Std{nVX1{$cwp<7;6-R$rt2;57=!FR6x2S^lF{S=0{ z9;^QylFQ}RgwOwy&%kYpIa@(M@vZPSX|}0Km^UD$ozCF>WY(>=16a;-<2fw;IZlyx zKf(L_KLhXCM?c_`?}51wmEiM;(j;5L4plxoN3zWv4Kl7xc`wZZtwre+E}YK(6f;|= zDF1kwKi^5zlm@wmndss-M(;KiDz$csAga2LYPghg(JE=>&|iUXQ|~y?xxOBSeZq=w zv}o@~>v%59d^m^Fd9v9#dr4_CGJIpjpU9}Bhf`M6;!k0o=?0CE@K+aqTB!)K?$D_) zvh8A~Dnm1|Os1K+JuXLg1hmYeZbUSXDj$3(qV&F~Ev)3QxUzC}Z;+!H zRL)9FyMZn=YXB#dZGdS24S~prb~`^?j^PkJLD-CIJ^UlKsO#UqS$mOpU%Ii60TTGi z^637lTp|{nnk~#o-8TC!$V`OuQ5hErw?l!5dtammYLQ{Do*VRUSyhMUd)J2Pj;5yi7)Y9z=>#_AF&{_d zk*^`H1j%?4-v?j~Fh0Z1d0kKDsD8kYm)-HSyD>0OZ5v@fp_Y2c=4bo~&^cV1zf(IT z#51o7?Pdx8%E}x{bQDnb!0i<3F!dUz9UY5ySVH~gF1Ipxp367)99N7xXA`MbcKs=R z0)AgGim69@iQo{uO%xRsi61U~N~j&-J(6#V?^?=B+p3hlGZ;Vd_&}F7Pm{ zChqs+fa2#A*BPpb6S$``mEK%9L>tCg%T|5D-6UkvL_ z>5~dRZiAT>n%3=A!G|9&AAITD_5bt8yhA)P8XcGS`xK0`ctY!=i})1He|KM^nUW^*kCe)c<%Z?(3`Nw@#*ZY@N4zm_6sCA0kiA%T%rW} zm2|(JT7Rg!(h1df@F}5{+H6M<$R7{r_ZoH5*ynoecZEZx+ag@6CB1aeuvQuqUg|!| ze+3?2D2S|kNb&dK!@#T)Y|F3p@=;j4={7C>pCHwvjkp=M*@jXjkMj~U)cnhau=Mdn zm7S>AI~2lAnhC(G>t*%R-6@gZ)~+nsRerEqHW2aSl%4wUb`T;C?S4K3SLn=oIN|$N zug0sYvukEQHG73At0mgrH&MZRM zo^*^TN^^$Tksdka2}`ZHlTCfE<@Do3#cq4u;l9HveU#$+5y6mK;1KwL{b03VB~Z|c z*b4lzuOFJTVTeH6u`Cqm_=zyE)OjBxPWpEG*P0YTjC!^8UqHaZQZ^Y+1cszYHptB# z(fQAPFlV$i6y?~sNcSNgU&J==rlI`d-`b&Uyf(M^ODvr6Stzejykq1M zgQ!qof#7ZG)Hs>ZU@+O*e157WTn%A0GCnm_4hCUk`Y*qLaT7fidjP_0AeXANWz#71 zPe0=PP-KaVA#TKS9)HPCV%no3`h%bHchdXb&;<9U1yWNe*wFLZzZ|Csu(c)-P?Df> z8-L{+p=H0{YmZC!%lPhVL`E2|fS%Y0T+Z_!k%L+B|0P8z9LZNJ!s0&?o|Cao6dcg4 z7+#6Tsagspof;vpK04VjuO$-Vx}Q?6xQpi4F)p&^f0}`w-{cE%Uo=?r(U#P%!mMQc zZfpp|2nXo;+Nb$oLRU{I-mc2z{L426C8Z&$j89F~vttRK$RV}QF5XdMZDbUmo(H3nB zxOn`RR>=%!05Ey7IRlYB&{RX&m~`}6jrCqh!PCK9PVo-gz1Vmk(o2nK$mHP?u0`Eu z`5SFmd-y-SlJ;FdDs9h>a7{LN^>v5IkB09)|GE1t zt%Y%Y4!))nraYdGzPFt(uE}oSHQu~?5xf`)r&@P?Iw~X8T{U#M)E#))AD! z=Vl3rGH!!^GoP=$YmHfBcwB})t;Q73I=@z_yH+*J&e1QAZy~U9*vUKz9wuM6pI>JU z2=QXh^i&E$W^!DF&l9{sj8<&gw08nud3{vI5@ngVRcU8#-t8=aCw3G>bFlSG& zLVb)mz3YHuvpqe*uUgqM|5cC4o-S3&L86f9+%`&0@7o8j;Mp$F7s0E3_=4>B#1BY7 zJAzcv_kGnZVaQGF|C53&foe?;KgrBaGzIWTw1l(L%l)g@G6K%5bSHo{V`A$k7n=)qzGFO87(uW8_+AQqa-wrsExgepq zXA@mY1WQ$BRHJ4N$pX8q%ExUCw@SYrNPK<0B6)ObS7~vA-FZfi715G-z*^F>QLoM^ z!XL8u65`xN6eEQ4?jyBi;1SO-0;3J^U-8BzO+A5FUwf)wVooFe{mdwOvO@e4l#P|D zl-1=vb({1J^R>TyXvy-4+B*-dM2iQ5KI^Zs-f}Wo-x!q_GNoGHuj!f(i@YQ7?7K^} zMFT2vD+m@mn;*B}@FAZ{A%&EP&whXnKInzRXFfMZ3TcK)>KhNYAS@%|pCIL~^KRomiiyM?XNdu zP9Hn;Nh!uhyf{?HcsBv=eakF`hW%>RU*G;=HbxkPqOCWF&j)%ZR^YJ57*eE$+z{eU zxi$$tmGez6$M^%K^H$LV%zmvpLVCaWYRjz2fG{G& z`tlLdLnZ+Oe$h3t=OUvDT1I5&#k&{|8MXNG2~!C@%b-2VWh#{VupdplZXEZ8l>n^# zmuu3G>$40DbaIqK0!=?wRK*LKOc|u}_{P84XZG+B`|O+RaW`+Z=oYPUb~dTnzbiZwh>87=7L1LNc97L2Pf@rKkbt0`lyhmqQcRCk0?(5o}g&?8CDXftMW*Y zVj%k-WqNTRS& zemWT9vmvSslYDOGpM+VjF%LYfh|KG^mnTZ=9iEE&5;d;3#at&s9e*~eHV!wyTK4iD zg%(^z51&hhP{#*xBRZ==rmS5nIE@VT#pdB~QH0cDnNo=}%XPXlLNHB_#UGY-L`L%v ztgbsUY31LMZn4^`>IWIwy>4G7D)N6YT_q{79xR9Q@GD`dIl^k+ah^+9q=lXszsTnk zip(A|tN$g{8aEw-SmlH4jUN_Hru6IWRUY=$b9846Nd(%iyvNMFixbdgSJ(vNxZ4ji_$qABv>}>0Ua16n(V=GR)(o9$*S{P-@o{i_v%~lcBUr3RjLRR1S27 zUt1%{$V%#)fbXwi7B0HK|9s+zk{O=WAYSnUXCyOdY3sM;18*(h0Xq6S3~`z;R*dnT zph;R@`iq}@H;E#7q#KQZyxP}0#3!gBV5|*0qP~7v1kK|%@#7B^6?w^Wy1-fqm0((W z!VLer9U&V0>ve@L>D*K}d+2;jKpiv;oMYeo_8)&-ASvK;g7zI>C(k#BNTtH|ymE#D zJWBu^DXQ=8iwf$!D=Ux;h_7tQsc*xf6@bi!?u;+7NB@a&xsj^>8f$o0A6Xd}3J#dL z=Kw_IbStT|=q#6jmI#ZQJ36(Rj(3t5gQFTs_1>fJNa3>=8kEC|=+y-nTIf5^NO%O? z2(hu5qQn^_sZ`*xtjZe>yta@I6<@|meV&oF{_d9Ql+iSi_$Ts=<)yzXKMou=?v3i0*u5P(D#+|IBk(iAmkyZ=Kj#PnrlYjjL6z#=oW9cc-ZEYaIV({3kA| z_WS3q<`k?5EpSS$1BnNJQOXd5XTlJEqW8=I>;~`A&W>PWdBuUT5>hK%u#K@BLo*rp zdn2x4+yJ6T2A8SOs_%RP&)sZ#(Rp zKi*?Dk{%Xu6>s}oRJ4NZ3O5=q^?hBzPfJ_XW5-KXdfA(LP+q<_kC)OK-Y>IvhgDvO zOo0LMXJO&Y|Ft(lY&h?)k;kLqOlzi)@~OL zTshhOGW^dcWaT;d_a{B}5{5Uh{3zBHvLW_?K8Nx6xm~)#jWcFSa~YRTTTf8B8l?Em zwm|r2#}i{Uglpz`)ux#z^b+83gdaWVo-&UXKREq=p%mxzfku!2m9?X3SF@Zq0x-1;h`8Cc6J=p-rKSNj6oR?AZw@9=(Dl#gYgRK_$_y*vUk-y0oRwojw^bn2o5N< z+rmT6%O<4=`!s{vPy0zF#7h)$Hq3I(H@>(oNy!-dCbnjh1bO?(hXWryyABjlO=r%S+sej&pR(l2jvB5)O2ClYP6 zhpiS4u!bvDOyAP5Hf26T+FP04o5PTMS0+6`Qrz6q(GQg07?a}}*k-}}B?%e>7VszI zc*YTpo4Qtr(6gVi;?O|9yyXTPYX2^dEoWU^SO6Q;gHun3 zt*kakBh3AD3VfR8yOY{DXu>0uC>061riT)*C%)&J5`rO}f;Tj19z^&JxvXw0^$anx zhYvj(hrfql_jUsCObvD&mO`cH+@fYt*W+s})+O--hw5%O-2)FOHR(t;BvJ&-f5~ik zD{KzT1;hz+c6sR=H`UhU1P+uWiRzC%6%_K3{7YXDgzIUi}Pd!-cZ2T|JvgMb6sw}jo6@V7oMQE+`Bom+zj!fCRlehO3Rw%*h0UjwT-XXFj) zJH3G%EU6^?+UW9B?4Um?Q!WK)hgY@CN6;tVNSv0d73xP>I#Bk;fh#(O}jVFT~oIqZ3V3aik;f1(+``CgM5^^%BUow!6` zgiMfEJ!;nlnDKLeQ^HM4%JYZ<0|m8d8+4AX`4mR;%fD0t&vHafJY*<;Y^tK@-(aoh z+v-Yz4yLN=)l=8KE0@0`5hid2A}gyLGSUEpY%CoVpmAcO_yN*mti+M?GFOs!DaOvf z342p&Ljr!I!W1wco2;h4Obfr^`_J2{cw||nBGhuQ8<)2WFO7jq>jSy>{#dQl?mrJj zvVJ!8_`=EC;okadm$2U1?*VTHpIvF1vM>_qxA9k5)lY@LKxEe+=9dBh#O_KdbmO&Rhde-`L-Thtm~(9U2kvJ{X}Fgeha?QVDIrOszjLwxy9%UEG^kVyK%BC64}?*gl)h? zn8S#JJF;ZBYdkx;C*(>%Tn^+{je@hADbA4{?3+h@z+?_sCD?jz^Wp#FkTP7 z_n#XIAMn>OIMHtLess-Qo3?jxZZtfYA5VJO%C6CSRe2k()|=Y2f+nJ8tqZtY$s*NH z`<%u2g-^M&($&v9%snqpHxnOL?Jnvs2Rpdi3HsN8grF#D+z2%YmqTUvTf6b$&}HzgI^EE(7qrKF^RwdV9R13a&4WA zTve4h8t+ZmLM(X<#9p8zC8r}F2DzaC%=`F}?N|*Q4{JhYryK34h0f zb5z%z24+{MGN_2;cBXfp6I;p`9 zK~*E+@ci}gRl6`AJ%3WwUQRXkC==*99Uo8;GZ-Y+H6oa5b<#q6D}kq%aKD8vUrf}h z^uT%4)T3K{MbjhQj=%5+&$ER^?%v$#3Yf$cmPDj}{+WXzT zm3zt*|KXjk*qKz$L#m>s?~Z1qa!7j_DhFk#uJhHRIZtzN6xT%kR01?Uhc9S;pJKwo z7(WOivKu#ru_H&j!NxWbz`!*~498^}1-(R=3w%B8hOEpkiy(pbT&lT>PA3<_1s_@* zG0{ppKF~SuGJ$(_5yu*>CZ0mX>Ig=8D2j=;`gd*W^p7>$E=CRImkKrcp=`E8`=?MX z#fh+41irAnuhYd-qOsbkp$W+&-vk-hE!jL6ks^0R%%}NFaPi{Zz5lsGm~RJ;Y~R+p zvTb zJCr~}=jBgV7GpSKSiY#(2R{^$bjlUe>8LkoQBICeXg$SJHi!n}n6So=&|#^w=htJsQ3poXs5hQkY$O%Z zjwQYX7&FyJpjEO8J**=6rpleTuT~b0&kh2gEXWl`eoZa2%7aXHWyPB&^2wYsJ$j%c zYyO1>Auz4kWu>8e;}Dl0%DUu!c?fmlc*q?8Nvb)oGAiU^l5wLUN&R@AeB5gKnJIs_ zTogx~9r{2=tC@T6Ap{NbFcp@XP2$B@C}&FhGF($v04(!_o(Q?)PfvQSpk{CKsX#Lz z+NG~WQHl%?djPzupxt!`Mc5+jcb#zr(L{3!Zx@DP8*RZXL9EuPNCnkh+g~%~@^U7SQ1<&Pzy;bC?u(GGIVh+3{hsqT1qlD{ll<^T zWZ>;CVnUGKXf@n7IcLF2MaDiK)eW#^$GOR?+~5LWXN}NB8srm3Mn+j+QSCJxl7QY&EJQ%L+x8UNXzXW9(_#qSrkR^(>Eb zXD9IazE#|ns4fU^X-X8UD50f(dfQ3kbi?Bt#k3YR2)($+h<5*m8XToR=6s?k7j9GV zPHK zz4QMneJ>cU{7nZy&Xq_0i1a$KSNTP!%GQQm>Bf8SddA1OzKOBRjfDs#&re5x{k^;eEu=%ko{yH6sn z|BiYIN&nim9>sM?zntq~%)t=OkcKZEiGN1|-I3T(>;GX&zeAW3toSDR<_)7pnI!r& z(@U~si3cGI$-v}g!^Uue+FEK;sEM%sAn*{}^&&kkJKJ&tC>XmQ@}C&E-k%(-=YsQ) zPrSlpnVF%iT(M#%Br%kYpft5;GiM$u3julY-}qH?Cb!tqXnw!1Jgj(Sw+3=suPe`S zqsUQ~)QhBalanBk1(N?ktZjk1|CU>4Jkhq44|-hT{-6{-?wwgL@);p-o0@|{wiQL_ z5slUIA09}&Z2pfmqmaPom%-yomnotP2fR0jQsthYjx*yxq`UeeBGI9a_wSJo?$171 zXLmjT)ski9oO*UU4GHGFvAVX5VZg22F(6dg%wPI#i#KLhPMYIwB^L`VhtSQmHJM-+ ziOi+SO(8~dZTz~^u)@?)v_TIajr`g;G16oZ@q8azM3QkAn({q!z>cbD_Yc<|GYHjm z?OM^tY6%L=rKkqG0E>8T{#!;t`+!8Nd=gfXC#U1T^c|nF z?GPf=psu94*=*-8A(G~kE{Al8{sU*)Wo`_V7n~#3n1U&m@(AWyP&+V*&jRulbexrh z8vpgJPRcH`E~Jm>Cn_aM`etzn@tJpW53}pPW;pk7cgEXxl&IjAx9m>dMcaA0fp5_| zzfK+6WxU>7`U`gVEWN*_BiUc&-u;ONRk~af8N7OWjtO_MZ#;@V$~CQD)p2A(CWA(R zwh=k3y)~amv^fUZl>GSjEFbVOyDa;sM=!^%JK@~Z{woa%OFS&KSY#{qg_BU;LC&sgirm+*4 z-)cqu-!ViPTckJ7*WQ%xyj#jPU8y&d#?6LAuIpi!bdT>$Iu!w25Olv3j_Wx4jcOfR zC;GtEclEWcwDQ>yZBTq&HSkX$>bepU#rla70*`xifCtv1#?i zh>nD0>$0@W+g;iV*6@PApq#iF0>kt{_X`9Fu;}r~U$b%tcq{v|ydUbaGf&Ipm{SwB z4O(02Dy9+_AL^}(-ig{pn!FR@J!PEh#*O{oEYm}I$qJd_z62S*<&Ck==3h#af;hWq z%Z&ecI%V=iAYLSXYgAZh*3MWHFt(5YF}=TY@$Z!wlJDM=2k%mS>!RZ@+m zmdid&%~1LI$DyAHYGUuz!FjqF)Y!&S+mT*=Z5Z|Wm-tdl6ihoa&$MQYmn}sCUs%*x zZI0s+%C(e6`%@Ac6#h}h@i?Vo zDi5_DM@gPjAXufz+b>6U&BuswW-rr8n`oo0m4ipa4$5!=@U!IY;j^O5VtKDX@^{0D zd%hBlEl-S)3=7$U?1P7KL%Z%vT$og5wQfU0PFJI@#e=G3C@;1K{Ad@=wLH>@;s@1W zt7gIO!@b_Il@3u{Y^PgRB|h?wmmj{I|2EV3FGi`PKF*+7QaoX$-B@I? zvAS`P>G1X0S~YcKqVarE0@-1! zf*mau9UIw-nc-5^GRujpr+@ONn^_x(l-Yh>&n^MD|CGhr3f%jujK5|}N5BzPG-gMa zWUc{V2@NDr8Y|Mm8w-$ls|^Hcg0W?-fN(XZ2gu? z(A))?c|`~R+l+$n>K7sSHa35Pqx%HHDZ+ohHn$$&*^fr=2;aWfeK}W1?fQ2JjZd6m z?n}m!3U?m;f}+p&w-vf<-)NmmWD%Z&=*+bh@)=w56Tkpx{W?4?e3zXe_K|*rd+GbMwkvCx_6vQAE0``;()<=UD>-gx0=fESX&kK7MRo0VTsI=Wxc z?xyw-CG$+BzHP8sI-xzvrAgt)2kJ_Imc4RG3v`=Rn@>v1*luI(=M^wf0IP$e(IkAD zRL@}9V7ce{%cKJ_IY1rE^(Q~;vBhyZZwuh?|Ga~yS&W~j&LiM)#;}*((B&9r8e$q?15MqfS6u@tqY)Rm|n)W!RL)XCzV7IE? z!yglEo!2u6j!CibqS%#$*yZ*?*njv)y2+^6ARz|H@(dCAJ0j|of;Y9}c(vb3l1gQh zUr^w~V6m|=1Y#=Gc_qtZl~iux!dhF@*QZ5KuSnGUf#2p~jAU;0RMRICI;aQYyACs) z6cqyz4Yda8;=^jc!p_U)Sg33~qw>s-Gf>ON;X1%d9+8darAitg=rRB(iu2^9{RIeg z$k#*8YQ_l}-~js}h2nWFLm?4JFf&D~?UBPSEr6fe4q6-VdtbsQRj~s20=@-kpxzbN zC+aiEZgPqfD*TX~W?5Z5r}J+^?s3G4lVe9rGN*BRWi-W~s~vWhcG<>jPf z$|RpZxx_>Cf1WkSraC4HHNHN(N_Ns8k5M}8%1``W=jpBzK>XKq9lOncMlWCzd20I!-UWt~bAuKme^5!o!q1q>PWsW(5J-Tyj!Tj$D<+!O<8Cpn zy5%YP6gR3zw8f>AbbB~%pvB2h?#E8T?){6SI8QC;=W-HoNFGwnvr-{I;<>PnMgP=& z1pXUDC3i6<4NEpBisc0@3Bhm-iy1X zmq|21lgXDy-Q+rGf)x}vvkbyWzc%VxEkL5BWd`c8b!T`kM*-GHzk%>QCV;O-j$t4b zxT`N0f9a`E@25Hu!(Upn!z&y5DQZ0?Kdns%WG{z2MeGxnRe!y^SQJ+`Gm*ml91+1v zb`|=r&tA$RGSE#dJ|F$iyajqx3J<#;z=umHd`&=d`&rLu1VA%F2bU|m373wHTZ=ap z3ves9=h5ITi*tP7X^EnCB7D}aK~BMWw6Fl-pO{CCkSQDYts*204bDd|$5B#xB8{Q5 z`+=!+^*JyNb29y)q^R^Pp`2BL^XIDUBm{e?duI{#NA8)?pD0cOV@~HK1`|z9bLWPU z@R&xiN;F(@G8?4^X0Z$tB}cdMUh^vo{z~!>&02&~i_1!tAj|JS17>DssOcmNTES3};xJ-CC>~)^0K3#}8ZvNQZ=rkL!eX8$}S!;rbVm zkh=#d+X+R5BQ&N4iBte8#Ok5fh@$U+&A)?A(T@BYMtYX0skLWqmv2_-+vTR^MEQN{FwE^KGi3HDJo2!zwoucnMR^v*aGA7I9$Kb4 zkIlK)GlJN#HI4Lyom8I{`}a)o$TCo@?Ecd=|0hczn^wX|Zjx?1^&$6mIA*`isU|Th zMQgP(oXh`}FIop_fOj_BE(p5~@tRKm?AE;f^2O(>c(biP(cje=lFga^!~myt3f!b?D`dBnV>{3^{?Ar;C-*@m@K_R zb3hLpQGlX86ihA?$pL|<#s<&dG1wIGrf^g&Jxi{-(9q#)p9x*)^%2`Kx=i+G%YkWu zBsHc93bPVp)96zDq#H;QriZ3F$c6V~9A=zpR`&HEir;q1Q_VRaQ(rkgR`?poH!~DR z)%wGE=%}=MviL%4C$w8bu4{`Pg!TQR<6*UpiW@OyWLR|wdC^}e*-Cqj-sKntj;w?} zE}B6OPMtm}$LN}ur#9yc9?4ToaQ^>_8M)s|I0f(5@O14(2YWt4{C11#M3hrUl1dR$ z%J?%4I4(PU`>}5X3XNg^ENY^*ywodqDS{AX%aRwj$ZY!Hm6h7z*P*=S-#A?;lqMR# zy9#YXm8qYnqWxpiR!hpZYQ!O5F;_zm3WlNt#DGHVlx64TMS=UB=Ck+nQ9 zW|%<7j1?pXlJkk7HRdzCuGkJV;wzeRzlhqBB)X-CV#&GzZn-VGI&HQ#T&Kk#%@uKH znB#3Z)appEW3(drh%4I(nzC1Kz!!N;c&4Q!brIB^-r54FUr&nosd5PWI27}`#by+y ze^!{0p>Bc_X=s{rp`5sFRyCEd;X-r5Kq3NEZg9->k~q+k=tHnH;E2AaWPm-)^XoSj z_n2euDnvR7dwXR~V6~{FL=Icw21;Iv`C}{KuLz6H@G;<b%(n1T&Wcr%KRl0O|2gKoB5KDDej)(8wf>?00gzdfEYwU&E0Z3hs9hW$! z>$-uv2=_^wmp?no#zc~`ZW~%PvQUw^zJ2#TtO8X1Qk_QQ&-FPKFd9}$(sy}f>$dwV z|6_Em<7;V74=xL@gX*mAh1sHtxk8=|e>>!+g+)ocIhkN;ne*x$UG z%ig$3=73p9@q4 zVq}m<@ugUfLk9%!;?4+WKJpIXDH2NXw49GzDNT!`C8z)8S*bOJq9)yS1Dk`RzLQk{ zcUV&~G>)Ve25yp1`|;w@)oR;UU@T-gwO})g-v2+Q-ZCt%F4)#>+?^o7A-KD{yCpa@ z4vhwPcXxM(-~@LF79>D7L4yQ$fXP42YLAjmSN&0>wYT` zNCv5RA0sJ@JFht6W-X)~w8rrevJuT>j#Be!aKFx7=s<%SU;>qFW6QI^QkI~Uqzgiz zzl&pGMvGN`^@H=t@}j{K`8rUR*$W?ovcsW@)d%C3Os-UvSgJ4Y^<&cytl5{E0f|Yu z^=f`1lzq#A+j{4UDfS)~(L0x?GjM%1a4dd7c9d^=v-huv)U__eObV{uTq z+PHLC4UMAo@G8}Y&h1zu6KD?K&-2?@f$|6nS@$SFxWX8%oNwFjp9vpw5?$oG!yz*i zr_%0k(VrMyGBr`FibOJw@WH>dL&$|GOWTa+G-48oerkOFeM35ulGtSv;qICcqTu~e zFPD7@z*C)lXW%1pS)j)1Skq8Xf4nVQA!_z94U?v@x*?iPBKDT5VKA?l(kBj}O%5sE zvE>(vQm@3&UC<$uv!-l;wYec1-xM*+VyglD2Y(E=VZT5iSs|&PqJ=Mn6Ry3m>MxLOE+8US_;nq9v}AvJfC*bsrw4W~40S zvT{!RuNFMdZpQOSoD-1?wu-MadS?LcbW~Zl7M+SLy4b_VkzujLLnq5O(j#1^dM(*} zn8I{tZN~(X!cxFy4R_t5N83jv1xL_#%eW)27mT3~G5}YeGOgA4)XM|3?E3*-$2fYh zBrlb3vuSArtBP^0rZVQV?^VGns(cf5jY1h^RJ3ppQ|GnDI0ei(?E`j|_ zMc%$67MO~<`CdoygOcHa17}qom3?=c!WdQz$g`i9LmU0pPYjk|;2u@zN64Us|F0is z=!pdoT&;YV@jXYcnH?Xq2O*vgYye`7Z>6^y70bjqntwJ8KEk6KqW3Q#G748r?41*H zwB2{!_IK(+E#vRE3kTZ9)oK={ch|4K=82@QD=f^u|I7H{{RB7;^V5gO1p7==QOCBA zfoqN(2OQ8m?Z!qcf&Q}Z@XJ*GmKR4ls>bc-O-h@ghjWArTczdbA$;`{vTvSwOyAQ} zd0KP%3;=#nr2e*uaR7Lf*?yuv#(v}&WuJwhFi>=bLa-)#{D^B+U>noTN4Jbt`o%vC z{Fa!3S>4MK9{NAwN(reIrR~#b(R$`N65+R+VUV6y0&+i5&g37XvUd}dy<02i2-D^J znol*{M-DrwGGt0|@6C*dBR>zYK4}c5(764R96LSR7;_M*#TfAMJ~S<&RMyegx9el@LBT zmQ+0AAyF#EzA=pGeVu?$EA1M${icN%17^3Ze9a$S609@NyAHe$r-9u@pK27J!siS@C01WT-H-k8&?23AnE)|%@&WWN_q0v@YY&o zl5FLI0u8?(?+7IRwbCEWxp>!x-JJFivK;Vv*3pGE7)zd+? zKsC&a$u}T%u3q2%?!LFaiI)lvV!h;Csw=S_bzHOGKhr=yuX_UhR&TS_UYC9Pgdoo! z>Ja+ZS0LC>{N|VYN*7@jo|vnDZi0is0F@y%(WC}GQLu;?Vl!M-96#af+5f7?ku6~( zVYL|_@~;d~pEf(IoI+dgx?W7FYz)ddtwlA*c{jNj1@z9|=r=$rHH#>p{q}8Bsst5C zJWyVUcSX}nPIFtFj6Aw30BcE?5QPpb1>O zPCQF|!=Z3m8H$@m;{t)l;_vAz@=(9CFR9t*4DJ;)87LZ4>vjE~5OegI! z3b}`J$hsbzUZS~@f-3p3yp#d^NzRA$zlsWR1gx~jw(ozk(!F7*8|*OG!wAO6HwC{V zu-2@K-&YI=Ipgj#DKT?0*oyLw+5V?}@A;3`?n4NL(j*Y9EzEAg9U&F%L9x~2}W%pKC<8{Qj! z5s7Gp5UcTdjTV=>I%|Acc^Uy}1AzAhpnR40J=Ki~v5UwATq$4HMW}`g04tFaS znj-~d0Rw&5@WRNi6Sc%@kO1Mud1n%&5;3eFwBzkH#3X|`gOrE`IVlr}ishe%TM%;= zT1Y@7FjCMIvHA3xeLP+qVtPK@fa8@5oUd`#3S@IBe{DWuK1{&7f4vj>yekmcJy^7~ zqX=WAv79X*?<_9pd4#u0&7}vTcx|bMB(gEh+mJg>!GD_W&u~*ex8mC(SB`6EQH9u% z^T6hDVudWG-t|?OOjw-@%31EO6q(q>pB(ZP;$Qxi7UsOWqW=6``6nJhJqu86L1EZ4 zo4Z=Bs*qYwurwN3*A`IMHzDYqPDUD6rXU(%2(<7bilLtWmi6SdJ{O36fEs~7z`u-nj z=g2GQ-ph+*zZh`%e;&{Oy`%e7yA8uvQ&nESoZ>z80buV|k(_F48QYTnsI}5TPE)Gb zrk**GdO-JfxjV%oc{`&XHTKg@J8BlgCIkF1$UK1?9_q~lpD}CT0A7OcOuK!TK z`d9R1dk?Eb+yJI-E%yumPcStWvhuO#CK8whWkgZk8aJu5bFHlaf6PgN;PbK8kG6X5 z=`9}>kqS;yQN_1<7AbH-WN)93FKMMGlHnoA36%{wC;$1+nX+`yxFuvi0u+z88PSeT z0@?6NVxjDHH_PcY?m;^3KSb>>YGA~Mc*K}>8@{2intvolX9cetqzC*hjcB`&s^dmQ z&6LSUjAKBtI}G#Vbzw})Hr@SVS#Wtd=D?oLv%4tnEafEtPzCD@p~dH-|EDf?z5lcm=^X&wc@XJT4v?o%!jprWCoKa?p!GGrqHw} z>c9R`f^9WY;|PA)@U0(;o^%1^j*4qAS2&rS=xhXdt1udw18g7N?{MTvQ%)p=R7^;1 zH*y_Goqzp3wA`K(SFv0D(O;TSN~|!`g1Rr0S?O-^^ko5<*V^s^?@fcp0WES!Wj@w)xoocg`QhUL*RT@%f+S}UKm4Qoi;`%(=7Hj;uyT6A(OGR{2A48+qOyfiT=>9e`uhUb5eg;Mf zRc7$gzZg@d;Gx6|k_RsT>3=Hh`7UVkVq|BneO*2;Q-{TOe)OEqIR;$)iNLn1ljvw? za95NbNT9PlB}2z%)tqEx>L^apYRJsTytmtk#kH37NEcD$%I)lZcsYMrkEIAijlS;{ z)~zx&{xkhh`@FQ|7ewVa5YFhjV+Rn2jgCY!EHSxWyLl>j(U_5`ds`O+6_`3?Fy{}S zMyRSa-e8ywZPU?(MxU9l5&Zr9=|qt%#C>eaomsoA&(zbMVU5mSXS3jL!?~z+Kk*1A zUi5v6|NFv6+Zwa**||vd>2$!^7lO1|`1Cf-o-f9#kge|U!KI=9VUj1k#65!WASvL~ z;_;~>%On>#>~k;UUFZrd3hzln5BP-nKU;wR9eBU*BgQTRKg4QaKR<1nX$)KBB_F;JRm;$cO{UyQRs*V9_4-V zjxrM5oIumo#_zFvXyC%wR?=O2zkutf=~lWZAAGQ*9#>v-)_2R)jBP?jemZEVu2rt_%0L9VThaET(l}zc@lr zskZ-*Zn=0VG8d<%`3MgZZL!M_x0n7=D>b!@`uU;Wu;H!Zj&h4 zt8ID^k{Z>e?mE&G#*?FGeYr=Aj1q_%N6yO zbze>b*d^o8j|zxDFB75(JAoK>wZ^TW%J2+(TxAJ*!|fMT%)W*86a&a$6cfq zb1TW9`f&?2qNJbc`+zvZSVp7L`L*d+5fmtD`cuJkIgzsX;#)Bmyn3C0&ox|acHLdg zz8bqnP}`Ax>f-H{%9$zhQ2M2v$9Xc zMjC}hVGGy(ZHI!9`nG_)MNn6vZNq|RrK2d(mRZQ?2U};Mv3vLUxN_6_7xOi3a`Hd> z(jxnXSbP-a%2_O`@3VDs*jnJr;Bx(&BcFhab{O)OlPRi(tz*|M-J=*o99BkSg$P!Ig~ z<0d%_OG&&J+RgZb)v#IQWggheRp9mBS}whwDEl;QzJ6Z=_q^SZ9c2XFkNLZvF&DiK zr-LlH39tky*Kf*5AnsSe=4T|6eYyZxFL{oy|4Z6ta0Hz#{z1CW4X{K1aG%nLlCnnV z+t;b-fE4d{)}ZDfmL2XGVDQ@UB{RlHt=&)!&vU)rUrVJ2uff8qYKcTDTzVw{o@+k$ zx7T7kM>Fq56lM2y5_&HF)MbBM` z#Y>HUZo${ney-<_}*N*DeI!Vt#~8=Oj%9}xi3 zd%{vQ8j?8@eEq18w)^U%y`IkX$;IKQe!^Pc^I>DbDvy|^jND2ZD&fC-QP6zRh$L6Y zZlcBZIG7E@E17)j)08)3J&`ZVFw8DeFdsRZZsQbsY=K#7KgE#axL`#L)ZpzHE)S7U zhrpca2`IANBPAQHoLiWW!MoQmUF{-wF|XJaBx#sOD4|Ns1ZP=~vP46y;au{j6(=?v z>P@T`g3d?n{hyKHZ1C^n*xB%p@iQc3>cQH3P+k+2vRhlh+a+P2%BtPsOS_g$2|oz zpK$D0CN5q+zyQ@7CHcfni;tXw5D#qjhr59zC-&!;nF6N<(p9^LMWhU?;$QLhYAiBo zCGkB88G_mc`&`ytYNYBzBVq7@>w|iuNe8Q?^`f#`PYm@$#ffIbSULkJ# z>29H~3-_g_w2nJM;iT?!k|5ozhQ>ie6%s|qxmwN_9Mx(8-EM%<$?t*9+fm(!YJR<$ zzdtg|uI>ozNh-w6zAr-8+;%Kpav=p)U2Am%bWU(wivuHCC%hHL7mGF1K7X$N7y^dv z*?NRtz-WazGvR7}{lAj*dtrU6p%z%|nuEc(PDuY~Smt6O{5Sb*M09H; z;9O>L<9X7(*ustdGgD}GO`o{as46)H=Dn=?Q$c{QaUzv~K>E6J%UeH&*HS~-+zFgX z$-V4F^oB`hA-c3!GRYkMdBrPt({h{G$@ z0WYiG(YD_|_E%#K*lX4B3+VUcqLUKyd_i>hRG7XR|4_dY0T}cX4&`ckx}Dr>T5%-E zeaiT8G%C;mwui=TgYqjdvpV$#`i5;Z#yOGYKj`zA>ybHjt7yeo_+xhl$t|rI*SyMQ zakXq%qe+GqH*DFlf1_?S48pn(S(E+Nz9w52ICHt4;kP2=(Mve{xSp&@xIKTctDI9q zXa%G4WhoIe7FwG`=!g4*n(gsO=i=PdERC-s0N{DV4Yp8$kR{h({b(Mll7Eh2cT0}MJuSuoKosOZI$Pce006!mBnno%aZJ?~U8NwI zD@$xcJ}^Hz{a8-5nvp(m#0B=~4=%RC=ViZi#wS*0q0b0q=+X{o|Ao}b%D?i^9H}6} zNMajaShzsAPQA&GE6nig8SwhBW8UQ)>Ktj%WicLis2j@Gp^Nue^MlV7&QP7Mt9n!|>-Ws4b!&e82;>r`46 ztV16G2N6Gk+YqHg!A!i{hA+x7@uH}@IBf86CF}C{9)3HCArmi}-}rQm5w{VKE6<1D z?}ECY506hE?=xhcvu1OBgP_@Nl>4j?TU#fa_lxeMk69J$f!iY?pgLnWb>GdCH@=q@ zq*t!{8DjcCN7GK4qPueU%Z`5GIcG;*odzP<@c(~u{9mPYV#Rs3PuZuoUG7?U#!TLS zabyp#ZQw;xl7^M6ghe{Y6TttFL=gA9uVSJpo5Zxl33Exw!8EF)J%H6%!Cbn&9kK|$7A>E?%MT!~N4w|@@R z=hLp{r3&4Q-{ULiupoiYa^sVttN8Ks-Na>oj#GKZm3&4;s}HXBm|eYxBwgT7DoymY zayK3Gz`INVU%I60 z(G#wEZ<8N_J-Xyqw`4{bJ?8!P_#lLIkHOl>1h??WM#C zNfMteQI(d`HIa}@RDoyHsCFp5nw<`-Sd|)pVrp(x?RchJ;^6zOe^-TVIp3I`ekxY@ zjH2gz%$bpdkBQ&RNxK*xi@DM%ybE^H35a&u>f-TnT=2=|4rc%nU+`3S_u2W!vU$pvt5Ye{LOOfff_ z!U*y3I>^ z*sXt0r3~+|+H0P8doVR(U53q%%q{y1nHa`5Q$LQxzVw(5*)jp^ptrCr(u;x>f~wU& z<6S85<0-BZ<8{Zyd<=oZM=Dv(Qu+wZf}p`L2}m2ubRx`&X4bqPpD z=~Y&4^l0VeK=G@=sNkj4XUDJ}|1!jMw3J7pE-A;+(z}6{YzUX1Gi3y@<&0P~QZGo0 zb-_?(&37BVrOWYroOA~RKuZ-ArsI#;`_p}re2&Z6Y{CcI?$hkztOZaY&~DvOV|wPl z1$0OTp-HCNL)K1}3R3dFqD|=Q&E$Yz0Qp)~(Xurz8_WJJ=d6!`?;FV*+l&mR_u$;f z|JI{4B3lfVN6rll*E@fz*6_m!*hhTpmj_0gDH|Pd;xIU*<^cy;jxQ@`tHl3KpO!am z2e+>J+~mD@hVfOV?>9EQpK5>PaO|7-!#98f`Rl(Zn}8lm6xN&=$MkNE!II05Cla&O z!n7kd*kB$E6fFZI8G00n3IzUoBGi94CbeFFPP%0v<^?j;=hp>KT=*8N!Qp!8kkY(P zcwb|WmGGqlN+0`ke0%3Z(83oq)KVMSs0#9&r$1!{4`^Qz$fZcCr@jQu@ZK)J?>WO) zXM}VFih7R^$D+3}gf)l8B`f%5G?=(%g(o4?h0eyu$1mO@eDYuZL9Sx~gmTNwrNJG3 zi_4so>f+&45^D8uFOU`Hz+@0cdIez}tF2knnWZ4@lj>tjX1882puKwX}V!p(p$ocorU(L(AKJB3%9R&m#*^@soa%tnpwE6mqkfruPU^EDJ;J~-KZw4ZkpLav&DiVY?* z>Tp?fh@fh#84aH@)oFz1@Tor0M(1#UiM(xN7a#r(FooRirm6qhn;)q`ZrWR@qPQ(l zZFeGTq(=lPFpmaNFQ_9X$IG8CSyNiCkazXf44Bef%2ePm{@e%IXz6I!*EEzqa*a?k z7_fPw+2duk^gbjP(J57ILXDF-VPm7iKF_hWAfDM&*u%C?NyTrO!ILE5jrJGC^DZM` zTfE5!YgLvP&r zGaEvInzIr8?$*|eY4fQtIZEZCAlS)i`EBIGOGE6H_AMa`#7WM*EUU@oN=LvHj3&Y# z_$A9jgT{nBzC}~6GvX&B(~JuezW5G+#*atr1;h^P0Arg|WyxM|z@Rj&Nm8BFnk1m_ zrl(Wa5h9!``wMFc#q=(;^+ef%tCiQ;w~rwBcW^)-4~}(hS?ku1kBGJWpI;DVES%>r z#g{|YhBt@uDsq%kz^c23Mt4RUPJe)A2N1gZouAi>_15^GFKY z!xC}V{hs{=86^Z79ZosdbC#LFENb$xuFhk^hVE7bW_iZlvYeP4kJIK`@|){kPqeMiy-1}BLJ!N_1V>;Miuxr9)qo{SnR_wkL@Ma$@>3ZLQJ$Yf9Lq-K2gmc6 zS(J4Hnj3CH%DZf3HJ)X0>MBj1Y1Q>}jv@0okflW7Co0@WcPcs28aR)%Io;GXIXfdb zz|DQo+dZT@=YKpP_{FJ|$&?2S%(LQe&mTEG24mAvp$Y@Eki#f-?HKA&zf;cK6yUI2 zlVV;sPaOqf6}q5)B-qu_R*a`rf_C}`D}vsH^b|}glH!0vu}W7&>Y6%lFl=A?82qOY z*)fTI{#yjIMR_v3>Kp)V+0osavJ%stY4H3+uAb%9U-f$6d&EjIdWFC|f_PmPi^^p8 zdxG}TBO0Y$k6zH%P;vi``Uln?v|{kX&dv*gq4u20+8AuQn)eII*w9HR_Dq|{P!BSt z=SpNH;SQ>bT#!Ir}>FElD7wcQ&*qrl{CqobU6)YpHXP)+^bZ|(&`B~P09dO7fh9>aWtYV(+Y@UxL(4P^>xngC=Rl@kLr@nOFfh&& zr!x~t7WW~S#M(Mq@?^rhUwsLzR&MO)ll_gf81Q~fGY}$)h2-4JO##OV^B&f55fSHj z#tcH5s!~j;43r7Q>~bCP4LHRL0R!E70mPx=$%Vd?sh`7VyE61Ql?6OTa0RHP)r>2; zwciD&%7vlM!_Qram{i&rUe-p^7ib!h00R_2Y;$y9wXiN0S$)?fp(rb;rYLLLjFbcC zoWgxBgto+i+i_`1NFNzw1Q#{@fxu->HcnBdZ-CMU5dkh`vb}f%_qzku;(5K{xE*+= zvUCI1!@#OMFgU~?*l+G*(BT?Wf8KrK3dB8cP6}{HK+=#K)(jF0uM({$+yOW+ zw$R+Kdlgatf#%*wJ;J9T=qelgu%GmeLXhR((&HybiBDxpH|2(ph15C%BVGOg3{d-H z4O7}11}%I4qY$Z-S;`IBR*Sr|E56ADZsQeY3oFaN=gr}G(nnF!ydyy99zaok>Zb0&@- ze`E4zZJf)uG0Mj*l@$yha@*9;-wU+98GY5AL?>GA4Kvv6?{WYPjV!y zeNrnbZFzKEb3A@8Sa-(fdFHI%TX5vr5OdD+c9NsHq>+`NwXUDkw+`MT`A>H3N`k1?(NHfc=TXQI{-9%POx)hu7zOi=f85~Mk z%$%oMXolmz7Q$h7)%yK2lSUv+>1Fp>NWAWeT-$#6UCcxaG`wJ=XuW;%P3Z0(%)9wE zRJYAy+{;APbYYg}v}jFJ{@s24+f6jmtAB2N-FmCgdiz}zH|AT$n}{G;;BFb%`T6L= zvM4+&-^~R&JTBIuN@6Q9yz_dxel+{`*vOss?{b_p17@O!$#Yx9a)L4+IIny9D^~n} z>q~xdD$Q75*ne+@pPl?%3%d(t{Ea{2i?kZc1H+Iit>%C-`FOmWXa;UJc;r=swoCVTI{;pu)j zcj*CSL(C==yh1&EBaL8DLnbc7rzM(10qPzyJb#Og;tO5fcs8v%%U-bR6ad1N7tSb2 z7vWli)Lv)M1nH)M>IQRwN7FwE8OLbl-r(ErvcId$-`EW1M?mf~@ITayg`#$Mx8#sN-ppkDFw5Ku-16=!1lQt5Mh zk#HD{qLqYF46+hxSqKcfqgHD+q8a$GQB4CqJR?N1%2geo$St31g=;z4mv`Cx2Q2a& z16Y@lfvZ9}hBNxY1`A5B1YlkJI??cA-rX-+zGo)hAnlu(9NEFWwgD9afP0}TlV zea(KCEzW6&s!u7(_B?R4jh2;AfS4&%D21t>LPi}E`IIOy8EZ(g*wm6l?t5d{bhO-} z>37@KoQN^b1W9%smlhcU3%5QXoE@7jtaM>Zaz)jOP6(a4w2XY4$gI`K(?!{$#~oPf0{G7QpvKCoirkFl<&=goC4}9zM%d%R z-C|=aZ9C>XkBQ5cSuwwZhiRIXmh~tO@tTWbXmH=5-1#l->J^Fy zIa{LK8o3UqEfgSUj9l;e{re0asrF{_*&^3*j-zq#1(DqG{MNAI+klt4l;_Y)VEAX6 z<<(i0Q9;SSmB}`5+s;)_Y&kiKDprr+05Bf;W2htSZFh_1d~ECeBvPdX`E?Yi{py8e zR;k1I`Ui1T)u!0xJ%!@M^R6G{p8dr{XfKW4X=#jDyW;K6i$clSd(#kdt80?i@3-&6 zoj7ISeMl8mUcwW~?NEo!(97;&0~5{z~IZ&Iyne6c&adv`q8? z)fA+()&-$>Ki(LU&YfzhS|wTVThbKB0OWZ@QotON=s>rpdxk09bKnRKxn{V2;ZK#r zuT=Vji8P{MNgI^pYJFuGDvwtKGb1Ay{f9-kA4FK62qzhQ6wzT``YDqF;TW-q5QMZQ zsVU(Di+`%zLHv>Nt#y2nzk%3vw0sUGhA)8<=Xx5X_P$U%9>92Go~$!d7r z_Uhe59z(D2s?ibOk4vqZRW8$G{EngK4bz;C?N0YzsI1$J6e(j@f~I)>o~1G4(fk%6 zcRStc)B24#fE0WeUgkV!7RRojAqC9?AzV!G3cwAYs0g3MUs!cW_VO5 ziW}VEqmyS4cieA0m9Lc)>7er>VHXnv%*vZG#YXuS;8GFWaBiOV2CL?)dmXwyeq|E# z`G>UIRW8i-jaVE^ebG;%qrTFtB67t_W+#UQ*jbd{0eXm^;hvk>{DY|B7ifMMEHhk$>j|% zFLT8!{&6L!d~}4WW#N;grLA$jiB76kdQnO*Cvq_#m1vIPVBobPwcQjZh-xy8R{>8PTr(-Jyv6p0J+4ZerX9D@zH+H&kI&Ci#x(^h?Fb~$ne~k?(**?z zAHXZ%P{_5H?MERzxE~*HI||EgtqSgS(vnRg1s4*|#VU5!DsPQem~5T2Nw%8Mp=44a z#h z9py^L86d@(-}sfTF>ss=T=k(#?0rr;E+)6bvsK!&;%vrlKlDLjNiKxo}@Dza zYOECZoqAKG4?1-_LK*n#?mHG&l%^*7noc|_ezR48hS`F7$zr}Pe-kl9XVHGtl&DJ2 ztw@qavH38@f4sSdiXC`c3#?x``On81IBQPB3GuIt6Hm`kBqcFG&W;&z5p=kJzZmWb zH%cfTg5g@eVDxW#uALn%ZkO%ly?@ZnIK;K8+{ySCMdtg};LDNZBK~FI*LAD|$KG1! zpT1S9+}{dKUF!gzPT@GKrh=`;=Rb8NQU7^HKYCEw60 z2Xn)T<=vl)p8gzMy?&-6gsQne9b4bxApM9J1!M3(e+pBodjzO<+J&jOK#!^pUI?G) zha|W0=vunD3bl5Vh*eN9k3li)IkHj-@Dws+EJra#-bwCli1a9kA`z{Fihw@setG4T zZTwK9Qv}{wMT3Q?7Xd3;XODU%i-516L@#61Cmnq@d=5T9k3DiUq=a@8Ff+NNVAB-J z$ocW2+xf(ZM~O#1Q)5TUF#B1(__eW13~4trFBjg-<0Xo4RmlRu^a}~nQ|VZw=wtLk zp4+g<+MZu2SUFSABb9WGao6PRDKq%KT(EIQ2&7IEVfl~vwjlg0ebf1zNka=2AoxUB zQzmbTZYQZxAaYh1axn?d5@4C@zf`f5$!f7uSF?jmY=xbH;bG&5iia@lf#9k}EBUp; zSSqC=jn9F>vCZx&^U1Ol$lbvG??H{KJU2CK(x1Yz1^HG}j`U+LCivM3QH7~JrA~}V zDFR2RP{2LgbZp|iwJ7fa-l0Vm3FvDXGBrhQqN8r3WFp5tgV|rS99=c{vjXuRk%JHE zQ#?yCI@P+`PL4+wrpvan!aVJ(@f;-!WRA&hgnlhpG9K9yXt|ZP8@GNTIh&(S2bywn zkG4yt ztcbMqXnF$5+{GkrYp9sc^`|fp9YEK!)u~(2*LX5$M6PF(F2F_ET`y%bE1>z(UkNE<>wJyOUh3 z%LkdLhS2i%4lL{wNkP`UEennv}v@B(Kp$s;$%AnvGJ(8RNB zlWyOMvSp{LXQa9tbq!Y$*gh<e+O!#|#p#3-K@?`Y^ z<0Yvj1^ux=3`Nv6x$V0taGRxojgVfU-D{G@QdIXl);?~2nokOFU2TSvcV5@n|#EkZMUd#bY`%^ULC6_#B+z`q9ZxpYgtyq<*5I$!7eFH2)h9;wsw z<4b(HE}FHxl4pNehdu=rdwaqR?72ZQ{`1%4-4wEKVA^=8xfIiIlehV7ZBn%C z2ZvnJctGB5a$~}E^+_e8VUBOX6ubWqAp3u`{;%kLj8NU%>f^uj>ZL)u!V)RPX05yQ z3=IF|$&UrP=pQOPL?%DsD4>D37AB<89CB~U)e^9JQ&!6vN{b{t);XT$NdEEX0$4q| zX!MI1lIgwM+>f?z>TE=r3xZxBk1`F_l7UoFg&0xmtHPbvREOllEfrl+8CM1 zLsJnLVSP^2kR=7$i6-*`(gKgj0RZWRp6P$lNvP-hyvU z@2-0``#20|!+aE;=8N=af%;JGC3Qd4V_3e}fx{h6JPU%%x?hKg2?JA1Ct>CU+l~tG z81;<~bJchqGo5m;rhf5%S7|rBEoQjx@w%^=oEDpRHksFQN9px=C!Bj;+TB<7vJR9= zu(!gl>{RzU;v}h)sc9Hik7+Gb39fMIg_ts$;nBtnqpK-nD&Z;&e8CDm)zqg?_-W}O zOxh_ABalMJ2Ko8RC494D3-fM#eGLy%tKdtkcfR2#3JFb>gu<`eD34;dzl*4(1g;`! zCu(3+rBE}u6l0J~8MkL3UcIF!c9U)J+)98izVE-J6%*I}DDNWK~ zMhkJU30mir1*s3uc|B(~S2s>ZWaJnTDvj5oA^t7r;lAeyVd9v{P92RV zHocmO(@3G)nS#)TLv|TeyS~m38wD~>g*u9;OlT(?0zTt)rHmd($MbzEjLX~qjKb~# zgbXy`RU(l(77IkY8Q#|404+h0&7K{%C*D*z8yyVEM^XE6t_REE+tb5eWu%GTq+$fBx8YW(bK? z9Q>T{1f+(<~qNe!EvED{9w;->$Sck(JE)w+##k ztUqaqpC0`%+VZ{LVy@qXn>?JuO z$2-^RvU&5qF|;ld_`E}5^+LHd8*u(TXpy+|WE_KJ$%n$6HFLKl5SPWQ&UNUMu==|A zKui@#ZAJw)Fa>c7S4bYf2$s8#2Nf1XVm%t1% zFk9iEY4F!?7_=8V<#;=7OXJKr{7fC~a_kN-(2`%%bKK#7blNb&H5T}%LcECYdp)&e zWYXii+Dc4nr>RsmPah5%0XrRg&mH`!FCt!;SEbzm|?G?pi@q{Q1N zoi)61TuA85B77ixQ38s*cVg4oap%M5ew#0Dz-94nFtw_p&~Z!Ifb4PU#zrd_jJ1p6 zD_EAyjOJ%llCKLVHDrD!|D7VqV@`o<7malIvy)C_kakC|_Df1Ojgw5L=0{tzd8#H@ zzc|xV2USBp=b9DCPw`|M=m30#{LH<7Wf<29YTc65CNU$=IUy^9|E*%QK97o zl%L47O;g8O@Fsw6)x>j371SRFBY#0!t6#^Tk@Fw<)mLNZWmjYt5pY$ITHjof0*s)G z9sLlIm*QqNGomf4Z3FS*flcp0gX@Me%wl|F{4XYajs{ zG^ZGA6)&In|dNPYc+R9*k%(GUZ=FLvE?z%9>jK%8A>`txBBsj4(&A zTqTmedD&I`k-1P~KCs0z6GRVA5&Rjd)OVfFconm+x}%6RZev2p{IpuWz8rPFbYy5l$bbt76 z)L>Doi2Ob5dkxo)bjs2?oDy=do34~O8g_C+z`T>rq2rpgj31lPxS3eCM#;36(f+Qf zQu9N>p^CejN7y{=e%eNi`{iWd)qZu|^7gD2BHp)m0l9cvnF6D|?k+(N#GY$Vb}VND zbSkpmexHC5-U6JUlT`!D-SQLmY<)s~*XkRoD|q5P!X18?~d+Kyx4) z+qwk^hKo&s|K;D<|7;1pLAM5~Zci4~8D?jl9zf7>{sH>F?M7gp&S@7@!A)aPjz(d? z{U(3kQD0CB`Q26+I1zhs=)bjkUJDJ+EvhI|u8{dr-_ba-C{2NY38cAy{DC|C)(*b- z8l08N{ek%4(f9-Z$Gft+wI3pD8xQ}rUy-iPgu%El1bR}N5Bn>u*HP+$+nU7TDf94@ zobQpEI?yTd^mdV3=EsXriU6hrlYdtsf7`{jKY&g-FMe&JNH0qc|6PB3`E$@J(Mrn= z_*-ZcZ#6VjGbVf_Uh^PE265&(3eDnXrJxp2$ak9+We>RDRyMq6d}9~6WfbfKGOlik zzq5JI%!P-c%8!2#*^e0wD`Uge){4CwReG47l{ zY8f>yIUpjPU#@cI1^nB68X`a>gqieNmbM#f1sCZ0v|V(d5}*>@|E%XQYjlX_d*J{1 ztC#Vx%->LX0DtSb!W*Iefe)& zz77r%NkUg`AcVUH4>1jPLUcdd!cHo7s~~_<@I9o(KTGFSO41U=ecqS+o|q=}d#T6J zVX8{O^O(a29F@8uH*72hMG5o#P%=&W&7jr|Mm@%4hpy!LmP;+sYJ0r2FF#UL(t{hbKI3la$3!Bz?i_N)g?O#wVuue&2F{<7(NC#Jo4fs zA$DMYUA966vDQluO3OqdZnYFCehWtz5(5H60#d2r!iWZ^$Ds_Vp&l?cWm$zIu>D6| zJW4$6ah2wH>FQJ~3LI49*-_suu)l%1=MGE9Lmu@Y_)6dg&An)?sR9%eCwZ!vy>mLa z5O^P%Bu-=S#-iH85EzYRTp#S}0xr!iYzFb(0Cb z-}^jnNfA+(THiGY*}h3$PKabsJ3pfjf7mlSo0G4a*5Ld903|`%z9yM^UF5N)s=p7? zg%$Ye;_o6X_1cp!o~T%|gop6L7V}hq)iLME3+IF2N{HARXn-YR>qT&&AGo0 zFxc~tAx6qLofDTeik2pW&lTT0(r7<#Er`s3s>y`I-}I;Nd~JIEOJ9^e|FO^2>+q_5_DrvN&zsYw z=UkM&ay;PR;1R+c{NeYn&z~><$H{4Nu};GnT(r1iR&Ust*00@=4*?8%W<R>#@rzWb)vz4xtY?=Ts(T2?KD*hm&-G zc*Onq$3LEa|M!1?Mj#>S4`SQ_I(B{gaawM!*oi|K%20+flwC&-ufP&~n+;O_V_Niq z#oqn_6_aST%KxDZC4z&<_5Fk9_agWU=MG?j#%r*c$ zz`v9v`QIqp0nXX-r7qI>wa9WZ$gxCT21maPg<@2PuD zD?}ASJMP?S0ERM@`IfC?1;|qo1_TDhAkggtY=H})KfSTQMes$32~l;bT2*5j@LhLf zx=JHWnOx{6v20kns9K$FxbY_$z&Y%&!_v?H{Lg2LaI*fuQqb{&iIApdGX)>D(#A*T zU#V9{2RvT8iqZ<40OI=f>#{{Tbh&&;M=}L<$^cW*F890t{nJyP@|27@XmL=9_yX+% zUV$F{nrp5}x88PZ+O%;~P3ma$y$c%Of|PS>>Anup0%X?fr$&ceGXyKVO5L}ZggWsp zm9@E4_1j3L?^8ESe?*YgenY=<|EivkfBfS!?nPZ`0~nU#QnWt@x-&k28QBWBE6F2>=Y{MHqlu z>ZYr{4Z)2XvaD$Thdun^>4>5YMk$Pnd~M-IlZOIXvY^O>ia1zh!j+~(_sG}yXup@g zf@oAKI%O1G6r7>3d~ond7?ffZ9EF&G03AhzWi}!F)*+;W)jl$|glloJ5q$T-!W@=uhsaPA1l0_uZZMwFb4-dzn<5{NWc*TG$0o$@2IUy4r)_TfAhOuMX@um~m-9hRX`d;Ux zV(V{&3{#ruW1!b;7V3EYb~^d~NV74hb|Kw7d#$(;qTDuVgPi^iOuhgGHV9^0x-7xA ziP7fn#OO`#{SMqX-w+|74r|WtX5!0@`1*U+rVst)ap_I}^p5-+kT8RT?|xGT2Tv}3 zr&(;0dCBB6A-~?t`eDuLyV3{V^q%yV55F^?LOb&dr>A}Iw_kew``(t$`qCNccYpcU z(vFamPCBV(?#&e7Q+OXDyZ|F6H(&9JSIpRtTvvF>B^}-|6y9?HEvFMrfAEfiP%aM4 zOa?9zj+fM!_iSXk_-wjtSDsY@qO9crp~UA|0OOrgkdu{@G)PO4)k!+kS3d&N)we5_ zgY-m<`LhG?Dxs?h+&X8Hg<$iX7)46 zO!A+qp2#4kF%sK+rg=>zDstnY%gKcIM^Bwj_RQiw3Sz^dw4csA-AvM*O}Lo|3jg~Q z@A(Mz#-!g=Y0k8rrV2Oc9!mOE)%D4Ig3Y!~_uX&bDbqu)EtFmlsLTMaSh*r?yln^& z4rON21qYEqpkEgp>}|O#jb8b81Jjh1cf*Z079iglc`oi(jpL~V7M$~HDkdC00)MRl z#h63e0=}6Kz~W>e-(&(PiA?AB@5-yL4r|ipG@tU-qOJ51`0%1`ldUJRB&owp0YcfF zJ5tK6YS>BkCg&2ie)rpZT}47gYws~&8zSh|2F?1j+4gL+%qWizC|dt$B{WHb{U(vt zF1!V*_R|cvXG0mvE+Lt;)8A6*#nB0tV;R4An<@eZZ#Y)Dx2FMRFsGE0u*JJZACeB9jC8Bp0mWooaj+U9hI>Lu?2P6e836V8^gdm zI9_2Of_Pd} z)Sa3-da$JnF1R2wMCo5J7KvTz<;ApUONlbxqaDS1vgy#u+^0aUdhCc_X!$NaPogPR z`$Jtd-_^^D*JM0n!Ds=5j?@^J7Bp!Z%0)S;H()mL*pqIl-124J`-&AgZ|X;VuKU3c z%KW-^T{TlFVyQ_=DozHmK?Ro>NTq4MhJ6#wXL5}&G^haB%9Q{XOIr4HDI5g`t>*Xj z*I$>@x`)A*Dln-(UN>x{a%RTfisc2GyRinsx_<8v%_=han6OL)CwK$}>}0~CFk{e1 zfT9MRDv*eKD31uQf495tx-(y=ZA6L%f^6ex0uuBcG+z*te$j+#IdRH~a{KLf)BugB zwCybzWtpfSc{{oI?SdtnGuj>&bkW>pckzr=;%Kin4*1yild%ZvfnAxxjAKQ!ua3uP zbGQpY#=N_I9k1Ko_M0fb6Ig(ig&FXev6>gIA`F0_j5il1Pd|vh6PJEqe9KZJF6r=F z_MqjCrY~J!q>9_lmdk|!6QCni+sW`HJc~PvfK+Q2Z5G{C#du`=khbGjy12HgZIdoN zxjc1^<)d>6NJ=^sClT|qrR{VJt^U-3W3@MdTplhc7u6l`20urV+atLN=>?!LKzGfpBv9yUeFXXg z)Ybz2YJ;QoNy!v8Ozl&G{nTt3w)gyC7Z~hyU`qkLa@_VO0*KYs_KDBNFJr$B-5*fS zF5KAwg}q5)%(eX5ul?F?#UflVvM?RiucH1%ODf z08^e_Fhsxv;ocw+fEUx}KmYleLCBg4>u)B2-edw09Rb1!!c8WGk8u!yMc9axWl(uA zVH)Gi79gB1KKawW2mm0!2<7SectoaRZu0!bC!<1IfG%7UcricHf9zu)n|}G1e|buf zR$r4b{%q0^oZQji)=Y(Qc-(Qv65gd&9GkzzVx6WyeQ~&z0Pfp3K!9N!7g+I9N`t*fUzTCRUbzI?>(dK) z-#>nE()?q%j}~R!7LX9866GA^KMdtPte|eX)JIC zy5Ir%!m6U)U7?JWbr9h2&maCsnoefpKW!*OQ3ip7fPM?EY6E|Rz`=!QK4(fFglB&9 ztO=$!rxWMKbfEbS#XWsXJ*-d;P6iN8bzm=omot^frn_PqP?2?VI&+_MB_7nLY*-4L zOwK8QRalLz7aS;pKAKGktH2^~y%8nKG9e3Qiq1HRsU`|T4 ze)ZBzFU{}YumD*n(JKn#aE;%Ycrdb{))8yj?ZFRza0a5#bF#Mbo4DXB=puQ42SkKP zxT4bQiw_V-f`$51%8Blhzr?2uvj4<#qHbNiF~A~wUG|_HQdPIhHSzWo%1>8Bl(SB_7Vr zI?9n=OYbib8i`$zbQCcNUAvLCc!)TI{t@|&Y1wao``hU`&v{PXJ5dfT=mFr`x3sx& z5cT}g3Z;FZu0Mmf6(J_#N0vvx&%Is5RHC~+Nj17i69;h2N^+tPb&n=+i2(KjNNWZb z)3#u5;(U_VvPshe4}3_mnf+PLoP<@BGk8(*BWy@1NMzgzYEWREWFkx$eYH^l((_cS z=rh-)@2Z@Re)2#zHx=P!H9D9?!UX`O?&3p{2a#+@8!(y`b)t~>BOdXHj1Hq95Tv0j zf@Kk8qEQL^E5Gt9>4Xzb$ZRA48S-G@@Vy)eckao#6-)4iYCrj1y_XkxdxE#)z)Ru_0I+!A8^co(qB!!9=f1DMqyI?BXrWSBJ8g z4wYHS;;G3^`wy}IEW1FJKsv%At65yZMdQ#n{*H@Tyr(a~%sMQ_7T={Q%T6^v7goY7 z&*0jU`o16@<>4Vl+ESkdYMmbfZzXuQIL|E?B<&;Dr!%U*0@Re7Jp4}H%o7g|*#EYn z3nOKt&2^?L>(K|23!#gt3n=MCXS~b{4=`xHm-^7Bjun*PT-WcRAUf@)ufzi%@#wU> zNI1-wny>(&2;(~&7K1Y2n~pji2Hq)EHn8Py=88-jidlH0!HgA)%kcKr~> zvYTTUMvN@S@f2{>)K9&bN*T823D3B>X~4!BP~8C=pWV&O=&wBG?Zw>RGo_&GSi9z) z^p9`+yY#o8Ixby#;pOQIA3I^E0`Qm{{oq^Qm;T~k-j&Wh{afkO&wMGp=9RBZJ3<7w zSa%~hrGh_#St|A;!w>L^U{~Tbl`n}MS;ty(%_jy8r}Zr`l|#0d%+7{kaks}9@N=GKVC!V z6E}R0p>7z)w9MGaB({m~)yAhS5qwDiw2>EK_z=eTXzti^RKeff&N63-xxyz^zOg6+iUP||IZNwi3;d-gevVX(&UPPPQ8yyzBBw(?0o>FK^R0lm$DJxIZb@w!;r) zu1{np#`(tF@A?2_&D$2=g_aNLuC0Rfn|9KAh-Ibhk7)6tNyB6_dquga2kYl_{z?xI z=3Z&D9R(1UQX_y_qE(spHnlneTGRhh1$Z@wOZ)xCU}@UkC$I?Xc)QU7>!A!KN`6mYwYbjKB{%L*DPd{N*p# z01!;VY-!@w+cNeM8Dm@r@Ixnxejn=}u()6zYNZbsRp=pkZjL&-3vvQeAJ$pl@De;=I^sR55 zn?C&E59j?Y`9*?H)ZrtMypz)?z&?P)9IjuoEn~RsMyAqd*LXq2-x9M>H!RCgz)##q zEC{hp1Wow}zG^K~ojZC1esj z)lMzPXmv-j6~JU9a)@u07(i)47VubWp9tkq=WbgPMjw=p9q5LMk)K`?*XNKse~ZifYdg3UsK?cv8f<}rCteDj;%EZJg! zvQ?)FWH>q4$WjiJhWQjh0jvnd(N4Z#+RlO@Fcc9dM!(+xoI2NJv3Ssf9@GK`mBOD~v_(9PP`1t1I!}Ra7T)a7uL7_26X@v? zY2%vNB$c3N?x)eW1CAI zmxUNXVoyBSw}kZp;v%erSmMw&jBWava!^;=pRmkP)7tfA;weZ^JGIzv15Ql(O5bm7 zm14~JyVnBc#LsGmUq1k7t>X04_JM@Gve^9zxlb%tzny1Op7`6tja;qPhZHwMgLr|R z8rAu1ANV;F0leTd8PKsS$&(KIt#r(zUz$F1=IdM6m#E(a*n)Jm_oR}Vc^kdVpFML*d;R{}v=36wSh;$Y8fB;<( zNyR>=`vwVV2toil2nx(rrUD-Z31wwnXIu(;Fg_#0iQhXDAf!D2N4n*tjUz)6kY+j) zpMrygV{?a)cI+>HlUBE1Wpyclx`eSj=bn3ReV=j-0()T&#jg@D=@v z3t|b3(Vo;JhWYfTKb_lw4||cx*pjx{%U}NTngtl+(?&2(NgJkMOh@*s<92(%^~r=W zBQPo3QK6h)`N~&v`N*Gkn5>=xsM|JZ?1FS@hP)*rUJ7@GjafAlL3T> z1wg6GT8lsyETAi{y1L$za2=)blM3ZB`=@6XpjMZCxFdve2o6&IZ+!FYmSP?z(laZ- zjc{L^M%a2c=VYJ*;1y=mbj%)wcV1YD_Sd$69z(hJ zNq^5rr7gcufP)KH)z{_<97MJS9AqpnsrGQU7W>ZWT_a8uPd$X?;uiJ-@~C356-3# zueK%ALKoA31XDLaOXd?CoO#+!L&W2@z3!plGSL?3*PoDtAV4?ljxnb?l%dR(lsd(+ z%z$$)$Xtv1Spho%!lK! zxdP3j&_V%$cp;&Yvfa~78*5;L4L6j_qmMp1|IP!c7*Mc~*u+za$oA5lVze8Z74?f3 z6+qYKmT67Wp%B_?)#PCbwv={i1uO(WN)T&vp2RY9{oK#}T%FbxS6q>m2r}0QPhB_hsWu!3sV zhtne?)u|O(g_M&#WjIC~%;drN;GshusH1&Rnx-w8y_>6i8Gno)efW?*W6ZL;U^<6o z0n2#R0j~?3481N$h&XO3gU(lJ3)#qqjexF;vgH@#B#b9k$lE^E->Ih`;uzG%GGPV2 z1&R49b(`OglU-dV?sXbwUmNUe{(5!72B4KnO6?6!^}R|v`W)!B;>hxAo8{bA>4gnO zFGUt)RhR16V-0A=jfpW@8>U{GKn_e15KXBKjken@vIuw7&Igvui(d4iv@6S_5BlZw zt51DbI_a#xOy_;?vuU(CAhC4W9_jV(duzJ#g3HqhA2~klweMc(PyeO_5O)ShxMbPV z^v2`fmcH@%)6!|5`*J$>^lzv4efa(90S6qA=3Anv!3du54kh3rpukHU?^OT|yaNo9 zXdaP`#I^#208mk;0}xoF@tqh!HR{8LLGX>glz(UW))SoRmJ{$KnjIzFbUqhlc}3ub z`oiP`pj8HeO%WU%#QdYLX-~_;-!2eDJ)(4fZ;t>_&Xhr?>D%dq^r%;qXcpJL`OV+_ z%?V*(Q2y=T{%!uvxSwn+^8A)f+l~MJ_rE{q!3O}bI|k_^eL*tz=Ncd+Uw}A-anh{= zrIbIKEbg*lZRZbt=tDI)I~#xy!Sp8p9t^*4PND#QcxB433q+w1 zj^JR|M4+ZHRg43~&BRpQT+72A+O!Gf>4I_qaElAj5GDBL9l+>;p)?swvE8O;Phe~k z4<^qazVvsq!hR;q9)xy(b_IeW-^l>wjsqlyvhxM@-`Hc{oqC(sqy?9}xd8CiOdn^S z!NF~nc))WeEWGH~Z*hk;7eTU;0ew%J3t19;rgmAuQCN5Q04RQ=SV!kuD1$y8r>iUA zCGCT+)W<#UF_VISaor-$s+B9!juL^tyu+*a$6Y>Q`*3L;bYf~r@?38M1u^a#2gH<= zIFEbB$^2X@G(QIN=(1-VL!8HIUfEaZAm@DO3^|Jy`IN<*G&+qJuE4Nb7ueL71&|``% zsJG-duBWzEBo(m~$rF@O6PT78_8>ZB!roW_LU$JBW1sQd|MkBz4uN!a?0moV)?3os z-~RS==9%Bj^^T0c1(lvykSoShf9tcjwj>8`5P-gjGYBl^_76lL31ZQNsvli+se_Lo zp%(2VhGF4x^X5%iS3!EThbBeI^YqidUg*cy=6G0YRJYCj=#p-2p+~3Rz=SLh_ho+4 z-r{7Cz-$}aKsQ`kZPX$jeJ=2f9-K7wc_=7oo9fOCeSY*`NE(}ho6 zepDu5*5PAb6qBBdDg{3cD6@*cWXJ&g+Sk5T1Bieq07wk>E)a-llh?sgvJ!hW*yksr z#a7l2lvc{MU{Nu^H;>oplyui#&DNVhXU$6EasZT_*h~^CO=CQ`*uY1?AS0<2l@*DI za|D`bn`4eSCQ~pky6B=J-Z*A*UZ!8h_~%K{f%-twk8^%~FQ@-uHfmVr0+Eaumd}Ujz$eNc#kPvzVWj(Z7mR4W)c%eAX}YnDhL2?%#Ab};&w2Gq^O>|ilw3jD$lhcUX=j-4 z>vKPj=Ea>hBuzmvsk@O8=-6?x(T?=}o`sTXAxyQ(#HnSpAEd5p4>_~(ihTQ3TiP!! zjEr6TKeFh!9>L+*cZ6YVuux^dy`rSwQ2=(g+;VHKFAJJLHt|hUkWSmnfP5KX9qH8| zB;x!xjrc%y{GhON5fcz}T=6%iA?WBDBSTQoTvn`gEY0zb)Tc27BBLo;+dB4@cBy?K z$c)LYjbq(iU1Y^ICm_RoNNun@O@P1R_Ncw7iQA@>!W_;?^s2p5*CStl05rL6Qkr^6 z)lQ>75!?(Lo3eaWJH)L{cbC%Fak}PETe(#LA_a>rkNnm2 zzkmKO)A47#E?xNj6N^W0Ik)!QXV3JKH~ewB_^b=k7yte9IqaRctxg|&%X`xwzV#L9 z1%LAE>2n|cRA+3?pX_trebX!6@tSn{313ZToOEiU|8KkbmUQ5X`==ct0D*|$fuc!7 z1h7H{J}>J69)rY#N19CB)-rX?g#u(vC6uuXx=;?l5Y2WXlxb%Hgvg+BdOiAT zRHklelRKv@)N?97i5?#4nk??-B`^+{sQH?i z3d&qkp3KZ>G%woqqZLh6PUSxgP)>7mfQ>sEAe@QpC{Aw*KCRvir-HBR`z_`<3Hhya4_(fHwt5xT}JLNCco}1`sx3y4GW0 z2>$!0j?V`e0OaS&#PG_hjLbQ~LFP3+5MdPpR00aZS_XhSq5$BK`2-26JN{3V@63Sh zhbd~-n#ULG$YcPa>z3==jsyr(b6m9gSyBpHaA05qYGSn4+2?scv}KW|iZk6lcRpEz zGs&ZUIqCqKOVI~mc}Y{E)9vW1JH0_=N<$gSZX?;EUu8i}Qbcr%#a}IxMQ}<`D&rGa z{*y9Sr1$RP`uacMoX(_=#%*_ zA%eA(?^=T6-CiP#7TL7yrmhtKhTLa=ba#Q{@P3X}#ozj^-^!*%qwYSs#KJ7TQ--=M*s#hX=&?N(UzmFHLa`LSK`{ID?8gf zlXbRiu768{TDfwxW`t_xP(T=LhQC)`bydCxK@P0OF=4-F5?uid-gMJVHK|35g1YMB zI=De(oko%oanP%4X;W}e%t!X8K(s)!^;B=JJ2Um=j1%dL?cVFxtZr}E1dk>_Z# zFgn(BKsv$bgqOVJCAkrAz8MPec+IxMwk-EG$wrgfhmA` z`yIFEj-qcebFwniDyT~Ov}Fc2P;yl9qA5_!t^yirrcofr_AftUx%^HL&A~Fm1&rbZ zi~wENG-w3%;zW*r9S0Ev6g;vF-Mr#LV3_#33l1u#U5}Zn?j@*P0=o6PVU-skArp@Y zg)>oZZ`e@wCF2EQ0t3P!J|twKNh^l^3NTH)+)uPMKxwsjWjn;x=>W=;Qe#e4e}n7R zt}Dk+MIU!zlC5XmWWchZQGVxf*K8}}4_nxjU%Srv#6gkXEw}tE1ED@h1W)Y`O@-1% zFF@xwrra+w6tA?zl63=e(@_(l@PCT{VwZ5=WW&wFxgmFxG#85oaw?x z0U*@R)F3mX(Y$E)o<6pw1N?a3LJ1GpxTDtNDA7b9iu$jfcKS{ODW@}sXq(W|Jx&Z&aJSj-HD}#gEBGZA0 ztP%6&kCQ_Y?15qJb^$i>1eC;#C#FsT39r5Gy6sw@%0nG(fDSz#y0OO>=?jovbnzuK zS_?Q#@VEjTBn?5-D6BEATUYD^R&s?0wy(9mxi{{GdjldxAZ#EGHz{;BL9x9f0K=&z zRHC)HZrpRk{eN4h&@K1idQ|6WhccAiM%0^1K*F*fZqL+1N@!4rE;bO|OQ~34!er=tN^^(8pnn@qLK~jZlGUa1WCOeS(>>p*F z2@ay07W6`A3c;f!z^u>T*~h;4#V^kLf=HeaoFwk|zIRo6>s#NNF2C&ZZ22j#U+8L6 zR&S!RA3Avy#`y5Jd&@c=8y(yiq{|+V;CE+(=+|3Csxlb!6*W_NC}i~F6zCk z#wO@biEvT3EH# zW@NVlNaDnd<|iXyCjd}R#*xxrVex-|Yrk$d8EbqnYuc+6;9~$bTgPh?oKALWx6z!Y zU}a>h8IF6z|KmUY%E~f{c``qUiNc2OC zHeOy+LF-9Vls_{9?z}6f>tK#7syHF|yS{4U=+}*WDG%k9sR$^E-$51{&~AO)5zi9| z3^ak1uEH#WJzelE0(=qt>5hj%%)fXt!*=MNBv4GFn|>E0?1EF>wv+Lfk@wMZ-0E;t z9|YR*wf$);L1h=uI*n@V`#>n$pd7v5s3kYQ^o$KCOk0~Bn@$`bGRU>4IZu95ddj2z zFn#{)zeraU;Gmo9J@?rwz3w0WJYD*oi_#Yg@NbZ;zh`awm%sj}^rjEKJ$KOO|84$2 z!VC_+zW@inbVmB>r@z?R^uOknuSz>xBIq&*q>>ReRov+qL0uqlknrL=AO6^OhR|kH zwMiE|(qhI;!ba(WsgW@l!9e=yxZ{pXGm)v7b8{_S6RcASz6HA=^H8Q1z&PH!=u^i= z_wV+IX}~(2iisH4m~CNAqL4*r+bJJtl|<{!QEck}P5{K3vd zB13R8(HHAXgz^y{1|e+ZEAP$u6~ z57K4rTxlJ%Eq_&Y?jFYNey^pyZJrPyr#U~{x}gkZUS#jR_eyJ4-&X6GM;7!-T~Nj^ zN{e%>cjzV|9gS3AVbm3?lk_+2tY7$rUr0}X`qOJGb?i6MGxC>v=ylzPvL{n_tkpB} zL_aK;wWVS?Y86iM4<890go%Fpz;4tr^MUvUV-P>>kd?Y5AOR)-us6Q(jad&7wQO6* z8+}@-(@oi`oI2R7KU<@Fu&o8Q(W~0dYvUEhWZ0;~w@g_N47gZeKcrz;-CJ2^ z!18L5@+$iAOA>d3FX_S#Gudh(N=l>Ii+j`SCMJlyS} zH>d1cffTcm7#2ZD_h5Y2RT%fQs8^D?QYP()8xTiek~G28B>Ql%hcOc^9o~NX9hs!0 zH9ZgbO2DC==udD1b@`A10U;sZX-RaH1`&N;xM*Rm8bXuJs02_DvimO)evP{6^+0_sqeba^6H^|B2FowN%p z>Ri^fTp&_4@aeown+p_r<9Hok^N5~eV6f2X)R#{0I=I9UC4-7EG7<$*q~ON7N3ngB z$n5OWu?2xlYciH$1o2EWE)X$~c&{swT^REe^wb`&CM!kmX;a#LWdRgo&V%c;BmD^Q zCM_4La(_$7q!WN8O#oj2Du8z9LIlbptIaw&-ZgbUP9uElVXH7Ho` z`q5>Kda5P%e>wlQ<^|F8q;T8DGHByC(s67#J^%@uhrOWht}VUR-+o2YuXusYnZI89 z1;1^qvzbc``)=Z?$1+00#B1_tO3(%hCvKKzFa6FIrkz*KkSuq1#jo)+*#@mCzhS$u zW&NkEYA`AIU9M|EB+v@7<*Fs_*ecQ-EsF%($13m?)9+rJJ0-ZI$*!U z)0vn2zjW56|CXZpo&xN@(R=~eIk zvjQCa?+M^wW)JQF--EM}K}@#E6#6pXknLgbX|{4Q6NS5@Ob3|GhF!L;OtkqhUpbWB zL)bI|sLlllIGM!pMLsjlYg@{6TD0>ImH)Q8jf{r^|K)CCC#frH5S zeoskT3qWwel_f~Hvzdc&&6&xXvT~LtN#{bM-@`7b)|I;}aE*f+4|>4-yti)5Ozzt< zP~KNbH5E3w$snQyEaOPGL+p+?svcYr}g*TRr{JAsVvQv zzz_QQ&H1mE;6^H4sCr!Wp^Gp>x_C);mBt?Opo0#o0Yml8=vtktm>X4Ri+d?_&0qkC zL4q^(b`&O@9TS(m6CMYSTj(Vih<;waj zt&nXgKtjxwO2X0tBYJ#()7}u}BnQ#Pw6Ay)I`AJA`#M~5iQ&*cgar!{ATIUJrEf@x zJF*1@e*xqG;xzTiJ<<`A&~+FfW(Ez>TUYhdiYIz}FdfRO-$X5PqQ@t1UC%gfAe(8` z&^E@uFs|sprbz2M=bTgfH$*ZvVk1)+oijf8!4J-voqFo2IdPqFx|!%#6=`UjV>KY6 zzb_T?qN##kk?BWgZr;41X6Bh@HbGj^QEM=gd>Nntkv2V1+Y*@BnmZbaCQmbXH@>w7 z&}1fR>d}GdWcT40??oU3!Tb~h1Z4o%X=DL5;&5`{2~T)JrWG?-M3wWxQ!OlAwjAZc z6r^So9dy!jnTQPBE&vCru6MI=y%EoV5sRvUPyIf+GXo? zUqB)wEdGtaUj%x(^^L5=?)}MLc)H+41fk>0n8Kt2F=t#as+_<|Lm~*AnDl4CUuMZFl||=G$m>sBF#H9ax11G z{YBph4B8$MjE@T}3(|!bUQo{$!)wme(m&E zZtS8Ij@Z|j5zWn-A(<$G6*MxAFo^w$;|4bAC(~p6*Ywk(!{?oroulVa% zrx*UwuV;{Oyg#1iLiXN&-}LIgdt){!dR74t#?3px;ErYw@@$34*HzH-axOCq7LYIN z$RIPIisl88sr-~70DyylE`Y`9;_i+z9XK}|kYp-l;Z$rt`y1iCf>W9^k)7$caVXm_ zjIED<{Nwq%zB!p^vRao^ZV1?PZx4VNL2&Yz&UdJ?E#r$hFv!g4VZk5XRLry4uyb}M zgH1n!yzdGQQfCefa_w2qI5yV>(|bExD8qE1f^zE=z#mEi+*!<)aW$0xp;CZ=3%~uF zY3#@kr>zh9t$az4aP*Qlr?H?-pYfAjq5F&NeH`nzgY~u+0o0Y{Kj) zpj_58vStSB-d#nQ2c!eG`w+!PKlaHvUuUB!>gY$7X9VS?KdzhMdNtQF5WAb?{AvRX z_6COSbznsgeY7^`sUSH=d9&H^3N{EAYN4fon#J)R_oSb{SnY`JTnGg z_Oh2{v!d#o1&-8jUvkMM>0N*OuJqjtzgy!rQHRSkk2>el9P5IXxzA`NP7EObO(oDL zVWa^s7iPv^)G?0)U9-TNV5a)uXzn$FiRz^HSh`0BUN>&onDyuEgVcr7Png?WSF!!} zyU%^n>H>hH-8N@;QyXeGT6bpGayM5xKkh8r|MfiG6cCt9P-90r9LPb4}7rvNjj8?IQW+Yhl1>y4(5RvB00^G8>R_;2BfY~}m zr)~Z0vl*{2VY|vu=dDr|4*|>1@;iF9-@cE=Yyw zQ;kK1bm(N`0uBO*p`5UtBD>Sy3{(afbY&^dgEsZiwCEs!PUf7TRg^*CkYG||nMEKe z_CxmsRyY5crU0A_*Gn(GG&_T3{Ic-k6L4cuPEN~ljfELSsf&)jogzcd5BH)Y(6|_} zu-Q(HsnM-<-<4s;YDcM=DqbxBo)VG0De&bu^Rxhqo64qcTe%3w#hFFwj5E%t87Gb% z#~t@+6Mavz012FH8<{Zt)i$>IMmA_17ZKDPYc<>?XkJvP1dcg{+ufA^o$xmSKFZQMM6W=DIy1?j1e{e$#NkN?wjMF9$a^}P3{ z7aa4>^r^4EGHo57I7imqvo4J;nz@;{wS{8xAK&~B8O(d`@4XJF()A<$bnq!;_Gy#a6jG@^Dcw)G2-uI^Vzc-8XlKy}B%U?dPfUmwQd0x%; z_`*LHez2mM(Z;;!MD}3zDn#x_N6m;-Ztf}%qiN|ezHUY`H9LBjU?2tEf#e*CGl<*8pz{bdi% z*Tw=ci0n+CgSQtz-%J^I`xgpaSm*tYVmQuLG(`<42wRY~>hS6i z35OXr?xb>0&{qva3>#_fTKg0Cy1mrcOz8~-1_$qZySvGqD-+A8rJUBG+~x#s`Lo}b zMA<}2{g&0CR+_s`g@-bfX$4RYeXx4R@k$reB{ityXMYI0PO^wt1^QAzxW_!^F&PJt zz(BfuNB5|n65T5pf`kTin#89Z`mn=w8DtXP_t&hsE90?u_q^vl>B0*xY`J~2yjmiR?yLm+;`ONO74yit z+wp!~U-h(Q`VGc2Ql*QId~?OjTU6o%Ew==J(fNb zH`Tb7>CjH#W{eTa0|r3;68cC7tOsxqHsDs=WEJ&1`skz6WtUx+&7Z;+rF^7GJOt4C ziYu;2|MXA))WS^i7_4xDjO56S0^rE@#IL+EE3z_k2~>I$%9f=ys2~Vb`#T$H*kF8z zrd|&IUZ$L?z=!6kYBzRXKQJ?*Bg<%4)(+)~v$Yy@0#UL9c|zInH7Ho2u!0E(gO~%l zRXIkgYDAw zI#fT0q=zX>nrzv!(mpQcMi*AJRyU&hR@3&arY0#H`7n41%Y+dWVxa1OPR1d?nMw}( zBLam2HQhFhpkKF*BaJwI;|fmHe;OcUD9`4a&<+vEK6&(FRGeL&gv5tydDD4U=lTb~F}(q@zwMYDbwyK}eO zUvUAq4P%@L_!Y zF1-50w0w`bF*%l{sM?~1d!%O_{rV#Pm!==wbWZxeC;e)={jMLT*S+{F>EFNhia7-( zHy1ChAAQ$9r&qq~4LR>qKDq56A!bG2c--64H%>k+o%UZ}o}dW8wCJxEpB*iNN38j5 zDpct6mvvQkA-?KV{3Dz`i~wQ`OSu0jego%P&N=6tfo(&(ahOY)&O9sc4wIQ*?fT@4 zU=W)_%7AqHyerXPp@)ZtvU`f)GvjJ9a8UCv+biVJ9nbS6fKh@|m>K0gQ6|e|%n%jy zD2%7_XGJ;>sFe6qv3C|t0T4d$0rPj@gSwwtl>b+r`<(n9y<+8xwDGpG98p<66$p5r zIWh@qo9aK`lqHtmj* zA6)mtY`PhF*h3GRmbvH}G83P!J3}}qf*)6&5r-ana9%fU*YE5k^PWKw~o&C+QC}kC&{Zf|NiT{hsFnnNdkBMShOqy;{{-~eLS|_e5d||z4uUtvipfv z-n23(m=uXB?9Vuu`{+kMI_nKN=#k$G3*A3|vk$vMXU~2Q_7;bcM=OS3bkT&67Zw}Y zOWyF4pJe7C%r9Mok-to|_3QbRZmcL9WT>c5FU#7`Rt6RIrkWomKky2D4vJ+&T{0ln zJKph*bioA|l)7*}V;%-?%!Aez#`-Dy1cW?Q>RuPr`rlYr_0QQH=fcs{bt@K$W+iE@ z*D4!kH?Cw)lLH`gQj3-*H zEWx=FkcHWa=~1wn?8mPn7m1b8pVpoDTIti89rGk7TIqN0Wk#SL`!v*-}^rd<25Z z>tl6i+JO$t@iCS6MMsX5D0X~MMO3+f9$KAgK3$MQ<$f0=+B)8!Fkv?o4ZE~xFa|>O zL6$*~Ds38oMqOMX(6M}jULNA0MWnDsU{5eYpN-OMW7!={<{{8z-eY483xo9Nuxu){ zx^5q63bkQ6m7CL2d0yG~9bFL8q|RHag=BF#=u@g4RXV^u32YPwLMECVX?cp?p`;MgsW=u;P)FO@-FXy& z;mPJ!96ytl-D?r}b8JhgkK@32lv&fRns=2g7)`e7tk?GEH~q?7;d`8yyQ&D4O}yF( zBd*tL*nV}~ykPe$0I_=tGS#42onEikoD}S}!@5BjT&``A!JuF3e#MUL_q!!Y9hk4# z2 zdd3r8nfBlNVd*EgT$;hW^_%W&9mE+VzLYIrx?ftgZ2xrN{hpAHJots_KC2E)dn{U& zK6v8u)6Z64L&)^Zqh6POddK(D^*^2a&Hi1t-iOE z{`$4)8=o)0!Oub|;NbJ0|NQ)&4FuOQ`+<-a zW-Es#DF+MS+(SCdNuF!8bCl_PF9OEWUtKhvQ*>l)w6$ZWW81cEbUJoAwrv|7r(;&q zVaK-9v2ELC1%G|#Kj*G)_82ur)w|!d=9=r7wMn*U83iKbD<|V^OZemhoCra4ks|NA zZ#@3dtLe0cA7@&wU&&hyfR79K=g9i=QFRLk==c^b+>ThWJMziDL3WvOHC}@Z&3Hi( z))4?YFp=OohEk(>eU%Lfu?c}iG^x%}dDs3iwQk?r;woPiya4S2H{yd~x#awH`iyT> z$hbbGpb=y*YjSYlY|8Ecg87V8?j4UgAe+xNTeud&fX4Jos7-lc-e4rq?~Eh)3GERC zQ6f&xR9n83Oq*8rP{4yimv%xutpbihB|2G#hQisx2VwQMbFFUV*?g#ATdEZ^94)+C zJut|=GBe-h*?n4f^~c0;<#Agwbt<_>8S`T-2uPRvJ1(iXRZI8lZCtkF z+2yh$%l8Ms2|<6ZQ1Yl)axBj&xaQ|obtat&1&CaE(nP-g^acKnHlV6=^%w!YpPR>< zmOn{rG^o7)Y?D`7`Jm~O7sdH8ozuzKaAfk?-RkIdnqt7zCpeUm8qwBoy!fsdN18Vz z{%2@d5`&mAulyscl!)$MSzSJ(BzH#77GIS~S|ptLS_%;M-?1*gUolNk_-~pyX@m)@ zOoEJYY4~|)82!8wWApJMTVt=tX$rF#g7cl==QzW0MEIx zFERz|PY+B9uxYS;qrRVwR?bhst@-42ss3zR^sqqkQ9%%NzOq|};OL3*^GYMA4&DDB zJS*Oq;hJHPgR^;g&J&tdN~IL;gl7nJFMZsR!X=~|Pf%o5GSO1h55HF;1&G>nj>%WWf;{!P7!G%*D+ z$a2-lgF!5rvR&`Mv+F<+|DKopcb@|al1wGZ8=-72=Y#cIt^y2h>B~Qg9Tgr*C{&Wf3T;$`S#xj554k6K;qd{sDn%SBH zuZXcIMPvBy+@-#gK5qw7#|yd5tBLPFNa#(r84!l*$iXMx|C|`yWc}xG!xC3$n{gj^ zrT-XY4qM+S%GtQyYNPYBa>|X%oE?}?^~i3cY0X-veAwzw#vbZlXG)H}5k`9!g~$ks zzn_|ys^tD|)9VGAwD~ToOeu%_Dy-P1uNbk`_n0rM;R!OTt6vC&mba`W$>Dj8>G4tE z2j;?=y{HQNW^Za-^J5#^rn5(Ka9`5B>q3y@YQd zc+IjDm|j8}h`b%qQB@;cZv?YEM5dxgI6+QOF|iuxP0IMeB>=~94Znmbs`|^S-Q2Q?@>Xk#x^*VsJzgPg~Fj{FEf88|9H>XW8L|v zs-$Tozer-n`m1VeF_f}JzWqnWLl#V&QkS1D*btx!4FkD*Zz1!NvGstvd-nzK#x{vJ z@Q23f;ZiwUZ-Am)x=NMUQ;Kp4-#a?PJY|@b z@JayO`LtdJb{GvvuG(T`wI1E&O@@MVsCb$)Mg~9IYwrPKdC7FQJABJN_WWYu9 z@id%Q$I5xL-}Xed8%ck+B?B3FM;jphOR?|BSzA0+N@zr0N898>Voc~y>SsN_{@b4A z|9sg$fA+nW*%a`KO(W|{bMG89>>W$0OtyD>NureWeT3jf)sH=7)g=&eu%m(Uqd9lr zK&!YYG-x)`ISG#eBH}RGjcDUZ>ZzyqKuc)7Mz}KDF3?Is)}*@) zRS6V{nmr%tIb>L5feeYHJlMNf4(-`|RRzhYM#_)eF4EF`lM4(#Mb)VP+_wH_YG0Ws zqVw0=uT!39!XL$kZ^iG%N-^J6<{)jsOhswx%OL(x4M`&A6=wA=SY{D5fpC^StkN`T zZ<3~sAyG1Fh4j~Wq-)lfT0~SQv>zI2O7UuR`_^_>o?^VX(feHy^yfi!>;at4^vaeJ z_>n`Kp4%Tb5r9`o(xD!-nUfH<4Hl)8w0ORCYBU*Y)(Y|rReaPeO# zBu>T2zlt%#p*>&P+-7^13OBh0?Fv^aWaDo)C;n=&d#!&figNun-*ogn*P2JEp|y9& zMGBQQB(tt_(^ie{lY(O5q06Ez*Kw+fms-yVx=szGURIjQHB{vE2i5)RvLHEHc^9U= zTvP3Cfz8vcnY54TY*`5D9P4e<2SFu_cinhxa zcj~#w=Afnww_qOkug`31h-!WvUa!OpC}A_>QG(h34e*Zi!PO?Tkd<`!^UJb(*)63eci=?*=>;nTJ3?qr9aa~V9sjn+2#a}XfC zF4tJ)64Yr&JY0^8BwhXVb5k(u(Q5{!0lrXv7lN-Kn*OCRp+lbNq&^Oq1Y}9?Fv>b(H2zH)+Lg=0BZ-T^> z4{dgaeWFKg7E;i+d&WVRXe+}cz?I>W36xAk*MGDn##!JmxABc7CcW}hhP0SlJ({%3 zkK4nrLCE*A455zVbGb-4;(rt9gzum*C3LQct(?t0FfsBs*0F=R+^oRSwTA{!gm^>Y z1$B-4F0?YAC;-!WH?Q!w#>f!tV4pfNfbpf<3CFzD;OI+eIJ7aN>^bZM86eYNZN=_u zrNxporYCUU1dzWYG1lPiDCI?-EGR4nA<;<-U0xSrN}vV2IzX8=7){%OJwa!jnj>`7 z=U8x;g*weKhaxeaEA5NbaC?SExDj*Q{P!Gck*62;!Z@I z+fEzMY{ys?6X$4OnXwClFocg`ZVJBE_Y`^>Nz9jRQhD>ut>@&2@#*ffy){_D!eUGe zoVqc8^7H+FZdLCd@7-{ml6fT&p=%Ubk&ZI@Euq^aSZ>j`Fd0WAW7sY**r-t;2jEqn z|9*nQRj`j%xHZc6h$Bx$mp^6U&*_gXkYGK?vb19qv!NF3bmr9&mN(ZSl8&q!GA3_br)0N5Z4 z@mrnR0(+G8f#vm2gR-TP_W8?t&xgS2MZ@%zkDXKg+|NC2{>KG1j-83_pf2lCMZK)Y zceqRCJ&ZUMai*&{li|hrbn>h=HEvi7&A`qrD+|f?(k$*Gl%q(S<=~ZwcxtVNd=tE)<0mzF6?H1;zJ+j$XAc-;X}2; z`FR;Sjb9_%GI2RWilX(&;J}Jy!u^xRC=Zb>3I0oms;`JC*--rrd(FLb`zf`htzPCF zYJNIDU|-I)PPcw_QC7p9HO+o@{2BKJ!stzXp_CZ%23F)n%)rjc4WTLxdfbEP-^E?W zzG$7&{zt3qzx3P@{8-b`_ly-9fS*eMT~qE~K)&_p=lbtp`*Hl$I$C>eJ+K%?oa9Ul zq6GmC#F$RAQYIvi;ue@4xzbXIT4v#=n!xWd-36Cc64))R8PYT3J`A_&KRsuU)WPIK zt+M%qGXyn$i_JjnY|X9z9#-q2<%vlUEi1)+h_TA4{x!{PL|_=j6lyE+kq`ICdEq7+ zWDM24-vmc4;K6w~K2T*i=^5;@KHxIKxf>QPdVfXga+YY5c@bn$5C`&}d`il)6mLO^ z%IDYkooEph*`&`4k>O68Sy)w(%&$GIJ;Lf;YwYAcHhgZP{zc5tG{25?!ph0SFEI-w zZ^b`ap5QUMr->x?uxiHmYG3Yh=-mSw5t((5tb=}eBz@d3WV{VB;hRs zL+`$R49wno=xaIdetRDl<~d!fb*a%feGCdU{Jefx-0#sAi7ec4y;&3(ZM4$*BTh2^ z+YYXT!<8>(HO3ynXzuZO=%-SLjFSg@(xx>)YL^ex~HNO_And_lM!zoZH-YnnPsa>u8AJtf*oX@8ujsW4j~(<8lVTTal~V zf+44N31>+@<)Bjr+^Qfa{cnTe&2UQ{`wv*z7Hf3PZ(w?-%jHnFP%vnWfqsvT^DT&8 z>+0ZvX?5UJ_=AELcZx!nvBB5j=0r+k!U$T>qx?o@nC_zrlMe8MvQc!3Xb0sAh zsjm<;pf>jl)7b&BOr(~5n!sFv3qZYozlpCs@g6=Xm=3}Ey}c48#W|7BWbCEMBzHY^ zH8i?X_FzcAq;oSg?-s0FV0kO{rn+?i^}+V;B*KcsjOqErUT9SB`$G5xqqq$RxSC~) zK93e}nf|sF2MThU!~@V;i&s|QKVa$%6SHAED`YsDtmlnnxhI ze*my0P(bKJ3}x)Fw+j>d0Pa2(gt{lzyYOwwDiy~0;lg@4CG@=U@Jq>$yhq6sUNMIN z_w?D8U3+;>7e{L)@^RYsQAXhJ3d%}9e%faVBFq2FJkXdm><_gmynK?r=vl4k3!)4;SngSw zqp+qnT5sFdbTw0>NHLN%$T7G!fPDCr`=+CNR8(Z8Ck$)-0XqgmJ<+^+h1--ZWJa3t zBut{^fy?L?J>)(4(_UNmdFjtp8Q;1~LgF1T=hQ#?Y3Ttkm9q9%^$_FiDcY;+aV27LF(b;+NcpnG zga?5N0)GuE{G*w0N7XgUx0Ho?Q%{&1DscVCZOfC+AZS6j3UW5?CXoy4I+^ zwZ8SGwg#MYatfi>wDbo54cN`a4O*l7%!tfg5&Tq?VDn`gLg`igGfBx8()!+5zp;nt zZG?&;{6R9J#7z^+OM(fWf!z)ze%N{VM?;*el#d`Sjji3HU>AC29emXfV)`-MLRWxi z4b4-f>t%g{Ovr_KGx83SrPZ@f)xu|HxHjl1Qo+BZfvq>});eAia^@E&dez*~e zG_edW1hB=Id|$o!kH=X(!1vU44LQyg;oUFsTaB}E9MJWcdzuaq;5ov?VL2&1jIlgc zvZvoQj@)>D93N9q_$b->u_k5BjnMVY3f=IQW10VPf40{EgBil+Rj!7u02LB8A>yyE zC6EUmtA>qcW7W+~v;&4Q_Qs&6!Ty{_)MjJjAF@KvAeY@$*!jrxme$(C(8K-I@8_Pc zkJtNhoB-impHJsZvDPk#&>L@&=k(Lo=}(u>k2%B7Ww%S@ZADAN&s*#Lr)Zqc*|S4e zU-Q>;tkcxM=b?wt@g9KUd*HCr&3Divz!I&_`zBD>r65JF`xqW%U!MeG(BaG1%QUu{ zi6x?u5>$@avm0JG!Z|iguLXn^-WH^26ifFy`6j4#+fW%Ay~|r#B%bpi|OTc%_Aex&jr5Ag7;gdcKKnio8G4cCnG>Y(R@VBxL#hZ61a3dc1Sg{YMc z#utDfTD%AY03_B)aeuqEq}}G3?AIe1^WWbGupW`jZ)%>Vu>ntMQ#Lr5Oc@PZ0@x-U zeAG6x<)T__4x_)Xn^E7@j+tFtfeC~1z2G8|9-Mj9wlgdBQG8E=@lN)Y1@FUN*)z9i zXc;)1Je@3_C*1SrG z0E~aUd_J$z1onJPZSK!JDtnNeCSdxEVMUg-{iYGJQ}OBjcp9y4y3+HvtTK&DJA5D- zHgp@hAk#K9PPkP};2Rcs9}t;+peCfwIdc%!Gxg5WTeO3Vr zC(JWTs9_O%lJ_&iMknqB%Qf)e(s)Sn@m~$rgC#AQc&PLG?c*k@cta!BgTGP1%2fw9 z6d$Yn@k34+;(Ycu#VIqB=;`D6@}h+)p4Neq-)d4bgSXsBAAP|}rZz6)Jcg2BUGpcc zkvze#^(2Ga(}eQYbYv=C4FNH|)-vgM4hlHYO8gOB28_bl34l>J=h6t~+%0)j8>aNC zErh{0S*s$CMo}5alE(%i?Y^Oix{R~>_1dDtnAB`@@EKV{tVFk79gDtrI0J7Xj*DhH zW+o&k{Ik9m>emd-<_%%)>eidZ}0kIl@UQ&uZnFvW+H z#70|e>|fNB95*`PX<06+ zf9lIl_Tz0;lXx7rQZA(s>yx=LCZJ{c*~Nv6{wm4(^S1{{&bNZnqwQLly-u|Gzuxa% z1xVKhxeamPh_*DjBV4mm=0hPC#v}WzpkL;h*Jw-Z8slQ>4o{g67#p9g8^ejlw=cI} zoKB)q5*_$&gVBbu4wEN;(HUj_dxIcCwn#LOcX@;5Xt(C!`zmR8I64$8UO3NDti%gr zGmxXJKl14Udtao}ni}`5(l+;rvz`s`M`$n|;;;uinCQGg?#L<$Y4QN=4{DfYkRKie zoW*Hf?MN!@Y%psY_b>F&;P8OXSvWe)ZUDC)kTfameKtNHpY}iIKHtlZxr)y#N0=ODvACE7|&5{QO`x{7P$=u?2b{ z^jM|>or$04!UhO zV{J>%%G24W&wSxyezEdRyPA>(_dieT%QPE+pB_-Wy#B)U@TwIT$!|!pHo>^f%Z|a3 zRo8UIn^GVXO9Ac4uZGt_p|s+r=gKNJc(-u$vepPiG4Ww9^CT3aiqB0bw4}21*!(oo z1fNOSqD+u!pN?X?E(FINaQMiM>fLbd^){a{$*;5&AW92L3=VH}c>Y@T#b8~JzY}a( zy4u`4wlYE&hl4-05*5eDq7fxU!A<&cfof>?n}8-b@?eYR7s>E#S6v_P5k8?(T^sii z+rMQf6V^~9+*(mcxEUl_D@h1sD^UmqEB-mY(vIgX_vL3Te+|RMEq~5jiuRiG^tX#m z+Zv6Eqs{ZcbKrS0;Jruy^v_N1$;XvG)$7GREZ z?vjR*QJbP0xM&9>y#q(WPu?JEFH`re4Pxs)Gn<_KB~*n54g-;jl>Dl%C(iT+XP_tH zjSMKNyN}-}vPHpigSD@d)cyJKyuH1(pTjRsUOmXI8FQPN@P}NQGOQ<8PVFf3Cc_Cm z7JL|vX?DBygJA9tTzs@>IiB%Lt|rujHB2}jPLt*?tFSH4NL-2U zXLx8Ps_lrB2fH|&M?fdajB_`vZP$Ro4uwPrb+lk`qdU7)`~cWWhAd+&aFFetwmmC? z=n#kl>dN6wl=DXY&W#qp3u>ctvtK1c1#fyoHPJRYsNQuhYbp}!AO?J@_J#q0hQ&lSljYD;NxDZU>- ziS+kjogp3AY_wL(l63#`mj>c^V6ie#sFFum=nw2l(AOEdcfRZE6>TyL+5IFN6;{wJQ7&Y44JiGZ40{-8m; zdI}==O^UTbk(5FByt1J(7V0Jpur{S$USAEj+T@6_1{qy0j?S=fD*#3Yo*iO=+ao_q z%!^}~k@By>(3arbhDU>J>6oFhX1+`-*GWoxYaZQDy5PF-qxFm8UM>m|cD<$M%LB^j zypLV_d~W-lPu=Q_gSn9Fc+Vi^6uuy4X1dr_$}wH-p{>Uok}FrxXaRQ?=Ts1DUMK%t z6JDQO-s!1)G$*_I{w`AU8|}V6{Fj#jS_!A{dzf!4mw8awvY-86?#ID9j?##t@CD8$ z7SCrcfs*D%!T_r87fS$8X*2N6(XnA093Iec?7A5UitHKpf1N*Wy`OsTFL$~zC3!9D zs+Jn)Oc1)sC;aD0t=U9o0v4Q)-yN7o&uO((xr)IPfit*WJt-Gz6B%@)t7UMQ#bvux zsngKpsK#0OEdwKu>K;M0jZ@^<<2I8=I9iT1m*y#_g99t9VJ@%lDF?IUAeT0)hSb;v z&1$WkC~`~02fUMI&Th$WQ~UV$8A`XxW82)Z>{IVdtXCSWdjEBh@PFB$=t?J#5BGDP zx}iLezDogyLM6sLkA%3AQV%3XxWt|t&Lvr))5%WApNen!;G>{r%Z5o2WYb2TEtx`| z@~_nA1^YSN!NgB3EG-qv-~U+HLS5O+O4vfSWGYM;Hq(`^(^u6k@7eMe!A?MVp<0kB z4_XXP;j`%|A}mJa)e`#PSz>5jmrcv<*PuLzA+V7_C&zNQQw9r8p@0Z2N&f!k6?2$!<~Szn?;T@>R{Su60=rygYh@Ypb~WSq07S=932n8c-vmvMB=;2dNv61 zfdq&3h)?4FEpkKIX=Snjwi2hzx=`&wMbiCA1gbGb<27S1J4LgU%340#39&Pz_t-k^ zTjEUP5fNN0M@T%@Fb6_C1e&^}qgF&VYglQ?fpiRsl0V{Nzcb^@$<^?0h3!^4ag3wT zCS0AiJ57uv@Zl&&E_&rdAB%E{hw4ECUaXMbZ$b~nFgm2*lf9->>MA*9i8#&Q5t{I? z$6H(RHBZSj=K{T7 zfG$R=edUlFwo*^aP`l6dS~Pv6Z&W6Ng$^q?Y+H)FyHL8}z1%bW8IQ1L$kLC9(AP;< zupr-)=lCQ~DHf!rB^rGNLb`u48ILm)kn!v&sWp(6T30T?`Kn>>lf}uR(4NfnIK7A& zN%X^5wS(^n`gs^9{V3CZG?lD9TDnL3EV240&&4>GotZ#kTp6ypfQ#XGdU`(Q7ibu( zj;{dVb)(3hXM{W5I~dA=(1RaDAbJGHDX`tI&NUhlnwDM^u_SPYm#=grSbS7$~0I^f==brLELnB?o2N`xx7}7Sw$Yp!+o{x zWwQ17^vQfQfXMuj-1f-EorC5ino_DkX z0e^!zEj+}HAW8rHA)cMy{ZQOIP2d;XKnnP*){U+@0_GiVD`OWP?_dRZ*gXz5wxvS& zKefULL#Lpqz}GE~f&`G%Qnud=S;OFxW!jOEm9LO$ zUr7i#mS3Gd?$v!!vSK}Z(mCqb-`=op(-MW-Zvi`o!G>RlEP>vtIN};n%2i&(|GHzn z1|PwRBi~8;k+4(#RP$Wb&tjG~jU@>?lp*hm$gCL)wCJk|w|?NRMJEZ+Nrrsonitw6 z>WL53s4`_dH|5|~9yME-*`73%=6YnMCa~fTt9-j7dmBL3Q-#MNd}f~4$5hODc$xVT z2_L^y4To$^&&er!VbvO}^0K0X^#Fz+Z?apKfpD6VAGWMRLkP8EWi(^{n9bE*hHsA| zPB7Azq>Pqdl(vN=H~nq;J@rg1U0fNR+nQJ2LyqbSN?jeR{B$c2|BuQ*9=cJ8UEq}~ zph>GA$szWaFDYOG2Npsg;~O=umL!zBWyu}CGM6MnKAVK04J_rm>P)#Wk!ry&<=kwb zSSWbZFN~T;`slSYk$j5}wBq4g1pa`>MXgd7l){{J)w6tBy*iJ7zRhSz***12u1YdU z^t1xCZ+mAIHK&2qXZN3ihyVn0N^VK1O(QT)68|H)agjdt=}$V+c>Y8w1tD z2mSCr7K<6@EBUiDEcL3KZwGNY&Z|47?Biyp-`LlLs{@R98IYOA{b|8_Z*8JwV@sj- zd8ol;u_%$;qg^1KOyp1V{#>Y8C4(U3<0gFP>5?Dj+PX}GmVs5lCzag=fLr|P_L=sh zbbK7V8`tV?FiO1@F<_Yx#!;&Ct@azv{^2qsadM-GUDD3Gm*(>mo@vgc}o#`gZL;CKE z8dp#24lK(3V6}-OCaFp^!5Y zdmki)_cU*L#G9LT%)m})MXTKq+lBjbcJE=;)OLf3aHpgx<3M2cUyBe<>FPqZqP^kK z#?E4DEcAClyHATYf?xmOBJ-@C(*e zMuiIU2VJ9W<1Ak#`agK(r6{jr*mn&i#F@BJ!H9Z<;d{l4ebg{#3$8A{kYF{~Mx^Iv zA}$ufjMn!^)#&A|Yvim}Rh+BQfS(P;E357`l5T6pvEJi-`Mzi_N_^147eOhmXQW2Q zvkC$lgR&}^X}Aric~MS+>udp-2_s8X z8v*Qqw3~7|RsvG7>`y}Kj>@IE;4oK>!Kn7Fs_U=>yMTY<{p|8IeVETD17QfQ5VRvs zzxBGDR?X9OWTXRZv5Y^4Rl9t|7tPE27e?XBWl){+Y?HrV1O{!3(RVUFkI{207NzL9 zN`Z4{34vVeo2R7!2Xy-d#D&B%nMS%X1l|bng!5W_$YEU7)DNg+WLQSCNwDG!zZGnF zC#}OOvD32(xz1p*GFUuxuBy%x*3;DF`X^h()`4Bp(NuVJ6_^78^8>E~v#9ghT?Wh$ zE3HP6^-x3Fa_WWVd6p?1!}v8C^}K5Th*Oo-Q5LWd@|_inpE(S)1eW@@K8-Bv--1wG z2iw^`-AMvR@p<&=bSh4xrfaA3({1IT3q0p==C@h!mpGfpD=P&R?(ZD{W+qgbJqVT)Dqiu_v=mR>o2fy`wMdUf+R23WsI&_iQ?k) zq9=+L!!h7KJ7zokW1vn=Nyt6&YPW6c5={kh%Xi!V19)9@g8ARg2dYzlHup~oOCj2x zk^=lnS(wwZ-@IB)f4Ao}GN>NgcTEc?Ko@iQ=|9<9Vh~=KUR#p zm%IT#`-n-ax*{1)u?p3aKH&aB*zFnp^F|^-ESwC$$-YXOTE)x z%DqnP=)T!8T6gNJxRQTa$kj&iryTz%sq(^L)c4WuRG7-h@G_N_Ag9carGMHHZs?l# z(Pu99LF{-wty*^6PYKk}Eb(L=q@4*aP>NUX63WZLC45I6h*+*opK5EwArhehQ%@|S zSN>)b5tK_3!6~u1s-8%#T%^Drf3KpM+Ou~bN8nk9ni_SDc*=okUYJH(yIG{isqJ!M&$X>?lIGRm>CHis?i zRzSQbWJ;nw#-(sVoy;NXOaM$q9vzm%uT}w{v9LO7rmi$OD=qulo6tTS>l?|}%kX8Y zA@6?CPOg%n!?uhWL0U*#EQLbZkU z*rWc$5xcd)^iIYPCejZ}o)-QzRMlqSuSQtzI@{2262x*_PKPp#*1;c!yA|tEeht!g z@oeqxefMr{@T_Np6_vyF+b2+V|fyS(5Ybyb zoyUkzoPp$i1=>Z?AFyQj!EEHY{d8Gx>kYEBFBfp9V?XLyGD^B{asr6wA8zMrMn?is ziTy!!7DtcZ$Bw#`$weq1lwshOi^4tf6$c(H;H&-urHqVa)Z|9seGHG-hpv!E^m`NK zoY`v%%HqYZ00Y4a$QhNyqCX^uFm&6gUpiRf>ljta$}r5e*7MZQB|KfD@1I7T$~bG9 zUZJ&3#CRBQVCTr!eeyQ~Z}XpXKl>zt=`J5QJVVJ~e@2Lh3w<>vr;&1+g~5sEv91J z_(g>!+>pCOkv?$9B9^(~zzUsEh$j=HLH^ymDBgtJNCI1z9eP=FUtG@UAj{&&8d+HP zR8^(Jc9C4{W4${=N};R6g3?RILgfAK)`%{U5x|Y{xm%oCfIrOJYNT{f2;vIH!oj1k2?UuPn@1(qck1=(;WUM+5GnbG2vHx zFPCoVbo6b?8@$U{JDwn`%Fx}|+VG&7CpU>65fb$u9YU&yoVMr{gK=50Ze9_VmE3MG zdl4EQ+Y%TMyVS_;zCQxF&+=O!&Bxdf_a8&{u-|UYGDK3Gm=G#v9OE`5GzqCR=3i+{ z&j}>U>r7Q?!eWWyvMIZ2fVULzev-|=K$PPn1Vh1ajeXotbP-yml|Qz85|gvM@KLb@ zQfznyc`oD#%8Z6+FjJF{!h5%JyYpH>rMh`^gcnYM*LJ>~2u(YdnlObb`$qV6%1r6& zR4NOM#-Oi5N^J#7sxh<$_=B99>?Ay4><{yh4`g=;*9n)CP6{Rlv&Twb9Pe|qD3ZcR zRMCt<6t(g@JecRGsN#v?Zl2fIlbw4UH`Mf)XtT58>HR1qZJ}EPdI$kNqLM|W#?rGxZDA7eEh6B7hZzj?=)FB)8ZzO$-IRK)DBygz!(ZsyskE5TF4nF;@EHkY9^cvUQ+WW?LfGU6z8u5{2PD(kyh@>p_ zU)j+-ebsqiz4Bz-1O`K+Nd%U+h`?(P`SGXhT&9msWPmG?FYuv|r0Ev`HQQd4iwubH zhOwCc4za(o>of=zELf16Z~anIPUC-n?||jh zes424tu@K#{9Q6X#SYcz&v*M^6H$Y1ZU#_Vv_5C=B<))CpWL2>++x-PYfM|dYV}qx zpAFguz;^EqY9oO?TlczG8%lYHQ(qmfF*J8FB;E^~_mk@zj_+rIE(3iz|098=>^ocy9}OtcqT5UhExZWuy6So9zW@>|B;HG^Fi z)^`Oapx-@PhhPX-eCbc#;QcJz-D|x3<>=)P-Jz)R}K&64uF+ z>6^ESp!3YRru$gnnXKajO33s?rjoyj2~mzXiSsx2GsHfb;|7!8pX(@rF9Rh<68{*} zbp8L)i_(#sDVAwYsF0=x((M#E>dX&Z!#PGbf4(jEFf-a%DaQx+J7Gk*8KJ`U!NQX8 z=_E+k?{6hpu{_ks_DT5Zl4TnY=6BCmz4dgEi8sn}T{)mVJQi35xrMc+`Y;2t0 z__4-6zYnhp$GZPe9v3Tjindn68qw;PTjCI-JE)G@M${hiSoV>_81eYDQcWeA9`;T2 zRJ7RWO*ZCGp~006O2Y;7ALpYtSWP3;PJ!i+XIu-p(^Dh|X|ethwbvkF>})*182^Wt zl%H`q4_Y*D5?E=bxx3eC$?ev2$A__*h1 z2`Pc#Q()Zdt|xS`A+iq2Q9Jb# zDI_f~)&mYD7Ak2fygU*JUv;~^0y=0+^}mF;QjiM)kkU%_3l>~8zzl}@El7l zoQ-n4ij@Y?Z64(bFX!dJwX~;6aJqOoF@l90lO3>HiVvj=Ue+garHU!}Vg+6+{Bf$J z8z!*%tUO-1Q5^GWtR<$1bs0OH90KSS=kxix(92^zC9o`#iG#p-zZn0UV}{o$4NV!v z{EHJ0JpQoj^(cEYQuu)^X3rC}=%rx)TB5>c>s6K$^Q=<$ok{Pz_peK6=TQd3SGn>BpY0RvDZUH zx(=xS9pNPIzquuHI45l^0#fpp@q#}g5 zo3flC{8)7R!>9AE5pr@O%oX^{FuRHmVbH3nrsD=D)(2;bCzT>)Zm?EKs0Q|b;Us8s zSmr}uMK1B~|F9qtGH$7yA7*9N!(zb^v?GLI*_>8Oxr+X##VIH4vqrw;eyfUZ4|BPR z7r3HTPsa7tt1`M|#iv;rZ~TqW@)Zm8`9s9_G~ZN*-0?zuD5Y=YvP%z*n@<_P8_5>y zEPQ4l@i3KTXA4~yFO<8G>Dh$3#(i!4lVCj}yZL9?{)$5xO}ERGI6xY>?KFVge0B-h_0xDuqmR9#@uq-xdH9y+ewSK=d1d<@D#otn!Av~@VzPp@<*WC>Z2W|Q zl5ZXAW|4H}zJNBslMmZ&&q8EM{&{p*_g}4MoQ?Ru(ZFfnt@~DAj6V)n0Xx*s`?as( z-p>p@Uw}=m&nrRCtxQAN`@sf}z%<|co=>gj<(&qTi^u=Fum0zdWjEjPEi98PCY1YW zJ{`ik%g$9NLkTE&Ke^VtiFrJ^9_))$oP}KOAZEZn6fygG|E&N_{n$HMo@HONm<-u= z+L3T;s}8+(PxONJU%u&a4Qxef4@pQ7DGwVr0VL*0vlb=z1>~t9+(#9VCw?AQgG@XA zU+;w2`A20zp~ry0Zs5zX@R38;CHFoSKZ#Xi*TpfS>kxvNiQjZtln06E$ZgRI{W#6z z-b(YYLZ!4%J99kFdfd0-(?}Z6nGv4=^l%ah>EH~RITR?^C^Y!myOiRaZpzF3uu4BPuZCGL=}TA-bc3 z{x=FrNmG>l0||FZqykMIMh98t$k6#R&L@P}!=qkPB8gqzSRj4>3?JMj92$79c*i8x zos}DHj#5yW`@X4{;u5&|7y>xBLBDXcF@#hprPmU8jtRjJf@LSobFKB+C+yfD*ylYy zC=vOX*ZqKBCt4?ll?0n6hfkvfccCSSErVT=k_`p#SGR!1KS=qq!o{){**#_ps}N+=1>GyB`kP=leLw zVr;<#<_`mKo6dxf#?i|!hr=XK`awS<1kCgLKeN|W+Yx3T$8CJwivSIQ`v%}9Q#cGB zjng|=A11m;By!4dR18L>cM<%(lJU=Q1!Ji7D8es^y5}X@@#W1(F#d1%f?&ON-nm6e zGd1Tn3i?nXo&CfSxOokF!Kdbj1t$;0IEO-chYz`G+B(NYF7ja!2L}MoJq#jsKB1Ye{I~TVN4r0xquncPyS`bq&89KOesa^j)T)L51RH zm%k-9>&pLjWB*ee(pVx~A)spuTiq>FMwRuEQ{1S-)t*~yRE!1l8g@7g7jo3M`Skhs zU8sH@w6)Pr#L{?PSKPT*t79+4IB9(}=`?WJ>pzgK%JOW@;I>V~* z3P1d6Kd=4)L3M*6O)8n496Cc2x6ca$c)hz*{SuB!x)HFeI~Hm`Bz#7oycf;7(UGW> zxc$~5B|+?rOz{=`gEf|JELhO>OIot52u1&VEzANxor_ah%TD$?G^tpO7!=?crE);g zhs0#8em;)Enwp6iL5F5$)+y+(#9O8GyrLYIewmxw9xE+~RLy=>jW!p+iUPFFC#B4v znc`q*uJ(Y^*jda~7jRWBM4l5`slx=T&nv5(^WUQt2sguGb2mN_{7>lZxNKgxXAv)I zVpBB_1?5H!cB{p`J$F1TlhHID}t{SRU8Mkq$$X z{tFrglZv&bXHR0D>mxmoOn6YHZYH7Z8z%D9iSaq*7xWu*i}Dsk;U>qRwC?*lZLJ~{hY?Cb|8j8iaOd3OD) zcZHXyr<~xUzwV|OqfM|-OEA=*;QeYvm=@4?Hs`%U1S^B9RTe&bp90bP5q|V^_s3LQ zARv65)qIZm1J}2ig$;jtPuOqy0DMa>&1p#XpZJUnpLa#x#)*-HJAy%D^Zy;zfj$UH zF&qZub}mPbT{&yEl+f%}3y+01eBcDo%-18~-I41rPlmYVb`H^ai-aRvQdc8-+_U(k z_79*|mFhG+nye5w@=ly_B9zgdu=SiYgTag{*y%QG!6M2=J#RcA2ur`npnd=Li5QWi zzV)Y4bddDnl|C>(P%RSzWE`jra(n>QZqfE)?#ucjtUav8CX@UZ!jNH@L`A-{mJ@<~ z2u&s%*>$Ko$n6kAbkhZnSeig@XG6*V$JJj&#np9PxM<-{;qLAh+}(l(2oT(Z1a~N` zu;30!aCdiicMt9!-1XFZcH6D}-@ad$Yt1!ApS?dlb^eLd07l0AHiMZZe}Rg+I>G3r z!N}!5Z_0X37}>Ydd;=VMe1euJ3F!g6~w+<1k{_ znxV=n@g?rQtZO&m0gwbv?VYY*sp8iH*c?*86<{&CCMW<)5B~j0_L;81!pA#6 zDKMlr4$DaK3FZ{7a-R$lo!cciuZ@guC$Mh+inpK)Ygppa3Wy=NNX}1sSVW%)%yzOv z5RvLABADlL|~Ayo5*tJ#m? z6`w#8NF5?v4bo+#Z}C&yxh)1PxX*4v@m#@}rSDPTA!^7*u_q_F<08)MRGAys2;ijlJy~ zy%#7;Y65aa|HdAKnVi)cO8H9*p+&~kecLP#qkY0ca%wz8?auQrvB6(*yjI^QJA~IC zYaX^EpQoo|EoPc&!bNu8DKnE>`lqkD7(5G`c9*uFDrm||EvA#)!!BA`DK5aUG}sc^ zD0V1GpqeYMU&f1eTqrhVlLHv!GS}D*DuL~dC`f|dZ+*o`Udh`9rF{+DU5g0_?tEvJ z7<<%Z(Yp!@n`_DUVPa4kl4G^Lwu}nzi%Hh&{m6VuE$^EsP2ojLcURo zsK#^Tr$~YV-`}~EG!a%XZg6k2hp!`0E zXB1zwC3?nOtoJws@uP};yWUQnQK^QI;s$8da3{=&3L3kb-I zAL2G*As>_C8f(ur;n=D{Dm-^8CJlK|hJkuX9RXO1sL$Ywo1|eBQ}p{ery98bLbKU> zC}0PbC^MBK=-YyE)o3~oLraoA^%bAwN@9kp)a6u_DQFs%tPxT~pK#mfmy}8_hAFD) zc15uYM`6>^E+`RaCol=+2aeo4pH_Jwkh-c-&99{DG&LaGHS~-_aYVc@V&O$o8C&wJ zE4nubryFxrlZd;fqpEA`yajd~p+nrJntc4^m$)xxP=H#)Uvy^NF12);C{NH7`tn}W zBh_EguI1?I(}B1fx>gw$)o9(`+$&#|eBs?Ka24NY3G%0N+N}r<^QP(f(Pek%wZZA$ zamJkUCF%Xe>Cze5nSj5=#r?j4E#mk%)$cQ10_vtL&ig35R_l<1+frqxj?PZ&=$gMP zZ8joJCv})ulHovLOpm-kT1a&#@;(G-zA5A1QsdCggQM;Gt*XD&S9ONs6f&jRIim>E`Z$iPxUU3?9Ud7Pw<(irS3p=ogB20q0?jD3DK`t9Y1HJRp6~pbQxiY0Kwhj4 zeO59T+Wslus@zXXidKO4jO}~94lM{5%O{5QQyZ~u+S@U;8Y+T+JI>v#n*Hxx9Gq{! zIsc_`! za``tF(qf;C!#pJk22U(zNlat@LTr{oULwdr)eZUaZ{cN`ak9tfQQ|7ogd&F^m;rhQ zWdwaodw*Db<)@DlZ$8{fFo^rnX8~C1XM$W2hu)a+JZ>dw=4TpCbp%s%k-92c!tNR8t$eV-q#F&uHm82WM#uiy{D zH)!i__KD71-(XXU=joZwElix$D}8$)q8Oi_m8SgT8lfT1gG2`fImVA1G0C3cSDdYH zC{rpqqlEx=cs$WMc6SdaCX;#XsA?xp(^nyaR=jxB9ma@v$(G#3RAt_yG3d&6rv5D-) zy=+rNjr!DsSn(f*_kr_0`l`Z$@yF6|9Ejfu=Ua3^dY_w0Olep&0w%Q^wZ73}ba6Yf zox~k-a+gP1t5=LVUquFkEN%|uupXtpon17en~11?05XnJj}moQ19KOB{1yvAl2F;n zLMf3$UVa_3@bI+sC$Vp#|3ru^+*s(8&nR0=`ed6HCsYI5b^H$XLz;H_fE8n{QokZ! zdN4F5fa(?}SMj5DnHhiXhT|^u#JGe7PG>V*N)6zku^(@>j?LZ7_z&u+;%Y}U&8`Wp zi#6VIXu7b;&K7@nyh7p}cm7YCY&;f}y|FvEkwWgU9Q~O=VdU;7Z1utvTH+W*IYde0X?Hoz?;H2)9CGElW-_L>yxZJL8 z{Hd`FQCtL~-x+7{NTAL&e92x}xIY!PpEfDrG}KIGa?tXkXB@(pFw`f0T|jrDA}Ttf zzW?w=7ZZM)B;Lg{4lua=JA1vTQffYS^R;4>+8;?W4zPmpghVM*)%6a415BLzPN4BD za-2oGl-3}V_Pj}m7AG9)1_&?dv;b8Fq3_EQjl_sPtY}_JeE+s20TTU%ase8lO>O?K zZp-<<&=u*2PZP1Lc zig*lEKsh!)oJhO+8Dc!HNb1&KrumgP#TtME|75dV7K833Nh&G&!`M9T5)3D8fu&-N zkUyTRX>^m1Ea{4Y`c8@6D@rZS4Im|K!j1f(Z>C#Ef}6MzT5RB^Y%36%{#03Tmguuwf&|PCC4K&VR>M?)KfT8R8 zYSn@^#dF`a}6k9Cw zqt$nnP{sw$4BReG%?r<@`Kq(5&+3g)z8FM>15gYOM)v7pqMc5=@c6w1s%r)w>lSxG zg27B4kY_tycTiF83%c;&H)PC)w`*CVo`T~r-D-p+ZDY(3h9of1nrqq4B zRZ;u~9Tq1m^|z0)X^MI2OOG zUK@>Be$iL=W6Zig8JAJMkBmdNG4;AC7soG3?BtDHo(G-28y4!4mz28YV^;PZjV?k3 z`jBl;!}48~oVsQ}ag>^q3Z7&D|eA}w8lR9wJHZev#hoqv22_s1L{C(xrvOe z6LOznpx#nxnCOs8^at%46DK52TStb3?(a_K-#GbIb0a3bEI)k@p z#=#qUHqs^3~A27RZ*(aOATx#6j%YEVrNQVurge{mi0b zhK09CLgNb3qT+y5B<(wX&17ByvFL{qm-*$l=j6;W7@#s!%71sq^78&i2`3HHJ^fG~5W*DcrVh2)gCLLYMhCvnH0w*J_$sY zC&}}5l@Np`scr^N_T=}8T=8S5zkizK@9Q|=sZyYDnJZK-N!&#s4a&guvdYMU@Xhe@ z9w4ZKgqO0eXJ(;B(?p%{ns&H;V8%3w&UzN4VZ=OdXJNoFD%OOcJb9{{$^2jA|N*=NX#U-DuE zneMD5IZF{)8y!f@(_k5B;ct6=RL~cPWZ=G41r_%+kjWXcwH{HSh@r4>rjr-ej7%XN z>F~yAy>Vra#&cx2^|jJ9TYl{O1?~RG&-qkOhNE>>^>b_`>L6#zsA`Y=d;H7h9 zO{!eh!&Y3{jTI9G{kj$*M{ZOSRhxP2!{$3zKQ-_9|*f5HdYqu z_oU9>+Qo9j&XHu_2jsPN`@jD#+r|S{ktLGpq zmLFmt{Nd^+`b0iLWQ?P7cGrvFy1!wCXae;*NU(~tKkZs2{;UqRVL`;(`ct(4My_>b zNem27XopmWUr=uIeF;}QP!GW?9X1nv{8hSdjQo(M{rqLYs{C;7P@fqI_c@+%ChYSj z>&^J7mR*5wz@6&;x+A|fVvDRgi`2RF)*S@EHTl}%e=FyYL%o=6kx@XRiKi*IVD9JH zAseen&I9{x`4ISm+516C#Vz_h9ai|~4krg}wBWGcfX|Mwq{!oNAFs||NU2FU_az}A z&MJZ_=OsPl_#9-2*J%dsQ#=t2m`Myb#Od_x?7;Jx)6Q($WAs?)7&4WdId7kn96XOHz)94@LqQlCjjGXkamK}h-9gpB` zxOTDg=G}7rKjiDeE|fhhpFqt$Mz}B%U|36o0@AZWV?!cal?Hbc3u7l%iOzB+C6VNS zP2~zz-``)sV%obs&=4?PbIP(@xL^je*qtG{_vkDmVoTeVc^IXT%Yl7CHX(y3&)VmWz9LtrSYozlOQ&`PHaCg8PxU9;frP4f^KIRu?c>_WPe_7$Lk~{s zj#()=2w7{S@|NTvl>`kMryh4cV@qfcHcC^zhUD#VZ4Ao`>`!vsClhC}4jB7S=9YQu@@EPlJC3m}e!;b_DaCAQu*`H&bGpBJ&=Ii^O9Q+W9q*>{gr6Gk%x3qVT`$(r}2V zboc(_>@gtfXgoc;`Iy#XW;Aci9#K zoC0LNmpw?0w6rIq&NF;ZpCV2Kpapx`4RCuTP^ZHJ2LlVXl3-9^e!-Ai=lF93(^XBl zaj<=jI}&LaN3N=BhA7mZPkp&E^RJ+#G7)1sZe5s8&qS}h{eC+7a`ZEtBD~yER>5x{ zgvn%JH%dwYL;`as~Dj6v)XMX-Q4iW3=>i~LZzKD3X30R5q@7& z7FA{qBL4Y*a-RRsOlJ3wkW5+o(5aMc{EzZ@5cu9Y6;szc<#x{c$5d;E{KRXb@Z`1I zmn~h@CJS{?{89bMERSs?8``^`Y!)u&~E_~SRy}Hi~60E)D(>;n=5>YJzXnM52Wdy zT=8ze!pH@S$c|m(- z!!#7a(~|pk+%7A{U&@^nf;Mgc{W4my4%YnH%mv(nL*y!6u%;uK6cL;hoUB;#`-|R? zBW1-tVuPdTcc$zBcWKgqM_vtDJd+4jQr0zuGU!V}N%IY-p$Fwz!I%tSB9z%$t_0!G z!u@rmS_q}AlWIGaD#@*;VNT}E7Jx9#*AOziu3E{%FqH|NH~0B*7+(=y=z1)ByJ4N` zFQh5@!LE+0>dG2Da$zHpQNR9d?jv#=Ilz z*I02-IB#DA#T$v-l%IQQ@2<_COYayF5}4zUetOaPujt)~i-$Wj=83N~N-2+hm8v<* zhZ+S}u%@*SzZ+M~XsfP`+rFn;E2#x%%P0D{UJKtND0#27Vc&(`@Y(4o z1(1M#gV2~M9tQ+%-4I^LPGEmX;ToeQBe`(OKGFt?04a})f*v4Lvq0rFV%}h1vo6)j z(WBP8OYQSp!GTH1pVuNL*_FU_zp3rT5pZ0Tcuxon8&ML@sP~CG1*A{$k(d)Wen>%=g*bG|Ft4PtVAd&S-{;3HqjHO90ri7N@|n4 z3j60N-;Lxk^$wre{;7+8Ik~&6h0gjCrTl?f6|#)B>!+^}G_M$On8p(-+wZ>1t$$dG zcCjN9i}5CHrE+Aqm@BT+MAmb9+n}NK&-LcPQMQP*a@uCEz$q>+KMc3lhN<7)R;9j z=#96prqtP6v%F?zQN=CuAL!fYzBf43oU?Q?-_0!phHyN$4Ud{GOLhgNbzz4)?-unY zE>xh>WRWehR3p?X5X;WUj?i+k7y`3G04Rv!M|(|!QgU?WKfkEDv=<`pd3;Y5gq0%U zqFxj)W(a*c)2EbEM%()oOc|Ho%eu$o%H1o+O9s#(g=q@4pgG~q&ZUar2qq=Yk%4*# zz?#4oMctG-{(M2&(I1T`EKS%-$O!g9VPX-4wEv=vaF{ISy_pB44W|v)=bdy)1lEQ-ju1u+7bsTgwJ9kq|Wq+b2pWJ=-_3XIVc(MB;b7-I} zqb=1Z*!4{ol}t;F2@#gNe>9?6)l{cJe1ULmL?NlmVV#Hq3!bNw0nt=flvMo4WtX4P zMOE_tLhjj?&@dT7zP+zeKKU7L(dYGF`ULIUwnxS?>&3D!cV%eza=ISs=w`vpm#;S_ zcKy%Pp7~VCiNf@oajDhbs9#*_u}7tJNQ^HAHM>w(N1XahlQ~#f3LMn4V{D9 z^xD$OMd~pFPX0A*+8=L6LRuBB?MaL5Jp2T%+r3#Rrj$@ZukF^o<&a&XmOu~dOPbAU zPdf-k^KB1ixchp*lt4mq=h`R2&ri3Q!<84;*g>VYit;*!Mx9l-k?ZIo!TTLd97}HY zU(f$-*2mQ_le=3M5|e)y1Z6Y!n0xZ3Yc6sBuV&p~E`iW}*m9OyKXsOe2-{}={Fl!U z4{v~rO1Zky^J{toz{72ot=)%+*VgHciO{h?cy`EYlaMpqQk1U>p3DF``boUb^0e=M zqb-avRwSwMrB+?+DB&V~6|YAw`~ibW?GEmP>NWc1tggxl+U`%V%W;wB2mk9PA~1sd zIe+`cA9BHy(2Y?Eq@=5x!tT`8hZ%;UCg#}WbzVKhrK)0yL3-DSek~fB$XY{Fl9n2l z_UDXxm3V;DG?xE3Pv@ZCko$!Ks>F%Ie6=XZVWzrOH_nZRH@RhG#;20Xaq!ibS;{ja z4NBPj=o51lJIXn8ct3E!dqGHGz4Ta2Toe&2gJWz6FM*FeLaBoI+Si9f-)l0FgcG(% z0qjlaFenmPL8>HG)LBh!)Gm-fXIO<8oO@CewA4#pm3TWm0MV#5!j_-~3uFgJB0FEo zbUoXs5|tY&4y%q_G<`!rz9@)5M9;g^j1b{+^#?0E7s70lME)(9JVpq<{yFKhE`<@7 zkP!m5adPUxQMqqpmjXD83App?lS@X&R8RsS{yt<;TN%omGRLT=K_^Y_d63d)N|FF{ zI^=2n6nt^aL+VOSaV%W>W-EZUnJsteKPA$|kEZ$`B?Q8DN+=I2S>Wpgz@f5Gm`t%Y zI*AIjTi93AmY~GfB^wQ(i?u=5tWV%{It4?DBu zqZ9FRBe$)xUQ7xg7}_!Hjdst0YhfFg=9os{*Ph>TUMc#I2wXBmg^|Wyr*7R_=5{&k z8b2f$!jx1au2)cl*v2e>;w$Lp&!#KBk*j)55N*;AyTm@owTnWGMdkixdOB{s$*b!m zZjMULbHQmB1Ba!)zFSRwz1@S^MhWkpIe3$kQ@t_zIv1_E75sv3v#A%(OpYj0!q)#n z?9e{*3>R>Alhq{$JkCs%DjMwkjfZo)-_!Bba&Og$FuNxjN79$(>!jK(KF^rcPT)b% zBo8C*zhvO?nc07_wL~@s$Y0P|g*}fOQ-DB%PyQtW_fy@i)0GKT#-9t$Xc!P^VXnIV zYt6S+!fpCqN$n{C?}{qt?ty5pBdsh*%wFicY)jv8ZKsGVDRZrK=6{qm+xzjf()Zi= zCVnz=-OI3X<~hGt7*iDyHeVtXpm@Y{-%4WDT_%R6ioL_VrTuEYUE%XVH4Ea}-_ z>4*%`&~BnO%hD1ndwnLYOpFR}(|q|j#-GaNv{Fm-mAx2foW16p`rhy6TZKP zY(Wtt=4_0+h=Dj%Ce;8%C$rJkzy@b-Qc_sOIbC>+?ccR7U4rEd6f>3t3WXuHJ0wi- zNbmL7r*tWbJ8swtOr4G-*r;3ZsHL?}h`czVrGhF5++#mmrJDz)okHWG_B!CMm{-2? zNy=EAw(1w139bEclIbRS7_RG_4$)~T4%7WpyNDepxokg3ZcK5W#&Jbxj4>*?yxYcB zB95jnDM5cCr4oMB)@Nd<%yCaS&eToK8zgekexvm4v!g)YLLI|T_0JX`KJ!yD&ABqh zkOI^jI;4hp!n-sXEGSA~_6(sN7W-sheD2$B580(5=zDYJGVQ1qW0VGC-g3RUve)mL zr|(U^As$stl(9=b&eY7|5*9B3CVM#tklVyI&%yG2uCiSL|w&<+8akqYQpIS$4N zefX?NpfGa3=4cb5L;U=ool^o~Zn+lbM#iL$xM#*SQhLF{avzU}nF3q_`R&qY>fyH(uBEXHHrNs0{>n zdTt#vU4GZ@f3bek(be_|u=yXUz|wXwXth|M|CF{fDl*1Z8+LlHg!b{2=8LMz(n#;$ zzYp5>O`o>5?#ny7$})>1-MUs4^0C#9ikx!FBJY$^>vZdiY>m;^n0~(7Nf&Dj0>}^Z z!b9;urNAI>omT7Y@$ZL%uZ7O|&m9X(6Rrzk22zr^ zwe#KatL9hDvSON9)mNnXb6xhUjeGwI(Z7wJ8miuYl^nU0P3SdLms?^f$x-e~ODir9 zqILc_BS+d4HQowDAUWzhbl~~m>kD;$VZUX59Ok=yi&tk*=Zn1){SmNp;s5b+z#bNk z)afpJK6hDR@wU$jWw3_`o6kZXLMLpAEGae_Q7fIbYZxpV#0)L2^|wcd0?zekwYzTP z=;Y*hRlj3cHMGw5dre>OcuVYHbc|jvzSbiKaTF*dbeU*#e;EoqTRJ(`ji{fhDZk9aW4U6+i5pKP0oOxT|+|e#fZM*>qF2(Ukj(Z z5KFIv`#MWT^|huM5i6zkO~0UG_i-$J9!Gh2uj7oc-SJWWbD2!({5MRX@{txR`;-I3 z3GMAH6CDe{i?e5oK?7HCl0PP_t*NH*1C6rP9BokT2(*nIRb~}_xpiZ79ofR3m;b>( zYe)r|P#NiX1-|TxawIA94H%T+Wm;>ZlgEzr#T;!RH}6{&@!lys_kJ$A473qcmT^%< zK%H&Jlg~Dc|9ru0O{wjXlExL#nRdr!$R0_rA<*iLYpF|)hR*wc=eEBq4S;S-mh=qK zIcv`uUDaye$}>yR4R?DytD6uChr?ooK9J&vocmXY4?(!_XBs2g&Xc=iq&742tXCh% zxHf|Ej4kSZ<1#t%OPpHz$0_J)X6Q3H!yx`^A_ochGT{9)Ht8(|vikicp>G@@_I4He zvfBZeWww05@wkKZK8y_6^of>NEKLbWioB&nNTO|Ld_`S?kL9U}Q!QVY zXTr*&gaaIPW*AwdXHW3LbgBN@R4hM!{I<(YkLy}&=X;olGf+U3YW^&xmNFTIz~x%mHn zpd!72XU?#W&ZZj9P=t_URpk^WP%laYWRqb+c3wK{ZFpeGir??*w-fn2 zGzEPg{1|{c-v1?iNk^?0rY$}TNAp+opKfCk;p{5_nL+!8PwG9Yd9!+9qIqqnCZi4N zDrG=q0IN{dhePF`&t6~msdk~UBN!MLAI{dmh{(7UrPrf^0e{f7B0>kGq4qI}p%P(< zFsnFR0cxhDi_^LUx$(y}nJuxTCy3a1V>vh(`@VbIKNhVWqtxOl!XH{6cOsQ{BHk8p z3VknpHa`I%w+o<&b>Eu_`oYIRYKv>%_z=@ZJ3R#v1t8z)GS502R0QWNGBc_#={m`ltzO$+v!h2Y$O+< zwETkO&tMp+&-s3;sddF=H_EJUAz<7?Fqn0|TYg6$I+Rchk?e-+nqRkzdKV<%?hyF| zDpT_6iJ2YaR2c%*(dUs?@GeuCdhf25So{72KNzgZWoOmsBOSMv$fZJ`x|#4K{!rT} zf#wi(b=}A6M}^K{o>KXx;PMjViI-c-eo#9qrH{74D10jk4C`O^==T&Xt_p3>#3N*s4{v-#VX;ktNb}!MCjII9)rNe1a=>H-4V32j`Dx zYj=9Mdh+BC(b4+P$S)LHA;>mcpzvk3Ud$z3cwTLl8)HNcgI!=3MgS0*?NNGrHDnp! z=eHb)K=xgdOga~Agt?0;sw{m9zHotDDRbEa0KxTJ?6haS7oHYLyoNpy+z_$m7v346 zR5~IVw_PCt?30yMS5%_#N_X~ zu2=+Lz7X_wttX%KQ>-C(3iN6h25KC;6mF8K-@qF*<4ddmXK(%SrvwZXZC;+Oj3Fwq z(27s)<=)3Gr^f|vdpM^3$E6z7^F&+0e|M%PA@bH-8)nxOcKKYG-AGlhq}5me*ndCM+S0<``}`k)?yM46=RWUGhi%M| zLeuV6M1NUGWIj6X1jmuJkvUmskzE#B50V&K&3>q1H3yt_#vI`nBg_+4Qa-BBX(4~R zSeX$Bq_F%nySq1fZ)E^AA6?-PmW%>F_ci;gOGv)J^iB11poxPM8ze!6K(cke>L8|( zryesA_d6II9K)zt!2sulHrr=|X_!|gSX>GYCb6gmVHB+K#MPC(!cSb&2ZRl*m?xeS!u(MUBQz9O;1M-r{cj33vw~bAj4F z)*|@0eK0v;KkG;n;4X|YskN7qNw|k%nk@D_=R`i4wYaiR!6LMyTf9bKNX~!ur>wne zN?V|cc8e_N4h#e)FFOeW%dJ8(|Mft8-T&tkTTHj!`}2L@VRB-}`{We0$aVjCfA{E2 zI#WUq4RP7(*nrP1*r9EK-Md}rPy>$cT!~<%{O=^k<6G;9^R*SciAK!4tUos>>#`5# zy=Mq5K8qvr3B`yWAykm3 z`(BSMYD2Pc{4cMNw!rq6VL4G#5VEf=PLnTjbP$*8P92j*7q(_di8^*ejGHa1nfj3< zKqh}kGm4RdIb(X?s&siY&h!farB_zJZW^YelUZm4SUAu?jSBy~fpRNbUisg#jDLIvP8sc|xP%tFbk#QK8;4K6g zvJ+lz;Vqc!eyE9gGKfnGo%r;k z_n7A32(bXBatwi^EwJ*);LlXR@(?%gAVE*5nmIW&$s$I2u?w_O5XY;rR*!bqvzDHE zkb8eB%!(c#p^RJgh7O;m?FvD+mlH_{5`Wl^R=8+cjDSFOmM%FocLwm^BAO60^9iz% z4GXmd$q+K?exYK@sEB?fSI(64q=Z3sC$EDacS%y8Z}q^BHH*AxW(I?qrI|yoscEBD zs*5wJRw9|?i8K61X#eSZ4^*{GNW9HuUWCrDx3|G=PA5``$#Y?Lzb-N?jfJOav%UDr zvXJEC+=?ipP}8)R^iD_Z;$dhV;S<;R(`87ZQ!>frb^*!@qo+~o9DT?(j($Ci!MR7} z|9oV>D|k*bhdI3(wRj9X>BcE#I@rJlQjT$rBJw)FlWO%WI32vR@8KTH#2);T3mQHF zd2JirCxTHD&x#IpcHCuAr$$)&0q$F$^MUwTn;&^E6NfQBa(REOm@i4O!1^-XX&Jms zUS^wOgw=FMh67rnFtqmFiAznxDI(&2CMfkcHNYR${tiOxSDfp~XO_nriAffSu;C`C zLd&p8GDhknNReJGn8L*O4+yAxtRrA3M&*rjXE4O?mZaM%D_PLtMO8~j z;eZS8Y0b-xMHcEDdh?$L^9Mr>Y|#fp(Ljm!Zz1GUG)wfU^G4{@$>V3h1i*?Vh2+Au z)Gz`t&qHqG@eI|!Dv77+J1eN>T~y=3WI?P0YYOxk=GhSB^>bX?QEsFi*Y8-b3~9Ju zizTCA-JxKK@ATb#`4fV*afw)Xi;~@g?uNRCHSIb0CeU!%!Lv2*W}$8qJ;g9_7kwL% zV_)S=;wB455wYK`g~)CGoI(;mF{;UCQVv#rR6Agi?L zhNZF%{GQy?=g=RK3aJra58_XR{TOpT)X_$q#I=~a^k=f5b^s?VO7@#W>Rss`{OFi3 zK4WH-NsM%}|M8IrVA1#4U#R$SE&T+@b%pM#h+lkv#F*$o`CJj6$F{F8sLt~PA9Ako zeze_36HI%n`-Wc~KcGShX`5D@U=D9svZVCr7ag$}63)qqZ9^mT>3KZ3W&tC@YS_vi zPtw(=Q$ObqJ90$}zu5&3&n^XUFDQD)T?=7=|7R1^ts}MA)8i0DIiwec^Lc9FB>jg) z=Xy6+?xBw{F;MXI`tR2>zDK?rre@`V>K{L{iwN5%J3aPsH)jxs5lzzMA-I_Y=obPp zI)wdFb!)}|#dTiF%~o0aO>KxhAAw@=a+z+9dmo}0TtBZWt%*~j%81=6|CwfdHrgg0 za1s~4qn&F)Z4P%KIF9vF%a-e+k5W^mq-ueA>m!QL*d~%N?${@};rYyhcb?HyLqxz% zR5VqAZh&Xx)YB0WXN>a|jS~H|Hp}Sdg5HZ!O!mTd1mld3I;&#IxTU|Q{@#MQbdbm z{zsi4$js<@5x z0`=)SF&B%w-Z-va5G7jPY`2682)iVci?aJ3lqcT|WvBL5z*K{>H8KlZLQc%k|03Ng zV>uQBhnQ-tw1jJ+0j=*}Wh+0}8pwH>gPMPgS83Hdq9YoLurso%Ooe!`5);Fj;L{anLg(Vca39gZNKf@o7UZ%&_mwf zYd8-fRaX`flI8Z|<9lroHU~|Zg-DLDnpOb_rpCiC*u7;bEVZBjP+4rs9r9fhV#lfQ zE8CBDOuUR$6buP)1Dyt#Z)pBRsK%?j`#DX0At=BUeqcNNQ2Vbe{HGbgYDhN9q47mq zAG*+ZHs)%=F5DA@kUlV%5A_`JH+qdl05J$y-A@ z`Ip>?AAhiUIepm8LJl#kRTJR*YJ9yNd{$t9O%pxs;p7BHacUMq8B_F};BwYN(+YwO zFZVbg7kf?U=h5_Zp6z44uii^#rT0sJ>01_?*+}DudNgj2y(bL$3wZ>;hojdaA4z_q zWvny3=3DVa8zB6Vgd)Lapus`fRJ0h-W`6>Ftp_-sTv1T-fGS+W7Qh3yFc^UbgoyJ~ z!Ve=lfq=L2HcNhj=d1n+M3!%RQ&Zzv!tVoCL`LKuLhph9pI_HMPv*?PT`(E`{NhyL zYTaeJZhm@|e^6{2QG>PIH)!8=*ZHPP1;L%d(X$)p3Pia0 z^i<)qF7t^C98++9bvORd<3cnOosW+dl{G5LX zTa5el_hn9Bs-n@SN(BC3Bi!Q||H{rwnyv8n1Bw*_y5&5*PGwHqpBt%gT)L?FfFw}^ z)>wnNrIDRpQ$?U+@7`3bpI7Z~wr6?-C%qs6pz8)E)eAY^9LMCl;hSy{y4b6uEp{-vj2^W? zcaL_*i56o7&FFajW9Ax+UHCibTyOZbXPCy=+>xkvK__dBipUqdY4ij!gIpee( z@$I_QBBeo4ZaXpqrg(Q$i>s6>nDdNE0)Q-cZ1xq&k_T^2O_3z#@a+KSRs8NKR~joi zP(p^NiXuV`R<|!AuI>yq5=m-TLMA*AWglCLl}nlu^~qTG68ykGT`5Do<(&jn17?7I zM5)lY{nY-3H1{pnbFdHF>c1VK7p14eNzLMTQpM_;9nquMq}@C+LL%H< z`M#W7au*lF{75bCGo!H-cIXKw}?6ki}#qc zPVI_M<3L41tWatLw=k6$u8>>n(&_)RT0ug%jR#8P`>li~(pt(*9j?CdA z=LAL}%i&4?DHz>lmN72%-Ln#)TBivnjNVtc3x6YE6bp7odWEC`rOT6|wM$VUw zo__5s@WtnI&S#Z=7@rB3GlT-?p})gpBw&!r6Gy0c_X7C$k+=|3G&e>jr6VR@t?8$y zBPi|6DCUQO3z8}nDemaBoy^dYu4bydWW;FK({n096B%9yzu@sb9W*>9(CiAJcE)p+ zP?eErsmCUu06O4${$IucIc7VVjY9^S-s%&y2p5Tpv|IRiEI*IEj^K36O70n;V$*&>?|cP zW533S^>lcM410>Jzp^<^U+gH=$=spJF(v;Tk1dGib#{UCB~hj0!n=m;uZeuHvt5y{q^v#^1!#yi}a zg(3`kX+Be0ewvip>=EINuls!}nn!8K8ESk)9B>j4<47x^3HaGa%!O2f*KeyBOKg;8sbHFLo<7oj7J zB}E>_Bvnl>+9waiy&k1*8)}n|v?v3dY}~&4{qeJPtp1sPw3(&H9yi_+GMF9c)mJYLu?svK`4>$ZzIKmQOp{+DuA*L4Xf2 zJWPfE0xl^he7NDf4dN1&3Uo?hw2t zNZ`~D{8*{)GC&|_$^|f0gJ)8WBAXCdkEgUY1*4WQ%6n`!8|+CDq&f*3CMK)4D5auG z0}3@sT5%#YasBoLASEdcqQ|F|4{GM#uzC&TnRgO^qJ6U#UEn2}8GEnT$IU}wMaz7X z{sS&>NNA)@uh4~EO_k_MxvtiTeB&*= zJ-Tu|Ki+W3uAERhh#;IM6~KEYQEVgRu0#s^b9zuB__1*LHTrldBX$}IE-c0=HlBP`w={b7{KJZQ{Aago8xKT{FRNQgX0nvP;5W-gPh#J{ze!#-~=)%fAuqXX4l^r*~|au+pW=_nHXI zy7|OD?L_Mb)r%;pNtiCdd^XMA=fIpol1*=FqC z$ne-$D7s@I{!F0vjqR=XK-%`QV~W13TTRU4)@YnreKgixT;S0n*oR~M;#*^7fcxY= z2c<6I;T-t|c&F|afSH9VOljKEI4b~W_S89d((T+~{1e$DzXs>Jc@_yhy41OMHht4G zDN=@JY5+WC)8$&z*|GRPI4_8YZme+3IMMBCjxDf`D0yJNVSi=zar@Tg#{H%`5%1UH zmec)@lYyE*rwHb3?h7lJ#Zk_!BKzfrf%=KXW9srD-Agg|km~SG zLs=AI2!(qBANhkSdlMr1oU*m-Vcqb2!I6aTYhQC3`llca^&K&9_Hzqbl&BRG$uBa{ z=*emooZ6_>kvjtBxNl)WzCC__+{C zQfpc^B&QS)VX%}AiGgmj(F2ae{2dYg7yV-;=EQ~nYb>zkzm_fyeaTZb0lA4t9Fo!? z)g~$pw>;Tp8opeh9&R4XgX(L)YMS`J(y~g$W9Aq29$XWemrs!4L6|=an!WJ<$JIGT z#~HTmdSctQZBN`ZHXA!>Y}-y_+h&tAc4M=RZ5w;~eS59F-}m?Yn_2fW&wX9zc^rAX zBnW*I$6vy1XaleylmIA&50&Z)kR)z@kW|9de20UqOBxL!$y)xX3xtYBmh&$}nL%*a zzU8Z&poX`cBoZl_jF(1=J#b7AID|O^m_a;8h2WH1Wo4*8?^B-;*i$-BHGZ$Y zvs%6=Q-o$OEuw@7i-^Q9z6KS^I6P~FrU>%a6gbnBne+(m#~HJ4dBP%i#ev?2>>Z#> zevp`h5pWR>MrhHdr5a~{|061dS4R_`YPFTlwCe0W zpOL&2?Qh*zYwlqfpaz757=2Be;i!R6P$#kUPh}bZ{k{-|l>B* z`oZ#;pXX#A7D|O`?)lU1ukkUNEs=ePCyRKV3YhFZ>zMp#gfo-Rktw<+5958^Hh*#S zbioZrKzpN&pO7?M*f>7hX6?)m@Au&vG=tpkl_oS{$}RH6@7_L( z{*LoTK4nxd&x0Ht!!vU}@TfzxpoM1BpWl`0S6{ejp~E3qBiZ3If;iQCDn@Cau44|;M0ewtxLLPN74K$(qlbMBebB3GnwC~zuuyJUZK?F=s$LF*a-a8+0u9g*WNs%u)$C# zuDnqWwKj$5Kq9yuzVjeP>QCqC+(ue*U%|E^fVWe^Hq#3UUgOS$VP{#2Z=gB98%+qi zJkw3pSc6hdDF&3)CTCVJZ-pc2s%q#L9ybN!R!9OVyW|{4IQH{c)i@XBLP+^%;KHJy zZ&SRrzcOr^)Ei%Ib$bh5dfH8QHyDD9dHjCEUb_}*7T`{##bne_B7NSMT_fDjJ{l;- z0EC85`~X;dj6;C6VQ=9@x$z!xIOXaQDkK#FYAqMCC8AsauSjm!LS1s&XXYO_?Ex~jStA-%P+6a%Q?eOYu7bviKbMHkb zqc=dS(>|1@Tm2?I=0cxgl^CKIlFBz$q4A&N%_QdlfUL-F(dI;u_0Do|ASh-{3{Q_Zaio zdYDKHr}Lgpd^Ub+H@TY38Nt0Dai}SXY{F8Jy^X73qsz^P zhue;MAi~g&RfY=SHzbmW>s*&=Fkl|DXyysrgg3H-6OCIR-ao@TOI9RjOBe|F^lah6 zUpX-Q95k&Y?Ekjt;pGK8m}?isuIO8+vwL&_MlOkUS!h+DDrl>!d>Lk!qioD?!NUy) zg^}d{HE;85Iox`|VAS-Avv!oOsCGAEsz+{{4J^B6dJMz|buiVNm};VT;{>L9>Ds4%Kmc4<4Q`)ORVm4)DlgkhBggq#4? zLlUW-dwAU-C&Q7xxO9kGkV2ut2T-_#MWPwYaA<0XNrD3xPdJwM7r{8i@Ss?1M22gr zr_NSOWp*2L-nxToKMFLU&5Cc>z%M5Z)(bdq@BFTMqCiw7vcL z={nMmma|7+vyA9mO=P({=zy;ZeGg(@upCjizXYWok<=2*h@foGZ_QJ<(Fz8l;HO@O zJbo-VbUc#0V5#ZXgOb#Si1m&Oc^Esa!Wsb4NpT*NM)UPQ&eK8~%U=l`;9LhqMOa7q zXXVWjS>#NUDT<~s&CdMwLAK8h5M~UoWW;b@+JaL@`t{WD9vn9%g$ z8p$&JRc~a1VKJ@^XM`FcG`GKzb2N`T&!33Av}(}0|5)2!r=TInFNLa+lXx(Mo;4X@ zh$iOt_w0(H>hSnjo2RRl4O<>i5)ZR`xxLB!xikr$^-^Da>`n#yC(!Ia`)lR`BR9MD zx)0A1d>b(^T6Kz(_xSS_WhaZasZBl>nJnd{l{8g9Gg&VG*gzHdD0R!_ydh#YfTC!& z=`=?iQjn4)@9S6EMGW(KRkH|-B-PzgxW4`CUOnWHumSRhfGZAW=|SKX;E98#uvGSs zsh~n3lzbY4yJw-cM zN%7@IXnr;#_K#l-BBc4Y-Y;o1-|4qiwlyLsR!oA>*8|qF3Vknb&gB$W+-gYl>&4^p|@X*-mT==IwW-g?+WTT#Ty{f&Bm>wSDp!aD+Ol;2l{`Entb*@x3m6+KnC9_?6#1OcFmI& zV=o~3(bvs{IrjoNcbkU(;=cKc4!Iy0JB$&Wx|Fw{%Y2|Zkl7eF zFqT#eND8>%#j)*gW@|ugRHPnv1joZ2T)-k$!biWtP-%?Aba0Y{5Ffk29Z0$1?=(JZ z{Hp|kPYUx%7=Q|^F^-4%CqjHvgKNr7eRzQ zbIMxrK!e*LwWfQ6@6dW1;_%e`wLZ5&3P_P8lu^zc>Qb61fV$|1AlgYqFpfC833 z>+S2qF~Ek7^ETy^Sfh7#BTq4IkIQ~fI>4B1jPhsGj+<&VjhPT@@~m2-FTcUt4$faV zn(QsVk3;`%HMZf-1@QcayR?p)E4+LnpQTZlx8*-^3Pz;!^7mAaRI;#g-?u=uvCpCR ztO<0^pc>+cymZoI=UKM)4Y-DtiWz#GokqAOi59Z;k)A&KwK1% zMqr^D&nj$w<4IUh$R}DDaRQTsVD5a|XW_wx-MT%u_g}}4Y6xr43D^=p1;c>ghs`IV zM$nDkOawVulqA4BNI7>VK^$|t_GfB$+0Q6-*w?P7A!vzmn_b1_&0P>Ap!M|Yy*7** z39<78+eJSDOEc(gV~g{rC}!WPp`FqKfxqlhPK#eI`9So1rtBIS{ymB`3UH0&*W0z7H-@2DgKO}?Y< z;7;+DmZ}B4l~m;wz*CX+8Mb?|J}@XhPWnW6 zjA>j_Yt_wSHuIQZQEVFmr%Qa?8ybDusa=L&@zJc;{^MMMZYbI`(G$P$&|IIY*W`)l zCT_JGy-+7lBzRoSyqNvNYOeCy+v>eeV5jBvArCDIfJ-Tlb}dkYZYA!jP{2$v*3J=*GUB(KfajQj^GpF zWzVOSo#Q|ufe^pUEV835e$Ts5T3Tv!eSW0mlhbBj!spqkdR|lIQv&u+D`Q$^{Q;rp zbYv(x4Slrkat|4G-*h-e8%wp1ik%-a6Se454%~%GFjh+1qqmrQXx2d-O1Q;-c2K3z zC^Q)s2{4OhTSC1qZHvTO7#se36eJDx^)WA6uW4Mmht_Wqf&qz-q#I(C0<48YL!q>d z*HBekisu8Byy4L|%!m5V?WTR&J>}scmt%r(+!^w6c?q?k?l1%pY8#i? z$)>0m5H*;!7^SH)9&~FCcQKfyC3OTXIn*!(fWhlfW$15rBH;qZQ~hhJ#fo1oL#v_j z;}za$C}4NPRHh^tpDtLzwpVFsN(9A-y04U*dw$@(fgX?-%vodv1gS+Hkla&#y_{4b ztr*<{Fs%UIv@9vwZO~)l3jClVCnPQ;3*VEW`&=XqKHM-04N(A^oJ6L(PuO=bO;$Xk zA#hADNcy9@I`6-yXSsm5kW}YjM)%OxRHAOBR=E7CfeELXYoJjO9(7wW>Ez2dHIqU_ zC2(vfYdqMq1@0M>kzo|*WqF>z$;q)XJri$E##$NGcdlr?TrV6u*i9tg*5fr)(v>!x zFLAnO)*{*UphDD^3k*W*E0PKoLwu&n1Q(*zHX!c(FWL{ay{C{4;ZY+#6T-$K!}YZ2 zwi9spr>`UTKZU8IwLQ#<=KPVrzS@hDRr;=AE`NjOy5n!&O7RrS-=QK*th8iLL0lTK%p#;^$C(!xHy*KMdG9JqxGZ5Hkx7lHMsWz zJA?z(9>5Cp`pY#0?C%eD83Wi`t5E5m%pZ-D)_UCs79@cNZ)Fst81+2&N()p&`d|E+ zsRbiYz&DbEpZh3;2|YC-?_H+OW4zQzkE7LNa>lo&BR>*Q7x`%R=NCL;$-kspHOdhG z$MqkiH)|AwJGxcfiAmCZ#>^1TY*BJO3C5canWiM1KsXlH|FL7f^gG1!VMnzfpESEa zKpzKU{ML`r|Jhr=BI|apdi90%Nt|YP0^265sc)uhOVT@&2eJFh6WK8ecVHign^Pwt ziU|q4I`3DECyDTU`0H*Ud->hqH8YUxW&d(mL;G86<}{X-$pmR59tG46KVYcN|NYde zifx~WQ=MRLQ!WttRHL{rx#q$2enK7MI3>XeOEKo81BB@dSJ3D^@+}5AH4-V`N_$sa&3tJw+L9Z^ zd5N`MZfQK@zB|shxiS%#fNGog>d&f9d-LcJpYHGU>uEJXhJybZW{y{_hpu|I>q6_9 zj%{^ij(^Hjmv)jLXTe)7&?4R&+9sq@S6^QyG+RsVk4oZb7lP&we#Y`^Sb;HV zLnVOxfv{$`VoGIBj@hhMQ>PnhBp*I>F#Zn{5ni9L#(WOLO+f{E+&4g|ObeeSU&R;tL1^(SSL~u+^*i5G^*6 ze%wdkCz8c}~J%nF+=6ikT>3IgV3|yTG_A;zO zzC&a0r1{1(0L1i%(O`#wk#1Lf+%B8n&*S+o-g&ftJJcnyn;7FF5Bu7C{9g1K)1DM&e|5 zh%{D%<=^6HSi$gEyZB?Zx{16ce|E1|XxxgQ$^9o3b=EUROx~m}^Ng&%6u@(hPt)k^ zblb5ENEm{eoe;L-&8T6^Pm-qQS`0eqo&>$J+OYbzimL+g@!~gk* zII^qoYt!cn5eOq<6;1W$wlJF6NmJKRQt2^c%#PBy46#zJS<=LX>m`z_kf^AmBAx2# zo7C_4suO-J7q+f`sY{=Rq3lYW8phx$0O@--J+HE;7OyblM452j7e_SPR8)x~RAXp% zY%ct)QOO~B3=DBsD|8w{&WlA3wqB{1#(%rE$LDGPjjnAZd)CpiO3_?#xbW@!?Tf0oSGJT?1i|2#6tdq1=I`?~r0(QP1HRb z^R>6V@Pn4LiCwbW%+_w|CI!`8MSut_UPZQ(nK5i8-&J=3^0+W*1AhNkVZPZ5aA@rw zoI<@&x;!Ewv=l@a$c)YjL8^4o0V5_QEJ)S(0gj^pKedlqh3D~PtjMP?AbULQoqvtX zk2WTD#xm-Vl8%~BKX{lf2+#!3DIvRb7}@w#mbmzH9-tj>!7Q^4Qi#Ic#7<$C-)SVJ zk=L~mKMxLuyuR%zeiZ+@8DsdnpJtHDA00Hu+Sb*ln?tGfK#1I2tqSo?IZVD+PBQf# zr=seam#rW>9l%OPAGv(hvS!YUyb{UY*XGGUqsX$nc@LxK75JHHdX1+ZrlXiG*kb4L2EyUH{!>!uh>9U3owNa>i(i(QnQ4VM0*KbF z(_Uo7G;Oao2pIxCd7bV2aH*|TDLX)k8xd_5Sl_XyNkYx1ovrM8T=nFF+D@EB_1;TogWU#Si>(@^PjBOQY7JI>-gDY^h?Rvfw{g{|~!7m;2Y;z)erE zz-`L3d_3V(^s}8NW}>d`x7s$lleCOy2j*7>_hze(4UofGuuS{D|Nd-9(JVST$M@ZO~GIg9X-YJ(=Bev7~N7WWG_R5b4iHZDtywHfqf04SqCQZ2~abC=WYZ`?rZPj^N~YO|=lAIDzkITt z_j&7|dSX!Y)DQV}q5x&pzRv zX8Sp*i_urjfAt<~#B9Xg$#jIInsA|Rt1W+%h@|NT_;sSuF&(qbSDQTC@L$M}$Xz!D z1-VbK&GmbdM$VDnJlAB_8=QsY4XHi0$9Nk3Q$>*Q>&C&`7wk+Bn)=cL=#IQF+3$q1dvIp+NEVTEvVr9O`D~1&7!u zWU>MgWGa^rFkd@dAu+do_>GJtGTJJZ;p&8o@U%v7i>(lT5Q|3Q#d8IC=myjX9~kh4Lp^7w>nkb{Pos0R^xAfJ`6coCvqe-1|>hT?bR0;!2X+O6{~-rn$QlYDsNPT(%SQo@+f@HX7X(R*7Y%lu*bPy-AxY=LA0Jd6pPx71RB4PO|W9V;~~>}+@truVAtw~kFV z$}dVx%sTB$-6<%PitZ_0^Tzp?K}buzQ9M3H@K?T@f;j9I&SuW@167k1@DSLeOKrp_VA(G3*?4K3sFQn1XVaUc|MuQ*F$0b;wgLhAKV5muUd9}o?Z-Yex#*) zz<%6-9NzOj{k$XzXdj}cPN~WI*JZBS-PsT=X*YF)yki(E(5RCPi0gtC#EMco!-j23z)f&*%R{bpUvk=Q z@!t13QxEg2Et6`|QObpL#mZdbu4{4w+j|)tx}Mi_;nu5!a?z1d$d7xnA#6R>MQ!j{|R8QI~T8>vkBH}UcjCaKn>Pw;g)B3M9vFo_t zfve0GOxR%#0|25=zb3azJo<)%JH(d?u>;dT#V|0oFKSl^;B;WJ-wXbg0N){sPg(?e z6R6#yLI0+e-X6;MP13|T-`YbN2`-YvjW`vKDBGF`9PdMG;#=PCIeRRA$QDc$466QJ zB|}{^9N0a?KJg{g{w9bBp8fPx}fY6>~; zq~e{>mshtb5}Z&52wW-%9#gtscpllQ_Y&V)~<}B6r)U35iE4(%p*NE(oP#Wd=W;CG15S`gbpX_%hjkIjK2`t1TI}v`+R!t}&t20W<0Qj~Bk1u(HN1gM zB;Sb&Cv7nnS2o3nVCZ;zD4&vUHVmS>r&-8@yaB>@R3dKY!i>TxF}UtD_r7_)qsguB zyz8io9dGEh+b24$JF?t?0+oOJ(;WFV`-7kGQ25?%fxh{bqM8@yhq^R}ui$y1g6(Bi znO>3N^K8ID^>}e8L|1Jztunl*j6*1~rKw*Px6Qog4gb~k$t{tR__(N~(FzQ(vUq(G zEJw-FtD$5>UnrZu*$^D6J$8%ps&B15MQ_XM{#FY%>FBp~zu$L~P!;g~I#nY1SO~(* zcF9Ic8@*NH30#=;Eo2i!rT)n9Hr4r+@ckWCj^>&g#k|g+vNNc zT)Q!~O7*+`k3HmUPIfX44h-V0`ibzd>-B;Jn;ehp#Rdzp;_`G*EmZf3WC>zYIdm*-RCHNSHTxQWSOPSMBhp6%fG>= zQ_m?jBg1Bc`OuZCvLW|XewpRW0~#t9;Q#0^r;lI2;VRh{fiqlztp;!lE{e%`jx>;Z z=~*(S86pY@8EsYv%9&(VL!NFie+}0DJ%J(+R0+rs?gZaaI)^c|Fk+_|?7dXuQf?>% zMC2f203JvNtpU9(we%vHPd-2OAO!nN9bb;ie*KH0Lem47x@|JWN_bc0=96`RjdToV*@O(tq!~Hi-jJ9mWujiiGO{(oXX3@2vbWb z+eWjHg$eH8S9~K6= zFm5Dmbu3EuG0&%2je9nyP31EGi}%t(Fi226>-mUW(RcoF8pTls11OA(bxH^o)PaZc z<5(YlQ}6%p)PdqW-?51??)#?JTQRA&s%n4ZQ1_2Q)$94dJ{+UHT<19+A1d`MO$Auo z``)Ez?`I26XL-_$Tb7yZ)klq4_#2dJHEIaFw0V<(dO*WAjBnsMc*+;`L^$N&{mWBI z3E@O;TE-{_pf%vcXWS#EpP^tc3`$WC=DJp3JK#-JK7zAhAIB#Vl@EYP9&-$Qz4-nJ zFBZlGw>7g_tMRGeUq=T`VH4c#zh2o(Oqjb6+@d~C|7)PNlOh5KoF7ZBk-U2 zM8y5|ha;e`ZA<4aGzqO9xVT(P2j7xgP@9ZpfI%v67j!oXIG&49g$iC%{_~pzbEE13vA{IFq z2u|w=;@}a86hfvN3CZ7c61VHQ!;y*5$w~%`<9%HO$+qACr0@ld4Rpt~LJp?SU?c`F zb7>v_AgBObYSdoJ02Eya!w8bSLo~8HF;aRQd&RokI@Q2sth0o@|AWbD|-9;Y_3gKC(9(u*xfoSyzLRAHV83wfnA{nr-T$e{_ysf8pzt*(A z0AU?hA3;1Xn<7QBHyR_JI5M}#R3Os=OTM7SV?Gg_IoJ`KIp&jChO8v*#rNk3GN0AF zrSY+!%>CT}7z^Ji;fGo7XX)}4%T8yGWGDCmiC9ZTFdz`or378E?!+x5NGT<<)90qhlk-B)EL&V z0M!!WRcRyz7{3IH8@{yuJaUwE`oQQ5n6Cz`6zrMlkbdVtlz@<{#c<1vDc`375;u^M zB*FW>%0O>HV7w|psXc&ssazPsJxJu(-S5y{j_oP_V^+9v(}?UvzntbyY7c9Ft>*C? z1(!s{Ld{yTaGMrPaG#5%7H-{qt04(~B{;Iuq?8oDC>lLK=Yp=|JpabAr66-Ji~*d; z$*`mm4i3kjk)&gi)cU%d2<6l^!<31jUKkWomd!Byk(h@H{pAWOS#GuL>t+M_H6Qe& zx~85Pd?y4lZ*Ts;w5AmVRzn~f4ep^B5o~%0C*ba)>k;_#af5{uHp&M8+!74Id-uy* z8esci9Sg;RfK+Ppll_B%^r%AXbW{+ov$j%?nQ#?luV*xadI+xTWN649xb5YF`UaF; zlYzqCLc5WoBBbU&i-cXI2By` zdKV3v;^l|uzhXa{lT^|1?p$qbLXfzb23dq^68@1ai@(aiAQ1N^Mn|P1rtWw zZ9_lm~d84uWzNo7Yinfq=!ZSXVbVV5WpmSEtNDO$=M-=yD&%UO^ zyjUT1^eG~LI%Be-#bxI4zT7%8wGw1u3x>R-T+8^^Jl9Jx>_0dTv?trY z>h)Ha;JlwZE6Z1qaU#8y)K_L;%uXXhw*dU#9Dw&|*$~us4e`DcT{kIp4jnWmHOOjN z_r7BK7%v-k7n>jJ{-+xs4?ll_cs+lI5&j2inwzo`ezP4;BJ&(%Pjrt^3aRD$59G7S zr;RwS)#^VL+$zu(#!l$;qo6XG6HSA?AVJ?ohE3CMc=S7#mSweiiZ1=}p1ujb^CExu zDA#s1iHo+c#L*}cwVhyh6{Kah1Rk%aS)@JD+dhuJ=aP}`FN3LF@TvKs&v zAP_(&0=EZ&6nBcpl3ssOUKG~|2Up2gET4kwhx>_Z7&0c-N(ED+Kw z@Cs5pXKl&_|2T$dbqK0RF$I~3OTXvSfEVE(SrHlvkp$ohYmo)M(h8b(yQ)LH;!!W} z!-if<#SH6xdBw!|A?JkeH7{lsr}1MB!vR&rUTy(@T~|o1B$aqfy1z=R5$mwCPRVn=*Z0x8 zf9$9{J}IhIId8wi@mJaWohIKdyp8OETZ!q{5;QZ688#q( zJ5@sdj1CR0Tma{ASzC006wktsamiJ)X4cm}-q9tUe~Bgb}IR5I8?5E+&n9i`xTv*y~8JuHpaL54-g zwBJJ$HXnMowG%}$%t&uKNN~$#NM6L%pI-Wak%~9$mW|bt3Mv9>*qc|m2Wu$GN;RvLzajg?qL#7kJw@bsNPf$y?rbNbd z)1f-RjVZST5N0Fbv2KwVVg}&%f&(D%VYfxWc-s1o%+h?j{JS=x`7R!3#8DpVqM@^M zdq#*;ftcY^5BVrtXaQ))9D1n;tj-`Q8J8ImKbU_4H7WL#)RVH@*FO%)QLG?K?8+Bg zp9K`Zk-_1{qHbkkdIUx9#i~Ece*b#+UC)#DZrVuY>AV|Sl=+K z>+E1;{*|>L@4X5!MH(6fZr2aY!iqnu6vO} z=?In|4B`(2-krPrz%z%#kLMMpFXt93ktR?`1UFNTX@>IBez_=uG}4EwnmYZ*i+o1= ziS+cIO!GTocgL@iP9uv+mE;RmB@S9yZn-Jw&yoa^qJ z^C!3$R}A>ST2;BQzO6mqczT??p_-5GOO7ZIhqSk8w2~z%f-Z+F&@D@Eq&8U4J>&tK zpxjQXPl_Srossh?>l4~f!B(m#b#D@;2Ufy}`eJk9KVvcE$WL?)pMOC19veTEh#y<0 zFn576H5(T)tC4onL(@E83>YqDx$rg_woLf%;Hv7rNs7t^dzR*90kxsrc*DP$I==EC z9paI$7jMbarxwI(zxOc=$2k=XL=C824?8EKfFR_At!?#M5%ZW4VyOK$y$<%p(qV(m zKaS|(yZ)KSItEaLtHWKrS6umf7>-Q!LASa>zTD|9*t+hQlZEMCpQEgB6avGmf0Ent z;K;^Y|BvTkNiBr~@n-_CMkj$Kh*#w2oc&Ro&fgTbHB9y2NW{gG;ppL*iTkv5~K|vhCwpH5MR8D?s>_AA0I_xFY1qNBjsD!^GG#1?H94}N}3Q%fh zc3=@vICG4UXKOg72aw$adiPw%+PnP20ICu#%ZT)of$P6<`OAvAeZ7b#7$R$8=eg5&=q;IdQm?PsxQ^@->Mn;3_J4L|M`F~gTQ!5i4D zv-Zmk#;+l5liB5Pk{QA|mM*%aEI&8>!iN}!O$z~W!;uu1O=)mFVPDOv&Gc;h;1aLm z@5g_uf_2v}8E$xzfm*kRQR(9jP%W=hbyTWG1pN!|&(#R&=?wof;(fYTFtX1{Bg zh&B1`AV;2$reVjUeQee+je{ws%l+?&QjvCXcX6Tt z>B9X|(XT>uJRf>lr6`m@+CNb=*oT-_@}CX41qK@X>L`4HokJ`@^~3%74v)(hV#UvG z5itEJZDeZt`srbYktr7$4mSs-%e~5ON#g*Z;l^ew%L9$C3KK0R9$N z$iRBo{{E(mW|exa1s&m|j<@IP?t-6nJV9eJzI!oOv- zAwTwFoBzRw14`+_7`*+1+m9nqJfZa_TCobfbRMS--$U;XhRcBpA19nQL?g`a6ZFt? zw?}1EHsnbeX)~1}5zzszg@aWoL6wxIZAueWm4c=()#FG6V#!|@993?S_%Y%uK68K$ zSb-%DCFFYz(=dUuvm}c5g?5tAZYKR`3-Qyh^o_)zm8s5Sg`)4;dzSyLeUwF+Hfh}B ztjPq4@7+r9wQHSs4jl}c8<{=CCFyrj15uKoO|%{g6#8oA(ec-XAjX#mM6 z2~|5{R@Bacspb=1GF)NBXIel{MM z2VD_8#{#8GNDwg)`_qksL=+#*N>tHjObvevfiUShoAy$mQEtibcJF<2vd$c2x-f&bPX&kDKM} z4C0>v_zsa(86TEaQz9AypJrA>QoZq=eClJbgSkg=sgDL-zr8xAy*^?ayAZvZeG9*5 z)Oh537-$w$3iUgFbEwaP-_F>0-3g;|XS_+^^834BulUnvo99r5sUSM4_r^yvMPht+ zN8BJF(hGZ9EjfMp$BS`y)vvI)_y68Da~J@rsk81)Pk!ENKMArZOd3x9&N@FFs6BV< z^h$UbRFB$kveCNL>FYb+82vratvFO?zq~}i^-b}i>+{#T$_7M_LT&o^Am1i?XQYhff5p zu3B85<|(x{$Z5!h8>&5#W`{7MSvq}_*fK*?Jri3^9g0JQ2%pysFYm!bjr=85P3g-# zth+}^Z-{O%z)?k18H*jPqW}prTbl@wem^2qcpFP$auTFy5Gs~FFNdZRx?61U`cEkR zx|;VsA|&gfJod;qI`-D^uEv)hg8a_VIts4Zaa@`;$8EF)g^316Yx4TbBwlFA4+g^s ze-FwAN2fy9W$B_tC&9JIlaL-$jeU|tUY9Br-}r}Bud;BW+_i2gQX~x-6hXR`G8D+* z%0~mh*iPO9;s`9;c9 z?5UVHeCxdx_#3W=LYJC4*J4QMzC30JMLbIduAOO##}IoC@~Jok-)Xt$ zKWgThpM+fMy|4<&3V-rT8ioD<+PNWypgPG(SVAG@teoGFw-=Ng+#vpVBdri_2M-F) zTfXI24V>IZl2bnq=VV2lMJ_jJW_aa24J}~jV^blLF4w7|b7kFmdVlZzSMhy?tIxC9 zkwFmC*1Haf%!HRz;Li?#J>`W~3oXCzO~|F=v_=UV_B&JCnec+-M6oH6eeco$`@S;I zIYq-a++52ovPosLJgtR1g%Xo8*%5M$05%hEWvT6I1~E+0Eyt#yrHQT2nH%gk)wsob zh~Lcef>SG%XHKvsi*J(RfbXgGr{3}qBBa(gOa2meX)dapj`yp}l6*lud+ZA_c#nmT zK2ZOO=B2zLXU@uS_6GQ1?aRAa(_QC-(O!`fJ5;Fz7U`&aRWLFM$ltc~zWL8K$-N;J zCu1H=QjN?{er?jzN*=|%jSsxQ5Z9M^01Gqm(x!z_!+2;YO2L}J49+1CZe^u<6`$=4 zrOlBH)_be0EZ-&H5sI-5VXK)Mg_lx_zj%tEI2C zSs4kHuV2(HXH6Tx@RrpDgaqITIOormejjZObz9B_uB8{+y1V|CMc?Cet zgn`NGAOwWnl0HHWh0aB&tfIPm3~|$k-kc|hBeV5Ft%!WRJ}Szxz2vIKG+dGMDea{@ zo5o&93W#k(F-8`zf!&G_jkwuy@7&Snje8Ndlz?btOWsrQXHJgzb@@^K;rEMD>)8Q* zgir1jw=uZj0-%$?)42nKLBPM?^Op9>E}E&TD75H%S46@EmE$V%#&bO+Qa>dN;5lLx zv^`3|2o4ZLkrB&^=?IiJ)Fm={j*V)@2Oj6QH`e8i5PLW0Uk3zRQ-0{i=5I4rD13{N zVW$Q1fDmkEG!hHHBZWH(1Gi*pFcU#mpX zQ!I#SBIY>LN)s=1$q5i&64Cj~Tl1F1Of?p-F1h=5r6nXs$8O{EUj&EJl<6TKNBLU6dEIGb)hGARnu0X>W(G5pF@QQf!CnRhN{zKI_S^#AjoM(lMY z`gmo^7ny#vT#SMTl(fT1S+IR?C-5SnEu1~?`J{G#{44D8FSJD0MGL>yXjWxg0cAi- zo*l?W2;$!#1P^;&N%k+pC8D&>b?ihj#0Y?dQ>%!veg;@w39IE%a$uQX!ggDRg^fdm zX`hd1EKIrXm;2SUM<0ht$ps38^B#A&l@Ani3DU2O*_nM?CoJ0R0a0+3l7S%b8RO0F zjSM;omS9<(>=!Ll(APT3btD_EWMxIg0sgoDyO_}*-ujR$qJ+ml)4#j-+0N5!<&T-m zoz#nb>jhQmSTT7F21*X$Ocx4#`s zNGqr(RLik)tC9g?W~o65awN)ZwE)lj8E5Ch5&r)gp{#U zC$s%2XsB&;%GM-Q71oeEL<3!!^%UJKrBQb{T5=&VINAwVY=^T@7N3GZ#2L5p7?C)% zR8$$Ew>Z{ZB}%a2ZTt|b_m9UO`%AibNxVAHa7OjisIc!joz%fVB-Y{{a1;720Z|_) z9a11wQ9sZtq7T~*`w2j0YXIn@7xy~Us@S-RWDf{7*gb+^ubgZkiAuC;R=vT|UO_Oy zb(qxkEdcJ-k8RX@6piupBLOlQQY-%6u(b5dXZkm+jI=^3bb583%hDrYVdi3Oc!J^; zodz~*=JjFtOMO7w5skFxatax~ORu8{1En0PfC4L|qU9rt&o;tE15-Q{CC6{a^yht( zq)AFejWq@F(H@GJeGlW;)sS#C7`$6n&f1H1tEje>ZhV>a;ny9(&_cBrN1t0RxsD>2 zIC^`&Rn{2V>BGHA6L?#ixz-qh$&NkZ5MF4@aOZGGHUMxFq12 zmd*W0Hdb+Se){c$4r0N4(vQ#_I6EuThrh*RY7ice>L(8mhwFqS5a%QZvmK1-K;=j&QEbUC_(TduB%AY^Haq3CmT-PIiSw5JcclZF~H+lkD)n2MVYNJK{o$LuzZ^(;+#PKGbC8= zv)Pe9o1gESzw+LG=P~9J-j>_~*JYvHPCgG?MhKl_UO}Elv+6HDg&@4XY5z|A0LAN{ zTaMyfr-ZPrVu(kbnm)hlhnM!=c(QQOcJ{wcE%0^Hx+A;DX)2B6qi3;nrw=D~l*$}W z8$?9$lx$a)!dw3zuHLe(&8};^4(QgimnlAbJ3g!?&KVi)#SIFw>0USrI(C7X*lbefUj8^|x(hFWzKTtS!2EiS?We1#x^vwgxtE#` zEEd;FnF?ZtPb0W^!YCzWAvp5|)l2m0*i{MNmP-2*4ytoZdQmK6_FM&O zLjCC$-%q_(q%2Iz2&T$aRk94-S@cOTaPynoASazu##0crvL8$@-yLMVI?M z%_8O9Pp)69Z(dNOHCf-xxp!b}3xfXqrcM3!O%eD-0^Yhn)UN(Uh+nlD+L!#F#=kuS zS%I|q@GyR_j~_X%+(Xccrqy`O=qKj5&-O;>hcgk7vJD?`IESiVw<&OvW{eJSin zht<>e{~yg|4le@ucB6XK8R`D>-$pskxL zWx4nn!iu@m0K;(-Hh4z^-7R(K+2HMVl9 zvEt=`h;t8fT__oK#g-(aPbt1n6Ov)Me1tV((+OFoO(_ag8sHE1#)Q0`*(B0f* zZ6L3k#dmZcBCIqeN=?u~L85)USVnv#@`8zrS+J_TbPwMsuCbS+l3Wp!s~4~L8{!f2m?lXShxhU@K;qcY5sH!lAua(14Hq^$Za}@^Ca%E8UWfT2 zI|{%K5`G*$A%#}Y7Z9S4(D1cp>Sq{@y7(fBXNrYZ`K^wbvE-6$Z=@5&nga zG>Y`sA|xi0mUGlfRQ8PJNAm4$wst$z3jsc|&U)95dV!)xk>iEI++Ufc0tHL{1o^vV zgedO!UO#CNg-j4(#P?7Kh%+ExWKrYUtA{aov3vg+wLgY?{MTT`V-nGSLPvi1l5`&1 z^M*+Y523xy3{Imj2W%XTqYB{Xb0{(Rtd)TjH#7IaVWq(24t>-D&zK%eRHO9;q+Vy- zd$)RUhfe!k4?9L+S?3Hz8&pzn@U9ZiRLLvrF@lkf+P-1yg18PB`iw!8DLe(NEy}=A zd3uPu;SV8lQb!8-e(+`65c@4B2Q0g8inq15Cx8B2+_`A?Sx{)>_l{NWhWzxclzdHM>LJWyIP=8qw7aFK*17OJc)J!gAyc4`{I}-j$vhKO?qQ z%sKk(VlAfKhjZigs?haICsns{Hn~ZB#tD;2C_|Y`JYe~IAFi`ZusLG!OUvlmReoFa zyqH)c)WT5~Fqk303BdBG6^6J1LH8zT!cp>YRN@zKa}<=6I>2@OgLTqpu^l9}p|Ig| zCSVSm1j{UpF1AQ#xZjE-)Wa0RrHWCY1I}~2&9>wVj1eY80 z16}OEsHMBe0d>jUYeqXZcT^le15Z}-vIXCd@)<`mX!Etiz-TTfLCEdVQWFv;H1bJ# zXbhxmGc1Z3OwG_6i=}d@e>y}H-*8)E`YA{iKBUwhE?CB>!gYnjJ!Fp7UUh}ge0?VS zwc*!B7>QGTLMKn$2nUcB2D3?r_ce2X+U6{x6lUc;R)9MDOA42F$H(O`Pyn@R%tNRbJ?H)kAG5j z<%<|iYPVXAc(a`gW%YSgTtJ@ zid1H-&#FYQUQirlL|d%e+J1i*6&-zLyK+|@*rh2f3Gm**t|+O-Lxhf}2k1c5&bMge zYwFL>7lXgDpIWkAtuvg%mK#25US9}ZMM@4i{jp4KktO%)^T|PGFRKVTFVafek@Hfk zpEFU^HXRW{F420H0JDyWQSkkw>`e}QQ5-SuehfgRYH1HmHo_|rb8q^JIP>F!Yg3O|f|Co~1l34RAk6NIEGWJfMlJ>g;`7Ya z!z^yXnrQK=-clAY>r+Q9;Hl^Eo5Y^e!6UM{hiDd{MFdD_c8K}$ZL|I#wYD03zxnw@ zOHUh66W+jpZ6&9QP@}^ijpk5@WExzO*WfhQc!`=p$<6tq^}G94Bin1PATakN<%n z$7-Z44__ChGi+&Ol(gUk+|7V;Hu&msI8QtGg35526dUN5YIKC>`zU043y92l2fKrP zaxfAQmqOsM18x){uNozAdqs2+T4$msgZ>%k_A_(IQ(88o=ncE5ykw(;!>PG~H{3m~ z(kLgRLS|&~gNY5Qw1e@mT)ToHB)9z|Nq7cAO)zvX$vfyIrA-*NZ7uM5JZxMuQ~3BW zm~6{ojk%)q@>Fv8ELEYf=Wpg`%79P8BU~rdx1e4!94=gmA#_@J2{FX}un0tKaYS(h zqz3=|rb>^Wm9L*ZA64^3H7za0eTvRwn8uXLN2Hb!mqSKL2pNMzL7{RTa^n8$Jt63Q zc2oVcY_;sC?V0OYOUoVIUf<(4*NIiTYVGn}-}$@QCvCM5hl@|$7twa-SwB#=h~@5b z`=lXsv_OSFYM5`IzPQ$bO4H5`MY@K<;Kty33wT{;n89erl~cl!B#?cv$~GZU^Y;}vYWR?I0y zqZ#jzQJK062I>X*9pvDb&Z|cK_4BdMxFH1{>5a)>l>6wDx#@^79_jE8JJu+4-&%Y9 z;9Xuo1UvE@Urd!s+z(TV<-U==O5Ok2hz_Ip+M&c=av-4vzBjsngF#jL>D6;0k4aC7PCW1Vy!FVr_}Juc^_Z7KH1P})Oh?= zCVE8^{1;;?$-gDvogq{C=yUsv;hScrROqPe(kB)BPK`0igR@~*u9`S^=NF(T@-_11@{j8UA znInF$48Y#LV|)?S)!{1?f@z>V-=amrr#5G-|D+}pqV>Z5wnLo#ndmaUZPQOnB}`o4 zGB`b$A-tFLi&~$MMTu(l$ng`3;R%!QuAfVZA_H{vG$ z&QjHElhh4ye5a!ugsy&nxKxXwC4T%Av(Axa$6umdi)nO$q!V}M7*QgD->B>lbDBGQ z6eQHMi{ihEUYPt{F)0H+9U)c$UU@K%4wqf2j%S8zd+TyL@CnSO=h|WZ5UPi-0FnsatQXM*DI0J^oaH*+cz6@b=r6A$(-ax79B*zJ*bn{OoH^esWZw z%IS}p74NOT#pEkitK~kl(KB`elD8}wU!2SzT3hk@O^4P0ch~OjsFDqHg8>1~=lZPHo6Xyd75iZQ;#@VbTY6U%3tQ`Kk%n9#Aivw9{H0dlA#x*_SM+tM&9?P;7X z`1aE?`R=1;L;ulSYPF?0<*4f<#bl8exXWo33JUvm0;y*6KAgQ)X*Wp!=D*67_?f68 zG+d8*U9E*VI-)Qk>C=h+u-QfmFDv+sqD3A?r_cz`kV#C%*eP55_-zlFG3?-{2M+>4 zLauFXeb6A(lE@~jF{!{t23y`9yowkQ5zsW`&1{jwAX4O5 zwn>+|o&4*U8i#rvr_|{SFSjilW2)n>o` z__J1_I7p>A;tN!rX?FrgaW57HK1ST3tE-@2WQcoLeSUm(N>_!6E_<@zl>|SA$78e>bYC#$Wo;j z60JPR25+9$$D*;2c0!npazI@nm>J}0E=KO91(}Ji#g10^MgMcyIvU#lIfQgS!SnJh zpBrc+@=lil@t4s`=d-=PSv|3gO0W1XrGei_oaKl2uFoZk7I=;p;x6t+Ki3rgRQ!46 zWbcdUus=3Hnj?6Jgz&U=jY&o ztRo@@YFUTkZB5@H2(#dhoUPDy&;0|iZbx)Grk^T@OL)$BJ5834?CNOjjly3g=O^8% zNSV$m)v@$bSu}T1{%To>Rmy&XxX?oGq{pVzQ@cosy*DkJwr115Xzn2*kfN_sN+wS} zv>pU)l}tKe0-lq5VOs>r^%Lc$6Z@k zGVRGfrdYJWf4uwaUH$L=sWDL%^e6b2xk$3*yug3(ws>asYJ&L0!uuOYk@ z1CZ_m1Bp9Ft){ulPFDkRX|r<$L_a`&ny$YuWm=qNOMD+}!>lxZU-=?tPGtwy2q2}~ z5Sg;sSFvw?JWz#L@UMF2XYaGTsvpw(QK+_{Zydn<_0cj~GO?u)2S3Ep6){s!sv`My zw!xUe75n8g1CSBD$nNg0PQutR`6Ch;x=d4h_A$-VOGn@uNTUu3_h>Lm#M-|&f7AVf zh4^?&7V<)JgNFLeI-nEqk#HupY-IHc6<0AA^e6nqs)Y-Z<5<_JIXxlD&sn=6I)LK0 zrTG0Ng-JrGRWAGK6PFMBpl#Ge8%zZ{%;EGm86%YRPV_N$-0N(*W_D)%3}*5GR8dp- z=^JkC7ZG*JhIAmkw-xXD>{C%=j8B<*W6p)C%wZ&C{6Oy`x_3@FNS9sJ`~H6}gssMt zRb#FGtnu~V8#+^|R#WEoh>w;lzdtb&y*sU@AN*tDq$^ODXm{14r^{NejBt8b?DXRU z_ffTSw97x_m@mnzi1Qs`4jSSb^~*iHrjIK4dOLSj7sQ|a{0&R0YXT+v5$ccJ2<890 z>Q$`zls@58qdo_=5&PJmtW=h2&x=SxAXORU7!g&c(byBn#U<7K>#(d`1T7#<>8em2CYjf! zK+%%$2OsMra*`ak&(E%(4vb9nO&exLOg>u9YJU-L>`q9w1wS(L>u@p0#g=V%T*cCuP z^ao;jZiEs($|L;9qBdf9L)K3KmTV>r{(cc`-fO@6Yo{ojD$*7*g3a^8R;Hv3fN~+3 zOpp{s2=S?XGYCjTEi9uwUWw60BJGVOuoXY>3=Ucee>wK~A@-4g{zI5?zlllsLO0N9 zu0L={ULPjyRJ zk!_H+2q(?FD;-&84L;&q&z-1tm8+tdgl_?7m#`z7&s9OTf|hR|Tct(F*O-;cKRIrgeEog+!XogFC|5D3=2e7CZ42eG zIT4~f-9jaAWL&m1_TRKo(<;k7nuoi@^GOj~hBJrpzVDu@KgUZ&C%)E zUfEp7d(!P-KYp4EZAa!vgL*5H0S33cwXc540Udam?5J3jwz)u3KgoOi4M-p7JsE{B zp86Ph-4BP>;ipY!*5{S;+8Hp4ds=+l&!oGrGc~EGJ6Z|aY={BX0;8X*R-%)JE z>$SJuy?B155|so<2w=B>*qHATUjn}()S#nwBKLZp35C$>C%F{j6H?5iQsdw3{Srqs9VuUrFUH|G4ovH9zGW}&BJ^g9QD87kUbVX^NMfZB73Vm zYme~>b1Pw(X-@c%> z5mkx19AL&3xr75lalBY@?1oEGSIKPhqldp|xL_Lxw(Ot|hOU;{JzH$r1A<+fqwv<4 zKNp*mnPoXO2UCFr==xVKbbAoZc6kQ5&Li}aLwcG{?MNaHNeJAL{fi@26a3fj=_FJr zl5#ec;)f#a`A`lm!xa_9Qy}u>#XqZ!s#sbLfIAM#7S3`4n4HNaSRDSe^~d==PDyu^ zVA1H&_;fQN4e7y9yGsOZd}+ILPUVE@i{n>xD5w2?pqAP1V!NoClI=afi4i&|0K~1< zos1+-e1c&@F4$BT7M3i7jr#}GcaZY-?TET-OvxvkcR9j;2%QO$IDuMhZ^|VXI|a_) z^=LgoX^$ZJtkJe3TAm0jJDkzq_!OZE(=B+vzB3DpsNq>w>b~|LL3@tyaTOJ%TZPio zVoF2%LzAioAXXI9&e3fw4*EQQ%Q-8`P|krbL?_DqtQ$hp5>M-Bdgdlu)_c?A9guKm zsdL$c@aN0uz=cgwi9Kpkj*bFsSfask(33p{F6o}b;f$%hGvISbY@w3(%a7C4N&Hdb zQSdDOgL1aG&Afcs&N%u7q+0E`C=0fg4Q!P3{ns3M&AdZ7Ov`3E%kHo`zJ8ae{_y^0 zEu+BqQGpk%)ImZ*@kKUW%m1Ma=|?hV`g8wSfiqJkI_fDPMnc9#M`ceVW*-blDwcKU zPi|+*OnLxlJK3WYv(e7h9i`cs%jPci7?zgzvPAH#e=WlOF|aXxe#8VIg6Z6NynwP#qIMEvv6J^yL!M-!4#b+zyD2;*W3DrI_69dRt`&=rFcYA4d|*La`4)!U|@OK8UDJ+poz zgr5GEWT@AAK(!wd+X#Tl8r|ho!%u}1!s4vY>m|Zbt$3L9AWACB;ZH-bBH2@68Xxn5 z2u}LFo#kcqh%SZ^i}b}HH5#KT6Pr@yOxZF8$Gr+5m!u~YT-MwCnc^}a z5-}ju1SeOhMV4AJ8fU6H8N+V>Jdn18OeJFk>+;r^B!;26=>W~#9mcj^u;QHM!wC(beBX``jpWVDV*q^yy zPsw^u0jVgS4@3AzH20$m9l4@S2Kb1YgL)apw(K6`y*}ZbUi)27DIk6#Zs(kp-=$}Q z4Js5Fe9~a)DRHRi#T#~1b6AsxG(YwpoT=o>{;D!ToB2e5AD;j5RqJin_n_0tD;?;7 zTl_%Nd09~e1|?t>j-__&dFZIUnJTusC{cXy>qY$X-_L7_+IHV$%Vts@8(Jnv)@Lj-y7wk~ODR9=G?PAR zxa<^<$GeI>%VJ}035bzc)*ed=04)PGW>odqfcqkWdbOwlFLAh(z zt|6ra^l#2IJ_RH!1MCv~nB6 zwh`2>KT$kosh~Qn!(SuF3v}SQ)m`jFGW`{V0KKRH;=H=9P7sZGq~#XmSmHqeF|rQ5 z((_*L6j$e&yffL?Rh(A5Y_HH9fcLc^09=JBuHQmPbvEo;`mNuZ2(Fo;_mcYUUIO&5 zxfEKcSb8?0hGCYBsPo)aqhMnSSAyKDbUb4HosIO$e}-gFB-F&n6MmhOFx$mYrM_eE z81Bo=rFho?>R5Y?e!1{fC-zdyrNy4-odc7j&%85Z_)dQ&or92_rEfuUGgLnQe@CZE znyv4X=X4ZkyHSv)u;z3!f2%rn->Qs@(&abR_>HewdGT|X2jJg(%Q1%ZyMOQ|8%C)(t~`|9WlVh@<$7RE&O5{3>4N%Q z@lkkg_BLKpnvK#?E7K4+DE!OMQm0Thw(dk|AKivkC5#dYlYzSs~e1w}C*P zt|}-t)G_u>!qyFBsj)58S`(5hDQX^teXFI`gwy-W%2bn0__kMzZIExeLz9~$CGpuJ z=LuN$J@Py>GZ`feHV&61pi&Bu6I;xqJ!dEqClQVbR??V7r`h$fq)o;mhR@b7%2E{e zC>o)~+5q%;8&33G5{HRbkSLIZ^lA61xTsNP(2E60kR9LsqZIGIPrny_EM6clpqj~@SOyaec38phQuD1*!1p^lcFD$?wkR*Uv6VxWA z9;%I=-L?+fIRc{=;kDj3xM0ZE@4Lsxy4$`dvVL>;+Y8Llpc&>7L&dv=8qD*jg~e9} z^c5^d3#;6OA3%4hxCb8P%SLo$G|<7PnF;=YgDP>lToMrPRU*zeVg5F)O?0eiGok8x zs2NJkr~HjMpV~;pZAI5fWH)UXSB3~KxCZC_D^}6dut?L-@I7HTlbgF!7rhTkF0UP1 zP8j3netTx=fN3xAuH&X|HTsK84T~%>oU=5kGs-QjMW@nUe=UEcd)mgnLMzf~coSyw ztm^C)n)HzNE|M=^Vs`4PY$4q;%ASkgB)eGB4~TupNpsdgU8fm~1i~|P$GT;#9MMqI zisTDtJ}B95>pyv8o2jh|_}FiPs2^gs;RCG}xQG9$qwRsu$NXCxsNrzvuJwLtwVG^& zHI8P=_0n_3Jrs3PyM-lsSsA=5z#85)6n7x-!_V-)H5gV$Q--u={Vtj?VfuLcR+GFx zQPm8dW=>zjL#oczRSa(2R_A2Pj%dk1@}K)Z8YO%9{XMC~&C#zPoa*93o}NrTK+2X1 zM0gnv(O_mC1wCd0d6%A^sif{7tKS~s-PXMa$%D_qpxNxZiMQtoc~)cyOA{#Q{=M`& z(nzolywG*JW?}J)3EoTN`Av^4a#C_wB7CR7I?9`tdoigTq}MTu3m@>2qutu{Yv-#! z`*uP}Y&sf8>tBJn;?;$PaQhi)n*}1&$-g70`cEWr0>bRyClX(d@@yHX$=Xs3g z9&MQ}l#&cns=_ifLSZiAgJauZ2Gh)b$Q~fKEJ4L98H7`V6>+eqV3?i)D~6!BqN~=+ z+C%PpjSU38drjCyJ4<$DMp?ON*69fQY7uH2)OH=3G`T*c`&*oFQ+be1fj)z=cV&rArq9%j^@;~xk^Ehavya4Mf`k`_ zbmIk;Q&OJ(g6_cCmofNPOrTZCkth2TFk%JfMv|?z%hX_i?A60plq;eU9 zvvxF=IzL2Kq4i%$mc7>Zu(H2gLF@2QD1_jEBZ06B zjGPGe>!&mSF21;Ark3+%e5Xu6UpRuJjQ7op(rgQb4H+ePcjTD1 zahILiO9v0EOfxUFptsD!_sj^c$shP>id$B0%r$=kN9QX3^J2OEDZL>1uzfWD#f&~$ znptMID7)C`nf{ktovovPSN(B)10&=^GP9z^q<$39_w2oXp>nQcCjBBs^+=7&%=Z-H z(8GDZh$@jRXD@B95UL~=48|tisWwvY#qV|s9N0X^`!L7zJh%+FHz7QWl@Vmvuz25N zb7L)7$aZ%)>1I4GWsfa+;8uvqwPP5A1JrGzqG^ipM;VI-fxr}58Woe9<>v(x!iTGm zrzN3Uqw2LXZvvqfBB@2FVqCEE?Dzd6E{oOiQ{;1cYuDEFR3Emnv*OXYcq~^RvIczY zUN@}geX&caXR~Yfz?PsiLROk(di%dk`=+$Hn=8*8wd_(h(3sXQpSLsw)5e#>P%;+W zs(wEGb8TF{M~?_gNux~x-(}&?Y?cGF`q&O0)Tr{IVJwNTJ21IO+6o^^MaJRrV!H8o zLePO|#FZRQ{pH!MuvVVLr^$gWnzgt>{AsPau&K4vIyy4aRTCp>cdMkgO^_vYZWlK@M%cQ69l{!!deeID1MNRtY26 z+YONbY=$%P1Jk2^QRKGg%TZ|SS#=Yu=sKJf3~Ad*)AF%+mw1x%G(Ht8r?^I)5&{7t zU2oc&JOEag{KbDeO^jyPyqAtg1&(-E`EJs_A$L zvoX`Ipk$6dg9yuqA2$Dz<(qy2Bj^5adxnp5Ow$4K5!vHXzC~!Fr$0*|j1*j180;$F z93h%r;x9d{)O}|&X7envw8wrzB}9&&Sj;}baTYyfeo$X>Ua=vh@~%~U=}2`MEsRqz z>SThQ>l!1d6Kj`y8>O6V@f48O7M{KRTo&&9p=I-m7scrdn_pn9gHGONif{A_J_k8u zpNNFC+#dJZG{8CrNIJOKO(^(COt&$&X;dw1&cD8P&beRz-aXTG{=J2sWHnUGlEp}? z$+ahCWbGwg$o(_A;cm5v+$*De1R^1lI+7XqvE*C&hSZOU*B-^xw30H!}C3e!Z%x0_>jT7pV@e0gOd6y4Cd<%+hoeg>xX;_g^IGdeZbFQ`(u+t3W#;|Q3b(YvNR;Vj*|X%n5-<-TqL$KJ z2WLHNhaDM!;d8(ug#b*J53z@5H>zM^I@1Gavxj1ai>Q3tPIQV&ZmHq0HfVBCK9~p! zr0qmSOb4>!`ZHG+J@`J(ha%9{=i3BQ4Tbz{^cy$S1MEMGe?dI%o>%=^^ znGTn#q&Ep8X!`SW|8Gu70~kK+DnOh#RjBVgz}WBUr~h)bp_)G9Ew|K=Y!Gu;@^;DQ zZvGe=zJJfMpm_8KiIBC7^Dnyl@f5ART7a=i0o$d%TwggBU0yo^s8SuyaYTR$MF@KAOCzSSG_?I5|uxK+eb(KI5%i% z@!i+aPL*$m?wKu$i{@+dWpfD$VRn|V;;3VBwLO*0k54RDucOWfXX7B3R7xB_$;EIv zXFyN@f5ZsV+U~y!EsE!Lq!Xwfw1SYnSwLrBQhHJSp6DNia^@HkuYn-Oflu8{B=han z&;`(_^4vWPWRpO`cTrE8&OSim2{1*jOqXWNK-;(0ougK|dkbbv&;+E;Mka!Oi=V^P zSGBY2OR2WeCp%qxB^FOwk2&RoMl2>+I36a$RU8VJ_vY+n@m;Sqw`B7{bmHIH!|}b; z#S>nw47gdF9-u7T(Osd)MhI?^lx<^4j1Gl=H=6Mz-2UEDG249s&@$?b(o*Sr=j3?G zBCObZi5#YSb5WbjdQy3Yfe}r;qD=AF4Vd%@|1&PB1~#6we)PJnfODo~jLR#HN}_QR zf(^PUfiaRCNo5Hb=67)k2tA_Ctca}4v2e1&+{`k`fv7Mje;O{e$s%2AiPrfw>GpIqb_wzJy0(sT>a4(HX5158o$jJl0^r~~?L)z4w%g&>`?oO<{CNELHm9AVV~JI9!0g71CBnrolU>Z zN}aTIa>K8=zLJ)EBH^gZSg)?6odtDcWV_2#p%-KAug~Wv(T9>L1H;)bUNgH`*~GQY z>G3XRpZyTv?{q94n!kl4a+~Hb3F;Tady4Q8R^N>DY#735nHuKi=YDwi9j~y@5%949 zVTbbe7rJZD;|D%VP=!Bhs{dbigd|$GZQzVn7}k;pPyaa9p9`ps zU+GRutyImEx7SMzyW_f$y*9W;`9m#*Io5heQv{Xumaof?7OnVaqu%|;!G%a2GmR|9 z5(7hqhz1S2vqeY-I|(K1fWFwLY2_lW956@nJyaA1=F6hR;w8o=j={AtU2VRj2oOiH zX-$vKg$o``F2h-8O8m398`GXS#hO`@G`pF)5Dp;A6T2OV10C2&vTyeJ3ouENeN|Hetw8 z&2B)t{Y6w92hT?>Nh4A&nF!$W9l34WQ&2f3!3+*(o*A3m=bQ@Suv-XNa^Ad3aOYsr zL4?nldqBzXo@6Am?p(M#7!`kvY7(Yrw`Ys?oHq8g*6A~y2T7RZM)Ax`{-sA5^#QS@ zCYm=yC#<|-3vWdSawfdfP4|uYL@=@_y>sL;RlFzVT#~pvt)aG_%UZ8cr|6qvE8XRL zbV&s7*-IovS2w~fX~rfMVEYxfT-xcdvrk4m>vwGgXS=pm_X?UOjNr229*$B5dT^OL z;t;YlyB{36d4)-^>56$n-8YkiM?U_>Y3zCI1HoA5%)l&MmNXq+0wN^{nhjbx98TP^ z)lRQxwXe0lsEy`gH5vuJS2~8!)?8+lElXoy+}%E%=H2IUtD@!q{MLWZ$GhX04j5d= z_Nhig4)B(Z^6CtEOOd#JTz5*jFK7R}atUo35hz6+T=}*%^HJ1Zc2!iQ;5CW@`MouW zjIvjYMKYKd1AC~M-qsyOD(={O!6*XvQ2cQ1Uq??-O&$@^btyqj>Dx)Kgv=ZLnLhJ@ ziI+EfB!KYcc!Xr1IWv_t4X<@7TgkjxZ8zHcSv&xi6D?HHRlQG=P7G4#@`LsjJG((9 zTWhuVN>Aq{9XBgaOx5=%s4S0=$C>=3%_eF+8k!|7p^SA`zWRuPB#kG+M55hYytC!L zuy=H@ZB%p)vZ(=iTqJ?PjKR*69x`O7^@EV zAWsFbmhkh_GWj(Xer-=+BJ3Fq3Hr!y?ss=Y3iS^U@~EtblEMo9Q(rC~LYAts<2kyW zI7+^$C4XuI(&=C#8%y>R9Ywn51RKni=rh@YEZ)y1swKH>RwS3V$t7l|VTFp*NIDhQDO%H{t%7{A4E{q{8*-F;3bK+uSHDxUiI1cz2@ zU7nKg(9ZarNlhx-3f9@WE(r1gTfQx;URGqygbwVE0H;$ot$Z#?Ba)Gsz~R+ zHnVAIu;i~*=%mCrd+^w#(xodfy&&bMbRfmLZ}`P6eI9E!A>&=4^4n-Ql|+jV4-$!c zeWQ1Lt(zrzJ?8Ytv#aH|BpPs)wxzpeJ6^f)=L=dMchw)z{*(Wk4>o}8ddOCng)4x- zRUTAx%wFl0`Y&3oZ|7@OtWRYwx7K@>jierh@nU+LTJoChE!&X{qy{=|&{~JTKkn?l z!KgKxr#!cuGZ%5hwV&YxWo*R@k2@}eMz~}@t?ab{g_&h^a!YN7mas#DfLnv(=Uu7c(gZ=So7dAEp#peIAN_TBRsH1YBM2MH~Nhvu=A z-Pp7;h!52?4JqYCGy5^g2%5RL#aKJXkoMj67~{~Dmi&UQpndG_1;s~0cyIrxSBV_vjgbrDD2mI(Y+YS&<1L*+)Jn09JjJkmSG zz>u$ZfR4XkY8`^J63=obNZ7TJL%)jqM+t9)`cyLvDy8=gjUv@z?g(>Sf^iGLYVHE` z0Eil5+^wyO{r7W`G`B*pw-mAj)bKcP+!%(n3#o(;Ps$2HjabOXRZ~_UgK7I~X zJF7_j(qkg1jwwb=)(j8~2}Au-l5mAPYm*_i(Hw3WT*4qZW)wgUMU)KaB{Q3w>m-gc z;Zktz@}7Mu5DfpxrltktU+8ZAV>K(vL26Wqi??4W%Gz5wTsM`+5*4F_ZoMght>F$U zz1&K+UY64L)7`$3L~1F#s>sa!dON9F7zY=H`4_S7)l#8xZJ1lJz9g=2?)VCD|D7V!INY2(GLA*3#Wt3W>mhB&`Kb6E=C z2GOTK6Sso^0Zs>}>{v6e*O3yHJs#I8SSv&6#o@IpBN0`Ks0?bkA0G$~lun0x-@DYe z>ly5qX|3tlaWpfnS$#hn z1$^0aNL8&k_@lROfXZGUt|O9tCRt9{QthJSuIzW>q5r5N9n4qQQ(+8MT~Ebq$=q$_ zcS2s$ucdp|FyE*o+{iVsQGNejf3jT-T3bTBg@u{5(Q+Z$ z)x~$6DfHruT*>~Kq!&9?Dvl?*Vc9(%yzQR0SRtJBo$}U&{vMSm)0JMj7DMTA>cX1a%x?q{ zNlB+p!TALNOgZChEsGK`;hj(Pqz(+hGQv9WW8PJU$I?eXUbw5MM_86XYP1a%6sH8T z*~c0*TX}li=n-@JM0}vF_U|+XaJ4(zr*#DeMItLd_I@W2Nb;{UPLteUXHruG7?hR; z%1s_g1VJjcB$8MF%h0K<%Cdc)#0FB}(<-Tg&oXZw$3Tuju8&S?@JPL96m#|rN z6y;n3j1kf=#Op>nPGxO|y1h02Ax=44JmTR(zsuP?V-#lDZV7>y7w_n4>lrzeJ)?Oj zeUYv$hcx0E0T{BSzbAiA-GU5l{4)~L6S@32WM1bwA9Xt%VNfO9qmOD^;nuNyt@8C! z_l;i5MV)pRtA#*Q3?1e}Q=YV{6zfIlRlSl*39>-(mf+1ywOYdgR4@!=!NT}dBzm48Y^g~@WB^J)>5mb zN7qi@9YN3axOVVyE!FVL$IDJTEx{sj{|pIX-W`sc6)}H?8BKMhnc@WLKMFxMiSkg7 z)C2L&R~diS=ZZG#P0Xpa!~79tx?w^k8m;_nV^c$;l!qawuyxRue7F8S^PZgxc~nnx zEa^;`m|S;G7=iXHoF*UcNEvmbniaCPPTv`%h9xi?cfwIZOZ>5BPh>ECTgcd0-mB+q z!e^H|YqaaGqU)=B@8hyII4}z91^TxczzrB?DSAiV#k%qZadH7=ba#~cMtS!q>4Z8P zLD~nTnl$c0mD|7&Tw3J3251dQ==6CEuaEr5Pk9HM=dRKTqGlAl{_FF(=s@BC6aS7K zjvp#RPUDVh0Zst5$^tCq?i7zcwJLc|qS*!(A=CBy(&DV|msRKTBcqjyZ6*93Hu344eIObW3UdeH`Aji>z#e1|At8kTHjRp=!?!X;h2^9vnIjEE8 zaawywYFE5Uy&T(`Rc-wxDRI9g(!ZuE#A^~#u`KYw6$#Ar3NiIAehBpft1@)a60YA= ze74j?6?!M5ZAy6bReh&zQLfMaL-qgT>aF{t>>9Rhy1RQ2q#LAT=$3Bj2I-O<8kAPL zyE`R^?rsq2?(TZW`=9r`p3gA9W6fH7Z2L%X7+(K(9ob{!gWvL_`_+;_o@>q+Rpw#! zgWkKvZSZT$1{>i0Vek}^GB})Tvi!s2?Z5ZaOGam<`t7Lt&P1BdK#R_FEmRie7P^yv zB2+o^b$anbh(Ay2k&&KKWjeGn)n_fnv4}~fdlyFmo|12GgVH848=qe(oq6!{3HGs^ zY11Q}tJ3w{ zp=AG0gY7T4HTS|32Nl&M5r~dilO6lw6qL8>QPNNC#a~sc%=S-wJFfigO$o#9vGY^L zn;uCv_<4&P&nw^yNqWlB)Y1nWB!r9^_V`vRsyR(!TbycWoj4gyuuX|9d?h&hI|({) zi27<08N(RA>u0dyz4=bx5mM}`=owe71cq>5c{dU3ulpvXeyCfN9COREC$r1~@LK3a z-q5T*T$;6vl2bdcIk++Q&i-I? zgO3Rd`YEiIbxCGf8VwPpmk)BK#f~Sql`rb^3L?$d|Er-?zKQgDjbaBC0mGzQtH9dQ zC-QozywYTk`B4-pA5$xrV3xI4H_gV4M-#ftQBR5(ghAT8!@IZtfM7#S`)ANg2aOT;;Z`SAzYXcnVij^^)Y1d27?)x-46_j7;6nxJ}p z5Ny%9;u|owIOz11Nama%wJcO$`wHR7m9T#l-KG>Dl-!NcDv}`W`I*hQ%vqzauTPKA z7$l|T9TY{iJ2$Z5uE$&OBLA?Atn*F709xcSU5Mzpj(8Q`_C2udk9G-SC8-@PCz5A8ceM8I{6*Mt@*6GobS8 z5r9|MLGBVemJ@+W9rb}QU#w*o!4kb+=Vl>7)aEh1ADoxb4;-CJS0!^0zSxO6+)^_e z6pra71%zG&>M>nuMnVimTZ+JkBN>Ue*zpPA-`O_qJb$(!Zkg8EBEd@>(yvogkt*qVnl!qBg4b3GPy7#ofBn~ z$<7wIt=?vmVn3G0@JtXxNC!G%|5DFBl;7WHT#MzkMRlU^NN3>G=N2Uc75F0^u~lKR zT<#S%MYmD#M2lzOX*fI-1=L+Uxca)}r4REe=x#4%>2D_@kr0HtaRP}KgF80h*=o#U z_y;DjGh~(k%h}?q)$g5lnFo-*ZEa~W7NNGb;)!e>+E2DJIJU1D}JhO7|hj^ScwPY_ek(8CEnM6y4&frm9LPgz*wGP%KA`OvBu9_cuK8-QibOvdiDVGd7 zv-T8FNwd)szvM70V1o`v5O|&7m&}2^{09<$(;YtaaNl8;Q8fC~pN!PlsogwYHeZi) zqq4P6opYCTMcCA2d?Ket$v=9w1#Gy3?c&)lqs)bzg3ipz4jJP~SiilX&dYOuIEp<| z4g02W1-4U3aoQFY>cDV}k_A zgQ>U?QaXg%TJiNStZ3Z+mg^q8U%)j6A^C=Hmskjv-`cg3!jaLdsks+V51^?ZvdgjD zd>9WiJBvt^Z2aZ1%F{CCvTGA3gi9Nyfg#1RX_Arzpw!A>&Y&kFKB1)X>u|k_Io|cx zyb3CDu3pe~`$&I}2Qr9YsNw(dq?wK*jF!#a?X)Q>fgb&{&WRyHk!M?H&-0c{Dq4bV z^g05LcU(ROS63MhiHj2%m8rUrH!?=WUH8m+rHI_qr8CA$)WzqrOQ9#qMEog7Wgu&X zC3^e;q9#7kb-^_Gq3eCND@uC82%mq2|M-EetDGViIY>iFkVI2875y3`n}v$H$MN|f zKb95)~2C(9Z>`Q;|%(*H48Mv_bvYd)aYxFozTg~1Mu zT=PE<70qua|J#Mno-6{wuc<|&phP+7o#fxo*#50<)|q4D?K|mVvO+EV-4MLR1g3f% zb#LPgEj0$fRhN8u7~S6sFU4fyqrua#PaZXJgA~gbKvY&k@$d&rzPNnazHDbkn)*U*V^}UE zPTs#H;bjZbqW8LJmlMJE`^nU0d<^|nwvAtO*~3mo39AfGy`(T+zl5}}L+WLX-$=_>X{bEl$6lf%w%XGxKHUNA zC^Ef^Xtzojqz))NABv%8uf8W`vBVc67IN`xtUd@ruA%!S)1idu9cGE!*-ej^rgRqF zB;%#4&EQvEM*f^gfg;Tha?9WlqNAI@i`DxhLq}ND4^ry-5?kBA__0U9hydi3li5Z_ zrIT3DYtjAEYN}OJ_+TSty{<&!-2JyV{`P?VxJ*%bo9Sj*)^c)p_%m3ogvZAC5ngg2*`tkT4 zslRe8#tuW{{e69IfxI!Q_n*#bUL5}Cr1!JG@+5unDN=4IBSb61R6rf-(;hbp zdHw`ZAhUdz|qz zYR5uaD&nWDPx3W_7;0A!CLsu~rtJKB3V2aEGpVwkAF1W|Dx3F|C;PPMJVY1~QA&)W zzcz4YZl{cYi5B#<@{p615HSLI8FF;YAqmoqeEVvoEf-} zHQ^jr4;`0u7FdLiAzJdxc|^I`-GAfhmp4D-L{`I)DW~lQl~HfM!UNE|`Byv@K5v*s z!Q#Aga!L`Htq9!EX6vh|waMl$C6tfoAme?b5f>l-;jrOSqO31dcqB=iqVgMd9T$NE z%yx#3|6xXyUx+dyKK_^ex>me%S*-Z$LN+Gf_d6AQKw_v!68qC{QMEJ}*E^XVaxciv zOr#K8;g(W&p14pHO&Es(1q&g&;xqP2Y8Qb;-%j6lKqPyR=zftz?h|NTqLKymi=DbZ z%Un{_XcRJ*kq$|6s+q$_+#pBS1{<`*GF+||w?1Sf+*qc2!U{U4@pRBct%PSkDAHS8 zkb3>EK6hyO>FC5~?Z^J0?SQ~$1V6ZJa;GlnHwrs5gGcr1fYL#cSQFOng6LP2SkEWu zCTvCRJ9h&e6rG{XaT=&48gBip;2w3!$F6)#xiWP$i3OUQK{_@O74zt=DsA~ zmd<%THjvtd#BY?8IRixDEw&LIW2L*zJpKB!mz^evHPiSxINV<0+qd_{t>8VarrUl1 zvGf4+m$4W>524Hc@Ik`xl{-|&2E?};J<@g8$+ANS(8`M4&7rc7aq~s{{<8Qxxm3>o z`q4gS@RR9!XkQO&Sy{@s#k*KD3!D@wXz6cj)yjhW=2&%5Pe!CK}Z zZ)ZDg=91HhAdhG)o)r}WD%ssj)a?8oQ4}H#Q3=>yqGcXvyg8((zat8o zW{SKr*JgOlXom-rnq6|n_`ajirmfAVq+JU-a2!H_M?Iac$Dit5$}B%|xHITlpjyzE zk-Bl05nrZXGTuDbs$-6{VL3JVZCi9?@D^0a55`a?@^($ZSeFx#9n8TBMDWHJ%JE;V zGwW8dZgIBo?fhl@=>x6RJO&yV>NpN@0gpp6Cl+izx<|NU)W?kkRt!V^m{6M#<;OPJ z(A(y~%n$V8soOWI6&`W)KKtHRaa7_ij!*pNk;|o^nyCk)>pR#Bl~2biiP+4Frh_8- znjcQi?W8_$Nlwj9_Y-rBvbeQ)Vi=AFg1n$uPrqdXQ$7Wr&Jvmn`w*#WUZ9q=7{g#p z1%3s7URUUDS#G9>EX_m6*FTrOubzAw7)m>&jLmuj<LXMk>my-YfQ?cj1#%#FEhA?*3yay}@mo*^Y~Z5p|0L#7^z*4;4olh%Kn7YTsV{ z!7Qf259~i@tXh$7UjQF!!5*xq4gH|4%aK~}>NmS};*kYR&OWw*oDdk7{RwhI^im&+ zI#?z$1LA7D2q5tL6DI_*n+wL|Y%frnAfhKjx;?_$%N%Hh=4*Z2OGjHd9yig~&TqBQ{kn1Lcn>_qYX- z*)&pBqrc3cb}sqCU2mW9)p~@;2y)c5*{~AAYx43L`l;#KKS!pkiImzYnhAn_?y+$u z<0T0=G~^%KAZ98I_U>&tpfsg20qlzAzp(~3U+j{eSz!|@0r^haIRD_4jb+=Uhgspmd-LSu zSvDT2PP)LOj6JB}hdev@_q$uigI?_4^9;VtH{J=qzd4@vp1%pKcpuQNSP=~Vl?Nto zS;@S=!lj>^Z2AHR?cZ9f>mMO#&j0cIe3)q!*}?lgS}9BvA+uC}j`FS;YV}I=gaq0Q zcDfV2mIisy5j@~&U5PVik%U}a7iA*wLCnK{{w_Ai5jrMylsfy~sP zd}ra)yyo9r(b&b_5>EBNg|6N2h9nuxFNv_i&`+S;z8}O{i!C>rq-#{QDr~j_IS$`$&ncWuFhZ z*Ury7$aegns^%?}l^^WC`HCkoLtKL$R7z;Ng;Y?WKJSo&T%0J2Vueb(qVF?EV@IRO za8HhkEMhGSBODwYL?oD6EXfjON%T~>#J}fXNrR-}33Bl4;@35%)vOe!*srp$l2X@i6oxQ7f1L-(r3Nj9UD#oxepb*8vxA`0 z*aM?DnbDvMI|01{G2+5(l7)nyOrhjDA4xkm5%)gxu9D%|!v|sl4?d;%GQ$wRP0b6(r% z)QG8BqxtjH5u7a-fUK7ve9L$1P7bJ`gW;>B>&wS55tOjj=`vO1la0r9UgC243M}GX z9=Vc_Kph87Uc<~uiS!b#n&Kmp8D}u5II)-o6avG^W)`~16Js{Xk4WxJAMj4(EESBJx_cMXj{WnUfY0kd zTSvcDj=MQOWJI4YLfdFxz#BoO+i+nklOij5|AuG4bn&hPzh@ zVJNXktm>u=d%tV2C!=J_?A6qyVq^MVAQb9P1Q}KOKBvsq+XQKxf_IJAYdx!rV>NbZ zj${-M4R>pUVY`In3tqn+W~LBviUe;d3oJSM*_oQM9oCVyPfvh4uKP3v_nhlW z=`o3Cw6G6)xjic1^A7gIFabW;J%iQywn@M0!U}}$%CtL1-mci*>-L3sX;cZ1uPIF{4-dPD7ylgaL%$`C@b+M-Efx?+63qnS zo2TH9ODm9bb@%;v-}H*QBw_UG{uVY(?`SGVge=?bw{KlI(uYs}*&^da=noW%ms{M| zVlhLg7B?t(ttGE1sExu*8+I~9do0~=4Hx~RDv`LDX0D`?ar3*+o^gA}HU}o?b1KZJ z0a1)6vNaMz!>o;LG!uvV$G+YoAr(yX$!Hultmn(&F1hMzCUQvqd@TY2Dn=xWDegZ$ zIdan;Giez8+Uuc-yvb>@*^k)RW+ABUF?p+wG=Us?hj>sPXE z%CClU7B?H!OuYI1Rsp_Gp<*S&OHsMTm!r2_QU8c$;v~`Z%OxwBCODon9TR{yw(O|ucX;L3{PYI); zM7M{T^0WHL>gL6@Yan>wuCN87;NG|{K5PTlcfFuOG2a#(9~TjQ7r!kpIKD2>0pk9v znk|EB#~!a-frLp#bo~6qzO78_h2;WtzT?9!Uxy->MZ$ymUxnqgC!#HuS8pHr|1b*x zDg(PLl?krjUaF!fOKka4>dp)XhAPD-RQ^U;)zY}qchx_!sZVUectXObh zykzrb|HNg;OY^iaILFO2k_9PCe^ECf@GWB8y7CGJc8;1R7Fd#@<*yUi#Qv)0{b5m| zpZ057y=9fhLnu`$}I?dJ9fyP$F+OLnO@QXLzawb+lacOQCREZr25fOy1?4;DU;j=-53pIk&v96uqJm&Z=kN74=M1gehr6FY1|_<86X6DF zP5P9&4c8A1FqCasVe6dXd;_}p!re2?E3j4S)UR6OKuc^`T=3yYy%LOQH1spwsYCwB zfwf9Hkp0aF2^N`+zMEN5MaHgQW!q#SyFxD)y|0**xk1H3)95GxwSQ2Mh!8GeF@@rw z9raVY!QX$txfh;79M{4ldCE^KF|VJnUc9Qk1-CA!PsX?=8xlh z8wB!EjTkEcTw(MEa(*P+GyOsvL|4(Ser+Q}7Rx1X`rVT2{$aJ1<&Mo%G~<6ju zJ}}bdhhBDOH~b+@{XV|a7zh$8vU@1P0G8OuiZZUld&n;cUKkIFG=N^ku}Ij@Bis8_ zJ-8E`ymCAkHnuB&o5lQCgT{P)BxL|+tO>Fb8FN!59tKZ;M%fuEo36p5 zm(!2D zBxs}29iHP~4>-m5imf5JZ(Oa#lu)yz1Am_rniOLH>B=kM?a9ntCw8Ibqv)GQeTXuS ze$);a1i7ucFFszwd_*b7HKZCkeqoFqc|{aQ^<{NRi2eFXu`ES)vaYaf7c zJS`Cn-lB!-RX(tNS|V=i!db+r@m6^?iN7B9NRP|<|?=c9D_Vip!c^& z0_JQ!;xS)`wOR;snpZGN-)5C=-(^o?2E(8+ih1Y+h*kSGK*e$sII5(1c)?CCO+$Yn zGM(!klAZTb?qSeuo?c2KV`fNlQp|ph-(*q+y@kSvMq%Z{h3ElLel&DmWzoi?u9@Zh zbs1&JZSQUQM4|R3O`Hd-e;GUR6#3yESzGarOhy9+VwTSSQ+!f84nI3y!D?w^*8r`_ zZG>zRu>Al&1Y5Ul>#s5%@2~f|rF-Oiaui+>hqzmp6e_r8dEJD=JzGJJZCFR{Y9+4t zPDg;^5Z4S}&D$Z7;g|ZV7ou!bCVnI-ZeNbBEsUE~q)=T#GF+4_e%*S|t#76q|8L69 zfLfL3r^j*ofBn)wHdr#x}Qu%30n0w1$RqPISvGG zCuM>j$S|C6igeir63X6X!^th8A~IJ_lirA5uHNpwIe(+v&+hMB$j=4x{c-1F z(?YcO8ad1Q;Wd6^82CK$Y1M#ky*AFGsqL`%EW?AjdRMCH)_b$Gzi&Lvp7I(r%qk;Z zpq%oi$Bo|-CsSe^BpPuN@FwFDY?FgWMS2A~;@Wjjqwq_+_1ZyXw5F1o_MPIDZ#2^m zcx8PY(^TPzQAI(9$8Yt@e(?PcH|%P|2-};I$3UI%sf}{|zvg4z*QyrU$8oRmiA;qSFqx#L#pBYX z)B0k^nY+h=uGQncqyO`_$I6yHye0Nok@$r|>3N;1F~}6;Q#CKCm=cz=3!ZZ9=L)7Xk*dk1E>M50{h!QQi8gQ%Y%lq1hOxlbOE|`|p zv?jub&1HxJy~bNYC&h@n8;hXR#iziCb|$E?a>c7YUet>%#O+fSN3Y1vw|_u1%=wY# z1+vHo9Lbfj!G{IIrnTK4UzgC$0LUsy_o6T6268>Ej062$g=_>t;_ZV=H}@3bQ!NMU zIz(}_j*1t8$v78%RvFp^d2Z=8U3^ug9}$?Pb)iC`i(iSzc3R~2+2DIKKB{8Kf}LX} zVuiYR!rY5&JLWKGM zTS}0N4f|XT^V|m!diHPJ0UuzGcQCsLO$C8h*eg%M>t;+46DRt-W$;|J*>_&kqwBZ7 z531mw|3B@yEYiT$L}BgdIr8DoAe_t+5%XK86IRW|NqEyO5OZl=xM6%Dyk$!hl;qYw zC-C7y z{sqv@-wd5f&@~suCl@m^{!j=odhKGI$pL2VqkTNea6x_&Z)1H_H9_rc^Ujv^kgQIj(|;@tT;&UkJMrwQ62#MLL<-NGefFr#Ht1lsf`|rbq-K*LeKkHPQ$qpO z1l_m>#)=**`cV~A#Qk=J`Q(uwiG8l@wIZ4(g%L93Zg5It$>(Ds{+C6eWy~JFK@xja zP~NYiS8A(7SJVCr@1oEM!%QQ10cS4$Yb8j}r4|EFnX>%LBBj}K*;l(BUXB7#ysOGT zjYEn!jp4({8raePLi)!duYq_^7R>pId1P@+0nkX;zh<(*IETagpLeSkl!6}5WaKyP~c$6OLK zM2@;zHcngjHhp#QmI;MdddVRn&Bx+sAC7v12awgDD*?EiKf;}BGH4eku09sK53<4c z_??vh@KM=pX|CbB-R-&C)KQ^fr_F=!g}6!eDJfY>hgByA1#Jw+(qbLf35QN zWK3|?I=tKJp1_VrN5Rq-Z|Z0y`Jz<(hXx@HKgKd7Osflcm;Zt!`M^KCKi~!Mb`IzdlgPjg=z1&mwim`vqt-iVa1b+2!6|PvnDdJ6} zyNs3crCW824+pI_83^LEW(wM0UG(z!{+YGgJt}{#B?mmGgohx4*6){#s$b;)o0?lT z;|=J}7P+#1_fbZ}x;zahJM(wYHLNpg-pBAchrR!UYtgNoE=NR2UF{A24BK<1`wJuz zk2rA?z_G zD-ui+$Q;t^(r z>UnrnMxN;g>znQ}6b|1tn)SA)sCmTd2x)u~hG1lS_Ehi6AW9Z4-JhT=9T`oG7fVUO zH+rMo^Xw7vbJsf&j|TUc1)?MN^ZcR8!Koy?Qv5GHs;58JRSRG*N9Ez7o6oqiAEP=R z#~oRl6AdT5E1!vCA<45^j|Y?RWk`cJy9)61#(jb7%rYgBNGU*h77YqvFB}B;`*t^; zb=g&~jPTDfjxAtkMdaV>;nQ`tF*61ZlmdtH_{IHA($BDQhh<1- zk%MmquwsZD*D;edUi46Gi5X(u5$LiKhB*mvX!Do(xYhw?#6&nAhqe*UXl)T@lc2kL z;rV?OgaxlvkUo63$K0?g%~DDU__?if#i4l<0SHYqb&T(;x_r=VRZ=haw}2aZ5IMjX z2|7v+-K)Ext~rI*t{)JOa-*xXKnI>HG2CenoKz9H6AaxvLChFi2Buc^zjc{~99AU% z5LMbC{-8~4ndDyL8VSt7zjTx@s`aR8<>u}2jl}^RnnY&if5I*H6@(ge5%X0zfOa>{#Svk&-s0pAk0&59(Ey(_G zv(0X_H@kNJj-=wW?(12-vVqI**ZXYWVMFdeBe#nXp~Jg7Z8BSM6YifZ-&wqI5NXt~ zdp&7%yXiN$Y_%eG?pu8Nav(FyQG)#Yb&Kr}w4+bDHtzpu^^+&fZ!M3QSvlc(&%>b;JQa|#(tHQ2g@Y%$^{+@dH^zx5-gdG_3+>;*H$!FqTTp+edG5dYwO4pj?on8SOe<>C&Q|cI2WaY<$QKaO+ z?D66k#TOoHUAKZw>*RPCu7{;b!E>R`#AP|UOpUp~rwaTu+m@dqAuN-Er=Qwd?sI8M z$YuxcK2B2?Dd>##y;IA{3(I$T2 z91rCTo!CdgPaDZ`r6biSCls$RJxw8_kIOI>$aIipBdL!kMXraHm#bf{d`^Qq z-^;j-u4C3;GG15Cy<^CohiH9-?A@-%-waIy{d4o^6I-O7U}L z%Dio{*%d19=e%D4?4~B;|J#sQ>3~Lgn|GenSErvA<_PFHRu)+WZB2YR{dU$dN}=r9 zSQk5ae;6kU9F1(X%1k%(U?gJBPUfC}^7=w}0NkWqW_{S3^D=O6O?Nis-JWrfcu-B_ z9!-MI2`c@wn1SbF*u_ErQZ)i)tAn8aUdGt_kRC*d9)9>bQKwDmmwZEayr472)lc&Y zKJ=5t2gV4LO$G%3zyIv-YLMkqAHLx{HWqi?woaauElA@aPvpz(ER*bwX?zwnRFDm^ zU%{}B4ELyudFv0Pys^k_w{@{y zSh&i+6DJUOa3kPt6Tqqy8gXDN7T*LAd5)-lKzTlp0UkXfLXvCg3U)v8s3rHPkNI!s z)K4=Z=N942hU)?w2)xugz>jk(0GG>y!jzgBo0+sV>g$B5LZJ}+9r%5|-loVlxhOU&eaz?3Uvs!@2Rs-1+SbW)p;F^mE^WhF0XuWx9UZyuS|Qwy)Tp9L}=co<{juO{JrMsU^h0 zuw!u_7LN?bY?hKRkj2WRqi={DY}KH4MmH?Hqk*&94@7YqH#M?!EfiV_ukY_`=9G^o zItnFCe*z;E9+<13UpSY2bF&j)W}kM;x^g`i3rQ*X73UrfJrr2LgzF|gD|c8o%6eTB z;~v)SIPp33Z4Ok#&ghSv^FDCj?4Q0{Voh4$eloTNBg!3(M%FcMv!$nO3dWiC+I_yc80FO*T?>XZCUN3n)Sn-gTmJ1zjzOc zgXO>la_sb%vl}s4SeI5}Jqa!Y3mZAr{RqV38NAX{AB9Ki5M?F-*9rIh+lG}Kk(+jQ86YroIGuL!+@AL47+pCs~X({?W7KZkMpzG3r>^Pm61rmxRG zdt)2ko#$$sfT#Xs?uwXz@H^CR>khHi47;OcqT0gGEv3}A>FoxgA1!$NG%pqP*;J~? zZ=Yu(ZmY}nl3!qa)2pkmXptkB)|-*MxSLasi*)OwO$@O(oYi}9^w9fW__q^-(7Wak ztKUrDMa~vw{OJ^5<3luVQW>#=yxy5#8eao~v&qol%woZMxbYm+u*%BhNYw?gNs~g7A~-_%e8N+-s?yaMVf1RDmk*G! zJObx%l*DZMM>K{wD()~ex^%w{>7xHiw9~bWc|2!SIioZM4y?KqJ z$r>Uo6nivMka8v3U`Ji~1tyi)a(U)kLm`sirS4m5KH?i7#b+I05!Q$i| zw%d=Gh~=!K=#Fl&DvXSIg>9nP$ff882rw~2m+%E3{aSi|;Q!T4nH5V;HYhgzllCZZ zD)O-AE~ekm8Tk232tmKai;)ghN$7;IjOSKf$|eS>Icu!7V#Oe9n#8wH;KaI3W@eEnB7VwY-)cB}wuZCs&|vZ;vsnDv37 zk^eQSvydW=sAA0mOjq@>XgB`=^mDH~W-PyrPmB9WEa+#zixb4=Nryl%I66h45w-KE zk_US(6F(^@_^9el-@8*Su!+^THyd~8tx2%G)fx(yBwz|^rX+jS8#HW0l2nJQZC>%D)iFev7yzo~1wk?)WHo zIo0S-ae6oLw<*&8;((>e%fuRZ2|M|F{W1RQ$s)-1-sk%D55Vwna?t&_JL&BbVDHq2 z-E<4_2A;EDQ{7dZ0pBvl$$cu&F$BkW{8ks2cXGpfhMhOV{@f}1jbM1rrZ2Gc{m0xs z`O!28i!p=cfOqF9)&o^ml#LJsQ=`Ah_|>rYdi&FTJ>KU*TlcN8c&p>nR=Ou-PLa!j z)=gJ1P%rMYne(X2#mB&K?i4$xnq_IOb1Ng=)GOC=W%Unue&siS1+>skP0eDFQ(aYb z?gY9}c6BF=HwC--M*fHF_@mDnKgu}s*^Igi^(t}ZzIwHb5Y(x%M8x^T=}yH~ z(?3*dI;k^}&&!P7?+~EyAJ>(_uoVK&&h3AG$YE8D4bBaZBH@p(_zZb0#`5a44L?P9!g3RsMcFKdf8*}Vupogh9IKpz|A#_xd-yShl4^zrJC-`!H zAoF*Sv!U?%C+c)~3k|8i6G#oqHh4ZW7`|jPuB@;D7Qd`_m`@C^hsj>C3>_Jn{ zMJt3NI#@PW_rMnylhug@>chMYqzb~6^83l11$x(t`AcQZP3EUxCLSW11-W)hovcvV z`ULj+XQkg=td@2oy2}`g3oz0{(E0c;jxLgr2lBGa*$c6L`58Q_6xNDB|7rlKm#Ejz z9pX!KI>HGV_VZ^bPic>qFrnxapWw<_><2EI9w8b)cK2sfSbt3{AAbM?)HSAt}Ic)?<*w;0iZ8?n1Ver-m&l4k!u}jo$vq-tK$TGdY z09ql?MYsJS-?ML$yP(;L`9fm&^hD9eF)-^u{M_4v5IsDXQ01T_x~(pNFQ#e7hcR3? zTn~ui%OQ>TgmZQHmM*I;#>T5tsD>@t9M6W^1WiauPie_Nw^a4VXt6#KrN!7FH}@E> zb~l{Rzuomp-jJL zy4~?Gtg_w4zu5ewW`{x znknKgx~RC}l-BkNI+mjDe+yGToplZ7$ zkY6zsVK8T(I5CYCSti7+qDV#GK1&7Y_ov8KN!&+(sf)~Sf_<0!h3PGbqk#VbJR_=|rNuffb1L)6WU9tJm4mzS!ZmkDHTP;NrQ0qXG ze8oTZWn`1ccRWx6ZAKi)1?QA)N5h;g7j2oYQW5F#0kQ$Vn-6E}uR=MV-8yl|^`2J! zNb-CU4%GjiLRs-#7fE`(yU%3`x*qf@{6Cx%VS9T$;>i&3&p9~M?NQu6 z4sQ2i3gdM$bD8(K^Cfen9RuX( znfNWZrLB&+-A!b)5To)NOlb9=T=gm@AB7oBOne4TwUiRV$Qiug@JN*%58Cn}^=c2T ztU2Yqj=Y0?lNaAc&J9c){a$}G*<_X=+FVciRbg_sZacm&4Dha53r-3z4t!g+FaT!d zQow!u`v$&W?(Mu$*n4U*oh4l}#wh>&R2!46`o8Mpk4ZRq|JUrluPsy${d469h-YuC z9P;7s!C`vS(ze+yR}E`=B5ZvYr&aNMWl>!ErJW9RfRlB!q;qFVIcOz~Xy29ss#t~* zeiK&@vXpZ-l>n{S$BSe{C5F<^%nc0i0#-TUrj*V6N>kiOu7kH=K3ZQqtsdg`^p~8> zf7g^E!^@jGm&?l6m+Th}QDkPLhA!(&S}d>DS^g<)Va8wL^wCot8iBggrtdci53N{| z@4(l@&j*($ya0Uc!vPLQJmlJOZt?u5>Qvg-mq2xkfgVopEbM6C;L}vUk(SR(Z*b#d z@7eE<**^Y_SC~mg{9iIzM}7a;OkPtlbWr?6J`@Z$`NDQb`0%H(y3P^wcAfp34Ro0V zh4giv_yfXbut$h4Tv|3dLNAp=6=;G~ogW@DCJn`-__0sUmI&Gzg@M+fgffiuCVIO9 zDsid0vgiVZ3wbFagwU)0kj>G;F-P>fcf#j_DrKg#5@tGx_)W^9p{1v=U53yd2t>5v4s7OI{We4EcOk zj5x(|WFH(dsTFsvKGwaEzV1?$No85TBDJ-OsnIP=L|ycKirU=lcgnoH0x5s#S@qZ!XhQ zIh$mRge+Nr94cBmrHKlKFfxmdTwkQ}2ybW3PMwo`IRE$`vJ=~cyn6}k%U0hjh%SB9 zZ{{cZ=^c(bm8Mvpj7*NKs`sHdQE!ENhG3fwa3=HVCFZS<>NOJcQCcFbJ*MgOn5Y;g za;p5#ndp&+siUdle;S*}kPcB%&RY$6$~WMK3^V^VI}UN=$~j^~l9K|Tft zzJltu1Yv$v3A~1{D}e_a5bFwT_Yh6?kzU zW=>mR4EZ@%@4JOq->KIFt_J7c{5ogb@Jj9p&wcqY9AQCBI?}!a{D!Vx5FF>r%L!*{ zW?gIaYf1F-pzP@B~3>^PS%_PdKeQqAR_4{BX-&<+UaTx!=N)IIP_^V{~@=Fw+$_$&u z*6aJDV#!xwArt9L>J}*W5QQri#0wVFof;AH?k#!1EsZz@0{(_nGbLc}+G66*RU&7x zh8{(FSYEVg;jlb*4KEyG*!~00deQ5{A{i)^6@VUq)vk3}JZ)P>icVTb+HSXJMYYLs zr%aNq0P%EWY~6<$jMh$)hSD0Qjxr8g!l_(MEe5(y{0Gi4d-xr#jfy!wevpU` z6^}>YISbC0Lj^;b6z8M}4fiI2Zfu|4+wU`32E6ny#vOS1(vo9xr;A23pMGRS9U=)g*;ilA z#rr>^0xPL8B(c^T3;}fD3_5o|OSwuSMhd-nb^I_#G0fKpAu9m0DqT--Y^v`kS4PD;jTpU!zk0(T-ogT$z`&2`{PC}0`-aE7e@zo`k^sY1!rhxPhi8+tVj z?fH_h6y(CmrlO+;5rXs#*@pLyy>Z#2G2AMZlNj&;h&9{+@Cl~CoywfvN0NLER*3hnYo=fNLvVZi43EE&$Y z!Vj8s%J$BHQd4L$XH}E^DWG0{SxVplk&-EY-)Z%9C{cb-T=6g5w9Xvs$?2cFwbqX} zYHp1h^Ay~ zKkYd`^}7@O)1XYFRWRPcLir(7wH7KRpsh26`ApB#tz_qd%js3=55k@YiN9)6PqOjE zwNU(w$#h!Vk($K8r?W^WZ{P2S;5pG+?I0!_zYh;7fNQ2~leCj-;sINs=9>L5wM~92 zMe8NO@zOutnQ;H^_g;f^pZGj*dR?$b@fK7cQg)58tQrHq_!Mh;-c=KKr?`K7?$y+D z(JqZNF>UMQzZb;eo^q`Ja3DHU^?S)jD69-ue~l@>^fV|ob7WENU-J1TrN zgS{7ieH9}~^-&3CxKZNn^wESI13wk!GZ1_Re0H0oz(|OI-RvHX%zn7jr$JTSg{+(T zoz@j+p{uf?Kv_^SB9Y@k?wCRBG|gKy~@A;q)P;$ zZ`E{=R#zk0*a$ZY!5fcxlM>_T^ZtHi&ovXb$6sD!eusVO?|60dGwktoo^u}-Rex>% z97mb{*ZJcCPf6i(G``5)n*|s$#u87z`~`ZYntNzi>n;awr{a^1pWJ+m?(-8P1MJ@R z0yCDH7gCjuIXAupg?q87!JVS}&@tC4kjl^%^cno}s$&dJx>kGsTh8)>DBS66#gJhmm7K znY&|Y94%HcEf>8 zuYWwLae4|Pcf3loMOi$8cu~ZongM$R+5E#$v~Pb^3_%#lAIzWCv#}Cqq7qMc+qcJc z8ZYD_{sR*0T6Bms@O|2|B}SV%)|0AQ_I}Qzg8EQ^^rAZO(ZjELspA-Z6J=#eUx&Dj zq|h+^-$sQs-U8YsP%qubP!sSDxpq792qDB}&}2ZK15fyvBpX5Xphs=usv1V$PFN(i z<{ORTn!zN_z&?@Egx+#@<478fQgHP#=ZR~8ohtEV@k)7?;-_HDQb~=s|4x;8f~Dza z^e0gXHvYR{cC;ZQQEO84$CGPT%-mv7N_*p{xq=*tn^r@T??e&OrcaX%1g=w`Ntm=l z;W? z4oILO*c=()A%?BZG7LIo$cU2OU$Mx#7Lr+H_L9uIs{m@y8g+$@IeEw@k0iQ=KffvI ztaghfCmdj7d0X4ni7~p?uz0w(v_ia$G9t(x)J#X*E4xgx0g>>2v87kr}3%ugg#R3x+#0;MN)d2U4aMQ8Z) ztE}popX$}9D*sx3)kK$g8aI!lktNrqXNGyX&^8bf*0ZsG~Pys%sVylmm2y`oz` zC729%wbsMz6BUR6Nk|=he5tOPS3{#p=G|QO(mw?3hqJMTgcODHm3p zzBxD8(lzxV+pkOfP60>+TnjKRb=xEipH4YXft2iXWxB^Y`Cg?v-A}80OPjx|Uvbn1 z;04GJG3o-^5Nq`=%U}M(hMfMWWJFXkUMaT?*#9+|9m}HG>#w6*VS}NPeplL)S8D-& z71(&EV|8Qm;4yoR#SY{xay{^I0kj6|<+aJF|99HbfoM7XFVgh?xi(jnV6P(Szr+n? z(?}khrMI1q)$^P}=mU2GF~edf{A|L_+=rVhVJ(4{Zy9gzBvT=$uqWZRU*F~=Pr{2q z?DRErZoL{k?(V+b-*nd>aIMpNEGTpFhU$dh$x5sM`cR9Ji$tK}+b)eSR zvchPVIW>C`&TRIXh40=+jQT=s@6@txxWc_M|04ZY@7a80C$~R-mR{8uf%H%Uv?H}z z>w26UC_4{O(%jA~>i<~(Z;$fMC_Nz-0SsP?7J+;xN&2Q~VK$=@%gZ&wJH<#xgx4ME zU+`R^(D_zcX!~rrk)tBSSsY9Lv-OtX~-vzn6&Y%9K))OaZEL>d8r);uPL?OXo|dL)ot3MqQa< z!JrZSXxC4*`2(M5(UZ!3U1P)FlkFnDu=rGBqW<}@pn|-eL+`|CHzxNuIVb&kAakO# zNR4HXySrt^{wBmOgN3yoGs#9LfwcO9VUdUUoHOiC2Jymh>k&t+;M6lr$4@`Vc{4IJ z6N~LV=go8Z%tXCV*^75>48zlXA1r^NU1Z-`R&rN_WLw60?p^J5+mnY6W(f?2HP$9f z@=1Vf|3#U1oI6Pja()zqxlGREeI7ACq$X+mz1(of3+q ztF?pN?Ab2yJizw-s{+qNxT}zN0fkF@YR3vvi!0rPW(Pgr&xTQ|!}uN?=s$1ym-cI= zh3D6W2>}4=O82P-O=b4H&5#iV_Z<3+7T{O1A>}OMKD}+R_$raG>JBq$=j^1cX8cd{ zSLYXOg8%oljd5NCxx}3svfh~GLYMblNFv(s01U)VAX>_vDAvyFd`=B@Lg!s`@di%r z35^2JVG~bKI5A?{00ppv}yN}O_?8QZS!kvXU!vwGStDQdfesKA@nb9Ha)WMR= zR*C2GTGzJZ@EhLaA{Xb;6^gs6RGARM4_Uv3F6>zamg!JliW}) zmhlKpT8N)|cGPS#MgHs^14y2;zmva^=FDyW^y9bIn)&Zr?Jwod*G*^zk{Q%tW4!SU zTg4?k;850GeGWNx5C--fQ&|ofE8cukIhncX1-!iF?2-bQ!a|}_!sBmCj2%@?*M8O4 zRh09_^|lGm+q_JE4amdx&$t9AP*d(^f3;eMF@_myuGI5eoyDZew@a&y(xTAITg2RM zgiJ4=YRH6j((Wt4yvy7>(SdyRET%&yPNVyDFb_9(*yEnCGi*UNzVI>Pe$wl;FfS&d)5Q|HrQ5JV54o5`jXhGvti>_QryU-Jx6u{xXqmIW(E+%KP`Y zcF=)-rb0MpxmAD#1?E)*EU~yx1P#D|t1elwWx?Sw>kUvmT5JP+3JJE;Q*$#;G z8P~8@Z8}F7+IP+o?U!Ts3jJ?YMB7Dt1TNNqw9dg6*I_Se{$?dZkh{2}XRRx+aCBk} zvNHhZL+Q9z6TG26xbBq2jTaNj+oTF%p96&*r*QLIj7kOtq+Cok?aB4K^B!=(QIC=kTTwUf`RA1 zzu0Ns%&}Izmu$E`PFWdbN6QDN-sYi2pv64|Jzb-{Qf!;>Z_&^7L06h^^>T!4J|1`@ z4_?K=7q0cSHGAO9B*vcz%+>$CE}k&ECp=ENF(3>&64o>WLWdB%P82IU&FRl&DkGZH zDAb~Emx%*Ay7?@i#z#OB!EpEf;gamf z71{-KIq{Tx^sIR2#?5{mGk4|K8PhLPzbtbME=txvWV)ObaQu3vR7Vxd=zG8{B`ZDo zwe4npRyMI4-Zd=`{G~4Zq#VL8rekrix;gT51Fd7Ki}x;8{HO?zJOW#iN};pM?@Rur zi0HL5xxE_g_>T}_9-N$_k zeC@a+9l<E7v;~$(pyP@n`5hGJ4np>WtH%%lLbE@8sWmIz-=S5R{2mi z(8NHrwJyq7s_ViazkH-jdP>O>VC)2Zcx;C_dg(bxvX)}2g?@_w!g~CFqf75Qn z(WF-ey{4OQ1HjG>v-@35k}MBOllQuH%Et-vEvQ+98vdP`6*a3kAewCv=P>2vuj-Vv~OYE~{p=|6i?0J?ZIn8@B5=7$_9^P?2il+q6WD_?nJ^ z)_uhgun3EDmHl!tuDUgKqViINIE^rk5YLKJ&NjKIih6 z7CiH#%Iz3q1-d=^Np%ym8VtO~FP#jN23d1lizKY%;dy=xbi+#R5dIO|&;IH4vE`fh zYufEScrK%uw^vqSqU(yc587vW?mv?84}VKdf-ATPDwv^Wd&9TsO zNMf9LWrr2Lt!K9Vq1@_jCo(>B5KGnfS_XyduLzK-TF+7a(vFDy#H@cVP4j<}Qh&u&&|gXSGGj7nG72 z7K$_d;koJ(Zd4xB{P&f=@xs55{@F0?kws^!9%kjF)5cX8IHkqopC^G;*N*c)FXa?Q zuk6a!tbg-~7TroyGD3Wkr1%ryY558FNS0V3MDf&ayiAin0s)ZnCb&N!n|nLU6;@Kl zNq$4V<6wdovuCfHxHkup+8y$c`6lby)4IUPuKD8Z)u1$5CobD_JvuBUJZ!)+CB2lU z4^atGZdzc_IH?DGoL-+Q+u3E>5I@Ma3XeMK5eJ}5P6Hp_+hpm83brmme*ra8@o{Bf zAtfRpVj=d$aFr=qMMhxIy@mBg>-0m{`P(IdgTq+BH`i!ATvE_F8CK0y%s$N%f zWKWk_N9!n0G%f7MchAwASVv>T>TwAR+7^UcsS7xR;|*BrsU+^go!FAMjzawkoR|0<`wOV#ikHz$H@daWDZ&$T1l6U9T zrv@}Diad=y+|3?N9LsFRg{j}$ zrr$KLP=nMFNh{~LW&6#Fc)06DY|a1STcy%Y8V^)_RB`4u7@vxldg2z_>$fTAVRSWx zdj(2a+)U4sDbx1c>A}o%O>`AUqh3{Z)2IAAh^vb#9FNirYa#Y$!5-Bq*#hWkVr&|| z#AhB9U!Qc>5Emfzuk()1qyOZfo)r>s(Zk4Vutx8|pV)UDEMel@d9uH-(Blh|x-4GQ zPJa6{UFzQ)7F-gMetG+zk>5Y}2pLg)5OS_G=lbHR>^@k~hM(Y2CUHisTFLJ9p#c3| zu#hkPL)8xJXQE+ibD`^LH-6_b0kufaY~~MX4JYpPFJnHr7GJSKGlwamW}%i5^1BQn zjtw5_`UFWjjXem5mCLL}Ld`3ww8Q|-W7Q@cQ%1Kz=Jjvt<2f9vQ8t~nU!eicw-8W= zGm3^)2&(?pc5IRriDlLVk#{vfV@}1$b_uLkicTC~`CK+~BioQ~2d}LEs2Cl!#h>En?vX{_nLF#Dy(Wca`uUv$LpKU;JdhRTaxUmBb+f7h%j1AkI0di*`WYiLzLy_P+z zardagHkFL%>frLzxD=70!OE>MC!evQbzcpu9q}*v7<)4^apa&IL8RTOc+kHf&i*=8 z9Pixo4Lf7a_0qnNEOhNRPtwMKZbr)TUY!#VWq{MRxzcg+PUOfFeajn!dGJM zDCe5e<D`2NzgVD`C-T zlF7-C_!AmB#4v%9*`>OvQCn`63c!9fjxVRT zJOP1kdeWRHx_bL&wQY>xEgbl?L4a5TBkfT$GPYsu6Zp{izJalB1?oQ=8wl4!93yW^ zgrthwkr_P8JWmxPrvb>Tr1}@MpbMLoixf(fUb^a@WkrZWzD|wc%C)7fQq$dAGUN91ZWHkL4raKR4FZJ~yRte!?E8BE9|7}y9ZGv&4Xjz# zzKD^ML@^+7n8T8?zx%~@~`;TUM-!h|Fdf^uX_chD1 zr+yu`em`%QS?V?efiNqe9-aI$A~)cd@NjfBlYK-o_HHL&)^`9H%&}w`5!fLi^F6Sw zA;LG9fD~kuTi%FdKEn3Aj1XzHnDdfhm;#y?tt@Loi)>TR7?sfodI)Bhe=%Z%fmZ@R zD~CD((+f90#J7M#WK4Ur(@LmUUy}Z!AMylw4TP@z^;kcLJ70FhNx>SWk+}EU`o^x| zCV_oPFv5;Im4LJk!K-D)`%T8`88GOR>k0Bv1G!W`X25$ngS_duzL%^Qg*_toAmggH zfI#GJ)NOTv{=CV2O_sF{@IKN2OO75lu~fuiJAg6~mRqbV@qXk#x-OFGjr&aw6f9 zY!%Cy-?1&5Y^AT#q;=R8)EgtFNSfkUh#s&e!i;=n?r7VbF>hbt;uFY?@aY$0O)C@F z=hJ1?=;6YHD<=VANvisx&xJUPKH`liPwn7rI)vxEo*G!4e8X0d+391)r<{%F^X?rq zI@4Tp>T&1j1yf(G!4QGlBL2tq5}tR-KCf%j_er8Pr@SEnxA|S#BeF}fVoPCIT%OPO z>Vgzr1ih@FCFBa#s!E=X4E@+XeC*GV3!lsjEHc+Qyy))s5Bc*%lhV`tace{uhk;G4 z8^<4ykLGMHL{gLA;Kk(6EY zWJw=lwV4XXU*5R7(pXUxp&*0vGb%`;i&mT zbmfyi{sGp`iroeQ%0KJ0`ZFQ3{EsrWD3_%l#hWYG4joy5}A5SY5N1n>d1_PeTki&B2!ttcXF z-GARLSstLwYVf%A0k!_Pg$wIHNoB<^c7CS4_&n2hA)w+<^8K*yNlTimcOld4^8u)^ ziAS17)X>MSug<~WZ8;_h!Qmx z_H#XnI)*kJfAybizn>eZC|f>^&~p8jV&=OE}0rgx8C`iiz`3>rU} zH~Qu?{tEQ8?{y#n`9eEqb6SjZ9t0MckQ6}2mjdJ5X`<>nKZcTH6Y~b4ce0@+(&RD) zWW`NL!>8iPB}8jQgiG7C%tQlG;EL1gc8elPF2|C%E8~Ys6Es7ui7**^PliPnhcN+1 zXLM#Xie>M*>Pp59Ntq%kbS=b+Cgb2lS#v)6k(6E!_K`ZE^^Rj^2$vtq zGzbNuF{9JzV}kCCPg}2~zd!>Zg4qx8jQ7kEciEv)Ry7;1m@G5*vd|{})5HCyR|3OA z5i?}lzp<+Y#&mgXyZKTS2-us-)y?5e3#UT@PSTsK1 ze&L`T%C%y^*)SP$=n$?_vZ#$0306xNc>@yeB_fD9zn}kSl;E7Oon-!gM&o7dpmyS$ z^SU!5B@pm&>PTP?as*g7&seAA2{d~mR&FfI&@RvE28;82=J+W^IN7`{)|%y6K0ppC zdj75Sg0emmK6Fzfx0(>98T@VT7L2`-8XIAiZ)SE+_BgRA)#2TI?K77AP{$2w@U}1l zjidOZCrempIrDs16`qBpc&Ig_*?_Mhc7-mlY6;@17n27bp?#)sca~~1WfnS%q@X&uhFrddZ0Uxe1sgTc{n8`u%wE5y z1)nV%vRQggTZyG|v%y2RzMqFug{td`xm15V=of8Q)y4;ohVrnhC~q zQ)ZuMV9ifZ7%|Cig6+L5-Ce7 zkOx)TGCo?+IZ6SG3xIs_8Vn|cSwTR5+V(4)4Q4#kU*N!y{>L?^nH>R3Ru1Z1&kwsc zx<^cHR*)0Os@%N2I?;JqL}(p)^&E$!P-mAr@+m`ibEC{J3lR^{K$k8e za*%kuo+*fx%``A0^m`Nun7O_wlmZ&(ov1zG zZfbO3^-z#O10~yJQrj9lD$tkz{CZ+|AN>?+IpriCM=`myt=)~XO@qhA0EPY`1UA=^ z(k61ir%sK4Np0ezt_?jXW(3dlyDI#LWD*kgqFh(iDOL%p&mC-Y!B#&W-UFNAZzpC-772!{DX@~~K1K5T~GwC`;iTRv|u-`o2+=ynxC z+NyNax)JO$5cKF6|4Fx``1cgx9g;(mGO4bs$*wow<~YNwOWxh!&R~@TJK|K5yx*IneY1ZG3x3c*v`(@7s!}2sDrpxOm%f`W&&Ud%ILP-{@??-`Ta$vHre| z+*NOTLg;mz^XXbQ%_;f43ui- z&wtjz&xh!bygK4>Ek-{`oKE*XAtD!FEP!IT5X|YTpj)q+s@I=4m9( z5saA3Is98)A-~x!HN7cecNzXwS_Br~;UfCwViqR2#^|L9fMpXK|CIe8O4!*Ic$0!` zk?J6L7Ue4rZM|yyzOC}%09o4XW5Ah|t3H6oxB-UyZVSbG);W6`xK@|}F(|P_5!dHpJ|3 z;j_+-moEp$N2<~c?ERC)_E$d)H!$^0^Ce$aVZosT3Atk~4bx6L1lV+SV+^y=5Gesms_s5R;PkiFj#8e#~u6P*6I&V$% zn<-c<7qby$d8s-Zrm$F+5J2*1B>NVJ5oXVvXU!;UHZ9xtKr|MGZ-8j-Hc{sdgL2WX zB;0iSS~V>>i`~~eiLr=DFG8tbc(XBOK1zV5nr6X^4KvE|7+#T8BafV7=!fThbwAggcSjzg&c)k1K6ix)ZCx=cYvY_O7O#DQA-HmBvkx>?^6RMLv%6 z3P(Gi_5!S1U7xDQ&bjSYt_`F^LCfFz;{BENAA}gCdCoscmXnN)`48Nr?e6$d zD(q!Yi0w+!I9h)4lCBMFH?=1v96e0LPTv1>x~aWsn(Ei6180UHA;{L=rz-&XEAH1c zBW;?F^A&K7bHM3Ey{Ee<=y)$DVC7i7((DnEveE*&k6tqcZOn@yCV(EzkKN8D_WKVP z4vzoo59=!Gk|qMi`x_wbEAA-WZhz?UcIABVl@s0VxyhJU;KTNRZqw!1j{Ed9Fi)0G z!CNzz;D3GDOc`jRFbI!E7`o2rvQ=5Vz__Rti+6F13q}cRSUlD*wCqkPm;!!X>GcKX zVsK>yDzC;&YFpepUkzD#FI-Q5ORPZux1Ch(_suh2J^5Zk3z1@J?M^Hq+9Th#)QI{# zW(AwIeuF{X(rZnChK^Zhz!Z0xQTMSp&@NOwk9*nkt-ZmT_HSWMos%-nn$?!y5mn(1 z7F7DHalaxS-!63sIvMxy=lruLiE14UNi3z-@xDVJl=mLzEN`8Tm4X={{xFn09-$x@ zv-i&4kH;q`fTYSeKgtq|)mAvInSK}|jNoFm1L?x2A?tcK{_W`_I`+hwMQ(uzAx_{J z^~@vy24A>Et67e$)MLu}_H-{GpZ;Iae(8OKkzTT+)N<&he>=~=;~gK!?%K)B%-6tH z)rsF*^r^lEdo@-OVSeY5g9vD3c)`(UGpYNM$|oPgph8`ZChqp5YDl(H$%tYCGG}CB zHf{mc5oCktmMr^|7{YdgIRhPT!?xIAN&hQ~zaWJ|<*hFmmew4wzW;M=vQ5`U`B**- z@Mzz#Rn_QG^NJ79d{kttC8!pZ%Z)-cX(@GZUiGyewBZvvyn6|9*pcg2OQtyLQA>X@ zQ$=HMFEx8_;d8h>FGVtErrJ>t@2}$?`p9OSJCSN_DVA6_d7i};93YFWk50_(!w8#H zR3y632TInOl&u(WM=d|hTVIG~CA2^6@~q5c!D@5k&ZVRCiXWoyC5k{_<-Mm{{sLpN zf6FAV5<#cFX;Bh{O3i3nr-sn!;dLd1$mr&LVg0K-qS$fm61pHjeDia@8NT1$ACky~`RcFZ?DR%+evX)XuO80? z9rr}lys%0M5D#dS=SL$8w?6_8RMD$HbO;9oxb&v=j{TgA$pLmHTk zr|<~+P?F8O$+tUqeo$bh$G675?rx<1DLO*MQ-o? z-A4uBM(-FO9ZQdEl{XXTOV&WLxBB5k{^DqhEbiDjZ-W^}+S{C+%N1mws0dO|i>ul_ zGrpu(z15X!SyyB9*m@}gjBToXDMjSL-BC-1u?IY(*t3>Qg?|ilFaH#jT}+W(T@z6M zX91b7;ulr7j;$7X=wu;L2KD3> zhI;46e)5IH-Z_!4HCH_CBY_vdq~X|xj$K2T5Cq2DQhGho)q1QW4KTMxkx`cZrFll&#PG8 z2J4?OUt6ceKqD;9aND~M8&kA@yQaJ7<(gD@ zt>$gO%1XQZrEk88YhO};4TLHLbDhi#VcrS&8laH83a{*zXvsGUH;jcs*2+)zfB!`~silBt(X!v1#6fx=R| z1%##HH8qrNUUSenu>UPCRoRRl=#azq660Ap`9nk!B~#mX*07n8@Y>bL%8_0V{jh4o z8c&CHBzp*c6xo48{siMN*In*CL*Vy$eRJ^(I&%PuR9pATKuGmIG=MN>SlA{D^Lk0 z3!DtxcpbLph@|rp;&>lfRYO{NFh&t_H!eDw%@%GF9n@0_&Lz^fE-cEMWXqTPMNip5 z;ZeEG%~O#VMU=#=WFGWvcP6gN35*J+_kON{0$M>{d`<+ zamAJ|v3b0M|Ld`7e^$|a#L+a>)$@gLB^q z;e3m=s1kf2GObmamkUkD*x*deKpMN~!=D?IZA^XqQ7$P1((=={3Ns%`)FW;mGCKJg zJumSq%DeJ+1e4_CYnvPGKaR{xCC}yheF;@8WL`i%{4G5J`a`cyf`m$NL63lnTB!fF z!0BL*Fy#Pvz5^L6lKHs<7U|#~L5y%i?f|4Afx93x)TIVDlJP4s=kM}Wc}32rMoEO@ z%EO)r6gHc=pLKjI%ys5>kW-(J8?Az3HlF^FT83N=Sq9;Ez#Ka2Asb9Y+1ANg7kTx- zTRCF@PM!9%@OjJ9@DS`)S*c4~sbX9UhcO73_l-i&Rx<=6d+zb7^=h1Red1`QlzO<# zQ-IU)}=G8v84+&Sx%*>bb8F-6%M~?Wjhege1F8ZVG&LNrXlEUgwta z;Ro_M!9f}&(K4cZ{7VUQ(iaCk^$e%~k&#{bJc}-u_}CAOO@YJvhs6VJ+ zLYGZje_c!Ed)pe8CJM`csW!96HW8M6*|M&@_WwtWmcZ{>9bNC*Z9_YFz#7IwM)E6g zdgJdi7ov%I9YFU!2>A%OQ}Xam4uX&csrCq!#8FCrpbL$o*(VvaORU}Gg(fHh^9YQN zn&i>bF7e^jLwHQdTWxHUop*D1A=eOXmu_*3br&L^$he!StbdxMk(cy~h{HJ1t?vxV z^rxIBOMOqoR%1gyw6OM@Ef zb4eU$SPI>wT$$1Y#GR;sNQoWCv9@Y*Jax$g++eER)E)8Z^(_wm@sxy_Sfm~o^m z@PtMXH+U8OkcyfSP7g2Y)?V!Hw$u`ypz6QqD`=Nh%zbYaOfe{{mKsDRZ$uDdQ;<~4 zF~#t4ISNBzI!kuTAUn82e8vs=E)?f1N_mQ^hN>1VG%T!9-S_rWmzY|xx+>Q(*Gnjraw6xW#|BQHefaF* zJ%ovK_?(u^X!dP}YHEhdX+6UW%WkR7F!Gav^smY{DDa%fGT#7Q!hsl^`nFiRlCX}| z{8iW6v!AkMC@%-7(#LV|DEKdc%>k=|&k0qx9lefCfyeE~o z{ku6@F5O+bv@g>lDk_gYc5p|SEfm+oWIf(lVH&WhPx<{)pv=WEb@_Gju0k4X=r7yh zvH%mG$<>9-9G;!Px;E!4wF}8-KBWodth^zg{J*&PTe&^k794C&(N88Egi@3R05|OM zj3-b(c61re-$2kc z0ppq8xiRo2&>{%e@o2N}RmYXYyc0w~5%Vlq^Dp6qUzU@HkrSXX;7e&Zn?HZ&FR8k^ zqN?@t2oY6!`P8BRw7VzYl4dF#_g6IX6PC;jW)&jxYq_!1a?gFRUcVOYbqKEy0wYQ^ z1We?nSwE>Yv3jj@ATDzLpuyP>S#!BcAxc4wey9__NwndKAPC!je?w&cz=@ztFp;eyp94@}L{qFh zw%=8T_BqUe04=E}5hF>~Ra*8rRXF^Fy|Jj_F&ChNt#iFE0K`%o;p2KU7k)m$;5L{u z1dX!A3eRYbYj|21Hh+UI%DCkE#rBUgTZjey7@5yxMd^9mz4iLx$bOS=^sZKHpC237 ztCUTLm=dLk9!d6vNzDq=;=|Oh>)-h0dnWf(rf>D$b9$lbG$?L20*>IkA0l_9HiG`W zyQk9Cd?#XvCBm*?Bn@Gg=A>bJ(U`8ZhpklQj(s4kOs$r!Dqb2v^;tefEzye9wCdxL zoVEoef112JKj4Gck=%N$=vFcNYz_vG$MRdFQF%#6xEO5nZHr)$=Ok||R~IubfrQ{l zSC=ePuppY-S<^m)hM)YhtYyiKe5ZD#h6$=ZD11$3T!p@2qJj3aF3qN%)TC!e$5(!K z9LY`&+KgT^iM*0^V=Mx?j4vPwa0;3)>ol`#MVBUw?zw0_Uub}jF*~M>5yItfXj5)G z-l-hI>7o*fik6*Q<34|Q*at#E*<6zFgTFIq-9Bb_-INbfm0*BQWMkmF?&6QkUQhS? zewC@iFF8qC6;?Q{-kju}s2r{oKsDB0XT-Sy{@TY|zovUcF8KjuRHFD#k*Ds^Olmh5 zv|#G3U)0s;ui0^aqf=nws6OcGeS3C%)DJcCq`X;~TeBXEQr1N8i7I1?digT@x9gyi zde5(((Nz~XL_-&-t1)-zewB%~A1-mb>fT&h%6U_Kt3P2>uG*~}UOV=;R?LnxS*%Ie zQiyO-*Lxs1GRNdNtlPCrF@bwRGOq8 z?dfrW(S2hO4zxD>NP(v`=+=J`RzF1Wy&GU&V>@=X`2FUvFa?KAOfv?*=f)lYlTV}` zz(+HI;nzFIs+7^1gy%kX(a)GI6PU|r*Wzv3PZ{CNP4jT!pU^rt|33gELEFCdjKj|Z z3O@nKCsSh6p&YcvbR&!`66mK$umc?ehbQx&&Pfc9Coxn)$g*mO46LX*P_gvhYJf>6 z%%A@6tL!h|v5T!SXMF0x&wS}B``|lvwe{DT)%f{y=dNMze8cwkg>PPK`~CMJW2T4a z(}&)@&_4IIYwWvUIN0))Y_Psfo@?b*k9LIqoQNSb>2fS8IPyqqKlq?6uf}N`iW^Du zIRXf|<~!eMv-aNGMi5&1+uydy+S2pSUnziayecC+=f-0zA|&PN3bAIw##47FFF>fK zaI)MBkDo!22#AFcovSi%MV=9&6TbnyRu`ox%Jq;#;#h}!ARyF`pRkFj2h|83S+~!0 z742xY@cb^ac8HzzY_Z4=#7( z1EUU-HK=+_E%m2*v5K0C66lv@cuT#^k}j%U%})rdyf2<`ahA<$&kEuz`hRtQ zMCMERN4LL~1+fh1FIIt@mKy~m>UUPLQ?AwvEMO$v+prMG$*6@4Nef?NtBC$)tM8G~ z8(By0+={a;TC~W4R&1xHtW2+?ulYI36nhZHd|@lODD?4>4G|S`IDw<|k-N5`q5DFN z+PV=08LBk)sB%jyL>nzD3yyy5q2_L;<-ONY)ie?c3#axuy4eaaB`)bHodU`^^>JP4 zG^|AYUVr`dVx`n-MJ2tc>!PS=X-%uCPGVz4zPVqYDgfiX7p~K>GpT$SU;+&t3O6_d zib6C!MGVbI(8xxQvOyTgR`w88WfMS5t`yYQEOTw(rFF_r^|h+D@alI^&p@ilz=#I zv&}YhK#<^Xr=51PZMNAatWe-RA&rYKzBmA4wsYREtKY(Zfuy>O1G2{H?VHu-+UOA4 za9v-nPFt-A-~gR14c8h5`x<3X_T3gZuPZ~Ks2jH7P)`v!Q|;DDRus5;YWXj^Yztd&!Xe{e5LqqDwLIVmF38&noQ>;GAS{_#L_x* z8K*dwO{n7j5@mLLgruo2Pm2Eh0RkM$LY6vJi&9CllTk>AvoDuUoAYf~%wD`vj_LaJPl=^;tjtU-w)0Erjxr z`w&8TsK(=a*0Y}V?3rpmef-f?3J{+1=n|VLOQ!Pj2WN6r5T@~WES>x2)7YAi6%Gzc zIt|xuf^;(l3H>#rUv`wE>RmW0WP_lkPTD9W8lg7ncm*hP*Om~c>|9W-m8@u_+m31u-Uy%o zoG+oX6h%_;tSGI#Rx8SIxnU^jKKB(QRPuRhIrWz+B;6*Tb-HNus!p>qf+j5&l@+&Z z@X3azl$sDTZIF_Tz}N#0IKUPzTtRz~6{311RMHsqo#2?be5CEnQhyVacikKcMvmGOto(H<0&xh**|! zk4;tQvMg8z3<=7=T{*QP=!>gO{MT6r;YU(n5dmUsGlPT6L%(qMT*rQ9+xJS&sP615 zVq9zwRN_6jJk|Y>?TRWsoXfgm+77zaaMle>MJwzR$@i#4>B zst;ox60c@4?CIp;V7wtSUAt&<4TYMXON$cFQs5WnGlhgAO(syfDiW{To$xaOic`F| zF)Rr$T3zas|9Pc-?0tLKT5F6`J^1CT?Qh<{yUm&1H|lX4tUKG@|JEJt(Yxnv|1Rb+;-R)N4?s z0TKcv5mI$CLbz245_W@w`2LL_fHnL^eE@Qbv@y3GPpu*;!|wZC|%9hrgjR+`U#2YUt75)`~#vTljCE`%HKr z`w*UT$7vnmOMlx_5caHRJ$r`Rg-<aqfnx zUy&{k)%kHW&H*{+GoL{r7 z_)!lM_G~pX=bx>GQMpoFsPJ1VY@$!o`E+YPHp6%_{TvX|)GrzR%KgDVuaVD~ zmcFKvPmc=5GQlC%%xw3|$<+nG}{3%nRPn~RqzIjI3BYe~7alA*~6YonS`SEj> zftH5?Q!P8)-cG@*))DX1pNo8yNd!Oo#(55um?*Qo_ue~PQ+ZIHx8VD)KEs@oes%9_ zFP%~o=>kSlWaqd`t1sb04qWC9m={p6 zgb_p#czEFID?c`E}fod<_qd%t4Ia6NoidWcy2Od-b!Z~i$ z0iYioD{!~~9B#0|1`ZZt)dKdQgnAI~L1Bj12>l9mA<~Bm5d#*kkI%57>dlp7nxSkd z`=x@GAwvnF{GcBPLk$JXa^s++s~pgd>u;&CmCyk_`Ue%VrqGsBohNxJXo?s~%` z9Rq}WI1G=3(5FZjD$=D>sMyoSdcjuFO=5qJU(c zlJgn@otC~|MVRPQW=fpvbu6k3=aCcbaw?BX2xK6+R)<^B^ z^Y5_Fe*F34I{w(#ZEu@C|GW0r$L(fY&s%%U4Q1~w{r>lD4ea%Xuou|7uzDU$)CRA; zwg(7TQ)>!)dev1|*?6o8)XY>EReWzb>7IxpYJsCqy8(T{e(L0qe;{@(rWcRwot;T?D0+3_Cs`1|X> z`Gify(5?{silM2mAB` z_u6^etb9QD>BW;<)He|eKC6048fT`Rv)BZWWm8yP_*#~6xve#(m8D!z46|a%S@Y#G z2&jy+X?9z7M)HZShFB_kt!6OUv!1QG=6tth+|w?HY4oA0h({x-ssDbY-f~ODAT+8D zRUD#VNB7IieU$X+E3|_#9gRfiu2El4BSPj26_}4)#8VWVVJwwR@Vd+&RdVph-+lMp zZaxIRJA^y%xkIEwj`CRb+0qJ9QJkh~MF?_+h6a;7Q#`_asQ8L3sYzu>(|Xd+^1roQ zRRPE{;IY~clrJgc$zh&CVED7)!{&7zpJ_*E3u8HhQ>f{E(3GL@9td11enaF>lp{mR z&YCql@T@i0S~C&(xbsy_#)ilW(Z=icGAkqODr%L5Yp5e_x6hfXs*;TNwc?Yd^!gGu zBo~TuXt;xh}%bJY0YM^{yfm8u6ZdV{xVGJ3wDcR9~e7C<=>MfqmF(ttyA%GQv#-w=PU% zeJ*~t%XBq>(sDL9mIsVrr6|%bE+px^_Yfv_n1f5pBK%CjvD*U?d9zq|`E`9c6hWDn z51`RBf(;U9G=4~QG5P194>clw z_S$PNyZrLY!@#41fP!jYh9lw2T~p`JS2So{v25BhaA{dXx#(bCJuo{}Z*?4jA=}Y# z-Tr+ZEW~PNzwq#Ss4iEp+HG;Q2kImAYhV5ht?S!aD`_9hr`x*_ZJ|cM7_@mm0|MZD z-ucjLCH;vCLYSAK4<>^=rS%N)%Qi`H4EX4k&KT^TehOuo#At|b9fN`-4YF4q7S9g8>O#Hj|}a)H98FT7}e4_^HzfSqc; z@Z}{1y9z7KEXjAe4wBHXogE#qVLCA?%{r$_(`v{qU_cOJci&6(XA*YR^^^;K|ip?&JaEA6Dujvc~u%_<)ssPglvDqrV*>rnq(7{yu} zY+!2~eY8{kJ$>q_HYGz}-+JrDkCvWwmbK>2wK<0#>YyR^E-b6(!gvh|!k(?32Jn{K zSyg+$1Z6b121wHc=9lBvt?hx9L&LVzrd;3)mLAe075`NRTAg> z3F#&=X9SjKYDPs;p{&Qp8Vv(?S~o@~)~NuM$;_Zfx= zYegEFkbY=_K%Pqx@cwX3?r+8dbs-c>U$L0*-+Id}F5hg(xVMpZgp;aXA{>k$M#6%QL{;WU zd$m(RIp7?3LKp-vMd)H_OK>wFX?Hae3|U3$Q4v)FQ>^Z`>;Y3_o-G z)bSjtcb8UQ;Tkl`(m_ME6*rj$|)ziLFT~+AGAOG;Scu8SH9B0MdS%;LYxbgAq@G)2C7I0;f3(})?06l;K70E zVc+Iz;6R`@Fwk#VqQyc=;Rc%nce2Ec?9! zQJ73Kv@9RCHXb`5BF^*kZ zDG&lQt_JY~c;Wh(yXUH}^!N3LK^NgdOYiS22r0V^6hM>bZp(*qkp~tjLrdl0&n95us zKUYC+$eYW1E6KB2+;Op#{4j(}m=l)qXn#z-!ZW*il7^JfOj)rsuBiR}RS(*OOP1L& zui187=U#W~qjt#^57_7ac6{nV+K1o0tId1i_v{}&u!n6kf1NRpf6J@3wJ)8#$S$e! zx8Gi>A}o?gW#9byHv2^{vZpI&JoXfv7Q^^hZzi5wyBg=0o_VGNg|qkD&svqkxWpIG z>e<|W=pk!ESt#EH)P^6Jm~*3NtBwKq0Q@L;!E1!Xq+%~Od=4O&8&q-Fr*67T)oL*0 z6UwsOFmb|tIZ@1qs*u~cG>zma1cE!}5 zW5?Zno2?8>kXM{GrhJbR#Ka#E7mpUfH)OI{HTsO2IN*wagWg>=7KqGz1R-)xc1{LQ zM@zBHVWBIBnZMtj^=$gA?C^mr49bNe3ex&&|0P8fcwZH8^rPz*Z3?k+P(@!bEcZ{+ z$Ei$#eoF;^1YZ3E{cgM>gj1o5`qOCY^CxV}U+3Fw;WxK)B1lBq0DSk~e}CnNf7FSr z;M0fy9#Q@v-hqD}{(Hbg@)f2xyYeA36ejbhyh#CNfPgOYW6{AsU_4LHryp4vgmQ@- zsA`?jn&70Ks~{Ib8v3cd?|R7Q%$c3kC(D}WLICBd#Ju9Q8e+tiu+9rH{xN zqV`q3^^v0Ou1&E~4u<=q{58UZJmg*yo3(xhHW6GXp;{K9Aryw*DKFB>pi+s7C#ul^ zG-kDCCB)tRF4GvVe9c0KqKpb9U%(#B5oD+oR2!I9W;+9*{@4_lK_-jxB6#2)Yxs>$ zaNm9R`D@o{dZ^QvbXM+lmv>IBXs?c`2MhCiIkJq{`Py31LXmL@^P4vo((VXR0P*Iy z2Rq=+P%Bks;eE<{6)>_49YkZ^k#^}WUZ-JoUOdcc*XbpSM1j$`XXF8@FRTJS{P4r= zu)_{>g9Z$7c<~uRgrXi$ws4aC>}Nl#K6}EgPC%X>Sp0y4gn)kl$8g4NyY04al|9lT z_`=?X0IWOixTD>6+iek|0k#gS47+sg*LUn;ABMEglGPzT46Ml)N(+C7da3Kp$K{v#&qyfv=jyd7q-`5XJlw305?K(4pxBC zBcXk@Ivz^=%`l;8J3ho{;ovI6xcc%O@qpWfFl{9dfaTaQcGj#qlq<#!svjYXj(aDv z{51A9!;sMpwvcG=sLZ{jrkA3RDuC#X?y}fO`;T)TYtvRRP{^>9Gw152f%j~rOeDRnJ z6vuz$J-gZ0zIQ_c2>Z6$($+X;zV%gK00*maxK;gr`g`BYCfQT7;cKt8fj#%M!9V`7 z>oEX@%g*)=-(rn5s(fu_{o8C~#oB9k<^_8g9&(5qgWA}awi-89lmYBCDcG89GW(*f z%9h_Zp4F2Db2t~uuNUBPfgjuyAaX(rvoS8Q0I%Ww0C)0^T%%wY*FK zF!OvPyd{kHi0?*#gVU+xYf>?y455_M&+s0=>_jYY3%JM00I=~I@{FIR>rN)TC@crm z3>~ZWhx&Nk>s~jZdTAS>LD5DGw5C!+4u^8ExayeR!!p{4u03$W~2&wAFgXQDyD z_p$r#w6D+G+E%n7#3@vaE5>B53*TCiZIMx=$3>N3f)eZHp=r3%Flm%I?+W06U2WX+ z(|X}=^c(2HUOAsWU$bPqW}mzYc~FMJ@zM zDjZQC7JRiaQea;a7Ls8fa}f>mrQ0*{oKzH41X#x?Zpuy6G{PT#C;(C)a}bI2_c84zEP<}k`ROqSb>^m zBou7@1W^heZ@w_y05K=!9#YK;BvT#vO51(mm0p=xQE`9(Y=Y?|nh3>N?+xdn7Qn?& zA~$76RcWbK7%kmMvJ7eBZ-%5~L&dph^oXa+{MUtOQ7sl%A21|n0EU1@rEHejj|S@j z9tRhHTvSfQMufjdl1f#uq%>X|4waY;FF80^I>-uO%g?ow5J+k#15~0C(hV@us5z){ z-#DMfNcdX^JnmhG_W}R*-+zC5*~?z$jzPyjIU}#zZMU6EABsP0g8GYJ{KB1w0S2U; zs}*q0MHgMtzCM!gp%Ay`A1ltEfO=)$Y^*Q!{*i7N63(1+JlDx!{O z&z|EJ0HBXBzA{zB7;kVq21ZJ_^SaU>eTdM!>T!~*Dp!=DUbu3x^uVqVNa#(!43ybt z7FInH@1@hDhSh}iQDuEdksVdq^o1EMYUrtOH-1*-;-bbq7C18qpep#n>8v}xT0(# z`?Nii03pE1Az3+p^PAtCl5!8_1HhKU7r-*?`8FN{WSDLR89-nT2mxyF9$^%|v!E^H z3NJv<9O5898vx=zH9MpIjuNk9lgXxF?Pr_#re z9)Q0Z$_91JdKm9w0JR)^!O(xWX+1v2cjDYoC%GXHJP$V9pu(PR19;2#3xtC2#_Rs% z-X|nP*w6LXU#}y-pZ?bq8q>vh_FSDv)BOiyT6el1Tst>hCx?|i>silw_DnZ~_}sFZ z|Np}#o7;-A|6cNdeR}adw!*E ze5-MBahgU4P;I(InU-vi5%z3#veK`LPyD62MG?g^^dsgfz+8EzJVn72;gtTZ;;hji zo;?U1%l3%*@~Jos{%2FuEq6a2cR zQwrX7*WD-)=a+}?9wA5}@WJtTjUV`U;lsu2ru?eUIR~&TFD*M*4)S=vx~-n3%lg!E zRi82IUwH@XiJx&BFBPu@bzUu2(st%xW$8n&(m#)Kfsgy~YEFZ4;=Qn4@psd`bL%w< z*R=jAa4O|*mTw66%A3{`@nBF1DffxXrLtOSg;^s|iJ6KWjupVdiLr6nVcPDR{j&L^ z7A$4JW!pn7I8e}#o_^|ScVlc^C{&fy>PD6giiwT4>MM&`=Ae){I|c3E}=lTKCEj*2ZEbb^asV4}OXzB)8)VFEjS zeIwm9*IXmqvxZ=eWnv*+#%k6&ZMRdEqTg#W4scvmz`+aGWF7%~q3qjdpMBhG9LtSO zk%#kt`AhriSHEf}o_M02a>^-g??KkXvKr1}RRUCjNaxBcue9^dJI`&FiZtLz*=eVp z+-n>MIEeQE5_Qn=aj0V*n-!rSXqoEZq`;f?i?YbUT)LHd`Q1VzRS9<(KlS^MX7&+{2X}GbdXXtZ3vMoV}Kj{ORxInK7Z~SBg0`(Eo8xg z-$Sr3wA0~{&dqWiL@ZiKNYH+&7cC!O2>WBdAe=}25@xfnXnnD-@m{nI)AaQ!Yc(8W z8>!!tiA-Gc=)dsup5%7|-J+6JCkKTI7%XG>&X{Hzwp7oU+U=Dge`!it>e*#tuDIT_ z3=lB_)FOPwcfwVZG7&gf6iJ#hVuR7Se!`cow)eboN2l5ykDYYp&Gymb7TQ{Cj8AQt z+gfY%x%0n#%5~#99;(JS9l5p3XJS?P0pLCLr7zjjr=4cAU+@B(eZb_PWusyIs376M zF1y%dtw6}|H^13NmMpQSE2lo-AZ%puV(Zz=Ta$unvdS$Qz6buY`i?MJu!Qdd=bwMR zO}F8`0M~K@EP~1pKY18Z=BEi*A+7O()aiuE-9n=Rt^jr_;GD>Ho5F2QwJnUtn&9hH zuKSPw_>YZ>Jk&FltT5DTQ+w!UO=UhFW7yqQ!H$08F^!-aa1UWW@sqX#`p962giQDrylHC&wBRkv`;U-*FOEgiUD?jgMWSBid&3P1d!M9%Y=+sBlRjR zP8!W5nhd9=6PpJ{LB`DZ7vf^{Zb#5Bke|o-aqW2KeKiz^Po=Pwm9k(vO15V`TWt(J zL4p!)o1{MXe6f0nfLBJ*NJ}Eb!Bmi>;t%+f;qz4Rr(jTFRI;sC(Mp6K`Iy3dIzMWe zhAqv3An&uVs6=!^kJ?Ay$KS$({O?u*!B{4Y7za@Y?hV2z_}cLsKk#oO#HQP)u;L~N zFACp1*a`j91NHT+ER%?XdcA~K)%jMRbzUC&9>+)(%B1Q`3-9asTHXo`^Y)-^f#s#p zPWc#k64jl7v6v;S1?>@Hru*){ze=lbR5+;b;hGRcp==X?82Ch5rWE+7@U6w0rZZll zMH!%0J2WH!CRIeSpjr}olJ9$1(hd}w70AXttClsg!4p{c#^=K}s$vl^Akf`99W=!C z)?HU%pRWiMIvO5+hN}X`-&#ou-TcEo4P%PDX@TQ>6!a5M_>c(r9321v3ko#Kd1H*; zsE*SziJ;Se(Qtp)k6olisVN&huETO7WC`VLv9WqM(n?6RHgqVmptvmSaG6*Mpko1uwa36Y#>A^ zKp&KUKmPHL-Q*szi~(f{eo-H+N7M&?0}xSFh&*GT!h7$%*OlkS8*l7BLtTQdWAJ*( zOI~8zZ@;|^3T1#{zTJ#?;M>}w6` z6tL_0`fx}eSRP#P8XVABcY3^A=`j*lHt=BrBM|0;OZx%iBeOlCJ#kQR_ffYi4vuJ( zl%eBqZdglIs1B%XQ+(gmXFYDrS!0bbxKfp<%3Hh-NY95A+HnAtGQjH?#q{(+(gX`v zRVx!Pr`EYwo_?`UrU*qLNRJOE%Z`pn&^6pLVluOI4P{uEU}n=%go^L7X{voaRTYv>2#paQLOai+BG*mY!_X2zrFRS1>-vHp2bhwFMoHpz3b@h zZ7TMzH|$`4yyii>^{(-S{(1MCcC_FA@qU+I+U%FV+?N017gpj~S@prCXP<2azAPSj zq)nF%UV5p`s=#AWhZdiZRrSB-vB%o-3MLI-m%`8dK$$~vKqyb0>HP=K8 zz~=JHFQ1k?G#SD{P6V1E|D)yShd=z`n0dwZrdtj9`OklTOkjo2CvvYjEC!^Tsd`Yu zcTAKc>IOgKDbvvm=Z>X(1RVU)kA5_v>)_bQ`03JPj3j0`2?%vBfG-vf6$_2;xFrP6Vt*gA@5f^rpP|cUA-()g^IX`f)@AJ6O z5o*O{0DpCTXs4la3j+o6S^95Q?l|5Dm3=P6WDNmT-ptpb49${j`Z6wqGLdzGAJpOV zp7%WGL}5AL7{I|l{_&4)FF_7I9K_LaP_BS}1XYN;i-88610hB+@SJnbIW83KuDkB) z1}mnIfe41{B1{w8Ci2Nancxn4?&(T_{K3FHRaDTCe#Cz)+fo*KZry*_jx_$Su0Y_R zmxsq>|00+RVLYY3Mn!IXps?QDwJfflaMux#jRB>sm8D3R0|(m_m7fZuRUxT^9Lka4 z4d%==hh^j8(*l%{FQ z(>Yzq17=!KA*RE+;yyuhrC*s?CUc7j2~ExllZ7dvv^1|}$EQWeUxa)09*Cu?2;(bF zRa1|z2}CW0pamygWn*5Z%Zi~#UEV+O*+1I<`Ox0B_Bhpm|MdCG?fq}v+3gKD6{P^HME_pZ~Er6-Zfn#u*bjZ_W`%*wVAka{CnKHj{u5(5ER};AE{yi8NjX6o6cQ zZWBnDXr9NDCM*Yp)2SFEP=R6#As;Wi@WMvLB94Q~3imjk>o=A6L_(qhn&Ete;e_H2 ziYn#{dEx$d{29$~{4KZK;*@E)N0cYN^Wc5t36PFyjHiuEOy;`H^K4@p4GU7c6&wAF?oG(<=gSjG$yo8WY6=^wuQ(vu$6euQIXYI8^SWWtf z)rZUD^?V9rV7X8wALrmdWtdNFA%)}N-)8<{DvnV9KK-l;DqUWv?lTLY)1ORsM8zQf zc3ZD4UFzD>eb@Ad>-(%bUQ64N`m%X%3MZ}4ABQx#eb(~fz3R?0CWS$wHl^vG(X4&9} z3I)fkgs1Wa7Nj2(AIo1f4mRYBin**9t-0D zqvsIRt4#A=mI)Aaby>D!{*u{BM63QUg{Eb4eSe3% zetljwBf9V3o^+owF8Fj<@%VLi@vkqiPv0c?KT+mczn6r{>5k7ssC}X?X&yN_TVQ^v-SfIG#J7VtUd z*c$OpMOhS5AuX0Me&hY2pp5E9e4R5j{_uR!QX(Uwb)8BkE6Fd%}sVGlJ5*$=IZ zd$U;^o!NIHR<_D4^9p2P0`O@A@G3Q*Uxbo;Q3Z$anqY&K2=gkr(n(%;Jwg(Nc6^_T zHC8%-7Auk!11A$3FgiIZ6HelmtUORjsWvA};p{`&s&GWQ1XkF45P#2_ zHQPzMZ@THG0DL)!!chfE5BSJ7&9=`z;ybZ9K)|vJZ7qY)Y|9Fy6%4YIay3K_QdQ=j z!%5b!4pdBod2k_R>+_t%c%+>u9;x`%RTP>JzUSLtT$w=l$$N^cYWkCY?7?$?l5g#s zG`3g#26X3jI7l;n=8uCT>IC2rsj}VyhS|PxZM?5)WEdYJPd~l1`h0eH2x@;_9W1gj zg=Y~BCMzWCPxRb~$D|)8Iy$pX#ayjY4#x|K?8w>ln4hC4tV`xC$;!sUbxwbHoY16S z@HBxGbY|&?&-}A2Nn@rac;&F2@a3!RuimxmxZoh{^WXZD9sBz2rv(neo;!C9JN9+k z*?)ZVPxg&*s0a~O`4h+QZvXrrSK5#MZlEh1gbiMJq0N5b3vKyD)1%lJzU?-rT7)Wa z+1Y1zoz&CjR9~{5{K6NkXDinf2;td1(|gYKfZ(cY+7|F#t_KHu*0Y}VtYM>=K zKt8W-o@&UfyUP@Drr}^A^WQEJD@L7MLYDD<9!GhzzhJt9)IHTB&r+zr|K_OpQQSfRWQPSOaG|y z236P5Z|jen<;IF!07d{tra~WV0E^Xo^h@jg)29soEBvsz7=(UX@&x!{;Uh*lkSll? z*$$!?-mCg()pF5S4pPZZv`#dyx{X&364f+K1-U#&+Xzh4&&y4>ozptP{WCfx{rc)( zM>HDv$Kl7w^Vh`bm?p|MaDePyS${A_u7nXD_M;+MklHwTF@+w?h$^a-s{1gOJQtxc z+wN(ll|>ln8r`e`8(cmp`>#b17QshNgM$pR9l~;AKRN7KN0ncblON7Yr%Wu;PR`?8 zXKMR>5>+azKqi+O6UeY^f{L!N1iVGqT6BOOmrnIxtLSt0$n&&e^oc@?CBsrig`E#i$iIE;-B}LMY`e`i_KHIfb;?1kBG7>V*TFu7sAq!jA36h}9P?E^MZ1`8&NUsikxZwNVx9~dCvZfmS*TMf*% z^<**rW7Qn|_Mzo=&C;jsnq^B@Iuumng6>@17Y|52%KnTiP|GkU%+KwnH0(Yf*179+ zo4?=D(l4{$CiC7RTU2ae`b!J*VqK|go%h>=gFRavjeSR%2#tPns@2rb4$H$(s-GQc z!FTS}Vcu_x4nrVBA8-VC0|An0TKq}2Xc7Sw?}7QTIxXGS3rqw)T?L-XeCS8!`%w0I z;ZpPwvx6f|54QSj?%a*s{c+1R_}M+n=o2PW2MlFK3ctB_1h|FWgeZM7=C%P zq`p+i$1>ybIZ#vHL&id%x+)TNYdlSp*HdV#ne^;N?zu^z3fUA$~Ktmsfo5Vq)a8fGkZXAtkXgIv5#5)q~wu1-! zOcjG{5N!OEz$)Ab;W4Xg4Gj(ja57jIG~fqRVU%JzsjL*NR$AV9*r<7ISwkNx@NgW` z4T`Vo_@}Fa_Qcy$V1%ku-wbsy+&|Jp zp78p*>#lPlMga?P9DV~Hy7?neosd3*JM#p1IE3F4B!jInXm4><~6?En3Oo>R*_^uQ+&($o-UN z$*L5l;o4B?!^z@Hq3$#41|Zo(G6$)BHPqv{ppX#e%GD{3i+Vuz8}>SYSeG}(1fI(= zL)D8&A9-SERlIhJ!s=YA2z6}J@*|YhRW9j5iPc9*2#feHe!b_4KwrlVlbia;l9QY; z5t7d=)g6{aRN?t+b?OIA&Y8*_fnYCRpBRU6K2-fZs2(t$o58_k;v_NTWgL~RBfqEP zj>fjHoO**D|Mp#MuzG$DJ~24v@&EX5m)i&4v5RfI;T)SzgNpD2Z{OMe?|)utUpry{ zZgb$oKJgcO*jK*w2m8NUjecB-m11}%3{RgE0jkBPPCCivy!_=htLpp9fAuSC*Gh2O zzz#du;8j;y&sMJWpis|f3W{M<-G8uWJ?mM|de*a^O}}9e!IiO(;8ZN1yA(x=yPSoI zuFQBFCOyevo$n^EOY6LD?g*X<8y|cWoR{58^R2~&x1CzS=DBfEO#hPMp7m@d&H0QA z?>AFlrvg9<9dc8J9Q!!a!Qb$2LIi`)u}u&}D7pWH~UUn)f%j-H<;^i=r^`iBc!#d_c|7QK> z$1!j4VdL{<%a=K(pwTSBC{Vs=Lp)yZNr5EG6m_7gP?w(~UY9&)q~Zt5+9P!cS?`Z` zdDTWwiE3$4Dab+qfC!l?n;&+BCsal8Jt~jr#AWC~-5XGElAy(>Q6zAW1Q}2OVKAu> z>T7jw1gQks7e=Zg=$tmvs!%{+X(B)t7b~gh*h8@$0Z9G$2l*smLEVYyRm)g9I*Rpli9I)$ef3)Mc7u|NPU3}l; z6)0R5-rH!sIrg#_Zf=VoURL$p5!+(Zwe44z-DmeMc`7-#|Dr2w zv28zpExYOBpW6moJlEEK=aF{CKYYzbo_ac-`};rHK7EhcrFTEvk*CA=+oHPOJ+`d6 z_TO30X3}yf)B}XiUdv%|4-odOXFcm#&wAFgnJ~`%YS&k?5|qy7`au6%=9Xoc>DF`V zGF?DxA&yPwVq9IATF%V$u>(F9g*99&o28S7);HAw7v=9nG#JXS9P5HT>)C2yPN33` zK3DaP5+vn72BycVpOj1izac6jL?irDFsMqvJZ%_x1=CZ%E{|tA^apE%KmtK0v>Ggv z2yTw?NMu3W03!+#o;Y#tQ$2jLIzfq8V1C+j?G zR6gY>^yTWdT0BP8m&$qQ&sGSi5K=jp!at34sdYe8h{*J{4swAPkKu9sS#(H#PH`3e z)GDgN`%q@0NQQ=n6P2L~-gsVFgJO))U_&eqM&Q)2sd!?w>vE;I&6P3LHiH#09O#d# zGvBb_XkVTvYywfOl~Yl1k*af8xSoL0F>Cchm_M;`vtWwaNlmit2B@OvR!)Vr9qzR_U1wZ9BF5AZ2tue_n;7lAt0Z- z$#d<9BaX16k3QPPWY##wI>6p=!wqgPLZrdH4*{L#RP}-Th^r!^DwTDGYxRj54azj! zBhms4+-|$=T-s0!vR-&B_QAR0^2_b<>hl2fiW&$SAzx?v$=e_E)-4^eRli!J3~f9749X4(^12R{W#`{T#D$=HUL&n#B1aRHmTA5N0Cj1#6Cj#gy;V>gT(qr=ySrNm?!jGx1qkkL z!6mr61t++>yL)3n0tENQ-QAmp)BiqocilQ~{YZ7OdaXIf7~fcz=jJSk86$ zczl{}_v{G({Ui@ur)O)rdOQ9B>fZBgKlzbX{`O4Z`!Lbg;^gOe?vG@;^**5VnEBB& zGo>$TE$h^t35MA9IvHSWpItA8Jy+!0Hwia=-`PlkMnW}Cvi?uDzAlxY^(c(8g${!x2TF**M1S;)7g8~Dzs_^aBXGvo(xQK z*m6&*y(u%i(AOnta1Cr-k6#Z2_@4zjo=nn(8{;wH}l(mHnnrN_FfcEd!n=P`tq3C9|&;P#S*vVcV(hW}YUgz*2)yZDLq?EE}#nd8Mu_b}@xKD_YH! zyU54P}L20yKEvTd9h7$xHGaOnWF48`>WCFk`jTr1P%hW+4ELyNi!A+ z)6G5KIx;8`k!M`75DwUI>Wtig)k=u^eNlm>qeu0u*XD>(LBWJ9C^-@b7Wz8}8ygh$ zw%#gRvJ9!IdFG5m$;G1T;o~p$)QxMTCk$>xhR*tYUXv=Q%rZprA;%!tXX-KA^iPgK zCiFIc$Dsyk$r9eKAWq-5tpHGGlcmo0+7E~wbm;vg|NZZOz0ZW3&GxRwV=D}X20MaR z9kTFEVWHU$ZJE9MpV=F=0k^4_T_irg-3J$uh*Ub$$*H;Uf}W|5&Aj)#dSsB)s>F0X zF=!VZFhhB+Q2Vd{%OU_ByZ@!)BK(AsVL)pK27qRG4L~eKMQPM(?pLB|oTePWs6#1W zdK-0Qg`=dUzAiTtRd zWsq95<2I2sXc8W}_!P%G>4A_+O{sG}p%|$9AWDf8`XH48COly`Y~zk)6|jE*Z%XFx z7r`2!ib)tbEUC(LMVGK>m$P?CzY5JX(SS;fl6GoZ+$3?KO}Lp($DIT0^=~F(LOOhq zqqhH=>6M{bruUfx?nAk{6XJ2c(J2N{hNtZ>9f{`N|1IILA^kG{{eAaBXtn(Pj48cX z7hKC3_s- z+A&Ec_aD=0J6Y#BXBO)c?Uz{jJ(>pOo9?P4!Ie2@jdTQhU5fL__}{a3uXzRrRD%!4 zK|>EJ{_X4F4)8_#yY(nVW3EHnY?is@(53!u3;62lvMHg@!4u-MApuBTuLgqyAd_B)WQ=aA};?&?ktN7q$KJC=Q!o7!6xsCm!KY-_AnA2BZN!02M} zD^+GEQ?(Yf%wZf2Y0Zpons3hgmoiI8L@b{9%=mCe_YA9EN))iI93kd^1B=OTBBhG$ zU&#?}c?y(H;eE|HIVw$HYCtile!QvdkomO88Ov}t>}zSH5mdU&K7prm`+j`)Pg>9d zMWYp(O@>+*PxFRMhB>S-_Hc55BF^rab=r3R*sF)ol1X4*wNLot2f$P~@1TA#9dhxR z)H2|L&RAQJPQ8qM>at!R7|-Vf_uZvdmLHvi(n_wk>>2*c&juKS*N zMwFtm*-_6(D`2M9XDrD{Eg{gy-~Vo zTz#<0hDz+eFER8~;jW0U*7oz~x*DW_JU&|ZOmLGLRoIx@^`A&*S{4|cHHa}j`MOZ_ z&yO{o5T+{j%eXPwt!k5Bz#B zY0a`}SM<}O7`7&e$NtkkJS-~T#h-Wo{O3RP=T4CRUi%%#?r;~g-iN2*xUW0`Y^P}7 zBo=@WSA=PMJM~=A!3;tlPXs3d8X6Uv?yum-UnFq0W8c!qhZF~ARMXh}*TB6aB(Cd0 zO2?5iWkCDpxCv4;uIqn%Tk8Wx3r9WPcZ0Ur{!|rj`iNBM7bLu4tZMT7QcDr3SgkbL z6I4!oMAi>$B*l7&bb$cvzSpJXh#t$ACgY}F!EHty`y~QY#b6SBw4 zuJExm){&eDXp-j3HFtgUz6Cq}*8Odj4sGMSlTh~#EpnH)8_XO5iNXrZe;sPYV&38^ zHU>W|yQ)aA4F{Z_A4*DP3?C%^c;27^T*Q2;ERz}K#h|RJNB3xRE7~W@(30DVb~J56 zKAk~u8Gz|elE{l-sIw^oCZEeHH-L6|)V_2qm00Lf%N$opCJT_q83Jt;g ze4cJ1hdBzDb=w_@RsLrVI1=@-!(>`Zy*N;PNM}yD;$7)Mpt*i_t2)q?Qp`T?!MXNb&AB|9 z8~@k-Dc>c%oJT=og1(YXd+7&W8J0qvsK`cjqTgqij0#@$#^l$H4ewf-=a*J8ZwhI_ zI$@BWsU64j;l>R(7UigzpzT!7g{eaozVvlhcXMB&>W>BatsiTCPIvzpb4ui_MpW?; zHzSIxXqigJatWqEp*>V2kA|i-TJcJ?&Cahw^gd|Zn_`y9pTS%XXOry2@ET!-lN-Z& zHg9_UNP?TIGpo)zQD_g5l;Wgx$6WTRse}^WV&|h1bZr1T_n+Y}Nc3uF<{}Op8w3$& z5WexHYSF~yMA^#D^f0*pE6wboKn3(U7OYS;X{z6dBK~>Z5aBe2!dBUawaW#E)>9G1oPl?nUQapQ-D)W$L~jM7?+iU3rod zx@a++HCz?&s`bzC^`0m>K0NJ2QeG~GS)#F%it9|&=TYhS?U8O-=fCaKUnELl{1pGb zqbE$C1`S0`!e8(&dC_j^`1P#z=+ef9=eP;B$?t8O;OR{&@;3h=avCYvIS|S6S7Mb4 z+AaX=_H@(t%KZg_N_aa6)*k$@jQKEpYgvA@wI0B!>v*ts@(-BS&*DcmfyW?|y9-m0Vd9^hcCOj<&$E44gYV3r*$724j_Fznu2} z#b+Q3IZ?n#vfK(&zbksLG z%E=FI!Kxs*cZI$i)=a_CYeuj}!T)5|%z~-TJk-Ifu7#8tGU*edZfl}Bai6RtFky`U z*Bu6rhbL2B3HI&$hvin*qjF$tIvA*3-#D;yBXQd2cU1S7e0SNYTuS&loI-69_w_hT-Uqs`_VW4`SUS~E zn+6)2QVRUg7COk$Ki~YUoZ!P&cXPGX+ZGarYJb;hccRfY`}!lUmiPZANB;MywN>fg z*|TMimKJrr_FvwG|5mA8_>#3v5=h95N2CV5V9(W^hU}vHXF58CpZ3O04U0ieclxP`%xG)*Tdw3?)z?qs~9wUr1Q2T!|nmzYym{A(-J*;SclR?d6lsXNjRy< zrrBSfeKMcORvG~&C>Ti;?V;x0vkO<%Jy8eKT&fk7lI=OV6$QU0V=p2D>;OM<_pehP zYIj-jNpJMF;a5UA*b#d%Ng$aMlu@aQvf0Pm+kMb|cr%6}MK4YX((ODP-nWogB^2`= z<_eQNm($WV&$_ZdU9k-X4e^q61txwANnO-x_;|Nk;GWmTdJh!#Ru7?zes8Ph8naev zYv_Y(M6sh>&%E4#*At1s3GRFT9Mt2|6hU5oV_q<^-~F;ZJ-%7EyUKA+%3WtImnGAT zm-73ok)?E0kqUx0X~ikFtw7jI&%t5##R#dRbAF+@BSfNP>!H^Y71tb%CqzhOQtJ8G zd;%2+HnbE!2bXRT6BHqzfcYL zAj()c>YlbtEdvWxKR=8A>4mE5!tg34hH%D61KTgezpJqv*xwuxnHy7@+$K3f|`nOLjtUZP~T z=3lur(HiSx?Ilc(l2T z7r4hMImidFC-uE_J&g4^?>dXS%KQHm(f_;tfsi87!n#?SKDTnkEfKr(@sG8jOyS2& zy?D9^518w&MME4bg0Zo8ul()pSj15T<2A9(Z!|sQM?((@C|@SER=^Oy<-Dne@_{7! zdwfXA`=9BGpl{Q~-@a>xGMN-quCWtxUHj*ilLZHOL-D?D>ESRY9^Dy)2x)%y$*Fz7`HfW>*VG8GUIZJ11Fznd-4Yo$V$Y=p*+!6yz? zZ7qnl=C-$05AZh}#cFyR;Q6bto&3GXcBw2VDZm0yuX>_$*&~Jz%>!*+>l*;@doi*2 zwD^IhntdeizyB=p-srwIugvZqLLHckhZ5;r#*<_D!r9jlz-v9HMhkR7zd%C9ROkz%G-&BR*lRTGZ((vF4WQF2T-}~qi7s2$k zZr?O7rGd#pouMN2A3{m=SFN=;zD?_`=Mg?K2Hzc2BQSzBC?pK$ZwdV zV&yR5DtIt%MVmLoP?Qp+69h*ve|C(13a2C^lDj2tK!6<{4~yEskGJU${)%?UXeYoQ z)r*RZ(Q0nfdukV6%%Bh?gvM|ErD_aT{7*rsAAh)2s_1Q8Q@_ZL|2bzAH&PZf3bg1A z>`)Mt^)FXvw7DUQU(X;2(AQ;j=N(!#ziMv(6q&qdBB_dvgJA&-b+L6Z5b4XfR=Dv~ z8sT!T)jK6p?w28W`9UpXOZ~T{lsvE4(F6EyC-C_V+<}sUDj*Py{A9S)$=spFb(xS=>yGAH z_S|V8FgKIgIv8p7K1rWvg-GGw*1Ok=Y9IRWBf)!(ezU!s{4w73!q91DU`ZcBa2WTdBuQWs0}i*8)y^_Iob%r`imuzAFjqcd zijftA{t9GiBkD2d3lO3Gp)RD*f9NnO8<$0Shq12(KgLsDQz}@RI?RTbY2(V3Q`y4G-sKFQY+cxe^A%dK~hF$C?&@!kYJVp zZ(NGRKz)wH^hVSe^ch0uTR9Sa66l7+iEb*v&!b{(hMpTd;Ud3f#W@(Jpvol`jq@8q zxvE56iQYX!u2yRm%e*pdWXPDH8mYmmPJ z>Qfy&*qw)VxSep7zrr35A!MKTpsAl|eU_UvVIV}F=}@JADv$zD)(NgqzF#AHQt}@? z^?meq6O>M_Gh-jXWFTFSb@VxkeIq84H0D&$aDascrzv#ys^*a?+pX4kkms0WSVo6H&0!(ZoiKbTCn`kSljoM!;mul*|{bnQ% zSDSytkO{-gXE>ff(G!qjnRFoPvmQSq77ZPmKQ`w^_tmS7DVGctB8FZSXqRyxp)OY($F zsnJKHFMARv9<1pM$b=EfcGcu}*V|^^~-G@F~d9};oBDosoekWX# z8_FUeISPq9X*oQL^O2F=44oIw-tkyBYjWw3Bfv$Ztgl^3tb)iZ8vl@O*Q8+*7WyzI zQoZjJ?)aI;?ujhng=I&fA6{_ott%Ar36t$gX0ejAR;KG8cjmb#OP_Gd-e>Sk(CK$+ z^S9EvjR2NuO(d`JnSrkbx<;j6?e5KTk{Haej9L7;G@7x0+X^z3spb{UQsH-1G1{p# zjB_oj6wR`)AHBVJCCsw6QY>C!swb|}%9smQMOcs@9aRLCGraX~c`qBrvpZ5r%?d1pB|iG99I{ z>Yv_5FRNhQdZEQW?}+S(YH*!R8y?Q}yjE48*HtC!`m@XpU_O$6jSNA#tvbz+jv?su z9e(IowZh1P!Zozt>6+4E?-KkY#mQc}6kl^|_NEj){kkj>$E=_e@ae)7m73ZPp5~o~ z+fu;UdC~uoLE7^uOB=5!ma%q8LhEeeslDPWdSMVe@9h;fpWG`)Gg;zFdirv;&7cV# zEOlVQ!g9FeZ%ooaWzkrc$p;Y4c;}7&C_JVhswdf9dA0P4m_WxD<+_|Z;$bG+ldQ2< zpik3zV~rJ(WmOGswd`8d__$XM#Xt)(?hui~A5TlzfSlKT@go!IG%M7-ws7U4^!W3) zhN>nsXtP0jJ-Ql?;o{QL?JvsSdbRAA!B00&mp{aWpFDhbD+_<-z6P6nHh#QMxj!D` zhVl~o?*F3peD6-!bTk;n)C&60w_4x7mCxp>o?bGv(|Xno%++lJd62Gc^_pX9&y?r+ z#z3Hrh+;mIlAdfSp;kG8kiKz11E6p-*_j4G&TG)KuE~?4v$yvJAGij2C@1fG9vONz z{J)oI`)7>3HRIpah?Oubd-B4C)B|scKLRCYsbLy4QL@}EO6{+4#*|WS>+@2E1|Tj5 zcWxQ=iB9n;B$KLo!rV*AoA&C-(ZNs9txR zO{2famixm~=sK?me;At^KQig2KYhX%R!n94*r5Eu@DQjl zpW4%6!+SGi}Gr^()CeAENL$!h>qVF%v z3h%;Gje|lV)mh#}NIkXQ&I1-NUeRZfyb}g-cVM%~TLZIHn*;}+12A45PrAt7g2MZ| zn5G5iAM2`nwGKEdF_MyRmODY7aadRKD}a@2bFV%+lML-Lr%5@dF_8+5O8AFOh%x>u-E)vD4?Q>B&E1jNJ7Erx9cs3`BKQzs##`o1HmaaKpw5E zZjM6seXdb*teBx4SiGU z`F|{yuzFF3-JwWow7(pq5T`7PlY?}D(M$PtD@d?jwFD(L{AAQAdR6CO`GWg}32{EEma|iz+Ru`G2SJwHcsHoo-^Pbc^4c)az%oeB37MZ*@c>Ov%&^hn&_xIUM7hfl*|KDEqzmHnx%y2$0f6YYLM@WA+BYiN*QNNYaug|1u?>ntv$~ zjM6TVrgZFb=AHEU{9%B*ktACfKpx{Lw1LEnYxG}EZy4h2s8BZaZzlQ0fz64`TsX}c z`PEOEI;-0joEcGS->R0B(gdaHXCpY-jawJxB`>9UOT@?9Li1&&_bx*jA6k`6lIqz1 zzzD5~rrf|ZV>?|^-Md|&F&4)zQL${44=}ZH6f++SZ0ko~Il_VVyc-rvHH*}kY zFj<)2WyWUHd(SPu8&>2>kmhfe^V)}-u&6Hx+y)I`6-q!_JZ5Dnt8ZTuQNP5vgx+Ev z?XHcH4*hb$lFk|!l)Q)nmb zV`5}Ty0Bs{;Xk2U&^waE-WMjfn_mu$NqlC40u7!L9m2}!5D!?TBz+-c;*D-4IqYnK zda#v?>@&1ByMNi??{Ap8k#eUsrI26?$S^(}K7B&wtIV(rV35%M%sRJs*yU#p=5F&f_NY07Jcg zSDZqt`eOHXzCgS7X7^W%KOCse8V#3BSua0M0-123>wv7Tx)^zFGZaZ+rh4co{GV0Qd2Q_iecx!*oBd4ST$@)01ks5ehx5i}76wJSU@G zHGn+>FG~jFFfiLMJ_5ifpfOI=iyBm?Hs5q%hqJo&crbY6uGXqL!Sv^ReIVHG=@k;* zaM>b;yk$>#{fSD{# zys1_i{Fcjnak{*#sKWWj4OZ%*TGz$jn>?Xz;;Rt^fcld!rg>}1TyQH_lu{fr$kHf+ zzEODFs}$u|8uJmh&s-^dm&~sVp?^q%ZyF69)-Q%HjMYq}7;y9YDX z>>wzK`rNATCE-=^;cSKmwSWrXEi{aWraG`K%kS=4T zx=#7gP5o72u@FapS%69QVLNK88qLN$WRAkdGtn)amStbA!4BDiIYRF1&NlxgVdVYH zN@m*o5{l+^aq7P_8`#c+H2w3Ea&ZqK{tSYqa5=Z z-NY3#BfCXQLbd8wyLgq@TGV_0vpb)G*zM!_jP@Y1Ogp3vVFkQxP||zR1{6HlegryE zR*0TaYfsuBE-j*YOCjEyQ*e4w8?QKFR7n^t%cnv&M$^<(1l@%>z+J#Ek9@_el3*aa zORQGqBmfeh4UqHAVNiuwR);2539Wjp(z&`*fl#3d2Z{zc(4wf{otVe}w4xyo+zLBW z9d3`lZzD1*4E}V1GAI7C_=$qMvzJ}gPygo@Is7%|A|~pgy7uIPAs-;c1=4`6Ni4X1 z`kqyFyyqKMFIaKbQFzm+`T-SIfqJM!dF{Ox*7Xyud2zS9c9$O@;ZCM4@4}_QTXz|A zRql&GkXA!%0mdl_Z>LdVNfuTz-r*_y)_Sd2X-dVVm6E0iGZFvyTUj8B+I2dNJl9au z&iqNci%D-{f{4fFjK|6k5uuY<(hI4pb@xeEzTEDz-qw&``7gJNkE;n{(<62t;9O~s zwrLPP()&_pCYpAoqFQ0LRzTJsP`Ja%>?LRKkL5yw7t#nxEqS$d;li|Mijeb zsMdD`H9I$22@cKcdh&K)mBxGxXg0!WQ+* zb@TbPc>PTMI|vZ^8#38?p^1jXF=e0^#4TSl7HmMPJe}*Bg+~;9I^VO(!9Tru$>$_d zS`O_p?$TnPsyT=mmxWI{N$CI$9v2!Xt~h@53+#E|jkFd?4gmpJKw zaJ`XP2vHBVV{B|Hlm>+!ws5ItkM!?H&2J{h{kuE+BW#XEuAiY;ZuGq{{F{X1ckuwD z&PDg+xFM<*(CWN@K+89>FA47)#w=3d{%{sPluWlX)`>p(J%A;0F@-<&Fn<<|r<=?H zULvo*cv{Lg%ggG`_?1ErY6~oiz2+O(S;C1B_+64{m27 z5hD*~1zN^1iO=uF~!4_d};@#!iFrunRAHM z4~aY;7E!|s$?oM|UHLOq=nNXUflz%~X9#Zi57rY4M~+~%v2j}NPxVrB^`VGDt!bF} zNzbHIj%T&)7}ZD}Pn*<{WM)o2^Vu+pa28T89}vhOs;T@=%YTNz^;0mp$I5_Pf+&2* znu1`$s;#!vr_lVCoY~|O zH-7TC!b6=O_!KO1UQ5~4zj^a|v5^;GzPhD7dgXkb`G`rmKD4CkYQIRm5LEtgKkf1E ziAlMfbJ<9baKT(|lqTm@}{jht+T5 zu3Ebq(ecr>WQW+Sy%WfEe>XUsBnZ!7>N~F1FsAcQ zMG5fAd$q*Zs#MkrM;>QeVDL~8v+BFXeufz&(3aLch4;vVA^H2@f$qzTlR|yiC?}=#kI_t;#q^dS4n{X zQsbpU258oGrZS{~?ELe&VoHIZ@|9t59+Mir7E^Xmq2fU0*g}Ui;RNtkS=E*a9b8yA zzer5eaTL~=&-`@>%TNG8hgQ)b3Bxa#LN*#Xl|)%9vBjv1*Dor^y0FcP_(ER&G$o(D zl#wSyOBqWT3Gk`K^MA%gy@D8u3OC&5zh|w^_ZQgy4uP2Zkn*NfWS2D2iwjtM}uW~1bnHqXn&DA zJ+cVqoO2r;qYm+-zICUDRl6;2eF;)zN@%000u}gt6@&_fgaF!WbuvL%>2hsZj%$bHhz-{(PwnINv*Ozv|GDPCkAE`Hs zbMp%@4wVlT-?|#fffG?r(1fOh{s{qXnE}Q7dAkQ`)kFuosin&pL%449l)d|B{a{)* zJ%;4@(>dW9rLM=jkwQB^724+QwJD`}?uiu6&bK6GJ%bZ7Q)9)RNFOP~qAP1-7_1zg zYIappTix)s8M=jY*B1niuRsY>l~!8K=)%nyZUfHLzibKC*(a{PsXX7^#!2p9=p{@! z!-9l!Cb_Otzh?~MRhr|dU#ln#MV276d-f9BbdTG3_!d)-IeI4wL>k8JlDEdGdSXZ6XxTba z;G|J{+0crV(yf4bxe`V)2w`ww7?Z6LHNdqn6osQd*3ffvc7TWpy#1yfx{Hl~bd#L_ zUhf-s1$OeC$hPl2%@JXaa+N1!=rPV^Yx~tvf*7CQ7=c96V0PSSBY#LAbDXnRx9y;&Hh}vh0`RS~1g3~ju zGr9tZ41edJfU6TDU(_WuFqBsF7)oY+zr#y`zrX#tE#*Xqk@~7^F}9MJp2VeUeM`pl zP4#g9Yi!60riohHM3^4)Uw1B3h zjdAYw+hFO`OO!|a6&qmEEK2=T|F8(r&b3^T^Vt>q@9%~!yM&x!zsYTeA4dm5ZB+$% z1fup&zhY3-%|CiKlvUG`tyBp*R$G5|kfKD9f-$}Gn0T7OK=BrbTlLZ`LxVma-&~aH zCA8*+I;>`a!D117l3_U*D9gTu6kcq4~IF6Aj6dog6iyPPx|7?`0O zKc^r1a=!mqYabR^-)$GA#U4K{e$@MJv0rrXU6{D?5t07(JNNGf2FrddY^nwKDKVL> z{G$kT8GGL&U0z!h3*6|~-Y!;VY5#a#q5)lC#@h=A6f-%IIyFT2KaAoUbE3l^i=J92Uq(p0f6vxhuBJxtKP4U#%`3@~2b7$xrz zF`ehxIAblb{$Uyq3&LWWa6I^Le{h~@;~YJoi5a!fTuHj1z+MMMJx;JALSHQzg1red z2VHTz84Sg);WEak9mw zmeu~aAir}9yqs&|AM@Q>*wLc8ePxUy|7W7}cF0ie!?twXD0ItDVF^+x&;oC_HhjtK z<;;&p<0FfqrCq;%NdF~MnFINCFJtVLMoqS!i%~5X0=IN$|42v}KSe_Tjb&SYMS^x% zcxRzbSC8gaB78G^s;jiik3y6|4nzkT=gtEt(=H`{F%)F?V7n01;m_h7%h;dF*&;1Y z95M%`SR=-eP?^gF!B>z~X+iLpe&w2g@0u|OW_bqIcyI`oRkRusA+MI1bpA)06D!TQ5Tup|OL74RJy`ko?T(e+JjwfskNIB5b^xE6QxpD{rBa z2V^?K%Ik@$vNZ^C4`y(;*bPET(r7QjYYFg3gh3?vW9ERe?X+}$c?<9r%hicm}M7}QgS1)QOHVW8`062EU*-b@j6lCb;7plr9MH+C+w{j{%iq&?h;(y1i1PIOFx$xkM3w8c`)jK5uZy^B2A@+N&=qSG z=Wu3aHd7g+szdZ(e2ufkPebWGNJpq+p!C(Ts*nc0%BMIPa5!JpEJVdQ0;>Ll2I zdp+xkG_)mLvNda(viXdV{zPw>`J@l*;h&g z7taptoP)&W(0*&$-^zI}#f0+^Jk}QO_Y#@EwS@x~f5OdOrTRT@ZW^AihfSRx=zt9O zXhIxb#BD_oe;E{#2haE^S$6$4tVVDw&#kV*|f|?XX1ZOQTp+|t{oS+ zynfq2-gPCMUD0dO=Wk!8Bo)5fxn|)N4~;cgPz1~Hf9-Hqp|iyCR~91T{2~!ew0LkF znKjfFG@45376sBuLxYg9m-nxg28PJ{Gj7@;L>vcpU`%9?K9PU5p3SSe&$K~7H2tIx zD81qDAJ{qa7hpiT@#F(qTJ_a)r!@Dy|8o*rJIB{HGHgBA>Rz@6LKLo6rG**x0&xv2 zdG~V*v;v&N)TX}n6JGJrGRk|Ke$OffXyl_fXnM}k&E|+-dz`UX%zDI`)1Ue4t3{kv zXTwXIdy01EU)Q>o`0a}6yYIlq2GCGK@YEiG1 z((S8cvS6U~hS*NJb6`8`^Yo6@vUBqN*%^hq_i9DKSKSjOhUckAa z4?2;5FUKv;;b%nus{Vy8b?4DnQ-DpUp2iui@mtc6e2DZRdD+F+#Y};IbHV4!$(d#W zv4NLow0C>>rt#l8OH3=bMID9lJzWH&BEuR3_>VNMf$8xz)p!I{)79}%q%|HrS;?g^ z8Y!1QBH`Y3gDk!2C5xe;oPR6D;KNvR&VA>L|Egz+4txE6(Z1!F4_ZGurx$zq)w@T2 zWLE zSQOIKW7;;if+`^;GdPUbwj^d|J@?fti>@VOYn*a{As1YcTSr=QB{-cvG%tT4f5M{Rd6}}dAiH$6A6nD6{2xkVb+{y z7##SJukU0V>o)T2lgP3WLQfa^=$9$BDOfTHo8eq4%DfOMl*{y_sT5h!9Ww~vb`XLA z_Y#d!0q5%aq}Tc}V9K!n`deb?;$oHJn;D2}p}bB{rIbO`^J2h{$Ve;$|2y7 zIpFVI)6?F~e6zCgsuw&up6%^+OT%e%tOC(zR!rnh4jS*M3C_QnmYE!?%k4^>2a~!2 z4FH=A)fGq~45Vg$2lc$RUe~1gbu>N@qaEtwTzY zk9`{)zbQo64ZBy!NQ3%YXX>&B+Ye9n`2Es+S^x^)izC`za-ib_D6D@P4l)qFR_b5Ez(4$b{<4*v8Vjpdc zJ>}r{S=RR?eI!9B#der8g%Q>+OwKmohY1n$jU4=P2exigjb6&sd zE!Pe=Rv8GUOI4T$bL2KY?#|t{yL#)4_C_KL-(spP!8KR#&iM^sC-gWPNb#rHv~y?K zx9m9r9w6Q9>-1C`78+{&3Cl#a0cp#oWU{c(49x$o&Q}A1=giTc`kiAuA)eQkFoMO~ z@**_%sOyU64H1teV|9_@xB`F0Q9%5($HWd*&DG<)VEiXC4Z96j5A!UP;&<+EARhF~mQ&s)j8XAGVi{?;h4UWBCwY3aVzN2}+oB}7ZH@1qyv4+8B ziYtylhtUo8aw&*UIp)h+lhHZxPa{Ss#UB96Rp^mj7GM)&L5b7NJ-)-qx|Y?rp)yj^ zPn__AJavz$(mM;Olg?jWJ@4ZTp^ybt(-LCvZ*(_?c@q+v-=eBA8D}a7$oy(M3Q%0S zNb=#;!=i511YI$9I;Z8u(+r)I|I43xsuv z@~a_L3mQF=su7#)fz^5mZ?M?-pjZEWzSkpxa%u+64?`nSyk&;Oh1Uu2WsMz?|6SGd zzW<@3IQSF=@!?&Et!AE#n`vvuDGn!NNMzsjWUd?+sy5&!N(dQYQrlrX<0sdd>sm++ zP=?|(@>B*Db0mHNj)EJ>zn(coISF7M+7h3!V5FGS)L*s#K}qOTs13dq#;Zqg)D zH}Z-pFG?wbw0C}f!}qNZP@u>A%Mp;-=my0XLTSdS>fm`+&YfYwAVo z^5%C3xsFVs&^wvzi89xh`ZoH!JIEq+mcr+lTKjOQ)n)$TVEM}Yx~^~Y^^%`oxM1nb z?!}HpA21nQc%<md(cP5Tc69}eU*yFL)48dC_%ZxPPke{4E8v2rKv`4?K`s3deOHpVl?aF@2GI;Nwygb32oUw$^Vx znv5u08PMj#_$!qW10}3eu7~ZucgN{@xz16>!G6&N#)Wg~x9pbcHp{m_TGd?bm^Q|^ zpM<+h_2KqBh`ECA*D3LR`^+R#ru~OoL4jWRh1){#2>#|>@NO*d+rRWh^^OpSB7_)A z)zCjvt;L0@h)6`9W>NWenUmL(CUuBp!URrUUA7P%v!6Cjj94N)yv>>u z{EufEl)trZL+LZm_IS|@-VD!nXesl5^#`rc_j!klylc3QsXz^D3kT-;R|{H|AiR^- zd;tR9lS!G6JHmSp3s|e&u84Rrk=S2D2t}r~!k{I$iBS?^OaOHezWraS@7H-16TWU!>@NZ0%&nXr*AXW` z5Mf+fJ1AuOW`mwDPfV{fe$-j?Db4;HltGh9dTYgIS zrP|F5(KV*=P=}`M9il2Dn~q1wFS8fg36x&b#wlp86XA+@wCL(5`;0L8B=xl~%>}W; zuLB+eMOlg@!hKVw2x&NPT`I#RsdktC-ktRx(N?kq-{nRRpMwm?5T$i(HbgO~pU=T} zgesOzRfK*=#b%Z)4^(6)VGxc>MC(`4hI~QtIYPd$0ja#L=htPAb4a_`_R-N1xCy(0 z+F_qsEU-B4N91jg?MXCq{b`5tKf+7PEtYeN3A|-lx&F#3M*Q`nSo&m&epx`d379#zZO$%k~Px6Dp)ZTw8OMJP=xEy)GmP z4vdtkAVRk$%lQXQ0UJ4hg1_K{6v(LOX_`^Q5ML8cF)ulIOoSZHpWIG-*zkpVAFbck z$pfR(35cSqu1r*9dJn-bngLMeU7?fm1%-DxP-8jhXP!q5+CVX@1LCOyQ@KV{o|tyd zne=xKG{Fg2ziknwX{5t$+(OlSDwfJQj@DhSK25n=5w5MEmfQ*TltAGkRYzie+bW{M zlkK5n3#0ZM*?ARZjfn%a+#(`v)b}J^1Y$ z@%{oHVpx%+MHLr87C*}icBBrI$Vzj2>Ghj~2LM`_Bwu{-#rFH(|K5%`;t1#Dz!wwV z6P0bh`OR_G^7XHOeP~ozjZ0## zs|<2j$C8s1sAxGs{k-;s5lm16mi@(+iYX|?6bGqM^WoeJCAk%-ES}NyeffJ$aK*~ z7ujW(T~o`E?FrCy zxE&P0E{toj>I1?uV6)l^>r@fC_lu~ui=<_`H*18CWy7`89GOkWy^X^VW>f$n>=VcD zVTT@juKmT^cCPx_*eb#gzh|Mn;@ETSL+{?z!8F=k_djK4pMR&FbMj#}9(&~R<@VLn zZm?6naPX+dp{wn&{d4Tf8y~W7J49DR zcu_T|hraZsFS##q2sH|5MMWVD-yfPl1CCb*KR^Qj9gfFm033vjfFQ>ofBcjcZ%qaj z6afYD+@yS48Ah0evP0S6-dV2r4Tu7O(gc7M4pK3Q-!K56sg^6wXZ<2?$RpB6y&+%B zBhvrKM?MnLhvF25GDrRDdrT|84(|hL@H%+S``5NM8VuvHxCY=C(#hcx(jgqw_L!F& z!0l*ob~Kwvn;#8C5+EZFxNp`k)4=DrA6%meyfzt*Bk)HZbpwQKf3T@2DpA(B7Wy0& ztyDx(5y&wmH!Vl)BQVw#zB?WI@|U{(<#Hn=0W1R$x#iZ|-1~51!%++;G4_|j_wC83 zsx-Xh;!zy>fM$e)$jg~Ew|3?ng;IUHLpCveb>v=zDB++&0@PZsJ_c&EZq9S41`*27tI@7=&Uw9l+#BtCjb$=k zAfe~ZpClk0uc_ld&ojWs8!9M^dq!EI?B-)`S=Afr0ctK9%J{IC`)CE1Oh8FF5kvW- z&7eKA{*izDurDw#dJN8i>I@1hv^4;tsqF3eY_%!F!3LEf4Q+fp?Oeg{SPLT%k~H6! z?}}+qPQ%cSa1D+pE7PE&MPBC2o;Aubj|bq7@xSSrp+;5ZWA*c4MFGOxnawCO`4m4a zv>r1>BDDB-Ju$YGYenL`CcA8Ab~16+xxLJvbBl)*+3c9_)~DAKxE?C(+3IHb;sN?h z$pv!IN1;d|FedkPs|Z2+XD+1C&w}bpK4U~a`ek#7r`#eioXW?t)|zVtKRfTmr{Ai5 zr)l&-%?nJ=RVASE0X2nv8kJ9@A8MH~-zZClN*!UuLm9kSX53>?1upt|6U?LEI}b6c z{$!RfMOIoCx%yDAqxY4EDQ$v(ny*|~q^u(d$mBC#58O%>r8?#>by4sJy*g9Mk!nrX zeumOevl`h^>%X6gEA?a?*rJxJR`}03_Nh+FD~pYdArf(1IqdfpgY!M?zecE+OFzrl z`0@s!6&XMWNM}V**O;@0&(}yzfz1OEz@Z=h@Q2P(^1>Is&^hGx*=HXY`V)oo+u#1y z{`#-~+U;4V)LTIXE5bpC{sAMA9}y}Kd%CJ9>_v#{Y`*#Cu8}QRu)viA^Q$stZIrs& zz{x*{%VYq`NxMQ3*C6Z7R0q&m$}*8c`*aod^o75fFZ@PX{rS(gx^hEE(L3+BBiX1E z;1D1VfN+Z~wy^#8-`}MTHCTTQ5w*TtqpCYE-=jj6D{a{r^AN1Kzw4?=GD5~8Uo1Cw zUk*I^)eo4U`SLLrN)k>Ez)e6QIF9hyEw|hf%Im>ug6`W5AZlC@fc8m{#WsqxId}zF z-PcO`h+m;3nNA`TLX?6*~f!8!D&R@}=`2Q7Df zS#`i`xJ^g_h5PyvaI1qb z!=y*XP?M=kk$%bdZ)rf0PW;7*K=|?>W>9e|m0yhys-o-UWW1KHTGY^`f&G=T5qhcE z(r2Kkp}ubeF;C26Y1TQ-Dg1<~uuc#dkDd9+U^#mu_fss7;k$&+ zGF$`a0u&KSjs|zpHb+w$=Abb@j_?e?YBV@InvJK;^BOn~ZU2~Kj%iE_c|f`3fEW2h zy#p}9Q2yfuggl=rP=;5{jiE)E=gAt{0O~c*AM3SSJ~Tuu0CV1kkRFal9Z?mf&T;}1 z*eR!dKlu!=Pp_QAP~*W_jAJJQ2vx!gxV)n1j7)L)U>%!8HX0s7oj{wHygdW-^Q?+Q`Wjd{w}MJTmMkp$5wS zE~!Jj#yPB?(Lyoytl4G~4o<{09OzvQb*N(n`YS>h=ly-EMko#Yc{UcT45P2+u`!@L zf&X|y7js;=wO&}V;w)Xt8hkg#<+Vk2Y!T*CRipH9&Ogir99(}zxxkcLH71)_?QG=1^_Mht}IsyZI8`y^@v zK>|)5&tr1HsQz?*roQdG{0a5czpLfXJgO*#c_Q@WeQU_h{JvOEyq+5GS)pGJ7kM28 zl8Ahl*CEfzk5w28U-+N@d|Nf|-8$4c&xikh(D4x}1!I}!ZJc~Vp{X(^S7rnce8cY> z))yK8RDp27Ba9=IZe<ug%xl@vF=43N^Uwd4oqqc1cH8Z@xdugc za#gA2gAdCkzrJ=foDX0NC&#U~-s+A=-U#owCm*lFeG+fF=_VH%l8t@Ik|lO)1w8M) z_g=g4#v3c(xX9(#f#hLdCdi-LvuAnZc2x>urJQ{zIbi90Y8oo0Z_a4-WS`uD(9FFKGcvOtW0RP zJ#1+a>_MeC(hlI)jTNGJg}S&j%i4LFJc#=H2mGot*Z=yGd*Xeuj`IgGRgyzheZtAL z*=C!$y)yM7O#sV@k3vT(DGBxQH|um&HVt#B4}6H+EMpp4V!luFvlTP(Dc4%i z@!mgvU=REEuU%sgKR&*Fgg$)SLi_Q#x7(vn47$G`seYgL+k5P9-nZMh&c}8B``g#q zKYwWNF{i&nLv1_h?3?WP*X`VOpG1Te{ov6%d*z_OLD&@)SVVmtv}X*nxcHH!4j6vy zZ3}JPwFYdLZPs_6LpjL0LcZR9_yRlc(z~t5GI?rson^~L>>WpL-;tNszVX#A#OR7P zfDS6H05=GbM%y2bkO5Gq8)yMo0_=dn*`LF_=`dV_*Fi|knNTTC#tu8|u*R~Zk`6}2 zo|^6lh%}n+N5e->rd$bUy1^l|g%5xD!|rFY3Nw5URS49rEEJ$!dg-NI)5CYZ(SV-9 z$Zp5sUXTuqZG0lebGUb6w`;J>0MJIO824L3!>Z9o!nKv)eUvUoq_8*X_7-yMzU$ z0U{!=`019$<#lLKW8KdrbY0cW zfxZghjqLol6FX-WfP-kO1Xs6IeupN=tBV!T9;aK$s!$#6@vfB&no%ZPtTEa`4H)vu zadyu-JZh8axPAGRSJ{fRvh2im@jY4KuAhg4^mAFFTBK=()we6rqW>qpr*ok+dtovk z78d(5i3^95lghFgA{Iru2szD>^{i*Bo0Vm{PoOddnySWf_(J3H^qhxYBl|?F2^OHGSO>M0MK}l5kMVU+Y=|*UV8v!(6VJbLUwsdJw#DxkSiL|$D zj|6I7mQlH)z>1+4pQr#5LW+L!pa0oD`N>Z@h~(*LY;?#60Tc_3*T#KtPdL4%R`&P5 z|9xAuXp#FHj+yPY+s@r9ucMHNm5RO9yp;ijagR7&TL%#ZP!2ZLp&fnQ#Kz5rfzM+j zD>|SY>lcj;z!c?+i7h}2ey_96I_?L+<$2G0o;w%LG5Cp8-o`*?A{)A@QU=CWD{AxBwH&n3oVYc%VNF$Q(4&he_G7f<$5W z$VfW@ajyT=kjFg)k^gondv2D=z=r3Gz7JFcc}{YV*rJ`V;yd)UH%8vdqv<*e1A|1`|C%>Bs9yAAaCG ze)!>syFT<+CF9z?1kM%tl!tq(mH`p+3w<&nDzjAGgg{qMW_XbhjbALxMB6vr6N92d z=@%w)ztf3dA;!G)+YfHQlCr_vJ?T14DRiHG)^%%V?oe30_gI_|7pABMb&8dgPHu8jr!X@iSR~ z#QVUtxm4+pGQzWjT}mxzUR7 zPatN|^&~3{W$|d!T$eA4dQKgZug@Lt#BiqPk43UVabKf?>X_F}!Qz4rA(rL=RlsD*o<~&8!lUnDRj{erX z%&z8VEKALg0!Z8!*%=rXuceVH@!E@c+KD=pf*>t(y;s&1uHmAG)@9z$M(!GYmC&-< zsm_Z=l^$+BDua;7a1FKSoF+u%Y7?(yN}&pGqEEt5DsU;RNWfA~=tCI5I(^+UkkCR& zxi&fn>EzxJZm|druYSK(g!^gLU`tmD(AnN6od9Y5`Okm0FMQz(cH)UAIv4>E;R_jg zN;etR_`$FcMzb!T{#t$64N~NC)fgIeI01Fqa99?)D#9xY!ctw`p!v%O&pdxHs6Ed(;|%wkX_%zP^O-k7XM$Cf z8Jr-xVuHjUB|BU@1&a*IF-_)I!*>SgRvpr|c5-iohAdOP&#*!S?NjKywi~Sr{cNOa zD+&%ZY%B*4+_SD`2m?yns}92?oqqO!qJ6r3K%aZs0_W0fH)yMt9K6>14w$>VBt6h(M?RIv^F+a1nzkYlB z;>p+9Z_YT<#$!Vj+&}&7Kif~f{;~;G=6y?^wjW(^yS?j`+gD)kCRgTwgH2&Yn+z}z z=k`_S{pxZLW9H26vs>?Z!ZzAqP6*YEYW)9>+R@Ir_%6Hafu}}I^Vd~68g6uDsRwC* z6#yUH!wm|cZg5CdZ`?brn<*&B_l$cc+w4(P9HWInrD6={j|Nn=EGJs706Pr*i0_Gj zjSLApSuoWtf8$9HZ4dduIo-lyjs_}u9*-NX+!R*fyzxRRE#tB7Nx@&P5Ty~)cC)D{ z`&hr-!h7=iqXEjP+C=gXP>X!xhvht4n_g6bDmZ!9tKrSAJMZY6$ACL4GPL3*wfYTv z!HuR6giR#$9~|^lNv>Ce%K4)x_TcEILmZ)1Q4UZbAzu?IkFoYh!}n9%C&Kq_G=s{& zsT$zmzBSYXNSVqWY78xkbQJR9Jpf4TSqKBTyLg5HV$a65;;B2WteYmF9tu|~}6WsH7#xm?13do7? zPgpFGpM&%m?)gtM6e@T$OJN`9zp}{Gy>CGT7>+NBh6F6kos(lHFx#E#oOsOim0p%* zha)@8g(b^z;cy{-g6w#Gx7+PzPWG9<+n)7oI;~86pY9#;I|p!53pg~2V&2bk|2tnZ zO)BK_Yxs1%D2c2f$zd6VO1!=k!I+N#iKB;PPdQ$jq72Lv%DPWe6vgOi^yR__3edN# z-jmxY+yEtOe=M&Jqwkh~x5f7h^ARI0fG5WQ+GPSyVak07(UgHSu>+QcGA4y)$|ZC^ z#30HMXvq4=(+c3V;kU9C)|G}jRZxi6C|7M03O1Xr*^)?r%2_a1%YiA6gSbx9RnQuQ zJ6V~j*oVR#2Mu}9tXZ>duqLlTqFGnE2M$9?=6wjumV*KPE`(qOj0T5dsKP$K2Of{t z8=&z9{4o|(hxulfd1WY7){Pvsa=4eA7EpkT@*eZ#_E)Uopii&#szGT$g$s`u8wV9o z_>GD7MHgLU|MD;Y;y?sm>wrQS!+dDtNr7hFLCSP*IGPSJS|1#=*idsqos;a)DXRN! zl`YrlTlIZVQp~+^F1X+Vw?`6`guCv#YZy4$PV!Er#)hxut!;w!1%QK)p$z@#(v$Yr zls;Dq=rwX6>e_A%e}=OSJ9VJ({vz-vv z00zPdhxZ?Q>~XiK0nSJqk9+|R5>_&%HdU#3XyQHOWxe&*OVq`yuEijj5&H?OD~bTo z6o(ZG&x&|HS5bzE5BJ)%zz%&M=>Uag2IVF)UFvRm8eyd(|1M$}Q7hsUN6*!Bikg_Z zHX}EaPK;QJW;%;1Wg=yTSGlezE7#o8?1}5DfjvUC1zWCb`|h)eo%yrd>>WpMKV}*` zELh)OdcfxPvbX%q{oZz~^^(b4H~a37Znb^)-q;pwweEx~^p5+Uv|s)4J_q_v`sqz} z+-r7d3@h5ra1PFWaaHQK-u;-p{Dt%Ei$A!|Hs5GX`^z_CPr^m^_9M2lb1u1iOgKn` z^6+hkF0i9ty0!hqN>mTh6!vW;D!1GevWx{-6co%vh|y=N;X4j~#$%HKgt})RLSb@G zK`Q!i%v1mp!Px9^gu7E%hG*VH;g~7`c~8@ATE^qOC!CWjL|23X_G16Td=E(&!;6mA zzgL7IZ&-{lnGm_$Ywr!OKgt0#ukwmYLIARv+$*j#Kg-5(%;*4-&Uu8g98ddF1AK&< z=-`76aODB#I)Ngt>w)2)$1IPrASZ0F2844)$>Yj|WB|weN{GZ?SFmL&X*GowMaV;d zSgj|#hhw4cTC@_tL(jYoT=fYXTz{dJ>-&)P-5_BT9Be)K+m!+bv8N}Y71XX{f%=&; zC>UW6RKJq3v$LC^14Ff0Gi_hwDk?pe=PUGqNE z6s*DYHI0807AgqJ&&hqlF!h_$cj~s{a=v7Mv3d)!%15uh7pKq@IOMj)vKXlk$LrQq z1fK5L>Rg0Fr{7rBV)&kEMcgKg{#6Pnm>$JQ-v6J9xpL(oVW*3Xh{$k5!vRz&+u^5v)@ z?c!KQs$Qg!isz6k_{fMZ(-kBh>R?AKdu_9Nja=N-A3@1T4GL$)O7dsb7a_2UU}tdo zpxeKUA^!pi#pgxb43i-ORW^$ESc$yxzQRjc*&r1_DDa|!NCBG`vKEM{wGbTcnNXfM7Zp-%k1i_uXcaG;uWv3%{SlNrKNSs`^gEtf;g;j zXo(zoI9uTWxC18@LWDA3RGH?GPyvy4PUM#>Az+ltvaBF$)`2$EbUmQ)JZ{FyJrI4{ z^C1>EAb8MRmBYwTR2>3t^;sgncJ)yG*RDf-s`Ag@OX=!6Z@aj*!dJcLd{sl!fXe!X z>0qS!=MM)q?Kc2q9B4-B!ObiDW*G}HU)`dEdq3`TSifM$5Ta}=1a)i={)XB?g*aT%w{_T0NiGqZJZ+xj>5;?gye1q`~V=~Ky;yW!$ZzL0C66i?oj^n z^BMjwKP{&1*F&o!kg$R(xf*;Ig-mFa@Rd33i>f)HhQ{Rd8-FLjOE)o3b$1iDe9Dzi zy;3k3-bp5UWxgho_f*!;G%GvuTO_G<-eIv48b^EmeXO_iO;Q9wYb?Vu{7+6>Ps!byN7gm0aUQDMI#Oo1Yvw_~aEn$CMB8SV>p z3;2e*B_P!BqNDBSIMarFV1I8ySL7FUh9A}mz9Ui1++_GZg}vh72*wWp4fnGnAcHgb z^a>=-G_aaIdur_{2TlNWODZQlej`NB@_OYo08}@FQyf1yhI<487UDdX5fnLan2!ep zPR0=865nwjTS=iVagPj12=(M-_n(AmXi>mu1-!3$fV%*SxIV(o&Quxq zYz!NE%w|;pZ}8y%O(0=8Yd!1#!}}}1_gEtwgta$1z(#iYTkBhNLO5@x;b0yL_mY(q z))V6f)q4N))1TR@U;u+qi%%3*LhXyO0OKGQGH74q!ih^(TKLqWLq%8=aY1MRhV^3F z(6qzH8*3a1sAz>N(Jl3!C)B zv1dJ72?n1(+AV#ks-}V|hoFB|ebn6BPXS}@7bpMFD1=N$p-%u+_^%aN2!vTJ@%i>4 z&*JZG%eGSVuLc28kw#@q6k~b$q2;d+oDGNml6p(`< zGnsk9McvO(W2Jg!VwFbQLpBg+W}Jnf69?`y2tz(74d$^_yD^Mszd)yv8V+h_WqQm% z3o$3dN}yR;IEG3jE$p(4>Ng!g*ip1$vUBF2u>x7yD0`N-gHV0_4SPurhjK!`sH$Wg z^@%!-(8WnqD<=~s$@koIkAo)o68nM|yucPNTo~Z0fRvlsUdN#iLAGv17&LHv?kHm#uFcnNI4Za5w$dN>m1MsntX0?++l*hi!Fpi+waw}S zFbqWPk+2#8dl(|GgY_n=9-PYnA}bM~*yA3WJf7_-ly7w$R`_dKupKdEtOxG>wRGL* z8Eu9zFoZ3wS4-d= z;mxo2NxXaUQ}&X*H+A7gar#jQZ)KnO;vb#D@MHh=hp?hGJ~P z*Jvujo@#JnMz99;-elq2cnv@XAc=d)VV^w!B&hHJZvbdU+lz8E<9#e2hofB8dE*WF zsiq-Y^?s()w9QZCo+fiY(_si3i~7?rqYN*K?<6ZLUof^W*qh({=Ei*^+5W~df-xAr zBf|j<=P(`K2;G;fNXBEVV>->74EyNKGk~~t#!PRcQTFU3aDuAk=M~gy+Ak0HH5wqK zsd94NT1^N=9bh5y0F_}kaEAN1dyMpF)ey4@x=uv3xqe-)k4+Q2#l4}fVSs~^sdF`` zsZb|?HhQlpPqv9)ODIUZ-nptD0Eun4-P(bTo~gthsA(*c*t3GKELVk~cdaD#VCT}(?6WdD?-MI@0c}*&C3EKl4vszFRo~QfLqqlJ zr0*xU!FNq3bv!06#4S4Jz4)DJN{g)#WMpNNVJT3)Bx$l+RCDB)^ zK5A9Y1rdp;-f{>G~T3_dImcMyDHQggo>x1{Ad_(U=jrXKsU-SA?FwA`FzJ1D9n#TH^ zSMYdk6D$`6&&-#KdmyI5_X9@XL;bVNl#MW-tgoi~roUd*rj8}$1)nGC6HAqPN;7jJ zs{jJ3HoZY!0~sKEA=xCQ4xOd1ZpalZ>@Nm_!ci-L9fXyQFsbd*H$EPVi8_zv%@Y)= znosFDt>EM2uP4d0rI<7O*XL)Hq zP*})gbTD+E;hyC|po6^*UR2w%ZM0=zbmt5WCj-0Qrw*pfAK+TdLD>PPb)^gQfeC_J z^%3s1NLED<%j27gU59n!F4yS zoN$Pwb1*^VsLXxlaD`hgnb%FYKkwUZ@=3(3n zQyeInt(Bp+I{o^f{JVdlc`Bzbz=&R(uNCug(|AA6i<2}}2fDwDs3MFdS6aGu674ch z_`;_srm_szUXgnc(%KvBV|@=)j>a->!o1Oj<5N=Hb>CC=n@jJrpM3M>cIeyBwNHIy zFWX??jQPu;{wS z9khc&ab68iyBQRii>y1?LS7kKbgE!)GWXNXP<~Wk5)Lk(f`Sj=25I6Rb7-@!K#{yn zEvc?OPXJ=iR-!dPLi`?l+3%9Wu;*awj?--Dpz64_wsh|WNLbr584kh#``BiwHXh3k zefi76>#v{mok>GNz`>bNc;9^Mt*$RZHGSokSG)0h*Lr~k#>15r2Dz*k$yyRwhNkaj zTXt|YtXbni(WcZ!Gj$YCHyrGCt)h_*js^$2z)MLdltY=OrI<8-)w7VKy1(nanYO1EB9AGi9WwLhs3*5q2sPskra1U^xT!RRxl$V2_Xuv|%}(f)WHMyaFU zoBn8uFbMZ}o<{5=uTYT;e$(&zmcTj90T-%fwGM)rg8{s?t3r{s0W5-sKtLh{dj%A$kV|^n(xCZ_Pu=&=vzGWx=_lb7eY2UTG?z+pR zgKItSdCzmIMOB{Wom^J~sOcc2p)0AJWO@6v`_ciWw4^@6z;0y%zu8xGFs15`9aQ^} z2j#<|KG^O9SSuocL)u)4VA7ElwrbrGG_pNu-_ibyo9nCla021$wykXmzcJv92;5U> zs`bo4&Xrry3Xr(J?)N;Nz>U||FQ6P~Y5o{rM(EdQ3k(Iy<9Hp88(c-PM%6BHKI1dv zT7ZB%@4Rz(h@u@sf{k{9-#CvdN8}4_8}I4Ec-6Hyc`355=cW9Pz^iCj?^nhz{$#7n$xq*!)?RmPMMIPQ=)BwQ$d_zkFWh}2d(RuUw=bP`!_+Hi6oYc`9Y=0&r=EL@ zgM+Z!-JXQ^I?y-J-x~G&owi!v4%+j%0S>~xdDe~ghL>(_gG24G=iuT;mf0D<{ImVd zvAfy2YmE-}H{WP2`_#u?XY=+v#8x*We1Or}-_3MyJB2JTg2mCGj)p~iHWdw2pHgGgBw@a;n>t=Aa({8!7xlfN5jML&sJlrAz|_8HH{ww4r0&2;;B1r;5XHAPud3AWv%dZ^Y-p(t+3W0up(ir}bd17jfvpt?O&Z*!`L&2tNm1UQiYQv3k zj^CKinw%uQhTsS2hDv8}g4wg4&44Koqt6gVe`}NPlbHiN5$Wo5M zGA1A3J<>PLJn>%C@0z54=yU3!ay= zD&Dh3+GASVZMU7hX~ zOc_R#z=U~pv4aKhxa+$6h`^^UVI+M4nG2!F0&Gbo^GV0gSsu#=mrKVklFd9rW066o zzfPO?O2Y2av{Xr@q#5BM0b(gP)d~dcA&hk8mUR?`scfv2!R87(4g{{V0{)If88Y&I z=%I(YP>nfIW#?!5t-AAS+tGVNy_0q$+|gIR`ZfE(nLn_z&iaX6aKQz(WXTc-b~f2$ zlQ6ir0lJnwqaFy@n12n$svVX0mJV#C-y^VYM`&|ApCGbTcMP^;y+7YpM#6i1UzKNJ zaO+DY>CCqRS$1CZJy)J(J1A=XId#6vR5_`zk^>YWC4al?RC&M`b>7?ZWy{0hIopNu z91f5J*eU${^(q;*LxxfL(t+n>rZq*9GAAX|^qx}*nhw!A;@$3&O=hn@2SA8p^_N}l4 zWSPY;@MOX|FvsARwqNb5=8y@$3JcabSxKqj(eWS zUV1ne3!?DTt-KT_H32TYUN`uM`sQAM-R^^RN1MvNcnGNpW7#x8LS7H~8Bh4i+?c=d zws|PzR%R$q_BrI2_Y1||%FGkm&RC!oZECc>{E~wXbc!+jbOTZZYWQw883?6-apNv< z4{2XfJDZ_YeDy1jNb-a4kKC88o8oS<Z8~N(0y5Wef!lLVO(-%WrwTo1!jRv$z2(iWZo0K4wABy1|NpbMSI2b=F^TqpWA+^dh_6!C1ribt z(wZRQOu#|(afZUh*fCi+IGqYy^g*Zq$NJIG#OP~$e`TrhMl*~940nun%@C>>|5j%I z^wZHeR}6lc@J7QqJh5Stk}BwA4&5*(EQP{+M@F8q#0t^3jS)Gx}v`*FIGG zn9rDj>O!I= z>NaONds4`z>F4h$4`CZp7Q;w+d_Pw-GOzFr9DVfB_MGQDCmg$W<@ZMYI-h*?H|D~a zcPqb(@OLBuq=c57yX!eHA7ZraSBJQc&=+NxXgsda%F$C8A_B2LhK3G zb}-7^>x(YB$adIa2d5rHxGjch3LVbX4e}n_lMXDdTw1Xssd_So!X2rhtkocEq+^g_ z`z8R_@TNK_(bz}K(v=ZVv4+80%0ufv48TR7?|UQd^m-05w%kKQudkBr0Ek=ux-5S; zkd2gX-!XoUJw4ec2PG~vS2VblA^kBd=rR{-bhhuC4Fh3RteWfh?3-?H%(6eUFNLt^ zFSvHQOy&9gv-{nBj+BJ9DF`koajzxu3btGI1Cvmh<%B`g?RAOcEXvCxiQ@6CL69{ zuRUmM`;XKA6yC$f@2=qBIhWk+l!M>>`IGjZSMA_x>{plH>yBCc@KXEi$yeLEtM|X~ zy+0+_Ls-$b9=gE#wtn$y7g|&eAPWwD?pcS|01|v&;%_R$y6t0!<8dyIKj)ltTv$y$ zbCE8-T;lasTWuB6%1y6>v{p8hnX2J_0isYIcs>B+0qUR(P)^*NjbEZC3e`{-fN4-@ zAsv)Cp(I0eGJWPHH-(0PZ3IzB7xl;cIO?dQT>g>gFMQz(u5PF-Wc})WOr_qCF4907 zKzl%lRNNOso#xlW-ggr$<)Bx-WvcxN`8%n>Wt^F^1=( z0~)}HdkX>vyto3JP1oUmS;#kzXNXjszhK*K-Mzrs-3{zqQk4hN26%hhTi)dIL?DTL z?OM&72z;&>r95z5lF^d2A2;8@G_c>wl^U|i{5qj-Ce;A>K0{-cJ`_Y$o~2_7glvO=pY0^<=rc7f3xle zNKI&q*ZE$NXm@xYw$VnjZSOsIcl#=;p>41o0JyBoP>mBZh8xA6f*bE|eTzP0eK&up z<1j)(gd4S4>rFcx)O|8hc6s@Z$F$vFapl#n{Y{6Bryd`FJnpr+tR5d%WxKT+&-Sjv zOq=<3d&MfD9;_#=?w8DEIkQZsi86O?n(4(ON(a~E32y9sHb4K7H+D`Zs-+>bq3IL4LfyUSA@j zQl3Ko!XJu{sQ$ya1px_-*KM^r!s?vxgBnBabgpJ_G zLI@dSguMP{Xi5C-MK6$h{foA{#cFrk%LaG5X#;|$p|LU046;B#3oK|Pgl3ggr5aF8 zS(RDyl+(TU?A~we{ltDF&bc?oTXnN4zN(YwoV{bmy!W{gPrP4vT{y&a2<3pU9pZBj zZV5mdm!ZyFJVko}zEZu~`BF&b&nw)j62<{=2^SsS5w8n?yxcMupLv@*t6&SP#G1@I zrQ+7#i$C^bKiZ9>-siX-eQnjY&hxh3I(XkJ<6NN88F0H=j)J7lrUX|9 zb>7^3rZ}V^p7?51qcvk^vW6hc2((t?z=-_`6$V#<4rZLt#?BK9IBpurjZ!MgyphVr zb(lG>1{#k?s;=X6efd3oZ-fLqhK8oBUg@z6?I=$LNcqha^Hl7?M_O^J-16=ajCLzm zIk?1BSwFkI*r+>$G zAO7kCF7`^i5fEbzG5dVX4{&}|R+889-tFscA?2l=>*GIsxT}|DgfW7Rx-GoSF1a3m zj6-q(>#kc-!3qXq8nkJmQnG8g6OBJiL)irhz5aCl-o1NcJuxv#y`Tb?>G?A^cPik) z(WU`l2w_dV+lbn58`_U_{QM9@0)Al_Uo{En_>f`z-UmDxPDb-Gm8ta~+Toeljw6(* zSQ@)N8#D#qbROkw4;{yW4|p;kHa#aUMlrb>J@b`e?%^X(R6OzY;qX8I@-5*{e(l?{ z<3IF{>%!lE|Hs0+-+pbl;GCmZLmkPkzV!6)SAVv=CLF{b=pdoojsDPgTpK?02VYIc zzw;X}3;*XI-;utnmf!xyE5aXr;=bfoRQ3zMcUyQ*ckMTai^9i%gpYN=;A@YEt1sRe z?zpdaSGRkBgUh?WA-wJFZwsfg>2EZ-`v#O~vR_cEGyw{MqFE|89ee9r-x?v4Os*N- zJtOA@3f2OIWxQIwcB&gN6-X$!Q!3toce9k4N3!q!?(dFq17l{@t>BU%+AL_N-#Deq z>Oj<9x&}Z~Uc4&D# z{pDnv+6mS0tU6TvsgFt(JjR&Q>b_5t_)1;Q!;k*yx?xoZ z0@MNzQro%xS=ZJFPf*3U5#|ckweqLqs3MOB+#cS9+yeH8aM{v&Z7o@a);Nwq4`kINfPh92n}M8OGU+n@1_3U*Xrb z;W;dFwQ9#~?)|Ks1LS)N4l!4b#rf3^zOAmTW^pj*>7a=?m?!qR?LN)^*^;^IY?{Oo zT;0eVE}xnI*1V{@@&pd~{J1OYqF|((YsKrKgC+g_&D=P`3JfklSsa`(2kTo(F}Ln+ zImxHjk;liJv$qG&_i~PXR{L8IwU^l1U}EM zbS;dIcy*oHCoN$q`Wd{-nS=8o@|hHbB;u+pNU%Dfci7b- z1HPwN@7go;`|b^LOk}EUbdR`@8lnVR4$XRaFuHAAv3ZrJL0{g5j8?q(hr!Jb#%qf|L70n4@x1Rfov@=x6 zsQEUVuiv-B#!*2BC8@%JwS5uT&oq|JhPcWW_i9}Tfm$ruy#>~`TGptZsYl&-Gi^c^ZHwpryz=t|}=2kOPC##1) zuh|30?3>zBYTR}a6q?~DYvJYGCNihSel-z@rp z(qBa~j^<(~5VL#4OlY%AvwAhq^<;M-&J5LSOaG2gr4T7?pQEH@_28_}`;OI*kB}#pm;=_J2Jc2QJ?+GLrm1{B} z6s6yvHq=%J|BLo{H!aded5=-uXY|P*Qw2X%LIO5C-y{ARoRS9LEp&x$^2xS{b#9x~ zM1R$CJ)1Q5aYKBsb$Zfs2e@}#jAi@El$w70MUJ-a{lcTW-*H7Cz_oM=^9TO7KfcJY z#OBAF*INB&>cITjGI8q7X2H?Y9x49zD1Y&LQ#z*{C6nNo(+_SW8APxTslyEJg7e&8 zVZP%r?Jm{c6gyHnckO0ON*0C-+;4c_ujvvDmEOqrl~NtFs?HlJ%1p)tq+zRTE9Fi2 zC)1b1;^+1#6&Dx|(8hdpd-205j959#NAFGyVc)NpH8#z6^Ic}y=ghxms6k~dQP`hC zKP?lU9#+;wW4V;00vsGxqhLSFE^cSiU1T8NE>})Fx%&lg>Wx)tN_rsp*!br~Gc$PI z@phrNUw~#@;GRDrSlGR^l2~#^o?VA>r(?L+0wfb0PN_y?d>2d~gRah3{dN7crCZ3x zXywIfXElAiy7lR}!hqz&#&pIC*3kl%rvc&&mF1c_6QgKV!qgJo6s1G4itpz4O&cG) zcvh%Ts|N8)K|ed*=FCqE^jA?mocOG_m4s^N1D(p<#z)Y1d^S*pd{o>9B69B3W>s8 z$&;Rb$+9h?n=n2`&QBdSvWkXn+I4kKw$pgee?~q&*X;h{eRya?_Z#KgE-jY(8DOqe zy}L{> z+oG1~7AaSw_P8Zx?5e?zB8c9Jji$Q9T#iygpk37a}$lri7Cy?oX z((ZhN{87F#5}n+ZR>(l_^CaCy z^(Yc7%hbFyg28J024P<-S5+#H8_hH{TO^!Dre5?ygtK~eqs3}eh3R{>mFu4}TQ~|8 ztUpfvqeEElLVGdCKn_htN{*;+i8)}D1OD)=q9<3;HFEfSk)fre;;p=_onriA^|KeU ze3xMj4ua?qe!Ir_`ATY~{c_KOam668d9>@a%j^4}6{lU#4ZNC@=65lrwQ6v$YoQKU zW2S4suhmWDEro$ntN*j>=k#ijIX0b7PJokNXJZDP%y)|k_l6Ud@!Yk%fFZEJA#V^t z^S&vn?;xbmm6(seaK0fcAAv!i)7=~|Q5-p;Zk*iSZvV#=L@a{ujSyQ@-Q;QQ&4!!| z{_8NbR$mFwGcvGAzHO!#WlZCO%;jfoVvp@`EoPM5r(Zi*j(M`H03nR9Um7uOP?tpG z?)J`&!*;I-s{kqU3)+t<@&a)JgNl_x_tZZynOV<`5v0H8rUQ^lpJAcePrIg`FRgCA z)DMan!oo6z1(_e@H>iH9Jqw*!F1uED@&A|;{qaG^f7#=Qx`)V86rTgK_n#1?yfiQ7 zT!r-r@VVYO%S>A=b7P&=*PX4qWunD$Bu!b|sN`PKx&wV~H;}mQS|q?pY`K; z*n!fk_BYVw1MEcQxZWAh-q+8K5m$1Wbhr_{N;`30tZ>}?T~H1 zOVAads?9~e$$q{fSB)YgHQQjRW8)sif`gj;T$+ns<>qf{0kdW7X2E=cIG=EAIp`nu zf>AwAFIx*Xm{Zgqx^dsp%6#K}#1*kkf&08#|0KfVCA_+n*=*b<#CT1?9S}IK%^Q05 z3qLr4iYu?m_iCa~uX^=s-tR_oy=!BnM3!_w2F2vGu{X@<&7$w?a5aYCWuo+TEpBQn zhK5tGd!)9F|Kj|r2Edj+i2Gs$&a>uGpVa(*+XVsFUZgj(u{oB`h(C0Kda9xrD=oT~ zN1MEzIqBAsTdoa`M<2}(+0@Cbw{S8^MDG~5-;;jjCIkp3jPL!BSs&WhCg+R2$FYNc zO2$!pP+xWg`SX+&+g43e2`#912WV@ba>o0aAfnz(ar@LNtO2{HuxZY3n>#z7E#ge= z4P<6}^9tv}_O;3WZM%{GGmY>!3X1R2{o9txrR736bLMR^#nAnh?wi;WP}u8`frb7U znIpX*Nysr|y9<=Dp2zu17PyIq%|eFiGZ=!#2V$T`}D?sAcsN9^|GNL>D^G;Or8VD;)@4$Bru0j|8cn4`92;vJGLEY zWTdgnrRR?GA-TZm>{N9HJ6k{Gy~pK}SrlnaXQKitaehYMt+vH-TlJ=C)~_7fC~it& zT+h}Y@Hn|VvFT6gZeRUBSh#={f(7mEvGWeWA zX0eg&`a4L@Ze>ArJNSr=^>etw3j^w9?F+<3RL!Yv;g$+Oj^oS0?9&FS-a4S`CfVQJ z-MG&~T_=;|Xofh{#e&`<#zR+9J(*ZyL$ON z1A4os2ApMdXzsBJ?eE8KLviIMM2kD^w2J%)=bwP)NYO^3y^?A$bW zB!(q(kgd<+CA{`Htbub03&pf%FP3bkSCV`Kd1+45Se5dfE#CuAVFrbzea%r}0Q5G1 zVI5;`Zv`@e6#?ifs?jr_>C9SnR{&B_26hQeZZl!WXk<$jC zEj{W?>>_6rOwrUDUGH732Pa;G)2p@MBs!fJ%Iv}7MkKltU z&V7F(c(BMpeqgXm{3Mva0iNHIU8#28S5Ws%_dK5?HElPBw6<2ZwlY(C0U)(cp#R!y z1LM<~4}aRTw-s02IWd_4)3IWtw_Yt@i@bcA;zmK{S_jM&1YD(eJ4#@kpnPhfxs9&% zj}MQGjb>tLa~K%(6I)$AW6zdwuHlOrfeUl#ElL;}E8*R0B%EYrF-zfM&_a3x=`Wbo zjC`A_)Wh8+MU_5s9y~XF)n8mygUfUS_cv#YxMq9@qTXWo0nz0r;E_pkX#5)&R8N`k zIR4@u4otRHC8EC=sy)FXR&tmT8Gzfk;?rwHHqkI&{ZxObqCM@=B~!3m-ddIorTY|* zWK^APem{=-YD-P+RPyT2PL+XTZka-V=}m3;#{K>pge-WM=SXxg_&SevzyxDG6-kC( zs69kei@P2BZFJlAXPb3N-JwTLWlN59tHC|@f6vm*SEAs!L>m^Vo2R_a$TD`ul*ZGyFEP(u1rqREHvHZ@Ts-N)8q>L+$(V0_NU zyY}w?-Zj3c3Az;Pe#P_Hh>TfwL%rhFeCk{ndxO~L|H;qoHY&Z3NpYSa_pP8$IpV_- z-LRb-74NJhw~JYYq$Gd%9D94Z@|&>Iy4NgBb$_mw->Ewj&z&X)!kMbP?Ts8zx2aqL zQE!Xc0pJ!-7kARw&lBIqM!dDP1 z0iji7y*blsTWe1TXFaAFe!ad|Ng!y=YyYZNGNIwG%)bb>&(i4o8tUxQsouFF0gelyd>6DyaNMSn7Pqm40CV&42)CS`^#!AEI&F;bP zMwPz|LE0rpL%^$-nZ`Z2nYePfQkN5r{r`AWEl<0HK29Wnm`#|r@wnK}l>9#5i9Hq? zciUn}O&gc9QWC~D#516{Tv!SB#7+E}p|QeEc5m}7+m(Ugej=@?rM4>KXP9OK!9QI$ zcb4VYniqReLdoU(mWIxxa>|dmTr$poL=D~!xld)QPLDowMblM=}Iq!;P16jXWG=j3baXxFfb=pR4-PVzMWL!uW3x*BQP z!TXpB=n7i>sJIzV`}TmSYJYKTvqrnv?06aIOdnzr8foV;f-Nbz>Y23m5vKi@I@Qx# zD8VV$18zB12 zG$P0uF9|f2+FiEx>{0Hop_p%MIIF%va6jH19i%q`*V)-w;GXG+tlt%5L)jsnz68f( zi@3`Zt&-IFng=_9kZR^{yUpFd?{~<|ZX;nN$|nZwY~yCsl=U$#sAi|9#P+8-B)qLm zCrUB8Ca3=80G$`Bgh!U;b%)^axF9Rnxa?(T(B*^mkuv}()fH}v<|A&WYuLpt33Red zmj8wJTU;JG*Ld=?vz_OoIEYrjI#Y93=CN)t6MY`#J-Kwk}oydMun@-;(v>`BSwJvI9 zgT%X1?Cs0AecETW9Wuc25OySRA(kMAEb6y7c?teb6HNieDbqhO5`yVbJr>{_qdQJn z9>-GUHMTE-2B^LwzDyHr)}58O$@NC;xFsqX3}HXeY1|+g2bHp@;x9Yz^;Ig9>thpT zJ}ioe`G*~Er?y&K=Kx$3HwpecD>XVXRFPV6F{PY+a-Z`HswY5IrxjnXF_fg3*C+i; zp?%oGj>vwc`Hb35-~Y;^S4A34!;~{=PUh!R>;EX%wMX*iXMMHlF`EYhdXg6YRssF| zf!qAL;_PGted1Qrb*k$0PjnD>PLBce0p}4u8>Gu&tf;wC?a2?gp?$OD^@sta(Pu`lnNPA&r zsikwR(l>u4C1%&*Sx8+mDw$)Tf)n7;B`w^}mg0KB>&iN(70;`8p(PqgDy8c%7_IgD8^ z(^6|agdm4VNaxpSC)i^mPm+>WvOWmG=`FQKtyng*#hJ=tcv zvfCdH{bN&AYe8Kna_z$Zfx6a?sL_w-Z9nn^Qcl=I4uYlkJnu$eo`PPyHw{65_vzy3 zJal?6K=1``vO=zAU-W-ljzVu%LS#1lOcBYN>x?ka*&-7}h)}=(;ljk62yk|?5N?j> zT8Y1HU)ysc+j#g_IpA`)r^H29g~_TOpKb#T&7NFUc;A)u9DuGDKvqzRuj#!OJz|Ro zVaBe3P!bN%76te*7`C}(BV-`Mux_zXy1ZRyj7g|wi=t1^#sJB{(fG-H6WMjA63v=6 zwr0#eH00m%)*{P5zo@_;>h8&@sy4xv`k~=hv?daP8cRKY`reqtS08a7iYy@PJH|BbJaWgo;#}1&n`;Ijho(z_$$Bjwlg%~ zjBcT$76?3s78%@D|Ji)~RM^|2b9_KrsGoiE1gG(Zs8q2In|h!4Rk7q zl(vt=j77i}M`*7zn=LHaSMUwx*D3tY`}5dL-ye(1SdQN|P(Q-tiwD+YD+)1ILjrdQ z)CL$@hH4*sA26km!M&jGZ zwptByoq=9V7-o>qxUuK`v2lJR*>;0U@~`$`@e{|@w!*I`v&`lKRDcTjR+PgDR^PGE zkkQZ+kj8kj|Na|(%=U)1yXit+zdSlMbv^Z`&)XWp_$#P{c zoI6G3QZRZInUQC!|4ckQwKz&aDwFxopa>P538+CvJSHD?O{n4cy@msarNiem{si$i z0VI6#wAJgV8P>$wTrXLcOp#SCdsz8`uGe2?)Ts|>%^Z0^D5RTaakTY;o$%T}C0Ceu-LUM|XcNkDY zAEN8_Ahz)b#wW^%5qe-Ce4|UWf0!k6bF-uS&Evn2n$EJTm3LyU z?eS`|eb*Ex7AJ&0D-3=nd$Y%wP@{M~v4Eo(Cj*zc((=W~D+g>uu;1=>{TGQ9GPKab z-!xV;Hj2NSb3?;3L+d!1zr;(k1&S)hH4gut6^)NUic)@>NbEWcnm3?d>x##ZU%3rR`C80Csu~>nis1SO3oa#@yoroau*f)FRrTzcnXV zS?kn-8TnMyKJ`9HRJ>a+8m%9EhLs-V=Naht)gr7_RqfR-%jZ1wAl9;_S-iwW1F0pu zlT;=VUirtUxXjYg(cf>$8(KR)MuqYkUN#qi>{xmkJoaQYfz1kA!~<^7Y1L>ox|E@5 zqz{of`T%h6Qpm0(j%OyRlxQSG0c?oKrY5;;*%h^JH#-m9VR_)}++pP-bqQEm`D6@6 z46^rQc2T2sE*sq`3;5Bc9rnj-RNk4nR~8r?^DFB=%3?Inb=|3Qf_6#8Chs0$=pJ7q z+i=={xV;=_{i)0;0yxopAvxL9Enq`NllD=`)}`aT#6gft3NYBwDaaKkeCA?+(x4ls zA+T74a=md0bRoYR_0MdzsiC=lY`b6>m=_7CQ~5&}el^KH9xs8TwDR2;e$RiDZ$~|!ERS@OB_gV$s;QpAmTCq8t^gXGQ zusdc^07>U?6NVgFnV+zBwd$zfx1n4t-DIm7&h%bc0bZg?0nx*qEy^ciX_kneB=<37 zOq%`{QzH6QxWc*w)h-f_S9)ZX&;@_N)`d8D?7YZ5Uv;$I!9v$oTJy_btV?xwuW240 z9!09o6pUW5h_8LWU{UOP9bkJKplsLR8Uo@;FY8;5Os~PMYj@BAZsn)au$G&IeW5k830j2 zqrdkX+0xM)N+;bj2K(9~ezus>!kO_1o3e(on0&2+%z%w zQ8w&C(Dw5^d}k!ZRJxTnj@fAReC2YZ!^@~I)BWTyvj4v7S7({5q^Ue*q-yw-k}Sl> z(6jud4xCM+G5z&h;~T;+>>GjWr@zY=If6V>DG-PG^)$Wm#{5#097Ud&cj5N)8Z;jA z@|>}ufM~)m@iUM_?rr{uJ-!nWg22r>{J^TCi%a~H_7!Y&Y8A?@JM*lemi zCi__PTX6=)DbAya)rVRJA+dSXrkH&Kq29Y|i9J-Ar`>*?krXhTo$Vk*1|p^9Om9)= zjAu@mlD;#BAMRb=uv#(0iiRCahiuVR*P=exR-<(n-IlR7KjCBL5%H%%rvZgnia>Qo zi|38kCRPG18uUngcm9_7Fz)gT_Rb#_V zWD_FeBXVHzXZ-1Sr+B~oY1w-BFIBQ-#oZ!;WgUDK=i5Vr9?5nZO5x7!c&P$9O1*qs ztsn#TNFWK_VWVWdT*11RsppYo%HhL*g+UA-o~&X<(#%&C5x*ssT>hxN*Be~J94^T?p{E@c1Y2=$3e<^?4d6Lw@h zfY;Njj#{+qCzRx$`DM@8eJw&;Hhow@d7gfT`1jw4u`+B&im#3oKR#P3#81C5r~O?1 zPQ9C*d4BWoH^@SW=rP7)z=RO=9%l0A(@TO#i?Q*^IKlvvML##gHIR2kz)tPk#yiB* z0dv3e__H;aF6O|q4bZE*1OT$awX^w`Fr&94qf3rCW^WCl-|+WCES55edw*Drc*q4j z7$Y9Kj)ugJOa8&xaM0U1_ zK`hL#=J^#ux*?1eN)KTb#CrLOr^apChP3jl!jwEeQ{kTKWz$6Y*7@XKcaZZ&-{P;< zvU=oNqLcV^rU)2j;w!LsvL#I2o3RmSMnA0EkfihDy{ROjag$T$Rh|>&za9Jc%Pl#l ze0^XzE_ly->a8i%OAtISWO8$G&kovRFDP*Fw&m!Us=R1t~k=g{kY?_P)lJZF7{yyj5qRPBGS^R|MHvC*@Wo!%oXC z(6C(Df;!34djiT@$y7<&7|+vefr5 zm+Q0J_F7c?BVwG;SqsI3=AW^VwvAdqk-O{g&^QIst?x`u8Mc$mKQxQVQBBh8%C}J~azO-<-*~ zbTS7S=;gUB>mc?Xb}I*h?_Fe<;PXq~X zJO6*<%m44tXH{kD;Ja5*TiXBqasjk1{KOa@DIEKGpT?bN-O=H@+n$GmU#5WKOy(X~ z_=Wf7x#x!gs3V8E3gt_EsrOF*?yAFB$VQ~updOWV6ohe8#?n<#2>mZ)U^f7;s?NqG zomojtNg)4@v-wMoQQWt`GmJTINo zpbdDC`IyydUX%JsrLZ?_HFIpsW5Zwhcd^W)wiiBCSDJjK=>{GfS+wb&v_knJQsKWj znB9?V5oa1or5tPeNy<@l&br5AMH|~>ty(I)c7dm@q%yk6$;x?3(?@Uq6#ja&UH3G4 zuW0r1khrE{_Gy$@JFaxpv$!E5SBqLPO&48b^%ADCjkC^lVp+o2npNh6njKFOmc2Ee zDy9@&6Kx{I)knymJ_Y5>_jz8h(bBw^GcW_jpDza0t%c?!a6YBASBhkGJuKy_~(boISEXe5B8 znnRnupi^(9+??uqLb2;=7Kshzly=pUO)|&mRTLj4s?b^tGqNT4hSz%;5BwK2nH>)w zKEjNGsU3|Sf@F$12I)jg>8CPFc-z)@-X+hut78^1f$aHIm~ewge2foSrA!XdI_ zztaiA*nOXM#ut393h6=BnmvslfwiBOXQBw4H`>WTce!HWm}}o);GT>=V&!9F0D(^- z2K|18GJis4?&*z~H^0wx9gQ<*{GTf6e~-M=vZHjyqG3Ie;>-Q>`pnRc*!fYn)5&xKllkE{ht4&pWo@O|%N zj@Bfg^Nh&;&O0Vyz42^x?@K1i3rkrx6BmAxx7BPhjQu;{`MX8wOM5DfOAJ-E6wEk( z9qqSCIYmBm_iN1VO$mm&$xczJK;a%v<;;g4A0hTNFExlY%5;N>JFjPcGwlux~b(-~XJ)~rs5hFOxR3D$m8;~9Su zXPq&95kdGNKmUTm@I&@cyE|jc^YBPKQVE$G4K>9B{&l34wXnKz_qLAOR%xAv5GQ%< z>%PYlzg)dO9@wTTgCs#XbOW;PHFKBoyLH3C7JhQAcMG$)ptJ^E27bwS|M)v?sz8E* zzer%)hdhq5R% zvVe^97-Nx}hD{g9GVW>h+iQp0969ECf4vYDd|ivyYkfe=tciR-M`YUOWo{AV=#aJM z;&l$+@3s53c#XW9Np-ocy~tb5)lke7aYLcA*Wq-;R&!6n^JwP`BK{;ui1n+{gA>SI z#%I#@T@3BT=M!aBgOL9@XpZJOl$Twl5~F`$FYl5)oMS)lp&C=;3qB^K4JB zFHiUpHi<3#;*+7UBtBU}5z|hWj2(%sZK+Va-a?a&zpzZe+=k8G3P1NBNAksmkC>r% zO1*djwPMEUWoFsxn4d<=vn(XA5gMB*=0lZK#A*dvpbVSB$r$&^H}r2vuFrjTx$OHv zL4BW@n2POb`e{(9gf{rnJMobh)_%<89anmNf(~`6Um4I;`SBxhaR=V+H+%;=FZg=W z!|En9lYLzHY>fuBV{AV+Hsf#)Nfo=#VSD(+-{pG!GlGEc8-ri$wXMZ*MZTPsFP|Pt z=ELq+V(@#ttyWiW5tMaysOp1pO_tUKS+Kb|11*+WW1bJw61`9Q=p=YR-w^K1(QF(R zv;OG3i~b(oUe(meiET-O!%sj>r+cH4A@evMKX2W`Dvq8j)@bRmDZ5!O`)Gr)V``SU z;rxSNSO}Zf&4Y@gFMf6&neM)0?!?T<5ujlj+d*Nyb?%y0=DW#_J9UiktB2YW5=JIV zop*?NL0flgirvuhpwGrlv&j&0^%`wC9QJ1SyX@S3i}*&Lez;`vH^?obGyBVT_8%)x50d8s4MNQ&kk8j{HlO%#g+B|M&YArW28y3+~SN zsZJ)QZ|`e1CXHgo_Zd}Q84Aq930@M+fp~!T7|=~&7qtcp_#da@{QLOBA2}Hob`Jf( zu1m}1{#j)1^~r0IPQD!D1YuRn^OM<+=B>yp$^!aL);*McG{ErwQtOkiM0wS*uu3;&Z)=u-vC#y1)C;~fX)T3hK7;z?9(6~$*o-5(JJzK(06`qgX% zD)HT;wlbENivJeXcSAloN5G=s`(juI?7uo4OaG%`Ytz&>z*mVc7r!rw^AjdMe%+2ozHY>xra`JOG6kd0=3g&2 zFeqKCx>bnrUDbQN7d@~NoBi(e>5S(onWvon1J=yh1~+cr&kx75S%Ve8eYxZt-oO(F zb6V%l^I`}l|HWA(=n=B2U0mG8m5HL#T##w|1y?hq#GD!tZD;+U;Btl?nP=S?B38t7 zF#3{SE}(OKVVh{5h@tJRwpiZQpdKs83x7YQr?R$NGH+I3%`h}xhhg3SsM>NmPbP5? zS1~9RD`4ZVXs7D8l7ESZ+HPDyUA$daGp>^&3xlqqrprp2_xJy)5>MOp4-!Yxd+v4x zVlO<#d0N6(oKg0e^oHtJF@O`c--!b$HudwgW``-clYvA>e*|TGcNx6h5D;w?_49;# zl?RbmZRnH=UAbk(G&Ivzlsw>dMYwMdiJ2HO6xWQa^dgT5ez%(c>EvQQzRtv{*t;DP zNt<+>88CqIU5W4hshyG*{2WF0t+I%d%vu@c`(Hd6I}EwpGv03h{nwjOxp~Hu1QE+4 zDZ$xUgIF2jE+SnNq0K@$ACSv}iI$rJ_P8=n2jk)jKZ5qd$e^L~inLh;omLWKf@+Ks zdQLK0h@fEj*jdmi%Fq|SQcFbT-Fxsh`%|OB+$BxypDW{p%(#+}Pq` z?KIE?(f4o)D6{yVtH(9DIvs0t3n*`c zJs$E(pJ_>KoVWdz1rGoN_uFEiMV4;{D<#mnV#(xtVeABI)?3(@ zmmW(wLU!M1zwiQI82#0K+Rs38wezQD~6barr`T|JXD6aeEsxuZ?Kz!XVLo1TqsQAtO7@7$-g! zWT2$CiS_#n`A&b~JCV;%PTs8*7XsJ2Vutb@p;ym&+EXL@S4Mr3T?6kb%{c|UaR z^0*KM@15S|x?%`8!$hVRu-t|5Exa|x&pJr#nK>i;N#~yXn~WNZP0Y^N6mmYoK%(hI zPR3Mycf7G7-`pIf%^{%blm1IHG|#kIW5dsXaXA$^(Kr=jP5EO^{ipP!nFHfK`CZ5? z810GYUr6+vx0v#2XedsmPJ1~6-ap-h_P#F;xm1I5J`oE|T%~Gq+J1jTR16N~Q!%D; ze(Dq7^UG$adUxc#>b|S~(K^#7i>x+j@!SI|+sjrX3{B0x1uv$ecn_n#5z2Fb7CR?> zukZ6m8NTu+XjZ;XJ6R&fnms4YF*dDluVM_hv@$#KX%qhjCj}7Lll19z0nol=p%PKV z6hlm0X0U)@2JMH603X)xF-?Kdvp!iOAVbmY)Znp}ferK$SY$vzDRhz}hIWfn!02~k z0UFk#7iRZP04O6vZl@us=z+BaFZ5)iP1)4_rM&Rko%&b8rg)@~fq>nh*ei}#GfnCw zTGCB7Ou*@99MW~AVG-selYNB!aSOThWTyCgF~W}Qwe%4~m0W*Rw)^D~@^V=k|Ilmy-56REX)Y)O}Nst z4S>Vqa75$V*wrugP<-tcYHZGZ-i~MA@QV_y`&7Q-3ggtBEtv~9E67NeR>rGrQjrMv zh9D!_c5Y_5CuzM!U@V4XN-H1gD6>0+-as-ZC=+4XX>Ar!1bL$8srk=zR0IFM|MXkA zWn7;9Jxe?7hn}DGy=h`t(=_6I?Y0RdK$Y-g-Y9E-(QyBC+;C>Y6qy>>wY(93wOEamBDah>tx zpLu7lLdmu{pA^74zdDHJi0maNs~0XgmJRw`$xCdE)ZOz|>0*F<~o9 zVG25Y*(Y1Rd`415YwCs5nPj3jpDF7R%=v`gz(`BFfJs+|=*J;iJIkNxOVigG9a-IJ zu4Jr&DfwHkE`}<}8pIV6jXF(`dlLM2c~;8}caa%L$HW|@z0lB8!gi|7*8Xl4Zs16q zUnZHom=M3gt^=_Ncty&kR6KCW+_sQ=1eWKz9HKI(8?QdSk}s~6q}1{P&sXuD@1;MG z>kw=hE=-@RouzWnB9-n&8)qn?k4_ZTUJSb>1}X14M4`d~`fq8x>9i2}->$N7@NE1> zYbALdVmdK2KdBo1i5Dr4A(xg~+2vR!QQrdV`|G2NG86qDA!{P1sT^8NpPO5H1%Dxx zd5+Ew?iA_V&Fy))e%ZAUrRwbjIP^6O*hWG+y3ZT-RXk;+RzooQ6^uBa@vdVbJmG#4 z*Amr6rNqG@US{xrOS=S-Mgj*}xnGtw31Qj)aBT9c0HXA#w}o%P56k<=2={e&Md!C? z$PhP43*Qz8!u;iosJyiIZ5-?$Q`B!k_7gvKD@BdRKb`k2exGc?@_4lCsyysR7$e6A zy$>?)ch~3)mnFRc{(cXXg#KX{$RclOJ9|y~m(C$Wbwa_dx<_o39qS{)jRiz-YIrND2Klj%l%wPc1Tv&a5r!EjDKdFXi?P) zA;jl+T5Eudp?$ADSNWf#WQzggh8OggHmR?9m@(@c`VrNQ7st& zRr}y|!=FFp%Rk~HbJeZ^Dhuev>er#&FUbL`?kWbg%G}z>&dIzZd(TlCRqo86s!UMz z{0{|r3hLt!_m@&osZ~2Ec@&E61tGkqtvgl&(?0b}`SmQ;nf&!9) zE}2t0#r*t(b!nZ7rWYL3Jc%o})ysFSnHv$O})+$+X+L2kqmRh0f;mA zaMON~L z>&HO{7#}+*u53e`3rfXt>=pz=OY0l zj$DXIPAGon29yQtOEYAp!u;lVYXgwxfgl%gtC6&`T>95{YhFx{p>*JLv-@^~D5@@4 zDg=CYXVM;Z^-t^n3>DZ{N``MlcP6Ks9R3VJj^_>>U8RdeE+QRY9X-Wf|CTx8bjLSA z*V6nky!xZpt!Imu?h8isjg*i`;&INsd*buF-bf9 zsH*_yhM9OIO{M}M>Q2|ky`heE6N3ivDDsf$H8$PXIe^-wC(qQv?Zr1d=`3r$S7z79 zFqu(6|11rf1PY4LywHA3*81;x;+phQ;7SB|(ZjV#8F@_nbnjTts8yUY){&~k=iFP3 zvt~k^&T96B54V7Y7+iEC;|IwZ<*fsAmZ7?}DZ?S_puf>Kr-dr%*&y>tteS<BwN|rxOz6HV#=0dlMFIjaF915gb@)U4vuTD{ro|YIy)TD^MLAMG&_$m zC~6A|9zOE#v!J@_h0?hvu^T3}hC}atj>UP>G}XQ$AIg|Ke;cUit<75>KQ9r+V-iHd zD2~5!0-!(Gu`6|HWPAdb>GjP%nbmg1}r+DG$h8lDymMoMne1A+MIM#-N;R$)GtO?^dh60a+ef+6f z=0Jz7?WA(MK`A~_%iYUNA#1R^Ev))0V>8*D6yIKr)b}T>ABt)5d|CBqQ8Z)tWv5=H z4cu?Czg4L93Svtzyu7XQxUD&x%XD@u-%4>As%%K+3(^O2326aGu4}AtoW~4Sl^VFg z7t=EN>09{zW4b+|--uayza?o|refB&OceP_nM)2@U@whbZNtO#Bh{sr+ULr=(gzf4 zNFGxf&7%uHjRo{rNmDh5eRBWB!p_>R%zhap*wUV6mGDDf$O%6z4i zX2dX(sFt?P?*;nA&5AvCqx6TwV$%bf=^hejY(PO(Y!wCe6PEtF21nh<(qWp}E(ojx z?me9hV(i2CPP46gz!e~U7)*hVx3Y#UFW*PCc2;m!aKuM)S}FH` z+A!KryU~B9A8-GD6`jDnkd2)oBf}q5HyQ4GGBHhSNy@aKR!&ba{)i~Km03^m!;chX zFF)^m6D~&b!{_L?!fMIl$m*!WeJvHe5QvQh_eF2!+)FHa9m!DXpIm+(-W%Gw$2+bI za{fiXv?F(w()0&=06AYO$y=A>R+MBgap8L~@y!pVlYw&t@&A^Q6~X3s1a0{5m;-=8 z*YkGhuIIlKBh4IHy#H&ZF`!3on2BM9=C$cvNcEB8V$P}3!Ip5uT8oSMRUQYlU^^># zp|e{&bjyTe-b}4vWb`s(s5Lz^On%P9`Tldjhu5oX{z=H9mv4SoQpwSC!63GWOl+Sh zU!)pWxT1jiywKgBk9Q_RI7a58xhs>)$BHIMyXp9Uq%FKc4kT1BC@109#(rMj_RN2J zR9!foJfN_&d;kzt$vP$vk2h&Ics>bM^GDEHq9CFkM29tooQ?UhHSq%KsyS79D`WCm}~h?hK4Ad^YCDICya zuNYy-A619CAMeaz&zSF;oRbI>-M;rJSW|^5)OOu0KbrcKDb?Q7(D}@=SPiKyK2$Ef z?lwX%05FkcVy`QwB`TOE#wq^kuf;H$J+pB1Yu5YSsAzx#Cm1JL=e%4xhU9ChG>3Zm zEmoXlx5r-m<7lp~ao&zMC8Z8}n!U^GPG1aQybohhTeIJ9^TGKwbEq2AjxB*|Ub*5j ze=Q@TumIgu3d3?y9flW#!SlaYu1)~~O@Vw0eKc+57zNn=zdADxNh$+2sm zSx4<-K8gOD@Qs)MJ8<})Dc;FalQ%W(jn%N*kHyDkuD2nk$?BeB+e06P>lnG0A$Aap z7n-7q%>q}Wc~)nmM{4qmJNbgURd%!FOOeyB!h&D*h{p|lTpGb|oUN*RQrau5f~g_R znEr{<&t`5s)hnn}y{~UeE$yE<-2Q04$&t>`xVD6T$Jb*hLiRWv9ZS6^E8JZr}$gn?~1%R!s0n_SW6 zU3D`l*uyBsRM?n#9O9;q!;7zabIM-VE;zS3CF#?7V>W0S8Wz zm0+EuMiyy11<`(Ci|lNXjNu}VX{{!4P2mGRh)%Wo1#@%>*&j?0)8+@Vea&W=I*i$a zrLflnjEv2+gpT3%0~=?*evGS|sQ1&e0n5F|_AP(LW+9Pmh@;1~qO(`xC)t3XLBatNvG)qPavm z$Hnv2#Gu9^jxD8Opo2)21;{0WWIac|mqgva_hsYam`0!9J#eQjbtz{UYHvD$Riy=(93(i)KfWJtx-9u0hePj)`w-vt|U zr=3>MHVqNl^QngK(04?NgZOUC&ne zaPR6=e|%^s>#7+SzTZ~g$nrc5wQNLfxpr}?K0L><;O+Ql`-%IP04M-io-xvfDniQ(_3@dI?( zNSF)ic`#BNI7Pr}#i-Wy zky$93{LOvqr)IJgWwPP(ZRKn4;ej=878&!NICQ>R%w^MA<=_My|5MB!1 z|ACpAznSySl(a8t33~P)-~0!XWoRzN&UXI4bA4fBk2LGFP>6 zxG*u!oVK35VC)KGcmDyu+0rk>z8+S+Faas`sR^=@4Sn33{%(d-V*v?)ch~yWY8!Si zWmiBC5X~X!sS-SLGL%H7s-uz~dx5E{#XwaF09u28a=JpwKsI6&29QxI4QgTpVOK~- z%6g%oCg8S77(+?a+ckLftfmlW&9A+ucCHT0vkne-nJ)xN88nNBsf~0GROx1XmZQTI zHwv0G4Agj-DlP*?K&SMKlpS!oy$XVhC1B+n8Mc?%g>nqBd-hkGm|k&qFMv|q%J!|_#7 zAAEAVv|PO_u9!^PWV7N$FQUgB(0I_a(JbKwlOM5-AzTTy)_Q>ie`mOcmWinAk!+>88jj~@z1OHKuPa;Gj=M^})(wg{>jF8J1tP3wrh6b%_y2X$J%DOaMsf381+6ARi3A9289&cV(^J) z=bb0#SNK83pBs`HMed&Nh&1DGUYzVtbGFY~e+O)z28OURcKEKSe0C2*v-#~O$~zPb z;GH5K30?O3l-?=ylY};xA>I*-^5qJ~o;(YrixYPE!5S8O$&eLjPUjMz;WX)T820d@FV|qfKjiolDvM9)%kGfSF$*3xU`oK4Vmm+G z`NhRfF%2N@v)rnw9Uzf>J>t_!Hw)MYzct)p=vr}cT7wA3M|5L`SIE!&XNqS?=NH?a zQh|N%@@k2xL76C86i`t^F0PDf>|H_*` z7I!(om{H;P>GcKN>Z9C8o=_N^-A{J6poMx!;|*;+N!@EsG3VEE9Yb-Xr|(y0)qxg+ zA@Ok>g6YfgQHqJhS6Kgk?U;1|DNZMbEwU-w zNbswyPT$}An8+pg=7&#Wt(XBnWo(W+>c%6hnt}F%G;4>`xLvDU*b$xa+uUx0n#?X6mP>q_LBA zhl4UiUlyMEhyIg_`r-w&e81-5`!^QaON}XtbNaG4-xEZjkJp`_5Yj|~zn$mh!HA9* zvrUmg3;Wc}GQUw5#jM=!g{iP9$qyw_n(=L7xJd_}l~E5f=g{z#B!9Bc-JVwZ;i09! zL6_jh2&JTnQ8xoE1FKrJP8AY6D&v9b>-rn6CoPnxze=uJWZ1Dvpdi&@v0G=U!s7Zw z+y1KaL;4ade4EGH>Gy4t&HFj3MK5rKS}zS{-DlEzizv1EeZ39zo@i5R35~-F?U4}G zM^_Ij%%Y-bV8#G-K2wy5Soc40Y04z%}XJq>}V)_$=oel0N z9Vi@R7K2R>6P1Ibz2N^p?(qNP9>y$2QAg{0!j(I-Tm-&bQ7p0yJ#eu^8ZJc!1nPKL z4Tx?}C-^BH-H&#_C1Vu%qS6{8e5wmC{}+QZjlGJkJ(o`h#s{gb0wFve4UV?ah3jMd zv#wr)u2SSZPj$WGeCgrsO}AEL^w>0}S)b>Bk!%)=+>|0AZ3z0l?D>-CX4#&}b_|^C zI5h6v2V2Mw!S=?KSoq01X@D4f^(@{|>t>pU@#8S-`wBGW;k4rbpe-`ha7Z%!F!(9g zuF3ffNqTfveIV)<31#~ok}>s`#8X&I-S^dM5``#5jP zwQ>jSAguo9KcG7d2{VlM&wRs7!=KBrR|-otQY=wn2u_GfTwxfRu?roMr4Br9OC5g} z&fs3)BtsH9n8)Al_NaW`xq+7bwH|Lz!YL1)t|pE)Md?pW_A=*I3PG3{_RhkmhO({= z!Xc6{Aqwa_IX%p;0!6j^0y|(cT<*cd3g%JO?^CNN;gr}9n&%e&@a(0)59pIh-Zji@3;z|6L>ByvyeH~ z!hicw97+y}wlc>CwmpT_$4!A{qU?9WB(^Mgx`=Qy zWCH2Go{Vayo*ethrB^MCVw>U4>29Sq`y$`xpldH8xE6AVoc-Ta*XWfY5MiAD<>EkK zOVH4NiR(Zesh`l}>mpBd!|z6sKvKIx6>K{`H~k0`tMmuuEa!m~CX9c#qi^{B+m-5h zlvU5>OKWe>c50tPhG;Vm*6)#jY~gvOYdiD6#&?vYbpu941L~^qFE}`>=M`Fi-!|B5 z7;A5bI#_?gQicgS&V%|YNdJzFr3|u4dnl+|00`P*@OwEdzz5RUTKLbeXYWlgdzCV8{r|6AX5o_~B_ zj>De8M#C7u_#nHuWRDc?Fq3Fh@F9fmCb!3GLZtcq?5fkTu&fS56T?^@lQQxWm6k9E zzzQos-M z;~_YevHlS~U$0(!|Ew~YP{eHHjNw+salZW9d?&mqfjk`Ptql&0SbXF;shmJ5hmzjO zFWNa9Q(5u)f(4kuTXjwdgttlBZIl(26>%<)M4La453FZ5%WUYB?J0U8aP9inYmh#4 zW_j_LBISqKngpY~gmYRFhQ}(^#oL>?Hh$}{>S8@%TXW*S;v{A?2 z_1`(mlK$HXpOf=>;=#I?7kJ#cDAonLl&zW!A-zr}l%$lLw0)1%BI3~0ju;rl3O1iX zG>0m3667nX2mbiiZ|p%E96t-lxmkb16vo=cJeGfw889bITkTi(xuo~y@PG;I=UdV} zB8r5Zj0>I}H_H)tap;wAW#5mQ_ZYtH7zY2-k+V0@BY*a~Kz3)mCp?DZ&imxJlXf;( zPMJY7p4+N`Wzv&5M8@#zgZYv&M`|883JeM0#>535agE_U*(YY8ecJ^5W}c@agQq1! z6w4YkQ>UDYDWSBd82(s!;njqf;1$U#fq!ZTq%2UA5Y{92Xjiq#N#=bY#t9a0wv2ZG zeT8N)>_C_pDoNkPQHT=C*pTZ;-)<{CVV`uH{h5*g1xwQ9*7#Wc>0Vtqb75Z<094s>aX zyU;278%j%Ijqe zuaS53*$x3s*EnHP#VqqjudyUZ2`{sF&pU_UFiz zLK0j%%aHHqNU7cx>6v~Wn8irO^a$uyc0$Pc2N3dPf3R(4FMHSp-_laWvmpO&;kOuL zd!595R=v*J|H4Q_%7AMb6k|_o{fo{l=X?KdzH{W}KYNRt^TqiNo~Jh{S8*rl!|y0O z8y482Zz*%(?UM7F&+U&{GA%i7Ge#^>Uhh}mg^uXAu2j0y*~GJXSp+iEYsimCPdpD9 z{SW(-zgH+*hS^R&e6yt4q-I5QAiNOx)}vZ+ZxCwcVPG>sFZDCJI5jR~igCee+&BkT z1geJdX~cp5?>WzVva^Cy-=tfBNK2Ug_dP7u_U*XpAFv+}?Vf6=*Ylnc;Vt0K0Z|Dx zP-Ez$=!c5e7`8$f7T7MB4kv14)zV&6%3qzV&j<`hH!^=O=tN1d6*VfhdOw@rFCjD43BFFy6O<&_|^is~HlD^&Sm(0y{x()$hP2)!TdWAhl9n z$cG77duCP`)}9iZ#HbE+`fEiaBIly(yxJO6Ho{_$u;;Je)MqJTqFN|bj$uo$DjR9^ z$t^kj#BcEmCTd;j?lLYVGEpjv5a%Y%@qcb&snW1ryPn;>p#7rQ%=a{gbZi!@WTNo& zv^HO=JgC0!dBOba;-R5|hC^BuwBaZb(~(0J-P|N%YS|WVCW1ztD8lBIX`1V;WPX+> zaBXI^lm8&|S#0R~b3*yz=g%B}M&a`Q7fJdSQr4U)wwNEu>wa52aCouP>Me2UkTPa* zQCdqjUUGfPuW?wl&8N&Aw323!M2GoXaoN~Tkn5ng6onm`)=RZ~owm;M+)D3YNblcvS`dTfkEGrgKC|xdPla(w^^3?Eb znq~&>FOs@UXsJVYKtUEq#zL@aS%J-zs}C#Uo6F4JxMmE)MM(Cg`wI*jkQhSi+AL3| zdeMc0n7I1ZI<^z8Bw8MTU9>XY^`~{(+etf`TNlXvKgfcf6RW&vffDlI3Bj|c1vU6D z1yX`<_hVavC{;D9=?mh$fG+?Qf6bX#K>TQ;gFv*W+@aEZnF!hOC~6V@4Kk93p1)jE z30}y1MSvM z*$0RB^aDPxb%>&Bw?sFO-+YzNP5H%B5P<=qe$KFyfjyHj4DV%BE%yEozEa#X(QaXzc~my9^%Rm`B4Y04?s(q+j? zypUEsH&p%~T={dYr!_m=HX$-xNe_bRPr;@CF z=ckfLP;iZ{AJ30CuL{wq(5tvTRj&UCU|r2&e8^f0x6-;*LT+%? zS}AsRoIu^h1|h_YRouRr2!6bTweBvWFZZO8y1p(|@n&i=m)>nQrf5Of;ze8qhYb{H z_w~N7_y#kdCZ#8{yN({RAI92=@GI{<{qLZlHk=Ts>n+MisCmWe_g4uegDoHq;Rd>d zN5L88heSrM(w?E{UWiWD@0HLNK`j-*V07!<%^A%UMmB3@7Z~Qk-*VO@MUebnv8lXR z6Q5mpUk5wMRv7YUU9b*vmfY0CdH^O}&{87U0NXeriV+j^x0KqROo6Q*{n+vC8wu}v z?KP_80csJ&aGsgOoSa{_-2&bqsw3V8_T+W91u8$^tCvsk}c?DjYnl z#xiJl5+pieFT=1MP+yZQtAp13U4Vf=Sz* z8oa2g6uOwa$$+oY$u-bd7Kvxy%^qnT?ZoFv)o*7_l3|SgA7)cOUSb| zj)&RxZX?d_jR{eV)(fIH2>}va+E??bpl-6H-Si(nO`6spAA^LX(gS)qMnko&UZb+V z*IgUu%q2G{@$YB#QEyw(!%KaiSAuep=8fxy>i!q%G0PLbv)VG%1A6Bb95WC!wwSyN z#WlB56lwBN<_XETf6~6?ui(a@An3n!jE}+~x@+75ZaLxGecT>YBZ)HPA@$dKtKAkI zEuhJ)`;flwYC`gnM#*W?+s;SVSJu!{uFeS_lg0S+H_wC6Q(1U)?&2Trp52l`OLcoM z=Zc-zxqiMCrst}=Su`KbX{(v|Qc>rgQjh&bU&q7%4UoRy8}YC;II1u}J>mX7AA}VvwAlp1B4Ws# z*S%gm*I+P;c41bRJPiS2IK-*f--)4{<%z)Z|Fl_|E2oBfpQ;iZ3fbcj@P=K6Fl+SR z|HX}l7H1v9H#av!)g)nEFP!VKF88t#+sVriEE zG_dZn3d84v>_FpL05j}0sJ&)N%)Q%;!D6~Nfwg*a3CA_J>+53DiwXtKzNT9i%&CiJ zkpRp%kQZj^T6{wPH7WwH`T)XG06uJT`_TI(+($Ob$=Et$WYvt;WF%7C$2T9xaH zYV6%xY)l5C3l%Id6BEsiNLk{%r*I|wQBVCKBV)n1X~olvo(BdqHANWRmxLsKd(r2 z4s2!;BgrxKRD!<+Ke5?OQs=2EGn08FDzGL5)agAom#cAXS0a;5@{_^}czAL6#jSF0 zS{7l!mBk>wetLlcwp1*uN43RKfYY#9yX#>tVGv8%dOV3piv&oo1@62A;GB_ANA39; z%?_{F36A<^)ySiN7E5Z@v$lW2r@cR5+aKQe@{w0P8eubh8oh6H%3 ztw$tBKA%Gz9deOX5tta;Y+}40Z~Ln=)D7UK;Zh(1SPpus9=`B|Oe=|r{o9rJ{X~H9 zml2U51@*IG$tv>iwa@6GP!in@xLC?(I^q-;jn70NggaCGw}es# z70EqQu8QGbnX6KEu(`oW2{#+{2trlH3lW{2=i7x>1(U0%|Ft@oNA` zU5a~8vaLw(A5KD@(dT6eJ?uu=4yBfFAD)Y>76NMEvP&>b3@I)Pw!(Vv)Duvrlx!^m zzl^JWZ!kPi=2C}mES##&l<>8FH_a#Z4caX+jaQCdJe5k$PG$mAEYjR)?=`-(8OCy) z^7@3^j7F8R>!3%(k8JYG>wK%6L8U)Hl=1wx2boDAG*`mcrX7R2$f%5nU66CN;O2V5 zS*t>+P7RLD;i)TyCbf_JrwzO8F-<~M1zVA4_gUeOw3T0-k-KtE0fH5#o0t?6f;)7! zJ}0*rml>|7&Da9JWGkO`P_R%iP|#5DHeYb;3QYXPfqKKJlQKNJ#qu^Xzv^Jg=(ob6 zpO98_SErky6h3@q#5WR!P>)ac$b_3o8fV8h#cgbFIxLgDS?(>$7Yc<4EI`6iJq5{w zlRtIz!N1XaR)dGD4&k-mFmbcCW@x&`-nDoC?ceyc>a~fQWSp&}L{Ib-g|)%NqDq1Y zO@ZTbIZ+x62#X9^l3mngl&6vMmvm-*aV33N%B_tc36QZ@l_({Zx1W$THVyf@)!I07 z&Fd6FsN%tX-J#Y2(6m(jB$_aoPfdRSG5KL`{c={n4@jnX*SKuk(csU1z2DJ3t8HpqW_T1Jjf}n|^P7ir44# zSV#>M!9*jDvD|mlAoqUrst`*YOG|-%cIvlbltoOc)+ItGp&4+u*??mK@#bKDG z7l9KiEA~~$o<6mXp|H1)LtTuMgY_!x>gsfAhW?^d51O39e525i(Ykeg4hV9YQa<^- ziy|~dDhT-i8 zBMbG4Y*rRqYoz{^I6{s%s0J=@E{n89?P+kOR{`88_j7CA}`A_g0yIacc@86|= zxy8x@6FP&q*A%T;Ttb%!9c;bpmDDk5MMIH2-6_r%NoiJ*Rx2Yol+x)WI`X<6LrW-% zBtQr%JXGDmp6vU;ass!!{i4AS3EJ_8FK$NnoVLTch*QkJ~J8&CBiiq82iIid{Ycs^y0L9T8n8GSb;%yue*N(x=Q>Zj7q z?PBDRSe|XplfqgUBTRG!vCcbqZB4pa66-tx(6T`AtLeIR^AH3&zEAEJx6Wu?#RDK@ zdMzUfTKZ{gTT#^u#gx|k==phy$L9=~t4PaLHdNz_;X6{E@h-_{8x>O--bC6&5N~wF ztfiLSMRe1~lJaqT9WGJr&d%r^Lr2klhbq1`Cmq!+i=W$a9n+!Mr$jv`q-6)pCW z%OtX(W&(gXSfeaRz0w%MOFFXuf1ADVB$M_z4G6+@(Ym1^D`A7xXUz1bjq8w~9|$Tq zuD>s#2L&GWuW<2b@yyZ;%FdUD;vr}aMy-0LaStrr4|SrGsCWE_6{>Lgc2sD*H2i@G zjs?aL`5p_0UuCn+GdhslPJcp&hEP|2^*O~8ipaip2INn@s$_^c*j?k!uJWRp+px^V z3A^uq1MBa+5uSCkja)r+U)TliowzfY*B8=9rx_E&HE1B%cJLt#B>l5KB@qhFS&Dr6 z{CoJFTKSIcFgzSGiq=)3FNAZMsj;_P$wUa@tem9(AybNiyGBzv<4+OP^$qaRR}g&o zFd5ZB4=8QYBq*FPG%`k6@>>c(d}v4f1IxZU{~*8~(PC01;SCmMh_KP#8Pi|5q~sUs zQaB5Tu;RrJXK%uS4l|+-IPD1Vi#CNOcbuJ@FsVaMqw<+F(#u)8M2rN*K$OHGe8oN@ z*984Pu2pZ;z|ZZ4iKr@lTMip#f8mJLR9RVBovmv{JMh>bNj#T`KO`M)+Fc+s7?*0w z3J0ZjOsEt74uu1yM$|8s!Qu*>H&FMdc;RMjB1EZPKex;yxZ+Bs2{y;}sd`Qg50}Wj zBfc>gEi`hgp6re)t#mIomC+v6Zen$$fV@&TWWRl~o{uog?k7juY zuwfN@AHu<~b^P5#g1cb_$2gFbJxI_Z5+Us8=)umZPI$nW_z54t+&{fLR zw1c|H?8uO)zWTskpRAQPFYA}|piFImK$ZdtUu&kx5B2>|*L;lvtFKch2Mdo}FixfO zr_`8QSw;GD9kEq~&$}jllX{}W#rNH18U~e!?qGx__tbLG&fyEbsEA!+>S?*@B})8t zHn66WE4`iKM%iP>G&2me|8;XJu_(#5>kL>2>>%wTB-b{H!2_5k0H!bxN-PHeKm4Hwt!W}EQ1I5trKw>b3VG7w zAhHvS4R?eN)5q|4`>cd3br=>;ZIK&k1~Df!zkYcYk$$QFV66Az`dBSfO9u1BglceO zlAYn(oLzoT&G&+j5`!PxpQb~*xb?KFegS6UhVN|05z{R0i8Sx?-RcyaCS*T z3z0#MFZWW4|LY0-_ZIT>q>0n+ZME}k2h96;M)FW#?_N)spkL=MP~Yfk+F=QBPlTDG+*>=d$*5~$bK4E zv%EC1<>2UC5Fob3f)lc7DK*-StJTB#Tr$um^9|<1DODmMYR1pscV6FBzv(;=wby+w zi$FX+s*XMmnjn32MP__rByrzi6xQfhZoI4N z;lNSdF@+MRg#4j2TYn_6TG8cx9VD%w{!I|}(?u((t1-Z?9>x>(P_Rr?Eaj|0HtR;e zI*~;4ln0E66@;u;qmsjX1!uaWtQpLqc5ujfhjyyyXDa4)81U%NlczFPERx?&^pm!_ zh;K$@nM8~eFLEa6Q2k!>K}bWDxvZ_8jY+zaf3P0Uw)@j#JbJ#oqIUh-JKxpeeCiuQ6g!v&o4G|KgQ zQd}LN!hOwKS+R{{2%}w_7|GqNprCy_#j%|;@YImn<#o1ae34TlQvRgKq2N)Nt_J3g z!vsIW1PMj#^QYZ4-avu)Hd2 zr*$)o`TeMBKGV}dfF)}SinRwg(w~U|0fx4VNi2Mkz`YFi{iUn{zG&HpTO+tBJ~G2- z-Bv|84U+^C_5xQJ*22LTR|WO#hR&R2?c1;cwe~ql?%Au2`fN%wb#uh7gU9@65n^L% z8w32#Pi4H~te=M4f4y-zidbeDfYRYyH^0&{t6K#;2Dmsz{eF3Grq@Z2AXo%oU4rb6 zl36ik>t`+31IAu_hA=_bXU1t~0oJ?PKxzz)`s3oE=NjjI&f%ZTM}R7(x=$BVNhnZe zo!ermgRnfVxr1tzoT&ReSbfD;$(tdcgfiQHNjEUv|Df$%-rbDz|W)VdI3 zJ;-5LhZ=sZ{ZaIY{HDlBscLRusykmR#Gb97%Gw%kl`e;{R=VjeFg?2$&_(fkq;1Ed zy$`*qgl?PdBwS`&ALnB?+wTLYNw-^MMLjnUvUA7`gm_2EnnZnE2$9atPtHssQfnfZ z8+eX4c62?xitM`$BW}hR9{7(Oa751-6Ax^A zl>)C(-V3Ab$3HG+N1-@|?4rm(6|pqm?3RagrxX!`G2Q@T{RX+hWD<#~lv<=21pJ)6 zSOl0RwTbo&$0@&5@}lF#24;V8n67l&4t_m{U$h%-SL3LdLg!+EBtXwD27#c6=h=mM z8H!KVG`T|3T=|lS{hSq^O#@D~cU7!opxYA{y zroaEg@rfk)0&dmWQ#%J}2OACa1oU6Mjr$Ee!PB}C<4;7?bB>xULDYU8t4n|sR%#Lh zWHQa82UfYwF^zXtIa{Vk*E4(U+Lr^%(BB}w2rIB&(v%H~|*Nk?CM`FQ2Xl@ zGQmTlvNzx>`Hhohz8opNWFX7HhX3$NxpIuy0k2fGqcPJRU9zPyHKMoGI46E<;KCA& zHcrg@KUlGVa~Ynb$IQQ8ecqJl*R4YG*e5Ye?Y4079Wldf!?^T zM!LwhMC4}eTV17pD@>Jf_mGeIIz#q+lpF0DpQQq|JX&bf=Zw<-n*l3PinP?9X>7A| zaNNBAp3!g;M$ul2N|J%bq?MBohy0_y6fdh35xiSRBEiOr3Eh+BTT|sHjGm7K1*AN3 z!7nJa29I_opO503lz0RD8OBQBR4CT5ndfO@E9X9w=@BQnpUZo1s>1`P6!=OU?hQ;q z+5DN#+S;eNdl$`lllTr|qKMV2c3kJ<#;=0bX!rkgQYF}%F=hpA1U1cYM>0#;@j~%ZB&i(_ z0K=hRWpW&DNKrf1gwRz)_lboeW|`xT{@~2J2d_6NP1hT4iY@+yS;oUpiApDUD{+m~ zpNi1++);A2e7f7qqlBN%drN;b`8M6z)gcef!ETn7S00Xt9?uUNgoUY#y)*ieJe+e` zwhFLBDfWVuF6GRex9`tA^Z!$ZSSmt&p8kzuGzbZp7`&=>&NSg6{MYE!Zi-vsv*n0n z@0Fyry#79?HO6_7h+S+r2z#N#0hqJ$p?p(Zy!4j(CFq`}QlzkgU>fG$uJ{k(zT?<( zNdMwC9qj<9x>{U+J_&qJSo%RPcGRHI?UNZYKT3v7kaesEryH?#own{P@5q!kCgYDG zG0o8U?T{k$Le`r9nM3^N4{MntXv0?5#9(j#n^9*qXT&?sTDg6f+xgP!{4sx1r8l7! ztj3erq=Sc$IgB8$SUZ79S<)kF5?BHQ&*2MqGG=f&Ps*}U)^lw>W_HiZOp#q`bE%SL zcdZS@$5sB_EFnMN z(doyRz&=i1-t0D>2cFLyQdV!UG5AauuPPo7&Te4OeyurHu7!AfCss@f{17Wv`qz^y@Fa2#-`itDYNts4PAj(q?lGFhk{jjw~?(UJh z03v-ri^1bk<6pQMU&(#%`(flWlMLt2S}IfTo&$ab)kJACkUPABrtI)^mayl0CyC!B zwYg5t{rQKH!Y3Xn^W~SjyRD5W*Up2HQt3=>n+0CW>`a}NnlA}Fm68n!Mgqzy zTD!aG7GgkDEd1aOH^dZC2^L6IT)nr@y#C6i7h0QFg=jQg{+E<=xPQOCA^19tktLZl zoNLPmJqoZyMWq*;7113%D4aMKqcXgR5oGI^%BlLEHgyN9Fq4>NCt#@Y7k=_!wD3PF z^nZ=L>dfwks(*qto426L-A!vro}lfU1JT?1VlA%3IA}*PHbkY~fnsDN<{E zBanK=H{1idGp4=ZBwFrGC0NVsZ;lDu8ZWr+aV~+CO$mV=XdKf|>#?jh1B)pJwnLUK0O0O@3r7m;VK~7bny%p-=i_98 znGktA&h>Hj;)mhBy-`o=;-2wp+J$RUa~aNYN+8Ib|45E-ZQ;k=ApGK^*7SS6R6UWb zX+$Kl1m2si8>j6u#>iJbA-58wvDl(KsM*<3Oo3vgMW55+%pm6z68)&ZqeXsqCK@w5 zIt;g{H3(A<0Ry-<_Fda8`Dpl%w)-kOpdLP+B8Tjj}x)i76w(|#rRqVB7BqX z@J42Al>9%xsT3KbG;=H2S!7zlIZ_hfkbcMg!Yrm6k&_7`B(G|?6=^@LRVwF9-^Qtz zhi!j(OcA`b_`cLQiJ+ zH!XZ3^Bx}^{AKYiTgTo=;S-;5%LjtJU94(toAf@Y65svjD2?)4AXn*XrME3+Z`lqV z;ol3_>qjgb9>}88)fB3^llyWqq3(f*#_ncqoCTH7rAf$Fh5lSa$)}JnSZMd8Oe`2$ z`R;B_;-(BzJ^dJ-UcgMGr;JxDcNqK7eSARuIUoNiKDA@vd(X-maLV9@eM?$2%7wpr zql1bVRHriPXnF_+%sD?%-CCth^w@{2fFvY;Lx<$np9l_C6l`*SyAP7e@n=??MQ0Us z)(c)#0lWqOj78HB&FhdFyyNm%aqF~coqU2XK!tG~u19qk#stpYiBxRCbs*)-u4SSR zN8Bj}l2VHR@nJfTy(&DH!+c^w|7!-&L5&=cb;(psJW^}Mc--u(5?E0t3!`y};aX-1 zoA3o?k2m5hk{&)is=Q<3WKr?3-XQi*;6E!Jp}N=9>aBXR-#_-d0}y*w+aEXg_HP~R zCK!#7wjDH?Zy41D^Cvaj5*6?JGnr0k)k&CCj_>wkRJ)kY(y1C|FJnYbB{0z|KdEUm zi2?^o4w?*0Z1u}uq$ogcl(`R>lFA|`|8v<_l>n&XhLaF(5d6WIps+;@1BX)J}#*>*lEW*KtgVZVJ zH$o?k{WKDZhE!Imk(lH9+!4>np|E>KV7>Hz3SJjCJcHBua;$ zVE}%fyk7NaU0W<)$=Yha;rU?=a^7*M6K3I_{#@4lezM)Pg;#{YpxFWIUEgxUs4!Z~ zpyD2H#|P34W5hJ*d|MMCba!^g%%K}M1IUN)3?Xbnx~8C}&r4w*%1IR?onX%%8FnNH zBkk3L-?PKy+p!@b`wql<77U|D4BC!#Vmy0V_Iihi!UQAUP3@OSV%zqy?P-TC+71Ok z9n)jr1Eh|#NBR5q9de(n(Vgcu!5iDv>|d&179$j3n4%yGX}aMBH6cU}m$GJ0IL+t6 znW*qKKzoockpTRCSNu+{7O7|ae_+Zg(`Go^g!gk+f9hSxyAR*Gto;m>?q`rIxpSQ0 z=ly5Ygne6@r9n|9m^$3U?c~)>*N(>7pY6ejNF)>vvN}Uw`-MS|#h#o9Rewv>;Q{`(}(z+Zh19q`xJCi+vaWK6VpZ|u^lT$#xBbjG9 zf32`3mtcU?lx@=7$x_Qw>jN6y;OI$zZ1R4zNDU-D4+kYZ$p&psBChDO#3b2Wd(R|P zdabt2z5dEl1@lZIcdM{yH4a`b%XzZwgM@X`W>SPox#btfK<`8?63+@9L7&Ty!|m(<0?hB7#wml<$#0 zh!mAbz}P%lazmA)bGag@yYHblV+!-{OupetkA4d9UE=6)k#Se~52LzQgZzz*MbG%~ z(IgiNsk$#Tz~7@INRsgGJz`U$8C zOh0zq+rdHtYA~L*d)~=lNqr$o_<#oy0+uGFBr z!TD$&m&TJb*>~22`fd&-dJQS7^Td{9nLmV8uegMAvZNi!Gq$ z_tpq@x=`W&9hl{f_h@H)O1j&NrUouDatqn%CGOXhQJLlw4N>*dNb zxhJi5ANlilBv+sPeT@(hd&hS1*W%Kr=ds5qOg?hP)d}zQ1+fiu&dsz>whNib&1@f7Mobzs;|2AbLX^DY<&xGX>x zs#f_u-wT3DCVH3Q*dyKQDl13L@HZ~Gjpq2YW8_2LV6TY3KRSoEi^S86y2^kzx>_W% zk%fvdvC5$Hj{uz1E_h2Rv3z1x1uilItRvc7#2$;4z1hkv{GUC{Ob`lb}p7%0AQ_7JooE6~|9rtcQ9tSz!{wSKw6{EYCnO?;g>CU;KV{ zvD`Yqanyn-hGl~OAFkdqF0Lo}9v%jFclQB;2X}WL+(Llh4nYR@;2PZBB|vZu5Fj`N zcXxt25BctI_rK59i~G5+?)3ESTh&!v=NwHvf4Baigy~uDu}d$JpX_>lc@3lnO|U0xx0ZU@IIipIWCz& zsJ{Rba3gl|g$l>L?h0B<{ruO3FYG(_Sw{19&Qn8A-*ZK|NQBS}+fO5+Y0u27=-~rU zN~a)Kz1OcRk!crWw^)j1j@H+->8+np1wA)^2Xiw)V>nn|!d%s><4SNnlWpH$&WqR1 z##0hdob-GkvoYfL8~8uCTS@+`T*p72^v1i6VSo$StLi(^=iziJNdeA;H*pe0rD6LY zXSRgqX#>BgLO-nfiKjBUxVUcJxjuhW9ebkz!Qe(tV6KFE`D{emJ4&AUSwSydG~FmL z*sIDs;w)LDjoT8;546egGUlZdNcS*)R1l)^CR-R@wlYz8bREd|9$Q#Q`G&o2%;4=( zyGO=cFetmlhK7=Ld5>pW2yY=ONV{66THfhB&dWlFiQ`xl3l_!OGZ>aNAj=jSc^G=F z!W=G)c(13|D+nKC2bG)|+f1wyU ztOAg{H%I56aStS($w5gbtfq$znU7SCi{&qT0lWuwd9O(t7F?-4<(+GE280fmtVg?Z z^j}X%pwnK{P^ePCmNHP#?0;T+lO6#@WT@Y0yMC|mClp0~E(ZHgmDDzFSFipyIOBmi zN3JbSIW&REW>Wj30~F_n1nZOH7?AGsMhM-U9Ia^FKsXOmt0nHsywoxd1iF=34`ntq zMTXHQl-;6wtHM4lVF_wiPK+={3g=ZYnjm(gx5)XkWwmG809c~68j(4?2Xs_60sZFIA9F-aWdiEE`C zhx~8fYzEhK&a`99A4g8p$%E$O2GT%oCBn6a(e!m6mda6Kt^C)=*0_-=|Qbe$KU8no5)-<%++TYs@wcrK6?j7%V4jKv^uJ+lEn_MpAjI#72oiRZ+;l&zUS)0Cld69YqS6%aU_GKQE6@&g6|b%6oHDVh#fnEd>C=}!z~jDJWm9SQ*$tc66$+yYva?dmy7*){3Cw0$a~aZ-Bg z;3;{Eb8`&hS(urNy*7%@%4IrS*AQ`Tsuj=&gehRN4&_b;Li0g50iphqQ?4o5XnogN zFUtA{ArQ63_zO`8COwBaA)|tOk$jdDEA+*tkhyQ%!ib8=?4bEE{E+)j$jvM}nfgQ2 zkxI;?a0ZABDvx8P4`ifCHp_$|uq#911ZX{8_<1cDhmQw?gf5fYc5i2JGS@V1FN!rl z^pffj*!Iiz(gWoV^BdF61Jja|>P93ACr?)P%NN}=egszNI27B6i>vXw`phlZ6Oj0? zcN!l?^*WHZ4a%A_W^3}xzk-*-^i@osq_q|YU}PhO)uZ*Gg1(>(KsmaD$_+Xr;idx#CDUnNXtMsD?tl*byd)4 zUbZ%kl9CW8@Rv=s`Jgz1iee^G=m91NCk)38NydXQfn!~7iWn=st-hWFHzANpjnHHN z!_x-L({b$M5;I+Y`rqP~0 zR6 zKJkLy5Bh)xEKr9p{*~XHfE$Zh?rZ+Zv&Us@9ybgFy`g{h7JHan)MDE@onTDCeKM?YfltJIDOA*C0w-`RX+!{oZfd<a~-)7k&*ug$~l;xfCoSamB=T+5MIjQj4mM)f>jP+lP7P(N2~F zExYH#aLV#4QAH5*>p4#IJ4ZIfDQNZOtUQ&w+qv%=9CGdfUs?oIISyV_Is!AzoPXhJ z{1}X_q<~J=VjKVfK(+5Znbs*VTsA`R2ZZL09v9~Z&N%vOo?%#ziQ(uY&_ z`4q-5uw)IQ=1)oW6pizroihKe&s{V&G$&nfl{{~3H)%v;=c(7oV|TdCiwobLt}OMh zKr*@;{lE`W!`w!#mT<*25*R)c$KA~cxW@lzf z+*;!1$cThT1viQbg!XB^Yn2|oS-x(x{r0Y)q>3N`eS`V(&LoGVjJh>_L#W-iG!BY4 zzX=*J&C@*h@NjrrLzB5FGdnCCnG_`4+Zp*YwFMnF45zZ33EbS=3tN43gX^t%4_;&J zI@JURsyhPtlF<&e4K9FGEnPojZwVjRF>qkn+@v0Dg-*aQ!hJ4m#6RX+e#4=rt=1@Y z*T^wZ_}un#H?t=AwyUBMT=a+Qd^w3(sW;FYbB_9Q;iUD@3B5%?USwVMn{EL~R;F<0 zG-8LMbh+mH|D3#F5o`V6xH*aqVc1R48pVSi8Gvs?eZAB9u2a1z2sxE42X6iTyxcIQI@op9gJE?2Z6mz+B8hP@5%Y}UfK0g#DX~_i)S+@7Z z7o!Dke=^O4HtpkC9c092SoS8GhIO54WB$po*X=l|2|+oEmV#!>O~z>}1vVRiZa4 zrOs6C9wG*2H)XcEuyTAF(Sqj9>;yv{D6$(w7&0gpt8b)+zUg8N(#8{Q%LR-g-DDKl zhg?Mgr~sL+|KYAr1p4=O5!C;^jOYysJVTA9a)94VixK zKw#?!b7GFNI*7kuQxrX|dG2Kqy7noRo%NM9`1=o+OyxZ%hfYB=GBJnDAvb?HR6Tge zPQHQ)`_)`XOS-}eaJVR{EoF7`lh4s4@^{%Gyv{k4KPm6bZS8JkycK2ycTV5BRoy~m zN%W5x1q$A&nln0-8hdtsWg~Y)sQomqa(I8`!h%#1xWgjxNPWZeiZGc@<{7PO*T8Ma zhL+-7WRyrLks0AxE~}75hYXKKVVXk*_^E^pn-;i14`bw;^yW4(DZgBKx@0 z(^BD1lW99O4GH|vETLGV^-*e)*Wji!c1oVT0P=ONfmL-$Xkx+yWiHqY=q}odz>Ewz z{$?}C@S)5mwxK2$Cw`EqW-RL+?uLt9^lnc7H?5LfGDVm=+�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 TXio0g-@hj*DkoASWDxj&5-I2Y literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/basics/introduction.mdx b/docusaurus/docs/Flutter/basics/introduction.mdx index c8f90b0c..bd4924f9 100644 --- a/docusaurus/docs/Flutter/basics/introduction.mdx +++ b/docusaurus/docs/Flutter/basics/introduction.mdx @@ -2,5 +2,15 @@ slug: / id: introduction sidebar_position: 1 -title: Introduction ---- \ No newline at end of file +title: Introduction To Stream Chat For Flutter +--- + +![](../assets/sdk_title.png) + +Stream Chat allows you to build real-time chat messaging in less time. +Rapidly ship in-app messaging with our highly reliable chat infrastructure and feature-rich SDKs. +Drive in-app conversion, engagement, and retention. + +This section of the documentation focuses on our Flutter SDK which helps you easily +ship high quality messaging experiences in apps and programs built with the [Flutter toolkit made +by Google](https://flutter.dev). \ No newline at end of file From dc6c3094e96f49cd182a72335f22f30762281f6e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 9 Jun 2021 16:11:59 +0200 Subject: [PATCH 345/599] performance fixes --- packages/stream_chat/lib/src/api/channel.dart | 2 +- packages/stream_chat/lib/src/client.dart | 6 +- .../stream_chat_flutter/analysis_options.yaml | 4 +- .../lib/src/attachment/file_attachment.dart | 6 +- .../lib/src/attachment/giphy_attachment.dart | 2 +- .../lib/src/attachment/image_attachment.dart | 12 +- .../lib/src/channel_header.dart | 3 +- .../lib/src/channel_image.dart | 10 +- .../lib/src/channel_list_view.dart | 194 ++-- .../lib/src/channel_preview.dart | 35 +- .../lib/src/connection_status_builder.dart | 44 +- .../lib/src/emoji/emoji.dart | 3 + .../lib/src/extension.dart | 8 +- .../lib/src/info_tile.dart | 3 + .../lib/src/message_actions_modal.dart | 244 +++-- .../lib/src/message_input.dart | 945 +++++++++--------- .../lib/src/message_list_view.dart | 218 ++-- .../lib/src/message_reactions_modal.dart | 192 ++-- .../lib/src/message_widget.dart | 146 +-- .../lib/src/quoted_message_widget.dart | 6 +- .../lib/src/reaction_picker.dart | 171 ++-- .../lib/src/typing_indicator.dart | 21 +- .../lib/src/url_attachment.dart | 2 +- .../stream_chat_flutter/lib/src/utils.dart | 2 +- .../lib/src/better_stream_builder.dart | 43 +- .../lib/src/stream_channel.dart | 60 +- 26 files changed, 1224 insertions(+), 1158 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index a2fc4ed3..1e7f824e 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1709,7 +1709,7 @@ class ChannelClientState { final BehaviorSubject _unreadCountController = BehaviorSubject.seeded(0); /// Unread count getter as a stream - Stream get unreadCountStream => _unreadCountController.stream; + Stream get unreadCountStream => _unreadCountController.stream.distinct(); /// Unread count getter int? get unreadCount => _unreadCountController.value; diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 1b98762d..17b5654c 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1532,13 +1532,15 @@ class ClientState { int? get unreadChannels => _unreadChannelsController.valueOrNull; /// The current unread channels count as a stream - Stream get unreadChannelsStream => _unreadChannelsController.stream; + Stream get unreadChannelsStream => + _unreadChannelsController.stream.distinct(); /// The current total unread messages count int? get totalUnreadCount => _totalUnreadCountController.valueOrNull; /// The current total unread messages count as a stream - Stream get totalUnreadCountStream => _totalUnreadCountController.stream; + Stream get totalUnreadCountStream => + _totalUnreadCountController.stream.distinct(); /// The current list of channels in memory as a stream Stream?> get channelsStream => diff --git a/packages/stream_chat_flutter/analysis_options.yaml b/packages/stream_chat_flutter/analysis_options.yaml index a37a0fb7..f0a87ea5 100644 --- a/packages/stream_chat_flutter/analysis_options.yaml +++ b/packages/stream_chat_flutter/analysis_options.yaml @@ -3,10 +3,10 @@ analyzer: - extension-methods exclude: - lib/**/*.g.dart -# - example/** + - example/** - lib/src/emoji - lib/**/*.freezed.dart -# - test/** + - test/** linter: rules: 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 a676e576..7ec5fb04 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; +import 'package:stream_chat_flutter/src/upload_progress_indicator.dart'; import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/upload_progress_indicator.dart'; // ignore: always_use_package_imports import 'attachment_widget.dart'; @@ -103,7 +103,7 @@ class FileAttachment extends AttachmentWidget { Widget _getFileTypeImage(BuildContext context) { if (isImageAttachment) { return Material( - clipBehavior: Clip.antiAlias, + clipBehavior: Clip.hardEdge, type: MaterialType.transparency, shape: _getDefaultShape(context), child: source.when( @@ -154,7 +154,7 @@ class FileAttachment extends AttachmentWidget { if (isVideoAttachment) { return Material( - clipBehavior: Clip.antiAlias, + clipBehavior: Clip.hardEdge, type: MaterialType.transparency, shape: _getDefaultShape(context), child: source.when( 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 1bbbd54f..1572061e 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -53,7 +53,7 @@ class GiphyAttachment extends AttachmentWidget { Card( color: StreamChatTheme.of(context).colorTheme.white, elevation: 2, - clipBehavior: Clip.antiAlias, + clipBehavior: Clip.hardEdge, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topRight: Radius.circular(16), diff --git a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart index 5c431d6e..c677435a 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart @@ -74,16 +74,16 @@ class ImageAttachment extends AttachmentWidget { if (imageUri.host == 'stream-io-cdn.com') { imageUri = imageUri.replace(queryParameters: { ...imageUri.queryParameters, - 'h': '500', - 'w': '500', + 'h': '400', + 'w': '400', 'crop': 'center', 'resize': 'crop', }); } else if (imageUri.host == 'stream-cloud-uploads.imgix.net') { imageUri = imageUri.replace(queryParameters: { ...imageUri.queryParameters, - 'height': '500', - 'width': '500', + 'height': '400', + 'width': '400', 'fit': 'crop', }); } @@ -92,10 +92,10 @@ class ImageAttachment extends AttachmentWidget { return _buildImageAttachment( context, CachedNetworkImage( - cacheKey: imageUri.path, + cacheKey: imageUrl, height: size?.height, width: size?.width, - placeholder: (_, __) { + placeholder: (context, __) { final image = Image.asset( 'images/placeholder.png', fit: BoxFit.cover, diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index 7773b36f..16c1f1b7 100644 --- a/packages/stream_chat_flutter/lib/src/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_header.dart @@ -133,8 +133,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { } return InfoTile( - // ignore: avoid_bool_literals_in_conditional_expressions - showMessage: showConnectionStateTile ? showStatus : false, + showMessage: showConnectionStateTile && showStatus, message: statusString, child: AppBar( textTheme: Theme.of(context).textTheme, diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index aefdb44e..dd9bb314 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -94,15 +94,17 @@ class ChannelImage extends StatelessWidget { } else if (channel.state?.members.length == 2) { final otherMember = channel.state?.members .firstWhere((member) => member.user?.id != streamChat.user?.id); - return StreamBuilder( - stream: streamChat.client.state.usersStream.map( - (users) => users[otherMember?.userId] ?? otherMember!.user!), + return BetterStreamBuilder( + stream: streamChat.client.state.usersStream + .map((users) => + users[otherMember?.userId] ?? otherMember!.user!) + .distinct(), initialData: otherMember!.user, builder: (context, snapshot) => UserAvatar( borderRadius: borderRadius ?? chatThemeData .channelPreviewTheme.avatarTheme?.borderRadius, - user: snapshot.data ?? otherMember.user!, + user: snapshot ?? otherMember.user!, constraints: constraints ?? chatThemeData .channelPreviewTheme.avatarTheme?.constraints, 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 f5780d73..12b73f72 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -1,11 +1,10 @@ -import 'package:collection/collection.dart' show IterableExtension; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/channel_bottom_sheet.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; -import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -245,10 +244,7 @@ class _ChannelListViewState extends State { } } - return AnimatedSwitcher( - duration: const Duration(milliseconds: 500), - child: child, - ); + return child; } Widget _buildEmptyWidget(BuildContext context) => LayoutBuilder( @@ -473,97 +469,97 @@ class _ChannelListViewState extends State { final channel = channels[i]; return StreamChannel( - key: ValueKey('CHANNEL-${channel.id}'), + key: ValueKey('CHANNEL-${channel.cid}'), channel: channel, - child: Builder( - builder: (context) => Slidable( - controller: _slideController, - enabled: widget.swipeToAction, - actionPane: const SlidableBehindActionPane(), - actionExtentRatio: 0.12, - secondaryActions: widget.swipeActions - ?.map((e) => IconSlideAction( - color: e.color, - iconWidget: e.iconWidget, - onTap: () { - e.onTap?.call(channel); - }, - )) - .toList() ?? - [ + child: Slidable( + controller: _slideController, + enabled: widget.swipeToAction, + actionPane: const SlidableBehindActionPane(), + actionExtentRatio: 0.12, + secondaryActions: widget.swipeActions + ?.map((e) => IconSlideAction( + color: e.color, + iconWidget: e.iconWidget, + onTap: () { + e.onTap?.call(channel); + }, + )) + .toList() ?? + [ + IconSlideAction( + color: backgroundColor, + icon: Icons.more_horiz, + onTap: widget.onMoreDetailsPressed != null + ? () { + widget.onMoreDetailsPressed!(channel); + } + : () { + showModalBottomSheet( + clipBehavior: Clip.hardEdge, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(32), + topRight: Radius.circular(32), + ), + ), + context: context, + builder: (context) => StreamChannel( + channel: channel, + child: ChannelBottomSheet( + onViewInfoTap: () { + widget.onViewInfoTap?.call(channel); + }, + ), + ), + ); + }, + ), + if ([ + 'admin', + 'owner', + ].contains(channel.state!.members + .firstWhereOrNull( + (m) => m.userId == channel.client.state.user?.id) + ?.role)) IconSlideAction( color: backgroundColor, - icon: Icons.more_horiz, - onTap: widget.onMoreDetailsPressed != null + iconWidget: StreamSvgIcon.delete( + color: chatThemeData.colorTheme.accentRed, + ), + onTap: widget.onDeletePressed != null ? () { - widget.onMoreDetailsPressed!(channel); + widget.onDeletePressed!(channel); } - : () { - showModalBottomSheet( - clipBehavior: Clip.hardEdge, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(32), - topRight: Radius.circular(32), - ), - ), - context: context, - builder: (context) => StreamChannel( - channel: channel, - child: ChannelBottomSheet( - onViewInfoTap: () { - widget.onViewInfoTap?.call(channel); - }, - ), + : () async { + final res = await showConfirmationDialog( + context, + title: 'Delete Conversation', + okText: 'DELETE', + question: + // ignore: lines_longer_than_80_chars + 'Are you sure you want to delete this conversation?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.delete( + color: chatThemeData.colorTheme.accentRed, ), ); + if (res == true) { + await channel.delete(); + } }, ), - if ([ - 'admin', - 'owner', - ].contains(channel.state!.members - .firstWhereOrNull( - (m) => m.userId == channel.client.state.user?.id) - ?.role)) - IconSlideAction( - color: backgroundColor, - iconWidget: StreamSvgIcon.delete( - color: chatThemeData.colorTheme.accentRed, - ), - onTap: widget.onDeletePressed != null - ? () { - widget.onDeletePressed!(channel); - } - : () async { - final res = await showConfirmationDialog( - context, - title: 'Delete Conversation', - okText: 'DELETE', - question: - // ignore: lines_longer_than_80_chars - 'Are you sure you want to delete this conversation?', - cancelText: 'CANCEL', - icon: StreamSvgIcon.delete( - color: chatThemeData.colorTheme.accentRed, - ), - ); - if (res == true) { - await channel.delete(); - } - }, - ), - ], - child: Container( + ], + child: DecoratedBox( + decoration: BoxDecoration( color: chatThemeData.colorTheme.whiteSnow, - child: widget.channelPreviewBuilder?.call(context, channel) ?? - ChannelPreview( - onLongPress: widget.onChannelLongPress, - channel: channel, - onImageTap: () => widget.onImageTap?.call(channel), - onTap: (channel) => onTap(channel, widget.channelWidget), - ), ), + child: widget.channelPreviewBuilder?.call(context, channel) ?? + ChannelPreview( + onLongPress: widget.onChannelLongPress, + channel: channel, + onImageTap: () => widget.onImageTap?.call(channel), + onTap: (channel) => onTap(channel, widget.channelWidget), + ), ), ), ); @@ -637,12 +633,10 @@ class _ChannelListViewState extends State { context, ChannelsBlocState channelsProvider, ) => - StreamBuilder( + BetterStreamBuilder( stream: channelsProvider.queryChannelsLoading, initialData: false, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Container( + errorBuilder: (context, err) => Container( color: StreamChatTheme.of(context) .colorTheme .accentRed @@ -653,17 +647,15 @@ class _ChannelListViewState extends State { child: Text('Error loading channels'), ), ), - ); - } - return snapshot.data! - ? const Center( - child: Padding( - padding: EdgeInsets.all(16), - child: CircularProgressIndicator(), - ), - ) - : const Offstage(); - }); + ), + builder: (context, snapshot) => snapshot + ? const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: CircularProgressIndicator(), + ), + ) + : const Offstage()); Widget _separatorBuilder(context, i) { final effect = StreamChatTheme.of(context).colorTheme.borderBottom; diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 3189b0cb..bb41f652 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -1,4 +1,5 @@ -import 'package:collection/collection.dart' show IterableExtension; +import 'package:collection/collection.dart' + show IterableExtension, ListEquality; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:jiffy/jiffy.dart'; @@ -69,12 +70,12 @@ class ChannelPreview extends StatelessWidget { Widget build(BuildContext context) { final channelPreviewTheme = StreamChatTheme.of(context).channelPreviewTheme; final streamChatState = StreamChat.of(context); - - return StreamBuilder( + return BetterStreamBuilder( stream: channel.isMutedStream, initialData: channel.isMuted, - builder: (context, snapshot) => Opacity( - opacity: snapshot.data! ? 0.5 : 1, + builder: (context, snapshot) => AnimatedOpacity( + opacity: snapshot ? 0.5 : 1, + duration: const Duration(milliseconds: 300), child: ListTile( visualDensity: VisualDensity.compact, contentPadding: const EdgeInsets.symmetric( @@ -103,14 +104,16 @@ class ChannelPreview extends StatelessWidget { textStyle: channelPreviewTheme.title, ), ), - StreamBuilder>( + BetterStreamBuilder?>( stream: channel.state?.membersStream, initialData: channel.state?.members, + comparator: const ListEquality().equals, builder: (context, snapshot) { - if (!snapshot.hasData || - snapshot.data!.isEmpty || - !snapshot.data!.any((Member e) => - e.user!.id == channel.client.state.user?.id)) { + if (snapshot?.isEmpty == true || + snapshot?.any((Member e) => + e.user!.id == + channel.client.state.user?.id) != + true) { return const SizedBox(); } return UnreadIndicator( @@ -159,14 +162,14 @@ class ChannelPreview extends StatelessWidget { )); } - Widget _buildDate(BuildContext context) => StreamBuilder( + Widget _buildDate(BuildContext context) => BetterStreamBuilder( stream: channel.lastMessageAtStream, initialData: channel.lastMessageAt, builder: (context, snapshot) { - if (!snapshot.hasData) { - return const SizedBox(); + if (snapshot == null) { + return const Offstage(); } - final lastMessageAt = snapshot.data!.toLocal(); + final lastMessageAt = snapshot.toLocal(); String stringDate; final now = DateTime.now(); @@ -219,11 +222,11 @@ class ChannelPreview extends StatelessWidget { } Widget _buildLastMessage(BuildContext context) => - StreamBuilder?>( + BetterStreamBuilder?>( stream: channel.state!.messagesStream, initialData: channel.state!.messages, builder: (context, snapshot) { - final lastMessage = snapshot.data + final lastMessage = snapshot ?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted); if (lastMessage == null) { return const SizedBox(); diff --git a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart index 9137265f..98daa580 100644 --- a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart +++ b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart @@ -8,7 +8,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// /// The widget will use the closest [StreamChatClient.wsConnectionStatusStream] /// in case no stream is provided. -class ConnectionStatusBuilder extends StatelessWidget { +class ConnectionStatusBuilder extends StatefulWidget { /// Creates a new ConnectionStatusBuilder const ConnectionStatusBuilder({ Key? key, @@ -36,20 +36,32 @@ class ConnectionStatusBuilder extends StatelessWidget { statusBuilder; @override - Widget build(BuildContext context) { - final client = StreamChat.of(context).client; - final stream = connectionStatusStream ?? client.wsConnectionStatusStream; - return BetterStreamBuilder( - initialData: initialStatus ?? client.wsConnectionStatus, - stream: stream, - loadingBuilder: loadingBuilder, - errorBuilder: (context, error) { - if (errorBuilder != null) { - return errorBuilder!(context, error); - } - return const Offstage(); - }, - builder: statusBuilder, - ); + _ConnectionStatusBuilderState createState() => + _ConnectionStatusBuilderState(); +} + +class _ConnectionStatusBuilderState extends State { + late StreamChatClient client; + late Stream stream; + + @override + Widget build(BuildContext context) => BetterStreamBuilder( + initialData: widget.initialStatus ?? client.wsConnectionStatus, + stream: stream, + loadingBuilder: widget.loadingBuilder, + errorBuilder: (context, error) { + if (widget.errorBuilder != null) { + return widget.errorBuilder!(context, error); + } + return const Offstage(); + }, + builder: widget.statusBuilder, + ); + + @override + void didChangeDependencies() { + client = StreamChat.of(context).client; + stream = widget.connectionStatusStream ?? client.wsConnectionStatusStream; + super.didChangeDependencies(); } } diff --git a/packages/stream_chat_flutter/lib/src/emoji/emoji.dart b/packages/stream_chat_flutter/lib/src/emoji/emoji.dart index 617e372a..38af85af 100644 --- a/packages/stream_chat_flutter/lib/src/emoji/emoji.dart +++ b/packages/stream_chat_flutter/lib/src/emoji/emoji.dart @@ -114325,6 +114325,9 @@ class Emoji { /// Get all Emojis static List all() => List.unmodifiable(_emojis); + static Iterable chars() => + _emojis.map((e) => e.char).whereType(); + /// Returns Emoji by [char] and character static Emoji? byChar(String char) { return _emojis.firstWhereOrNull((Emoji emoji) => emoji.char == char); diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index 3b6ba3e2..5161c8e8 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -final _emojis = Emoji.all(); +final _emojiChars = Emoji.chars(); /// String extension extension StringExtension on String { @@ -17,10 +17,10 @@ extension StringExtension on String { /// 1 to 3 emojis: big size with no text bubble. /// 4+ emojis or emojis+text: standard size with text bubble. bool get isOnlyEmoji { + if (isEmpty) return false; + if (length > 3) return false; final characters = trim().characters; - if (characters.isEmpty) return false; - if (characters.length > 3) return false; - return characters.every((c) => _emojis.map((e) => e.char).contains(c)); + return characters.every(_emojiChars.contains); } } diff --git a/packages/stream_chat_flutter/lib/src/info_tile.dart b/packages/stream_chat_flutter/lib/src/info_tile.dart index 1e935942..b420cadc 100644 --- a/packages/stream_chat_flutter/lib/src/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/info_tile.dart @@ -40,6 +40,9 @@ class InfoTile extends StatelessWidget { @override Widget build(BuildContext context) { final chatThemeData = StreamChatTheme.of(context); + if (!showMessage) { + return child; + } return PortalEntry( visible: showMessage, portalAnchor: tileAnchor ?? Alignment.topCenter, 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 1da4fcea..bdee86a0 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -147,6 +147,119 @@ class _MessageActionsModalState extends State { widget.message.attachments.any((it) => it.type == 'file') == true; final streamChatThemeData = StreamChatTheme.of(context); + + final child = Center( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: widget.reverse + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + if (widget.showReactions && + (widget.message.status == MessageSendingStatus.sent)) + Align( + alignment: Alignment( + user?.id == widget.message.user?.id + ? (divFactor >= 1.0 ? -0.2 : (1.2 - divFactor)) + : (divFactor >= 1.0 ? 0.2 : -(1.2 - divFactor)), + 0), + child: ReactionPicker( + message: widget.message, + ), + ), + const SizedBox(height: 8), + IgnorePointer( + child: MessageWidget( + key: const Key('MessageWidget'), + reverse: widget.reverse, + attachmentBorderRadiusGeometry: widget + .attachmentBorderRadiusGeometry + ?.mirrorBorderIfReversed(reverse: !widget.reverse), + message: widget.message.copyWith( + text: widget.message.text!.length > 200 + // ignore: lines_longer_than_80_chars + ? '${widget.message.text!.substring(0, 200)}...' + : widget.message.text, + ), + messageTheme: widget.messageTheme, + showReactions: false, + showUsername: false, + showReplyMessage: false, + showUserAvatar: widget.showUserAvatar, + attachmentPadding: EdgeInsets.all( + hasFileAttachment ? 4 : 2, + ), + showTimestamp: false, + translateUserAvatar: false, + padding: const EdgeInsets.all(0), + textPadding: EdgeInsets.symmetric( + vertical: 8, + horizontal: widget.message.text!.isOnlyEmoji ? 0 : 16.0, + ), + showReactionPickerIndicator: widget.showReactions && + (widget.message.status == MessageSendingStatus.sent), + showSendingIndicator: false, + shape: widget.messageShape, + attachmentShape: widget.attachmentShape, + showPinHighlight: false, + textBuilder: widget.textBuilder, + ), + ), + const SizedBox(height: 8), + Padding( + padding: EdgeInsets.only( + left: widget.reverse ? 0 : 40, + ), + child: SizedBox( + width: mediaQueryData.size.width * 0.75, + child: Material( + color: streamChatThemeData.colorTheme.whiteSnow, + clipBehavior: Clip.hardEdge, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (widget.showReplyMessage && + widget.message.status == MessageSendingStatus.sent) + _buildReplyButton(context), + if (widget.showThreadReplyMessage && + (widget.message.status == + MessageSendingStatus.sent) && + widget.message.parentId == null) + _buildThreadReplyButton(context), + if (widget.showResendMessage) + _buildResendMessage(context), + if (widget.showEditMessage) _buildEditMessage(context), + if (widget.showCopyMessage) _buildCopyButton(context), + if (widget.showFlagButton) _buildFlagButton(context), + if (widget.showPinButton) _buildPinButton(context), + if (widget.showDeleteMessage) + _buildDeleteButton(context), + ...widget.customActions + .map((action) => _buildCustomAction( + context, + action, + )) + ].insertBetween( + Container( + height: 1, + color: streamChatThemeData.colorTheme.greyWhisper, + ), + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); + return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => Navigator.maybePop(context), @@ -168,136 +281,11 @@ class _MessageActionsModalState extends State { tween: Tween(begin: 0, end: 1), duration: const Duration(milliseconds: 300), curve: Curves.easeInOutBack, - builder: (context, val, snapshot) => Transform.scale( + builder: (context, val, child) => Transform.scale( scale: val, - child: Center( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - crossAxisAlignment: widget.reverse - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - children: [ - if (widget.showReactions && - (widget.message.status == - MessageSendingStatus.sent)) - Align( - alignment: Alignment( - user?.id == widget.message.user?.id - ? (divFactor >= 1.0 - ? -0.2 - : (1.2 - divFactor)) - : (divFactor >= 1.0 - ? 0.2 - : -(1.2 - divFactor)), - 0), - child: ReactionPicker( - message: widget.message, - ), - ), - const SizedBox(height: 8), - IgnorePointer( - child: MessageWidget( - key: const Key('MessageWidget'), - reverse: widget.reverse, - attachmentBorderRadiusGeometry: widget - .attachmentBorderRadiusGeometry - ?.mirrorBorderIfReversed( - reverse: !widget.reverse), - message: widget.message.copyWith( - text: widget.message.text!.length > 200 - // ignore: lines_longer_than_80_chars - ? '${widget.message.text!.substring(0, 200)}...' - : widget.message.text, - ), - messageTheme: widget.messageTheme, - showReactions: false, - showUsername: false, - showReplyMessage: false, - showUserAvatar: widget.showUserAvatar, - attachmentPadding: EdgeInsets.all( - hasFileAttachment ? 4 : 2, - ), - showTimestamp: false, - translateUserAvatar: false, - padding: const EdgeInsets.all(0), - textPadding: EdgeInsets.symmetric( - vertical: 8, - horizontal: - widget.message.text!.isOnlyEmoji ? 0 : 16.0, - ), - showReactionPickerIndicator: - widget.showReactions && - (widget.message.status == - MessageSendingStatus.sent), - showSendingIndicator: false, - shape: widget.messageShape, - attachmentShape: widget.attachmentShape, - showPinHighlight: false, - textBuilder: widget.textBuilder, - ), - ), - const SizedBox(height: 8), - Padding( - padding: EdgeInsets.only( - left: widget.reverse ? 0 : 40, - ), - child: SizedBox( - width: mediaQueryData.size.width * 0.75, - child: Material( - color: streamChatThemeData.colorTheme.whiteSnow, - clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - if (widget.showReplyMessage && - widget.message.status == - MessageSendingStatus.sent) - _buildReplyButton(context), - if (widget.showThreadReplyMessage && - (widget.message.status == - MessageSendingStatus.sent) && - widget.message.parentId == null) - _buildThreadReplyButton(context), - if (widget.showResendMessage) - _buildResendMessage(context), - if (widget.showEditMessage) - _buildEditMessage(context), - if (widget.showCopyMessage) - _buildCopyButton(context), - if (widget.showFlagButton) - _buildFlagButton(context), - if (widget.showPinButton) - _buildPinButton(context), - if (widget.showDeleteMessage) - _buildDeleteButton(context), - ...widget.customActions - .map((action) => _buildCustomAction( - context, - action, - )) - ].insertBetween( - Container( - height: 1, - color: streamChatThemeData - .colorTheme.greyWhisper, - ), - ), - ), - ), - ), - ), - ], - ), - ), - ), - ), + child: child, ), + child: child, ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 0d2070e7..7e62f1c3 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -268,6 +268,8 @@ class MessageInputState extends State { /// The editing controller passed to the input TextField late final TextEditingController textEditingController; + late StreamChatThemeData _streamChatTheme; + bool get _hasQuotedMessage => widget.quotedMessage != null; @override @@ -305,9 +307,10 @@ class MessageInputState extends State { @override Widget build(BuildContext context) { - final streamChatThemeData = StreamChatTheme.of(context); - Widget child = Container( - color: streamChatThemeData.messageInputTheme.inputBackground, + Widget child = DecoratedBox( + decoration: BoxDecoration( + color: _streamChatTheme.messageInputTheme.inputBackground, + ), child: SafeArea( child: GestureDetector( onPanUpdate: (details) { @@ -332,7 +335,7 @@ class MessageInputState extends State { Padding( padding: const EdgeInsets.all(8), child: StreamSvgIcon.reply( - color: streamChatThemeData.colorTheme.greyGainsboro, + color: _streamChatTheme.colorTheme.greyGainsboro, ), ), const Text( @@ -390,65 +393,62 @@ class MessageInputState extends State { ], ); - Widget _buildDmCheckbox() { - final streamChatThemeData = StreamChatTheme.of(context); - return Row( - children: [ - Container( - height: 16, - width: 16, - foregroundDecoration: BoxDecoration( - border: _sendAsDm - ? null - : Border.all( - color: streamChatThemeData.colorTheme.black.withOpacity(.5), - width: 2, - ), - borderRadius: BorderRadius.circular(3), - ), - child: Center( - child: Material( + Widget _buildDmCheckbox() => Row( + children: [ + Container( + height: 16, + width: 16, + foregroundDecoration: BoxDecoration( + border: _sendAsDm + ? null + : Border.all( + color: _streamChatTheme.colorTheme.black.withOpacity(.5), + width: 2, + ), borderRadius: BorderRadius.circular(3), - color: _sendAsDm - ? streamChatThemeData.colorTheme.accentBlue - : streamChatThemeData.colorTheme.white, - child: InkWell( - onTap: () { - setState(() { - _sendAsDm = !_sendAsDm; - }); - }, - child: AnimatedCrossFade( - duration: const Duration(milliseconds: 300), - reverseDuration: const Duration(milliseconds: 300), - crossFadeState: _sendAsDm - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: StreamSvgIcon.check( - size: 16, - color: streamChatThemeData.colorTheme.white, - ), - secondChild: const SizedBox( - height: 16, - width: 16, + ), + child: Center( + child: Material( + borderRadius: BorderRadius.circular(3), + color: _sendAsDm + ? _streamChatTheme.colorTheme.accentBlue + : _streamChatTheme.colorTheme.white, + child: InkWell( + onTap: () { + setState(() { + _sendAsDm = !_sendAsDm; + }); + }, + child: AnimatedCrossFade( + duration: const Duration(milliseconds: 300), + reverseDuration: const Duration(milliseconds: 300), + crossFadeState: _sendAsDm + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: StreamSvgIcon.check( + size: 16, + color: _streamChatTheme.colorTheme.white, + ), + secondChild: const SizedBox( + height: 16, + width: 16, + ), ), ), ), ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Text( - 'Also send as direct message', - style: streamChatThemeData.textTheme.footnote.copyWith( - color: streamChatThemeData.colorTheme.black.withOpacity(0.5), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + 'Also send as direct message', + style: _streamChatTheme.textTheme.footnote.copyWith( + color: _streamChatTheme.colorTheme.black.withOpacity(0.5), + ), ), ), - ), - ], - ); - } + ], + ); Widget _animateSendButton(BuildContext context) { final sendButton = widget.activeSendButton != null @@ -463,8 +463,7 @@ class MessageInputState extends State { : CrossFadeState.showSecond, firstChild: sendButton, secondChild: widget.idleSendButton ?? _buildIdleSendButton(context), - duration: - StreamChatTheme.of(context).messageInputTheme.sendAnimationDuration!, + duration: _streamChatTheme.messageInputTheme.sendAnimationDuration!, alignment: Alignment.center, ); } @@ -478,16 +477,18 @@ class MessageInputState extends State { ? CrossFadeState.showFirst : CrossFadeState.showSecond, firstChild: IconButton( - onPressed: () => setState(() => _actionsShrunk = false), + onPressed: () { + if (_actionsShrunk) { + setState(() => _actionsShrunk = false); + } + }, icon: Transform.rotate( angle: (widget.actionsLocation == ActionsLocation.right || widget.actionsLocation == ActionsLocation.rightInside) ? pi : 0, child: StreamSvgIcon.emptyCircleLeft( - color: StreamChatTheme.of(context) - .messageInputTheme - .expandButtonColor, + color: _streamChatTheme.messageInputTheme.expandButtonColor, ), ), padding: const EdgeInsets.all(0), @@ -522,7 +523,6 @@ class MessageInputState extends State { } Expanded _buildTextInput(BuildContext context) { - final theme = StreamChatTheme.of(context); final margin = (widget.sendButtonLocation == SendButtonLocation.inside ? const EdgeInsets.only(right: 8) : EdgeInsets.zero) + @@ -530,49 +530,46 @@ class MessageInputState extends State { ? const EdgeInsets.only(left: 8) : EdgeInsets.zero); return Expanded( - child: Center( - child: Container( - clipBehavior: Clip.antiAlias, - margin: margin, - decoration: BoxDecoration( - borderRadius: theme.messageInputTheme.borderRadius, - gradient: _focusNode.hasFocus - ? theme.messageInputTheme.activeBorderGradient - : theme.messageInputTheme.idleBorderGradient, - ), - child: Padding( - padding: const EdgeInsets.all(1.5), - child: Container( - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - borderRadius: theme.messageInputTheme.borderRadius, - color: theme.messageInputTheme.inputBackground, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildReplyToMessage(), - _buildAttachments(), - LimitedBox( - maxHeight: widget.maxHeight, - child: TextField( - key: const Key('messageInputText'), - enabled: _inputEnabled, - maxLines: null, - onSubmitted: (_) => sendMessage(), - keyboardType: widget.keyboardType, - controller: textEditingController, - focusNode: _focusNode, - style: theme.messageInputTheme.inputTextStyle, - autofocus: widget.autofocus, - textAlignVertical: TextAlignVertical.center, - decoration: _getInputDecoration(), - textCapitalization: TextCapitalization.sentences, - ), - ) - ], - ), + child: Container( + clipBehavior: Clip.hardEdge, + margin: margin, + decoration: BoxDecoration( + borderRadius: _streamChatTheme.messageInputTheme.borderRadius, + gradient: _focusNode.hasFocus + ? _streamChatTheme.messageInputTheme.activeBorderGradient + : _streamChatTheme.messageInputTheme.idleBorderGradient, + ), + child: Padding( + padding: const EdgeInsets.all(1.5), + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: _streamChatTheme.messageInputTheme.borderRadius, + color: _streamChatTheme.messageInputTheme.inputBackground, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildReplyToMessage(), + _buildAttachments(), + LimitedBox( + maxHeight: widget.maxHeight, + child: TextField( + key: const Key('messageInputText'), + enabled: _inputEnabled, + maxLines: null, + onSubmitted: (_) => sendMessage(), + keyboardType: widget.keyboardType, + controller: textEditingController, + focusNode: _focusNode, + style: _streamChatTheme.messageInputTheme.inputTextStyle, + autofocus: widget.autofocus, + textAlignVertical: TextAlignVertical.center, + decoration: _getInputDecoration(), + textCapitalization: TextCapitalization.sentences, + ), + ) + ], ), ), ), @@ -581,13 +578,12 @@ class MessageInputState extends State { } InputDecoration _getInputDecoration() { - final theme = StreamChatTheme.of(context); - final passedDecoration = theme.messageInputTheme.inputDecoration; + final passedDecoration = _streamChatTheme.messageInputTheme.inputDecoration; return InputDecoration( isDense: true, hintText: _getHint(), - hintStyle: theme.messageInputTheme.inputTextStyle!.copyWith( - color: theme.colorTheme.grey, + hintStyle: _streamChatTheme.messageInputTheme.inputTextStyle!.copyWith( + color: _streamChatTheme.colorTheme.grey, ), border: const OutlineInputBorder( borderSide: BorderSide( @@ -625,7 +621,7 @@ class MessageInputState extends State { constraints: BoxConstraints.tight(const Size(64, 24)), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - color: theme.colorTheme.accentBlue, + color: _streamChatTheme.colorTheme.accentBlue, ), alignment: Alignment.center, child: Row( @@ -637,7 +633,8 @@ class MessageInputState extends State { ), Text( _chosenCommand?.name.toUpperCase() ?? '', - style: theme.textTheme.footnoteBold.copyWith( + style: + _streamChatTheme.textTheme.footnoteBold.copyWith( color: Colors.white, ), ), @@ -828,129 +825,126 @@ class MessageInputState extends State { final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; + final child = Padding( + padding: const EdgeInsets.all(8), + child: Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + color: _streamChatTheme.colorTheme.white, + clipBehavior: Clip.hardEdge, + child: Container( + constraints: BoxConstraints.loose(const Size.fromHeight(400)), + decoration: BoxDecoration( + color: _streamChatTheme.colorTheme.white, + borderRadius: BorderRadius.circular(8)), + child: ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + children: [ + if (commands.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + ), + child: StreamSvgIcon.lightning( + color: _streamChatTheme.colorTheme.accentBlue, + ), + ), + Text( + 'Instant Commands', + style: TextStyle( + color: + _streamChatTheme.colorTheme.black.withOpacity(.5), + ), + ) + ], + ), + ), + const SizedBox( + height: 10, + ), + ...commands + .map( + (c) => InkWell( + onTap: () { + _setCommand(c); + }, + child: SizedBox( + height: 40, + child: Row( + children: [ + const SizedBox( + width: 16, + ), + _buildCommandIcon(c.name), + const SizedBox( + width: 8, + ), + Text.rich( + TextSpan( + text: c.name.capitalize(), + style: const TextStyle( + fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: ' /${c.name} ${c.args}', + style: _streamChatTheme.textTheme.body + .copyWith( + // ignore: lines_longer_than_80_chars + color: _streamChatTheme + // ignore: lines_longer_than_80_chars + .colorTheme + .grey, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ) + .toList(), + ], + ), + ), + ), + ); return OverlayEntry( builder: (context) => Positioned( bottom: size.height + MediaQuery.of(context).viewInsets.bottom, left: 0, right: 0, child: TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOutExpo, - builder: (context, val, wid) { - final streamChatThemeData = StreamChatTheme.of(context); - return Transform.scale( - scale: val, - child: Padding( - padding: const EdgeInsets.all(8), - child: Card( - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - color: streamChatThemeData.colorTheme.white, - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose( - const Size.fromHeight(400)), - decoration: BoxDecoration( - color: streamChatThemeData.colorTheme.white, - borderRadius: BorderRadius.circular(8)), - child: ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - if (commands.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - ), - child: StreamSvgIcon.lightning( - color: streamChatThemeData - .colorTheme.accentBlue, - ), - ), - Text( - 'Instant Commands', - style: TextStyle( - color: streamChatThemeData - .colorTheme.black - .withOpacity(.5), - ), - ) - ], - ), - ), - const SizedBox( - height: 10, - ), - ...commands - .map( - (c) => InkWell( - onTap: () { - _setCommand(c); - }, - child: SizedBox( - height: 40, - child: Row( - children: [ - const SizedBox( - width: 16, - ), - _buildCommandIcon(c.name), - const SizedBox( - width: 8, - ), - Text.rich( - TextSpan( - text: c.name.capitalize(), - style: const TextStyle( - fontWeight: - FontWeight.bold), - children: [ - TextSpan( - text: - ' /${c.name} ${c.args}', - style: streamChatThemeData - .textTheme.body - .copyWith( - // ignore: lines_longer_than_80_chars - color: streamChatThemeData - // ignore: lines_longer_than_80_chars - .colorTheme - .grey, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ) - .toList(), - ], - ), - ), - ), - ), - ); - }), + tween: Tween(begin: 0, end: 1), + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOutExpo, + builder: (context, val, child) => Transform.scale( + scale: val, + child: child, + ), + child: child, + ), )); } Widget _buildFilePickerSection() { + if (!_openFilePickerSection) { + return const Offstage(); + } + final _attachmentContainsFile = _attachments.values.any((it) => it.type == 'file'); - final chatThemeData = StreamChatTheme.of(context); Color _getIconColor(int index) { - final streamChatThemeData = chatThemeData; + final streamChatThemeData = _streamChatTheme; switch (index) { case 0: return _attachments.isEmpty @@ -982,7 +976,7 @@ class MessageInputState extends State { _animateContainer ? const Duration(milliseconds: 300) : Duration.zero, height: _openFilePickerSection ? _filePickerSize : 0, child: Material( - color: chatThemeData.colorTheme.whiteSmoke, + color: _streamChatTheme.colorTheme.whiteSmoke, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -1044,9 +1038,9 @@ class MessageInputState extends State { ); }); }, - child: Container( + child: DecoratedBox( decoration: BoxDecoration( - color: chatThemeData.colorTheme.white, + color: _streamChatTheme.colorTheme.white, borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), @@ -1057,12 +1051,14 @@ class MessageInputState extends State { child: Center( child: Padding( padding: const EdgeInsets.all(8), - child: Container( + child: SizedBox( width: 40, height: 4, - decoration: BoxDecoration( - color: chatThemeData.colorTheme.whiteSmoke, - borderRadius: BorderRadius.circular(4), + child: DecoratedBox( + decoration: BoxDecoration( + color: _streamChatTheme.colorTheme.whiteSmoke, + borderRadius: BorderRadius.circular(4), + ), ), ), ), @@ -1072,13 +1068,14 @@ class MessageInputState extends State { ), if (_openFilePickerSection) Expanded( - child: Container( + child: DecoratedBox( decoration: BoxDecoration( - color: chatThemeData.colorTheme.white, + color: _streamChatTheme.colorTheme.white, borderRadius: BorderRadius.circular(8), ), child: _PickerWidget( filePickerIndex: _filePickerIndex, + streamChatTheme: _streamChatTheme, containsFile: _attachmentContainsFile, selectedMedias: _attachments.keys.toList(), onAddMoreFilesClick: pickFile, @@ -1150,7 +1147,6 @@ class MessageInputState extends State { } Widget _buildCommandIcon(String iconType) { - final chatThemeData = StreamChatTheme.of(context); switch (iconType) { case 'giphy': return CircleAvatar( @@ -1161,7 +1157,7 @@ class MessageInputState extends State { ); case 'ban': return CircleAvatar( - backgroundColor: chatThemeData.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.iconUserDelete( size: 16, @@ -1170,7 +1166,7 @@ class MessageInputState extends State { ); case 'flag': return CircleAvatar( - backgroundColor: chatThemeData.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.flag( size: 14, @@ -1179,7 +1175,7 @@ class MessageInputState extends State { ); case 'imgur': return CircleAvatar( - backgroundColor: chatThemeData.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentBlue, radius: 12, child: ClipOval( child: StreamSvgIcon.imgur( @@ -1189,7 +1185,7 @@ class MessageInputState extends State { ); case 'mute': return CircleAvatar( - backgroundColor: chatThemeData.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.mute( size: 16, @@ -1198,7 +1194,7 @@ class MessageInputState extends State { ); case 'unban': return CircleAvatar( - backgroundColor: chatThemeData.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.userAdd( size: 16, @@ -1207,7 +1203,7 @@ class MessageInputState extends State { ); case 'unmute': return CircleAvatar( - backgroundColor: chatThemeData.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.volumeUp( size: 16, @@ -1216,7 +1212,7 @@ class MessageInputState extends State { ); default: return CircleAvatar( - backgroundColor: chatThemeData.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.lightning( size: 16, @@ -1253,7 +1249,70 @@ class MessageInputState extends State { // ignore: cast_nullable_to_non_nullable final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; + final child = Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: _streamChatTheme.colorTheme.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.hardEdge, + child: Container( + constraints: BoxConstraints.loose(const Size.fromHeight(240)), + decoration: BoxDecoration( + color: _streamChatTheme.colorTheme.white, + ), + child: FutureBuilder>( + future: queryMembers ?? Future.value(members), + initialData: members, + builder: (context, snapshot) => ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + children: [ + const SizedBox( + height: 8, + ), + ...snapshot.data! + .where((it) => it.user != null) + .map( + (m) => Material( + color: _streamChatTheme.colorTheme.white, + child: InkWell( + onTap: () { + if (m.user != null) { + _mentionedUsers.add(m.user!); + } + splits[splits.length - 1] = m.user!.name; + final rejoin = splits.join('@'); + + textEditingController.value = TextEditingValue( + text: rejoin + + textEditingController.text.substring( + textEditingController.selection.start), + selection: TextSelection.collapsed( + offset: rejoin.length, + ), + ); + _debounce!.cancel(); + _mentionsOverlay?.remove(); + _mentionsOverlay = null; + }, + child: widget.mentionsTileBuilder != null + ? widget.mentionsTileBuilder!(context, m) + : MentionTile(m), + ), + ), + ) + .toList(), + const SizedBox( + height: 8, + ), + ], + ), + ), + ), + ); return OverlayEntry( builder: (context) => Positioned( bottom: size.height + MediaQuery.of(context).viewInsets.bottom, @@ -1263,78 +1322,11 @@ class MessageInputState extends State { tween: Tween(begin: 0, end: 1), duration: const Duration(milliseconds: 300), curve: Curves.easeInOutExpo, - builder: (context, val, wid) { - final chatThemeData = StreamChatTheme.of(context); - return Transform.scale( - scale: val, - child: Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: chatThemeData.colorTheme.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose(const Size.fromHeight(240)), - decoration: BoxDecoration( - color: chatThemeData.colorTheme.white, - ), - child: FutureBuilder>( - future: queryMembers ?? Future.value(members), - initialData: members, - builder: (context, snapshot) => ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - const SizedBox( - height: 8, - ), - ...snapshot.data! - .where((it) => it.user != null) - .map( - (m) => Material( - color: chatThemeData.colorTheme.white, - child: InkWell( - onTap: () { - if (m.user != null) { - _mentionedUsers.add(m.user!); - } - - splits[splits.length - 1] = m.user!.name; - final rejoin = splits.join('@'); - - textEditingController.value = - TextEditingValue( - text: rejoin + - textEditingController.text.substring( - textEditingController - .selection.start), - selection: TextSelection.collapsed( - offset: rejoin.length, - ), - ); - _debounce!.cancel(); - _mentionsOverlay?.remove(); - _mentionsOverlay = null; - }, - child: widget.mentionsTileBuilder != null - ? widget.mentionsTileBuilder!(context, m) - : MentionTile(m), - ), - ), - ) - .toList(), - const SizedBox( - height: 8, - ), - ], - ), - ), - ), - ), - ); - }, + builder: (context, val, child) => Transform.scale( + scale: val, + child: child, + ), + child: child, ), ), ); @@ -1363,88 +1355,88 @@ class MessageInputState extends State { final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; - return OverlayEntry(builder: (context) { - final chatThemeData = StreamChatTheme.of(context); - return Positioned( - bottom: size.height + MediaQuery.of(context).viewInsets.bottom, - left: 0, - right: 0, - child: Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: chatThemeData.colorTheme.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose(const Size.fromHeight(200)), - decoration: BoxDecoration( - boxShadow: const [ - BoxShadow( - spreadRadius: -8, - blurRadius: 5, - offset: Offset(0, -4), + return OverlayEntry( + builder: (context) => Positioned( + bottom: size.height + MediaQuery.of(context).viewInsets.bottom, + left: 0, + right: 0, + child: Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: _streamChatTheme.colorTheme.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), ), - ], - color: chatThemeData.colorTheme.white, - ), - child: ListView.builder( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - itemCount: emojis.length + 1, - itemBuilder: (context, i) { - if (i == 0) { - return Padding( - padding: const EdgeInsets.only(left: 8, top: 8), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: StreamSvgIcon.smile( - color: chatThemeData.colorTheme.accentBlue, + clipBehavior: Clip.hardEdge, + child: Container( + constraints: BoxConstraints.loose(const Size.fromHeight(200)), + decoration: BoxDecoration( + boxShadow: const [ + BoxShadow( + spreadRadius: -8, + blurRadius: 5, + offset: Offset(0, -4), + ), + ], + color: _streamChatTheme.colorTheme.white, + ), + child: ListView.builder( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + itemCount: emojis.length + 1, + itemBuilder: (context, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.only(left: 8, top: 8), + child: Row( + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8), + child: StreamSvgIcon.smile( + color: + _streamChatTheme.colorTheme.accentBlue, + ), + ), + Flexible( + child: Text( + 'Emoji matching "$query"', + style: TextStyle( + color: _streamChatTheme.colorTheme.black + .withOpacity(.5), + ), + ), + ) + ], + ), + ); + } + + final emoji = emojis.elementAt(i - 1)!; + final themeData = Theme.of(context); + return ListTile( + title: SubstringHighlight( + text: + // ignore: lines_longer_than_80_chars + "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", + term: query, + textStyleHighlight: + themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + fontWeight: FontWeight.bold, + ), + textStyle: themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, ), ), - Flexible( - child: Text( - 'Emoji matching "$query"', - style: TextStyle( - color: chatThemeData.colorTheme.black - .withOpacity(.5), - ), - ), - ) - ], - ), - ); - } - - final emoji = emojis.elementAt(i - 1)!; - final themeData = Theme.of(context); - return ListTile( - title: SubstringHighlight( - text: - // ignore: lines_longer_than_80_chars - "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", - term: query, - textStyleHighlight: - themeData.textTheme.headline6!.copyWith( - fontSize: 14.5, - fontWeight: FontWeight.bold, - ), - textStyle: themeData.textTheme.headline6!.copyWith( - fontSize: 14.5, - ), - ), - onTap: () { - _chooseEmoji(splits, emoji); - }, - ); - }), - ), - ), - ); - }); + onTap: () { + _chooseEmoji(splits, emoji); + }, + ); + }), + ), + ), + )); } void _chooseEmoji(List splits, Emoji emoji) { @@ -1483,7 +1475,7 @@ class MessageInputState extends State { reverse: true, showBorder: !containsUrl, message: widget.quotedMessage!, - messageTheme: StreamChatTheme.of(context).otherMessageTheme, + messageTheme: _streamChatTheme.otherMessageTheme, padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), ); } @@ -1568,32 +1560,29 @@ class MessageInputState extends State { ); } - Widget _buildRemoveButton(Attachment attachment) { - final chatThemeData = StreamChatTheme.of(context); - return SizedBox( - height: 24, - width: 24, - child: RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - hoverElevation: 0, - onPressed: () { - setState(() => _attachments.remove(attachment.id)); - }, - fillColor: chatThemeData.colorTheme.black.withOpacity(.5), - child: Center( - child: StreamSvgIcon.close( - size: 24, - color: chatThemeData.colorTheme.white, + Widget _buildRemoveButton(Attachment attachment) => SizedBox( + height: 24, + width: 24, + child: RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + hoverElevation: 0, + onPressed: () { + setState(() => _attachments.remove(attachment.id)); + }, + fillColor: _streamChatTheme.colorTheme.black.withOpacity(.5), + child: Center( + child: StreamSvgIcon.close( + size: 24, + color: _streamChatTheme.colorTheme.white, + ), ), ), - ), - ); - } + ); Widget _buildAttachment(Attachment attachment) { if (widget.attachmentThumbnailBuilders?.containsKey(attachment.type) == @@ -1623,18 +1612,15 @@ class MessageInputState extends State { fit: BoxFit.cover, errorWidget: (_, obj, trace) => getFileTypeImage(attachment.extraData['other'] as String?), - progressIndicatorBuilder: (context, _, progress) { - final chatThemeData = StreamChatTheme.of(context); - return Shimmer.fromColors( - baseColor: chatThemeData.colorTheme.greyGainsboro, - highlightColor: chatThemeData.colorTheme.whiteSmoke, - child: Image.asset( - 'images/placeholder.png', - fit: BoxFit.cover, - package: 'stream_chat_flutter', - ), - ); - }, + placeholder: (context, _) => Shimmer.fromColors( + baseColor: _streamChatTheme.colorTheme.greyGainsboro, + highlightColor: _streamChatTheme.colorTheme.whiteSmoke, + child: Image.asset( + 'images/placeholder.png', + fit: BoxFit.cover, + package: 'stream_chat_flutter', + ), + ), ); case 'video': return Stack( @@ -1666,14 +1652,13 @@ class MessageInputState extends State { Widget _buildCommandButton() { final s = textEditingController.text.trim(); - final chatThemeData = StreamChatTheme.of(context); return IconButton( icon: StreamSvgIcon.lightning( color: s.isNotEmpty - ? chatThemeData.colorTheme.greyGainsboro + ? _streamChatTheme.colorTheme.greyGainsboro : (_commandsOverlay != null - ? chatThemeData.messageInputTheme.actionButtonColor - : chatThemeData.messageInputTheme.actionButtonIdleColor), + ? _streamChatTheme.messageInputTheme.actionButtonColor + : _streamChatTheme.messageInputTheme.actionButtonIdleColor), ), padding: const EdgeInsets.all(0), constraints: const BoxConstraints.tightFor( @@ -1708,40 +1693,37 @@ class MessageInputState extends State { ); } - Widget _buildAttachmentButton() { - final chatThemeData = StreamChatTheme.of(context); - return IconButton( - icon: StreamSvgIcon.attach( - color: _openFilePickerSection - ? chatThemeData.messageInputTheme.actionButtonColor - : chatThemeData.messageInputTheme.actionButtonIdleColor, - ), - padding: const EdgeInsets.all(0), - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), - splashRadius: 24, - onPressed: () async { - _emojiOverlay?.remove(); - _emojiOverlay = null; - _commandsOverlay?.remove(); - _commandsOverlay = null; - _mentionsOverlay?.remove(); - _mentionsOverlay = null; + Widget _buildAttachmentButton() => IconButton( + icon: StreamSvgIcon.attach( + color: _openFilePickerSection + ? _streamChatTheme.messageInputTheme.actionButtonColor + : _streamChatTheme.messageInputTheme.actionButtonIdleColor, + ), + padding: const EdgeInsets.all(0), + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, + onPressed: () async { + _emojiOverlay?.remove(); + _emojiOverlay = null; + _commandsOverlay?.remove(); + _commandsOverlay = null; + _mentionsOverlay?.remove(); + _mentionsOverlay = null; - if (_openFilePickerSection) { - setState(() { - _animateContainer = true; - _openFilePickerSection = false; - _filePickerSize = _kMinMediaPickerSize; - }); - } else { - showAttachmentModal(); - } - }, - ); - } + if (_openFilePickerSection) { + setState(() { + _animateContainer = true; + _openFilePickerSection = false; + _filePickerSize = _kMinMediaPickerSize; + }); + } else { + showAttachmentModal(); + } + }, + ); /// Show the attachment modal, making the user choose where to /// pick a media from @@ -1948,8 +1930,7 @@ class MessageInputState extends State { padding: const EdgeInsets.all(8), child: StreamSvgIcon( assetName: _getIdleSendIcon(), - color: - StreamChatTheme.of(context).messageInputTheme.sendButtonIdleColor, + color: _streamChatTheme.messageInputTheme.sendButtonIdleColor, ), ); @@ -1965,8 +1946,7 @@ class MessageInputState extends State { ), icon: StreamSvgIcon( assetName: _getSendIcon(), - color: - StreamChatTheme.of(context).messageInputTheme.sendButtonColor, + color: _streamChatTheme.messageInputTheme.sendButtonColor, ), ), ); @@ -2083,9 +2063,8 @@ class MessageInputState extends State { StreamSubscription? _keyboardListener; void _showErrorAlert(String description) { - final chatThemeData = StreamChatTheme.of(context); showModalBottomSheet( - backgroundColor: chatThemeData.colorTheme.white, + backgroundColor: _streamChatTheme.colorTheme.white, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -2099,7 +2078,7 @@ class MessageInputState extends State { height: 26, ), StreamSvgIcon.error( - color: chatThemeData.colorTheme.accentRed, + color: _streamChatTheme.colorTheme.accentRed, size: 24, ), const SizedBox( @@ -2107,7 +2086,7 @@ class MessageInputState extends State { ), Text( 'Something went wrong', - style: chatThemeData.textTheme.headlineBold, + style: _streamChatTheme.textTheme.headlineBold, ), const SizedBox( height: 7, @@ -2123,7 +2102,7 @@ class MessageInputState extends State { height: 36, ), Container( - color: chatThemeData.colorTheme.black.withOpacity(.08), + color: _streamChatTheme.colorTheme.black.withOpacity(.08), height: 1, ), Row( @@ -2135,8 +2114,8 @@ class MessageInputState extends State { }, child: Text( 'OK', - style: chatThemeData.textTheme.bodyBold - .copyWith(color: chatThemeData.colorTheme.accentBlue), + style: _streamChatTheme.textTheme.bodyBold + .copyWith(color: _streamChatTheme.colorTheme.accentBlue), ), ), ], @@ -2169,6 +2148,7 @@ class MessageInputState extends State { @override void didChangeDependencies() { + _streamChatTheme = StreamChatTheme.of(context); if (widget.editMessage != null && !_initialized) { FocusScope.of(context).requestFocus(_focusNode); _initialized = true; @@ -2233,6 +2213,7 @@ class _PickerWidget extends StatefulWidget { required this.selectedMedias, required this.onAddMoreFilesClick, required this.onMediaSelected, + required this.streamChatTheme, }) : super(key: key); final int filePickerIndex; @@ -2240,6 +2221,7 @@ class _PickerWidget extends StatefulWidget { final List selectedMedias; final void Function(DefaultAttachmentTypes) onAddMoreFilesClick; final void Function(AssetEntity) onMediaSelected; + final StreamChatThemeData streamChatTheme; @override __PickerWidgetState createState() => __PickerWidgetState(); @@ -2266,7 +2248,6 @@ class __PickerWidgetState extends State<_PickerWidget> { return const Center(child: CircularProgressIndicator()); } - final chatThemeData = StreamChatTheme.of(context); if (snapshot.data!) { if (widget.containsFile) { return GestureDetector( @@ -2275,12 +2256,12 @@ class __PickerWidgetState extends State<_PickerWidget> { }, child: Container( constraints: const BoxConstraints.expand(), - color: chatThemeData.colorTheme.whiteSmoke, + color: widget.streamChatTheme.colorTheme.whiteSmoke, alignment: Alignment.center, child: Text( 'Add more files', style: TextStyle( - color: chatThemeData.colorTheme.accentBlue, + color: widget.streamChatTheme.colorTheme.accentBlue, fontWeight: FontWeight.bold, ), ), @@ -2298,7 +2279,7 @@ class __PickerWidgetState extends State<_PickerWidget> { PhotoManager.openSetting(); }, child: Container( - color: chatThemeData.colorTheme.whiteSmoke, + color: widget.streamChatTheme.colorTheme.whiteSmoke, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -2307,21 +2288,21 @@ class __PickerWidgetState extends State<_PickerWidget> { 'svgs/icon_picture_empty_state.svg', package: 'stream_chat_flutter', height: 140, - color: chatThemeData.colorTheme.greyGainsboro, + color: widget.streamChatTheme.colorTheme.greyGainsboro, ), Text( // ignore: lines_longer_than_80_chars 'Please enable access to your photos \nand videos so you can share them with friends.', - style: chatThemeData.textTheme.body - .copyWith(color: chatThemeData.colorTheme.grey), + style: widget.streamChatTheme.textTheme.body.copyWith( + color: widget.streamChatTheme.colorTheme.grey), textAlign: TextAlign.center, ), const SizedBox(height: 6), Center( child: Text( 'Allow access to your gallery', - style: chatThemeData.textTheme.bodyBold.copyWith( - color: chatThemeData.colorTheme.accentBlue, + style: widget.streamChatTheme.textTheme.bodyBold.copyWith( + color: widget.streamChatTheme.colorTheme.accentBlue, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index cb1f66b7..468f2d9f 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:rxdart/rxdart.dart'; @@ -281,8 +282,10 @@ class _MessageListViewState extends State { void Function(Message)? _onThreadTap; bool _showScrollToBottom = false; late final ItemPositionsListener _itemPositionListener; + late final Stream> _itemPositionStream; int? _messageListLength; StreamChannelState? streamChannel; + late StreamChatThemeData _streamTheme; int? get _initialIndex { if (widget.initialScrollIndex != null) return widget.initialScrollIndex; @@ -324,37 +327,34 @@ class _MessageListViewState extends State { final MessageListController _messageListController = MessageListController(); @override - Widget build(BuildContext context) { - final chatThemeData = StreamChatTheme.of(context); - return MessageListCore( - messageFilter: widget.messageFilter, - loadingBuilder: widget.loadingBuilder ?? - (context) => const Center( - child: CircularProgressIndicator(), - ), - emptyBuilder: widget.emptyBuilder ?? - (context) => Center( - child: Text( - 'No chats here yet...', - style: chatThemeData.textTheme.footnote.copyWith( - color: chatThemeData.colorTheme.black.withOpacity(.5)), + Widget build(BuildContext context) => MessageListCore( + messageFilter: widget.messageFilter, + loadingBuilder: widget.loadingBuilder ?? + (context) => const Center( + child: CircularProgressIndicator(), ), - ), - messageListBuilder: - widget.messageListBuilder ?? (context, list) => _buildListView(list), - messageListController: _messageListController, - parentMessage: widget.parentMessage, - showScrollToBottom: widget.showScrollToBottom, - errorWidgetBuilder: widget.errorWidgetBuilder ?? - (BuildContext context, Object error) => Center( - child: Text( - 'Something went wrong', - style: chatThemeData.textTheme.footnote.copyWith( - color: chatThemeData.colorTheme.black.withOpacity(.5)), + emptyBuilder: widget.emptyBuilder ?? + (context) => Center( + child: Text( + 'No chats here yet...', + style: _streamTheme.textTheme.footnote.copyWith( + color: _streamTheme.colorTheme.black.withOpacity(.5)), + ), ), - ), - ); - } + messageListBuilder: widget.messageListBuilder ?? + (context, list) => _buildListView(list), + messageListController: _messageListController, + parentMessage: widget.parentMessage, + showScrollToBottom: widget.showScrollToBottom, + errorWidgetBuilder: widget.errorWidgetBuilder ?? + (BuildContext context, Object error) => Center( + child: Text( + 'Something went wrong', + style: _streamTheme.textTheme.footnote.copyWith( + color: _streamTheme.colorTheme.black.withOpacity(.5)), + ), + ), + ); Widget _buildListView(List data) { messages = data; @@ -400,8 +400,7 @@ class _MessageListViewState extends State { } return InfoTile( - // ignore: avoid_bool_literals_in_conditional_expressions - showMessage: widget.showConnectionStateTile ? showStatus : false, + showMessage: widget.showConnectionStateTile && showStatus, tileAnchor: Alignment.topCenter, childAnchor: Alignment.topCenter, message: statusString, @@ -448,10 +447,9 @@ class _MessageListViewState extends State { if (i == 0) return const SizedBox(height: 30); if (i == messages.length + 1) { final replyCount = widget.parentMessage!.replyCount; - final chatThemeData = StreamChatTheme.of(context); return Container( decoration: BoxDecoration( - gradient: chatThemeData.colorTheme.bgGradient, + gradient: _streamTheme.colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.all(8), @@ -459,7 +457,7 @@ class _MessageListViewState extends State { // ignore: lines_longer_than_80_chars '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', textAlign: TextAlign.center, - style: chatThemeData + style: _streamTheme .channelTheme.channelHeaderTheme.subtitle, ), ), @@ -571,9 +569,18 @@ class _MessageListViewState extends State { if (widget.showScrollToBottom) _buildScrollToBottom(), Positioned( top: 20, - child: ValueListenableBuilder>( - valueListenable: _itemPositionListener.itemPositions, - builder: (context, values, _) { + child: BetterStreamBuilder>( + initialData: _itemPositionListener.itemPositions.value, + stream: _itemPositionStream, + comparator: (a, b) { + if (a == null) { + return false; + } + final aTop = _getTopElement(a)?.index; + final bTop = _getTopElement(b)?.index; + return aTop == bTop; + }, + builder: (context, values) { final items = _itemPositionListener.itemPositions.value; if (items.isEmpty || messages.isEmpty) { return const SizedBox(); @@ -640,7 +647,6 @@ class _MessageListViewState extends State { final showUnreadCount = unreadCount > 0 && streamChannel!.channel.state!.members.any((e) => e.userId == streamChannel!.channel.client.state.user!.id); - final chatThemeData = StreamChatTheme.of(context); return Positioned( bottom: 8, right: 8, @@ -650,7 +656,7 @@ class _MessageListViewState extends State { clipBehavior: Clip.none, children: [ FloatingActionButton( - backgroundColor: chatThemeData.colorTheme.white, + backgroundColor: _streamTheme.colorTheme.white, onPressed: () { if (unreadCount > 0) { streamChannel!.channel.markRead(); @@ -669,7 +675,7 @@ class _MessageListViewState extends State { } }, child: StreamSvgIcon.down( - color: chatThemeData.colorTheme.black, + color: _streamTheme.colorTheme.black, ), ), if (showUnreadCount) @@ -700,39 +706,13 @@ class _MessageListViewState extends State { Widget _buildLoadingIndicator( StreamChannelState streamChannel, QueryDirection direction, - ) { - final stream = direction == QueryDirection.top - ? streamChannel.queryTopMessages - : streamChannel.queryBottomMessages; - return BetterStreamBuilder( - key: Key('LOADING-INDICATOR $direction'), - stream: stream, - initialData: false, - errorBuilder: (context, error) => Container( - color: StreamChatTheme.of(context).colorTheme.accentRed.withOpacity(.2), - child: const Center( - child: Text('Error loading messages'), - ), - ), - builder: (context, snapshot) { - if (!snapshot) { - if (!_isThreadConversation && direction == QueryDirection.top) { - return const SizedBox( - height: 52, - width: double.infinity, - ); - } - return const Offstage(); - } - return const Center( - child: Padding( - padding: EdgeInsets.all(8), - child: CircularProgressIndicator(), - ), - ); - }, - ); - } + ) => + _LoadingIndicator( + direction: direction, + streamTheme: _streamTheme, + streamChannel: streamChannel, + isThreadConversation: _isThreadConversation, + ); Widget _buildTopMessage( BuildContext context, @@ -818,7 +798,6 @@ class _MessageListViewState extends State { final currentUserMember = members.firstWhere((e) => e.user!.id == currentUser!.id); - final chatThemeData = StreamChatTheme.of(context); return MessageWidget( showReplyMessage: false, showResendMessage: false, @@ -847,8 +826,8 @@ class _MessageListViewState extends State { borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null, showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, messageTheme: isMyMessage - ? chatThemeData.ownMessageTheme - : chatThemeData.otherMessageTheme, + ? _streamTheme.ownMessageTheme + : _streamTheme.otherMessageTheme, onShowMessage: widget.onShowMessage, onReturnAction: (action) { switch (action) { @@ -962,7 +941,6 @@ class _MessageListViewState extends State { final currentUserMember = members.firstWhere((e) => e.user!.id == currentUser!.id); - final chatThemeData = StreamChatTheme.of(context); Widget child = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), message: message, @@ -1049,8 +1027,8 @@ class _MessageListViewState extends State { horizontal: isOnlyEmoji ? 0 : 16.0, ), messageTheme: isMyMessage - ? chatThemeData.ownMessageTheme - : chatThemeData.otherMessageTheme, + ? _streamTheme.ownMessageTheme + : _streamTheme.otherMessageTheme, readList: readList, allRead: allRead, onShowMessage: widget.onShowMessage, @@ -1091,7 +1069,7 @@ class _MessageListViewState extends State { widget.onMessageSwiped?.call(message); }, backgroundIcon: StreamSvgIcon.reply( - color: chatThemeData.colorTheme.accentBlue, + color: _streamTheme.colorTheme.accentBlue, ), child: child, ), @@ -1101,7 +1079,7 @@ class _MessageListViewState extends State { if (!initialMessageHighlightComplete && widget.highlightInitialMessage && _isInitialMessage(message.id)) { - final colorTheme = chatThemeData.colorTheme; + final colorTheme = _streamTheme.colorTheme; final highlightColor = widget.messageHighlightColor ?? colorTheme.highlight; child = TweenAnimationBuilder( @@ -1131,6 +1109,8 @@ class _MessageListViewState extends State { _scrollController = widget.scrollController ?? ItemScrollController(); _itemPositionListener = widget.itemPositionListener ?? ItemPositionsListener.create(); + _itemPositionStream = + valueListenableToStreamAdapter(_itemPositionListener.itemPositions); _getOnThreadTap(); super.initState(); @@ -1139,6 +1119,7 @@ class _MessageListViewState extends State { @override void didChangeDependencies() { final newStreamChannel = StreamChannel.of(context); + _streamTheme = StreamChatTheme.of(context); if (newStreamChannel != streamChannel) { streamChannel = newStreamChannel; @@ -1209,3 +1190,78 @@ class _MessageListViewState extends State { super.dispose(); } } + +class _LoadingIndicator extends StatelessWidget { + const _LoadingIndicator({ + Key? key, + required this.streamTheme, + required this.isThreadConversation, + required this.direction, + required this.streamChannel, + }) : super(key: key); + + final StreamChatThemeData streamTheme; + final bool isThreadConversation; + final QueryDirection direction; + final StreamChannelState streamChannel; + + @override + Widget build(BuildContext context) { + final stream = direction == QueryDirection.top + ? streamChannel.queryTopMessages + : streamChannel.queryBottomMessages; + return BetterStreamBuilder( + key: Key('LOADING-INDICATOR $direction'), + stream: stream, + initialData: false, + errorBuilder: (context, error) => Container( + color: streamTheme.colorTheme.accentRed.withOpacity(.2), + child: const Center( + child: Text('Error loading messages'), + ), + ), + builder: (context, snapshot) { + if (!snapshot) { + if (!isThreadConversation && direction == QueryDirection.top) { + return const SizedBox( + height: 52, + width: double.infinity, + ); + } + return const Offstage(); + } + return const Center( + child: Padding( + padding: EdgeInsets.all(8), + child: CircularProgressIndicator(), + ), + ); + }, + ); + } +} + +Stream valueListenableToStreamAdapter(ValueListenable listenable) { + late StreamController controller; + + void listener() { + controller.add(listenable.value); + } + + void start() { + listenable.addListener(listener); + } + + void end() { + listenable.removeListener(listener); + } + + controller = StreamController( + onListen: start, + onPause: end, + onResume: start, + onCancel: end, + ); + + return controller.stream; +} 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 9d78e3d9..e9369e99 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -77,113 +77,111 @@ class MessageReactionsModal extends StatelessWidget { final divFactor = message.attachments.isNotEmpty == true ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); + final hasFileAttachment = + message.attachments.any((it) => it.type == 'file') == true; - return TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOutBack, - builder: (context, val, snapshot) { - final hasFileAttachment = - message.attachments.any((it) => it.type == 'file') == true; - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () => Navigator.maybePop(context), - child: Stack( - children: [ - Positioned.fill( - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: 10, - sigmaY: 10, - ), - child: Container( - color: StreamChatTheme.of(context).colorTheme.overlay, + final child = Center( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (showReactions && + (message.status == MessageSendingStatus.sent)) + Align( + alignment: Alignment( + user!.id == message.user!.id + ? (divFactor >= 1.0 ? -0.2 : (1.2 - divFactor)) + : (divFactor >= 1.0 ? 0.2 : -(1.2 - divFactor)), + 0), + child: ReactionPicker( + message: message, ), ), - ), - Transform.scale( - scale: val, - child: Center( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (showReactions && - (message.status == MessageSendingStatus.sent)) - Align( - alignment: Alignment( - user!.id == message.user!.id - ? (divFactor >= 1.0 - ? -0.2 - : (1.2 - divFactor)) - : (divFactor >= 1.0 - ? 0.2 - : -(1.2 - divFactor)), - 0), - child: ReactionPicker( - message: message, - ), - ), - const SizedBox(height: 8), - IgnorePointer( - child: MessageWidget( - key: const Key('MessageWidget'), - reverse: reverse, - message: message.copyWith( - text: message.text!.length > 200 - ? '${message.text!.substring(0, 200)}...' - : message.text, - ), - messageTheme: messageTheme, - showReactions: false, - showUsername: false, - showUserAvatar: showUserAvatar, - showTimestamp: false, - translateUserAvatar: false, - showSendingIndicator: false, - shape: messageShape, - attachmentShape: attachmentShape, - padding: const EdgeInsets.all(0), - attachmentBorderRadiusGeometry: - attachmentBorderRadiusGeometry - ?.mirrorBorderIfReversed( - reverse: !reverse), - attachmentPadding: EdgeInsets.all( - hasFileAttachment ? 4 : 2, - ), - textPadding: EdgeInsets.symmetric( - vertical: 8, - horizontal: - message.text!.isOnlyEmoji ? 0 : 16.0, - ), - showReactionPickerIndicator: showReactions && - (message.status == MessageSendingStatus.sent), - textBuilder: textBuilder, - showPinHighlight: false, - ), - ), - if (message.latestReactions?.isNotEmpty == true) ...[ - const SizedBox(height: 8), - _buildReactionCard(context), - ] - ], - ), - ), + const SizedBox(height: 8), + IgnorePointer( + child: MessageWidget( + key: const Key('MessageWidget'), + reverse: reverse, + message: message.copyWith( + text: message.text!.length > 200 + ? '${message.text!.substring(0, 200)}...' + : message.text, ), + messageTheme: messageTheme, + showReactions: false, + showUsername: false, + showUserAvatar: showUserAvatar, + showTimestamp: false, + translateUserAvatar: false, + showSendingIndicator: false, + shape: messageShape, + attachmentShape: attachmentShape, + padding: const EdgeInsets.all(0), + attachmentBorderRadiusGeometry: attachmentBorderRadiusGeometry + ?.mirrorBorderIfReversed(reverse: !reverse), + attachmentPadding: EdgeInsets.all( + hasFileAttachment ? 4 : 2, + ), + textPadding: EdgeInsets.symmetric( + vertical: 8, + horizontal: message.text!.isOnlyEmoji ? 0 : 16.0, + ), + showReactionPickerIndicator: showReactions && + (message.status == MessageSendingStatus.sent), + textBuilder: textBuilder, + showPinHighlight: false, ), ), + if (message.latestReactions?.isNotEmpty == true) ...[ + const SizedBox(height: 8), + _buildReactionCard( + context, + user, + ), + ] ], ), - ); - }, + ), + ), + ); + + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => Navigator.maybePop(context), + child: Stack( + children: [ + Positioned.fill( + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 10, + sigmaY: 10, + ), + child: DecoratedBox( + decoration: BoxDecoration( + color: StreamChatTheme.of(context).colorTheme.overlay, + ), + ), + ), + ), + TweenAnimationBuilder( + tween: Tween(begin: 0, end: 1), + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOutBack, + builder: (context, val, widget) => Transform.scale( + scale: val, + child: widget, + ), + child: child, + ), + ], + ), ); } - Widget _buildReactionCard(BuildContext context) { - final currentUser = StreamChat.of(context).user; + Widget _buildReactionCard(BuildContext context, User? user) { final chatThemeData = StreamChatTheme.of(context); return Card( color: chatThemeData.colorTheme.white, @@ -210,7 +208,7 @@ class MessageReactionsModal extends StatelessWidget { children: message.latestReactions! .map((e) => _buildReaction( e, - currentUser!, + user!, context, )) .toList(), diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index f1b5428d..7fb092d6 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -413,37 +413,39 @@ class _MessageWidgetState extends State bool get showTimeStamp => widget.showTimestamp; - bool get isMessageRead => widget.readList?.isNotEmpty == true; + late final bool isMessageRead = widget.readList?.isNotEmpty == true; bool get showInChannel => widget.showInChannelIndicator; bool get hasQuotedMessage => widget.message.quotedMessage != null; - bool get isSendFailed => widget.message.status == MessageSendingStatus.failed; + late final bool isSendFailed = + widget.message.status == MessageSendingStatus.failed; - bool get isUpdateFailed => + late final bool isUpdateFailed = widget.message.status == MessageSendingStatus.failed_update; - bool get isDeleteFailed => + late final bool isDeleteFailed = widget.message.status == MessageSendingStatus.failed_delete; - bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed; + late final bool isFailedState = + isSendFailed || isUpdateFailed || isDeleteFailed; - bool get isGiphy => + late final bool isGiphy = widget.message.attachments.any((element) => element.type == 'giphy') == - true; + true; - bool get hasNonUrlAttachments => - widget.message.attachments + late final bool isOnlyEmoji = widget.message.text?.isOnlyEmoji == true; + + late final bool hasNonUrlAttachments = widget.message.attachments .where((it) => it.ogScrapeUrl == null) .isNotEmpty == true; - bool get hasUrlAttachments => + late final bool hasUrlAttachments = widget.message.attachments.any((it) => it.ogScrapeUrl != null) == true; - bool get showBottomRow => - showThreadReplyIndicator || + late final bool showBottomRow = showThreadReplyIndicator || showUsername || showTimeStamp || showInChannel || @@ -453,6 +455,9 @@ class _MessageWidgetState extends State @override bool get wantKeepAlive => widget.message.attachments.isNotEmpty == true; + late StreamChatThemeData _streamChatTheme; + late StreamChatState _streamChat; + @override Widget build(BuildContext context) { super.build(context); @@ -466,7 +471,7 @@ class _MessageWidgetState extends State ? MaterialType.card : MaterialType.transparency, color: widget.message.pinned && widget.showPinHighlight - ? StreamChatTheme.of(context).colorTheme.highlight + ? _streamChatTheme.colorTheme.highlight : null, child: Portal( child: InkWell( @@ -527,7 +532,8 @@ class _MessageWidgetState extends State transform: Matrix4.translationValues( widget.reverse ? 12 : -12, 0, 0), constraints: const BoxConstraints( - maxWidth: 22 * 6.0), + maxWidth: 22 * 6.0, + ), child: _buildReactionIndicator(context), ), portalAnchor: @@ -572,7 +578,7 @@ class _MessageWidgetState extends State ), ) : Card( - clipBehavior: Clip.antiAlias, + clipBehavior: Clip.hardEdge, elevation: 0, margin: EdgeInsets.symmetric( horizontal: (isFailedState @@ -626,9 +632,8 @@ class _MessageWidgetState extends State top: -8, child: CustomPaint( painter: ReactionBubblePainter( - StreamChatTheme.of(context) - .colorTheme - .white, + _streamChatTheme + .colorTheme.white, Colors.transparent, Colors.transparent, tailCirclesSpace: 1, @@ -673,14 +678,20 @@ class _MessageWidgetState extends State ); } + @override + void didChangeDependencies() { + _streamChatTheme = StreamChatTheme.of(context); + _streamChat = StreamChat.of(context); + super.didChangeDependencies(); + } + Widget _buildQuotedMessage() { - final isMyMessage = - widget.message.user?.id == StreamChat.of(context).user?.id; + final isMyMessage = widget.message.user?.id == _streamChat.user?.id; final onTap = widget.message.quotedMessage?.isDeleted != true && widget.onQuotedMessageTap != null ? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId) : null; - final chatThemeData = StreamChatTheme.of(context); + final chatThemeData = _streamChatTheme; return QuotedMessageWidget( onTap: onTap, message: widget.message.quotedMessage!, @@ -695,7 +706,7 @@ class _MessageWidgetState extends State Widget get _bottomRow { if (isDeleted) { - final chatThemeData = StreamChatTheme.of(context); + final chatThemeData = _streamChatTheme; return Row( mainAxisSize: MainAxisSize.min, children: [ @@ -849,36 +860,16 @@ class _MessageWidgetState extends State ); } - Widget _buildThreadParticipantsIndicator(Iterable threadParticipants) { - var padding = 0.0; - return Stack( - children: threadParticipants.map((user) { - padding += 8.0; - return Positioned( - right: padding - 8, - bottom: 0, - top: 0, - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: StreamChatTheme.of(context).colorTheme.white, - ), - padding: const EdgeInsets.all(1), - child: UserAvatar( - user: user, - constraints: BoxConstraints.loose(const Size.fromRadius(7)), - showOnlineStatus: false, - ), - ), - ); - }).toList(), - ); - } + Widget _buildThreadParticipantsIndicator(Iterable threadParticipants) => + _ThreadParticipants( + streamChatTheme: _streamChatTheme, + threadParticipants: threadParticipants, + ); Widget _buildReactionIndicator( BuildContext context, ) { - final ownId = StreamChat.of(context).user!.id; + final ownId = _streamChat.user!.id; final reactionsMap = {}; widget.message.latestReactions?.forEach((element) { if (!reactionsMap.containsKey(element.type) || @@ -918,7 +909,7 @@ class _MessageWidgetState extends State showDialog( context: context, - barrierColor: StreamChatTheme.of(context).colorTheme.overlay, + barrierColor: _streamChatTheme.colorTheme.overlay, builder: (context) => StreamChannel( channel: channel, child: MessageActionsModal( @@ -969,7 +960,7 @@ class _MessageWidgetState extends State final channel = StreamChannel.of(context).channel; showDialog( context: context, - barrierColor: StreamChatTheme.of(context).colorTheme.overlay, + barrierColor: _streamChatTheme.colorTheme.overlay, builder: (context) => StreamChannel( channel: channel, child: MessageReactionsModal( @@ -1000,7 +991,7 @@ class _MessageWidgetState extends State side: hasFiles ? widget.attachmentBorderSide ?? BorderSide( - color: StreamChatTheme.of(context).colorTheme.greyWhisper, + color: _streamChatTheme.colorTheme.greyWhisper, ) : BorderSide.none, borderRadius: widget.attachmentBorderRadiusGeometry ?? BorderRadius.zero, @@ -1010,7 +1001,7 @@ class _MessageWidgetState extends State ShapeBorder _getDefaultShape(BuildContext context) => RoundedRectangleBorder( side: widget.borderSide ?? BorderSide( - color: StreamChatTheme.of(context).colorTheme.greyWhisper, + color: _streamChatTheme.colorTheme.greyWhisper, ), borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero, ); @@ -1101,7 +1092,7 @@ class _MessageWidgetState extends State Text( widget.readList!.length.toString(), style: style.copyWith( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: _streamChatTheme.colorTheme.accentBlue, ), ), const SizedBox(width: 2), @@ -1158,7 +1149,7 @@ class _MessageWidgetState extends State Widget _buildPinnedMessage(Message message) { final pinnedBy = message.pinnedBy; - final pinnedByMe = StreamChat.of(context).user!.id == pinnedBy!.id; + final pinnedByMe = _streamChat.user!.id == pinnedBy!.id; return Padding( padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8), @@ -1174,7 +1165,7 @@ class _MessageWidgetState extends State Text( 'Pinned by ${pinnedByMe ? 'You' : pinnedBy.name}', style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.grey, + color: _streamChatTheme.colorTheme.grey, fontSize: 13, fontWeight: FontWeight.w400, ), @@ -1184,9 +1175,7 @@ class _MessageWidgetState extends State ); } - bool get isOnlyEmoji => widget.message.text!.isOnlyEmoji; - - bool get isPinned => widget.message.pinned; + late final bool isPinned = widget.message.pinned; Color? _getBackgroundColor() { if (hasQuotedMessage) { @@ -1194,7 +1183,7 @@ class _MessageWidgetState extends State } if (hasUrlAttachments) { - return StreamChatTheme.of(context).colorTheme.blueAlice; + return _streamChatTheme.colorTheme.blueAlice; } if (isOnlyEmoji) { @@ -1226,6 +1215,45 @@ class _MessageWidgetState extends State } } +class _ThreadParticipants extends StatelessWidget { + const _ThreadParticipants({ + Key? key, + required StreamChatThemeData streamChatTheme, + required this.threadParticipants, + }) : _streamChatTheme = streamChatTheme, + super(key: key); + + final StreamChatThemeData _streamChatTheme; + final Iterable threadParticipants; + + @override + Widget build(BuildContext context) { + var padding = 0.0; + return Stack( + children: threadParticipants.map((user) { + padding += 8.0; + return Positioned( + right: padding - 8, + bottom: 0, + top: 0, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _streamChatTheme.colorTheme.white, + ), + padding: const EdgeInsets.all(1), + child: UserAvatar( + user: user, + constraints: BoxConstraints.loose(const Size.fromRadius(7)), + showOnlineStatus: false, + ), + ), + ); + }).toList(), + ); + } +} + class _ThreadReplyPainter extends CustomPainter { const _ThreadReplyPainter({ this.context, 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 07a820b9..43620640 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -1,9 +1,9 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:video_player/video_player.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Widget builder for quoted message attachment thumnail typedef QuotedMessageAttachmentThumbnailBuilder = Widget Function( @@ -217,7 +217,7 @@ class QuotedMessageWidget extends StatelessWidget { } child = AbsorbPointer(child: child); return Material( - clipBehavior: Clip.antiAlias, + clipBehavior: Clip.hardEdge, type: MaterialType.transparency, shape: attachment.type == 'file' ? null : _getDefaultShape(context), child: child, diff --git a/packages/stream_chat_flutter/lib/src/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reaction_picker.dart index 8a89b8c3..2e47b933 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_picker.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:ezanimation/ezanimation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/extension.dart'; @@ -50,95 +48,90 @@ class _ReactionPickerState extends State triggerAnimations(); } - return TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - curve: Curves.easeInOutBack, - duration: const Duration(milliseconds: 500), - builder: (context, val, wid) => Transform.scale( - scale: val, - child: Material( - borderRadius: BorderRadius.circular(24), - color: chatThemeData.colorTheme.white, - clipBehavior: Clip.hardEdge, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: reactionIcons - .map((reactionIcon) { - final ownReactionIndex = widget.message.ownReactions - ?.indexWhere((reaction) => - reaction.type == reactionIcon.type) ?? - -1; - final index = reactionIcons.indexOf(reactionIcon); + final child = Material( + borderRadius: BorderRadius.circular(24), + color: chatThemeData.colorTheme.white, + clipBehavior: Clip.hardEdge, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: reactionIcons + .map((reactionIcon) { + final ownReactionIndex = widget.message.ownReactions + ?.indexWhere( + (reaction) => reaction.type == reactionIcon.type) ?? + -1; + final index = reactionIcons.indexOf(reactionIcon); - return ConstrainedBox( - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), - child: RawMaterialButton( - elevation: 0, - shape: ContinuousRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), - onPressed: () { - if (ownReactionIndex != -1) { - removeReaction( - context, - widget.message - .ownReactions![ownReactionIndex], - ); - } else { - sendReaction( - context, - reactionIcon.type, - ); - } - }, - child: AnimatedBuilder( - animation: animations[index], - builder: (context, val) => Transform.scale( - scale: animations[index].value, - child: StreamSvgIcon( - assetName: reactionIcon.assetName, - height: max( - 0, - animations[index].value * 24.0, - ), - width: max( - 0, - animations[index].value * 24.0, - ), - color: ownReactionIndex != -1 - ? chatThemeData - .colorTheme.accentBlue - : Theme.of(context) - .iconTheme - .color! - .withOpacity(.5), - ), - )), - ), - ); - }) - .insertBetween(const SizedBox( - width: 16, - )) - .toList(), + final child = StreamSvgIcon( + assetName: reactionIcon.assetName, + color: ownReactionIndex != -1 + ? chatThemeData.colorTheme.accentBlue + : Theme.of(context).iconTheme.color!.withOpacity(.5), + ); + + return ConstrainedBox( + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, ), - ), - ), - )); + child: RawMaterialButton( + elevation: 0, + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + onPressed: () { + if (ownReactionIndex != -1) { + removeReaction( + context, + widget.message.ownReactions![ownReactionIndex], + ); + } else { + sendReaction( + context, + reactionIcon.type, + ); + } + }, + child: AnimatedBuilder( + animation: animations[index], + builder: (context, child) => Transform.scale( + scale: animations[index].value, + child: child, + ), + child: child, + ), + ), + ); + }) + .insertBetween(const SizedBox( + width: 16, + )) + .toList(), + ), + ), + ); + + return TweenAnimationBuilder( + tween: Tween(begin: 0, end: 1), + curve: Curves.easeInOutBack, + duration: const Duration(milliseconds: 500), + builder: (context, val, widget) => Transform.scale( + scale: val, + child: widget, + ), + child: child, + ); } void triggerAnimations() async { diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart index 8f974d6e..5609c18b 100644 --- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; -import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget to show the current list of typing users -class TypingIndicator extends StatelessWidget { +class TypingIndicator extends StatefulWidget { /// Instantiate a new TypingIndicator const TypingIndicator({ Key? key, @@ -30,16 +29,21 @@ class TypingIndicator extends StatelessWidget { /// Alignment of the typing indicator final Alignment alignment; + @override + _TypingIndicatorState createState() => _TypingIndicatorState(); +} + +class _TypingIndicatorState extends State { @override Widget build(BuildContext context) { final channelState = - channel?.state ?? StreamChannel.of(context).channel.state!; + widget.channel?.state ?? StreamChannel.of(context).channel.state!; final altWidget = Align( key: const Key('alternative'), - alignment: alignment, + alignment: widget.alignment, child: Container( - child: alternativeWidget ?? const Offstage(), + child: widget.alternativeWidget ?? const Offstage(), ), ); return BetterStreamBuilder>( @@ -49,10 +53,11 @@ class TypingIndicator extends StatelessWidget { duration: const Duration(milliseconds: 300), child: snapshot.isNotEmpty == true ? Padding( - padding: padding, + key: const Key('main'), + padding: widget.padding, child: Align( key: const Key('typings'), - alignment: alignment, + alignment: widget.alignment, child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -65,7 +70,7 @@ class TypingIndicator extends StatelessWidget { // ignore: lines_longer_than_80_chars ' ${snapshot[0].name}${snapshot.length == 1 ? '' : ' and ${snapshot.length - 1} more'} ${snapshot.length == 1 ? 'is' : 'are'} typing', maxLines: 1, - style: style, + style: widget.style, ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/url_attachment.dart b/packages/stream_chat_flutter/lib/src/url_attachment.dart index 995475a8..4944c73c 100644 --- a/packages/stream_chat_flutter/lib/src/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/url_attachment.dart @@ -40,7 +40,7 @@ class UrlAttachment extends StatelessWidget { children: [ if (urlAttachment.imageUrl != null) Container( - clipBehavior: Clip.antiAliasWithSaveLayer, + clipBehavior: Clip.hardEdge, margin: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 644ea99f..e98112fc 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -334,7 +334,7 @@ Widget wrapAttachmentWidget( bool reverse, ) => Material( - clipBehavior: Clip.antiAlias, + clipBehavior: Clip.hardEdge, shape: attachmentShape, type: MaterialType.transparency, child: attachmentWidget, 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 bf50966b..9691618a 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 @@ -25,29 +25,31 @@ class BetterStreamBuilder extends StatefulWidget { } class _BetterStreamBuilderState extends State> { - Widget? _child; T? _lastEvent; StreamSubscription? _subscription; Object? _lastError; @override - Widget build(BuildContext context) => _child ?? const Offstage(); + Widget build(BuildContext context) { + if (_lastError != null) { + return widget.errorBuilder!(context, _lastError!); + } + + if (_lastEvent == null) { + return widget.loadingBuilder?.call(context) ?? const Offstage(); + } + return widget.builder(context, _lastEvent ?? widget.initialData); + } bool _firstTime = true; @override void didChangeDependencies() { if (_firstTime) { - if (widget.initialData == null && widget.loadingBuilder != null) { - _child = widget.loadingBuilder!(context); - } else { - _onEvent(widget.initialData); - } _lastEvent = widget.initialData; _subscription = widget.stream?.listen( _onEvent, onError: _onError, ); - _firstTime = false; } super.didChangeDependencies(); @@ -55,12 +57,6 @@ class _BetterStreamBuilderState extends State> { @override void didUpdateWidget(covariant BetterStreamBuilder oldWidget) { - if (_lastError != null && oldWidget.errorBuilder != widget.errorBuilder) { - _onError(_lastError); - } else if (oldWidget.builder != widget.builder) { - _onEvent(_lastEvent); - } - if (oldWidget.stream != widget.stream) { _subscription?.cancel(); _subscription = widget.stream?.listen( @@ -79,21 +75,22 @@ class _BetterStreamBuilderState extends State> { void _onError(error) { if (widget.errorBuilder != null && error != _lastError) { - setState(() { - _child = widget.errorBuilder!(context, error); - }); + if (mounted) { + setState(() {}); + } _lastError = error; } } - void _onEvent(event) { + void _onEvent(T event) { _lastError = null; - if (widget.comparator != null + final isEqual = widget.comparator != null ? widget.comparator!(_lastEvent, event) - : event != _lastEvent) { - setState(() { - _child = widget.builder(context, event); - }); + : event == _lastEvent; + if (!isEqual) { + if (mounted) { + setState(() {}); + } _lastEvent = event; } } 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 20304cdd..ad1f11ae 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -346,36 +346,40 @@ class StreamChannelState extends State { @override Widget build(BuildContext context) { - Widget child = FutureBuilder>( - future: Future.wait(_futures), - initialData: [ - channel.state != null, - if (initialMessageId != null) false, - ], - builder: (context, snapshot) { - if (snapshot.hasError) { - var message = snapshot.error.toString(); - if (snapshot.error is DioError) { - final dioError = snapshot.error as DioError?; - if (dioError?.type == DioErrorType.response) { - message = dioError!.message; - } else { - message = 'Check your connection and retry'; + var child = widget.child; + if (widget.showLoading && + (initialMessageId != null || channel.state == null)) { + child = FutureBuilder>( + future: Future.wait(_futures), + initialData: [ + channel.state != null, + if (initialMessageId != null) false, + ], + builder: (context, snapshot) { + if (snapshot.hasError) { + var message = snapshot.error.toString(); + if (snapshot.error is DioError) { + final dioError = snapshot.error as DioError?; + if (dioError?.type == DioErrorType.response) { + message = dioError!.message; + } else { + message = 'Check your connection and retry'; + } } + return Center(child: Text(message)); } - return Center(child: Text(message)); - } - final initialized = snapshot.data![0]; - // ignore: avoid_bool_literals_in_conditional_expressions - final dataLoaded = initialMessageId == null ? true : snapshot.data![1]; - if (widget.showLoading && (!initialized || !dataLoaded)) { - return const Center( - child: CircularProgressIndicator(), - ); - } - return widget.child; - }, - ); + final initialized = snapshot.data![0]; + final dataLoaded = initialMessageId == null || snapshot.data![1]; + if (!initialized || !dataLoaded) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return widget.child; + }, + ); + } + if (initialMessageId != null) { child = Material(child: child); } From 23fed8d35ffca8a97e60f99baeb74027c2cda7d8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 9 Jun 2021 16:28:05 +0200 Subject: [PATCH 346/599] fix analyze --- .../lib/src/message_list_view.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 468f2d9f..195ae1f2 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1110,7 +1110,7 @@ class _MessageListViewState extends State { _itemPositionListener = widget.itemPositionListener ?? ItemPositionsListener.create(); _itemPositionStream = - valueListenableToStreamAdapter(_itemPositionListener.itemPositions); + _valueListenableToStreamAdapter(_itemPositionListener.itemPositions); _getOnThreadTap(); super.initState(); @@ -1241,11 +1241,12 @@ class _LoadingIndicator extends StatelessWidget { } } -Stream valueListenableToStreamAdapter(ValueListenable listenable) { - late StreamController controller; +Stream _valueListenableToStreamAdapter(ValueListenable listenable) { + // ignore: close_sinks + late StreamController _controller; void listener() { - controller.add(listenable.value); + _controller.add(listenable.value); } void start() { @@ -1256,12 +1257,12 @@ Stream valueListenableToStreamAdapter(ValueListenable listenable) { listenable.removeListener(listener); } - controller = StreamController( + _controller = StreamController( onListen: start, onPause: end, onResume: start, onCancel: end, ); - return controller.stream; + return _controller.stream; } From e2529231fdd1fb8a12928a8e6c86240784015911 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 9 Jun 2021 21:01:35 +0530 Subject: [PATCH 347/599] add initialized channel tests Signed-off-by: Sahil Kumar --- .../stream_chat/lib/src/client/channel.dart | 51 +- .../stream_chat/lib/src/client/client.dart | 2 +- .../lib/src/core/api/channel_api.dart | 2 +- .../test/src/api/channel_test.dart | 1872 +++++++++++++++++ .../src/{ => core}/api/requests_test.dart | 0 .../src/{ => core}/api/responses_test.dart | 0 packages/stream_chat/test/src/fakes.dart | 21 + packages/stream_chat/test/src/matchers.dart | 72 + packages/stream_chat/test/src/mocks.dart | 27 + 9 files changed, 2019 insertions(+), 28 deletions(-) create mode 100644 packages/stream_chat/test/src/api/channel_test.dart rename packages/stream_chat/test/src/{ => core}/api/requests_test.dart (100%) rename packages/stream_chat/test/src/{ => core}/api/responses_test.dart (100%) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 5bc59ae8..fc37cd5c 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -51,9 +51,9 @@ class Channel { String? _id; String? _cid; - final Map _extraData; + final Map _extraData; - set extraData(Map extraData) { + set extraData(Map extraData) { if (_initializedCompleter.isCompleted) { throw StateError( 'Once the channel is initialized you should use channel.update ' @@ -734,7 +734,7 @@ class Channel { /// Edit the channel custom data Future update( - Map channelData, [ + Map channelData, [ Message? updateMessage, ]) async { _checkInitialized(); @@ -814,19 +814,24 @@ class Channel { final messageId = message.id; final res = await _client.sendAction(id!, type, messageId, formData); + // update the passed message with response message if (res.message != null) { state!.addMessage(res.message!); } else { + // remove the passed message if response does + // not contain message final oldIndex = state!.messages.indexWhere((m) => m.id == messageId); - Message? oldMessage; + // remove regular message if present if (oldIndex != -1) { - oldMessage = state!.messages[oldIndex]; + final oldMessage = state!.messages[oldIndex]; state!.updateChannelState(state!._channelState.copyWith( messages: state?.messages?..remove(oldMessage), )); } else { - oldMessage = state!.threads.values + // remove thread message if present + // also reduces total reply count + final oldMessage = state!.threads.values .expand((messages) => messages) .firstWhereOrNull((m) => m.id == messageId); if (oldMessage?.parentId != null) { @@ -858,18 +863,11 @@ class Channel { } /// Loads the initial channel state and watches for changes - Future watch([Map options = const {}]) async { - final watchOptions = Map.from({ - 'state': true, - 'watch': true, - 'presence': false, - }) - ..addAll(options); - + Future watch() async { ChannelState response; try { - response = await query(options: watchOptions); + response = await query(watch: true); } catch (error, stackTrace) { if (!_initializedCompleter.isCompleted) { _initializedCompleter.completeError(error, stackTrace); @@ -956,17 +954,15 @@ class Channel { ); /// Creates a new channel - Future create() async => query(options: { - 'watch': false, - 'state': false, - 'presence': false, - }); + Future create() async => query(state: false); /// Query the API, get messages, members or other channel fields /// Set [preferOffline] to true to avoid the api call if the data is already /// in the offline storage Future query({ - Map options = const {}, + bool state = true, + bool watch = false, + bool presence = false, PaginationParams? messagesPagination, PaginationParams? membersPagination, PaginationParams? watchersPagination, @@ -976,10 +972,10 @@ class Channel { final updatedState = await _client.chatPersistenceClient ?.getChannelStateByCid(cid!, messagePagination: messagesPagination); if (updatedState != null && updatedState.messages.isNotEmpty) { - if (state == null) { + if (this.state == null) { _initState(updatedState); } else { - state?.updateChannelState(updatedState); + this.state?.updateChannelState(updatedState); } return updatedState; } @@ -990,6 +986,9 @@ class Channel { type, channelId: id, channelData: _extraData, + state: state, + watch: watch, + presence: presence, messagesPagination: messagesPagination, membersPagination: membersPagination, watchersPagination: watchersPagination, @@ -1000,7 +999,7 @@ class Channel { _cid = updatedState.channel!.cid; } - state?.updateChannelState(updatedState); + this.state?.updateChannelState(updatedState); return updatedState; } catch (e) { if (!_client.persistenceEnabled) { @@ -1775,7 +1774,7 @@ class ChannelClientState { ); } - late Timer _cleaningTimer; + Timer? _cleaningTimer; void _startCleaning() { if (_channelState.channel?.config.typingEvents == false) { @@ -1842,7 +1841,7 @@ class ChannelClientState { _channelStateController.close(); _isUpToDateController.close(); _threadsController.close(); - _cleaningTimer.cancel(); + _cleaningTimer?.cancel(); _pinnedMessagesTimer.cancel(); _typingEventsController.close(); } diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 9ecc780b..87e471e6 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -757,7 +757,7 @@ class StreamChatClient { Future updateChannel( String channelId, String channelType, - Map data, { + Map data, { Message? message, }) => _chatApi.channel.updateChannel( 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 761f10da..bdf2a401 100644 --- a/packages/stream_chat/lib/src/core/api/channel_api.dart +++ b/packages/stream_chat/lib/src/core/api/channel_api.dart @@ -92,7 +92,7 @@ class ChannelApi { Future updateChannel( String channelId, String channelType, - Map data, { + Map data, { Message? message, }) async { final response = await _client.post( diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart new file mode 100644 index 00000000..85f83db5 --- /dev/null +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -0,0 +1,1872 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/client/channel.dart'; +import 'package:stream_chat/src/client/retry_policy.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; + +import '../fakes.dart'; +import '../matchers.dart'; +import '../mocks.dart'; + +void main() { + ChannelState _generateChannelState( + String channelId, + String channelType, { + bool mockChannelConfig = false, + }) { + ChannelConfig? config; + if (mockChannelConfig) { + config = MockChannelConfig(); + when(() => config!.readEvents).thenReturn(true); + when(() => config!.typingEvents).thenReturn(true); + } + final channel = ChannelModel( + id: channelId, + type: channelType, + config: config, + ); + final state = ChannelState(channel: channel); + return state; + } + + Logger _createLogger(String name) { + final logger = Logger.detached(name)..level = Level.ALL; + logger.onRecord.listen(print); + return logger; + } + + // TODO : test all persistence related logic in this group + group('Initialized Channel with Persistence', () { + late final client = MockStreamChatClientWithPersistence(); + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const channelCid = '$channelType:$channelId'; + late Channel channel; + + setUpAll(() { + // Fallback values + registerFallbackValue(FakeMessage()); + registerFallbackValue>([]); + registerFallbackValue(FakeAttachmentFile()); + + // detached loggers + when(() => client.detachedLogger(any())).thenAnswer((invocation) { + final name = invocation.positionalArguments.first; + return _createLogger(name); + }); + + final retryPolicy = RetryPolicy( + shouldRetry: (_, __, ___) => false, + retryTimeout: (_, __, ___) => Duration.zero, + ); + when(() => client.retryPolicy).thenReturn(retryPolicy); + + final event = Event(type: 'event.local'); + when(() => client.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.value(event)); + + // fake clientState + final clientState = FakeClientState(); + when(() => client.state).thenReturn(clientState); + + // mock persistence client + final channelThreads = >{}; + when(() => client.chatPersistenceClient.getChannelThreads(channelCid)) + .thenAnswer((_) async => channelThreads); + final channelState = _generateChannelState(channelId, channelType); + when(() => client.chatPersistenceClient.getChannelStateByCid(channelCid)) + .thenAnswer((_) async => channelState); + when(() => client.chatPersistenceClient.updateMessages(channelCid, any())) + .thenAnswer((_) => Future.value()); + + // client logger + when(() => client.logger).thenReturn(_createLogger('mock-client-logger')); + }); + + // Setting up a initialized channel + setUp(() { + final channelState = _generateChannelState(channelId, channelType); + channel = Channel.fromState(client, channelState); + }); + + tearDown(() { + channel.dispose(); + }); + }); + + group('Initialized Channel', () { + late final client = MockStreamChatClient(); + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const channelCid = '$channelType:$channelId'; + late Channel channel; + + setUpAll(() { + // Fallback values + registerFallbackValue(FakeMessage()); + registerFallbackValue(FakeAttachmentFile()); + registerFallbackValue(FakeEvent()); + + // detached loggers + when(() => client.detachedLogger(any())).thenAnswer((invocation) { + final name = invocation.positionalArguments.first; + return _createLogger(name); + }); + + final retryPolicy = RetryPolicy( + shouldRetry: (_, __, ___) => false, + retryTimeout: (_, __, ___) => Duration.zero, + ); + when(() => client.retryPolicy).thenReturn(retryPolicy); + + final event = Event(type: 'event.local'); + when(() => client.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.value(event)); + + // fake clientState + final clientState = FakeClientState(); + when(() => client.state).thenReturn(clientState); + + // client logger + when(() => client.logger).thenReturn(_createLogger('mock-client-logger')); + }); + + // Setting up a initialized channel + setUp(() { + final channelState = _generateChannelState( + channelId, + channelType, + mockChannelConfig: true, + ); + channel = Channel.fromState(client, channelState); + }); + + tearDown(() { + channel.dispose(); + }); + + group('`.sendMessage`', () { + test('should work fine', () async { + final message = Message(id: 'test-message-id'); + + final sendMessageResponse = SendMessageResponse()..message = message; + + when(() => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + )).thenAnswer((_) async => sendMessageResponse); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.sending), + matchSendingStatus: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.sent), + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.sendMessage(message); + + expect(res, isNotNull); + expect(res.message.id, message.id); + + verify(() => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + )).called(1); + }); + + test('with attachments should work just fine', () async { + final attachments = List.generate( + 3, + (index) => Attachment( + id: 'test-attachment-id-$index', + type: index.isEven ? 'image' : 'file', + file: AttachmentFile(size: 33 * index, path: 'test-file-path'), + ), + ); + + final message = Message( + id: 'test-message-id', + attachments: attachments, + ); + + final sendImageResponse = SendImageResponse()..file = 'test-image-url'; + final sendFileResponse = SendFileResponse()..file = 'test-file-url'; + + when(() => client.sendImage( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + )).thenAnswer((_) async => sendImageResponse); + + when(() => client.sendFile( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + )).thenAnswer((_) async => sendFileResponse); + + when(() => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + )).thenAnswer((_) async => SendMessageResponse() + ..message = message.copyWith( + attachments: attachments + .map((it) => + it.copyWith(uploadState: const UploadState.success())) + .toList(growable: false), + )); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.sending), + matchSendingStatus: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.sent), + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.sendMessage(message); + + expect(res, isNotNull); + expect(res.message.id, message.id); + expect(res.message.attachments.length, message.attachments.length); + expect( + res.message.attachments.every( + (it) => it.uploadState == const UploadState.success(), + ), + isTrue, + ); + + verify(() => client.sendImage( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + )).called(2); + + verify(() => client.sendFile( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + )).called(1); + + verify(() => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + )).called(1); + }); + }); + + group('`.updateMessage`', () { + test('should work fine', () async { + final message = Message(id: 'test-message-id'); + + final updateMessageResponse = UpdateMessageResponse() + ..message = message; + + when(() => client.updateMessage(any(that: isSameMessageAs(message)))) + .thenAnswer((_) async => updateMessageResponse); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.updating), + matchSendingStatus: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.sent), + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.updateMessage(message); + + expect(res, isNotNull); + expect(res.message.id, message.id); + + verify(() => client.updateMessage( + any(that: isSameMessageAs(message)), + )).called(1); + }); + + test('with attachments should work just fine', () async { + final attachments = List.generate( + 3, + (index) => Attachment( + id: 'test-attachment-id-$index', + type: index.isEven ? 'image' : 'file', + file: AttachmentFile(size: 33 * index, path: 'test-file-path'), + ), + ); + + final message = Message( + id: 'test-message-id', + attachments: attachments, + ); + + final sendImageResponse = SendImageResponse()..file = 'test-image-url'; + final sendFileResponse = SendFileResponse()..file = 'test-file-url'; + + when(() => client.sendImage( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + )).thenAnswer((_) async => sendImageResponse); + + when(() => client.sendFile( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + )).thenAnswer((_) async => sendFileResponse); + + when(() => client.updateMessage( + any(that: isSameMessageAs(message)), + )).thenAnswer((_) async => UpdateMessageResponse() + ..message = message.copyWith( + attachments: attachments + .map((it) => + it.copyWith(uploadState: const UploadState.success())) + .toList(growable: false), + )); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.updating), + matchSendingStatus: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.sent), + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.updateMessage(message); + + expect(res, isNotNull); + expect(res.message.id, message.id); + expect(res.message.attachments.length, message.attachments.length); + expect( + res.message.attachments.every( + (it) => it.uploadState == const UploadState.success(), + ), + isTrue, + ); + + verify(() => client.sendImage( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + )).called(2); + + verify(() => client.sendFile( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + )).called(1); + + verify(() => client.updateMessage( + any(that: isSameMessageAs(message)), + )).called(1); + }); + }); + + group('`.deleteMessage`', () { + test('should work fine', () async { + const messageId = 'test-message-id'; + final message = Message(id: messageId); + + when(() => client.deleteMessage(messageId)) + .thenAnswer((_) async => EmptyResponse()); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.deleting), + matchSendingStatus: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.sent), + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.deleteMessage(message); + + expect(res, isNotNull); + + verify(() => client.deleteMessage(messageId)).called(1); + }); + + test( + 'should directly update the state with message as deleted if the state is sending or failed', + () async { + const messageId = 'test-message-id'; + final message = Message( + id: messageId, + status: MessageSendingStatus.sending, + ); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.sent), + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.deleteMessage(message); + + expect(res, isNotNull); + }, + ); + }); + + group('`.pinMessage`', () { + test('should work fine without passing timeoutOrExpirationDate', + () async { + final message = Message(id: 'test-message-id'); + + when(() => client.updateMessage(any(that: isSameMessageAs(message)))) + .thenAnswer((invocation) async => UpdateMessageResponse() + ..message = (invocation.positionalArguments.first as Message) + .copyWith(status: MessageSendingStatus.sent)); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.updating), + matchSendingStatus: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.sent), + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.pinMessage(message); + + expect(res, isNotNull); + expect(res.message.pinned, isTrue); + expect(res.message.pinExpires, isNull); + + verify(() => client.updateMessage(any(that: isSameMessageAs(message)))) + .called(1); + }); + + test( + 'should work fine if passed timeoutOrExpirationDate as num(seconds)', + () async { + final message = Message(id: 'test-message-id'); + const timeoutOrExpirationDate = 300; // 300 seconds + + when(() => client.updateMessage(any(that: isSameMessageAs(message)))) + .thenAnswer((invocation) async => UpdateMessageResponse() + ..message = (invocation.positionalArguments.first as Message) + .copyWith(status: MessageSendingStatus.sent)); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.updating), + matchSendingStatus: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.sent), + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.pinMessage( + message, + timeoutOrExpirationDate: timeoutOrExpirationDate, + ); + + expect(res, isNotNull); + expect(res.message.pinned, isTrue); + expect(res.message.pinExpires, isNotNull); + + verify(() => + client.updateMessage(any(that: isSameMessageAs(message)))) + .called(1); + }, + ); + + test( + 'should work fine if passed timeoutOrExpirationDate as DateTime', + () async { + final message = Message(id: 'test-message-id'); + final timeoutOrExpirationDate = + DateTime.now().add(const Duration(days: 3)); // 3 days + + when(() => client.updateMessage(any(that: isSameMessageAs(message)))) + .thenAnswer((invocation) async => UpdateMessageResponse() + ..message = (invocation.positionalArguments.first as Message) + .copyWith(status: MessageSendingStatus.sent)); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.updating), + matchSendingStatus: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.sent), + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.pinMessage( + message, + timeoutOrExpirationDate: timeoutOrExpirationDate, + ); + + expect(res, isNotNull); + expect(res.message.pinned, isTrue); + expect(res.message.pinExpires, isNotNull); + expect(res.message.pinExpires, timeoutOrExpirationDate.toUtc()); + + verify( + () => client.updateMessage(any(that: isSameMessageAs(message))), + ).called(1); + }, + ); + + test( + 'should throw if invalid timeoutOrExpirationDate is passed', + () async { + final message = Message(id: 'test-message-id'); + const timeoutOrExpirationDate = 'invalid-value'; + + try { + await channel.pinMessage( + message, + timeoutOrExpirationDate: timeoutOrExpirationDate, + ); + } catch (e) { + expect(e, isA()); + } + }, + ); + }); + + test('`.unpinMessage`', () async { + final message = Message(id: 'test-message-id', pinned: true); + + when(() => client.updateMessage(any(that: isSameMessageAs(message)))) + .thenAnswer((invocation) async => UpdateMessageResponse() + ..message = (invocation.positionalArguments.first as Message) + .copyWith(status: MessageSendingStatus.sent)); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.updating), + matchSendingStatus: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(status: MessageSendingStatus.sent), + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.unpinMessage(message); + + expect(res, isNotNull); + expect(res.message.pinned, isFalse); + + verify( + () => client.updateMessage(any(that: isSameMessageAs(message))), + ).called(1); + }); + + // + // /// Send a file to this channel + // Future sendFile( + // AttachmentFile file, { + // ProgressCallback? onSendProgress, + // CancelToken? cancelToken, + // }) { + // _checkInitialized(); + // return _client.sendFile( + // file, + // id!, + // type, + // onSendProgress: onSendProgress, + // cancelToken: cancelToken, + // ); + // } + // + // /// Send an image to this channel + // Future sendImage( + // AttachmentFile file, { + // ProgressCallback? onSendProgress, + // CancelToken? cancelToken, + // }) { + // _checkInitialized(); + // return _client.sendImage( + // file, + // id!, + // type, + // onSendProgress: onSendProgress, + // cancelToken: cancelToken, + // ); + // } + + group('`.search`', () { + final filter = Filter.in_('cid', const [channelCid]); + + test('should work fine with `query`', () async { + const query = 'test-search-query'; + const sort = [SortOption('test-sort-field')]; + const pagination = PaginationParams(); + + final results = List.generate(3, (index) => GetMessageResponse()); + + when(() => client.search( + filter, + query: query, + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( + (_) async => SearchMessagesResponse()..results = results, + ); + + final res = await channel.search( + query: query, + sort: sort, + paginationParams: pagination, + ); + + expect(res, isNotNull); + expect(res.results.length, results.length); + + verify(() => client.search( + filter, + query: query, + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + )).called(1); + }); + + test('should work fine with `messageFilters`', () async { + final messageFilters = Filter.query('key', 'text'); + const sort = [SortOption('test-sort-field')]; + const pagination = PaginationParams(); + + final results = List.generate(3, (index) => GetMessageResponse()); + + when(() => client.search( + filter, + messageFilters: messageFilters, + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( + (_) async => SearchMessagesResponse()..results = results, + ); + + final res = await channel.search( + sort: sort, + paginationParams: pagination, + messageFilters: messageFilters, + ); + + expect(res, isNotNull); + expect(res.results.length, results.length); + + verify(() => client.search( + filter, + messageFilters: messageFilters, + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + )).called(1); + }); + }); + + test('`.deleteFile`', () async { + const url = 'test-file-url'; + + when(() => client.deleteFile(url, channelId, channelType, + cancelToken: any(named: 'cancelToken'))) + .thenAnswer((_) async => EmptyResponse()); + + final res = await channel.deleteFile(url); + + expect(res, isNotNull); + + verify(() => client.deleteFile(url, channelId, channelType, + cancelToken: any(named: 'cancelToken'))).called(1); + }); + + test('`.deleteImage`', () async { + const url = 'test-image-url'; + + when(() => client.deleteImage(url, channelId, channelType, + cancelToken: any(named: 'cancelToken'))) + .thenAnswer((_) async => EmptyResponse()); + + final res = await channel.deleteImage(url); + + expect(res, isNotNull); + + verify(() => client.deleteImage(url, channelId, channelType, + cancelToken: any(named: 'cancelToken'))).called(1); + }); + + test('`.sendEvent`', () async { + final event = Event(type: 'event.local'); + + when(() => client.sendEvent(channelId, channelType, event)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await channel.sendEvent(event); + + expect(res, isNotNull); + + verify(() => client.sendEvent(channelId, channelType, event)).called(1); + }); + + group('`.sendReaction`', () { + test('should work fine', () async { + const type = 'test-reaction-type'; + final message = Message(id: 'test-message-id'); + + final reaction = Reaction(type: type, messageId: message.id); + + when(() => client.sendReaction(message.id, type)).thenAnswer( + (_) async => SendReactionResponse() + ..message = message + ..reaction = reaction, + ); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sent, + reactionCounts: {type: 1}, + reactionScores: {type: 1}, + latestReactions: [reaction], + ownReactions: [reaction], + ), + matchReactions: true, + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.sendReaction(message, type); + + expect(res, isNotNull); + expect(res.reaction.type, type); + expect(res.reaction.messageId, message.id); + + verify(() => client.sendReaction(message.id, type)).called(1); + }); + + test( + 'should restore previous message if `client.sendReaction` throws', + () async { + const type = 'test-reaction-type'; + final message = Message(id: 'test-message-id'); + + final reaction = Reaction(type: type, messageId: message.id); + + when(() => client.sendReaction(message.id, type)) + .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sent, + reactionCounts: {type: 1}, + reactionScores: {type: 1}, + latestReactions: [reaction], + ownReactions: [reaction], + ), + matchReactions: true, + matchSendingStatus: true, + ), + ], + [ + isSameMessageAs( + message, + matchReactions: true, + matchSendingStatus: true, + ), + ], + ]), + ); + + try { + await channel.sendReaction(message, type); + } catch (e) { + expect(e, isA()); + } + + verify(() => client.sendReaction(message.id, type)).called(1); + }, + ); + + test( + 'should override previous reaction if present and `enforceUnique` is true', + () async { + const userId = 'test-user-id'; + const messageId = 'test-message-id'; + const prevType = 'test-reaction-type'; + final prevReaction = Reaction( + type: prevType, + messageId: messageId, + userId: userId, + ); + final message = Message( + id: messageId, + ownReactions: [prevReaction], + latestReactions: [prevReaction], + reactionScores: const {prevType: 1}, + reactionCounts: const {prevType: 1}, + ); + + const type = 'test-reaction-type-2'; + final newReaction = Reaction( + type: type, + messageId: messageId, + userId: userId, + ); + final newMessage = message.copyWith( + ownReactions: [newReaction], + latestReactions: [newReaction], + ); + + const enforceUnique = true; + + when(() => client.sendReaction( + messageId, + type, + enforceUnique: enforceUnique, + )).thenAnswer( + (_) async => SendReactionResponse() + ..message = newMessage + ..reaction = newReaction, + ); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + newMessage.copyWith(status: MessageSendingStatus.sent), + matchReactions: true, + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.sendReaction( + message, + type, + enforceUnique: enforceUnique, + ); + + expect(res, isNotNull); + expect(res.reaction.type, type); + expect(res.reaction.messageId, messageId); + + verify(() => client.sendReaction( + messageId, + type, + enforceUnique: enforceUnique, + )).called(1); + }, + ); + }); + + group('`.deleteReaction`', () { + test('should work fine', () async { + const userId = 'test-user-id'; + const messageId = 'test-message-id'; + const type = 'test-reaction-type'; + final reaction = Reaction( + type: type, + messageId: messageId, + userId: userId, + ); + final message = Message( + id: messageId, + ownReactions: [reaction], + latestReactions: [reaction], + reactionScores: const {type: 1}, + reactionCounts: const {type: 1}, + ); + + when(() => client.deleteReaction(messageId, type)) + .thenAnswer((_) async => EmptyResponse()); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sent, + latestReactions: [], + ownReactions: [], + ), + matchReactions: true, + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.deleteReaction(message, reaction); + + expect(res, isNotNull); + + verify(() => client.deleteReaction(messageId, type)).called(1); + }); + + test( + 'should restore prev message state if `client.deleteReaction` throws', + () async { + const userId = 'test-user-id'; + const messageId = 'test-message-id'; + const type = 'test-reaction-type'; + final reaction = Reaction( + type: type, + messageId: messageId, + userId: userId, + ); + final message = Message( + id: messageId, + ownReactions: [reaction], + latestReactions: [reaction], + reactionScores: const {type: 1}, + reactionCounts: const {type: 1}, + ); + + when(() => client.deleteReaction(messageId, type)) + .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sent, + latestReactions: [], + ownReactions: [], + ), + matchReactions: true, + matchSendingStatus: true, + ), + ], + [ + isSameMessageAs( + message, + matchReactions: true, + matchSendingStatus: true, + ), + ], + ]), + ); + + try { + await channel.deleteReaction(message, reaction); + } catch (e) { + expect(e, isA()); + } + + verify(() => client.deleteReaction(messageId, type)).called(1); + }, + ); + }); + + test('`.update`', () async { + const channelData = { + 'name': 'Stream Team', + 'profile_image': 'test-profile-image', + }; + final updateMessage = Message( + id: 'test-message-id', + text: 'updated channel', + ); + + final channelModel = ChannelModel( + cid: channelCid, + extraData: channelData, + ); + + when(() => client.updateChannel(channelId, channelType, channelData, + message: any(named: 'message'))).thenAnswer( + (_) async => UpdateChannelResponse() + ..channel = channelModel + ..message = updateMessage, + ); + + final res = await channel.update(channelData, updateMessage); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect(res.channel.extraData, channelData); + expect(res.message?.id, updateMessage.id); + + verify(() => client.updateChannel(channelId, channelType, channelData, + message: any(named: 'message'))).called(1); + }); + + test('`.updatePartial`', () async { + const channelData = { + 'name': 'Stream Team', + 'profile_image': 'test-profile-image', + }; + + final channelModel = ChannelModel( + cid: channelCid, + extraData: { + 'coolness': 999, + ...channelData, + }, + ); + + when(() => client.updateChannelPartial( + channelId, + channelType, + channelData, + )).thenAnswer( + (_) async => PartialUpdateChannelResponse()..channel = channelModel, + ); + + final res = await channel.updatePartial(channelData); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect( + res.channel.extraData, + {'coolness': 999, ...channelData}, + ); + + verify(() => client.updateChannelPartial( + channelId, + channelType, + channelData, + )).called(1); + }); + + test('`.delete`', () async { + when(() => client.deleteChannel(channelId, channelType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await channel.delete(); + + expect(res, isNotNull); + + verify(() => client.deleteChannel(channelId, channelType)).called(1); + }); + + test('`.truncate`', () async { + when(() => client.truncateChannel(channelId, channelType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await channel.truncate(); + + expect(res, isNotNull); + + verify(() => client.truncateChannel(channelId, channelType)).called(1); + }); + + test('`.acceptInvite`', () async { + final message = Message(id: 'test-message-id', text: 'Invite Accepted'); + + final channelModel = ChannelModel(cid: channelCid); + + when(() => client.acceptChannelInvite(channelId, channelType, + message: any(named: 'message'))).thenAnswer( + (_) async => AcceptInviteResponse() + ..channel = channelModel + ..message = message, + ); + + final res = await channel.acceptInvite(message); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect(res.message?.id, message.id); + + verify(() => client.acceptChannelInvite(channelId, channelType, + message: any(named: 'message'))).called(1); + }); + + test('`.rejectInvite`', () async { + final message = Message(id: 'test-message-id', text: 'Invite Rejected'); + + final channelModel = ChannelModel(cid: channelCid); + + when(() => client.rejectChannelInvite(channelId, channelType, + message: any(named: 'message'))).thenAnswer( + (_) async => RejectInviteResponse() + ..channel = channelModel + ..message = message, + ); + + final res = await channel.rejectInvite(message); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect(res.message?.id, message.id); + + verify(() => client.rejectChannelInvite(channelId, channelType, + message: any(named: 'message'))).called(1); + }); + + test('`.addMembers`', () async { + final members = List.generate( + 3, + (index) => Member(userId: 'test-member-id-$index'), + ); + final memberIds = members + .map((it) => it.userId) + .whereType() + .toList(growable: false); + final message = Message(id: 'test-message-id', text: 'Members Added'); + + final channelModel = ChannelModel(cid: channelCid); + + when(() => client.addChannelMembers(channelId, channelType, memberIds, + message: any(named: 'message'))).thenAnswer( + (_) async => AddMembersResponse() + ..channel = channelModel + ..members = members + ..message = message, + ); + + final res = await channel.addMembers(memberIds, message); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect(res.members.length, members.length); + expect(res.message?.id, message.id); + + verify(() => client.addChannelMembers(channelId, channelType, memberIds, + message: any(named: 'message'))).called(1); + }); + + test('`.inviteMembers`', () async { + final members = List.generate( + 3, + (index) => Member(userId: 'test-member-id-$index'), + ); + final memberIds = members + .map((it) => it.userId) + .whereType() + .toList(growable: false); + final message = Message(id: 'test-message-id', text: 'Members Invited'); + + final channelModel = ChannelModel(cid: channelCid); + + when(() => client.inviteChannelMembers(channelId, channelType, memberIds, + message: any(named: 'message'))).thenAnswer( + (_) async => InviteMembersResponse() + ..channel = channelModel + ..members = members + ..message = message, + ); + + final res = await channel.inviteMembers(memberIds, message); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect(res.members.length, members.length); + expect(res.message?.id, message.id); + + verify(() => client.inviteChannelMembers( + channelId, channelType, memberIds, + message: any(named: 'message'))).called(1); + }); + + test('`.removeMembers`', () async { + final members = List.generate( + 3, + (index) => Member(userId: 'test-member-id-$index'), + ); + final memberIds = members + .map((it) => it.userId) + .whereType() + .toList(growable: false); + final message = Message(id: 'test-message-id', text: 'Members Removed'); + + final channelModel = ChannelModel(cid: channelCid); + + when(() => client.removeChannelMembers(channelId, channelType, memberIds, + message: any(named: 'message'))).thenAnswer( + (_) async => RemoveMembersResponse() + ..channel = channelModel + ..members = members + ..message = message, + ); + + final res = await channel.removeMembers(memberIds, message); + + expect(res, isNotNull); + expect(res.channel.cid, channelModel.cid); + expect(res.members.length, members.length); + expect(res.message?.id, message.id); + + verify(() => client.removeChannelMembers( + channelId, channelType, memberIds, + message: any(named: 'message'))).called(1); + }); + + group('`.sendAction`', () { + test('should work fine', () async { + final message = Message(id: 'test-message-id', text: 'Action Sent'); + const formData = {'key': 'value'}; + + when( + () => client.sendAction(channelId, channelType, message.id, formData), + ).thenAnswer((_) async => SendActionResponse()); + + final res = await channel.sendAction(message, formData); + + expect(res, isNotNull); + + verify( + () => client.sendAction(channelId, channelType, message.id, formData), + ).called(1); + }); + + test('should emit received message if not null', () async { + final message = Message(id: 'test-message-id', text: 'Action Sent'); + const formData = {'key': 'value'}; + + when( + () => client.sendAction(channelId, channelType, message.id, formData), + ).thenAnswer((_) async => SendActionResponse()..message = message); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message, + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.sendAction(message, formData); + + expect(res, isNotNull); + expect(res.message?.id, message.id); + + verify( + () => client.sendAction(channelId, channelType, message.id, formData), + ).called(1); + }); + }); + + test('`.markRead`', () async { + const messageId = 'test-message-id'; + + when(() => client.markChannelRead(channelId, channelType, + messageId: messageId)).thenAnswer((_) async => EmptyResponse()); + + expectLater( + // skipping first seed unread count -> 0 unread count + channel.state?.unreadCountStream.skip(1), + emitsInOrder([0]), + ); + + final res = await channel.markRead(messageId: messageId); + + expect(res, isNotNull); + expect(client.state.totalUnreadCount, 0); + + verify(() => client.markChannelRead(channelId, channelType, + messageId: messageId)).called(1); + }); + + group('`.watch`', () { + test('should work fine', () async { + when(() => client.queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + )).thenAnswer( + (_) async => _generateChannelState(channelId, channelType), + ); + + final res = await channel.watch(); + + expect(res, isNotNull); + expect(res.channel, isNotNull); + expect(res.channel?.cid, channelCid); + + verify(() => client.queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + )).called(1); + }); + + test('should rethrow if `.query` throws', () async { + when(() => client.queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + )).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + + try { + await channel.watch(); + } catch (e) { + expect(e, isA()); + } + + verify(() => client.queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + )).called(1); + }); + }); + + test('`.stopWatching`', () async { + when(() => client.stopChannelWatching(channelId, channelType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await channel.stopWatching(); + + expect(res, isNotNull); + + verify(() => client.stopChannelWatching(channelId, channelType)) + .called(1); + }); + + test('`.getReplies`', () async { + const parentId = 'test-parent-id'; + const options = PaginationParams(); + + final messages = List.generate( + 3, + (index) => Message( + id: 'test-message-id-$index', + parentId: parentId, + ), + ); + + when(() => client.getReplies(parentId, options)).thenAnswer( + (_) async => QueryRepliesResponse()..messages = messages, + ); + + final res = await channel.getReplies(parentId, options); + + expect(res, isNotNull); + expect(res.messages.length, messages.length); + expect(res.messages.every((it) => it.parentId == parentId), isTrue); + + verify(() => client.getReplies(parentId, options)).called(1); + }); + + test('`.getReactions`', () async { + const messageId = 'test-message-id'; + const options = PaginationParams(); + + final reactions = List.generate( + 3, + (index) => Reaction( + type: 'test-reaction-type-$index', + messageId: messageId, + ), + ); + + when(() => client.getReactions(messageId, options)).thenAnswer( + (_) async => QueryReactionsResponse()..reactions = reactions, + ); + + final res = await channel.getReactions(messageId, options); + + expect(res, isNotNull); + expect(res.reactions.length, reactions.length); + expect(res.reactions.every((it) => it.messageId == messageId), isTrue); + + verify(() => client.getReactions(messageId, options)).called(1); + }); + + test('`.getMessagesById`', () async { + final messages = List.generate( + 3, + (index) => Message(id: 'test-message-id-$index'), + ); + + final messageIds = messages.map((it) => it.id).toList(growable: false); + + when(() => client.getMessagesById(channelId, channelType, messageIds)) + .thenAnswer( + (_) async => GetMessagesByIdResponse()..messages = messages, + ); + + final res = await channel.getMessagesById(messageIds); + + expect(res, isNotNull); + expect(res.messages.length, messageIds.length); + + verify( + () => client.getMessagesById(channelId, channelType, messageIds), + ).called(1); + }); + + test('`.translateMessage`', () async { + const messageId = 'test-message-id'; + const language = 'hi'; // Hindi + const translatedMessageText = 'ā¤¨ā¤Žā¤¸āĨā¤¤āĨ‡'; + final translatedMessage = TranslatedMessage(const { + language: translatedMessageText, + }); + + when(() => client.translateMessage(messageId, language)).thenAnswer( + (_) async => TranslateMessageResponse()..message = translatedMessage, + ); + + final res = await channel.translateMessage(messageId, language); + + expect(res, isNotNull); + expect(res.message.i18n, translatedMessage.i18n); + + verify(() => client.translateMessage(messageId, language)).called(1); + }); + + group('`.query`', () { + test('should work fine', () async { + final channelState = _generateChannelState(channelId, channelType); + + when( + () => client.queryChannel( + channelType, + channelId: channelId, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer((_) async => channelState); + + final res = await channel.query(); + + expect(res, isNotNull); + + verify( + () => client.queryChannel( + channelType, + channelId: channelId, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); + }); + + test('should rethrow if `client.queryChannel` throws', () async { + when( + () => client.queryChannel( + channelType, + channelId: channelId, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + + try { + await channel.query(); + } catch (e) { + expect(e, isA()); + } + + verify( + () => client.queryChannel( + channelType, + channelId: channelId, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); + }); + }); + + test('`.queryMembers`', () async { + final filter = Filter.in_('cid', const [channelCid]); + + final members = List.generate( + 3, + (index) => Member(userId: 'test-user-id-$index'), + ); + + when(() => client.queryMembers( + channelType, + channelId: channelId, + filter: filter, + members: any(named: 'members'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + )).thenAnswer((_) async => QueryMembersResponse()..members = members); + + final res = await channel.queryMembers(filter: filter); + + expect(res, isNotNull); + expect(res.members.length, members.length); + + verify(() => client.queryMembers( + channelType, + channelId: channelId, + filter: filter, + members: any(named: 'members'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + )).called(1); + }); + + test('`.mute`', () async { + when(() => client.muteChannel( + channelCid, + expiration: any(named: 'expiration'), + )).thenAnswer((_) async => EmptyResponse()); + + final res = await channel.mute(); + + expect(res, isNotNull); + + verify(() => client.muteChannel( + channelCid, + expiration: any(named: 'expiration'), + )).called(1); + }); + + test('`.unmute`', () async { + when( + () => client.unmuteChannel(channelCid), + ).thenAnswer((_) async => EmptyResponse()); + + final res = await channel.unmute(); + + expect(res, isNotNull); + + verify( + () => client.unmuteChannel(channelCid), + ).called(1); + }); + + test('`.banUser`', () async { + const userId = 'test-user-id'; + const options = {'key': 'value'}; + + when(() => client.banUser( + userId, + {'type': channelType, 'id': channelId, ...options}, + )).thenAnswer((_) async => EmptyResponse()); + + final res = await channel.banUser(userId, options); + + expect(res, isNotNull); + + verify(() => client.banUser( + userId, + {'type': channelType, 'id': channelId, ...options}, + )).called(1); + }); + + test('`.unbanUser`', () async { + const userId = 'test-user-id'; + + when(() => client.unbanUser(userId, any())) + .thenAnswer((_) async => EmptyResponse()); + + final res = await channel.unbanUser(userId); + + expect(res, isNotNull); + + verify(() => client.unbanUser(userId, any())).called(1); + }); + + test('`.shadowBan`', () async { + const userId = 'test-user-id'; + const options = {'key': 'value'}; + + when(() => client.shadowBan( + userId, + {'type': channelType, 'id': channelId, ...options}, + )).thenAnswer((_) async => EmptyResponse()); + + final res = await channel.shadowBan(userId, options); + + expect(res, isNotNull); + + verify(() => client.shadowBan( + userId, + {'type': channelType, 'id': channelId, ...options}, + )).called(1); + }); + + test('`.removeShadowBan`', () async { + const userId = 'test-user-id'; + + when(() => client.removeShadowBan(userId, any())) + .thenAnswer((_) async => EmptyResponse()); + + final res = await channel.removeShadowBan(userId); + + expect(res, isNotNull); + + verify(() => client.removeShadowBan(userId, any())).called(1); + }); + + test('`.hide`', () async { + const clearHistory = true; + + when(() => client.hideChannel( + channelId, + channelType, + clearHistory: clearHistory, + )).thenAnswer((_) async => EmptyResponse()); + + final res = await channel.hide(clearHistory: clearHistory); + + expect(res, isNotNull); + + verify(() => client.hideChannel( + channelId, + channelType, + clearHistory: clearHistory, + )).called(1); + }); + + test('`.show`', () async { + when(() => client.showChannel(channelId, channelType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await channel.show(); + + expect(res, isNotNull); + + verify(() => client.showChannel(channelId, channelType)).called(1); + }); + + test('`.on`', () async { + const eventType = 'test.event'; + final event = Event(type: eventType, cid: channelCid); + + when(() => client.on(eventType, any(), any(), any())) + .thenAnswer((_) => Stream.value(event)); + + expectLater(channel.on(eventType), emitsInOrder([event])); + + verify(() => client.on(eventType, any(), any(), any())).called(1); + }); + + group( + '`.keyStroke`', + () { + test('should return if `config.typingEvents` is false', () async { + when(() => channel.config?.typingEvents).thenReturn(false); + + final typingEvent = Event(type: EventType.typingStart); + + await channel.keyStroke(); + + verifyNever(() => client.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(typingEvent)), + )); + }); + + test( + 'should send `typingStart` event if there is not already a typingEvent or the difference between the two is >= 2 seconds', + () async { + final typingEvent = Event(type: EventType.typingStart); + + when(() => channel.config?.typingEvents).thenReturn(true); + + when(() => client.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(typingEvent)), + )).thenAnswer((_) async => EmptyResponse()); + + await channel.keyStroke(); + + verify(() => client.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(typingEvent)), + )).called(1); + }, + ); + }, + ); + + group('`.stopTyping`', () { + test('should return if `config.typingEvents` is false', () async { + when(() => channel.config?.typingEvents).thenReturn(false); + + final typingStopEvent = Event(type: EventType.typingStop); + + await channel.keyStroke(); + + verifyNever(() => client.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(typingStopEvent)), + )); + }); + + test('should send `typingStop` successfully', () async { + final typingStopEvent = Event(type: EventType.typingStop); + + when(() => channel.config?.typingEvents).thenReturn(true); + + when(() => client.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(typingStopEvent)), + )).thenAnswer((_) async => EmptyResponse()); + + await channel.stopTyping(); + + verify(() => client.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(typingStopEvent)), + )).called(1); + }); + }); + }); +} diff --git a/packages/stream_chat/test/src/api/requests_test.dart b/packages/stream_chat/test/src/core/api/requests_test.dart similarity index 100% rename from packages/stream_chat/test/src/api/requests_test.dart rename to packages/stream_chat/test/src/core/api/requests_test.dart diff --git a/packages/stream_chat/test/src/api/responses_test.dart b/packages/stream_chat/test/src/core/api/responses_test.dart similarity index 100% rename from packages/stream_chat/test/src/api/responses_test.dart rename to packages/stream_chat/test/src/core/api/responses_test.dart diff --git a/packages/stream_chat/test/src/fakes.dart b/packages/stream_chat/test/src/fakes.dart index b7e431b1..f1468271 100644 --- a/packages/stream_chat/test/src/fakes.dart +++ b/packages/stream_chat/test/src/fakes.dart @@ -82,3 +82,24 @@ class FakeChatApi extends Fake implements StreamChatApi { AttachmentFileUploader get fileUploader => _fileUploader ??= MockAttachmentFileUploader(); } + +class FakeClientState extends Fake implements ClientState { + @override + OwnUser? get user => OwnUser(id: 'test-user-id'); + + var _totalUnreadCount = 0; + + @override + int? get totalUnreadCount => _totalUnreadCount; + + @override + set totalUnreadCount(int? unreadCount) { + _totalUnreadCount += unreadCount ?? 0; + } +} + +class FakeMessage extends Fake implements Message {} + +class FakeAttachmentFile extends Fake implements AttachmentFile {} + +class FakeEvent extends Fake implements Event {} diff --git a/packages/stream_chat/test/src/matchers.dart b/packages/stream_chat/test/src/matchers.dart index a2be680f..c958ff5f 100644 --- a/packages/stream_chat/test/src/matchers.dart +++ b/packages/stream_chat/test/src/matchers.dart @@ -1,4 +1,7 @@ +import 'package:collection/collection.dart'; import 'package:dio/dio.dart' show MultipartFile; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/message.dart'; import 'package:test/test.dart'; Matcher isSameMultipartFileAs(MultipartFile targetFile) => @@ -17,3 +20,72 @@ class _IsSameMultipartFileAs extends Matcher { bool matches(covariant MultipartFile file, Map matchState) => file.length == targetFile.length; } + +Matcher isSameEventAs(Event targetEvent) => + _IsSameEventAs(targetEvent: targetEvent); + +class _IsSameEventAs extends Matcher { + const _IsSameEventAs({required this.targetEvent}); + + final Event targetEvent; + + @override + Description describe(Description description) => + description.add('is same event as $targetEvent'); + + @override + bool matches(covariant Event event, Map matchState) => + event.type == targetEvent.type; +} + +Matcher isSameMessageAs( + Message targetMessage, { + bool matchReactions = false, + bool matchSendingStatus = false, +}) => + _IsSameMessageAs( + targetMessage: targetMessage, + matchReactions: matchReactions, + matchSendingStatus: matchSendingStatus, + ); + +class _IsSameMessageAs extends Matcher { + const _IsSameMessageAs({ + required this.targetMessage, + this.matchReactions = false, + this.matchSendingStatus = false, + }); + + final Message targetMessage; + final bool matchReactions; + final bool matchSendingStatus; + + @override + Description describe(Description description) => + description.add('is same message as $targetMessage'); + + @override + bool matches(covariant Message message, Map matchState) { + var matches = message.id == targetMessage.id; + if (matchSendingStatus) { + matches &= message.status == targetMessage.status; + } + if (matchReactions) { + matches &= const ListEquality().equals( + message.ownReactions + ?.map((it) => '${it.type}-${it.messageId}') + .toList(), + targetMessage.ownReactions + ?.map((it) => '${it.type}-${it.messageId}') + .toList()); + matches &= const ListEquality().equals( + message.latestReactions + ?.map((it) => '${it.type}-${it.messageId}') + .toList(), + targetMessage.latestReactions + ?.map((it) => '${it.type}-${it.messageId}') + .toList()); + } + return matches; + } +} diff --git a/packages/stream_chat/test/src/mocks.dart b/packages/stream_chat/test/src/mocks.dart index 411f06a3..9c20be7c 100644 --- a/packages/stream_chat/test/src/mocks.dart +++ b/packages/stream_chat/test/src/mocks.dart @@ -1,6 +1,7 @@ import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/client/client.dart'; import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; import 'package:stream_chat/src/core/api/channel_api.dart'; import 'package:stream_chat/src/core/api/device_api.dart'; @@ -12,8 +13,13 @@ import 'package:stream_chat/src/core/api/user_api.dart'; import 'package:stream_chat/src/core/http/connection_id_manager.dart'; import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; +import 'package:stream_chat/src/core/models/channel_config.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/db/chat_persistence_client.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; +import 'db/chat_persistence_client_test.dart'; + class MockWebSocketChannel extends Mock implements WebSocketChannel {} class MockWebSocketSink extends Mock implements WebSocketSink {} @@ -57,3 +63,24 @@ class MockGeneralApi extends Mock implements GeneralApi {} class MockAttachmentFileUploader extends Mock implements AttachmentFileUploader {} + +class MockPersistenceClient extends Mock implements ChatPersistenceClient {} + +class MockStreamChatClient extends Mock implements StreamChatClient { + @override + bool get persistenceEnabled => false; +} + +class MockStreamChatClientWithPersistence extends Mock + implements StreamChatClient { + ChatPersistenceClient? _persistenceClient; + + @override + ChatPersistenceClient get chatPersistenceClient => + _persistenceClient ??= MockPersistenceClient(); + + @override + bool get persistenceEnabled => true; +} + +class MockChannelConfig extends Mock implements ChannelConfig {} From 33f19ebd5d89a458aa29b3c137f880b86c06d3ab Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 10 Jun 2021 11:06:15 +0200 Subject: [PATCH 348/599] add comments --- .../lib/src/better_stream_builder.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 9691618a..a51c0762 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 @@ -2,7 +2,12 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; +/// A more efficient [StreamBuilder] +/// It requires [initialData] and will rebuild +/// only when the new data is different than the current data +/// The [comparator] is used to check if the new data is different class BetterStreamBuilder extends StatefulWidget { + /// Creates a new BetterStreamBuilder const BetterStreamBuilder({ required this.stream, required this.initialData, @@ -13,11 +18,22 @@ class BetterStreamBuilder extends StatefulWidget { Key? key, }) : super(key: key); + /// The stream to listen to final Stream? stream; + + /// The initial data available final T initialData; + + /// Comparator used to check if the new data is different than the last one final bool Function(T?, T)? comparator; + + /// Builder that builds based on the new snapshot final Widget Function(BuildContext context, T data) builder; + + /// Builder that builds when the data is null final Widget Function(BuildContext context)? loadingBuilder; + + /// Builder used when there is an error final Widget Function(BuildContext context, Object error)? errorBuilder; @override From a6ff8e70393206532478bc419188c32e5aba3a0a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 10 Jun 2021 12:33:20 +0200 Subject: [PATCH 349/599] fix tests --- .../test/src/channel_header_test.dart | 2 + .../test/src/message_input_test.dart | 10 ---- .../stream_chat_flutter/test/src/mocks.dart | 13 +++- .../test/src/unread_indicator_test.dart | 2 +- .../lib/src/stream_channel.dart | 60 +++++++++---------- .../test/message_list_core_test.dart | 7 +++ .../stream_chat_flutter_core/test/mocks.dart | 4 ++ .../test/stream_chat_core_test.dart | 45 +++++++------- 8 files changed, 77 insertions(+), 66 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel_header_test.dart index 6da3e20f..9f6e981d 100644 --- a/packages/stream_chat_flutter/test/src/channel_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_header_test.dart @@ -107,6 +107,8 @@ void main() { ]); when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); + when(() => client.wsConnectionStatus) + .thenReturn(ConnectionStatus.disconnected); when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(1)); diff --git a/packages/stream_chat_flutter/test/src/message_input_test.dart b/packages/stream_chat_flutter/test/src/message_input_test.dart index 63ddb10f..f07f518d 100644 --- a/packages/stream_chat_flutter/test/src/message_input_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input_test.dart @@ -53,15 +53,6 @@ void main() { ) ])); - when(() => channelState.typingEvents).thenAnswer((i) => [ - User(id: 'other-user', extraData: {'name': 'demo'}) - ]); - when(() => channelState.typingEventsStream) - .thenAnswer((i) => Stream.value([ - User(id: 'other-user', extraData: {'name': 'demo'}), - User(id: 'other-user', extraData: {'name': 'demo'}), - ])); - await tester.pumpWidget(MaterialApp( home: StreamChat( client: client, @@ -75,7 +66,6 @@ void main() { )); expect(find.byType(TextField), findsOneWidget); - expect(find.byType(StreamSvgIcon), findsNWidgets(8)); expect(find.byKey(const Key('messageInputText')), findsOneWidget); }, ); diff --git a/packages/stream_chat_flutter/test/src/mocks.dart b/packages/stream_chat_flutter/test/src/mocks.dart index 083c2ff5..d2b04e11 100644 --- a/packages/stream_chat_flutter/test/src/mocks.dart +++ b/packages/stream_chat_flutter/test/src/mocks.dart @@ -2,7 +2,11 @@ import 'package:flutter/material.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -class MockClient extends Mock implements StreamChatClient {} +class MockClient extends Mock implements StreamChatClient { + MockClient() { + when(() => wsConnectionStatus).thenReturn(ConnectionStatus.connected); + } +} class MockClientState extends Mock implements ClientState {} @@ -17,7 +21,12 @@ class MockChannel extends Mock implements Channel { } } -class MockChannelState extends Mock implements ChannelClientState {} +class MockChannelState extends Mock implements ChannelClientState { + MockChannelState() { + when(() => typingEvents).thenReturn([]); + when(() => typingEventsStream).thenAnswer((_) => Stream.value([])); + } +} class MockNavigatorObserver extends Mock implements NavigatorObserver {} diff --git a/packages/stream_chat_flutter/test/src/unread_indicator_test.dart b/packages/stream_chat_flutter/test/src/unread_indicator_test.dart index 29bc0d79..96a6c678 100644 --- a/packages/stream_chat_flutter/test/src/unread_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/unread_indicator_test.dart @@ -83,7 +83,7 @@ void main() { ), )); - expect(find.byType(SizedBox), findsOneWidget); + expect(find.text('0'), findsNothing); }, ); 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 ad1f11ae..20304cdd 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -346,40 +346,36 @@ class StreamChannelState extends State { @override Widget build(BuildContext context) { - var child = widget.child; - if (widget.showLoading && - (initialMessageId != null || channel.state == null)) { - child = FutureBuilder>( - future: Future.wait(_futures), - initialData: [ - channel.state != null, - if (initialMessageId != null) false, - ], - builder: (context, snapshot) { - if (snapshot.hasError) { - var message = snapshot.error.toString(); - if (snapshot.error is DioError) { - final dioError = snapshot.error as DioError?; - if (dioError?.type == DioErrorType.response) { - message = dioError!.message; - } else { - message = 'Check your connection and retry'; - } + Widget child = FutureBuilder>( + future: Future.wait(_futures), + initialData: [ + channel.state != null, + if (initialMessageId != null) false, + ], + builder: (context, snapshot) { + if (snapshot.hasError) { + var message = snapshot.error.toString(); + if (snapshot.error is DioError) { + final dioError = snapshot.error as DioError?; + if (dioError?.type == DioErrorType.response) { + message = dioError!.message; + } else { + message = 'Check your connection and retry'; } - return Center(child: Text(message)); } - final initialized = snapshot.data![0]; - final dataLoaded = initialMessageId == null || snapshot.data![1]; - if (!initialized || !dataLoaded) { - return const Center( - child: CircularProgressIndicator(), - ); - } - return widget.child; - }, - ); - } - + return Center(child: Text(message)); + } + final initialized = snapshot.data![0]; + // ignore: avoid_bool_literals_in_conditional_expressions + final dataLoaded = initialMessageId == null ? true : snapshot.data![1]; + if (widget.showLoading && (!initialized || !dataLoaded)) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return widget.child; + }, + ); if (initialMessageId != null) { child = Material(child: child); } diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index e53cd0a8..4e34f8f2 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -100,6 +100,7 @@ void main() { when(() => mockChannel.state.isUpToDate).thenReturn(true); when(() => mockChannel.state.messagesStream) .thenAnswer((_) => Stream.value([])); + when(() => mockChannel.state.messages).thenReturn([]); await tester.pumpWidget( StreamChannel( @@ -133,6 +134,7 @@ void main() { when(() => mockChannel.state.isUpToDate).thenReturn(true); when(() => mockChannel.state.messagesStream) .thenAnswer((_) => Stream.value([])); + when(() => mockChannel.state.messages).thenReturn([]); when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true)); await tester.pumpWidget( @@ -174,6 +176,7 @@ void main() { when(() => mockChannel.state.messages).thenReturn(messages); when(() => mockChannel.state.messagesStream) .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.state.messages).thenReturn(messages); when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true)); await tester.pumpWidget( @@ -220,6 +223,7 @@ void main() { const error = 'Error! Error! Error!'; when(() => mockChannel.state.messagesStream) .thenAnswer((_) => Stream.error(error)); + when(() => mockChannel.state.messages).thenReturn([]); await tester.pumpWidget( Directionality( @@ -259,6 +263,7 @@ void main() { const messages = []; when(() => mockChannel.state.messagesStream) .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.state.messages).thenReturn(messages); await tester.pumpWidget( Directionality( @@ -305,6 +310,7 @@ void main() { const messages = []; when(() => mockChannel.state.messagesStream) .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.state.messages).thenReturn(messages); await tester.pumpWidget( Directionality( @@ -349,6 +355,7 @@ void main() { final messages = _generateMessages(); when(() => mockChannel.state.messagesStream) .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.state.messages).thenReturn(messages); await tester.pumpWidget( Directionality( diff --git a/packages/stream_chat_flutter_core/test/mocks.dart b/packages/stream_chat_flutter_core/test/mocks.dart index 6ca24363..73227e7c 100644 --- a/packages/stream_chat_flutter_core/test/mocks.dart +++ b/packages/stream_chat_flutter_core/test/mocks.dart @@ -4,6 +4,10 @@ import 'package:stream_chat/stream_chat.dart'; class MockLogger extends Mock implements Logger {} class MockClient extends Mock implements StreamChatClient { + MockClient() { + when(() => wsConnectionStatus).thenReturn(ConnectionStatus.connected); + } + @override final Logger logger = MockLogger(); diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index 936e03f0..f8423e5f 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -235,6 +235,14 @@ void main() { final mockClient = MockClient(); const streamChatCoreKey = Key('streamChatCore'); const childKey = Key('child'); + + final event = Event(); + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(() => mockClient.connect()).thenAnswer((_) async => event); + when(() => mockClient.disconnect()).thenAnswer((_) async => null); + when(() => mockClient.wsConnectionStatus) + .thenReturn(ConnectionStatus.disconnected); + final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, @@ -247,13 +255,6 @@ void main() { expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget); - final event = Event(); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.connect()).thenAnswer((_) async => event); - when(mockClient.disconnect).thenAnswer((_) async => null); - when(() => mockClient.wsConnectionStatus) - .thenReturn(ConnectionStatus.disconnected); - final streamChatCoreState = tester.state( find.byKey(streamChatCoreKey), ); @@ -323,6 +324,14 @@ void main() { const childKey = Key('child'); final _connectivityController = BehaviorSubject.seeded(ConnectivityResult.none); + + final event = Event(); + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(() => mockClient.connect()).thenAnswer((_) async => event); + when(() => mockClient.disconnect()).thenAnswer((_) async => null); + when(() => mockClient.wsConnectionStatus) + .thenReturn(ConnectionStatus.disconnected); + final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, @@ -335,13 +344,6 @@ void main() { expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget); - final event = Event(); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.connect()).thenAnswer((_) async => event); - when(mockClient.disconnect).thenAnswer((_) async => null); - when(() => mockClient.wsConnectionStatus) - .thenReturn(ConnectionStatus.disconnected); - _connectivityController.add(ConnectivityResult.mobile); await Future.delayed(const Duration(seconds: 1)); @@ -397,6 +399,14 @@ void main() { const childKey = Key('child'); final _connectivityController = BehaviorSubject.seeded(ConnectivityResult.none); + + final event = Event(); + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(() => mockClient.connect()).thenAnswer((_) async => event); + when(() => mockClient.disconnect()).thenAnswer((_) async => null); + when(() => mockClient.wsConnectionStatus) + .thenReturn(ConnectionStatus.disconnected); + final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, @@ -409,13 +419,6 @@ void main() { expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget); - final event = Event(); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.connect()).thenAnswer((_) async => event); - when(mockClient.disconnect).thenAnswer((_) async => null); - when(() => mockClient.wsConnectionStatus) - .thenReturn(ConnectionStatus.disconnected); - final streamChatCoreState = tester.state( find.byKey(streamChatCoreKey), ); From b1201003f5ccbb4e5202b2734a1deed6412f65e3 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 11 Jun 2021 10:31:57 +0200 Subject: [PATCH 350/599] fix review --- .../lib/src/channel_image.dart | 12 ++--- .../lib/src/channel_info.dart | 4 +- .../lib/src/channel_list_view.dart | 2 +- .../lib/src/channel_name.dart | 4 +- .../lib/src/channel_preview.dart | 22 ++++---- .../lib/src/connection_status_builder.dart | 50 +++++++------------ .../lib/src/info_tile.dart | 3 -- .../lib/src/message_list_view.dart | 8 +-- .../lib/src/typing_indicator.dart | 6 +-- .../lib/src/unread_indicator.dart | 6 +-- .../lib/src/message_list_core.dart | 4 +- 11 files changed, 51 insertions(+), 70 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index dd9bb314..d3e028ee 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -86,11 +86,11 @@ class ChannelImage extends StatelessWidget { return BetterStreamBuilder>( stream: channel.extraDataStream, initialData: channel.extraData, - builder: (context, snapshot) { + builder: (context, data) { String? image; final chatThemeData = StreamChatTheme.of(context); - if (snapshot.containsKey('image') == true) { - image = snapshot['image']; + if (data.containsKey('image') == true) { + image = data['image']; } else if (channel.state?.members.length == 2) { final otherMember = channel.state?.members .firstWhere((member) => member.user?.id != streamChat.user?.id); @@ -100,11 +100,11 @@ class ChannelImage extends StatelessWidget { users[otherMember?.userId] ?? otherMember!.user!) .distinct(), initialData: otherMember!.user, - builder: (context, snapshot) => UserAvatar( + builder: (context, user) => UserAvatar( borderRadius: borderRadius ?? chatThemeData .channelPreviewTheme.avatarTheme?.borderRadius, - user: snapshot ?? otherMember.user!, + user: user ?? otherMember.user!, constraints: constraints ?? chatThemeData .channelPreviewTheme.avatarTheme?.constraints, @@ -155,7 +155,7 @@ class ChannelImage extends StatelessWidget { imageUrl: image, errorWidget: (_, __, ___) => Center( child: Text( - snapshot.containsKey('name') ? snapshot['name'][0] : '', + data.containsKey('name') ? data['name'][0] : '', style: TextStyle( color: chatThemeData.colorTheme.white, fontWeight: FontWeight.bold, diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index 91e7aceb..32dbeb67 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -28,11 +28,11 @@ class ChannelInfo extends StatelessWidget { return BetterStreamBuilder>( stream: channel.state!.membersStream, initialData: channel.state!.members, - builder: (context, snapshot) => ConnectionStatusBuilder( + builder: (context, data) => ConnectionStatusBuilder( statusBuilder: (context, status) { switch (status) { case ConnectionStatus.connected: - return _buildConnectedTitleState(context, snapshot); + return _buildConnectedTitleState(context, data); case ConnectionStatus.connecting: return _buildConnectingTitleState(context); case ConnectionStatus.disconnected: 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 12b73f72..d9f25710 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -648,7 +648,7 @@ class _ChannelListViewState extends State { ), ), ), - builder: (context, snapshot) => snapshot + builder: (context, data) => data ? const Center( child: Padding( padding: EdgeInsets.all(16), diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart index 4d560bed..4819ecc7 100644 --- a/packages/stream_chat_flutter/lib/src/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel_name.dart @@ -29,8 +29,8 @@ class ChannelName extends StatelessWidget { return BetterStreamBuilder>( stream: channel.extraDataStream, initialData: channel.extraData, - builder: (context, snapshot) => _buildName( - snapshot, + builder: (context, data) => _buildName( + data, channel.state?.members, client, ), diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index bb41f652..d9170eb8 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -73,8 +73,8 @@ class ChannelPreview extends StatelessWidget { return BetterStreamBuilder( stream: channel.isMutedStream, initialData: channel.isMuted, - builder: (context, snapshot) => AnimatedOpacity( - opacity: snapshot ? 0.5 : 1, + builder: (context, data) => AnimatedOpacity( + opacity: data ? 0.5 : 1, duration: const Duration(milliseconds: 300), child: ListTile( visualDensity: VisualDensity.compact, @@ -108,9 +108,9 @@ class ChannelPreview extends StatelessWidget { stream: channel.state?.membersStream, initialData: channel.state?.members, comparator: const ListEquality().equals, - builder: (context, snapshot) { - if (snapshot?.isEmpty == true || - snapshot?.any((Member e) => + builder: (context, members) { + if (members?.isEmpty == true || + members?.any((Member e) => e.user!.id == channel.client.state.user?.id) != true) { @@ -165,11 +165,11 @@ class ChannelPreview extends StatelessWidget { Widget _buildDate(BuildContext context) => BetterStreamBuilder( stream: channel.lastMessageAtStream, initialData: channel.lastMessageAt, - builder: (context, snapshot) { - if (snapshot == null) { + builder: (context, data) { + if (data == null) { return const Offstage(); } - final lastMessageAt = snapshot.toLocal(); + final lastMessageAt = data.toLocal(); String stringDate; final now = DateTime.now(); @@ -225,9 +225,9 @@ class ChannelPreview extends StatelessWidget { BetterStreamBuilder?>( stream: channel.state!.messagesStream, initialData: channel.state!.messages, - builder: (context, snapshot) { - final lastMessage = snapshot - ?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted); + builder: (context, data) { + final lastMessage = + data?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted); if (lastMessage == null) { return const SizedBox(); } diff --git a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart index 98daa580..fbe7ffae 100644 --- a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart +++ b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -8,20 +7,16 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// /// The widget will use the closest [StreamChatClient.wsConnectionStatusStream] /// in case no stream is provided. -class ConnectionStatusBuilder extends StatefulWidget { +class ConnectionStatusBuilder extends StatelessWidget { /// Creates a new ConnectionStatusBuilder const ConnectionStatusBuilder({ Key? key, required this.statusBuilder, - this.initialStatus, this.connectionStatusStream, this.errorBuilder, this.loadingBuilder, }) : super(key: key); - /// The connection status that will be used to create the initial snapshot. - final ConnectionStatus? initialStatus; - /// The asynchronous computation to which this builder is currently connected. final Stream? connectionStatusStream; @@ -36,32 +31,21 @@ class ConnectionStatusBuilder extends StatefulWidget { statusBuilder; @override - _ConnectionStatusBuilderState createState() => - _ConnectionStatusBuilderState(); -} - -class _ConnectionStatusBuilderState extends State { - late StreamChatClient client; - late Stream stream; - - @override - Widget build(BuildContext context) => BetterStreamBuilder( - initialData: widget.initialStatus ?? client.wsConnectionStatus, - stream: stream, - loadingBuilder: widget.loadingBuilder, - errorBuilder: (context, error) { - if (widget.errorBuilder != null) { - return widget.errorBuilder!(context, error); - } - return const Offstage(); - }, - builder: widget.statusBuilder, - ); - - @override - void didChangeDependencies() { - client = StreamChat.of(context).client; - stream = widget.connectionStatusStream ?? client.wsConnectionStatusStream; - super.didChangeDependencies(); + Widget build(BuildContext context) { + final stream = connectionStatusStream ?? + StreamChat.of(context).client.wsConnectionStatusStream; + final client = StreamChat.of(context).client; + return BetterStreamBuilder( + initialData: client.wsConnectionStatus, + stream: stream, + loadingBuilder: loadingBuilder, + errorBuilder: (context, error) { + if (errorBuilder != null) { + return errorBuilder!(context, error); + } + return const Offstage(); + }, + builder: statusBuilder, + ); } } diff --git a/packages/stream_chat_flutter/lib/src/info_tile.dart b/packages/stream_chat_flutter/lib/src/info_tile.dart index b420cadc..1e935942 100644 --- a/packages/stream_chat_flutter/lib/src/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/info_tile.dart @@ -40,9 +40,6 @@ class InfoTile extends StatelessWidget { @override Widget build(BuildContext context) { final chatThemeData = StreamChatTheme.of(context); - if (!showMessage) { - return child; - } return PortalEntry( visible: showMessage, portalAnchor: tileAnchor ?? Alignment.topCenter, 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 195ae1f2..82b64c27 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1170,9 +1170,9 @@ class _MessageListViewState extends State { (messages) => messages!.firstWhere((m) => m.id == message.id)), initialData: message, - builder: (_, snapshot) => StreamChannel( + builder: (_, data) => StreamChannel( channel: streamChannel!.channel, - child: widget.threadBuilder!(context, snapshot), + child: widget.threadBuilder!(context, data), ), ), ), @@ -1220,8 +1220,8 @@ class _LoadingIndicator extends StatelessWidget { child: Text('Error loading messages'), ), ), - builder: (context, snapshot) { - if (!snapshot) { + builder: (context, data) { + if (!data) { if (!isThreadConversation && direction == QueryDirection.top) { return const SizedBox( height: 52, diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart index 5609c18b..cdd2ce68 100644 --- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart @@ -49,9 +49,9 @@ class _TypingIndicatorState extends State { return BetterStreamBuilder>( initialData: channelState.typingEvents, stream: channelState.typingEventsStream, - builder: (context, snapshot) => AnimatedSwitcher( + builder: (context, data) => AnimatedSwitcher( duration: const Duration(milliseconds: 300), - child: snapshot.isNotEmpty == true + child: data.isNotEmpty == true ? Padding( key: const Key('main'), padding: widget.padding, @@ -68,7 +68,7 @@ class _TypingIndicatorState extends State { ), Text( // ignore: lines_longer_than_80_chars - ' ${snapshot[0].name}${snapshot.length == 1 ? '' : ' and ${snapshot.length - 1} more'} ${snapshot.length == 1 ? 'is' : 'are'} typing', + ' ${data[0].name}${data.length == 1 ? '' : ' and ${data.length - 1} more'} ${data.length == 1 ? 'is' : 'are'} typing', maxLines: 1, style: widget.style, ), diff --git a/packages/stream_chat_flutter/lib/src/unread_indicator.dart b/packages/stream_chat_flutter/lib/src/unread_indicator.dart index 8b235187..381bad39 100644 --- a/packages/stream_chat_flutter/lib/src/unread_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/unread_indicator.dart @@ -24,8 +24,8 @@ class UnreadIndicator extends StatelessWidget { initialData: cid != null ? client.state.channels[cid]?.state?.unreadCount : client.state.totalUnreadCount, - builder: (context, snapshot) { - if (snapshot == null || snapshot == 0) { + builder: (context, data) { + if (data == null || data == 0) { return const Offstage(); } return Material( @@ -42,7 +42,7 @@ class UnreadIndicator extends StatelessWidget { ), child: Center( child: Text( - '${snapshot > 99 ? '99+' : snapshot}', + '${data > 99 ? '99+' : data}', style: const TextStyle( fontSize: 11, color: Colors.white, diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 0da733bf..2a257fe6 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -151,8 +151,8 @@ class MessageListCoreState extends State { ), errorBuilder: widget.errorWidgetBuilder, loadingBuilder: widget.loadingBuilder, - builder: (context, snapshot) { - final messageList = snapshot?.reversed.toList(growable: false) ?? []; + builder: (context, data) { + final messageList = data?.reversed.toList(growable: false) ?? []; if (messageList.isEmpty && !_isThreadConversation) { if (_upToDate) { return widget.emptyBuilder(context); From 712e42c240a3dbf320174538d2548bdcc360d966 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 11 Jun 2021 10:43:13 +0200 Subject: [PATCH 351/599] revert typinh indicator to stateless widget --- .../lib/src/typing_indicator.dart | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart index cdd2ce68..100cdb53 100644 --- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart @@ -3,7 +3,7 @@ import 'package:lottie/lottie.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget to show the current list of typing users -class TypingIndicator extends StatefulWidget { +class TypingIndicator extends StatelessWidget { /// Instantiate a new TypingIndicator const TypingIndicator({ Key? key, @@ -29,21 +29,16 @@ class TypingIndicator extends StatefulWidget { /// Alignment of the typing indicator final Alignment alignment; - @override - _TypingIndicatorState createState() => _TypingIndicatorState(); -} - -class _TypingIndicatorState extends State { @override Widget build(BuildContext context) { final channelState = - widget.channel?.state ?? StreamChannel.of(context).channel.state!; + channel?.state ?? StreamChannel.of(context).channel.state!; final altWidget = Align( key: const Key('alternative'), - alignment: widget.alignment, + alignment: alignment, child: Container( - child: widget.alternativeWidget ?? const Offstage(), + child: alternativeWidget ?? const Offstage(), ), ); return BetterStreamBuilder>( @@ -54,10 +49,10 @@ class _TypingIndicatorState extends State { child: data.isNotEmpty == true ? Padding( key: const Key('main'), - padding: widget.padding, + padding: padding, child: Align( key: const Key('typings'), - alignment: widget.alignment, + alignment: alignment, child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -70,7 +65,7 @@ class _TypingIndicatorState extends State { // ignore: lines_longer_than_80_chars ' ${data[0].name}${data.length == 1 ? '' : ' and ${data.length - 1} more'} ${data.length == 1 ? 'is' : 'are'} typing', maxLines: 1, - style: widget.style, + style: style, ), ], ), From f521e13aca461ca6aa5d17aa7cc3dbad758103c5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 11 Jun 2021 10:51:25 +0200 Subject: [PATCH 352/599] fix better stream builder --- .../lib/src/better_stream_builder.dart | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) 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 a51c0762..ddf342dc 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 @@ -57,18 +57,14 @@ class _BetterStreamBuilderState extends State> { return widget.builder(context, _lastEvent ?? widget.initialData); } - bool _firstTime = true; @override - void didChangeDependencies() { - if (_firstTime) { - _lastEvent = widget.initialData; - _subscription = widget.stream?.listen( - _onEvent, - onError: _onError, - ); - _firstTime = false; - } - super.didChangeDependencies(); + void initState() { + _lastEvent = widget.initialData; + _subscription = widget.stream?.listen( + _onEvent, + onError: _onError, + ); + super.initState(); } @override From e60e15a7c4ffb133590c2c48e4ec08cbe51129a1 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 11 Jun 2021 15:11:58 +0530 Subject: [PATCH 353/599] feat: Added basic introduction.mdx --- .../docs/Flutter/basics/introduction.mdx | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/basics/introduction.mdx b/docusaurus/docs/Flutter/basics/introduction.mdx index bd4924f9..5a656d1e 100644 --- a/docusaurus/docs/Flutter/basics/introduction.mdx +++ b/docusaurus/docs/Flutter/basics/introduction.mdx @@ -11,6 +11,43 @@ Stream Chat allows you to build real-time chat messaging in less time. Rapidly ship in-app messaging with our highly reliable chat infrastructure and feature-rich SDKs. Drive in-app conversion, engagement, and retention. +The Flutter SDK enables you to build any type of chat or messaging experience for Android, iOS, Web +and Desktop. + This section of the documentation focuses on our Flutter SDK which helps you easily ship high quality messaging experiences in apps and programs built with the [Flutter toolkit made -by Google](https://flutter.dev). \ No newline at end of file +by Google](https://flutter.dev). + +The Stream Chat Flutter SDK comprises of four different packages to choose from ranging from ones +giving you complete control to ones that give you a rich out-of-the-box chat experience. + +The packages that make up the Stream Chat SDK are: + +1. Low Level Client (stream_chat) : A pure Dart package that can be used on any Dart project. +It provides a low-level client to access the Stream Chat service. +2. Core (stream_chat_flutter_core) : Provides business logic to fetch common things required +for integrating Stream Chat into your application. +The core package allows more customisation and hence provides business logic but no UI components. +3. UI (stream_chat_flutter) : This library includes both a low-level chat SDK and a set of +reusable and customisable UI components. +4. Persistence (stream_chat_persistence) : Provides a persistence client for fetching and +saving chat data locally. + +We recommend building prototypes using the full UI package since it contains UI widgets already +integrated with Stream's API. [stream_chat_flutter](https://pub.dev/packages/stream_chat_flutter) +is the fastest way to get up and running using Stream chat in your app. + +If you're building a very custom UI and would prefer a more lean package, +our core package will be suited to this use case. Core allows you to build custom, +expressive UIs while retaining the benefits of our full Flutter SDK. +APIs for accessing and controlling users, sending messages, etc are seamlessly integrated into +this package and accessible via providers and builders. + +Before reading the docs, consider trying our [online API tour](https://getstream.io/chat/get_started/), +it is a nice way to learn how the API works. +It's in-browser so Javascript-based but the ideas are pretty much the same as Dart. + +You may also like to look at the [Flutter tutorial](https://getstream.io/chat/flutter/tutorial/) +which focuses on using the UI package to get Stream Chat integrated into a Flutter app. + +Further sections break down each individual packages and explain several common operations. \ No newline at end of file From 59fb6583b93c2fbd388df1612438c22ab68f2c96 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 11 Jun 2021 16:02:27 +0530 Subject: [PATCH 354/599] feat: Added basic introduction --- .../docs/Flutter/stream_chat/introduction.mdx | 13 ++++++++++++- docusaurus/docs/Flutter/stream_chat/setup.mdx | 6 ++++++ docusaurus/docs/Flutter/stream_chat/user_types.mdx | 6 ++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 docusaurus/docs/Flutter/stream_chat/setup.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/user_types.mdx diff --git a/docusaurus/docs/Flutter/stream_chat/introduction.mdx b/docusaurus/docs/Flutter/stream_chat/introduction.mdx index fc8f385e..dbc6087f 100644 --- a/docusaurus/docs/Flutter/stream_chat/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat/introduction.mdx @@ -2,4 +2,15 @@ id: introduction sidebar_position: 1 title: Introduction ---- \ No newline at end of file +--- + +

Official Dart Client for Stream Chat

+ +The official Dart client for Stream Chat, a service for building chat applications. +This library can be used on any Dart project and on both mobile and web apps with Flutter. + +The Low Level Client (stream_chat) provides the basic bindings for integrating Stream Chat into +your Flutter app with the ability to connect users, listening to events, send and receive messages, +channel CRUD operations, etc. + + diff --git a/docusaurus/docs/Flutter/stream_chat/setup.mdx b/docusaurus/docs/Flutter/stream_chat/setup.mdx new file mode 100644 index 00000000..b4141769 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/setup.mdx @@ -0,0 +1,6 @@ +--- +id: setup +sidebar_position: 2 +title: Setup +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/user_types.mdx b/docusaurus/docs/Flutter/stream_chat/user_types.mdx new file mode 100644 index 00000000..7a5721bc --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/user_types.mdx @@ -0,0 +1,6 @@ +--- +id: user_types +sidebar_position: 3 +title: User Types +--- + From a5776fcf025d6fb6fa726cc22fc8895f8d908e7b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 11 Jun 2021 13:07:03 +0200 Subject: [PATCH 355/599] fix pin message flow --- packages/stream_chat_flutter/lib/src/message_actions_modal.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bdee86a0..13900c21 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -366,13 +366,13 @@ class _MessageActionsModalState extends State { void _togglePin() async { final channel = StreamChannel.of(context).channel; + Navigator.pop(context); try { if (!widget.message.pinned) { await channel.pinMessage(widget.message); } else { await channel.unpinMessage(widget.message); } - Navigator.pop(context); } catch (e) { _showErrorAlert(); } From eb20b31c825ae430f1cdf367f40fe40e635a5cfc Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 11 Jun 2021 13:12:52 +0200 Subject: [PATCH 356/599] feat: add flag for floating date divider --- .../lib/src/message_list_view.dart | 84 ++++++++++--------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 82b64c27..d4885c86 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -161,6 +161,7 @@ class MessageListView extends StatefulWidget { this.pinPermissions = const [], this.textBuilder, this.usernameBuilder, + this.showFloatingDateDivider = true, }) : super(key: key); /// Function used to build a custom message widget @@ -227,6 +228,9 @@ class MessageListView extends StatefulWidget { /// Flag for showing tile on header final bool showConnectionStateTile; + /// Flag for showing the floating date divider + final bool showFloatingDateDivider; + /// Function called when messages are fetched final Widget Function(BuildContext, List)? messageListBuilder; @@ -567,49 +571,51 @@ class _MessageListViewState extends State { }, ), if (widget.showScrollToBottom) _buildScrollToBottom(), - Positioned( - top: 20, - child: BetterStreamBuilder>( - initialData: _itemPositionListener.itemPositions.value, - stream: _itemPositionStream, - comparator: (a, b) { - if (a == null) { - return false; - } - final aTop = _getTopElement(a)?.index; - final bTop = _getTopElement(b)?.index; - return aTop == bTop; - }, - builder: (context, values) { - final items = _itemPositionListener.itemPositions.value; - if (items.isEmpty || messages.isEmpty) { - return const SizedBox(); - } - - var index = _getTopElement(values)?.index; - - if (index == null || index > messages.length) { - return const SizedBox(); - } - - if (index == messages.length) { - index = max(index - 1, 0); - } - - return widget.dateDividerBuilder != null - ? widget.dateDividerBuilder!( - messages[index].createdAt.toLocal(), - ) - : DateDivider( - dateTime: messages[index].createdAt.toLocal(), - ); - }, - ), - ), + if (widget.showFloatingDateDivider) _buildFloatingDateDivider(), ], ); } + Positioned _buildFloatingDateDivider() => Positioned( + top: 20, + child: BetterStreamBuilder>( + initialData: _itemPositionListener.itemPositions.value, + stream: _itemPositionStream, + comparator: (a, b) { + if (a == null) { + return false; + } + final aTop = _getTopElement(a)?.index; + final bTop = _getTopElement(b)?.index; + return aTop == bTop; + }, + builder: (context, values) { + final items = _itemPositionListener.itemPositions.value; + if (items.isEmpty || messages.isEmpty) { + return const SizedBox(); + } + + var index = _getTopElement(values)?.index; + + if (index == null || index > messages.length) { + return const SizedBox(); + } + + if (index == messages.length) { + index = max(index - 1, 0); + } + + return widget.dateDividerBuilder != null + ? widget.dateDividerBuilder!( + messages[index].createdAt.toLocal(), + ) + : DateDivider( + dateTime: messages[index].createdAt.toLocal(), + ); + }, + ), + ); + Future _paginateData( StreamChannelState? channel, QueryDirection direction) => _messageListController.paginateData!(direction: direction); From 850145f79166658dbced451f5ba2e284bec7ee5b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 11 Jun 2021 16:37:30 +0200 Subject: [PATCH 357/599] add thread separator builder --- .../lib/src/message_list_view.dart | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index d4885c86..aae9d193 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -162,6 +162,7 @@ class MessageListView extends StatefulWidget { this.textBuilder, this.usernameBuilder, this.showFloatingDateDivider = true, + this.threadSeparatorBuilder, }) : super(key: key); /// Function used to build a custom message widget @@ -277,6 +278,9 @@ class MessageListView extends StatefulWidget { /// A List of user types that have permission to pin messages final List pinPermissions; + /// Builder used to build the thread separator in case it's a thread view + final WidgetBuilder? threadSeparatorBuilder; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -450,22 +454,7 @@ class _MessageListViewState extends State { if (i == messages.length) return const Offstage(); if (i == 0) return const SizedBox(height: 30); if (i == messages.length + 1) { - final replyCount = widget.parentMessage!.replyCount; - return Container( - decoration: BoxDecoration( - gradient: _streamTheme.colorTheme.bgGradient, - ), - child: Padding( - padding: const EdgeInsets.all(8), - child: Text( - // ignore: lines_longer_than_80_chars - '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', - textAlign: TextAlign.center, - style: _streamTheme - .channelTheme.channelHeaderTheme.subtitle, - ), - ), - ); + return _buildThreadSeparator(); } final message = messages[i]; @@ -576,6 +565,28 @@ class _MessageListViewState extends State { ); } + Widget _buildThreadSeparator() { + if (widget.threadSeparatorBuilder != null) { + return widget.threadSeparatorBuilder!.call(context); + } + + final replyCount = widget.parentMessage!.replyCount; + return DecoratedBox( + decoration: BoxDecoration( + gradient: _streamTheme.colorTheme.bgGradient, + ), + child: Padding( + padding: const EdgeInsets.all(8), + child: Text( + // ignore: lines_longer_than_80_chars + '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', + textAlign: TextAlign.center, + style: _streamTheme.channelTheme.channelHeaderTheme.subtitle, + ), + ), + ); + } + Positioned _buildFloatingDateDivider() => Positioned( top: 20, child: BetterStreamBuilder>( From cf419f3a37ebe4becb5c896cf3d8984fc67c493b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 14 Jun 2021 10:35:19 +0200 Subject: [PATCH 358/599] update changelogs and pubspecs --- packages/stream_chat/CHANGELOG.md | 6 ++++++ packages/stream_chat/pubspec.yaml | 2 +- packages/stream_chat_flutter/CHANGELOG.md | 7 +++++++ packages/stream_chat_flutter/pubspec.yaml | 4 ++-- packages/stream_chat_flutter_core/CHANGELOG.md | 7 +++++++ packages/stream_chat_flutter_core/pubspec.yaml | 4 ++-- packages/stream_chat_persistence/CHANGELOG.md | 5 +++++ packages/stream_chat_persistence/pubspec.yaml | 4 ++-- 8 files changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 75fecc34..9a3910bb 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.0-nullsafety.5 + +- Minor fixes +- Performance improvements +- Fixed `skip_push` in `client.sendMessage` + ## 2.0.0-nullsafety.2 - Added new `Filter.raw` constructor diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index d473beea..5f72d253 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat homepage: https://getstream.io/ description: The official Dart client for Stream Chat, a service for building chat applications. -version: 2.0.0-nullsafety.2 +version: 2.0.0-nullsafety.5 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index e0b60fad..509ac26a 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.0.0-nullsafety.5 + +- Minor fixes and improvements +- Updated `stream_chat_core` dependency +- Performance improvements +- Added pinMessage ui support +- Added `MessageListView.threadSeparatorBuilder` property ## 2.0.0-nullsafety.4 - Minor fixes and improvements diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 2aa5ab4a..b8241bba 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.4 +version: 2.0.0-nullsafety.5 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -36,7 +36,7 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 shimmer: ^2.0.0 - stream_chat_flutter_core: ^2.0.0-nullsafety.3 + stream_chat_flutter_core: ^2.0.0-nullsafety.5 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 1930e36a..26cfa6c3 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.0.0-nullsafety.5 + +* Update llc dependency +* Minor fixes and improvements +* Performance improvements +* Monitor connection using `connectivity_plus` package + ## 2.0.0-nullsafety.3 * Update llc dependency diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 4292772b..f9768bdd 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.3 +version: 2.0.0-nullsafety.5 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -16,7 +16,7 @@ dependencies: sdk: flutter meta: ^1.3.0 rxdart: ^0.27.0 - stream_chat: ^2.0.0-nullsafety.2 + stream_chat: ^2.0.0-nullsafety.5 dev_dependencies: fake_async: ^1.2.0 diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index c35459ca..aa7c7513 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0-nullsafety.5 + +* Update llc dependency +* Minor fixes and improvements + ## 2.0.0-nullsafety.2 * Update llc dependency diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 661f55bb..749804de 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_persistence homepage: https://github.com/GetStream/stream-chat-flutter description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.2 +version: 2.0.0-nullsafety.5 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -18,7 +18,7 @@ dependencies: path: ^1.8.0 path_provider: ^2.0.1 sqlite3_flutter_libs: ^0.4.2 - stream_chat: ^2.0.0-nullsafety.2 + stream_chat: ^2.0.0-nullsafety.5 dev_dependencies: build_runner: ^2.0.1 From 2fda85eb4aeb3fef98fdef11ee9032ce974bf3ad Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 14 Jun 2021 10:44:01 +0200 Subject: [PATCH 359/599] upgrade deps --- packages/stream_chat/pubspec.yaml | 2 +- packages/stream_chat_flutter/pubspec.yaml | 6 +++--- packages/stream_chat_persistence/pubspec.yaml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 5f72d253..b13838aa 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -28,4 +28,4 @@ dev_dependencies: freezed: ^0.14.1+3 json_serializable: ^4.1.0 mocktail: ^0.1.1 - test: ^1.16.8 + test: ^1.17.7 diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index b8241bba..61aad8df 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: cached_network_image: ^3.0.0 characters: ^1.1.0 - chewie: ^1.0.0 + chewie: ^1.2.0 collection: ^1.15.0 dio: ^4.0.0 ezanimation: ^0.5.0 @@ -25,7 +25,7 @@ dependencies: flutter_svg: ^0.22.0 http_parser: ^4.0.0 image_gallery_saver: ^1.6.9 - image_picker: ^0.7.4 + image_picker: ^0.8.0 jiffy: ^4.1.0 lottie: ^1.0.1 meta: ^1.3.0 @@ -41,7 +41,7 @@ dependencies: synchronized: ^3.0.0 url_launcher: ^6.0.3 video_compress: ^3.0.0 - video_player: ^2.1.1 + video_player: ^2.1.0 video_thumbnail: ^0.3.3 visibility_detector: ^0.2.0 diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 749804de..9d141198 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: mutex: ^3.0.0 path: ^1.8.0 path_provider: ^2.0.1 - sqlite3_flutter_libs: ^0.4.2 + sqlite3_flutter_libs: ^0.5.0 stream_chat: ^2.0.0-nullsafety.5 dev_dependencies: @@ -25,4 +25,4 @@ dev_dependencies: mocktail: ^0.1.1 moor_generator: ^4.2.1 pedantic: ^1.11.0 - test: ^1.16.8 + test: ^1.17.7 From 364c43e9a20dd2cf0137819db8c6bbfc1ea4702c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 14 Jun 2021 10:59:09 +0200 Subject: [PATCH 360/599] update version --- packages/stream_chat/lib/version.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index 6c68818a..da7d8aa3 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -3,4 +3,4 @@ import 'package:stream_chat/src/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header // ignore: constant_identifier_names -const PACKAGE_VERSION = '2.0.0-nullsafety.2'; +const PACKAGE_VERSION = '2.0.0-nullsafety.5'; From dd537b6734095b37e63ddd8baea0466ed66a4b60 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 14 Jun 2021 11:49:41 +0200 Subject: [PATCH 361/599] update changelog --- packages/stream_chat/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 9a3910bb..b6c5cecf 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -3,6 +3,7 @@ - Minor fixes - Performance improvements - Fixed `skip_push` in `client.sendMessage` +- Added partial message update method ## 2.0.0-nullsafety.2 From 37507c55ebe87eed9cd867f70ef8edb57c4c23cf Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 14 Jun 2021 15:53:10 +0530 Subject: [PATCH 362/599] feat: Added files for stream_chat docs --- .../docs/Flutter/stream_chat/channel_members.mdx | 6 ++++++ .../docs/Flutter/stream_chat/creating_channels.mdx | 6 ++++++ .../stream_chat/deleting_and_hiding_channel.mdx | 6 ++++++ docusaurus/docs/Flutter/stream_chat/events.mdx | 6 ++++++ docusaurus/docs/Flutter/stream_chat/introduction.mdx | 12 +++++++++--- docusaurus/docs/Flutter/stream_chat/invites.mdx | 6 ++++++ docusaurus/docs/Flutter/stream_chat/messages.mdx | 6 ++++++ .../docs/Flutter/stream_chat/moderation_tools.mdx | 6 ++++++ .../docs/Flutter/stream_chat/muting_channels.mdx | 6 ++++++ .../stream_chat/paginating_channel_messages.mdx | 6 ++++++ .../docs/Flutter/stream_chat/pinned_messages.mdx | 6 ++++++ .../docs/Flutter/stream_chat/querying_channels.mdx | 6 ++++++ .../docs/Flutter/stream_chat/querying_users.mdx | 6 ++++++ docusaurus/docs/Flutter/stream_chat/reactions.mdx | 6 ++++++ .../docs/Flutter/stream_chat/searching_messages.mdx | 6 ++++++ .../docs/Flutter/stream_chat/silent_messages.mdx | 6 ++++++ docusaurus/docs/Flutter/stream_chat/slow_mode.mdx | 6 ++++++ .../docs/Flutter/stream_chat/threads_and_replies.mdx | 6 ++++++ .../docs/Flutter/stream_chat/typing_indicators.mdx | 6 ++++++ .../docs/Flutter/stream_chat/updating_channels.mdx | 6 ++++++ .../docs/Flutter/stream_chat/uploading_files.mdx | 6 ++++++ .../docs/Flutter/stream_chat/user_presence.mdx | 6 ++++++ .../docs/Flutter/stream_chat/watching_channels.mdx | 6 ++++++ 23 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 docusaurus/docs/Flutter/stream_chat/channel_members.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/creating_channels.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/events.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/invites.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/messages.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/muting_channels.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/paginating_channel_messages.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/querying_channels.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/querying_users.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/reactions.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/searching_messages.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/silent_messages.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/slow_mode.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/updating_channels.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/uploading_files.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/user_presence.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/watching_channels.mdx diff --git a/docusaurus/docs/Flutter/stream_chat/channel_members.mdx b/docusaurus/docs/Flutter/stream_chat/channel_members.mdx new file mode 100644 index 00000000..3ac1da84 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/channel_members.mdx @@ -0,0 +1,6 @@ +--- +id: channel_members +sidebar_position: 10 +title: Channel Members +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx b/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx new file mode 100644 index 00000000..b59dbabd --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx @@ -0,0 +1,6 @@ +--- +id: creating_channels +sidebar_position: 6 +title: Creating Channels +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx b/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx new file mode 100644 index 00000000..458a06cc --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx @@ -0,0 +1,6 @@ +--- +id: deleting_and_hiding_channel +sidebar_position: 13 +title: Deleting And Hiding A Channel +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/events.mdx b/docusaurus/docs/Flutter/stream_chat/events.mdx new file mode 100644 index 00000000..e171b295 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/events.mdx @@ -0,0 +1,6 @@ +--- +id: events +sidebar_position: 25 +title: Events +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/introduction.mdx b/docusaurus/docs/Flutter/stream_chat/introduction.mdx index dbc6087f..628c8a1b 100644 --- a/docusaurus/docs/Flutter/stream_chat/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat/introduction.mdx @@ -9,8 +9,14 @@ title: Introduction The official Dart client for Stream Chat, a service for building chat applications. This library can be used on any Dart project and on both mobile and web apps with Flutter. -The Low Level Client (stream_chat) provides the basic bindings for integrating Stream Chat into -your Flutter app with the ability to connect users, listening to events, send and receive messages, -channel CRUD operations, etc. +The Low Level Client ([stream_chat]('https://pub.dev/packages/stream_chat')) provides the basic +bindings for integrating Stream Chat into your Flutter app with the ability to connect users, +listening to events, send and receive messages, channel CRUD operations, etc. +This allows you to control and customise your interface completely while using the +Stream Chat backend. +Check out our Core package ([stream_chat_flutter_core]('https://pub.dev/packages/stream_chat_flutter_core')) +for easier implementations of common uses such as retrieving channels, +retrieving channel message lists, seraching for messages, etc. +This section goes into detail on using the LLC and various things you can achieve using it. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/invites.mdx b/docusaurus/docs/Flutter/stream_chat/invites.mdx new file mode 100644 index 00000000..7b3532fe --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/invites.mdx @@ -0,0 +1,6 @@ +--- +id: invites +sidebar_position: 11 +title: Invites +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/messages.mdx b/docusaurus/docs/Flutter/stream_chat/messages.mdx new file mode 100644 index 00000000..90aa8a00 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/messages.mdx @@ -0,0 +1,6 @@ +--- +id: messages +sidebar_position: 16 +title: Messages +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx b/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx new file mode 100644 index 00000000..7eb14af2 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx @@ -0,0 +1,6 @@ +--- +id: moderation_tools +sidebar_position: 23 +title: Moderation Tools +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx b/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx new file mode 100644 index 00000000..a6acee75 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx @@ -0,0 +1,6 @@ +--- +id: muting_channels +sidebar_position: 12 +title: Muting Channels +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/paginating_channel_messages.mdx b/docusaurus/docs/Flutter/stream_chat/paginating_channel_messages.mdx new file mode 100644 index 00000000..25311929 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/paginating_channel_messages.mdx @@ -0,0 +1,6 @@ +--- +id: paginating_channel_messages +sidebar_position: 15 +title: Paginating Channel Messages +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx b/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx new file mode 100644 index 00000000..fba0ca07 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx @@ -0,0 +1,6 @@ +--- +id: pinned_messages +sidebar_position: 22 +title: Pinned Messages +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx b/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx new file mode 100644 index 00000000..55046907 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx @@ -0,0 +1,6 @@ +--- +id: querying_channels +sidebar_position: 7 +title: Querying Channels +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/querying_users.mdx b/docusaurus/docs/Flutter/stream_chat/querying_users.mdx new file mode 100644 index 00000000..d605091e --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/querying_users.mdx @@ -0,0 +1,6 @@ +--- +id: querying_users +sidebar_position: 4 +title: Querying Users +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/reactions.mdx b/docusaurus/docs/Flutter/stream_chat/reactions.mdx new file mode 100644 index 00000000..a94cbfa3 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/reactions.mdx @@ -0,0 +1,6 @@ +--- +id: reactions +sidebar_position: 18 +title: Reactions +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx b/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx new file mode 100644 index 00000000..7ff1188d --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx @@ -0,0 +1,6 @@ +--- +id: searching_messages +sidebar_position: 20 +title: Searching Messages +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx b/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx new file mode 100644 index 00000000..c2bb2172 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx @@ -0,0 +1,6 @@ +--- +id: silent_messages +sidebar_position: 21 +title: Silent Messages +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx b/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx new file mode 100644 index 00000000..9cd50efd --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx @@ -0,0 +1,6 @@ +--- +id: slow_mode +sidebar_position: 14 +title: Slow Mode +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx b/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx new file mode 100644 index 00000000..b3dac44c --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx @@ -0,0 +1,6 @@ +--- +id: threads_and_replies +sidebar_position: 19 +title: Threads And Replies +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx b/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx new file mode 100644 index 00000000..908854db --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx @@ -0,0 +1,6 @@ +--- +id: typing_indicators +sidebar_position: 24 +title: Typing Indicators +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx b/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx new file mode 100644 index 00000000..75e693eb --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx @@ -0,0 +1,6 @@ +--- +id: updating_channels +sidebar_position: 9 +title: Updating Channels +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx b/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx new file mode 100644 index 00000000..9f7cf6ee --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx @@ -0,0 +1,6 @@ +--- +id: uploading_files +sidebar_position: 17 +title: Uploading Files +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/user_presence.mdx b/docusaurus/docs/Flutter/stream_chat/user_presence.mdx new file mode 100644 index 00000000..02e5eeb4 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/user_presence.mdx @@ -0,0 +1,6 @@ +--- +id: user_presence +sidebar_position: 5 +title: User Presence +--- + diff --git a/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx b/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx new file mode 100644 index 00000000..f35cc088 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx @@ -0,0 +1,6 @@ +--- +id: watching_channels +sidebar_position: 8 +title: Watching Channels +--- + From 2a58ca535dd7d8d04e26b4fd8edb5cbdde82f9bf Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 14 Jun 2021 16:56:36 +0530 Subject: [PATCH 363/599] Add retry_queue_test.dart Signed-off-by: Sahil Kumar --- .../lib/src/client/retry_queue.dart | 3 + .../test/src/api/retry_queue_test.dart | 74 +++++++++++++++++++ packages/stream_chat/test/src/mocks.dart | 21 ++++++ 3 files changed, 98 insertions(+) create mode 100644 packages/stream_chat/test/src/api/retry_queue_test.dart diff --git a/packages/stream_chat/lib/src/client/retry_queue.dart b/packages/stream_chat/lib/src/client/retry_queue.dart index 008270e5..4c44433f 100644 --- a/packages/stream_chat/lib/src/client/retry_queue.dart +++ b/packages/stream_chat/lib/src/client/retry_queue.dart @@ -177,6 +177,9 @@ class RetryQueue { } } + /// Whether our [_messageQueue] has messages or not + bool get hasMessages => _messageQueue.isNotEmpty; + /// Call this method to dispose this object void dispose() { _messageQueue.clear(); diff --git a/packages/stream_chat/test/src/api/retry_queue_test.dart b/packages/stream_chat/test/src/api/retry_queue_test.dart new file mode 100644 index 00000000..084b82ec --- /dev/null +++ b/packages/stream_chat/test/src/api/retry_queue_test.dart @@ -0,0 +1,74 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/client/retry_policy.dart'; +import 'package:stream_chat/src/client/retry_queue.dart'; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/event_type.dart'; +import 'package:test/scaffolding.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + late final channel = MockRetryQueueChannel(); + late final logger = MockLogger(); + late RetryQueue retryQueue; + + setUpAll(() { + final retryPolicy = RetryPolicy( + shouldRetry: (_, attempt, __) => attempt < 5, + retryTimeout: (_, attempt, __) => Duration(seconds: attempt), + ); + when(() => channel.client.retryPolicy).thenReturn(retryPolicy); + + when(() => channel.client.on(EventType.connectionRecovered)).thenAnswer( + (_) => Stream.value(Event( + type: EventType.connectionRecovered, + online: false, + )), + ); + + when(() => channel.on(any(), any(), any(), any())).thenAnswer( + (_) => Stream.value( + Event(type: EventType.any), + ), + ); + }); + + setUp(() { + retryQueue = RetryQueue(channel: channel, logger: logger); + }); + + tearDown(() { + retryQueue.dispose(); + }); + + group('`.add`', () { + test('should return if message list is empty', () { + expect(() => retryQueue.add([]), returnsNormally); + verifyNever(() => logger.info(any())); + }); + + test('should return if queue already contains the message', () { + final message = Message( + id: 'test-message-id', + text: 'Sample message test', + ); + retryQueue.add([message]); + expect(() => retryQueue.add([message]), returnsNormally); + // Called only for the first message + verify(() => logger.info('Adding 1 messages')).called(1); + }); + + test('`.add` should add failed request to the queue', () async { + final message = Message( + id: 'test-message-id', + text: 'Sample message test', + ); + retryQueue.add([message]); + expect(retryQueue.hasMessages, isTrue); + }); + }); + + // TODO: Add more tests once macbook is fixed :( +} diff --git a/packages/stream_chat/test/src/mocks.dart b/packages/stream_chat/test/src/mocks.dart index 9c20be7c..31e768e8 100644 --- a/packages/stream_chat/test/src/mocks.dart +++ b/packages/stream_chat/test/src/mocks.dart @@ -1,7 +1,9 @@ import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/client/channel.dart'; import 'package:stream_chat/src/client/client.dart'; +import 'package:stream_chat/src/client/retry_policy.dart'; import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; import 'package:stream_chat/src/core/api/channel_api.dart'; import 'package:stream_chat/src/core/api/device_api.dart'; @@ -84,3 +86,22 @@ class MockStreamChatClientWithPersistence extends Mock } class MockChannelConfig extends Mock implements ChannelConfig {} + +class MockRetryQueueChannel extends Mock implements Channel { + final channelId = 'test-channel-id'; + final channelType = 'test-channel-type'; + + @override + String? get id => channelId; + + @override + String get type => channelType; + + @override + String? get cid => '$channelType:$channelId'; + + StreamChatClient? _client; + + @override + StreamChatClient get client => _client ??= MockStreamChatClient(); +} From b0cc64ef3e1655bca7a7cabfa17eabe9eb639145 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 14 Jun 2021 17:47:14 +0530 Subject: [PATCH 364/599] remove deprecated methods Signed-off-by: Sahil Kumar --- .../stream_chat/lib/src/client/client.dart | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 87e471e6..9abfa94c 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -229,38 +229,17 @@ class StreamChatClient { logger.info('logger setup'); } - /// Set the current user, this triggers a connection to the API. - /// Set the current user, this triggers a connection to the API. - /// It returns a [Future] that resolves when the connection is setup. - @Deprecated('Use `connectUser` instead. Will be removed in Future releases') - Future setUser(User user, String token) => connectUser(user, token); - /// Connects the current user, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. Future connectUser(User user, String token) => _connectUser(user, token: Token.fromRawValue(token)); - /// Set the current user using the [tokenProvider] to fetch the token. - /// It returns a [Future] that resolves when the connection is setup. - @Deprecated( - 'Use `connectUserWithProvider` instead. Will be removed in Future releases', - ) - Future setUserWithProvider(User user, TokenProvider tokenProvider) => - connectUserWithProvider(user, tokenProvider); - /// Connects the current user using the [tokenProvider] to fetch the token. /// It returns a [Future] that resolves when the connection is setup. Future connectUserWithProvider( User user, TokenProvider tokenProvider) => _connectUser(user, provider: tokenProvider); - /// Set the current user with an anonymous id, this triggers a connection to - /// the API. It returns a [Future] that resolves when the connection is setup. - @Deprecated( - 'Use `connectAnonymousUser` instead. Will be removed in Future releases', - ) - Future setAnonymousUser() => connectAnonymousUser(); - /// Connects the current user with an anonymous id, this triggers a connection /// to the API. It returns a [Future] that resolves when the connection is /// setup. @@ -270,13 +249,6 @@ class StreamChatClient { return _connectUser(user, token: token); } - /// Set the current user as guest, this triggers a connection to the API. - /// It returns a [Future] that resolves when the connection is setup. - @Deprecated( - 'Use `connectGuestUser` instead. Will be removed in Future releases', - ) - Future setGuestUser(User user) => connectGuestUser(user); - /// Connects the current user as guest, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. Future connectGuestUser(User user) async { From d47684131231a2ad57b97abe932696e652380272 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 14 Jun 2021 16:45:39 +0200 Subject: [PATCH 365/599] fix thread reply with attachment --- packages/stream_chat/lib/src/api/channel.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 1e7f824e..8a099aff 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -256,7 +256,10 @@ class Channel { String messageId, Iterable attachmentIds, ) { - final message = state!.messages.firstWhereOrNull( + final message = [ + ...state!.messages, + ...state!.threads.values.expand((element) => element), + ].firstWhereOrNull( (it) => it.id == messageId, ); From 7bc55a018254d32b87a609ce044be9b4c9a94a8c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 14 Jun 2021 17:03:40 +0200 Subject: [PATCH 366/599] Update packages/stream_chat/lib/src/api/channel.dart Co-authored-by: Sahil Kumar --- packages/stream_chat/lib/src/api/channel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 8a099aff..aeda98e7 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -258,7 +258,7 @@ class Channel { ) { final message = [ ...state!.messages, - ...state!.threads.values.expand((element) => element), + ...state!.threads.values.expand((messages) => messages), ].firstWhereOrNull( (it) => it.id == messageId, ); From 3a44117628a93a0f244f2ed69dd91b1a3c51e14f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 14 Jun 2021 17:20:52 +0200 Subject: [PATCH 367/599] let users use custom reaction builders --- packages/stream_chat_flutter/CHANGELOG.md | 1 + .../lib/src/message_actions_modal.dart | 12 +++- .../lib/src/message_reactions_modal.dart | 12 +++- .../lib/src/reaction_bubble.dart | 15 +++-- .../lib/src/reaction_icon.dart | 10 +++- .../lib/src/reaction_picker.dart | 10 ++-- .../lib/src/stream_chat_theme.dart | 54 +++++++++++++++-- .../lib/src/stream_svg_icon.dart | 60 +++++++++++++++++++ 8 files changed, 148 insertions(+), 26 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 509ac26a..39247b13 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -5,6 +5,7 @@ - Performance improvements - Added pinMessage ui support - Added `MessageListView.threadSeparatorBuilder` property + ## 2.0.0-nullsafety.4 - Minor fixes and improvements 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 13900c21..f763585b 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -148,6 +148,10 @@ class _MessageActionsModalState extends State { final streamChatThemeData = StreamChatTheme.of(context); + final numberOfReactions = streamChatThemeData.reactionIcons.length; + final shiftFactor = + numberOfReactions < 5 ? (5 - numberOfReactions) * 0.1 : 0.0; + final child = Center( child: SingleChildScrollView( child: Padding( @@ -162,8 +166,12 @@ class _MessageActionsModalState extends State { Align( alignment: Alignment( user?.id == widget.message.user?.id - ? (divFactor >= 1.0 ? -0.2 : (1.2 - divFactor)) - : (divFactor >= 1.0 ? 0.2 : -(1.2 - divFactor)), + ? (divFactor >= 1.0 + ? -0.2 - shiftFactor + : (1.2 - divFactor)) + : (divFactor >= 1.0 + ? 0.2 + shiftFactor + : -(1.2 - divFactor)), 0), child: ReactionPicker( message: widget.message, 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 e9369e99..842fe79d 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -80,6 +80,10 @@ class MessageReactionsModal extends StatelessWidget { final hasFileAttachment = message.attachments.any((it) => it.type == 'file') == true; + final numberOfReactions = StreamChatTheme.of(context).reactionIcons.length; + final shiftFactor = + numberOfReactions < 5 ? (5 - numberOfReactions) * 0.1 : 0.0; + final child = Center( child: SingleChildScrollView( child: Padding( @@ -93,8 +97,12 @@ class MessageReactionsModal extends StatelessWidget { Align( alignment: Alignment( user!.id == message.user!.id - ? (divFactor >= 1.0 ? -0.2 : (1.2 - divFactor)) - : (divFactor >= 1.0 ? 0.2 : -(1.2 - divFactor)), + ? (divFactor >= 1.0 + ? -0.2 - shiftFactor + : (1.2 - divFactor)) + : (divFactor >= 1.0 + ? 0.2 + shiftFactor + : -(1.2 - divFactor)), 0), child: ReactionPicker( message: message, diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index ce75445b..a3b2052f 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter/src/reaction_icon.dart'; -import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Creates reaction bubble widget for displaying over messages @@ -130,13 +129,13 @@ class ReactionBubble extends StatelessWidget { horizontal: 4, ), child: reactionIcon != null - ? StreamSvgIcon( - assetName: reactionIcon.assetName, - width: 16, - height: 16, - color: (!highlightOwnReactions || reaction.user?.id == userId) - ? chatThemeData.colorTheme.accentBlue - : chatThemeData.colorTheme.black.withOpacity(.5), + ? ConstrainedBox( + constraints: BoxConstraints.tight(const Size.square(16)), + child: reactionIcon.builder( + context, + !highlightOwnReactions || reaction.user?.id == userId, + 16, + ), ) : Icon( Icons.help_outline_rounded, diff --git a/packages/stream_chat_flutter/lib/src/reaction_icon.dart b/packages/stream_chat_flutter/lib/src/reaction_icon.dart index 4466e154..e675128b 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_icon.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_icon.dart @@ -1,14 +1,20 @@ +import 'package:flutter/material.dart'; + /// Reaction icon data class ReactionIcon { /// Constructor for creating [ReactionIcon] ReactionIcon({ required this.type, - required this.assetName, + required this.builder, }); /// Type of reaction final String type; /// Asset to display for reaction - final String assetName; + final Widget Function( + BuildContext, + bool highlighted, + double size, + ) builder; } diff --git a/packages/stream_chat_flutter/lib/src/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reaction_picker.dart index 2e47b933..5101cbb7 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_picker.dart @@ -1,7 +1,6 @@ import 'package:ezanimation/ezanimation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/extension.dart'; -import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png) @@ -69,11 +68,10 @@ class _ReactionPickerState extends State -1; final index = reactionIcons.indexOf(reactionIcon); - final child = StreamSvgIcon( - assetName: reactionIcon.assetName, - color: ownReactionIndex != -1 - ? chatThemeData.colorTheme.accentBlue - : Theme.of(context).iconTheme.color!.withOpacity(.5), + final child = reactionIcon.builder( + context, + ownReactionIndex != -1, + 24, ); return ConstrainedBox( 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 25c2ca32..981d8131 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -217,10 +217,11 @@ class StreamChatThemeData { TextTheme textTheme, ) { final accentColor = colorTheme.accentBlue; + final iconTheme = IconThemeData(color: colorTheme.black.withOpacity(.5)); return StreamChatThemeData.raw( textTheme: textTheme, colorTheme: colorTheme, - primaryIconTheme: IconThemeData(color: colorTheme.black.withOpacity(.5)), + primaryIconTheme: iconTheme, defaultChannelImage: (context, channel) => const SizedBox(), defaultUserImage: (context, user) => Center( child: CachedNetworkImage( @@ -342,29 +343,70 @@ class StreamChatThemeData { reactionIcons: [ ReactionIcon( type: 'love', - assetName: 'Icon_love_reaction.svg', + builder: (context, highlighted, size) => StreamSvgIcon.loveReaction( + color: highlighted + ? colorTheme.accentBlue + : iconTheme.color!.withOpacity(.5), + size: size, + ), ), ReactionIcon( type: 'like', - assetName: 'Icon_thumbs_up_reaction.svg', + builder: (context, highlighted, size) => + StreamSvgIcon.thumbsUpReaction( + color: highlighted + ? colorTheme.accentBlue + : iconTheme.color!.withOpacity(.5), + size: size, + ), ), ReactionIcon( type: 'sad', - assetName: 'Icon_thumbs_down_reaction.svg', + builder: (context, highlighted, size) => + StreamSvgIcon.thumbsDownReaction( + color: highlighted + ? colorTheme.accentBlue + : iconTheme.color!.withOpacity(.5), + size: size, + ), ), ReactionIcon( type: 'haha', - assetName: 'Icon_LOL_reaction.svg', + builder: (context, highlighted, size) => StreamSvgIcon.lolReaction( + color: highlighted + ? colorTheme.accentBlue + : iconTheme.color!.withOpacity(.5), + size: size, + ), ), ReactionIcon( type: 'wow', - assetName: 'Icon_wut_reaction.svg', + builder: (context, highlighted, size) => StreamSvgIcon.wutReaction( + color: highlighted + ? colorTheme.accentBlue + : iconTheme.color!.withOpacity(.5), + size: size, + ), ), ], ); } } +class ReactionTheme { + final double singleSize; + final double size; + final Color ownReactionColor; + final Color otherReactionColor; + + const ReactionTheme({ + required this.singleSize, + required this.size, + required this.ownReactionColor, + required this.otherReactionColor, + }); +} + /// Class for holding text theme class TextTheme { /// Initialise light text theme diff --git a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart index ae344a89..fb5017e1 100644 --- a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart +++ b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart @@ -49,6 +49,66 @@ class StreamSvgIcon extends StatelessWidget { height: size, ); + /// [StreamSvgIcon] type + factory StreamSvgIcon.loveReaction({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_love_reaction.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.thumbsUpReaction({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_thumbs_up_reaction.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.thumbsDownReaction({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_thumbs_down_reaction.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.lolReaction({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_LOL_reaction.svg', + color: color, + width: size, + height: size, + ); + + /// [StreamSvgIcon] type + factory StreamSvgIcon.wutReaction({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_wut_reaction.svg', + color: color, + width: size, + height: size, + ); + /// [StreamSvgIcon] type factory StreamSvgIcon.smile({ double? size, From b31754d8d10034c8b6fd3030464954669ce3a12b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 14 Jun 2021 17:23:39 +0200 Subject: [PATCH 368/599] little fix --- .../lib/src/stream_chat_theme.dart | 91 +++++++++---------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index 981d8131..91ed2537 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -343,70 +343,69 @@ class StreamChatThemeData { reactionIcons: [ ReactionIcon( type: 'love', - builder: (context, highlighted, size) => StreamSvgIcon.loveReaction( - color: highlighted - ? colorTheme.accentBlue - : iconTheme.color!.withOpacity(.5), - size: size, - ), + builder: (context, highlighted, size) { + final theme = StreamChatTheme.of(context); + return StreamSvgIcon.loveReaction( + color: highlighted + ? theme.colorTheme.accentBlue + : theme.primaryIconTheme.color!.withOpacity(.5), + size: size, + ); + }, ), ReactionIcon( type: 'like', - builder: (context, highlighted, size) => - StreamSvgIcon.thumbsUpReaction( - color: highlighted - ? colorTheme.accentBlue - : iconTheme.color!.withOpacity(.5), - size: size, - ), + builder: (context, highlighted, size) { + final theme = StreamChatTheme.of(context); + return StreamSvgIcon.thumbsUpReaction( + color: highlighted + ? theme.colorTheme.accentBlue + : theme.primaryIconTheme.color!.withOpacity(.5), + size: size, + ); + }, ), ReactionIcon( type: 'sad', - builder: (context, highlighted, size) => - StreamSvgIcon.thumbsDownReaction( - color: highlighted - ? colorTheme.accentBlue - : iconTheme.color!.withOpacity(.5), - size: size, - ), + builder: (context, highlighted, size) { + final theme = StreamChatTheme.of(context); + return StreamSvgIcon.thumbsDownReaction( + color: highlighted + ? theme.colorTheme.accentBlue + : theme.primaryIconTheme.color!.withOpacity(.5), + size: size, + ); + }, ), ReactionIcon( type: 'haha', - builder: (context, highlighted, size) => StreamSvgIcon.lolReaction( - color: highlighted - ? colorTheme.accentBlue - : iconTheme.color!.withOpacity(.5), - size: size, - ), + builder: (context, highlighted, size) { + final theme = StreamChatTheme.of(context); + return StreamSvgIcon.lolReaction( + color: highlighted + ? theme.colorTheme.accentBlue + : theme.primaryIconTheme.color!.withOpacity(.5), + size: size, + ); + }, ), ReactionIcon( type: 'wow', - builder: (context, highlighted, size) => StreamSvgIcon.wutReaction( - color: highlighted - ? colorTheme.accentBlue - : iconTheme.color!.withOpacity(.5), - size: size, - ), + builder: (context, highlighted, size) { + final theme = StreamChatTheme.of(context); + return StreamSvgIcon.wutReaction( + color: highlighted + ? theme.colorTheme.accentBlue + : theme.primaryIconTheme.color!.withOpacity(.5), + size: size, + ); + }, ), ], ); } } -class ReactionTheme { - final double singleSize; - final double size; - final Color ownReactionColor; - final Color otherReactionColor; - - const ReactionTheme({ - required this.singleSize, - required this.size, - required this.ownReactionColor, - required this.otherReactionColor, - }); -} - /// Class for holding text theme class TextTheme { /// Initialise light text theme From a0f1c761005a108fb6bd990faf1e19b93e4b274c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 15 Jun 2021 13:57:03 +0530 Subject: [PATCH 369/599] minor fixes Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/client/client.dart | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 9abfa94c..7a3734da 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -320,7 +320,7 @@ class StreamChatClient { } if (wsConnectionStatus == ConnectionStatus.connected) { - throw StreamChatError('Connection already connected for ${user.id}'); + throw StreamChatError('Connection already available for ${user.id}'); } _wsConnectionStatus = ConnectionStatus.connecting; @@ -343,6 +343,9 @@ class StreamChatClient { /// This will not trigger default auto-retry mechanism for reconnection. /// You need to call [openConnection] to reconnect to [_ws]. void closeConnection() { + if (wsConnectionStatus == ConnectionStatus.disconnected) return; + + logger.info('Closing web-socket connection for ${state.user?.id}'); _wsConnectionStatus = ConnectionStatus.disconnected; _connectionStatusSubscription?.cancel(); @@ -1225,6 +1228,8 @@ class StreamChatClient { /// If [flushChatPersistence] is true the client deletes all offline /// user's data. Future disconnectUser({bool flushChatPersistence = false}) async { + logger.info('Disconnecting user : ${state.user?.id}'); + // resetting state state.dispose(); state = ClientState(this); @@ -1243,15 +1248,19 @@ class StreamChatClient { /// Call this function to dispose the client Future dispose() async { + logger.info('Disposing new StreamChatClient'); + + // disposing state state.dispose(); - await _eventController.close(); - await _wsConnectionStatusController.close(); // disconnecting persistence client await _chatPersistenceClient?.disconnect(); // closing web-socket connection closeConnection(); + + await _eventController.close(); + await _wsConnectionStatusController.close(); } } From 94e2c97468819deed4c3cd2227a23abb54a1bdd4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 15 Jun 2021 12:38:58 +0200 Subject: [PATCH 370/599] update changelogs and pubspecs --- packages/stream_chat/CHANGELOG.md | 5 +++++ packages/stream_chat/lib/version.dart | 2 +- packages/stream_chat/pubspec.yaml | 2 +- packages/stream_chat_flutter/CHANGELOG.md | 6 ++++++ packages/stream_chat_flutter/pubspec.yaml | 4 ++-- packages/stream_chat_flutter_core/CHANGELOG.md | 5 +++++ packages/stream_chat_flutter_core/pubspec.yaml | 4 ++-- 7 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index b6c5cecf..a3bdc6ce 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0-nullsafety.6 + +- Fix thread reply not working with attachments +- Minor fixes + ## 2.0.0-nullsafety.5 - Minor fixes diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index da7d8aa3..4049805a 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -3,4 +3,4 @@ import 'package:stream_chat/src/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header // ignore: constant_identifier_names -const PACKAGE_VERSION = '2.0.0-nullsafety.5'; +const PACKAGE_VERSION = '2.0.0-nullsafety.6'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index b13838aa..ec1d5bd1 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat homepage: https://getstream.io/ description: The official Dart client for Stream Chat, a service for building chat applications. -version: 2.0.0-nullsafety.5 +version: 2.0.0-nullsafety.6 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 39247b13..170bcf6b 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.0-nullsafety.6 + +- Minor fixes and improvements +- Updated `stream_chat_core` dependency +- 🛑 **BREAKING** Updated StreamChatThemeData.reactionIcons to accept custom builder + ## 2.0.0-nullsafety.5 - Minor fixes and improvements diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 61aad8df..e165b136 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.5 +version: 2.0.0-nullsafety.6 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -36,7 +36,7 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 shimmer: ^2.0.0 - stream_chat_flutter_core: ^2.0.0-nullsafety.5 + stream_chat_flutter_core: ^2.0.0-nullsafety.6 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 26cfa6c3..71ff8f51 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0-nullsafety.6 + +* Update llc dependency +* Minor fixes and improvements + ## 2.0.0-nullsafety.5 * Update llc dependency diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index f9768bdd..40a40c39 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.5 +version: 2.0.0-nullsafety.6 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -16,7 +16,7 @@ dependencies: sdk: flutter meta: ^1.3.0 rxdart: ^0.27.0 - stream_chat: ^2.0.0-nullsafety.5 + stream_chat: ^2.0.0-nullsafety.6 dev_dependencies: fake_async: ^1.2.0 From 8ec130bc84739b5cb8f3eb3895d8441a38cd08d8 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 15 Jun 2021 16:27:39 +0530 Subject: [PATCH 371/599] feat: Added new docs --- .../Flutter/stream_chat/querying_users.mdx | 64 ++++++++++++++ .../Flutter/stream_chat/user_presence.mdx | 86 +++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/docusaurus/docs/Flutter/stream_chat/querying_users.mdx b/docusaurus/docs/Flutter/stream_chat/querying_users.mdx index d605091e..83dc9177 100644 --- a/docusaurus/docs/Flutter/stream_chat/querying_users.mdx +++ b/docusaurus/docs/Flutter/stream_chat/querying_users.mdx @@ -4,3 +4,67 @@ sidebar_position: 4 title: Querying Users --- +The Query Users method allows you to search for users and see if they are online/offline. +The example below shows how you can retrieve the details for 3 users in one API call: + +```dart +final _result = client.queryUsers( + filter: Filter.in_('id', ['john', 'jack', 'jessie']), +); +``` + +You can also add sort options and pagination to the query: + +```dart +final _result = client.queryUsers( + filter: Filter.in_('id', ['john', 'jack', 'jessie']), + sort: [SortOption('last_active')], + pagination: PaginationParams( + offset: 0, + limit: 20, + ), +); +``` + +You can filter and sort on the custom fields you've set for your user, the user id, and when the user was last active. + +The options for the queryUser method are presence, limit, and offset. +If presence is true this makes sure you receive the user.presence.changed event when a user goes online or offline. + +### Querying using the autocomplete operator + +You can autocomplete the results of your user query by username and/or ID. + +### By Custom Field + +```dart +final _result = client.queryUsers( + filter: Filter.autoComplete('name', 'ro'), +); +``` +This would return an array of any matching users, such as: + +```json +[ + { + "id": "userID", + "name": "Curiosity Rover" + }, + { + "id": "userID2", + "name": "Roxy" + }, + { + "id": "userID3", + "name": "Roxanne" + } +] +``` + +### By ID + +```dart +final _result = client.queryUsers( + filter: Filter.autoComplete('id', 'USER_ID'), +); +``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/user_presence.mdx b/docusaurus/docs/Flutter/stream_chat/user_presence.mdx index 02e5eeb4..f33cb4a3 100644 --- a/docusaurus/docs/Flutter/stream_chat/user_presence.mdx +++ b/docusaurus/docs/Flutter/stream_chat/user_presence.mdx @@ -4,3 +4,89 @@ sidebar_position: 5 title: User Presence --- +User presence allows you to show when a user was last active and if they are online right now. +Whenever you read a user the data will look like this: + +```json +{ + id: 'unique_user_id', + online: true, + status: 'Eating a veggie burger...', + last_active: '2019-01-07T13:17:42.375Z' +} +``` + +Notes: + +1. The online field indicates if the user is online. The status field stores text indicating the current user status. +2. The last_active field is updated when a user connects and then refreshed every 15 minutes. + +### Invisible + +To mark a user invisible simply set the invisible property to true. You can also set a custom status message at the same time: + +```dart +await client.connectUser( + User( + id: 'super-band-9', + extraData: { + 'invisible': true, + }, + ), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + ); +``` + +Notes: + +1. When invisible is set to true, the current user will appear as offline to other users. + +### Listening to Presence Changes + +Of course, you want to listen to the user presence changes. This allows you to show a user as offline when they leave and update their status in real time. +These 3 endpoints allow you to watch user presence: + +```dart +// If you pass presence: true to channel.watch it will watch the list of user presence changes. +// Note that you can listen to at most 10 users using this API call +final channel = client.channel( + 'messaging', + id: 'flutterdevs', + extraData: { + 'name': 'Flutter devs', + }, +); + + +await channel.watch({ + 'presence': true, +}); + +// queryChannels allows you to listen to the members of the channels that are returned +// so this does the same thing as above and listens to online status changes for john and jack + +final filter = Filter.in_('members', ['john']); + +final sort = [SortOption("last_message_at", direction: SortOption.DESC)]; + +final channels = await client.queryChannels( + filter: filter, + sort: sort, + options: { + 'presence': true, + }, +); + +// queryUsers allows you to listen to user presence changes for john and jack +final result = await client.queryUsers( + filter: Filter.in_('id', ['john', 'jack', 'jessie']), + sort: [SortOption('last_active')], + pagination: PaginationParams( + offset: 0, + limit: 20, + ), + options: { + 'presence': true, + }, +); +``` \ No newline at end of file From e91b17df6adb38be71ae7ced81af98b83ba2fdc3 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 15 Jun 2021 21:40:14 +0530 Subject: [PATCH 372/599] feat: Added new docs --- .../Flutter/stream_chat/creating_channels.mdx | 39 +++++++ .../Flutter/stream_chat/querying_channels.mdx | 33 ++++++ .../Flutter/stream_chat/watching_channels.mdx | 108 ++++++++++++++++++ 3 files changed, 180 insertions(+) diff --git a/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx b/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx index b59dbabd..9d273cb1 100644 --- a/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx +++ b/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx @@ -4,3 +4,42 @@ sidebar_position: 6 title: Creating Channels --- +Both channel channel.query and channel.watch methods ensure that a channel exists and create one otherwise. +If all you need is to ensure that a channel exists, you can use channel.create. +Typically you use channel.watch client side and channel.create for a server side integration. +Channels are unique either by the specified channel id or by the list of members. + +### Creating a Channel Using a Channel ID + +This is typically the best approach if you already have some sort of object in your database that the channel is associated with. +As an example if you're building a live-streaming service like Twitch you have a channel for every streamer in your database. + +```dart +final channel = client.channel( + "messaging", + id: "travel", + extraData: { + "name": "Founder Chat", + "image": "http://bit.ly/2O35mws", + "members": ["thierry", "tommaso"], + }, +); +``` + +Note: Note that for client side integrations you'll typically prefer to use channel.watch() instead of channel.create. +Channel.watch both creates the channel and watches it at the same time. + +### Creating a Channel for a List of Members + +Channels can be used to create conversations between users. +In most cases, you want conversations to be unique and make sure that a group of users have only one channel. + +You can achieve this by leaving the channel ID empty and provide channel type and members. +When you do so, the API will ensure that only one channel for the members you specified exists (the order of the members does not matter). + +Note: You cannot add/remove members for channels created this way. + +```dart +client.channel("messaging", extraData: {"members": ["thierry", "tommaso"]}); +``` + diff --git a/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx b/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx index 55046907..3cd4d446 100644 --- a/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx +++ b/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx @@ -4,3 +4,36 @@ sidebar_position: 7 title: Querying Channels --- +If you’re building a similar application to Facebook Messenger or Intercom, you’ll want to show a list of Channels. +The Chat API supports MongoDB style queries to make this easy to implement. + +You can query channels based on built-in fields as well as any custom field you add to channels. +Multiple filters can be combined using AND, OR logical operators, each filter can use its comparison (equality, inequality, greater than, greater or equal, etc.). You can find the complete list of supported operators in the query syntax section of the docs. + +As an example, let's say that you want to query the last conversations I participated in sorted by `last_message_at`. + +Here’s an example of how you can query the list of channels: + +```dart +final filter = Filter.in_('members', ['thierry']); + +final sort = [SortOption("last_message_at", direction: SortOption.DESC)]; + +final channels = await client.queryChannels( + filter: filter, + sort: sort, + options: { + "watch": true, + "state": true, + }, +); + +channels.forEach((Channel c) { + print("${c.extraData['name']} ${c.cid}"); +}); +``` + +The query channels endpoint will only return channels that the user can read, +you should make sure that the query uses a filter that includes such logic. +For example: messaging channels are readable only to their members, +such requirement can be included in the query filter (see below). \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx b/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx index f35cc088..e6cc10bf 100644 --- a/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx +++ b/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx @@ -4,3 +4,111 @@ sidebar_position: 8 title: Watching Channels --- +The call to channel.watch does a few different things in one API call: + +* It creates the channel if it doesn't exist yet (if this user has the right permissions to create a channel) +* It queries the channel state and returns members, watchers and messages +* It watches the channel state and tells the server that you want to receive events when anything in this channel changes +* To start watching a channel + +The examples below show how to watch a channel. Note that you need to be connected as a user before you can watch a channel. + +```dart +final state = await channel.watch(); +``` + +### Watchers vs Members + +The concepts of watchers vs members can require a bit of clarification: + +* Members: a permanent association between a user and a channel. If the user is online and not watching the channel they will receive a notification event, if they are offline they will receive a push notification. +* Watchers: the list of watchers is temporary. It's anyone who is currently watching the channel. + +Being able to send messages, and otherwise engage with a channel as a non-member requires certain permissions. +For example, we have pre-configured permissions on our livestream channel type to allow non-members to interact, +but in the messaging channel type, only members of the channel can interact. + +### Watching Multiple Channels + +The default queryChannels API returns channels and starts watching them. +There is no need to also use channel.watch on the channels returned from queryChannels + +```dart +// first let’s create a filter to make messaging channels that include a specific user +final filter = Filter.in_('members', [user_id]); + +// we can also define a sort order of most recent messages first +final sort = [SortOption("last_message_at", direction: SortOption.DESC)]; + +// finally, we can query for those channels, automatically watching them for the +// currently connected user +final channels = await client.queryChannels( + filter: filter, + sort: sort, + options: { + "watch": true, + "state": true, + }, +); +``` + +### Stop Watching a Channel + +To stop receiving channel events: + +```dart +// we can also stop watching a channel +final stopWatching = await channel.stopWatching(); +``` + +### Watcher Count + +To get the watcher count of a channel: + +```dart +// create a new channel of type “livestream” with name “watch-this-channel” +final channel = client.channel("livestream", id: "watch-this-channel"); + +// retrieve our channels +channel.query(); + +// each channel object has a state collection with a watcher_count property + return channel.state.watcherCount; +``` + +### Paginating Channel Watchers with channel.query + +```dart +// create a new channel of type “livestream” with name “watch-this-channel” +final channel = client.channel("livestream", id: "watch-this-channel"); + +// now query the newly created channel for watchers, retrieving the first 5 +final result = await channel.query( + watchersPagination: PaginationParams( + limit: 5, + offset: 0, + ), + ); + + return result.watchers; +``` + +### Listening to Changes in Watchers + +A user already watching the channel can listen to users starting and stopping watching the channel with the realtime events: + +```dart +final channel = client.channel("livestream", id: "watch-this-channel"); + +await channel.watch(); + +// handle watch started event +channel + .on("user.watching.start") + .listen((event) => print('${event.user.id} started watching')); + +// handle watch stopped event +channel + .on("user.watching.stop") + .listen((event) => print('${event.user.id} stopped watching')); +``` From 2af7c78bd102f751057fc2f913561eb93db097e1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 16 Jun 2021 10:48:19 +0200 Subject: [PATCH 373/599] fix reconnection logic --- .../stream_chat_flutter_core/lib/src/stream_chat_core.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index d3ed4a0b..8de25615 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -125,7 +125,8 @@ class StreamChatCoreState extends State _isConnectionAvailable = result != ConnectivityResult.none; if (!_isInForeground) return; if (_isConnectionAvailable) { - if (client.wsConnectionStatus == ConnectionStatus.disconnected) { + if (client.wsConnectionStatus == ConnectionStatus.disconnected && + user != null) { client.connect(); } } else { From 432633e548b364e953bc5ac2f5b9b9cd2bbd3a11 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 16 Jun 2021 17:28:11 +0530 Subject: [PATCH 374/599] feat: Added new docs --- .../Flutter/stream_chat/channel_members.mdx | 27 +++++++++++++ .../Flutter/stream_chat/updating_channels.mdx | 40 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/docusaurus/docs/Flutter/stream_chat/channel_members.mdx b/docusaurus/docs/Flutter/stream_chat/channel_members.mdx index 3ac1da84..750a9ac7 100644 --- a/docusaurus/docs/Flutter/stream_chat/channel_members.mdx +++ b/docusaurus/docs/Flutter/stream_chat/channel_members.mdx @@ -4,3 +4,30 @@ sidebar_position: 10 title: Channel Members --- +### Adding & Removing Channel Members + +Using the addMembers() method adds the given users as members, while removeMembers() removes them. + +```dart +await channel.addMembers(["thierry", "josh"]); +await channel.removeMembers(["tommaso"]); +``` + +You can optionally include a message object that client-side SDKs will use to populate a system message. +This works for both add and remove members: + +```dart +// using client-side client +await channel.addMembers(['tommaso'], Message(text: 'Tommaso joined')); +``` + +### Leaving a channel + +It is possible for user to leave the channel without moderator-level permissions. +Make sure channel members have `RemoveOwnChannelMembership` permission. + +```dart +// remove own channel membership +await channel.removeMembers(['my_user_id']); +``` + diff --git a/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx b/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx index 75e693eb..a4de1fcd 100644 --- a/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx +++ b/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx @@ -4,3 +4,43 @@ sidebar_position: 9 title: Updating Channels --- +There are two ways to update a channel using the Stream API - a partial or full update. +A partial update will retain any custom key-value data, whereas a complete update is going to remove any that are unspecified in the API request. + +### Partial Update + +A partial update can be used to set and unset specific fields when it is necessary to retain additional custom data fields on the object. +AKA a patch style update. + +```dart +// Here's a channel with some custom field data that might be useful +var channel = client.channel(type, id: id, extraData: { + "source": "user", + "source_detail" :{ "user_id": 123 }, + "channel_detail" :{ "topic": "Plants and Animals", "rating": "pg" } +}); + +// let's change the source of this channel +await channel.updatePartial({ "set" :{ "source": "system" } }); + +// since it's system generated we no longer need source_detail +await channel.updatePartial({ "unset": ["source_detail"] }); + +// and finally update one of the nested fields in the channel_detail +await channel.updatePartial({ "set": { "channel_detail.topic": "Nature" } }); + +// and maybe we decide we no longer need a rating +await channel.updatePartial({ "unset": ["channel_detail.rating"] }); +``` + +### Full Update (overwrite) + +The update function updates all of the channel data. +Any data that is present on the channel and not included in a full update will be deleted. + +```dart +await channel.update({ + "name": "myspecialchannel", + "color": "green", +}, Message(text: "Thierry changed the channel color to green")); +``` \ No newline at end of file From e8e1d53aa34dad49a6a75f1b1008e843c3a00fce Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 16 Jun 2021 22:19:00 +0530 Subject: [PATCH 375/599] add client_test.dart Signed-off-by: Sahil Kumar --- .../stream_chat/lib/src/client/channel.dart | 32 +- .../stream_chat/lib/src/client/client.dart | 81 +- .../lib/src/core/api/message_api.dart | 16 +- .../test/src/api/channel_test.dart | 14 +- .../stream_chat/test/src/api/client_test.dart | 2135 +++++++++++++++++ .../test/src/core/api/message_api_test.dart | 4 +- packages/stream_chat/test/src/fakes.dart | 89 + packages/stream_chat/test/src/matchers.dart | 36 + packages/stream_chat/test/src/mocks.dart | 11 +- packages/stream_chat/test/src/utils.dart | 9 + .../lib/src/message_list_view.dart | 2 +- .../lib/src/stream_channel.dart | 2 +- 12 files changed, 2356 insertions(+), 75 deletions(-) create mode 100644 packages/stream_chat/test/src/api/client_test.dart diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index fe56aed4..9d0dc96c 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -22,9 +22,9 @@ class Channel { this._client, this._type, this._id, { - Map extraData = const {}, + Map? extraData, }) : _cid = _id != null ? '$_type:$_id' : null, - _extraData = extraData { + _extraData = extraData ?? {} { _client.logger.info('New Channel instance not initialized created'); } @@ -202,8 +202,13 @@ class Channel { } /// Channel extra data - Map get extraData => - state?._channelState.channel?.extraData ?? _extraData; + Map get extraData { + var data = state?._channelState.channel?.extraData; + if (data == null || data.isEmpty) { + data = _extraData; + } + return data; + } /// Channel extra data as a stream Stream> get extraDataStream { @@ -942,7 +947,7 @@ class Channel { state = ChannelClientState(this, channelState); if (cid != null) { - client.state.channels[cid!] = this; + client.state.channels = {cid!: this}; } if (!_initializedCompleter.isCompleted) { _initializedCompleter.complete(true); @@ -959,8 +964,8 @@ class Channel { /// Set [preferOffline] to true to avoid the api call if the data is already /// in the offline storage Future getReplies( - String parentId, - PaginationParams options, { + String parentId, { + PaginationParams? options, bool preferOffline = false, }) async { final cachedReplies = await _client.chatPersistenceClient?.getReplies( @@ -973,19 +978,22 @@ class Channel { return QueryRepliesResponse()..messages = cachedReplies; } } - final repliesResponse = await _client.getReplies(parentId, options); + final repliesResponse = await _client.getReplies( + parentId, + options: options, + ); state?.updateThreadInfo(parentId, repliesResponse.messages); return repliesResponse; } /// List the reactions for a message in the channel Future getReactions( - String messageId, - PaginationParams options, - ) => + String messageId, { + PaginationParams? options, + }) => _client.getReactions( messageId, - options, + options: options, ); /// Retrieves a list of messages by ID diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 7a22ac50..ecadfe3b 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -63,7 +63,7 @@ class StreamChatClient { /// application. StreamChatClient( String apiKey, { - this.logLevel = Level.ALL, + this.logLevel = Level.WARNING, LogHandlerFunction? logHandlerFunction, RetryPolicy? retryPolicy, Location? location, @@ -71,9 +71,11 @@ class StreamChatClient { Duration connectTimeout = const Duration(seconds: 6), Duration receiveTimeout = const Duration(seconds: 6), StreamChatApi? chatApi, + WebSocket? ws, AttachmentFileUploader? attachmentFileUploader, }) { - _setupLogger(logHandlerFunction); + this.logHandlerFunction = logHandlerFunction ?? _defaultLogHandler; + logger.info('Initiating new StreamChatClient'); final options = StreamHttpClientOptions( baseUrl: baseURL, @@ -92,13 +94,14 @@ class StreamChatClient { logger: detachedLogger('đŸ•¸ī¸'), ); - _ws = WebSocket( - apiKey: apiKey, - baseUrl: options.baseUrl, - tokenManager: _tokenManager, - handler: handleEvent, - logger: detachedLogger('🔌'), - ); + _ws = ws ?? + WebSocket( + apiKey: apiKey, + baseUrl: options.baseUrl, + tokenManager: _tokenManager, + handler: handleEvent, + logger: detachedLogger('🔌'), + ); _retryPolicy = retryPolicy ?? RetryPolicy( @@ -107,8 +110,6 @@ class StreamChatClient { ); state = ClientState(this); - - logger.info('instantiating new client'); } late final StreamChatApi _chatApi; @@ -160,7 +161,7 @@ class StreamChatClient { /// Client specific logger instance. /// Refer to the class [Logger] to learn more about the specific /// implementation. - final Logger logger = Logger.detached('📡'); + late final Logger logger = detachedLogger('📡'); /// A function that has a parameter of type [LogRecord]. /// This is called on every new log record. @@ -185,7 +186,7 @@ class StreamChatClient { final _eventController = BehaviorSubject(); - /// Stream of [Event] coming from websocket connection + /// Stream of [Event] coming from [_ws] connection /// Listen to this or use the [on] method to filter specific event types Stream get eventStream => _eventController.stream; @@ -195,12 +196,12 @@ class StreamChatClient { set _wsConnectionStatus(ConnectionStatus status) => _wsConnectionStatusController.add(status); - /// The current status value of the websocket connection + /// The current status value of the [_ws] connection ConnectionStatus get wsConnectionStatus => _wsConnectionStatusController.value; - /// This notifies the connection status of the websocket connection. - /// Listen to this to get notified when the websocket tries to reconnect. + /// This notifies the connection status of the [_ws] connection. + /// Listen to this to get notified when the [_ws] tries to reconnect. Stream get wsConnectionStatusStream => _wsConnectionStatusController.stream.distinct(); @@ -215,19 +216,9 @@ class StreamChatClient { }; /// - Logger detachedLogger( - String name, - ) => - Logger.detached(name) - ..level = logLevel - ..onRecord.listen(logHandlerFunction); - - void _setupLogger(LogHandlerFunction? logHandlerFunction) { - logger.level = logLevel; - this.logHandlerFunction = logHandlerFunction ?? _defaultLogHandler; - logger.onRecord.listen(this.logHandlerFunction); - logger.info('logger setup'); - } + Logger detachedLogger(String name) => Logger.detached(name) + ..level = logLevel + ..onRecord.listen(logHandlerFunction); /// Connects the current user, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. @@ -325,9 +316,13 @@ class StreamChatClient { _wsConnectionStatus = ConnectionStatus.connecting; - _connectionStatusSubscription = _ws.connectionStatusStream.listen( - _connectionStatusHandler, - ); + // skipping `ws` seed connection status -> ConnectionStatus.disconnected + // otherwise `client.wsConnectionStatusStream` will emit in order + // 1. ConnectionStatus.disconnected -> client seed status + // 2. ConnectionStatus.connecting -> client connecting status + // 3. ConnectionStatus.disconnected -> ws seed status + _connectionStatusSubscription = + _ws.connectionStatusStream.skip(1).listen(_connectionStatusHandler); try { return await _ws.connect(user); @@ -768,13 +763,13 @@ class StreamChatClient { _chatApi.device.removeDevice(id); /// Get a development token - String devToken(String userId) => Token.development(userId).rawValue; + Token devToken(String userId) => Token.development(userId); /// Returns a channel client with the given type, id and custom data. Channel channel( String type, { String? id, - Map extraData = const {}, + Map? extraData, }) { if (id != null && state.channels.containsKey('$type:$id')) { return state.channels['$type:$id']!; @@ -796,6 +791,7 @@ class StreamChatClient { ); /// watches the provided channel + /// Creates first if not yet created Future watchChannel( String channelType, { String? channelId, @@ -809,6 +805,7 @@ class StreamChatClient { ); /// Query the API, get messages, members or other channel fields + /// Creates the channel first if not yet created Future queryChannel( String channelType, { bool state = true, @@ -1139,22 +1136,22 @@ class StreamChatClient { /// Lists all the message replies for the [parentId] Future getReplies( - String parentId, - PaginationParams options, - ) => + String parentId, { + PaginationParams? options, + }) => _chatApi.message.getReplies( parentId, - options, + options: options, ); /// Get all the reactions for a [messageId] Future getReactions( - String messageId, - PaginationParams options, - ) => + String messageId, { + PaginationParams? options, + }) => _chatApi.message.getReactions( messageId, - options, + options: options, ); /// Update the given message diff --git a/packages/stream_chat/lib/src/core/api/message_api.dart b/packages/stream_chat/lib/src/core/api/message_api.dart index 6d80f35b..22f7bfef 100644 --- a/packages/stream_chat/lib/src/core/api/message_api.dart +++ b/packages/stream_chat/lib/src/core/api/message_api.dart @@ -142,13 +142,13 @@ class MessageApi { /// Get all the reactions for a [messageId] Future getReactions( - String messageId, - PaginationParams options, - ) async { + String messageId, { + PaginationParams? options, + }) async { final response = await _client.get( '/messages/$messageId/reactions', queryParameters: { - ...options.toJson(), + if (options != null) ...options.toJson(), }, ); return QueryReactionsResponse.fromJson(response.data); @@ -168,13 +168,13 @@ class MessageApi { /// Lists all the message replies for the [parentId] Future getReplies( - String parentId, - PaginationParams options, - ) async { + String parentId, { + PaginationParams? options, + }) async { final response = await _client.get( '/messages/$parentId/replies', queryParameters: { - ...options.toJson(), + if (options != null) ...options.toJson(), }, ); return QueryRepliesResponse.fromJson(response.data); diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 41b50360..22d04a43 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -1490,7 +1490,6 @@ void main() { test('`.getReplies`', () async { const parentId = 'test-parent-id'; - const options = PaginationParams(); final messages = List.generate( 3, @@ -1500,22 +1499,21 @@ void main() { ), ); - when(() => client.getReplies(parentId, options)).thenAnswer( + when(() => client.getReplies(parentId)).thenAnswer( (_) async => QueryRepliesResponse()..messages = messages, ); - final res = await channel.getReplies(parentId, options); + final res = await channel.getReplies(parentId); expect(res, isNotNull); expect(res.messages.length, messages.length); expect(res.messages.every((it) => it.parentId == parentId), isTrue); - verify(() => client.getReplies(parentId, options)).called(1); + verify(() => client.getReplies(parentId)).called(1); }); test('`.getReactions`', () async { const messageId = 'test-message-id'; - const options = PaginationParams(); final reactions = List.generate( 3, @@ -1525,17 +1523,17 @@ void main() { ), ); - when(() => client.getReactions(messageId, options)).thenAnswer( + when(() => client.getReactions(messageId)).thenAnswer( (_) async => QueryReactionsResponse()..reactions = reactions, ); - final res = await channel.getReactions(messageId, options); + final res = await channel.getReactions(messageId); expect(res, isNotNull); expect(res.reactions.length, reactions.length); expect(res.reactions.every((it) => it.messageId == messageId), isTrue); - verify(() => client.getReactions(messageId, options)).called(1); + verify(() => client.getReactions(messageId)).called(1); }); test('`.getMessagesById`', () async { diff --git a/packages/stream_chat/test/src/api/client_test.dart b/packages/stream_chat/test/src/api/client_test.dart new file mode 100644 index 00000000..b0b30c83 --- /dev/null +++ b/packages/stream_chat/test/src/api/client_test.dart @@ -0,0 +1,2135 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/src/client/client.dart'; +import 'package:stream_chat/src/core/api/device_api.dart'; +import 'package:stream_chat/src/core/api/requests.dart'; +import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/error/error.dart'; +import 'package:stream_chat/src/core/http/token.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/filter.dart'; +import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/own_user.dart'; +import 'package:stream_chat/src/core/models/user.dart'; +import 'package:stream_chat/src/event_type.dart'; +import 'package:stream_chat/src/ws/connection_status.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:test/scaffolding.dart'; +import 'package:test/test.dart'; + +import '../fakes.dart'; +import '../matchers.dart'; +import '../mocks.dart'; +import '../utils.dart'; + +void main() { + group('Fake web-socket connection functions', () { + const apiKey = 'test-api-key'; + late final api = FakeChatApi(); + + late StreamChatClient client; + + setUpAll(() { + // fallback values + registerFallbackValue(FakeUser()); + }); + + setUp(() { + final ws = FakeWebSocket(); + client = StreamChatClient(apiKey, ws: ws, chatApi: api); + }); + + tearDown(() { + client.dispose(); + }); + + test('`.connectUser` should work fine', () async { + final user = User(id: 'test-user-id'); + final token = Token.development(user.id).rawValue; + + final event = Event( + type: EventType.healthCheck, + connectionId: 'fake-connection-id', + me: OwnUser.fromUser(user), + ); + + expectLater( + // skipping first seed status -> ConnectionStatus.disconnected + client.wsConnectionStatusStream.skip(1), + emitsInOrder([ + ConnectionStatus.connecting, + ConnectionStatus.connected, + ]), + ); + + final res = await client.connectUser(user, token); + expect(res, isNotNull); + expect(res.type, event.type); + expect(res.connectionId, event.connectionId); + expect(res.me, isSameUserAs(user)); + }); + + test('`.connectUserWithProvider` should work fine', () async { + final user = User(id: 'test-user-id'); + Future tokenProvider(String userId) async { + expect(userId, user.id); + return Token.development(userId).rawValue; + } + + final event = Event( + type: EventType.healthCheck, + connectionId: 'fake-connection-id', + me: OwnUser.fromUser(user), + ); + + expectLater( + // skipping first seed status -> ConnectionStatus.disconnected + client.wsConnectionStatusStream.skip(1), + emitsInOrder([ + ConnectionStatus.connecting, + ConnectionStatus.connected, + ]), + ); + + final res = await client.connectUserWithProvider(user, tokenProvider); + expect(res, isNotNull); + expect(res.type, event.type); + expect(res.connectionId, event.connectionId); + expect(res.me, isSameUserAs(user)); + }); + + group('`.connectGuestUser`', () { + test('should work fine', () async { + final user = User(id: 'test-user-id'); + final token = Token.development(user.id).rawValue; + + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) + .thenAnswer( + (_) async => ConnectGuestUserResponse() + ..user = user + ..accessToken = token, + ); + + final event = Event( + type: EventType.healthCheck, + connectionId: 'fake-connection-id', + me: OwnUser.fromUser(user), + ); + + expectLater( + // skipping first seed status -> ConnectionStatus.disconnected + client.wsConnectionStatusStream.skip(1), + emitsInOrder([ + ConnectionStatus.connecting, + ConnectionStatus.connected, + ]), + ); + + final res = await client.connectGuestUser(user); + expect(res, isNotNull); + expect(res.type, event.type); + expect(res.connectionId, event.connectionId); + expect(res.me, isSameUserAs(user)); + + verify( + () => api.guest.getGuestUser(any(that: isSameUserAs(user))), + ).called(1); + }); + + test('should throw if `.getGuestUser` fails', () async { + final user = User(id: 'test-user-id'); + + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) + .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + + expectLater( + client.wsConnectionStatusStream, + emitsInOrder([ + // only emits the seed -> disconnected status + // as the call never reaches `ws.connect` + ConnectionStatus.disconnected, + ]), + ); + + try { + await client.connectGuestUser(user); + } catch (e) { + expect(e, isA()); + } + + verify( + () => api.guest.getGuestUser(any(that: isSameUserAs(user))), + ).called(1); + }); + }); + + test('`.connectAnonymousUser` should work fine', () async { + expectLater( + // skipping first seed status -> ConnectionStatus.disconnected + client.wsConnectionStatusStream.skip(1), + emitsInOrder([ + ConnectionStatus.connecting, + ConnectionStatus.connected, + ]), + ); + + final res = await client.connectAnonymousUser(); + expect(res, isNotNull); + expect(res.type, EventType.healthCheck); + expect(res.connectionId, 'fake-connection-id'); + expect(res.me, isNotNull); + }); + + group('`.openConnection`', () { + test('should throw if state does not contain user', () async { + expect(client.state.user, isNull); + try { + await client.openConnection(); + } catch (e) { + expect(e, isA()); + } + }); + + test('should throw if connection is already in progress', () async { + expect(client.state.user, isNull); + try { + await client.connectAnonymousUser(); + await client.openConnection(); + } catch (e) { + expect(e, isA()); + final err = e as StreamChatError; + expect( + err.message.contains('Connection already in progress for'), + isTrue, + ); + } + }); + + test('should throw if connection is already available', () async { + expect(client.state.user, isNull); + try { + await client.connectAnonymousUser(); + // waiting 300ms for `wsConnectionStatusStream` to emit + await delay(300); + + await client.openConnection(); + } catch (e) { + expect(e, isA()); + final err = e as StreamChatError; + expect( + err.message.contains('Connection already available for'), + isTrue, + ); + } + }); + + test('should open connection for closed connection', () async { + expectLater( + client.wsConnectionStatusStream.skip(1), + emitsInOrder([ + // initial connectUser + ConnectionStatus.connecting, + ConnectionStatus.connected, + // close connection + ConnectionStatus.disconnected, + // open connection + ConnectionStatus.connecting, + ConnectionStatus.connected, + ]), + ); + + await client.connectAnonymousUser(); + // waiting 300ms for `wsConnectionStatusStream` to emit + await delay(300); + + client.closeConnection(); + + await client.openConnection(); + }); + }); + }); + + group('Fake web-socket connection functions failure', () { + const apiKey = 'test-api-key'; + late final api = FakeChatApi(); + + late StreamChatClient client; + + setUpAll(() { + // fallback values + registerFallbackValue(FakeUser()); + }); + + setUp(() { + final ws = FakeWebSocketWithConnectionError(); + client = StreamChatClient(apiKey, chatApi: api, ws: ws); + }); + + tearDown(() { + client.dispose(); + }); + + test('`.connectUser` should throw if `ws.connect` fails', () async { + final user = User(id: 'test-user-id'); + final token = Token.development(user.id).rawValue; + + try { + await client.connectUser(user, token); + } catch (e) { + expect(e, isA()); + } + }); + + test( + '`.connectUserWithProvider` should throw if `ws.connect` fails', + () async { + final user = User(id: 'test-user-id'); + Future tokenProvider(String userId) async { + expect(userId, user.id); + return Token.development(userId).rawValue; + } + + try { + await client.connectUserWithProvider(user, tokenProvider); + } catch (e) { + expect(e, isA()); + } + }, + ); + + test('`.connectGuestUser` should throw if `ws.connect` fails', () async { + final user = User(id: 'test-user-id'); + final token = Token.development(user.id).rawValue; + + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) + .thenAnswer( + (_) async => ConnectGuestUserResponse() + ..user = user + ..accessToken = token, + ); + + try { + await client.connectGuestUser(user); + } catch (e) { + expect(e, isA()); + } + verify( + () => api.guest.getGuestUser(any(that: isSameUserAs(user))), + ).called(1); + }); + + test( + '`.connectAnonymousUser` should throw if `ws.connect` fails', + () async { + try { + await client.connectAnonymousUser(); + } catch (e) { + expect(e, isA()); + } + }, + ); + }); + + group('Client with connected user with persistence', () { + const apiKey = 'test-api-key'; + late final api = FakeChatApi(); + late final ws = FakeWebSocket(); + late final persistence = MockPersistenceClient(); + + final user = User(id: 'test-user-id'); + final token = Token.development(user.id).rawValue; + + late StreamChatClient client; + + setUpAll(() { + // fallback values + registerFallbackValue(FakeEvent()); + registerFallbackValue(const PaginationParams()); + registerFallbackValue(FakeChannelState()); + }); + + setUp(() async { + client = StreamChatClient(apiKey, chatApi: api, ws: ws) + ..chatPersistenceClient = persistence; + await client.connectUser(user, token); + await delay(300); + expect(client.persistenceEnabled, isTrue); + expect(client.wsConnectionStatus, ConnectionStatus.connected); + }); + + tearDown(() { + client.dispose(); + }); + + group('`.sync`', () { + test( + 'should update persistence connectionInfo and lastSync when sync succeeds', + () async { + const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; + final lastSyncAt = DateTime.now(); + + when(() => api.general.sync(cids, lastSyncAt)) + .thenAnswer((_) async => SyncResponse() + ..events = [ + Event( + isLocal: false, + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ), + Event( + isLocal: false, + type: EventType.messageDeleted, + message: Message(id: 'test-message-id'), + ), + ]); + + when(() => persistence.updateConnectionInfo(any())) + .thenAnswer((_) => Future.value()); + when(() => persistence.updateLastSyncAt(any())) + .thenAnswer((_) => Future.value()); + + await client.sync(cids: cids, lastSyncAt: lastSyncAt); + + verify(() => persistence.updateConnectionInfo(any())).called(1); + verify(() => persistence.updateLastSyncAt(any())).called(1); + verify(() => api.general.sync(cids, lastSyncAt)).called(1); + }, + ); + + test( + 'should work fine if persistence contains sync params', + () async { + const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; + final lastSyncAt = DateTime.now(); + + when(persistence.getChannelCids).thenAnswer((_) async => cids); + when(persistence.getLastSyncAt).thenAnswer((_) async => lastSyncAt); + + when(() => api.general.sync(cids, lastSyncAt)) + .thenAnswer((_) async => SyncResponse() + ..events = [ + Event( + isLocal: false, + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ), + Event( + isLocal: false, + type: EventType.messageDeleted, + message: Message(id: 'test-message-id', text: 'Hey!'), + ), + ]); + + when(() => persistence.updateConnectionInfo(any())) + .thenAnswer((_) => Future.value()); + when(() => persistence.updateLastSyncAt(any())) + .thenAnswer((_) => Future.value()); + + await client.sync(); + + verify(() => persistence.updateConnectionInfo(any())).called(1); + verify(() => persistence.updateLastSyncAt(any())).called(1); + verify(() => api.general.sync(cids, lastSyncAt)).called(1); + verify(persistence.getChannelCids).called(1); + verify(persistence.getLastSyncAt).called(1); + }, + ); + }); + + group('`.queryChannels`', () { + test( + 'should emit channels twice if persistence contains some channels', + () async { + final persistentChannelStates = List.generate( + 3, + (index) => ChannelState( + channel: ChannelModel(cid: 'p-test-type-$index:p-test-id-$index'), + ), + ); + + when(() => persistence.getChannelStates( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer((_) async => persistentChannelStates); + + final channelStates = List.generate( + 3, + (index) => ChannelState( + channel: ChannelModel(cid: 'test-type-$index:test-id-$index'), + ), + ); + + when(() => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( + (_) async => QueryChannelsResponse()..channels = channelStates, + ); + + when(() => persistence.getChannelThreads(any())) + .thenAnswer((_) async => {}); + when(() => persistence.updateMessages(any(), any())) + .thenAnswer((_) => Future.value()); + when(() => persistence.getChannelStateByCid(any(), + messagePagination: any(named: 'messagePagination'), + pinnedMessagePagination: + any(named: 'pinnedMessagePagination'))).thenAnswer( + (invocation) async => ChannelState( + channel: ChannelModel(cid: invocation.positionalArguments.first), + ), + ); + when(() => persistence.updateChannelQueries(any(), any(), + clearQueryCache: any(named: 'clearQueryCache'))) + .thenAnswer((_) => Future.value()); + + expectLater( + client.queryChannels(), + emitsInOrder([ + // emits persistent channels first + persistentChannelStates.map(isCorrectChannelFor), + // makes api call and emits network fetched channels + channelStates.map(isCorrectChannelFor), + ]), + ); + + // Hack as `teardown` gets called even + // before our stream starts emitting data + await delay(300); + + verify(() => persistence.getChannelStates( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + )).called(1); + + verify(() => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + )).called(1); + + verify(() => persistence.getChannelThreads(any())) + .called((persistentChannelStates + channelStates).length); + verify(() => persistence.updateMessages(any(), any())) + .called((persistentChannelStates + channelStates).length); + verify( + () => persistence.getChannelStateByCid(any(), + messagePagination: any(named: 'messagePagination'), + pinnedMessagePagination: any(named: 'pinnedMessagePagination')), + ).called((persistentChannelStates + channelStates).length); + verify(() => persistence.updateChannelQueries(any(), any(), + clearQueryCache: any(named: 'clearQueryCache'))).called(1); + }, + ); + + test( + 'should never rethrow network call if persistence already emitted some channels', + () async { + final persistentChannelStates = List.generate( + 3, + (index) => ChannelState( + channel: ChannelModel(cid: 'p-test-type-$index:p-test-id-$index'), + ), + ); + + when(() => persistence.getChannelStates( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer((_) async => persistentChannelStates); + + when(() => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + )).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + + when(() => persistence.getChannelThreads(any())) + .thenAnswer((_) async => {}); + when(() => persistence.updateMessages(any(), any())) + .thenAnswer((_) => Future.value()); + when(() => persistence.getChannelStateByCid(any(), + messagePagination: any(named: 'messagePagination'), + pinnedMessagePagination: + any(named: 'pinnedMessagePagination'))).thenAnswer( + (invocation) async => ChannelState( + channel: ChannelModel(cid: invocation.positionalArguments.first), + ), + ); + + expectLater( + client.queryChannels(), + emitsInOrder([ + // emits persistent channels + persistentChannelStates.map(isCorrectChannelFor), + ]), + ); + + // Hack as `teardown` gets called even + // before our stream starts emitting data + await delay(300); + + verify(() => persistence.getChannelStates( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + )).called(1); + + verify(() => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + )).called(1); + + verify(() => persistence.getChannelThreads(any())) + .called(persistentChannelStates.length); + verify(() => persistence.updateMessages(any(), any())) + .called(persistentChannelStates.length); + verify( + () => persistence.getChannelStateByCid(any(), + messagePagination: any(named: 'messagePagination'), + pinnedMessagePagination: any(named: 'pinnedMessagePagination')), + ).called(persistentChannelStates.length); + }, + ); + }); + + test('`.disconnectUser` should reset state and user', () async { + expect(client.state.user, isNotNull); + expect(client.wsConnectionStatus, ConnectionStatus.connected); + + expectLater( + // skipping initial connected value + client.wsConnectionStatusStream.skip(1), + emits(ConnectionStatus.disconnected), + ); + + await client.disconnectUser(); + + expect(client.state.user, isNull); + expect(client.wsConnectionStatus, ConnectionStatus.disconnected); + }); + }); + + group('Client with connected user without persistence', () { + const apiKey = 'test-api-key'; + late final api = FakeChatApi(); + late final ws = FakeWebSocket(); + + final user = User(id: 'test-user-id'); + final token = Token.development(user.id).rawValue; + + late StreamChatClient client; + + setUpAll(() { + // fallback values + registerFallbackValue(FakeEvent()); + registerFallbackValue(FakeMessage()); + registerFallbackValue(const PaginationParams()); + }); + + setUp(() async { + client = StreamChatClient(apiKey, chatApi: api, ws: ws); + await client.connectUser(user, token); + await delay(300); + expect(client.persistenceEnabled, isFalse); + expect(client.wsConnectionStatus, ConnectionStatus.connected); + }); + + tearDown(() { + client.dispose(); + }); + + group('`.sync`', () { + test('should work fine', () async { + const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; + final lastSyncAt = DateTime.now(); + + when(() => api.general.sync(cids, lastSyncAt)) + .thenAnswer((_) async => SyncResponse() + ..events = [ + Event( + isLocal: false, + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ), + Event( + isLocal: false, + type: EventType.messageDeleted, + message: Message(id: 'test-message-id'), + ), + ]); + + await client.sync(cids: cids, lastSyncAt: lastSyncAt); + + verify(() => api.general.sync(cids, lastSyncAt)).called(1); + }); + + test('should return if `cids` is not available', () async { + expect(client.sync, returnsNormally); + verifyNever(() => api.general.sync(any(), any())); + }); + + test('should return if `lastSyncAt` is not available', () async { + expect(() => client.sync(cids: ['test-cid-1']), returnsNormally); + verifyNever(() => api.general.sync(any(), any())); + }); + }); + + group('`.queryChannels`', () { + test('should work fine without persistent channels', () async { + final channelStates = List.generate( + 3, + (index) => ChannelState( + channel: ChannelModel(cid: 'test-type-$index:test-id-$index'), + ), + ); + + when(() => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + )).thenAnswer( + (_) async => QueryChannelsResponse()..channels = channelStates, + ); + + expectLater( + client.queryChannels(), + emitsInOrder([channelStates.map(isCorrectChannelFor)]), + ); + + // Hack as `teardown` gets called even + // before our stream starts emitting data + await delay(300); + + verify(() => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + )).called(1); + }); + + test( + 'should rethrow if `.queryChannelsOnline` throws and persistence channels are empty', + () async { + when(() => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + )).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + + expectLater( + client.queryChannels(), + emitsError(isA()), + ); + + // Hack as `teardown` gets called even + // before our stream starts emitting data + await delay(300); + + verify(() => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + )).called(1); + }, + ); + }); + + test('`.queryUsers`', () async { + final users = List.generate( + 3, + (index) => User(id: 'test-user-id-$index'), + ); + + when(() => api.user.queryUsers( + presence: any(named: 'presence'), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + )).thenAnswer((_) async => QueryUsersResponse()..users = users); + + expectLater( + // skipping initial seed event -> {} users + client.state.usersStream.skip(1), + emitsInOrder([ + {for (var user in users) user.id: user}, + ]), + ); + + final res = await client.queryUsers(); + expect(res, isNotNull); + expect(res.users.length, users.length); + + verify(() => api.user.queryUsers( + presence: any(named: 'presence'), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + )).called(1); + verifyNoMoreInteractions(api.user); + }); + + test('`.search`', () async { + const cid = 'test-type:test-id'; + final filter = Filter.in_('cid', const [cid]); + + final messages = List.generate( + 3, + (index) => GetMessageResponse() + ..channel = ChannelModel(cid: cid) + ..message = Message(id: 'test-message-id-$index'), + ); + + when(() => api.general.searchMessages(filter, + query: any(named: 'query'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + messageFilters: any(named: 'messageFilters'))) + .thenAnswer( + (_) async => SearchMessagesResponse()..results = messages); + + final res = await client.search(filter); + expect(res, isNotNull); + expect(res.results.length, messages.length); + + verify(() => api.general.searchMessages(filter, + query: any(named: 'query'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + messageFilters: any(named: 'messageFilters'))).called(1); + verifyNoMoreInteractions(api.general); + }); + + test('`.sendFile`', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + final file = AttachmentFile(size: 33, path: 'test-file-path'); + + const fileUrl = 'test-file-url'; + + when(() => api.fileUploader.sendFile(file, channelId, channelType)) + .thenAnswer((_) async => SendFileResponse()..file = fileUrl); + + final res = await client.sendFile(file, channelId, channelType); + expect(res, isNotNull); + expect(res.file, fileUrl); + + verify(() => api.fileUploader.sendFile(file, channelId, channelType)) + .called(1); + verifyNoMoreInteractions(api.fileUploader); + }); + + test('`.sendImage`', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + final image = AttachmentFile(size: 33, path: 'test-image-path'); + + const fileUrl = 'test-image-url'; + + when(() => api.fileUploader.sendImage(image, channelId, channelType)) + .thenAnswer((_) async => SendImageResponse()..file = fileUrl); + + final res = await client.sendImage(image, channelId, channelType); + expect(res, isNotNull); + expect(res.file, fileUrl); + + verify(() => api.fileUploader.sendImage(image, channelId, channelType)) + .called(1); + verifyNoMoreInteractions(api.fileUploader); + }); + + test('`.deleteFile`', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const fileUrl = 'test-file-url'; + + when(() => api.fileUploader.deleteFile(fileUrl, channelId, channelType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.deleteFile(fileUrl, channelId, channelType); + expect(res, isNotNull); + + verify(() => api.fileUploader.deleteFile(fileUrl, channelId, channelType)) + .called(1); + verifyNoMoreInteractions(api.fileUploader); + }); + + test('`.deleteImage`', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const imageUrl = 'test-image-url'; + + when(() => api.fileUploader.deleteImage(imageUrl, channelId, channelType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.deleteImage(imageUrl, channelId, channelType); + expect(res, isNotNull); + + verify( + () => api.fileUploader.deleteImage(imageUrl, channelId, channelType), + ).called(1); + verifyNoMoreInteractions(api.fileUploader); + }); + + test('`.updateChannel`', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const data = {'name': 'test-channel'}; + + when(() => api.channel.updateChannel(channelId, channelType, data)) + .thenAnswer((invocation) async => UpdateChannelResponse() + ..channel = ChannelModel( + id: channelId, + type: channelType, + extraData: {...data}, + )); + + final res = await client.updateChannel(channelId, channelType, data); + expect(res, isNotNull); + expect(res.channel.cid, '$channelType:$channelId'); + expect(res.channel.extraData['name'], 'test-channel'); + + verify(() => api.channel.updateChannel(channelId, channelType, data)) + .called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.updateChannelPartial`', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const set = { + 'name': 'Stream Team', + 'profile_image': 'test-profile-image', + }; + const unset = ['tag', 'last_name']; + + when(() => api.channel.updateChannelPartial(channelId, channelType, + set: set, unset: unset)) + .thenAnswer((invocation) async => PartialUpdateChannelResponse() + ..channel = ChannelModel( + id: channelId, + type: channelType, + extraData: {...set}, + )); + + final res = await client.updateChannelPartial( + channelId, + channelType, + set: set, + unset: unset, + ); + expect(res, isNotNull); + expect(res.channel.cid, '$channelType:$channelId'); + expect(res.channel.extraData, set); + + verify(() => api.channel.updateChannelPartial(channelId, channelType, + set: set, unset: unset)).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.addDevice`', () async { + const id = 'test-device-id'; + const provider = PushProvider.firebase; + + when(() => api.device.addDevice(id, provider)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.addDevice(id, provider); + expect(res, isNotNull); + + verify(() => api.device.addDevice(id, provider)).called(1); + verifyNoMoreInteractions(api.device); + }); + + test('`.getDevices`', () async { + final devices = List.generate( + 3, + (index) => Device( + id: 'test-device-id-$index', + pushProvider: PushProvider.firebase.name, + ), + ); + + when(() => api.device.getDevices()) + .thenAnswer((_) async => ListDevicesResponse()..devices = devices); + + final res = await client.getDevices(); + expect(res, isNotNull); + expect(res.devices.length, devices.length); + + verify(() => api.device.getDevices()).called(1); + verifyNoMoreInteractions(api.device); + }); + + test('`.removeDevice`', () async { + const deviceId = 'test-device-id'; + + when(() => api.device.removeDevice(deviceId)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.removeDevice(deviceId); + expect(res, isNotNull); + + verify(() => api.device.removeDevice(deviceId)).called(1); + verifyNoMoreInteractions(api.device); + }); + + test('`.devToken`', () async { + const userId = 'test-user-id'; + + final token = client.devToken(userId); + + expect(token, isNotNull); + expect(token.userId, userId); + expect(token.authType, AuthType.jwt); + }); + + group('`.channel`', () { + test('should return back a new channel instance', () { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const channelData = {'name': 'test-channel-name'}; + + final channel = client.channel( + channelType, + id: channelId, + extraData: channelData, + ); + + expect(channel, isNotNull); + expect(channel.type, channelType); + expect(channel.id, channelId); + expect(channel.cid, '$channelType:$channelId'); + expect(channel.extraData, channelData); + }); + + test('should return back in memory channel instance if available', + () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const channelData = {'name': 'test-channel-name'}; + const channelCid = '$channelType:$channelId'; + + final channel = client.channel( + channelType, + id: channelId, + extraData: channelData, + ); + + final channelState = ChannelState( + channel: ChannelModel(cid: channelCid), + ); + + when(() => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + )).thenAnswer((_) async => channelState); + + expectLater( + client.state.channelsStream.skip(1), + emitsInOrder([ + {channelCid: isCorrectChannelFor(channelState)} + ]), + ); + + await channel.watch(); + + final newChannel = client.channel(channelType, id: channelId); + expect(newChannel, channel); + + verify(() => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + )).called(1); + }); + }); + + test('`.createChannel`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const channelData = {'name': 'test-channel-name'}; + const channelCid = '$channelType:$channelId'; + + final channelState = ChannelState( + channel: ChannelModel(cid: channelCid, extraData: channelData), + ); + + when(() => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + )).thenAnswer((_) async => channelState); + + final res = await client.createChannel( + channelType, + channelId: channelId, + channelData: channelData, + ); + + expect(res, isNotNull); + expect(res.channel, isNotNull); + final channel = res.channel!; + expect(channel.type, channelType); + expect(channel.id, channelId); + expect(channel.cid, '$channelType:$channelId'); + expect(channel.extraData, channelData); + + verify(() => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + )).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.watchChannel`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const channelData = {'name': 'test-channel-name'}; + const channelCid = '$channelType:$channelId'; + + final channelState = ChannelState( + channel: ChannelModel(cid: channelCid, extraData: channelData), + ); + + when(() => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + )).thenAnswer((_) async => channelState); + + final res = await client.watchChannel( + channelType, + channelId: channelId, + channelData: channelData, + ); + + expect(res, isNotNull); + expect(res.channel, isNotNull); + final channel = res.channel!; + expect(channel.type, channelType); + expect(channel.id, channelId); + expect(channel.cid, '$channelType:$channelId'); + expect(channel.extraData, channelData); + + verify(() => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + )).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.queryChannel`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const channelData = {'name': 'test-channel-name'}; + const channelCid = '$channelType:$channelId'; + + final channelState = ChannelState( + channel: ChannelModel(cid: channelCid, extraData: channelData), + ); + + when(() => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + )).thenAnswer((_) async => channelState); + + final res = await client.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + ); + + expect(res, isNotNull); + expect(res.channel, isNotNull); + final channel = res.channel!; + expect(channel.type, channelType); + expect(channel.id, channelId); + expect(channel.cid, '$channelType:$channelId'); + expect(channel.extraData, channelData); + + verify(() => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + )).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.queryMembers`', () async { + const channelType = 'test-channel-type'; + + final members = List.generate( + 3, + (index) => Member(userId: 'test-user-id-$index'), + ); + + when(() => api.general.queryMembers(channelType)).thenAnswer( + (_) async => QueryMembersResponse()..members = members, + ); + + final res = await client.queryMembers(channelType); + expect(res, isNotNull); + expect(res.members.length, members.length); + + verify(() => api.general.queryMembers(channelType)).called(1); + verifyNoMoreInteractions(api.general); + }); + + test('`.hideChannel`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + + when(() => api.channel.hideChannel(channelId, channelType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.hideChannel(channelId, channelType); + + expect(res, isNotNull); + + verify(() => api.channel.hideChannel(channelId, channelType)).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.showChannel`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + + when(() => api.channel.showChannel(channelId, channelType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.showChannel(channelId, channelType); + + expect(res, isNotNull); + + verify(() => api.channel.showChannel(channelId, channelType)).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.deleteChannel`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + + when(() => api.channel.deleteChannel(channelId, channelType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.deleteChannel(channelId, channelType); + + expect(res, isNotNull); + + verify(() => api.channel.deleteChannel(channelId, channelType)).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.truncateChannel`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + + when(() => api.channel.truncateChannel(channelId, channelType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.truncateChannel(channelId, channelType); + + expect(res, isNotNull); + + verify( + () => api.channel.truncateChannel(channelId, channelType), + ).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.muteChannel`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const channelCid = '$channelType:$channelId'; + + when(() => api.moderation.muteChannel(channelCid)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.muteChannel(channelCid); + + expect(res, isNotNull); + + verify(() => api.moderation.muteChannel(channelCid)).called(1); + verifyNoMoreInteractions(api.moderation); + }); + + test('`.unmuteChannel`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const channelCid = '$channelType:$channelId'; + + when(() => api.moderation.unmuteChannel(channelCid)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.unmuteChannel(channelCid); + + expect(res, isNotNull); + + verify(() => api.moderation.unmuteChannel(channelCid)).called(1); + verifyNoMoreInteractions(api.moderation); + }); + + test('`.acceptChannelInvite`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const channelCid = '$channelType:$channelId'; + + when(() => api.channel.acceptChannelInvite(channelId, channelType)) + .thenAnswer((_) async => + AcceptInviteResponse()..channel = ChannelModel(cid: channelCid)); + + final res = await client.acceptChannelInvite(channelId, channelType); + expect(res, isNotNull); + expect(res.channel.cid, channelCid); + + verify(() => api.channel.acceptChannelInvite(channelId, channelType)) + .called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.rejectChannelInvite`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const channelCid = '$channelType:$channelId'; + + when(() => api.channel.rejectChannelInvite(channelId, channelType)) + .thenAnswer((_) async => + RejectInviteResponse()..channel = ChannelModel(cid: channelCid)); + + final res = await client.rejectChannelInvite(channelId, channelType); + expect(res, isNotNull); + expect(res.channel.cid, channelCid); + + verify(() => api.channel.rejectChannelInvite(channelId, channelType)) + .called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.addChannelMembers`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const channelCid = '$channelType:$channelId'; + + final members = List.generate( + 3, + (index) => Member(userId: 'test-user-id-$index'), + ); + + final memberIds = members.map((e) => e.userId!).toList(growable: false); + + when(() => api.channel.addMembers(channelId, channelType, memberIds)) + .thenAnswer((_) async => AddMembersResponse() + ..channel = ChannelModel(cid: channelCid) + ..members = members); + + final res = await client.addChannelMembers( + channelId, + channelType, + memberIds, + ); + + expect(res, isNotNull); + expect(res.channel.cid, channelCid); + expect(res.members.length, memberIds.length); + + verify( + () => api.channel.addMembers(channelId, channelType, memberIds), + ).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.removeChannelMembers`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const channelCid = '$channelType:$channelId'; + + final members = List.generate( + 3, + (index) => Member(userId: 'test-user-id-$index'), + ); + + final memberIds = members.map((e) => e.userId!).toList(growable: false); + + when(() => api.channel.removeMembers(channelId, channelType, memberIds)) + .thenAnswer((_) async => RemoveMembersResponse() + ..channel = ChannelModel(cid: channelCid) + ..members = members); + + final res = await client.removeChannelMembers( + channelId, + channelType, + memberIds, + ); + + expect(res, isNotNull); + expect(res.channel.cid, channelCid); + expect(res.members.length, memberIds.length); + + verify( + () => api.channel.removeMembers(channelId, channelType, memberIds), + ).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.inviteChannelMembers`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const channelCid = '$channelType:$channelId'; + + final members = List.generate( + 3, + (index) => Member(userId: 'test-user-id-$index'), + ); + + final memberIds = members.map((e) => e.userId!).toList(growable: false); + + when(() => api.channel + .inviteChannelMembers(channelId, channelType, memberIds)) + .thenAnswer((_) async => InviteMembersResponse() + ..channel = ChannelModel(cid: channelCid) + ..members = members); + + final res = await client.inviteChannelMembers( + channelId, + channelType, + memberIds, + ); + + expect(res, isNotNull); + expect(res.channel.cid, channelCid); + expect(res.members.length, memberIds.length); + + verify(() => api.channel + .inviteChannelMembers(channelId, channelType, memberIds)).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.stopChannelWatching`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + + when(() => api.channel.stopWatching(channelId, channelType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.stopChannelWatching(channelId, channelType); + expect(res, isNotNull); + + verify(() => api.channel.stopWatching(channelId, channelType)).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.sendAction`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + const messageId = 'test-message-id'; + const formData = {'key': 'value'}; + + when(() => api.message + .sendAction(channelId, channelType, messageId, formData)) + .thenAnswer((_) async => SendActionResponse()); + + final res = await client.sendAction( + channelId, + channelType, + messageId, + formData, + ); + + expect(res, isNotNull); + + verify(() => api.message + .sendAction(channelId, channelType, messageId, formData)).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.markChannelRead`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + + when(() => api.channel.markRead(channelId, channelType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.markChannelRead(channelId, channelType); + + expect(res, isNotNull); + + verify(() => api.channel.markRead(channelId, channelType)).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.updateUser`', () async { + final user = User( + id: 'test-user-id', + extraData: const {'name': 'test-user'}, + ); + + when(() => api.user.updateUsers([user])).thenAnswer( + (_) async => UpdateUsersResponse()..users = {user.id: user}); + + final res = await client.updateUser(user); + + expect(res, isNotNull); + expect(res.users, {user.id: user}); + + verify(() => api.user.updateUsers([user])).called(1); + verifyNoMoreInteractions(api.user); + }); + + test('`.banUser`', () async { + const userId = 'test-user-id'; + + when(() => api.moderation.banUser(userId, options: any(named: 'options'))) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.banUser(userId); + + expect(res, isNotNull); + + verify( + () => api.moderation.banUser(userId, options: any(named: 'options')), + ).called(1); + verifyNoMoreInteractions(api.moderation); + }); + + test('`.unbanUser`', () async { + const userId = 'test-user-id'; + + when(() => + api.moderation.unbanUser(userId, options: any(named: 'options'))) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.unbanUser(userId); + + expect(res, isNotNull); + + verify( + () => api.moderation.unbanUser(userId, options: any(named: 'options')), + ).called(1); + verifyNoMoreInteractions(api.moderation); + }); + + test('`.shadowBan`', () async { + const userId = 'test-user-id'; + + when(() => api.moderation.banUser(userId, options: {'shadow': true})) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.shadowBan(userId); + + expect(res, isNotNull); + + verify( + () => api.moderation.banUser(userId, options: {'shadow': true}), + ).called(1); + verifyNoMoreInteractions(api.moderation); + }); + + test('`.removeShadowBan`', () async { + const userId = 'test-user-id'; + + when(() => api.moderation.unbanUser(userId, options: {'shadow': true})) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.removeShadowBan(userId); + + expect(res, isNotNull); + + verify( + () => api.moderation.unbanUser(userId, options: {'shadow': true}), + ).called(1); + verifyNoMoreInteractions(api.moderation); + }); + + test('`.muteUser`', () async { + const userId = 'test-user-id'; + + when(() => api.moderation.muteUser(userId)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.muteUser(userId); + + expect(res, isNotNull); + + verify(() => api.moderation.muteUser(userId)).called(1); + verifyNoMoreInteractions(api.moderation); + }); + + test('`.unmuteUser`', () async { + const userId = 'test-user-id'; + + when(() => api.moderation.unmuteUser(userId)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.unmuteUser(userId); + + expect(res, isNotNull); + + verify(() => api.moderation.unmuteUser(userId)).called(1); + verifyNoMoreInteractions(api.moderation); + }); + + test('`.flagMessage`', () async { + const messageId = 'test-message-id'; + + when(() => api.moderation.flagMessage(messageId)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.flagMessage(messageId); + + expect(res, isNotNull); + + verify(() => api.moderation.flagMessage(messageId)).called(1); + verifyNoMoreInteractions(api.moderation); + }); + + test('`.unflagMessage`', () async { + const messageId = 'test-message-id'; + + when(() => api.moderation.unflagMessage(messageId)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.unflagMessage(messageId); + + expect(res, isNotNull); + + verify(() => api.moderation.unflagMessage(messageId)).called(1); + verifyNoMoreInteractions(api.moderation); + }); + + test('`.flagUser`', () async { + const userId = 'test-message-id'; + + when(() => api.moderation.flagUser(userId)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.flagUser(userId); + + expect(res, isNotNull); + + verify(() => api.moderation.flagUser(userId)).called(1); + verifyNoMoreInteractions(api.moderation); + }); + + test('`.unflagUser`', () async { + const userId = 'test-message-id'; + + when(() => api.moderation.unflagUser(userId)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.unflagUser(userId); + + expect(res, isNotNull); + + verify(() => api.moderation.unflagUser(userId)).called(1); + verifyNoMoreInteractions(api.moderation); + }); + + test('`.markAllRead`', () async { + when(() => api.channel.markAllRead()) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.markAllRead(); + expect(res, isNotNull); + + verify(() => api.channel.markAllRead()).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.sendEvent`', () async { + const channelType = 'test-channel-type'; + const channelId = 'test-channel-id'; + final event = Event(type: EventType.any); + + when( + () => api.channel.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(event)), + ), + ).thenAnswer((_) async => EmptyResponse()); + + final res = await client.sendEvent(channelId, channelType, event); + expect(res, isNotNull); + + verify(() => api.channel.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(event)), + )).called(1); + verifyNoMoreInteractions(api.channel); + }); + + test('`.sendReaction`', () async { + const messageId = 'test-message-id'; + const reactionType = 'like'; + + when(() => api.message.sendReaction(messageId, reactionType)) + .thenAnswer((_) async => SendReactionResponse() + ..message = Message(id: messageId) + ..reaction = Reaction(type: reactionType, messageId: messageId)); + + final res = await client.sendReaction(messageId, reactionType); + expect(res, isNotNull); + expect(res.message.id, messageId); + expect(res.reaction.type, reactionType); + expect(res.reaction.messageId, messageId); + + verify(() => api.message.sendReaction(messageId, reactionType)).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.deleteReaction`', () async { + const messageId = 'test-message-id'; + const reactionType = 'like'; + + when(() => api.message.deleteReaction(messageId, reactionType)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.deleteReaction(messageId, reactionType); + expect(res, isNotNull); + + verify( + () => api.message.deleteReaction(messageId, reactionType), + ).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.sendMessage`', () async { + final message = Message(id: 'test-message-id'); + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + + when(() => api.message.sendMessage( + channelId, channelType, any(that: isSameMessageAs(message)))) + .thenAnswer((_) async => SendMessageResponse()..message = message); + + final res = await client.sendMessage(message, channelId, channelType); + expect(res, isNotNull); + expect(res.message, isSameMessageAs(message)); + + verify(() => api.message.sendMessage( + channelId, + channelType, + any(that: isSameMessageAs(message)), + )).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.getReplies`', () async { + const parentId = 'test-parent-id'; + + final messages = List.generate( + 3, + (index) => Message(id: 'test-message-id-$index'), + ); + + when(() => api.message.getReplies(parentId)) + .thenAnswer((_) async => QueryRepliesResponse()..messages = messages); + + final res = await client.getReplies(parentId); + expect(res, isNotNull); + expect(res.messages.length, messages.length); + + verify(() => api.message.getReplies(parentId)).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.getReactions`', () async { + const messageId = 'test-parent-id'; + + final reactions = List.generate( + 3, + (index) => Reaction( + type: 'test-reactions-type-$index', + messageId: messageId, + ), + ); + + when(() => api.message.getReactions(messageId)).thenAnswer( + (_) async => QueryReactionsResponse()..reactions = reactions); + + final res = await client.getReactions(messageId); + expect(res, isNotNull); + expect(res.reactions.length, reactions.length); + expect(res.reactions.every((it) => it.messageId == messageId), isTrue); + + verify(() => api.message.getReactions(messageId)).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.updateMessage`', () async { + final message = Message(id: 'test-message-id', text: 'Hello!'); + + when(() => api.message.updateMessage(any(that: isSameMessageAs(message)))) + .thenAnswer((_) async => UpdateMessageResponse()..message = message); + + final res = await client.updateMessage(message); + expect(res, isNotNull); + expect(res.message, isSameMessageAs(message)); + + verify( + () => api.message.updateMessage(any(that: isSameMessageAs(message))), + ).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.deleteMessage`', () async { + const messageId = 'test-message-id'; + + when(() => api.message.deleteMessage(messageId)) + .thenAnswer((_) async => EmptyResponse()); + + final res = await client.deleteMessage(messageId); + expect(res, isNotNull); + + verify(() => api.message.deleteMessage(messageId)).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.getMessage`', () async { + const messageId = 'test-message-id'; + final message = Message(id: messageId); + + when(() => api.message.getMessage(messageId)) + .thenAnswer((_) async => GetMessageResponse()..message = message); + + final res = await client.getMessage(messageId); + expect(res, isNotNull); + expect(res.message.id, messageId); + + verify(() => api.message.getMessage(messageId)).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.getMessagesById`', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + const messageIds = ['test-message-id']; + + final messages = messageIds.map((id) => Message(id: id)).toList(); + + when( + () => api.message.getMessagesById(channelId, channelType, messageIds), + ).thenAnswer((_) async => GetMessagesByIdResponse()..messages = messages); + + final res = await client.getMessagesById( + channelId, + channelType, + messageIds, + ); + expect(res, isNotNull); + expect(res.messages.length, messageIds.length); + + verify( + () => api.message.getMessagesById(channelId, channelType, messageIds), + ).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.translateMessage`', () async { + const messageId = 'test-message-id'; + const language = 'hi'; // Hindi + const translatedMessageText = 'ā¤¨ā¤Žā¤¸āĨā¤¤āĨ‡'; + final translatedMessage = TranslatedMessage(const { + language: translatedMessageText, + }); + + when(() => api.message.translateMessage(messageId, language)).thenAnswer( + (_) async => TranslateMessageResponse()..message = translatedMessage, + ); + + final res = await client.translateMessage(messageId, language); + + expect(res, isNotNull); + expect(res.message.i18n, translatedMessage.i18n); + + verify(() => api.message.translateMessage(messageId, language)).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.partialUpdateMessage`', () async { + const messageId = 'test-message-id'; + final message = Message(id: messageId); + + const set = {'text': 'Update Message text'}; + const unset = ['pinExpires']; + + final updateMessageResponse = UpdateMessageResponse() + ..message = message.copyWith(text: set['text'], pinExpires: null); + + when(() => api.message.partialUpdateMessage( + message.id, + set: set, + unset: unset, + )).thenAnswer((_) async => updateMessageResponse); + + final res = await client.partialUpdateMessage( + messageId, + set: set, + unset: unset, + ); + + expect(res, isNotNull); + expect(res.message.id, message.id); + expect(res.message.id, message.id); + expect(res.message.text, set['text']); + expect(res.message.pinExpires, isNull); + + verify(() => api.message.partialUpdateMessage( + message.id, + set: set, + unset: unset, + )).called(1); + verifyNoMoreInteractions(api.message); + }); + + group('`.pinMessage`', () { + test('should work fine without passing timeoutOrExpirationDate', + () async { + const messageId = 'test-message-id'; + final message = Message(id: messageId); + + when(() => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + )).thenAnswer((_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: null, + status: MessageSendingStatus.sent, + )); + + final res = await client.pinMessage(messageId); + + expect(res, isNotNull); + expect(res.message.pinned, isTrue); + expect(res.message.pinExpires, isNull); + + verify(() => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + )).called(1); + verifyNoMoreInteractions(api.message); + }); + + test( + 'should work fine if passed timeoutOrExpirationDate as num(seconds)', + () async { + const messageId = 'test-message-id'; + final message = Message(id: messageId); + const timeoutOrExpirationDate = 300; // 300 seconds + + when(() => api.message.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + )).thenAnswer((_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: DateTime.now().add( + const Duration(seconds: timeoutOrExpirationDate), + ), + status: MessageSendingStatus.sent, + )); + + final res = await client.pinMessage( + messageId, + timeoutOrExpirationDate: timeoutOrExpirationDate, + ); + + expect(res, isNotNull); + expect(res.message.pinned, isTrue); + expect(res.message.pinExpires, isNotNull); + + verify(() => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + )).called(1); + verifyNoMoreInteractions(api.message); + }, + ); + + test( + 'should work fine if passed timeoutOrExpirationDate as DateTime', + () async { + const messageId = 'test-message-id'; + final message = Message(id: messageId); + final timeoutOrExpirationDate = + DateTime.now().add(const Duration(days: 3)); // 3 days + + when(() => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + )).thenAnswer((_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: timeoutOrExpirationDate, + status: MessageSendingStatus.sent, + )); + + final res = await client.pinMessage( + messageId, + timeoutOrExpirationDate: timeoutOrExpirationDate, + ); + + expect(res, isNotNull); + expect(res.message.pinned, isTrue); + expect(res.message.pinExpires, isNotNull); + expect(res.message.pinExpires, timeoutOrExpirationDate.toUtc()); + + verify(() => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + )).called(1); + verifyNoMoreInteractions(api.message); + }, + ); + + test( + 'should throw if invalid timeoutOrExpirationDate is passed', + () async { + const messageId = 'test-message-id'; + const timeoutOrExpirationDate = 'invalid-value'; + + try { + await client.pinMessage( + messageId, + timeoutOrExpirationDate: timeoutOrExpirationDate, + ); + } catch (e) { + expect(e, isA()); + } + }, + ); + }); + + test('`.unpinMessage`', () async { + const messageId = 'test-message-id'; + final message = Message(id: messageId, pinned: true); + + when(() => api.message.partialUpdateMessage( + messageId, + set: {'pinned': false}, + )).thenAnswer((_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: false, + status: MessageSendingStatus.sent, + )); + + final res = await client.unpinMessage(messageId); + + expect(res, isNotNull); + expect(res.message.pinned, isFalse); + + verify(() => api.message.partialUpdateMessage( + messageId, + set: {'pinned': false}, + )).called(1); + verifyNoMoreInteractions(api.message); + }); + }); +} diff --git a/packages/stream_chat/test/src/core/api/message_api_test.dart b/packages/stream_chat/test/src/core/api/message_api_test.dart index 041c18f5..d7fa93cd 100644 --- a/packages/stream_chat/test/src/core/api/message_api_test.dart +++ b/packages/stream_chat/test/src/core/api/message_api_test.dart @@ -252,7 +252,7 @@ void main() { 'reactions': [...reactions.map((it) => it.toJson())] })); - final res = await messageApi.getReactions(messageId, options); + final res = await messageApi.getReactions(messageId, options: options); expect(res, isNotNull); expect(res.reactions.length, reactions.length); @@ -311,7 +311,7 @@ void main() { 'messages': [...messages.map((it) => it.toJson())] })); - final res = await messageApi.getReplies(parentId, options); + final res = await messageApi.getReplies(parentId, options: options); expect(res, isNotNull); expect(res.messages.length, messages.length); diff --git a/packages/stream_chat/test/src/fakes.dart b/packages/stream_chat/test/src/fakes.dart index 74280a0b..15d5991c 100644 --- a/packages/stream_chat/test/src/fakes.dart +++ b/packages/stream_chat/test/src/fakes.dart @@ -1,5 +1,8 @@ +import 'dart:async'; + import 'package:dio/dio.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/core/api/channel_api.dart'; import 'package:stream_chat/src/core/api/device_api.dart'; import 'package:stream_chat/src/core/api/general_api.dart'; @@ -10,6 +13,7 @@ import 'package:stream_chat/src/core/api/user_api.dart'; import 'package:stream_chat/src/core/api/guest_api.dart'; import 'package:stream_chat/src/core/http/token.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; +import 'package:stream_chat/src/ws/websocket.dart'; import 'package:stream_chat/stream_chat.dart'; import 'mocks.dart'; @@ -96,3 +100,88 @@ class FakeMessage extends Fake implements Message {} class FakeAttachmentFile extends Fake implements AttachmentFile {} class FakeEvent extends Fake implements Event {} + +class FakeUser extends Fake implements User {} + +class FakeWebSocket extends Fake implements WebSocket { + BehaviorSubject? _connectionStatusController; + + BehaviorSubject get connectionStatusController => + _connectionStatusController ??= + BehaviorSubject.seeded(ConnectionStatus.disconnected); + + set connectionStatus(ConnectionStatus value) { + connectionStatusController.add(value); + } + + @override + ConnectionStatus get connectionStatus => connectionStatusController.value; + + @override + Stream get connectionStatusStream => + connectionStatusController.stream; + + @override + Completer? connectionCompleter; + + @override + Future connect(User user) async { + connectionStatus = ConnectionStatus.connecting; + final event = Event( + type: EventType.healthCheck, + connectionId: 'fake-connection-id', + me: OwnUser.fromUser(user), + ); + connectionCompleter = Completer()..complete(event); + connectionStatus = ConnectionStatus.connected; + return connectionCompleter!.future; + } + + @override + void disconnect() { + connectionStatus = ConnectionStatus.disconnected; + connectionCompleter = null; + _connectionStatusController?.close(); + _connectionStatusController = null; + } +} + +class FakeWebSocketWithConnectionError extends Fake implements WebSocket { + BehaviorSubject? _connectionStatusController; + + BehaviorSubject get connectionStatusController => + _connectionStatusController ??= + BehaviorSubject.seeded(ConnectionStatus.disconnected); + + set connectionStatus(ConnectionStatus value) { + connectionStatusController.add(value); + } + + @override + ConnectionStatus get connectionStatus => connectionStatusController.value; + + @override + Stream get connectionStatusStream => + connectionStatusController.stream; + + @override + Completer? connectionCompleter; + + @override + Future connect(User user) async { + connectionStatus = ConnectionStatus.connecting; + const error = StreamWebSocketError('Error Connecting'); + connectionCompleter = Completer()..completeError(error); + return connectionCompleter!.future; + } + + @override + void disconnect() { + connectionStatus = ConnectionStatus.disconnected; + connectionCompleter = null; + _connectionStatusController?.close(); + _connectionStatusController = null; + } +} + +class FakeChannelState extends Fake implements ChannelState {} diff --git a/packages/stream_chat/test/src/matchers.dart b/packages/stream_chat/test/src/matchers.dart index 967f3c21..c039b416 100644 --- a/packages/stream_chat/test/src/matchers.dart +++ b/packages/stream_chat/test/src/matchers.dart @@ -1,7 +1,11 @@ import 'package:collection/collection.dart'; import 'package:dio/dio.dart' show MultipartFile; +import 'package:stream_chat/src/client/channel.dart'; +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/channel_state.dart'; import 'package:stream_chat/src/core/models/event.dart'; import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/core/models/user.dart'; import 'package:test/test.dart'; Matcher isSameMultipartFileAs(MultipartFile targetFile) => @@ -96,3 +100,35 @@ class _IsSameMessageAs extends Matcher { return matches; } } + +Matcher isSameUserAs(User targetUser) => _IsSameUserAs(targetUser: targetUser); + +class _IsSameUserAs extends Matcher { + const _IsSameUserAs({required this.targetUser}); + + final User targetUser; + + @override + Description describe(Description description) => + description.add('is same user as $targetUser'); + + @override + bool matches(covariant User user, Map matchState) => user.id == targetUser.id; +} + +Matcher isCorrectChannelFor(ChannelState channelState) => + _IsCorrectChannelFor(channelState: channelState); + +class _IsCorrectChannelFor extends Matcher { + const _IsCorrectChannelFor({required this.channelState}); + + final ChannelState channelState; + + @override + Description describe(Description description) => + description.add('is correct channel for $channelState'); + + @override + bool matches(covariant Channel channel, Map matchState) => + channel.cid == channelState.channel?.cid; +} diff --git a/packages/stream_chat/test/src/mocks.dart b/packages/stream_chat/test/src/mocks.dart index 31e768e8..528c8a91 100644 --- a/packages/stream_chat/test/src/mocks.dart +++ b/packages/stream_chat/test/src/mocks.dart @@ -18,6 +18,7 @@ import 'package:stream_chat/src/core/http/token_manager.dart'; import 'package:stream_chat/src/core/models/channel_config.dart'; import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:stream_chat/src/db/chat_persistence_client.dart'; +import 'package:stream_chat/src/ws/websocket.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'db/chat_persistence_client_test.dart'; @@ -66,7 +67,13 @@ class MockGeneralApi extends Mock implements GeneralApi {} class MockAttachmentFileUploader extends Mock implements AttachmentFileUploader {} -class MockPersistenceClient extends Mock implements ChatPersistenceClient {} +class MockPersistenceClient extends Mock implements ChatPersistenceClient { + @override + Future connect(String userId) => Future.value(); + + @override + Future disconnect({bool flush = false}) => Future.value(); +} class MockStreamChatClient extends Mock implements StreamChatClient { @override @@ -105,3 +112,5 @@ class MockRetryQueueChannel extends Mock implements Channel { @override StreamChatClient get client => _client ??= MockStreamChatClient(); } + +class MockWebSocket extends Mock implements WebSocket {} diff --git a/packages/stream_chat/test/src/utils.dart b/packages/stream_chat/test/src/utils.dart index b17e35a9..91a61204 100644 --- a/packages/stream_chat/test/src/utils.dart +++ b/packages/stream_chat/test/src/utils.dart @@ -13,3 +13,12 @@ Directory get currentDirectory { } return directory; } + +// Extension function to convert int into durations +extension IntX on num { + Duration toDuration() => Duration(milliseconds: toInt()); +} + +// Top level util function to delay the code execution +Future delay(num milliseconds) => + Future.delayed(Duration(milliseconds: milliseconds.toInt())); 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 aae9d193..976aacb0 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -791,7 +791,7 @@ class _MessageListViewState extends State { final channel = streamChannel.channel; if (_upToDate && channel.config?.readEvents == true && - channel.state!.unreadCount! > 0) { + channel.state!.unreadCount > 0) { streamChannel.channel.markRead(); } } 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 20304cdd..1dcf9c3c 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -174,7 +174,7 @@ class StreamChannelState extends State { try { final response = await channel.getReplies( parentId, - PaginationParams( + options: PaginationParams( lessThan: message?.id, limit: limit, ), From 2b80611cd3f6fe70ad06fed4b4916445b90413aa Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Jun 2021 12:52:48 +0530 Subject: [PATCH 376/599] fix tests Signed-off-by: Sahil Kumar --- .../test/src/back_button_test.dart | 3 +- .../test/src/channel_header_test.dart | 4 ++ .../src/goldens/deleted_message_custom.png | Bin 2936 -> 2334 bytes .../test/src/goldens/deleted_message_dark.png | Bin 2785 -> 2262 bytes .../src/goldens/deleted_message_light.png | Bin 2750 -> 2161 bytes .../test/src/goldens/reaction_bubble_2.png | Bin 2920 -> 2937 bytes .../test/src/goldens/system_message_dark.png | Bin 1812 -> 1679 bytes .../test/src/goldens/system_message_light.png | Bin 1866 -> 1746 bytes .../test/src/message_action_modal_test.dart | 18 +++----- .../test/src/thread_header_test.dart | 1 + .../test/message_list_core_test.dart | 4 +- .../test/stream_channel_test.dart | 40 +++++++++++++----- .../test/stream_chat_core_test.dart | 26 ++++++------ 13 files changed, 59 insertions(+), 37 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/back_button_test.dart b/packages/stream_chat_flutter/test/src/back_button_test.dart index 3fae1ace..efb889ac 100644 --- a/packages/stream_chat_flutter/test/src/back_button_test.dart +++ b/packages/stream_chat_flutter/test/src/back_button_test.dart @@ -118,8 +118,9 @@ void main() { final clientState = MockClientState(); when(() => client.state).thenReturn(clientState); + when(() => clientState.totalUnreadCount).thenAnswer((_) => 0); when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(0)); + .thenAnswer((_) => Stream.value(0)); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/src/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel_header_test.dart index 9f6e981d..87f3d028 100644 --- a/packages/stream_chat_flutter/test/src/channel_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_header_test.dart @@ -34,6 +34,7 @@ void main() { .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); when(() => channelState.unreadCountStream) .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ @@ -109,6 +110,7 @@ void main() { .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); when(() => client.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); + when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(1)); @@ -171,6 +173,7 @@ void main() { ]); when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(1)); @@ -375,6 +378,7 @@ void main() { ]); when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(1)); diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png index c464bc01de48fcef06c538bd284ed284370f55aa..693db3c78ee724f41d6376bd5c7ba1eb1385750d 100644 GIT binary patch delta 1816 zcmV+z2j}?s7M>E2Kz|6xNkl`t918 zW=r}^wyN@nb8~`9;(r3==Ah7?x7$?0|NsZ9Uax+;9%TC zktE#9kTCG-`LqHE}wSQVVIy#DV46z2%Xf)zHJo>=y?ryBdk1ddslM`;p(behc zsZLH#Vmp3pfdoMi*P+p8;+!i4f}VFL0|EL0fdB>qxkZ+jmsPD+yY3eRffg1P)ND3y zU{u#1S1-CXfh;U6XmN4z%56aqXmN4zx7%-vot+)!a=EVi`MxjboPW~k^bL&a`s3$&(ntwA8uq^z3A2i629OU zmkqw}YinyunM|f{U!?2!lS%LU{v#D~qEa_~C)NZ#mJw4s^+;tmQzkj(ESCE~Z9c8mwWipwL zKs?V=GMSWfu4BzzsZ`W#HudJs8_mtlMf*zs1u#85t@-(R<@0&1uC8|7)^4}8w6vsb zHmmvhdCktwYI}P-x}w$Vb(PCyjgOCOZf;H!6BAlrUylN))oSuQPZJXpnxCInE|*iK zQn~W^R;yLj>wk5XN+r$B%|&kx;bU3P@8bQ(@Al2*a+;l;l_bs0%|&rokKJut{iyE) z3D>ykbXw_jI+~EpW>Z^RTPl@Gs@Ln$WxiIc$@l%}UC%kEY&IK(75@3~*M&ksGcz+? zw@K2>%!~?!f+R)nsG(}Ls$#LILZKk%oN~Eb$FajJTz@#3a=F}<|B$oYZfkvgJ=!;X z4C!p0-|hLGU-DdM-%KVGT>-8AyN#<~_5Bj-{EO(Ei(cGHrJ`c7sAMuJ-}j@5Td&vE zYPBwXqE##wC7n(BaH&)-mzB@wH8C;Kb(?fHfy(7_#|H)3Y*x)?Q>|7j8iRBeTMz`% zq?V+mrGKT4F0Zu_2}+}8fx#?`NGMIh(jsn*uk z2z94OG_#ii<+32 zkaJGeYBjpzgu#UG#Nn~RuzlZGKA-QnFZi8yyMNvBI2X@>_53d0e^~pT-+uAeSM6V| zR?TtstG-`ih4&xQf6JQiQZHRzbB~RUX=7s}y3*8YwdgOgn$4y>&+AxXIOmj1CcAD6 zf*?9Ztya_4)>bsGaN=xiY)CqLCth1y>j-;$dplY#`o6D9rE=v**3LOK8ja|fp67L( z1ApuJ{pq;~fXdKr5-Nx0g?!SNk{(o^C_~__JKY#xGtH1`ZGUY|o12>&85z;nuV0nV=ao*UH8eD&4}bbNfQ7cX8Yl}cTD&gpbo-@bj*r%#{s z`SWKv=hSRAqj!4i`E71)>fytO*E$!!e*MyyFJH92zOLubpKEAn$lSl%xciP*n254~?-U?A4->uQa4hK`Po#(8-3fw<>Ltbq&;4r+LKIL;xUhYb%8 z$NnYOv9EDPMn;GPmkbXN$M(t9d9i=Hd+6xsNV~hceB0t3mq;X($z)=`a)f#&kWly^ z=s7XA-_lTcw*IrhN-0000Z9#;F@OiS%YeXV+$nc91r*^VQS@vv_F`3$EE6}V1C`qut@p?JNJ z$0L!g9{h?k#YU60Y;A1=f`bj!U8(CSbpZhZXn)D(!r;oem;FV3B9Wl1OgwEC{W`%; z*M4L9-J)tk{-CkLaCC4Gb?V@W{olm+u6~K#mC^LmK!1NsteRCre0=5@p$guVSMbUYs$q-f%ZG-rIE}w&TMxC}W2)3i-8&lD zd*O7qlXN$oDmz-M5#5dL9lcRI^L6ZolGyR)O*W1|CI^0q_6rOQ+r=_J)lB`Dt2D%x+DeQ)Z^Rse!C1m#6@XQ~Vu17_A!eFrPM0gwy4L{%$85!A1 zEGsKR*xIVfG*3ZS|1I?M)vHYkYh8+7z-1%t;BSGUtiA7)Z0s{j0|XRB!S>gwXv~z z?CAB}S2;m()XsE)seRrvIy(2!tODoh?(QA{MCoh2T@pIZRi}0a7$7w?G{7XfJ3AlT zB9nXj`z0T7;M0#DKSo;|#Y9GOD=RB4936QYhfG^07L4+R5um;H_V&W!;ywJ%; z6Trdlbd5vH-R@`D-bOP6f(Y zm><|^X$$U3LV{mPT`B>WAg3w-GlpF>FVP^J3 zsZ>%ak(nGEJz2j=anbBBIwYiEX(X4;KH2os_jsr_$=`8(KFxgVFc|d6NS>W8C1qm; zi&U|$A`l4bGpY1CQnfXIrh5KP=VR-Fyu3VWI{-mvz9WxLhk1B-oTRn7QXesyf~qPG z(~?lgVqNU$=%8*^D|)-S{_O3=L`Fpw3IrHS!bmn8xbI3eFMjHG>P1tN^OyVfK~@)6 z#(S$)SK~cAJs%B{q5)e7b#&a?nAp6i*%VQH6SJD?bo{HtnxNe~cYvGA<(>rl3|tU* z=Ax35Ykn$u2zrDg`F#GOBB|{fzq89;OU+1I9Z6NHRA8Z<>!u`?yes*6UbU9S4mVO}skH>(4nDDA^h7EOZcADGR7>7I7k2~LOYjY!#NbM8W zNjg`rU31Z;H1Fkq@@raJ_uUrjbA1@NAQt<|ws*n?mikH{Kq{BQboSrFpil*yn<>r( zK1j&(Ex*6$PQ#NC@|?Z+d-e1?V4buxoU$(&K)oUntG3qR^y$;v`R8yrAm`1d?Nc`^ zVD;}4NvB}M8o=BX0sGD=i0ORBH;gpHCdL?Nt zFE4qupE|fUzup*N2_Tv4saN^mD_lnli=Q8W7!bE(u%K6xuyQ>%76s`gFK2<7$38Q4 zL1VEjfdJv*<;8ja+!=*J0q%>QyEC>?B9VkEHc}847Gc1IARhKlZ h$U5GC0pAKv z4-*p;<;L15EYl^oH9Thj_h-*O6$Oz!wmIeJ=S?D&!PzAxK481cqK19J`T+_?jn;C# z93aJH%bvSZsq__yPgd4f#JISyxHvR8jjS_EO6SJb3wkQo=0-_*=8X1M@zNjv`fMya z_baXO-Y$tuhH`iR?b(KGe$O0Kt(a!2-)mG#Sov@y@bi{9mjp*{rv!c=M$UXyS}Fw+ zw~16y^x}m}!;juH8V%ZpCKk(pTeA-8=;#2#1Ox^3EiS%{m6Y=N?-EBqfB`-tl9F&> z^KYqU%k&iCmHbelR6bm_&BpidADXjH%J28B>Lg)I1W`ZykX9YUOW9O}_V)Dr#o@H$ z>UzrC=n;Aq!vh01e(ZTjm;3knE&8fxlAsX8|Mr4_V(WT=sTgb1zI$R z2bGre6_XanE6IHo39#v{l2gnMskE0Potg0t4Q1uzWa2~5=4|; zfu{IF&0O+OYW;R8V@uXJ*TC?P(K%6l{cT@%Hh|jlSi`OwmAtCc?lVJX1j}zBVQ58o ztia9!fzVOqFbz8dSS=ZPw^d6=a+BcP&}Qw4Y)#8G7*?!LBod<>?AvY+s_t`A!RxsL z!qBmN&@CE;qH#i^FCeri%>_VMbLC~qRu1hYp?q_=x_i@d2GFV2M1%TiQ5w=T5d)5n zO;y^~lW{278;_q=R!=?3GD!%aJ Goqqs9Ee-zw diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_dark.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_dark.png index 7db0a6ef17a3f5586f9a0f7d67b1f49dd6fd5b60..d9bb3138bf3a71dbde33bda6e4605afbe76b5146 100644 GIT binary patch delta 1719 zcmV;o21xng71j}uKyCE<|^YbkM_@IGa4*)pNlTiU$lVAZ$lVAZ$lVAZ2F(iAn;loy|SDQv38b~C1 zvfZ8q(rUGI*lH=!qa8h$9}k1j+Y!SJvo$#EPx$@hPICYR01aUAW`YG*yPGjn_lYH!}0YTeTwi(EFV zzkUDxnT+c^x)^_f1VQ`_e|Opt3=eC3d|dTV1UXH&em(6NClac3n+NssFR4S?2Y)X>S zsgw${v+_JoVHj$qT-MgsR@Y^&>#8t2t1r@NNz$wBZIypYC52HGe{C+C)!^Wu_V@R- zy}hj<2=vcifBoR{rBdmmSKfEW?oF zKJ0qzx{a&fTuP_YfA7VHTkXFG`4qp+`)#qPN~NMJSFR`u!}z3{$>%k2>5|^mY8o9G z(UWpnl2m`3o0I2x`t8vpRVo#Yjg86oeO0T~IDq>PAL{YSiY|ZlnX=QV;9HTCFC}^Wv|QB+qpvNoyM$y8qxo*JYA) z|G|HQ_=Ilm?{|!|FgvTKm5SCkHWWsYwyRZz?U-HHjqh;Bac@_v@t96>T~||6Q}MdT z$iD48eXrH|Eb8~vyzH(g+0RjVnV%PE)5DvTnnZ*0hQU3s1tU&XElfgY7g@pD7RYYl%7 z%k?~!+VgYY_jP-5@qf=4A6eFDG<17$QBNxsmCI#?QKZ7mjQqhtNs8ZDJH8ShZ|gYh zAPDq)b2ENzi|4u>uk-FPu=elW=MQV$U!Z6f+v)Of-<2dm)GMS9V z$H(K7YxAAJUKC!hQVsu?`Op6-ANqxt#v}`|o*_|KpnNW;q~qxM^XJ;#+k1a+pBs&af*{cB*I#RFY)oMocH9-L{hRpmONCK%s$(%Q zFraiQrKzbYy?FIX2L}h{`rXF$XY}$RJuyE&-;@7d!K>sH6~X}(z0(@KRn`aoFUKcx4AwT%tU z7mM;dPxX3TOQlj@|M2XugeexzHKtg=IYuCWfj|HQfdB>q0Sp8J7<&41kQSd2f-YJ; z{)mj81(HZ4xP76kUVlVJ&jRV?hoS(d#YG&GApwe$U;#^$U;!x>_%Fneu2z=3CMo~` N002ovPDHLkV1k_0l*0f3 delta 2230 zcmV;n2ub(W5#be(Kx_(&Nkl?)dLhd1;NlrLHLns#kaa*+u)$L5{ zT6a79U+sU@+8L+Sb~qt{14Qpb`j}kRVA&_6I@+Z{OI<7-HYApLCwbljp#B zpGyLIPM$p35P+BRc6tH8K}M0$+LK@bB`$-nQ!l(zuWP5?=+w)ji}ZLs>hX5wT;8!j zB+2Xb$m8*JatS!#8ja4LM7hNRlRyC$e}=avkZ?GxP$=}u@nA5ha5(%{e=vFa)G1{$ znYQzKqfvb@JFDK_-Z!wS*I(B`^i~8C3I=uI{CWN5lTYM*$&Vz&Vln;o!UbJ8fBvQ8 zZ-YoUtRsViul32Z>$*xgj-Eb!+IDVlG^)(eqrdpX@^jX85WN*gOEQ^k{W%(qe?Bwj z@p$yd)2Ag##bQxAJ3DW5ZEs4_aU@BrtE+A2I?1F;rIKp3THCqfN>t8h51)wMNc1_qQ)r}g>8i!bbNI2_jbbLZso zc$8aR)!68$d_JF+mX_M?UteEe>)%;(?<_1R6b{Sh^J#s3UDax}?Y7&ks~dHFAfaGT z!9YN{wKc^P3B_Wu*4X=}PpecasZb~=?Ydga=jHeN_2JpGdidy(f397>E=f8wJF9p+ zE=ii4m{2Gf)Mpnjs#dG%$l#zRjvbTN>(#OG@mG%ZMx*k0Jo@(PRps+}`FuY8?bA;c z3r8&@_H9+GRSjh_dhgwL^@kHD z^ws6dDwWIT*xng`C7092#)iVpx13kk)|5yj(vLdvwHgUss4NGmJ-d&|K9UQAP`VKpV!szzH4p6V%i(@(qWI(xQtrjAA;l6HTK za?e02f2B&LqOGkhg+d`U8jY8(>3NYzM2$wHHRkbnluRbI^7wJv{d@lT!=&CH*vH1?p0qbrYqlk(>mxSwIV8)%ZkV28X6kXlZ_1x z4-f0mp+mZS<%$MfS3^TXs#Gc}mCG8+WK^%$e|OLFV`Hxz+q+ssA`vB%NxhTJDjtvP zLG$5st9b^Unw-?_J9m_JUAeBS<%bU?Nu^RrDaX;&jnj%YBQ zRyLcJB>nT+HC3xs6^ca-rqdc48q#n!t6Ht5>vMA|7K`TCcBLXGnbcr9t<2G*+ANjS z+uN&^Tu!U2tJ>Pyl9Nm-nMi0TlWEVO_d(N%eiVQ9j@91^smns9-RtN~QA35Adx0d)>KSuUCGbPutI3 z#jI_&Ti4IkXf))JexLtoLnl4|P2X?tU6|iuED#9j&*#o*C709Vl@+-I14=oLzP)<& znK-1^3uDjTJ58?~H}>y!=ho}>X_t;!+ith6Zu3_BsL4TAsZ{jC+?>LpkftUl<@fvb zkMF;4JqO@m``una-lV1F<<{pV=q_AA00V&l1_A*L1OgZc1TYW?VCd-Ey!CoRQg{BY zGTxjXZ|DA_jgAG<lY3<4pEcg2j7wZA{WE|0l%51RqPmgaR2}S07*qoM6N<$ Ef*kF!`v3p{ diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_light.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_light.png index d5a453ffc0cf157efe7d6a8e84648ab9a4a90530..1f6330eb9bd92be6130bae15872bfe6d51878d88 100644 GIT binary patch delta 1615 zcmV-V2C(_Q74Z;|Kz|4uNklLx2g!BEI-42W6ODdH5(ESyy6gwnH+%rXA?#h&NE7<+wo zGSkdGleuQ@iLnpjIQ}I7SNhMm2LRk<1OgZc1TYW?U?32{K$E@+>61_a2Rx?6wc(SK z6ZLw%acv$GB$Y~Oetv!|+a6mWQ55O;_*l-laSZ_zAdX}G`t|G23q+EfbDEu<9p?}L z6VH=j0UCez$j6T#)oeCLt{2C#Ha0dChT$!Y>gMCRi|$Pz8yg$i+}yl!SscgO+}yl& z`F(MCc&L0nKXSbw2;`hoCX=~^QQdr8chS8FBnSdYXCcIKd}+Dod5Ys$3kwUkJ+xah zI0m^~?ndk0^jLVFrw<=K+{(D_qI(m_;0?bxZ3usYKzn<8%4V|@`zGDQ@0|2O5Zw4! z{LZ@n>$vWsv3^UO&$k3|@Us@Ln1&ZbDc zUQa74DDv+>J&VUftI4|3 zKLM<)tZ03GU4=qH+uPeCm-Tu*)oL~6ayfskudi!uZB6_8`^g>6_kGptbuBI~s#>k8 zTrO*8XD11u)9J|bJeA93t*@^upUjsmS;JdWXk9BilXS!{`Pu3?dkeK+I-BQ@+Q0Kto|~+j&1RE3ptb&}aZRd;pJJVV z6Ph=222L-uYPGJ}-ilSr;(phYA949BWB-LuQp)p-GclUi?*=%;`6R7hi{iyv5f?#O6 zIBMPVC%3i!sBulIdlAU_D^;u2lIMAH≪w6h+B8hlht+US3X4l8dj^>-B$<`FR+I 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 WMtx2FJ+qtu0000VJUX<4B-HR z8jh3>1_m}uPZ!6KiaBp@`1{|EVru*Nov%#QeL|S0a{x!sqHCfljuRLC3q0DEYk#$7 zeI4Vb1V{1Ihpa2SLN;*(@#pF!uD;`Xuy|)nd6hZyEW7=(?5Cew{CIfdXL5S_^B@MX zU)O8(87fY2uo#RY>>-fS`1Nb)-pbF<_DyeOZ*UM>aWyN;Z1(rPJT?p$YxN%}zWiF% zwa7zTTl;E+i0iAbRg;#SOj%@ee(Sz{>n!?2T_>K}bfv;3Z2k4%$Z50umY@0@6DYFs z_FJtkrMdIxXIss^H$&SuSkyJ}uig3Lwd>YtoiFxX9y~R5XTdwy=XHt-Ag?DmHd+q0W+}TeohlzyDoHuF1sr>zkX#u`w|~*JWmAdLBQ1 z{MPN;moHtKl(lu%C6lYq&(GhwbLUFG<%{L*>sH);yXyY?*N+}K&GmB!I{eqCr`>UT zt3vM<%$_})OYz0;@9(eHOEXuQ_+HK1E_!_9CY{M&J}+B${q>|JKnbAq z+O@i-vwds-{0QuITWq!LW=>XpeSCU)x>lFd`T6$yw|2|O$-R2>#%K5L-LuZ7Sy@@F zdZycz^ze@7qzpSy0Bf}@SfnA==NU9Db=LXkSF0we?A*9c#dB5m|6gBMAA7vfi9?Eq z?f>fjzXz2kXnRi5Xemfu6SjJ#2G`O|El;3{U5|epGfa&PGk>yGqzJrC;y*$MneRn?RGE zzJ6&z!Gh*yW}rcjb{yg>Qax=1bkx`1f3tF|=Dzy!B?J^oz)z&!}1e)3oFMRfSCot96)&5$d(UsSGv~bPCf)(H2-w(HriJWF-XSc3Q z>gDsiiK#QMUAwkr`}Xj&XRS~8*S~r5CgvV6DRu#a*1a1`*N9;TnXy0>ySNW53ruPC>$PWFBhBU>ZG eC_)Z_WAl3py=T6&7ZU_lDh!^kelF{r5}E+l;D=KH literal 1812 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 GGywo&aKg+0 literal 1866 zcmeHI`%}_c6vr~hn9R~@OEhTKO4-aElX6{=LPOD1M8#G`+)9{)wuea;XR|jx#XuiI^SobqAZlN2l4Q$g7syqt+CTM%jqkq8%|yZ5$fSJ z)U$HwMKfs`hcmm+TicNWHJM))X^;#_33RC*V^JgySgAU z^j=*RBCAV~RqJ%W5XnrY7%#jtJO2dD%K~;)DOhjsAplcrt{s{hR?vhbe{Dhp()%*$ z0XcNfmN2*cSME$YouG`XH7=d;c!(DY{2j~2$LVWUrOWH|LncOD9-l9(ttAZ_6f@gy zK7A~e3iI-|Jd#MNNlq;Jmfma?-*tr}=J&6s)9Ihy_k2G!*bw4MYt<|)$b*YKggrf> zQ~gq(2V|nHt?h@&$)3^Cie0;RZ|3@b`l#F2l5D_peer6wI*LY%1n9TpQXjJRivfti zhSOrPI55Y*V*kv-XFZy)f}5|Bb4-%Fyu3|d_h_dr27__7yY;eoX}k*&@3w8H&DtS( zQXrK|y|l8tWX;g)_42bL8J?b=_c~96qj?FOv2#rWv48{V(Ib&Hu@T_D%Bm{xI)Tw> zbXh#bLM|45*_GS*UIT?CFqvWQcG~{_`&m!RfbETGL=gf1XkNhGiu81|BsM$YMG5wj zX}UPu;pa-Dkxgx82Vt>Tutt7<^6ooBBYXN4H1VQT|4~<0NH6hDi83U|s8lM?- z=H|_}2t3u;rgawj?&Zb#x`KiNO>b}6+qb`B+}$1l(IePr2Xrh7$22{3zP`S`@`M`%LC!%z zLC(B@lgE!AS7VfMq_=01)jAUspmTJ#;X37ZE}mz5*i(Mox>!m#eg1s1PePR^PGoQQ zN)}{fWXKr^1i}u1P=jE|8XHB&q@r_8dx!}v7LZMR{9zzTfSbIRABN^tQ@=KkY#S|n z{W^AR?&8ytvk~ac#FdM;ym7c1p>W6H@=!3m(}t^P=`-Qlnwox+$z%}8Nz~9_;v466 zAGzGMNf$1N6$+YZW;jwv`m5tM@`^_^l^TKO^}cx{0FmN#(U}|<7Z>OJBOie5;F;|K zyKZ7mc(A>_-Oj-wA~CV9HFc?4C=}8LCKz?OofQNELDSPC1X5gHP6Y^%-N&T& z|LEx81y?ao53Ej4>n_FB;fd3FiJRIyR!N* client.state).thenReturn(clientState); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(() => client.flagMessage(any())).thenThrow(ApiError( - '{}', - 500, - )); + when(() => client.flagMessage(any())) + .thenThrow(StreamChatNetworkError(ChatErrorCode.internalSystemError)); final themeData = ThemeData(); final streamTheme = StreamChatThemeData.fromTheme(themeData); @@ -609,10 +607,8 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(() => client.flagMessage(any())).thenThrow(ApiError( - '{"code":4}', - 400, - )); + when(() => client.flagMessage(any())) + .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); final themeData = ThemeData(); final streamTheme = StreamChatThemeData.fromTheme(themeData); @@ -719,10 +715,8 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(() => channel.deleteMessage(any())).thenThrow(ApiError( - '{}', - 500, - )); + when(() => channel.deleteMessage(any())) + .thenThrow(StreamChatNetworkError(ChatErrorCode.internalSystemError)); final themeData = ThemeData(); final streamTheme = StreamChatThemeData.fromTheme(themeData); diff --git a/packages/stream_chat_flutter/test/src/thread_header_test.dart b/packages/stream_chat_flutter/test/src/thread_header_test.dart index 578b2ad6..2b649be3 100644 --- a/packages/stream_chat_flutter/test/src/thread_header_test.dart +++ b/packages/stream_chat_flutter/test/src/thread_header_test.dart @@ -45,6 +45,7 @@ void main() { ]); when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(1)); diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index 4e34f8f2..98e83f9f 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -300,7 +300,9 @@ void main() { when(() => mockChannel.state.isUpToDate).thenReturn(false); when(() => mockChannel.initialized).thenAnswer((_) async => true); when(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), membersPagination: any(named: 'membersPagination'), messagesPagination: any(named: 'messagesPagination'), preferOffline: any(named: 'preferOffline'), diff --git a/packages/stream_chat_flutter_core/test/stream_channel_test.dart b/packages/stream_chat_flutter_core/test/stream_channel_test.dart index b48c02b1..2b32cf2d 100644 --- a/packages/stream_chat_flutter_core/test/stream_channel_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_channel_test.dart @@ -165,7 +165,9 @@ void main() { when(() => mockChannel.initialized).thenAnswer((_) async => true); final messages = _generateMessages(); when(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: any(named: 'messagesPagination'), membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -183,7 +185,9 @@ void main() { verify(() => mockChannel.initialized).called(1); verify(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: any(named: 'messagesPagination'), membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -228,7 +232,9 @@ void main() { final messages = _generateMessages(); when(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: beforePagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -236,7 +242,9 @@ void main() { )).thenAnswer((_) async => ChannelState(messages: messages)); when(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: afterPagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -259,7 +267,9 @@ void main() { await tester.pumpAndSettle(); verify(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: beforePagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -267,7 +277,9 @@ void main() { )).called(1); verify(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: afterPagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -285,7 +297,9 @@ void main() { ); when(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: updatedBeforePagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -293,7 +307,9 @@ void main() { )).thenAnswer((_) async => ChannelState(messages: messages)); when(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: updatedAfterPagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -303,7 +319,9 @@ void main() { await tester.pumpAndSettle(); verify(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: updatedBeforePagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -311,7 +329,9 @@ void main() { )).called(1); verify(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: updatedAfterPagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index 168454c8..f2b1b514 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -227,7 +227,7 @@ void main() { ); testWidgets( - 'didChangeAppLifecycleState should not call client.connect() ' + 'didChangeAppLifecycleState should not call client.openConnection() ' 'if connection is not available in case the ' 'widget lifestyle changes to AppLifecycleState.resume', (tester) async { @@ -238,8 +238,8 @@ void main() { final event = Event(); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.connect()).thenAnswer((_) async => event); - when(() => mockClient.disconnect()).thenAnswer((_) async => null); + when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -267,7 +267,7 @@ void main() { streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.resumed); - verifyNever(() => mockClient.connect()); + verifyNever(() => mockClient.openConnection()); }); }, ); @@ -327,8 +327,8 @@ void main() { final event = Event(); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.connect()).thenAnswer((_) async => event); - when(() => mockClient.disconnect()).thenAnswer((_) async => null); + when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -348,7 +348,7 @@ void main() { await Future.delayed(const Duration(seconds: 1)); - verify(() => mockClient.connect()).called(1); + verify(() => mockClient.openConnection()).called(1); }); }, ); @@ -376,8 +376,8 @@ void main() { final event = Event(); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.connect()).thenAnswer((_) async => event); - when(mockClient.disconnect).thenAnswer((_) async => null); + when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.connected); @@ -385,7 +385,7 @@ void main() { await Future.delayed(const Duration(seconds: 1)); - verify(() => mockClient.disconnect()).called(1); + verify(() => mockClient.closeConnection()).called(1); }); }, ); @@ -402,8 +402,8 @@ void main() { final event = Event(); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.connect()).thenAnswer((_) async => event); - when(() => mockClient.disconnect()).thenAnswer((_) async => null); + when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -432,7 +432,7 @@ void main() { await Future.delayed(const Duration(seconds: 1)); - verifyNever(() => mockClient.disconnect()); + verifyNever(() => mockClient.closeConnection()); }); }, ); From 865c4cbac952af8f452c150cbe035c78eff666a4 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 17 Jun 2021 12:58:23 +0530 Subject: [PATCH 377/599] feat: Added new docs --- .../deleting_and_hiding_channel.mdx | 31 ++++++++++ .../docs/Flutter/stream_chat/invites.mdx | 55 ++++++++++++++++++ .../Flutter/stream_chat/muting_channels.mdx | 56 +++++++++++++++++++ 3 files changed, 142 insertions(+) diff --git a/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx b/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx index 458a06cc..0630aded 100644 --- a/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx +++ b/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx @@ -4,3 +4,34 @@ sidebar_position: 13 title: Deleting And Hiding A Channel --- +### Deleting a Channel + +You can delete a Channel using the delete method. This marks the channel as deleted and hides all the content. + +```dart +await channel.delete(); +``` + +Note: If you recreate this channel, it will show up empty. Recovering old messages is not currently supported via the Stream Chat API. + +### Hiding a Channel + +Hiding a channel will remove it from query channel requests for that user until a new message is added. +Please keep in mind that hiding a channel is only available to members of that channel. + +Optionally you can also clear the entire message history of that channel for the user. +This way, when a new message is received, it will be the only one present in the channel. + +```dart +// hides the channel until a new message is added there +await channel.hide(); + +// shows a previously hidden channel +await channel.show(); + +// hide the channel and clear the message history +await channel.hide(clearHistory: true); +``` + +Note: You can still retrieve the list of hidden channels using the { "hidden" : true } query parameter. + diff --git a/docusaurus/docs/Flutter/stream_chat/invites.mdx b/docusaurus/docs/Flutter/stream_chat/invites.mdx index 7b3532fe..430970cd 100644 --- a/docusaurus/docs/Flutter/stream_chat/invites.mdx +++ b/docusaurus/docs/Flutter/stream_chat/invites.mdx @@ -4,3 +4,58 @@ sidebar_position: 11 title: Invites --- +### Inviting Users + +Stream Chat provides the ability to invite users to a channel via the `channel` method with the `invites` array. +Upon invitation, the end-user will receive a notification that they were invited to the specified channel. + +See the following for an example on how to invite a user by adding an invites array containing the user ID: + +```dart +final invite = client.channel("messaging", id: "awesome-chat", + extraData: { + "name": "Founder Chat", + "members": ["thierry", "tommaso", "nick"], + "invites": ["nick"], + }); + +await invite.create(); +``` + +### Accepting an Invite + +In order to accept an invite, you must use call the `acceptInvite` method. +The `acceptInvite` method accepts and object with an optional `message` property. +Please see below for an example of how to call `acceptInvite`: + +```dart +final channel = client.channel("messaging", id: "awesome-chat"); +await channel.acceptInvite(); +``` + +### Rejecting an Invite + +To reject an invite, call the `rejectInvite` method. +This method does not require a user ID as it pulls the user ID from the current session in store from the `connectUser` call. + +```dart +await channel.rejectInvite(); +``` + +### Query for Accepted Invites + +Querying for accepted invites is done via the `queryChannels` method. +This allows you to return a list of accepted invites with a single call. See below for an example: + +```dart +final invites = await client.queryChannels(filter: {"invite": "accepted"}); +``` + +### Query for Rejected Invites + +Similar to querying for accepted invites, you can query for rejected invites with `queryChannels`. +See below for an example: + +```dart +final rejected = await client.queryChannels(filter: {"invite": "rejected"}); +``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx b/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx index a6acee75..29874091 100644 --- a/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx +++ b/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx @@ -4,3 +4,59 @@ sidebar_position: 12 title: Muting Channels --- +Messages added to a channel will not trigger push notifications, nor change the unread count for the users that muted it. + +By default, mutes stay in place indefinitely until the user removes it; however, you can optionally set an expiration time. + +The list of muted channels and their expiration time is returned when the user connects. + +### Channel Mute + +```dart +// mute channel for current user +await channel.mute(); + +// mute a channel for 2 weeks +await channel.mute(expiration: Duration(days: 15)); + +// mute a channel for 10 seconds +await channel.mute(expiration: Duration(seconds: 10)); + +// check if channel is muted +channel.isMuted; +``` + +### Query Muted Channels + +Muted channels can be filtered or excluded by using the `muted` in your query channels filter. + +```dart +// retrieve all channels excluding muted ones +await client.queryChannels( + filter: { + 'members': { + r'$in': [userId], + }, + 'muted': false, + } + ); + +// retrieve all muted channels +await client.queryChannels( + filter: { + 'muted': true, + } + ); +``` + +### Remove a Channel Mute + +Whenever you need to unmute, you are able to. + +```dart +// unmute channel for current user +await channel.unmute(); + +// unmute channel for a user (server-side) +await channel.unmute({ user_id: userId }); +``` From 2d6cc796e86e0b6ee2b19ce05ff03edf8a6ecc53 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 16 Jun 2021 12:27:21 +0200 Subject: [PATCH 378/599] move jsons to fixtures --- .../stream_chat/lib/src/core/error/error.dart | 2 +- .../lib/src/core/models/channel_model.dart | 3 +- .../lib/src/core/models/own_user.dart | 11 +- .../stream_chat/lib/src/core/models/user.dart | 4 +- .../stream_chat/test/fixtures/action.json | 7 + .../stream_chat/test/fixtures/attachment.json | 29 + .../stream_chat/test/fixtures/channel.json | 7 + .../test/fixtures/channel_state.json | 832 +++++++++++ .../test/fixtures/channel_state_to_json.json | 418 ++++++ .../stream_chat/test/fixtures/command.json | 5 + .../stream_chat/test/fixtures/device.json | 4 + packages/stream_chat/test/fixtures/event.json | 29 + .../stream_chat/test/fixtures/member.json | 15 + .../stream_chat/test/fixtures/message.json | 65 + .../test/fixtures/message_to_json.json | 29 + .../stream_chat/test/fixtures/reaction.json | 18 + packages/stream_chat/test/fixtures/read.json | 7 + packages/stream_chat/test/fixtures/user.json | 4 + .../test/src/core/models/action_test.dart | 17 +- .../test/src/core/models/attachment_test.dart | 37 +- .../src/core/models/channel_state_test.dart | 1263 +---------------- .../test/src/core/models/channel_test.dart | 16 +- .../test/src/core/models/command_test.dart | 14 +- .../test/src/core/models/device_test.dart | 14 +- .../test/src/core/models/event_test.dart | 38 +- .../test/src/core/models/member_test.dart | 26 +- .../test/src/core/models/message_test.dart | 105 +- .../test/src/core/models/reaction_test.dart | 27 +- .../test/src/core/models/read_test.dart | 18 +- .../test/src/core/models/user_test.dart | 13 +- packages/stream_chat/test/src/utils.dart | 8 + .../lib/src/db/moor_chat_database.g.dart | 4 +- 32 files changed, 1530 insertions(+), 1559 deletions(-) create mode 100644 packages/stream_chat/test/fixtures/action.json create mode 100644 packages/stream_chat/test/fixtures/attachment.json create mode 100644 packages/stream_chat/test/fixtures/channel.json create mode 100644 packages/stream_chat/test/fixtures/channel_state.json create mode 100644 packages/stream_chat/test/fixtures/channel_state_to_json.json create mode 100644 packages/stream_chat/test/fixtures/command.json create mode 100644 packages/stream_chat/test/fixtures/device.json create mode 100644 packages/stream_chat/test/fixtures/event.json create mode 100644 packages/stream_chat/test/fixtures/member.json create mode 100644 packages/stream_chat/test/fixtures/message.json create mode 100644 packages/stream_chat/test/fixtures/message_to_json.json create mode 100644 packages/stream_chat/test/fixtures/reaction.json create mode 100644 packages/stream_chat/test/fixtures/read.json create mode 100644 packages/stream_chat/test/fixtures/user.json diff --git a/packages/stream_chat/lib/src/core/error/error.dart b/packages/stream_chat/lib/src/core/error/error.dart index 6f2c39a3..ac4a1e0d 100644 --- a/packages/stream_chat/lib/src/core/error/error.dart +++ b/packages/stream_chat/lib/src/core/error/error.dart @@ -1,2 +1,2 @@ export 'chat_error_code.dart'; -export 'stream_chat_error.dart'; \ No newline at end of file +export 'stream_chat_error.dart'; 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 74195c71..37e587ff 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.dart @@ -78,8 +78,7 @@ class ChannelModel { final DateTime? deletedAt; /// The count of this channel members - @JsonKey( - includeIfNull: false, toJson: Serializer.readOnly, defaultValue: 0) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly, defaultValue: 0) final int memberCount; /// Map of custom channel extraData 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 70529687..22705b05 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -61,21 +61,16 @@ class OwnUser extends User { /// List of users muted by the user @JsonKey( - includeIfNull: false, - toJson: Serializer.readOnly, - defaultValue: []) + includeIfNull: false, toJson: Serializer.readOnly, defaultValue: []) final List mutes; /// List of users muted by the user @JsonKey( - includeIfNull: false, - toJson: Serializer.readOnly, - defaultValue: []) + includeIfNull: false, toJson: Serializer.readOnly, defaultValue: []) final List channelMutes; /// Total unread messages by the user - @JsonKey( - includeIfNull: false, toJson: Serializer.readOnly, defaultValue: 0) + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly, defaultValue: 0) final int totalUnreadCount; /// Total unread channels by the user diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index de58055d..c2960af9 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -22,8 +22,8 @@ class User extends Equatable { updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json - factory User.fromJson(Map json) => _$UserFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields)); + factory User.fromJson(Map json) => + _$UserFromJson(Serializer.moveToExtraDataFromRoot(json, topLevelFields)); /// Known top level fields. /// Useful for [Serializer] methods. diff --git a/packages/stream_chat/test/fixtures/action.json b/packages/stream_chat/test/fixtures/action.json new file mode 100644 index 00000000..59da5684 --- /dev/null +++ b/packages/stream_chat/test/fixtures/action.json @@ -0,0 +1,7 @@ +{ + "name": "name", + "style": "style", + "text": "text", + "type": "type", + "value": "value" +} \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/attachment.json b/packages/stream_chat/test/fixtures/attachment.json new file mode 100644 index 00000000..faf38b90 --- /dev/null +++ b/packages/stream_chat/test/fixtures/attachment.json @@ -0,0 +1,29 @@ +{ + "type": "giphy", + "title": "awesome", + "title_link": "https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti", + "thumb_url": "https://media0.giphy.com/media/3o7TKnCdBx5cMg0qti/giphy.gif", + "actions": [ + { + "name": "image_action", + "text": "Send", + "style": "primary", + "type": "button", + "value": "send" + }, + { + "name": "image_action", + "text": "Shuffle", + "style": "default", + "type": "button", + "value": "shuffle" + }, + { + "name": "image_action", + "text": "Cancel", + "style": "default", + "type": "button", + "value": "cancel" + } + ] +} \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/channel.json b/packages/stream_chat/test/fixtures/channel.json new file mode 100644 index 00000000..2184cd6e --- /dev/null +++ b/packages/stream_chat/test/fixtures/channel.json @@ -0,0 +1,7 @@ +{ + "id": "test", + "type": "livestream", + "cid": "livestream:test", + "cats": true, + "fruit": ["bananas", "apples"] +} \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/channel_state.json b/packages/stream_chat/test/fixtures/channel_state.json new file mode 100644 index 00000000..1e6af605 --- /dev/null +++ b/packages/stream_chat/test/fixtures/channel_state.json @@ -0,0 +1,832 @@ + +{ + "channel": { + "id": "dev", + "type": "team", + "cid": "team:dev", + "last_message_at": "2020-01-30T13:43:41.062362Z", + "created_at": "2019-04-03T18:43:33.213373Z", + "updated_at": "2019-04-03T18:43:33.213374Z", + "team": "test", + "created_by": { + "id": "guido", + "role": "user", + "created_at": "2019-04-03T18:43:33.201036Z", + "updated_at": "2019-04-03T18:43:33.204713Z", + "banned": false, + "online": false, + "name": "Guido" + }, + "frozen": true, + "config": { + "created_at": "2019-11-07T22:29:26.776526Z", + "updated_at": "2019-11-07T22:29:48.286746Z", + "name": "team", + "typing_events": true, + "read_events": true, + "connect_events": true, + "search": true, + "reactions": true, + "replies": true, + "mutes": true, + "uploads": true, + "url_enrichment": true, + "message_retention": "infinite", + "max_message_length": 5000, + "automod": "disabled", + "automod_behavior": "flag", + "commands": [ + { + "name": "giphy", + "description": "Post a random gif to the channel", + "args": "[text]", + "set": "fun_set" + } + ] + }, + "name": "#dev", + "image": "https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png", + "example": 1 + }, + "messages": [ + { + "id": "dry-meadow-0-2b73cc8b-cd86-4a01-8d40-bd82ad07a030", + "text": "fasdfa", + "type": "regular", + "status": "SENT", + "silent": false, + "user": { + "id": "dry-meadow-0", + "role": "user", + "created_at": "2019-03-27T17:40:17.155892Z", + "updated_at": "2020-01-29T03:22:47.641589Z", + "last_active": "2020-01-29T03:22:47.63613Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Dry+meadow", + "name": "Dry meadow" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T03:23:02.843948Z", + "updated_at": "2020-01-29T03:23:02.843949Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-e8e74482-b4cd-48db-9d1e-30e6c191786f", + "text": "test message", + "type": "regular", + "user": { + "id": "dry-meadow-0", + "role": "user", + "created_at": "2019-03-27T17:40:17.155892Z", + "updated_at": "2020-01-29T03:22:47.641589Z", + "last_active": "2020-01-29T03:22:47.63613Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Dry+meadow", + "name": "Dry meadow" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T03:23:07.981091Z", + "updated_at": "2020-01-29T03:23:07.981091Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-53e6299f-9b97-4a9c-a27e-7e2dde49b7e0", + "text": "test message", + "type": "regular", + "user": { + "id": "dry-meadow-0", + "role": "user", + "created_at": "2019-03-27T17:40:17.155892Z", + "updated_at": "2020-01-29T03:22:47.641589Z", + "last_active": "2020-01-29T03:22:47.63613Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Dry+meadow", + "name": "Dry meadow" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T03:23:11.568022Z", + "updated_at": "2020-01-29T03:23:11.568022Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-80925be0-786e-40a5-b225-486518dafd35", + "text": "asdfadf", + "type": "regular", + "user": { + "id": "dry-meadow-0", + "role": "user", + "created_at": "2019-03-27T17:40:17.155892Z", + "updated_at": "2020-01-29T03:22:47.641589Z", + "last_active": "2020-01-29T03:22:47.63613Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Dry+meadow", + "name": "Dry meadow" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T03:32:57.403566Z", + "updated_at": "2020-01-29T03:32:57.403566Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-64d7970f-ede8-4b31-9738-1bc1756d2bfe", + "text": "test", + "type": "regular", + "user": { + "id": "dry-meadow-0", + "role": "user", + "created_at": "2019-03-27T17:40:17.155892Z", + "updated_at": "2020-01-29T03:22:47.641589Z", + "last_active": "2020-01-29T03:22:47.63613Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Dry+meadow", + "name": "Dry meadow" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T03:33:35.294802Z", + "updated_at": "2020-01-29T03:33:35.294802Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "withered-cell-0-84cbd760-cf55-4f7e-9207-c5f66cccc6dc", + "text": "hi", + "type": "regular", + "user": { + "id": "withered-cell-0", + "role": "user", + "created_at": "2020-01-29T03:34:01.698106Z", + "updated_at": "2020-01-29T03:34:01.708808Z", + "last_active": "2020-01-29T03:34:01.70353Z", + "banned": false, + "online": false, + "name": "Withered cell", + "image": "https://getstream.io/random_svg/?name=Withered+cell" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T03:34:27.393296Z", + "updated_at": "2020-01-29T03:34:27.393296Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-e9203588-43c3-40b1-91f7-f217fc42aa53", + "text": "fantastic", + "type": "regular", + "user": { + "id": "dry-meadow-0", + "role": "user", + "created_at": "2019-03-27T17:40:17.155892Z", + "updated_at": "2020-01-29T03:22:47.641589Z", + "last_active": "2020-01-29T03:22:47.63613Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Dry+meadow", + "name": "Dry meadow" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T03:34:37.638376Z", + "updated_at": "2020-01-29T03:34:37.638376Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "withered-cell-0-7e3552d7-7a0d-45f2-a856-e91b23a7e240", + "text": "nice to meet you", + "type": "regular", + "user": { + "id": "withered-cell-0", + "role": "user", + "created_at": "2020-01-29T03:34:01.698106Z", + "updated_at": "2020-01-29T03:34:01.708808Z", + "last_active": "2020-01-29T03:34:01.70353Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Withered+cell", + "name": "Withered cell" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T03:35:04.301566Z", + "updated_at": "2020-01-29T03:35:04.301566Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-1ffeafd4-e4fc-4c84-9394-9d7cb10fff42", + "text": "hey", + "type": "regular", + "user": { + "id": "dry-meadow-0", + "role": "user", + "created_at": "2019-03-27T17:40:17.155892Z", + "updated_at": "2020-01-29T03:22:47.641589Z", + "last_active": "2020-01-29T03:22:47.63613Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Dry+meadow", + "name": "Dry meadow" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T03:35:24.939084Z", + "updated_at": "2020-01-29T03:35:24.939085Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-3f147324-12c8-4b41-9fb5-2db88d065efa", + "text": "hello, everyone", + "type": "regular", + "user": { + "id": "dry-meadow-0", + "role": "user", + "created_at": "2019-03-27T17:40:17.155892Z", + "updated_at": "2020-01-29T03:22:47.641589Z", + "last_active": "2020-01-29T03:22:47.63613Z", + "banned": false, + "online": false, + "name": "Dry meadow", + "image": "https://getstream.io/random_svg/?name=Dry+meadow" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T03:35:33.101566Z", + "updated_at": "2020-01-29T03:35:33.101566Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-51a348ae-0c0a-44de-a556-eac7891c0cf0", + "text": "who is there?", + "type": "regular", + "user": { + "id": "dry-meadow-0", + "role": "user", + "created_at": "2019-03-27T17:40:17.155892Z", + "updated_at": "2020-01-29T03:22:47.641589Z", + "last_active": "2020-01-29T03:22:47.63613Z", + "banned": false, + "online": false, + "name": "Dry meadow", + "image": "https://getstream.io/random_svg/?name=Dry+meadow" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T03:35:45.458685Z", + "updated_at": "2020-01-29T03:35:45.458685Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "icy-recipe-7-a29e237b-8d81-4a97-9bc8-d42bca3f1356", + "text": "í•˜ė´", + "type": "regular", + "user": { + "id": "icy-recipe-7", + "role": "user", + "created_at": "2020-01-21T11:36:22.284503Z", + "updated_at": "2020-01-29T07:01:59.69882Z", + "last_active": "2020-01-29T07:01:59.693378Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Icy+recipe", + "name": "Icy recipe" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T07:02:11.535395Z", + "updated_at": "2020-01-29T07:02:11.535395Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "icy-recipe-7-935c396e-ddf8-4a9a-951c-0a12fa5bf055", + "text": "what are you doing?", + "type": "regular", + "user": { + "id": "icy-recipe-7", + "role": "user", + "created_at": "2020-01-21T11:36:22.284503Z", + "updated_at": "2020-01-29T07:01:59.69882Z", + "last_active": "2020-01-29T07:01:59.693378Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Icy+recipe", + "name": "Icy recipe" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T07:02:22.485136Z", + "updated_at": "2020-01-29T07:02:22.485136Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "throbbing-boat-5-1e4d5730-5ff0-4d25-9948-9f34ffda43e4", + "text": "👍", + "type": "regular", + "user": { + "id": "throbbing-boat-5", + "role": "user", + "created_at": "2019-07-30T06:29:53.060413Z", + "updated_at": "2020-01-29T14:11:27.80176Z", + "last_active": "2020-01-29T14:11:27.7963Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Throbbing+boat", + "name": "Throbbing boat" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T14:12:04.688552Z", + "updated_at": "2020-01-29T14:12:04.688552Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "snowy-credit-3-3e0c1a0d-d22f-42ee-b2a1-f9f49477bf21", + "text": "sdasas", + "type": "regular", + "user": { + "id": "snowy-credit-3", + "role": "user", + "created_at": "2020-01-29T15:29:03.693312Z", + "updated_at": "2020-01-29T15:29:03.702648Z", + "last_active": "2020-01-29T15:29:03.696144Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Snowy+credit", + "name": "Snowy credit" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T15:29:36.011315Z", + "updated_at": "2020-01-29T15:29:36.011316Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "snowy-credit-3-3319537e-2d0e-4876-8170-a54f046e4b7d", + "text": "cjshsa", + "type": "regular", + "user": { + "id": "snowy-credit-3", + "role": "user", + "created_at": "2020-01-29T15:29:03.693312Z", + "updated_at": "2020-01-29T15:29:03.702648Z", + "last_active": "2020-01-29T15:29:03.696144Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Snowy+credit", + "name": "Snowy credit" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T15:29:41.677819Z", + "updated_at": "2020-01-29T15:29:41.677819Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "snowy-credit-3-cfaf0b46-1daa-49c5-947c-b16d6697487d", + "text": "nhisagdhsadz", + "type": "regular", + "user": { + "id": "snowy-credit-3", + "role": "user", + "created_at": "2020-01-29T15:29:03.693312Z", + "updated_at": "2020-01-29T15:29:03.702648Z", + "last_active": "2020-01-29T15:29:03.696144Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Snowy+credit", + "name": "Snowy credit" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T15:29:43.354177Z", + "updated_at": "2020-01-29T15:29:43.354177Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "snowy-credit-3-cebe25a7-a3a3-49fc-9919-91c6725e81f3", + "text": "hvadhsahzd", + "type": "regular", + "user": { + "id": "snowy-credit-3", + "role": "user", + "created_at": "2020-01-29T15:29:03.693312Z", + "updated_at": "2020-01-29T15:29:03.702648Z", + "last_active": "2020-01-29T15:29:03.696144Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Snowy+credit", + "name": "Snowy credit" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T15:29:44.754713Z", + "updated_at": "2020-01-29T15:29:44.754713Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "divine-glade-9-0cea9262-5766-48e9-8b22-311870aed3bf", + "text": "hello", + "type": "regular", + "user": { + "id": "divine-glade-9", + "role": "user", + "created_at": "2020-01-29T17:02:18.312524Z", + "updated_at": "2020-01-29T17:02:18.320187Z", + "last_active": "2020-01-29T17:02:18.315074Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Divine+glade", + "name": "Divine glade" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T17:02:36.933852Z", + "updated_at": "2020-01-29T17:02:36.933852Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "red-firefly-9-c4e9007b-bb7d-4238-ae08-5f8e3cd03d73", + "text": "hello", + "type": "regular", + "user": { + "id": "red-firefly-9", + "role": "user", + "created_at": "2019-08-02T18:56:39.366516Z", + "updated_at": "2020-01-29T22:13:50.491769Z", + "last_active": "2020-01-29T22:13:50.450215Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Red+firefly", + "name": "Red firefly" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-29T22:14:08.54062Z", + "updated_at": "2020-01-29T22:14:08.54062Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "bitter-glade-2-02aee4eb-4093-4736-808b-2de75820e854", + "text": "hello", + "type": "regular", + "user": { + "id": "bitter-glade-2", + "role": "user", + "created_at": "2020-01-30T13:08:56.190678Z", + "updated_at": "2020-01-30T13:08:56.200333Z", + "last_active": "2020-01-30T13:08:56.193882Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Bitter+glade", + "name": "Bitter glade" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-30T13:11:37.191293Z", + "updated_at": "2020-01-30T13:11:37.191293Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "morning-sea-1-0c700bcb-46dd-4224-b590-e77bdbccc480", + "text": "http://jaeger.ui.gtstrm.com/", + "type": "regular", + "user": { + "id": "morning-sea-1", + "role": "user", + "created_at": "2019-07-22T09:19:07.505207Z", + "updated_at": "2020-01-30T13:33:05.831856Z", + "last_active": "2020-01-30T13:33:05.825369Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Morning+sea", + "name": "Morning sea" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-30T13:33:16.853116Z", + "updated_at": "2020-01-30T13:33:16.853116Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "ancient-salad-0-53e8b4e6-5b7b-43ad-aeee-8bfb6a9ed0be", + "text": "hi", + "type": "regular", + "user": { + "id": "ancient-salad-0", + "role": "user", + "created_at": "2020-01-30T13:34:29.286813Z", + "updated_at": "2020-01-30T13:34:29.296196Z", + "last_active": "2020-01-30T13:34:29.289964Z", + "banned": false, + "online": true, + "image": "https://getstream.io/random_svg/?name=Ancient+salad", + "name": "Ancient salad" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-30T13:36:52.749731Z", + "updated_at": "2020-01-30T13:36:52.749732Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "ancient-salad-0-8c225075-bd4c-42e2-8024-530aae13cd40", + "text": "hi", + "type": "regular", + "user": { + "id": "ancient-salad-0", + "role": "user", + "created_at": "2020-01-30T13:34:29.286813Z", + "updated_at": "2020-01-30T13:34:29.296196Z", + "last_active": "2020-01-30T13:34:29.289964Z", + "banned": false, + "online": true, + "image": "https://getstream.io/random_svg/?name=Ancient+salad", + "name": "Ancient salad" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-30T13:37:41.631056Z", + "updated_at": "2020-01-30T13:37:41.631056Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "proud-sea-7-17802096-cbf8-4e3c-addd-4ee31f4c8b5c", + "text": "😃", + "type": "regular", + "user": { + "id": "proud-sea-7", + "role": "user", + "created_at": "2020-01-30T13:43:03.903006Z", + "updated_at": "2020-01-30T13:43:03.912307Z", + "last_active": "2020-01-30T13:43:03.906236Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Proud+sea", + "name": "Proud sea" + }, + "attachments": [], + "latest_reactions": [], + "own_reactions": [], + "reaction_counts": {}, + "reaction_scores": {}, + "reply_count": 0, + "created_at": "2020-01-30T13:43:41.062362Z", + "updated_at": "2020-01-30T13:43:41.062362Z", + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + } + ], + "watcher_count": 5, + "members": [] +} \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/channel_state_to_json.json b/packages/stream_chat/test/fixtures/channel_state_to_json.json new file mode 100644 index 00000000..4b8a0369 --- /dev/null +++ b/packages/stream_chat/test/fixtures/channel_state_to_json.json @@ -0,0 +1,418 @@ + +{ + "channel": { + "id": "dev", + "type": "team", + "frozen": true, + "name": "#dev", + "image": "https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png", + "example": 1 + }, + "watchers": [], + "read": [], + "messages": [ + { + "id": "dry-meadow-0-2b73cc8b-cd86-4a01-8d40-bd82ad07a030", + "text": "fasdfa", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-e8e74482-b4cd-48db-9d1e-30e6c191786f", + "text": "test message", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-53e6299f-9b97-4a9c-a27e-7e2dde49b7e0", + "text": "test message", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-80925be0-786e-40a5-b225-486518dafd35", + "text": "asdfadf", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-64d7970f-ede8-4b31-9738-1bc1756d2bfe", + "text": "test", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "withered-cell-0-84cbd760-cf55-4f7e-9207-c5f66cccc6dc", + "text": "hi", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-e9203588-43c3-40b1-91f7-f217fc42aa53", + "text": "fantastic", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "withered-cell-0-7e3552d7-7a0d-45f2-a856-e91b23a7e240", + "text": "nice to meet you", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-1ffeafd4-e4fc-4c84-9394-9d7cb10fff42", + "text": "hey", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-3f147324-12c8-4b41-9fb5-2db88d065efa", + "text": "hello, everyone", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "dry-meadow-0-51a348ae-0c0a-44de-a556-eac7891c0cf0", + "text": "who is there?", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "icy-recipe-7-a29e237b-8d81-4a97-9bc8-d42bca3f1356", + "text": "í•˜ė´", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "icy-recipe-7-935c396e-ddf8-4a9a-951c-0a12fa5bf055", + "text": "what are you doing?", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "throbbing-boat-5-1e4d5730-5ff0-4d25-9948-9f34ffda43e4", + "text": "👍", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "snowy-credit-3-3e0c1a0d-d22f-42ee-b2a1-f9f49477bf21", + "text": "sdasas", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "snowy-credit-3-3319537e-2d0e-4876-8170-a54f046e4b7d", + "text": "cjshsa", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "snowy-credit-3-cfaf0b46-1daa-49c5-947c-b16d6697487d", + "text": "nhisagdhsadz", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "snowy-credit-3-cebe25a7-a3a3-49fc-9919-91c6725e81f3", + "text": "hvadhsahzd", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "divine-glade-9-0cea9262-5766-48e9-8b22-311870aed3bf", + "text": "hello", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "red-firefly-9-c4e9007b-bb7d-4238-ae08-5f8e3cd03d73", + "text": "hello", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "bitter-glade-2-02aee4eb-4093-4736-808b-2de75820e854", + "text": "hello", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "morning-sea-1-0c700bcb-46dd-4224-b590-e77bdbccc480", + "text": "http://jaeger.ui.gtstrm.com/", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "ancient-salad-0-53e8b4e6-5b7b-43ad-aeee-8bfb6a9ed0be", + "text": "hi", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "ancient-salad-0-8c225075-bd4c-42e2-8024-530aae13cd40", + "text": "hi", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + { + "id": "proud-sea-7-17802096-cbf8-4e3c-addd-4ee31f4c8b5c", + "text": "😃", + "attachments": [], + "parent_id": null, + "quoted_message": null, + "quoted_message_id": null, + "show_in_channel": null, + "mentioned_users": [], + "status": "SENT", + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + } + ], + "pinned_messages": [], + "members": [], + "watcher_count": 5 +} diff --git a/packages/stream_chat/test/fixtures/command.json b/packages/stream_chat/test/fixtures/command.json new file mode 100644 index 00000000..38a169a5 --- /dev/null +++ b/packages/stream_chat/test/fixtures/command.json @@ -0,0 +1,5 @@ +{ + "name": "giphy", + "description": "Post a random gif to the channel", + "args": "[text]" +} \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/device.json b/packages/stream_chat/test/fixtures/device.json new file mode 100644 index 00000000..4555dc1e --- /dev/null +++ b/packages/stream_chat/test/fixtures/device.json @@ -0,0 +1,4 @@ +{ + "id": "device-id", + "push_provider": "push-provider" +} \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/event.json b/packages/stream_chat/test/fixtures/event.json new file mode 100644 index 00000000..2568af02 --- /dev/null +++ b/packages/stream_chat/test/fixtures/event.json @@ -0,0 +1,29 @@ +{ + "type": "type", + "cid": "cid", + "connection_id": "connectionId", + "created_at": "2019-04-03T18:43:33.213374Z", + "me": { + "id": "dry-meadow-0", + "role": "user", + "created_at": "2019-03-27T17:40:17.155892Z", + "updated_at": "2020-01-29T03:22:47.641589Z", + "last_active": "2020-01-29T03:22:47.63613Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Dry+meadow", + "name": "Dry meadow" + }, + "parent_id": null, + "user": { + "id": "dry-meadow-0", + "role": "user", + "created_at": "2019-03-27T17:40:17.155892Z", + "updated_at": "2020-01-29T03:22:47.641589Z", + "last_active": "2020-01-29T03:22:47.63613Z", + "banned": false, + "online": false, + "image": "https://getstream.io/random_svg/?name=Dry+meadow", + "name": "Dry meadow" + } +} \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/member.json b/packages/stream_chat/test/fixtures/member.json new file mode 100644 index 00000000..a33acf13 --- /dev/null +++ b/packages/stream_chat/test/fixtures/member.json @@ -0,0 +1,15 @@ +{ + "user": { + "id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e", + "role": "user", + "created_at": "2020-01-28T22:17:30.826259Z", + "updated_at": "2020-01-28T22:17:31.101222Z", + "banned": false, + "online": false, + "name": "Robin Papa", + "image": "https://pbs.twimg.com/profile_images/669512187778498560/L7wQctBt.jpg" + }, + "role": "member", + "created_at": "2020-01-28T22:17:30.95443Z", + "updated_at": "2020-01-28T22:17:30.95443Z" +} \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/message.json b/packages/stream_chat/test/fixtures/message.json new file mode 100644 index 00000000..d469639b --- /dev/null +++ b/packages/stream_chat/test/fixtures/message.json @@ -0,0 +1,65 @@ +{ + "id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", + "text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", + "type": "regular", + "silent": false, + "status": "SENT", + "user": { + "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", + "role": "user", + "created_at": "2020-01-28T22:17:30.83015Z", + "updated_at": "2020-01-28T22:17:31.19435Z", + "banned": false, + "online": false, + "image": "https://randomuser.me/api/portraits/women/2.jpg", + "name": "Mia Denys" + }, + "attachments": [ + { + "type": "video", + "author_name": "GIPHY", + "title": "The Lion King Disney GIF - Find \u0026 Share on GIPHY", + "title_link": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", + "text": "Discover \u0026 share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", + "image_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", + "thumb_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", + "asset_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4", + "og_scrape_url": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA" + } + ], + "latest_reactions": [ + { + "message_id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", + "user_id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", + "user": { + "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", + "role": "user", + "created_at": "2020-01-28T22:17:30.83015Z", + "updated_at": "2020-01-28T22:17:31.19435Z", + "banned": false, + "online": false, + "image": "https://randomuser.me/api/portraits/women/2.jpg", + "name": "Mia Denys" + }, + "type": "love", + "score": 1, + "created_at": "2020-01-28T22:17:31.128376Z", + "updated_at": "2020-01-28T22:17:31.128376Z" + } + ], + "own_reactions": [], + "reaction_counts": { + "love": 1 + }, + "reaction_scores": { + "love": 1 + }, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null, + "reply_count": 0, + "created_at": "2020-01-28T22:17:31.107978Z", + "updated_at": "2020-01-28T22:17:31.130506Z", + "mentioned_users": [] +} \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/message_to_json.json b/packages/stream_chat/test/fixtures/message_to_json.json new file mode 100644 index 00000000..b3b1dd6b --- /dev/null +++ b/packages/stream_chat/test/fixtures/message_to_json.json @@ -0,0 +1,29 @@ +{ + "id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", + "text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", + "silent": false, + "attachments": [ + { + "type": "video", + "title_link": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", + "title": "The Lion King Disney GIF - Find & Share on GIPHY", + "thumb_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", + "text": "Discover & share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", + "og_scrape_url": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", + "image_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", + "author_name": "GIPHY", + "asset_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4", + "actions": [] + } + ], + "mentioned_users": [], + "parent_id": "parentId", + "quoted_message": null, + "quoted_message_id": null, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null, + "show_in_channel": true, + "hey": "test" +} \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/reaction.json b/packages/stream_chat/test/fixtures/reaction.json new file mode 100644 index 00000000..ce87ca1d --- /dev/null +++ b/packages/stream_chat/test/fixtures/reaction.json @@ -0,0 +1,18 @@ +{ + "message_id": "76cd8c82-b557-4e48-9d12-87995d3a0e04", + "user_id": "2de0297c-f3f2-489d-b930-ef77342edccf", + "user": { + "id": "2de0297c-f3f2-489d-b930-ef77342edccf", + "role": "user", + "created_at": "2020-01-28T22:17:30.810011Z", + "updated_at": "2020-01-28T22:17:31.077195Z", + "banned": false, + "online": false, + "image": "https://randomuser.me/api/portraits/women/45.jpg", + "name": "Daisy Morgan" + }, + "type": "wow", + "score": 1, + "created_at": "2020-01-28T22:17:31.108742Z", + "updated_at": "2020-01-28T22:17:31.108742Z" +} \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/read.json b/packages/stream_chat/test/fixtures/read.json new file mode 100644 index 00000000..7dc596d3 --- /dev/null +++ b/packages/stream_chat/test/fixtures/read.json @@ -0,0 +1,7 @@ +{ + "user": { + "id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e" + }, + "last_read": "2020-01-28T22:17:30.966485504Z", + "unread_messages": 10 +} \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/user.json b/packages/stream_chat/test/fixtures/user.json new file mode 100644 index 00000000..1fad8ea0 --- /dev/null +++ b/packages/stream_chat/test/fixtures/user.json @@ -0,0 +1,4 @@ +{ + "id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e", + "role": "test-role" +} \ No newline at end of file diff --git a/packages/stream_chat/test/src/core/models/action_test.dart b/packages/stream_chat/test/src/core/models/action_test.dart index d0108948..0cf1c996 100644 --- a/packages/stream_chat/test/src/core/models/action_test.dart +++ b/packages/stream_chat/test/src/core/models/action_test.dart @@ -1,21 +1,12 @@ -import 'dart:convert'; - -import 'package:test/test.dart'; import 'package:stream_chat/src/core/models/action.dart'; +import 'package:test/test.dart'; + +import '../../utils.dart'; void main() { group('src/models/action', () { - const jsonExample = ''' - { - "name": "name", - "style": "style", - "text": "text", - "type": "type", - "value": "value" - }'''; - test('should parse json correctly', () { - final action = Action.fromJson(json.decode(jsonExample)); + final action = Action.fromJson(jsonFixture('action.json')); expect(action.name, 'name'); expect(action.style, 'style'); expect(action.text, 'text'); diff --git a/packages/stream_chat/test/src/core/models/attachment_test.dart b/packages/stream_chat/test/src/core/models/attachment_test.dart index a433bedd..37716656 100644 --- a/packages/stream_chat/test/src/core/models/attachment_test.dart +++ b/packages/stream_chat/test/src/core/models/attachment_test.dart @@ -1,44 +1,13 @@ -import 'dart:convert'; - import 'package:stream_chat/src/core/models/action.dart'; import 'package:stream_chat/src/core/models/attachment.dart'; import 'package:test/test.dart'; +import '../../utils.dart'; + void main() { group('src/models/attachment', () { - const jsonExample = ''' - { - "type": "giphy", - "title": "awesome", - "title_link": "https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti", - "thumb_url": "https://media0.giphy.com/media/3o7TKnCdBx5cMg0qti/giphy.gif", - "actions": [ - { - "name": "image_action", - "text": "Send", - "style": "primary", - "type": "button", - "value": "send" - }, - { - "name": "image_action", - "text": "Shuffle", - "style": "default", - "type": "button", - "value": "shuffle" - }, - { - "name": "image_action", - "text": "Cancel", - "style": "default", - "type": "button", - "value": "cancel" - } - ] -}'''; - test('should parse json correctly', () { - final attachment = Attachment.fromJson(json.decode(jsonExample)); + final attachment = Attachment.fromJson(jsonFixture('attachment.json')); expect(attachment.type, 'giphy'); expect(attachment.title, 'awesome'); expect( diff --git a/packages/stream_chat/test/src/core/models/channel_state_test.dart b/packages/stream_chat/test/src/core/models/channel_state_test.dart index ad5245a2..8bd17d46 100644 --- a/packages/stream_chat/test/src/core/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/core/models/channel_state_test.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:stream_chat/src/core/models/channel_config.dart'; import 'package:stream_chat/src/core/models/channel_state.dart'; import 'package:stream_chat/src/core/models/command.dart'; @@ -8,843 +6,13 @@ import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:test/test.dart'; +import '../../utils.dart'; + void main() { group('src/models/channel_state', () { - const jsonExample = ''' - { - "channel": { - "id": "dev", - "type": "team", - "cid": "team:dev", - "last_message_at": "2020-01-30T13:43:41.062362Z", - "created_at": "2019-04-03T18:43:33.213373Z", - "updated_at": "2019-04-03T18:43:33.213374Z", - "team": "test", - "created_by": { - "id": "guido", - "role": "user", - "created_at": "2019-04-03T18:43:33.201036Z", - "updated_at": "2019-04-03T18:43:33.204713Z", - "banned": false, - "online": false, - "name": "Guido" - }, - "frozen": true, - "config": { - "created_at": "2019-11-07T22:29:26.776526Z", - "updated_at": "2019-11-07T22:29:48.286746Z", - "name": "team", - "typing_events": true, - "read_events": true, - "connect_events": true, - "search": true, - "reactions": true, - "replies": true, - "mutes": true, - "uploads": true, - "url_enrichment": true, - "message_retention": "infinite", - "max_message_length": 5000, - "automod": "disabled", - "automod_behavior": "flag", - "commands": [ - { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]", - "set": "fun_set" - } - ] - }, - "name": "#dev", - "image": "https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png", - "example": 1 - }, - "messages": [ - { - "id": "dry-meadow-0-2b73cc8b-cd86-4a01-8d40-bd82ad07a030", - "text": "fasdfa", - "type": "regular", - "status": "SENT", - "silent": false, - "user": { - "id": "dry-meadow-0", - "role": "user", - "created_at": "2019-03-27T17:40:17.155892Z", - "updated_at": "2020-01-29T03:22:47.641589Z", - "last_active": "2020-01-29T03:22:47.63613Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Dry+meadow", - "name": "Dry meadow" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T03:23:02.843948Z", - "updated_at": "2020-01-29T03:23:02.843949Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-e8e74482-b4cd-48db-9d1e-30e6c191786f", - "text": "test message", - "type": "regular", - "user": { - "id": "dry-meadow-0", - "role": "user", - "created_at": "2019-03-27T17:40:17.155892Z", - "updated_at": "2020-01-29T03:22:47.641589Z", - "last_active": "2020-01-29T03:22:47.63613Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Dry+meadow", - "name": "Dry meadow" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T03:23:07.981091Z", - "updated_at": "2020-01-29T03:23:07.981091Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-53e6299f-9b97-4a9c-a27e-7e2dde49b7e0", - "text": "test message", - "type": "regular", - "user": { - "id": "dry-meadow-0", - "role": "user", - "created_at": "2019-03-27T17:40:17.155892Z", - "updated_at": "2020-01-29T03:22:47.641589Z", - "last_active": "2020-01-29T03:22:47.63613Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Dry+meadow", - "name": "Dry meadow" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T03:23:11.568022Z", - "updated_at": "2020-01-29T03:23:11.568022Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-80925be0-786e-40a5-b225-486518dafd35", - "text": "asdfadf", - "type": "regular", - "user": { - "id": "dry-meadow-0", - "role": "user", - "created_at": "2019-03-27T17:40:17.155892Z", - "updated_at": "2020-01-29T03:22:47.641589Z", - "last_active": "2020-01-29T03:22:47.63613Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Dry+meadow", - "name": "Dry meadow" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T03:32:57.403566Z", - "updated_at": "2020-01-29T03:32:57.403566Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-64d7970f-ede8-4b31-9738-1bc1756d2bfe", - "text": "test", - "type": "regular", - "user": { - "id": "dry-meadow-0", - "role": "user", - "created_at": "2019-03-27T17:40:17.155892Z", - "updated_at": "2020-01-29T03:22:47.641589Z", - "last_active": "2020-01-29T03:22:47.63613Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Dry+meadow", - "name": "Dry meadow" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T03:33:35.294802Z", - "updated_at": "2020-01-29T03:33:35.294802Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "withered-cell-0-84cbd760-cf55-4f7e-9207-c5f66cccc6dc", - "text": "hi", - "type": "regular", - "user": { - "id": "withered-cell-0", - "role": "user", - "created_at": "2020-01-29T03:34:01.698106Z", - "updated_at": "2020-01-29T03:34:01.708808Z", - "last_active": "2020-01-29T03:34:01.70353Z", - "banned": false, - "online": false, - "name": "Withered cell", - "image": "https://getstream.io/random_svg/?name=Withered+cell" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T03:34:27.393296Z", - "updated_at": "2020-01-29T03:34:27.393296Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-e9203588-43c3-40b1-91f7-f217fc42aa53", - "text": "fantastic", - "type": "regular", - "user": { - "id": "dry-meadow-0", - "role": "user", - "created_at": "2019-03-27T17:40:17.155892Z", - "updated_at": "2020-01-29T03:22:47.641589Z", - "last_active": "2020-01-29T03:22:47.63613Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Dry+meadow", - "name": "Dry meadow" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T03:34:37.638376Z", - "updated_at": "2020-01-29T03:34:37.638376Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "withered-cell-0-7e3552d7-7a0d-45f2-a856-e91b23a7e240", - "text": "nice to meet you", - "type": "regular", - "user": { - "id": "withered-cell-0", - "role": "user", - "created_at": "2020-01-29T03:34:01.698106Z", - "updated_at": "2020-01-29T03:34:01.708808Z", - "last_active": "2020-01-29T03:34:01.70353Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Withered+cell", - "name": "Withered cell" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T03:35:04.301566Z", - "updated_at": "2020-01-29T03:35:04.301566Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-1ffeafd4-e4fc-4c84-9394-9d7cb10fff42", - "text": "hey", - "type": "regular", - "user": { - "id": "dry-meadow-0", - "role": "user", - "created_at": "2019-03-27T17:40:17.155892Z", - "updated_at": "2020-01-29T03:22:47.641589Z", - "last_active": "2020-01-29T03:22:47.63613Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Dry+meadow", - "name": "Dry meadow" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T03:35:24.939084Z", - "updated_at": "2020-01-29T03:35:24.939085Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-3f147324-12c8-4b41-9fb5-2db88d065efa", - "text": "hello, everyone", - "type": "regular", - "user": { - "id": "dry-meadow-0", - "role": "user", - "created_at": "2019-03-27T17:40:17.155892Z", - "updated_at": "2020-01-29T03:22:47.641589Z", - "last_active": "2020-01-29T03:22:47.63613Z", - "banned": false, - "online": false, - "name": "Dry meadow", - "image": "https://getstream.io/random_svg/?name=Dry+meadow" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T03:35:33.101566Z", - "updated_at": "2020-01-29T03:35:33.101566Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-51a348ae-0c0a-44de-a556-eac7891c0cf0", - "text": "who is there?", - "type": "regular", - "user": { - "id": "dry-meadow-0", - "role": "user", - "created_at": "2019-03-27T17:40:17.155892Z", - "updated_at": "2020-01-29T03:22:47.641589Z", - "last_active": "2020-01-29T03:22:47.63613Z", - "banned": false, - "online": false, - "name": "Dry meadow", - "image": "https://getstream.io/random_svg/?name=Dry+meadow" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T03:35:45.458685Z", - "updated_at": "2020-01-29T03:35:45.458685Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "icy-recipe-7-a29e237b-8d81-4a97-9bc8-d42bca3f1356", - "text": "í•˜ė´", - "type": "regular", - "user": { - "id": "icy-recipe-7", - "role": "user", - "created_at": "2020-01-21T11:36:22.284503Z", - "updated_at": "2020-01-29T07:01:59.69882Z", - "last_active": "2020-01-29T07:01:59.693378Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Icy+recipe", - "name": "Icy recipe" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T07:02:11.535395Z", - "updated_at": "2020-01-29T07:02:11.535395Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "icy-recipe-7-935c396e-ddf8-4a9a-951c-0a12fa5bf055", - "text": "what are you doing?", - "type": "regular", - "user": { - "id": "icy-recipe-7", - "role": "user", - "created_at": "2020-01-21T11:36:22.284503Z", - "updated_at": "2020-01-29T07:01:59.69882Z", - "last_active": "2020-01-29T07:01:59.693378Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Icy+recipe", - "name": "Icy recipe" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T07:02:22.485136Z", - "updated_at": "2020-01-29T07:02:22.485136Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "throbbing-boat-5-1e4d5730-5ff0-4d25-9948-9f34ffda43e4", - "text": "👍", - "type": "regular", - "user": { - "id": "throbbing-boat-5", - "role": "user", - "created_at": "2019-07-30T06:29:53.060413Z", - "updated_at": "2020-01-29T14:11:27.80176Z", - "last_active": "2020-01-29T14:11:27.7963Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Throbbing+boat", - "name": "Throbbing boat" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T14:12:04.688552Z", - "updated_at": "2020-01-29T14:12:04.688552Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "snowy-credit-3-3e0c1a0d-d22f-42ee-b2a1-f9f49477bf21", - "text": "sdasas", - "type": "regular", - "user": { - "id": "snowy-credit-3", - "role": "user", - "created_at": "2020-01-29T15:29:03.693312Z", - "updated_at": "2020-01-29T15:29:03.702648Z", - "last_active": "2020-01-29T15:29:03.696144Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Snowy+credit", - "name": "Snowy credit" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T15:29:36.011315Z", - "updated_at": "2020-01-29T15:29:36.011316Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "snowy-credit-3-3319537e-2d0e-4876-8170-a54f046e4b7d", - "text": "cjshsa", - "type": "regular", - "user": { - "id": "snowy-credit-3", - "role": "user", - "created_at": "2020-01-29T15:29:03.693312Z", - "updated_at": "2020-01-29T15:29:03.702648Z", - "last_active": "2020-01-29T15:29:03.696144Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Snowy+credit", - "name": "Snowy credit" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T15:29:41.677819Z", - "updated_at": "2020-01-29T15:29:41.677819Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "snowy-credit-3-cfaf0b46-1daa-49c5-947c-b16d6697487d", - "text": "nhisagdhsadz", - "type": "regular", - "user": { - "id": "snowy-credit-3", - "role": "user", - "created_at": "2020-01-29T15:29:03.693312Z", - "updated_at": "2020-01-29T15:29:03.702648Z", - "last_active": "2020-01-29T15:29:03.696144Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Snowy+credit", - "name": "Snowy credit" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T15:29:43.354177Z", - "updated_at": "2020-01-29T15:29:43.354177Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "snowy-credit-3-cebe25a7-a3a3-49fc-9919-91c6725e81f3", - "text": "hvadhsahzd", - "type": "regular", - "user": { - "id": "snowy-credit-3", - "role": "user", - "created_at": "2020-01-29T15:29:03.693312Z", - "updated_at": "2020-01-29T15:29:03.702648Z", - "last_active": "2020-01-29T15:29:03.696144Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Snowy+credit", - "name": "Snowy credit" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T15:29:44.754713Z", - "updated_at": "2020-01-29T15:29:44.754713Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "divine-glade-9-0cea9262-5766-48e9-8b22-311870aed3bf", - "text": "hello", - "type": "regular", - "user": { - "id": "divine-glade-9", - "role": "user", - "created_at": "2020-01-29T17:02:18.312524Z", - "updated_at": "2020-01-29T17:02:18.320187Z", - "last_active": "2020-01-29T17:02:18.315074Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Divine+glade", - "name": "Divine glade" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T17:02:36.933852Z", - "updated_at": "2020-01-29T17:02:36.933852Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "red-firefly-9-c4e9007b-bb7d-4238-ae08-5f8e3cd03d73", - "text": "hello", - "type": "regular", - "user": { - "id": "red-firefly-9", - "role": "user", - "created_at": "2019-08-02T18:56:39.366516Z", - "updated_at": "2020-01-29T22:13:50.491769Z", - "last_active": "2020-01-29T22:13:50.450215Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Red+firefly", - "name": "Red firefly" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-29T22:14:08.54062Z", - "updated_at": "2020-01-29T22:14:08.54062Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "bitter-glade-2-02aee4eb-4093-4736-808b-2de75820e854", - "text": "hello", - "type": "regular", - "user": { - "id": "bitter-glade-2", - "role": "user", - "created_at": "2020-01-30T13:08:56.190678Z", - "updated_at": "2020-01-30T13:08:56.200333Z", - "last_active": "2020-01-30T13:08:56.193882Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Bitter+glade", - "name": "Bitter glade" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-30T13:11:37.191293Z", - "updated_at": "2020-01-30T13:11:37.191293Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "morning-sea-1-0c700bcb-46dd-4224-b590-e77bdbccc480", - "text": "http://jaeger.ui.gtstrm.com/", - "type": "regular", - "user": { - "id": "morning-sea-1", - "role": "user", - "created_at": "2019-07-22T09:19:07.505207Z", - "updated_at": "2020-01-30T13:33:05.831856Z", - "last_active": "2020-01-30T13:33:05.825369Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Morning+sea", - "name": "Morning sea" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-30T13:33:16.853116Z", - "updated_at": "2020-01-30T13:33:16.853116Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "ancient-salad-0-53e8b4e6-5b7b-43ad-aeee-8bfb6a9ed0be", - "text": "hi", - "type": "regular", - "user": { - "id": "ancient-salad-0", - "role": "user", - "created_at": "2020-01-30T13:34:29.286813Z", - "updated_at": "2020-01-30T13:34:29.296196Z", - "last_active": "2020-01-30T13:34:29.289964Z", - "banned": false, - "online": true, - "image": "https://getstream.io/random_svg/?name=Ancient+salad", - "name": "Ancient salad" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-30T13:36:52.749731Z", - "updated_at": "2020-01-30T13:36:52.749732Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "ancient-salad-0-8c225075-bd4c-42e2-8024-530aae13cd40", - "text": "hi", - "type": "regular", - "user": { - "id": "ancient-salad-0", - "role": "user", - "created_at": "2020-01-30T13:34:29.286813Z", - "updated_at": "2020-01-30T13:34:29.296196Z", - "last_active": "2020-01-30T13:34:29.289964Z", - "banned": false, - "online": true, - "image": "https://getstream.io/random_svg/?name=Ancient+salad", - "name": "Ancient salad" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-30T13:37:41.631056Z", - "updated_at": "2020-01-30T13:37:41.631056Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "proud-sea-7-17802096-cbf8-4e3c-addd-4ee31f4c8b5c", - "text": "😃", - "type": "regular", - "user": { - "id": "proud-sea-7", - "role": "user", - "created_at": "2020-01-30T13:43:03.903006Z", - "updated_at": "2020-01-30T13:43:03.912307Z", - "last_active": "2020-01-30T13:43:03.906236Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Proud+sea", - "name": "Proud sea" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "created_at": "2020-01-30T13:43:41.062362Z", - "updated_at": "2020-01-30T13:43:41.062362Z", - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - } - ], - "watcher_count": 5, - "members": [] - }'''; - test('should parse json correctly', () { - final channelState = ChannelState.fromJson(json.decode(jsonExample)); + final channelState = + ChannelState.fromJson(jsonFixture('channel_state.json')); expect(channelState.channel?.cid, 'team:dev'); expect(channelState.channel?.id, 'dev'); expect(channelState.channel?.team, 'test'); @@ -879,426 +47,7 @@ void main() { }); test('should serialize to json correctly', () { - const toJsonExample = ''' - { - "channel": { - "id": "dev", - "type": "team", - "frozen": true, - "name": "#dev", - "image": "https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png", - "example": 1 - }, - "watchers": [], - "read": [], - "messages": [ - { - "id": "dry-meadow-0-2b73cc8b-cd86-4a01-8d40-bd82ad07a030", - "text": "fasdfa", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-e8e74482-b4cd-48db-9d1e-30e6c191786f", - "text": "test message", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-53e6299f-9b97-4a9c-a27e-7e2dde49b7e0", - "text": "test message", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-80925be0-786e-40a5-b225-486518dafd35", - "text": "asdfadf", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-64d7970f-ede8-4b31-9738-1bc1756d2bfe", - "text": "test", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "withered-cell-0-84cbd760-cf55-4f7e-9207-c5f66cccc6dc", - "text": "hi", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-e9203588-43c3-40b1-91f7-f217fc42aa53", - "text": "fantastic", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "withered-cell-0-7e3552d7-7a0d-45f2-a856-e91b23a7e240", - "text": "nice to meet you", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-1ffeafd4-e4fc-4c84-9394-9d7cb10fff42", - "text": "hey", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-3f147324-12c8-4b41-9fb5-2db88d065efa", - "text": "hello, everyone", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "dry-meadow-0-51a348ae-0c0a-44de-a556-eac7891c0cf0", - "text": "who is there?", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "icy-recipe-7-a29e237b-8d81-4a97-9bc8-d42bca3f1356", - "text": "í•˜ė´", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "icy-recipe-7-935c396e-ddf8-4a9a-951c-0a12fa5bf055", - "text": "what are you doing?", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "throbbing-boat-5-1e4d5730-5ff0-4d25-9948-9f34ffda43e4", - "text": "👍", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "snowy-credit-3-3e0c1a0d-d22f-42ee-b2a1-f9f49477bf21", - "text": "sdasas", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "snowy-credit-3-3319537e-2d0e-4876-8170-a54f046e4b7d", - "text": "cjshsa", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "snowy-credit-3-cfaf0b46-1daa-49c5-947c-b16d6697487d", - "text": "nhisagdhsadz", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "snowy-credit-3-cebe25a7-a3a3-49fc-9919-91c6725e81f3", - "text": "hvadhsahzd", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "divine-glade-9-0cea9262-5766-48e9-8b22-311870aed3bf", - "text": "hello", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "red-firefly-9-c4e9007b-bb7d-4238-ae08-5f8e3cd03d73", - "text": "hello", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "bitter-glade-2-02aee4eb-4093-4736-808b-2de75820e854", - "text": "hello", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "morning-sea-1-0c700bcb-46dd-4224-b590-e77bdbccc480", - "text": "http://jaeger.ui.gtstrm.com/", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "ancient-salad-0-53e8b4e6-5b7b-43ad-aeee-8bfb6a9ed0be", - "text": "hi", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "ancient-salad-0-8c225075-bd4c-42e2-8024-530aae13cd40", - "text": "hi", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - }, - { - "id": "proud-sea-7-17802096-cbf8-4e3c-addd-4ee31f4c8b5c", - "text": "😃", - "attachments": [], - "parent_id": null, - "quoted_message": null, - "quoted_message_id": null, - "show_in_channel": null, - "mentioned_users": [], - "status": "SENT", - "silent": false, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null - } - ], - "pinned_messages": [], - "members": [], - "watcher_count": 5 - } - '''; - final j = jsonDecode(jsonExample); + final j = jsonFixture('channel_state.json'); final channelState = ChannelState( channel: ChannelModel.fromJson(j['channel']), members: [], @@ -1312,7 +61,7 @@ void main() { expect( channelState.toJson(), - jsonDecode(toJsonExample), + jsonFixture('channel_state_to_json.json'), ); }); }); diff --git a/packages/stream_chat/test/src/core/models/channel_test.dart b/packages/stream_chat/test/src/core/models/channel_test.dart index 1bb400a6..ca734c44 100644 --- a/packages/stream_chat/test/src/core/models/channel_test.dart +++ b/packages/stream_chat/test/src/core/models/channel_test.dart @@ -1,22 +1,12 @@ -import 'dart:convert'; - import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:test/test.dart'; +import '../../utils.dart'; + void main() { group('src/models/channel', () { - const jsonExample = ''' - { - "id": "test", - "type": "livestream", - "cid": "livestream:test", - "cats": true, - "fruit": ["bananas", "apples"] - } - '''; - test('should parse json correctly', () { - final channel = ChannelModel.fromJson(json.decode(jsonExample)); + final channel = ChannelModel.fromJson(jsonFixture('channel.json')); expect(channel.id, equals('test')); expect(channel.type, equals('livestream')); expect(channel.cid, equals('livestream:test')); diff --git a/packages/stream_chat/test/src/core/models/command_test.dart b/packages/stream_chat/test/src/core/models/command_test.dart index 3306fd0f..e8ffb81e 100644 --- a/packages/stream_chat/test/src/core/models/command_test.dart +++ b/packages/stream_chat/test/src/core/models/command_test.dart @@ -1,20 +1,12 @@ -import 'dart:convert'; - import 'package:stream_chat/src/core/models/command.dart'; import 'package:test/test.dart'; +import '../../utils.dart'; + void main() { group('src/models/command', () { - const jsonExample = ''' - { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]" - } - '''; - test('should parse json correctly', () { - final command = Command.fromJson(json.decode(jsonExample)); + final command = Command.fromJson(jsonFixture('command.json')); expect(command.name, 'giphy'); expect(command.description, 'Post a random gif to the channel'); expect(command.args, '[text]'); diff --git a/packages/stream_chat/test/src/core/models/device_test.dart b/packages/stream_chat/test/src/core/models/device_test.dart index f4700b7e..b3ac707b 100644 --- a/packages/stream_chat/test/src/core/models/device_test.dart +++ b/packages/stream_chat/test/src/core/models/device_test.dart @@ -1,18 +1,12 @@ -import 'dart:convert'; - -import 'package:test/test.dart'; import 'package:stream_chat/src/core/models/device.dart'; +import 'package:test/test.dart'; + +import '../../utils.dart'; void main() { group('src/models/device', () { - const jsonExample = ''' - { - "id": "device-id", - "push_provider": "push-provider" - }'''; - test('should parse json correctly', () { - final device = Device.fromJson(json.decode(jsonExample)); + final device = Device.fromJson(jsonFixture('device.json')); expect(device.id, 'device-id'); expect(device.pushProvider, 'push-provider'); }); diff --git a/packages/stream_chat/test/src/core/models/event_test.dart b/packages/stream_chat/test/src/core/models/event_test.dart index b05da962..7ebd82fa 100644 --- a/packages/stream_chat/test/src/core/models/event_test.dart +++ b/packages/stream_chat/test/src/core/models/event_test.dart @@ -1,46 +1,14 @@ -import 'dart:convert'; - import 'package:stream_chat/src/core/models/event.dart'; import 'package:stream_chat/src/core/models/own_user.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:test/test.dart'; +import '../../utils.dart'; + void main() { group('src/models/event', () { - const jsonExample = ''' - { - "type": "type", - "cid": "cid", - "connection_id": "connectionId", - "created_at": "2019-04-03T18:43:33.213374Z", - "me": { - "id": "dry-meadow-0", - "role": "user", - "created_at": "2019-03-27T17:40:17.155892Z", - "updated_at": "2020-01-29T03:22:47.641589Z", - "last_active": "2020-01-29T03:22:47.63613Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Dry+meadow", - "name": "Dry meadow" - }, - "parent_id": null, - "user": { - "id": "dry-meadow-0", - "role": "user", - "created_at": "2019-03-27T17:40:17.155892Z", - "updated_at": "2020-01-29T03:22:47.641589Z", - "last_active": "2020-01-29T03:22:47.63613Z", - "banned": false, - "online": false, - "image": "https://getstream.io/random_svg/?name=Dry+meadow", - "name": "Dry meadow" - } - } - '''; - test('should parse json correctly', () { - final event = Event.fromJson(json.decode(jsonExample)); + final event = Event.fromJson(jsonFixture('event.json')); expect(event.type, 'type'); expect(event.cid, 'cid'); expect(event.connectionId, 'connectionId'); diff --git a/packages/stream_chat/test/src/core/models/member_test.dart b/packages/stream_chat/test/src/core/models/member_test.dart index 54c258e1..4cd8efda 100644 --- a/packages/stream_chat/test/src/core/models/member_test.dart +++ b/packages/stream_chat/test/src/core/models/member_test.dart @@ -1,31 +1,13 @@ -import 'dart:convert'; - -import 'package:test/test.dart'; import 'package:stream_chat/src/core/models/member.dart'; import 'package:stream_chat/src/core/models/user.dart'; +import 'package:test/test.dart'; + +import '../../utils.dart'; void main() { group('src/models/member', () { - const jsonExample = ''' - { - "user": { - "id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e", - "role": "user", - "created_at": "2020-01-28T22:17:30.826259Z", - "updated_at": "2020-01-28T22:17:31.101222Z", - "banned": false, - "online": false, - "name": "Robin Papa", - "image": "https://pbs.twimg.com/profile_images/669512187778498560/L7wQctBt.jpg" - }, - "role": "member", - "created_at": "2020-01-28T22:17:30.95443Z", - "updated_at": "2020-01-28T22:17:30.95443Z" - } - '''; - test('should parse json correctly', () { - final member = Member.fromJson(json.decode(jsonExample)); + final member = Member.fromJson(jsonFixture('member.json')); expect(member.user, isA()); expect(member.role, 'member'); expect(member.createdAt, DateTime.parse('2020-01-28T22:17:30.95443Z')); diff --git a/packages/stream_chat/test/src/core/models/message_test.dart b/packages/stream_chat/test/src/core/models/message_test.dart index f7b8f003..a5571430 100644 --- a/packages/stream_chat/test/src/core/models/message_test.dart +++ b/packages/stream_chat/test/src/core/models/message_test.dart @@ -1,82 +1,15 @@ -import 'dart:convert'; - import 'package:stream_chat/src/core/models/attachment.dart'; import 'package:stream_chat/src/core/models/message.dart'; import 'package:stream_chat/src/core/models/reaction.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:test/test.dart'; +import '../../utils.dart'; + void main() { group('src/models/message', () { - const jsonExample = r''' - { - "id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", - "text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", - "type": "regular", - "silent": false, - "status": "SENT", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "attachments": [ - { - "type": "video", - "author_name": "GIPHY", - "title": "The Lion King Disney GIF - Find \u0026 Share on GIPHY", - "title_link": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "text": "Discover \u0026 share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "image_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "thumb_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "asset_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4", - "og_scrape_url": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA" - } - ], - "latest_reactions": [ - { - "message_id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", - "user_id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "user": { - "id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680", - "role": "user", - "created_at": "2020-01-28T22:17:30.83015Z", - "updated_at": "2020-01-28T22:17:31.19435Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/2.jpg", - "name": "Mia Denys" - }, - "type": "love", - "score": 1, - "created_at": "2020-01-28T22:17:31.128376Z", - "updated_at": "2020-01-28T22:17:31.128376Z" - } - ], - "own_reactions": [], - "reaction_counts": { - "love": 1 - }, - "reaction_scores": { - "love": 1 - }, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null, - "reply_count": 0, - "created_at": "2020-01-28T22:17:31.107978Z", - "updated_at": "2020-01-28T22:17:31.130506Z", - "mentioned_users": [] - }'''; - test('should parse json correctly', () { - final message = Message.fromJson(json.decode(jsonExample)); + final message = Message.fromJson(jsonFixture('message.json')); expect(message.id, '4637f7e4-a06b-42db-ba5a-8d8270dd926f'); expect(message.text, 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA'); @@ -128,37 +61,7 @@ void main() { expect( message.toJson(), - json.decode(''' - { - "id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f", - "text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", - "silent": false, - "attachments": [ - { - "type": "video", - "title_link": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "title": "The Lion King Disney GIF - Find & Share on GIPHY", - "thumb_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "text": "Discover & share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "og_scrape_url": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", - "image_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", - "author_name": "GIPHY", - "asset_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4", - "actions": [] - } - ], - "mentioned_users": [], - "parent_id": "parentId", - "quoted_message": null, - "quoted_message_id": null, - "pinned": false, - "pinned_at": null, - "pin_expires": null, - "pinned_by": null, - "show_in_channel": true, - "hey": "test" - } - '''), + jsonFixture('message_to_json.json'), ); }); }); diff --git a/packages/stream_chat/test/src/core/models/reaction_test.dart b/packages/stream_chat/test/src/core/models/reaction_test.dart index b69bd986..facb8d07 100644 --- a/packages/stream_chat/test/src/core/models/reaction_test.dart +++ b/packages/stream_chat/test/src/core/models/reaction_test.dart @@ -1,34 +1,13 @@ -import 'dart:convert'; - import 'package:stream_chat/src/core/models/reaction.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:test/test.dart'; +import '../../utils.dart'; + void main() { group('src/models/reaction', () { - const jsonExample = ''' - { - "message_id": "76cd8c82-b557-4e48-9d12-87995d3a0e04", - "user_id": "2de0297c-f3f2-489d-b930-ef77342edccf", - "user": { - "id": "2de0297c-f3f2-489d-b930-ef77342edccf", - "role": "user", - "created_at": "2020-01-28T22:17:30.810011Z", - "updated_at": "2020-01-28T22:17:31.077195Z", - "banned": false, - "online": false, - "image": "https://randomuser.me/api/portraits/women/45.jpg", - "name": "Daisy Morgan" - }, - "type": "wow", - "score": 1, - "created_at": "2020-01-28T22:17:31.108742Z", - "updated_at": "2020-01-28T22:17:31.108742Z" - } - '''; - test('should parse json correctly', () { - final reaction = Reaction.fromJson(json.decode(jsonExample)); + final reaction = Reaction.fromJson(jsonFixture('reaction.json')); expect(reaction.messageId, '76cd8c82-b557-4e48-9d12-87995d3a0e04'); expect(reaction.createdAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); expect(reaction.type, 'wow'); diff --git a/packages/stream_chat/test/src/core/models/read_test.dart b/packages/stream_chat/test/src/core/models/read_test.dart index 47c252ac..eabfc18c 100644 --- a/packages/stream_chat/test/src/core/models/read_test.dart +++ b/packages/stream_chat/test/src/core/models/read_test.dart @@ -1,23 +1,13 @@ -import 'dart:convert'; - -import 'package:test/test.dart'; import 'package:stream_chat/src/core/models/read.dart'; import 'package:stream_chat/src/core/models/user.dart'; +import 'package:test/test.dart'; + +import '../../utils.dart'; void main() { group('src/models/read', () { - const jsonExample = ''' - { - "user": { - "id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e" - }, - "last_read": "2020-01-28T22:17:30.966485504Z", - "unread_messages": 10 - } - '''; - test('should parse json correctly', () { - final read = Read.fromJson(json.decode(jsonExample)); + final read = Read.fromJson(jsonFixture('read.json')); expect(read.lastRead, DateTime.parse('2020-01-28T22:17:30.966485504Z')); expect(read.user.id, 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'); expect(read.unreadMessages, 10); diff --git a/packages/stream_chat/test/src/core/models/user_test.dart b/packages/stream_chat/test/src/core/models/user_test.dart index f4ff5ee0..89d22b30 100644 --- a/packages/stream_chat/test/src/core/models/user_test.dart +++ b/packages/stream_chat/test/src/core/models/user_test.dart @@ -1,19 +1,12 @@ -import 'dart:convert'; - import 'package:stream_chat/src/core/models/user.dart'; import 'package:test/test.dart'; +import '../../utils.dart'; + void main() { group('src/models/user', () { - const jsonExample = ''' - { - "id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e", - "role": "test-role" - } - '''; - test('should parse json correctly', () { - final user = User.fromJson(json.decode(jsonExample)); + final user = User.fromJson(jsonFixture('user.json')); expect(user.id, 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'); }); diff --git a/packages/stream_chat/test/src/utils.dart b/packages/stream_chat/test/src/utils.dart index 91a61204..768d2fe2 100644 --- a/packages/stream_chat/test/src/utils.dart +++ b/packages/stream_chat/test/src/utils.dart @@ -1,10 +1,18 @@ +import 'dart:convert'; import 'dart:io'; +String fixture(String name) { + final dir = currentDirectory.path; + return File('$dir/test/fixtures/$name').readAsStringSync(); +} + File assetFile(String name) { final dir = currentDirectory.path; return File('$dir/test/assets/$name'); } +Map jsonFixture(String name) => json.decode(fixture(name)); + // https://github.com/flutter/flutter/issues/20907 Directory get currentDirectory { var directory = Directory.current; diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index 09156642..fe921e70 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -3550,7 +3550,7 @@ class UsersCompanion extends UpdateCompanion { this.online = const Value.absent(), this.banned = const Value.absent(), required Map extraData, - }) : id = Value(id), + }) : id = Value(id), extraData = Value(extraData); static Insertable custom({ Expression? id, @@ -4723,7 +4723,7 @@ class ChannelQueriesCompanion extends UpdateCompanion { ChannelQueriesCompanion.insert({ required String queryHash, required String channelCid, - }) : queryHash = Value(queryHash), + }) : queryHash = Value(queryHash), channelCid = Value(channelCid); static Insertable custom({ Expression? queryHash, From 51f8e030f51dc2bf3d5a9d20c9b3e6d2b777f477 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 16 Jun 2021 13:58:24 +0200 Subject: [PATCH 379/599] add mute test --- .../stream_chat/lib/src/core/models/mute.dart | 9 +-- .../lib/src/core/models/mute.g.dart | 16 ---- packages/stream_chat/test/fixtures/mute.json | 74 +++++++++++++++++++ .../test/src/core/models/mute_test.dart | 17 +++++ 4 files changed, 95 insertions(+), 21 deletions(-) create mode 100644 packages/stream_chat/test/fixtures/mute.json create mode 100644 packages/stream_chat/test/src/core/models/mute_test.dart diff --git a/packages/stream_chat/lib/src/core/models/mute.dart b/packages/stream_chat/lib/src/core/models/mute.dart index 9166d3e4..4b14ad01 100644 --- a/packages/stream_chat/lib/src/core/models/mute.dart +++ b/packages/stream_chat/lib/src/core/models/mute.dart @@ -1,12 +1,14 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/channel_model.dart'; -import 'package:stream_chat/src/core/util/serializer.dart'; import 'package:stream_chat/src/core/models/user.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; part 'mute.g.dart'; /// The class that contains the information about a muted user -@JsonSerializable() +@JsonSerializable( + createToJson: false, +) class Mute { /// Constructor used for json serialization Mute({ @@ -34,7 +36,4 @@ class Mute { /// The date of the last update @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime updatedAt; - - /// Serialize to json - Map toJson() => _$MuteToJson(this); } diff --git a/packages/stream_chat/lib/src/core/models/mute.g.dart b/packages/stream_chat/lib/src/core/models/mute.g.dart index e77b8707..b847a109 100644 --- a/packages/stream_chat/lib/src/core/models/mute.g.dart +++ b/packages/stream_chat/lib/src/core/models/mute.g.dart @@ -14,19 +14,3 @@ Mute _$MuteFromJson(Map json) { updatedAt: DateTime.parse(json['updated_at'] as String), ); } - -Map _$MuteToJson(Mute instance) { - final val = {}; - - void writeNotNull(String key, dynamic value) { - if (value != null) { - val[key] = value; - } - } - - writeNotNull('user', readonly(instance.user)); - writeNotNull('channel', readonly(instance.channel)); - writeNotNull('created_at', readonly(instance.createdAt)); - writeNotNull('updated_at', readonly(instance.updatedAt)); - return val; -} diff --git a/packages/stream_chat/test/fixtures/mute.json b/packages/stream_chat/test/fixtures/mute.json new file mode 100644 index 00000000..cc6c3a8c --- /dev/null +++ b/packages/stream_chat/test/fixtures/mute.json @@ -0,0 +1,74 @@ +{ + "user": { + "id": "super-band-9", + "role": "user", + "created_at": "2020-03-03T16:48:28.853674Z", + "updated_at": "2021-05-26T03:22:20.296181Z", + "last_active": "2021-06-16T11:42:29.466165498Z", + "banned": false, + "online": true, + "username": "Rioland", + "image": "https://placehold.jp/150x150.png", + "invisible": false, + "name": "Proud darkness", + "unread_count": 0 + }, + "channel": { + "id": "!members-Qsp7PpigdPkW0rJk0603y5GnTiF1iRfoDc4SAngMMmw", + "type": "messaging", + "cid": "messaging:!members-Qsp7PpigdPkW0rJk0603y5GnTiF1iRfoDc4SAngMMmw", + "last_message_at": "2020-12-02T06:56:18.003432Z", + "created_at": "2020-11-30T10:25:32.494601Z", + "updated_at": "2020-11-30T10:25:32.494601Z", + "created_by": { + "id": "super-band-9", + "role": "user", + "created_at": "2020-03-03T16:48:28.853674Z", + "updated_at": "2021-05-26T03:22:20.296181Z", + "last_active": "2021-06-16T11:42:29.466165498Z", + "banned": false, + "online": true, + "image": "https://placehold.jp/150x150.png", + "invisible": false, + "name": "Proud darkness", + "unread_count": 0, + "username": "Rioland" + }, + "frozen": false, + "disabled": false, + "member_count": 2, + "config": { + "created_at": "2020-04-15T14:57:17.00966Z", + "updated_at": "2021-05-25T14:25:30.405621Z", + "name": "messaging", + "typing_events": true, + "read_events": true, + "connect_events": true, + "search": true, + "reactions": true, + "replies": true, + "mutes": true, + "uploads": true, + "url_enrichment": true, + "custom_events": false, + "push_notifications": true, + "message_retention": "infinite", + "max_message_length": 5000, + "automod": "disabled", + "automod_behavior": "flag", + "blocklist": "profanity_en_2020_v1", + "blocklist_behavior": "block", + "automod_thresholds": {}, + "commands": [ + { + "name": "giphy", + "description": "Post a random gif to the channel", + "args": "[text]", + "set": "fun_set" + } + ] + } + }, + "created_at": "2020-12-04T10:39:06.512021Z", + "updated_at": "2020-12-04T10:39:06.512021Z" +} \ No newline at end of file diff --git a/packages/stream_chat/test/src/core/models/mute_test.dart b/packages/stream_chat/test/src/core/models/mute_test.dart new file mode 100644 index 00000000..f1f7b606 --- /dev/null +++ b/packages/stream_chat/test/src/core/models/mute_test.dart @@ -0,0 +1,17 @@ +import 'package:stream_chat/src/core/models/channel_model.dart'; +import 'package:stream_chat/src/core/models/mute.dart'; +import 'package:stream_chat/src/core/models/user.dart'; +import 'package:test/test.dart'; + +import '../../utils.dart'; + +void main() { + group('src/models/mute', () { + test('should parse json correctly', () { + final mute = Mute.fromJson(jsonFixture('mute.json')); + expect(mute.channel, isA()); + expect(mute.user, isA()); + expect(mute.createdAt, DateTime.parse('2020-12-04T10:39:06.512021Z')); + }); + }); +} From 4e9699dbf49816104f9d4975671f5604cefa60f1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 16 Jun 2021 16:06:55 +0200 Subject: [PATCH 380/599] add OwnUser test --- .../lib/src/core/models/own_user.dart | 25 ++--- .../lib/src/core/models/own_user.g.dart | 26 ----- .../stream_chat/test/fixtures/own_user.json | 100 ++++++++++++++++++ .../test/src/core/models/own_user_test.dart | 45 ++++++++ 4 files changed, 152 insertions(+), 44 deletions(-) create mode 100644 packages/stream_chat/test/fixtures/own_user.json create mode 100644 packages/stream_chat/test/src/core/models/own_user_test.dart diff --git a/packages/stream_chat/lib/src/core/models/own_user.dart b/packages/stream_chat/lib/src/core/models/own_user.dart index 22705b05..7ceffbfa 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -1,14 +1,14 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/device.dart'; import 'package:stream_chat/src/core/models/mute.dart'; -import 'package:stream_chat/src/core/util/serializer.dart'; import 'package:stream_chat/src/core/models/user.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; part 'own_user.g.dart'; /// The class that defines the own user model /// This object can be found in [Event] -@JsonSerializable() +@JsonSerializable(createToJson: false) class OwnUser extends User { /// Constructor used for json serialization OwnUser({ @@ -53,28 +53,23 @@ class OwnUser extends User { ); /// List of user devices - @JsonKey( - includeIfNull: false, - toJson: Serializer.readOnly, - defaultValue: []) + @JsonKey(includeIfNull: false, defaultValue: []) final List devices; /// List of users muted by the user - @JsonKey( - includeIfNull: false, toJson: Serializer.readOnly, defaultValue: []) + @JsonKey(includeIfNull: false, defaultValue: []) final List mutes; /// List of users muted by the user - @JsonKey( - includeIfNull: false, toJson: Serializer.readOnly, defaultValue: []) + @JsonKey(includeIfNull: false, defaultValue: []) final List channelMutes; /// Total unread messages by the user - @JsonKey(includeIfNull: false, toJson: Serializer.readOnly, defaultValue: 0) + @JsonKey(includeIfNull: false, defaultValue: 0) final int totalUnreadCount; /// Total unread channels by the user - @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) + @JsonKey(includeIfNull: false) final int? unreadChannels; /// Known top level fields. @@ -87,10 +82,4 @@ class OwnUser extends User { 'channel_mutes', ...User.topLevelFields, ]; - - /// Serialize to json - @override - Map toJson() => Serializer.moveFromExtraDataToRoot( - _$OwnUserToJson(this), - ); } diff --git a/packages/stream_chat/lib/src/core/models/own_user.g.dart b/packages/stream_chat/lib/src/core/models/own_user.g.dart index 760edc01..26e4786e 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.g.dart @@ -38,29 +38,3 @@ OwnUser _$OwnUserFromJson(Map json) { banned: json['banned'] as bool? ?? false, ); } - -Map _$OwnUserToJson(OwnUser instance) { - final val = { - 'id': instance.id, - }; - - void writeNotNull(String key, dynamic value) { - if (value != null) { - val[key] = value; - } - } - - writeNotNull('role', readonly(instance.role)); - writeNotNull('created_at', readonly(instance.createdAt)); - writeNotNull('updated_at', readonly(instance.updatedAt)); - writeNotNull('last_active', readonly(instance.lastActive)); - writeNotNull('online', readonly(instance.online)); - writeNotNull('banned', readonly(instance.banned)); - val['extra_data'] = instance.extraData; - writeNotNull('devices', readonly(instance.devices)); - writeNotNull('mutes', readonly(instance.mutes)); - writeNotNull('channel_mutes', readonly(instance.channelMutes)); - writeNotNull('total_unread_count', readonly(instance.totalUnreadCount)); - writeNotNull('unread_channels', readonly(instance.unreadChannels)); - return val; -} diff --git a/packages/stream_chat/test/fixtures/own_user.json b/packages/stream_chat/test/fixtures/own_user.json new file mode 100644 index 00000000..dc6aabd1 --- /dev/null +++ b/packages/stream_chat/test/fixtures/own_user.json @@ -0,0 +1,100 @@ +{ + "id": "super-band-9", + "role": "user", + "created_at": "2020-03-03T16:48:28.853674Z", + "updated_at": "2021-05-26T03:22:20.296181Z", + "last_active": "2021-06-16T11:59:59.003453014Z", + "banned": false, + "online": true, + "devices": [ + { + "push_provider": "firebase", + "id": "cRS8elU4Q-qqdCAvHR2kSa:APA91bFy7MEgPyXbnFWi3uoanr_x8Vbi42JcWOXlg8p3vyIL5FuW4bjpVfamqQjYCgwDGxPA0C4qavOadE-uiKeGQJp6Sp5D2KDW9Od_BlDqzwEPJnVG9gC1zbj7NKCfXRqbOA2Wh2mW", + "created_at": "2020-04-23T14:36:21.838196Z", + "user_id": "super-band-9" + } + ], + "mutes": [], + "channel_mutes": [ + { + "user": { + "id": "super-band-9", + "role": "user", + "created_at": "2020-03-03T16:48:28.853674Z", + "updated_at": "2021-05-26T03:22:20.296181Z", + "last_active": "2021-06-16T11:59:59.003453014Z", + "banned": false, + "online": true, + "image": "https://placehold.jp/150x150.png", + "invisible": false, + "name": "Proud darkness", + "unread_count": 0, + "username": "Rioland" + }, + "channel": { + "id": "!members-Qsp7PpigdPkW0rJk0603y5GnTiF1iRfoDc4SAngMMmw", + "type": "messaging", + "cid": "messaging:!members-Qsp7PpigdPkW0rJk0603y5GnTiF1iRfoDc4SAngMMmw", + "last_message_at": "2020-12-02T06:56:18.003432Z", + "created_at": "2020-11-30T10:25:32.494601Z", + "updated_at": "2020-11-30T10:25:32.494601Z", + "created_by": { + "id": "super-band-9", + "role": "user", + "created_at": "2020-03-03T16:48:28.853674Z", + "updated_at": "2021-05-26T03:22:20.296181Z", + "last_active": "2021-06-16T11:59:59.003453014Z", + "banned": false, + "online": true, + "unread_count": 0, + "username": "Rioland", + "image": "https://placehold.jp/150x150.png", + "invisible": false, + "name": "Proud darkness" + }, + "frozen": false, + "disabled": false, + "member_count": 2, + "config": { + "created_at": "2020-04-15T14:57:17.00966Z", + "updated_at": "2021-05-25T14:25:30.405621Z", + "name": "messaging", + "typing_events": true, + "read_events": true, + "connect_events": true, + "search": true, + "reactions": true, + "replies": true, + "mutes": true, + "uploads": true, + "url_enrichment": true, + "custom_events": false, + "push_notifications": true, + "message_retention": "infinite", + "max_message_length": 5000, + "automod": "disabled", + "automod_behavior": "flag", + "blocklist": "profanity_en_2020_v1", + "blocklist_behavior": "block", + "automod_thresholds": {}, + "commands": [ + { + "name": "giphy", + "description": "Post a random gif to the channel", + "args": "[text]", + "set": "fun_set" + } + ] + } + }, + "created_at": "2020-12-04T10:39:06.512021Z", + "updated_at": "2020-12-04T10:39:06.512021Z" + } + ], + "total_unread_count": 0, + "unread_channels": 0, + "language": "", + "image": "https://placehold.jp/150x150.png", + "name": "Proud darkness", + "username": "Rioland" +} diff --git a/packages/stream_chat/test/src/core/models/own_user_test.dart b/packages/stream_chat/test/src/core/models/own_user_test.dart new file mode 100644 index 00000000..edaf022e --- /dev/null +++ b/packages/stream_chat/test/src/core/models/own_user_test.dart @@ -0,0 +1,45 @@ +import 'package:stream_chat/src/core/models/own_user.dart'; +import 'package:stream_chat/src/core/models/user.dart'; +import 'package:test/test.dart'; + +import '../../utils.dart'; + +void main() { + group('src/models/own_user', () { + test('should parse json correctly', () { + final ownUser = OwnUser.fromJson(jsonFixture('own_user.json')); + expect(ownUser.id, 'super-band-9'); + expect(ownUser.role, 'user'); + + expect(ownUser.createdAt, DateTime.parse('2020-03-03T16:48:28.853674Z')); + expect(ownUser.updatedAt, DateTime.parse('2021-05-26T03:22:20.296181Z')); + expect( + ownUser.lastActive, DateTime.parse('2021-06-16T11:59:59.003453014Z')); + expect(ownUser.banned, false); + expect(ownUser.online, true); + expect(ownUser.devices.length, 1); + expect(ownUser.mutes.length, 0); + expect(ownUser.channelMutes.length, 1); + expect(ownUser.totalUnreadCount, 0); + expect(ownUser.unreadChannels, 0); + expect(ownUser.extraData['image'], 'https://placehold.jp/150x150.png'); + expect(ownUser.extraData['name'], 'Proud darkness'); + expect(ownUser.extraData['username'], 'Rioland'); + }); + + test('should initialize a OwnUser from a User correctly', () { + final user = User.fromJson(jsonFixture('user.json')); + final ownUser = OwnUser.fromUser(user); + + expect(ownUser.id, user.id); + expect(ownUser.id, user.id); + expect(ownUser.role, user.role); + expect(ownUser.createdAt, user.createdAt); + expect(ownUser.updatedAt, user.updatedAt); + expect(ownUser.lastActive, user.lastActive); + expect(ownUser.online, user.online); + expect(ownUser.banned, user.banned); + expect(ownUser.extraData, user.extraData); + }); + }); +} From d4b5154cbad0ce4888fa19617590000d5a6c95f4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 16 Jun 2021 16:27:21 +0200 Subject: [PATCH 381/599] add reaction test --- .../test/src/core/models/reaction_test.dart | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/packages/stream_chat/test/src/core/models/reaction_test.dart b/packages/stream_chat/test/src/core/models/reaction_test.dart index facb8d07..cfe9f69e 100644 --- a/packages/stream_chat/test/src/core/models/reaction_test.dart +++ b/packages/stream_chat/test/src/core/models/reaction_test.dart @@ -47,5 +47,108 @@ void main() { }, ); }); + + test('copyWith', () { + final reaction = Reaction.fromJson(jsonFixture('reaction.json')); + var newReaction = reaction.copyWith(); + expect(newReaction.messageId, '76cd8c82-b557-4e48-9d12-87995d3a0e04'); + expect( + newReaction.createdAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); + expect(newReaction.type, 'wow'); + expect( + newReaction.user?.toJson(), + User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: const { + 'image': 'https://randomuser.me/api/portraits/women/45.jpg', + 'name': 'Daisy Morgan', + }).toJson(), + ); + expect(newReaction.score, 1); + expect(newReaction.userId, '2de0297c-f3f2-489d-b930-ef77342edccf'); + expect( + newReaction.extraData, {'updated_at': '2020-01-28T22:17:31.108742Z'}); + + newReaction = reaction.copyWith( + type: 'lol', + createdAt: DateTime.parse('2021-01-28T22:17:31.108742Z'), + extraData: {}, + messageId: 'test', + score: 2, + user: User(id: 'test'), + userId: 'test', + ); + + expect( + newReaction.type, + 'lol', + ); + expect( + newReaction.createdAt, + DateTime.parse('2021-01-28T22:17:31.108742Z'), + ); + expect( + newReaction.extraData, + {}, + ); + expect( + newReaction.messageId, + 'test', + ); + expect( + newReaction.score, + 2, + ); + expect( + newReaction.user, + User(id: 'test'), + ); + expect( + newReaction.userId, + 'test', + ); + }); + + test('merge', () { + final reaction = Reaction.fromJson(jsonFixture('reaction.json')); + final newReaction = reaction.merge( + Reaction( + type: 'lol', + createdAt: DateTime.parse('2021-01-28T22:17:31.108742Z'), + extraData: {}, + messageId: 'test', + score: 2, + user: User(id: 'test'), + userId: 'test', + ), + ); + + expect( + newReaction.type, + 'lol', + ); + expect( + newReaction.createdAt, + DateTime.parse('2021-01-28T22:17:31.108742Z'), + ); + expect( + newReaction.extraData, + {}, + ); + expect( + newReaction.messageId, + 'test', + ); + expect( + newReaction.score, + 2, + ); + expect( + newReaction.user, + User(id: 'test'), + ); + expect( + newReaction.userId, + 'test', + ); + }); }); } From aef63f3722e51752a9182f90a32e32f6906cf238 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 09:40:01 +0200 Subject: [PATCH 382/599] add more read test --- .../test/src/core/models/read_test.dart | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/stream_chat/test/src/core/models/read_test.dart b/packages/stream_chat/test/src/core/models/read_test.dart index eabfc18c..94a28f08 100644 --- a/packages/stream_chat/test/src/core/models/read_test.dart +++ b/packages/stream_chat/test/src/core/models/read_test.dart @@ -26,5 +26,29 @@ void main() { 'unread_messages': 10, }); }); + + test('copyWith', () { + final read = Read.fromJson(jsonFixture('read.json')); + var newRead = read.copyWith(); + expect( + newRead.lastRead, + DateTime.parse('2020-01-28T22:17:30.966485504Z'), + ); + expect(newRead.user.id, 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'); + expect(newRead.unreadMessages, 10); + + newRead = read.copyWith( + user: User(id: 'test'), + lastRead: DateTime.parse('2021-01-28T22:17:30.966485504Z'), + unreadMessages: 2, + ); + + expect( + newRead.lastRead, + DateTime.parse('2021-01-28T22:17:30.966485504Z'), + ); + expect(newRead.user.id, 'test'); + expect(newRead.unreadMessages, 2); + }); }); } From 91907639adef5627dc9f4f4fefd7f604978b4f5e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 10:01:46 +0200 Subject: [PATCH 383/599] add more user and channel config test --- .../lib/src/core/models/channel_config.dart | 5 +---- .../lib/src/core/models/channel_config.g.dart | 19 ---------------- packages/stream_chat/test/fixtures/user.json | 3 ++- .../test/src/core/models/user_test.dart | 22 +++++++++++++++++++ 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/stream_chat/lib/src/core/models/channel_config.dart b/packages/stream_chat/lib/src/core/models/channel_config.dart index 2d5182b3..b7cafac6 100644 --- a/packages/stream_chat/lib/src/core/models/channel_config.dart +++ b/packages/stream_chat/lib/src/core/models/channel_config.dart @@ -4,7 +4,7 @@ import 'package:stream_chat/src/core/models/command.dart'; part 'channel_config.g.dart'; /// The class that contains the information about the configuration of a channel -@JsonSerializable() +@JsonSerializable(createToJson: false) class ChannelConfig { /// Constructor used for json serialization ChannelConfig({ @@ -87,7 +87,4 @@ class ChannelConfig { /// True if urls appears as attachments @JsonKey(defaultValue: false) final bool urlEnrichment; - - /// Serialize to json - Map toJson() => _$ChannelConfigToJson(this); } diff --git a/packages/stream_chat/lib/src/core/models/channel_config.g.dart b/packages/stream_chat/lib/src/core/models/channel_config.g.dart index 723281c0..b01049f3 100644 --- a/packages/stream_chat/lib/src/core/models/channel_config.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_config.g.dart @@ -32,22 +32,3 @@ ChannelConfig _$ChannelConfigFromJson(Map json) { urlEnrichment: json['url_enrichment'] as bool? ?? false, ); } - -Map _$ChannelConfigToJson(ChannelConfig instance) => - { - 'automod': instance.automod, - 'commands': instance.commands.map((e) => e.toJson()).toList(), - 'connect_events': instance.connectEvents, - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'max_message_length': instance.maxMessageLength, - 'message_retention': instance.messageRetention, - 'mutes': instance.mutes, - 'reactions': instance.reactions, - 'read_events': instance.readEvents, - 'replies': instance.replies, - 'search': instance.search, - 'typing_events': instance.typingEvents, - 'uploads': instance.uploads, - 'url_enrichment': instance.urlEnrichment, - }; diff --git a/packages/stream_chat/test/fixtures/user.json b/packages/stream_chat/test/fixtures/user.json index 1fad8ea0..49b972a3 100644 --- a/packages/stream_chat/test/fixtures/user.json +++ b/packages/stream_chat/test/fixtures/user.json @@ -1,4 +1,5 @@ { "id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e", - "role": "test-role" + "role": "test-role", + "name": "John" } \ No newline at end of file diff --git a/packages/stream_chat/test/src/core/models/user_test.dart b/packages/stream_chat/test/src/core/models/user_test.dart index 89d22b30..ce488cab 100644 --- a/packages/stream_chat/test/src/core/models/user_test.dart +++ b/packages/stream_chat/test/src/core/models/user_test.dart @@ -8,6 +8,7 @@ void main() { test('should parse json correctly', () { final user = User.fromJson(jsonFixture('user.json')); expect(user.id, 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'); + expect(user.name, 'John'); }); test('should serialize to json correctly', () { @@ -20,5 +21,26 @@ void main() { 'id': 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e', }); }); + + test('copyWith', () { + final user = User.fromJson(jsonFixture('user.json')); + var newUser = user.copyWith(); + + expect(newUser.id, user.id); + expect(newUser.role, user.role); + expect(newUser.name, user.name); + + newUser = user.copyWith( + id: 'test', + role: 'test', + extraData: { + 'name': 'test', + }, + ); + + expect(newUser.id, 'test'); + expect(newUser.role, 'test'); + expect(newUser.name, 'test'); + }); }); } From 907573dab1532cb76bb843c68b0ec8b19e13c62a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 10:34:53 +0200 Subject: [PATCH 384/599] add event tests --- .../lib/src/core/models/channel_config.dart | 5 +- .../lib/src/core/models/channel_config.g.dart | 19 ++++ .../lib/src/core/models/event.dart | 13 ++- .../lib/src/core/models/event.g.dart | 26 ----- .../test/fixtures/event_channel.json | 99 +++++++++++++++++++ .../test/src/core/models/event_test.dart | 48 +++++++++ 6 files changed, 176 insertions(+), 34 deletions(-) create mode 100644 packages/stream_chat/test/fixtures/event_channel.json diff --git a/packages/stream_chat/lib/src/core/models/channel_config.dart b/packages/stream_chat/lib/src/core/models/channel_config.dart index b7cafac6..2d5182b3 100644 --- a/packages/stream_chat/lib/src/core/models/channel_config.dart +++ b/packages/stream_chat/lib/src/core/models/channel_config.dart @@ -4,7 +4,7 @@ import 'package:stream_chat/src/core/models/command.dart'; part 'channel_config.g.dart'; /// The class that contains the information about the configuration of a channel -@JsonSerializable(createToJson: false) +@JsonSerializable() class ChannelConfig { /// Constructor used for json serialization ChannelConfig({ @@ -87,4 +87,7 @@ class ChannelConfig { /// True if urls appears as attachments @JsonKey(defaultValue: false) final bool urlEnrichment; + + /// Serialize to json + Map toJson() => _$ChannelConfigToJson(this); } diff --git a/packages/stream_chat/lib/src/core/models/channel_config.g.dart b/packages/stream_chat/lib/src/core/models/channel_config.g.dart index b01049f3..723281c0 100644 --- a/packages/stream_chat/lib/src/core/models/channel_config.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_config.g.dart @@ -32,3 +32,22 @@ ChannelConfig _$ChannelConfigFromJson(Map json) { urlEnrichment: json['url_enrichment'] as bool? ?? false, ); } + +Map _$ChannelConfigToJson(ChannelConfig instance) => + { + 'automod': instance.automod, + 'commands': instance.commands.map((e) => e.toJson()).toList(), + 'connect_events': instance.connectEvents, + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'max_message_length': instance.maxMessageLength, + 'message_retention': instance.messageRetention, + 'mutes': instance.mutes, + 'reactions': instance.reactions, + 'read_events': instance.readEvents, + 'replies': instance.replies, + 'search': instance.search, + 'typing_events': instance.typingEvents, + 'uploads': instance.uploads, + 'url_enrichment': instance.urlEnrichment, + }; diff --git a/packages/stream_chat/lib/src/core/models/event.dart b/packages/stream_chat/lib/src/core/models/event.dart index 33e696c4..21dd069c 100644 --- a/packages/stream_chat/lib/src/core/models/event.dart +++ b/packages/stream_chat/lib/src/core/models/event.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:stream_chat/src/core/models/message.dart'; @@ -160,11 +162,14 @@ class Event { channelType: channelType ?? this.channelType, parentId: parentId ?? this.parentId, extraData: extraData ?? this.extraData, + isLocal: isLocal, ); } /// The channel embedded in the event object -@JsonSerializable() +@JsonSerializable( + createToJson: false, +) class EventChannel extends ChannelModel { /// Constructor used for json serialization EventChannel({ @@ -212,10 +217,4 @@ class EventChannel extends ChannelModel { 'members', ...ChannelModel.topLevelFields, ]; - - /// Serialize to json - @override - Map toJson() => Serializer.moveFromExtraDataToRoot( - _$EventChannelToJson(this), - ); } diff --git a/packages/stream_chat/lib/src/core/models/event.g.dart b/packages/stream_chat/lib/src/core/models/event.g.dart index 90abeb71..5247af17 100644 --- a/packages/stream_chat/lib/src/core/models/event.g.dart +++ b/packages/stream_chat/lib/src/core/models/event.g.dart @@ -89,29 +89,3 @@ EventChannel _$EventChannelFromJson(Map json) { extraData: json['extra_data'] as Map? ?? {}, ); } - -Map _$EventChannelToJson(EventChannel instance) { - final val = { - 'id': instance.id, - 'type': instance.type, - }; - - void writeNotNull(String key, dynamic value) { - if (value != null) { - val[key] = value; - } - } - - writeNotNull('cid', readonly(instance.cid)); - writeNotNull('config', readonly(instance.config)); - writeNotNull('created_by', readonly(instance.createdBy)); - val['frozen'] = instance.frozen; - writeNotNull('last_message_at', readonly(instance.lastMessageAt)); - writeNotNull('created_at', readonly(instance.createdAt)); - writeNotNull('updated_at', readonly(instance.updatedAt)); - writeNotNull('deleted_at', readonly(instance.deletedAt)); - writeNotNull('member_count', readonly(instance.memberCount)); - val['extra_data'] = instance.extraData; - val['members'] = instance.members?.map((e) => e.toJson()).toList(); - return val; -} diff --git a/packages/stream_chat/test/fixtures/event_channel.json b/packages/stream_chat/test/fixtures/event_channel.json new file mode 100644 index 00000000..6078c545 --- /dev/null +++ b/packages/stream_chat/test/fixtures/event_channel.json @@ -0,0 +1,99 @@ +{ + "id": "!members-v9ktpgmYysZA-MjgC-GMoeEawFHSelkOdTu6JGxFZWU", + "type": "messaging", + "cid": "messaging:!members-v9ktpgmYysZA-MjgC-GMoeEawFHSelkOdTu6JGxFZWU", + "last_message_at": "2020-12-02T04:22:16.755334Z", + "created_at": "2020-10-01T08:49:32.90162Z", + "updated_at": "2021-06-17T08:18:48.188996Z", + "created_by": { + "id": "super-band-9", + "role": "user", + "created_at": "2020-03-03T16:48:28.853674Z", + "updated_at": "2021-05-26T03:22:20.296181Z", + "last_active": "2021-06-17T08:12:05.062115Z", + "banned": false, + "online": true, + "image": "https://placehold.jp/150x150.png", + "invisible": false, + "name": "Proud darkness", + "unread_count": 0, + "username": "Rioland" + }, + "frozen": false, + "disabled": false, + "members": [ + { + "user_id": "super-band-9", + "user": { + "id": "super-band-9", + "role": "user", + "created_at": "2020-03-03T16:48:28.853674Z", + "updated_at": "2021-05-26T03:22:20.296181Z", + "last_active": "2021-06-17T08:12:05.062115Z", + "banned": false, + "online": true, + "image": "https://placehold.jp/150x150.png", + "invisible": false, + "name": "Proud darkness", + "unread_count": 0, + "username": "Rioland" + }, + "role": "owner", + "created_at": "2020-10-01T08:49:32.905052Z", + "updated_at": "2020-10-01T08:49:32.905052Z", + "banned": false, + "shadow_banned": false + }, + { + "user_id": "cc48de8e-b2db-48e7-bba5-c03cefd61430", + "user": { + "id": "cc48de8e-b2db-48e7-bba5-c03cefd61430", + "role": "user", + "created_at": "2020-07-22T14:59:38.026809Z", + "updated_at": "2020-07-22T14:59:38.31338Z", + "banned": false, + "online": false, + "image": "https://images-na.ssl-images-amazon.com/images/M/MV5BMjA3NjYzMzE1MV5BMl5BanBnXkFtZTgwNTA4NDY4OTE@._V1_UX172_CR0,0,172,256_AL_.jpg", + "name": "Ana De Armas" + }, + "role": "member", + "created_at": "2020-10-01T08:49:32.905053Z", + "updated_at": "2020-10-01T08:49:32.905053Z", + "banned": false, + "shadow_banned": false + } + ], + "member_count": 2, + "config": { + "created_at": "2020-04-15T14:57:17.00966Z", + "updated_at": "2021-05-25T14:25:30.405621Z", + "name": "messaging", + "typing_events": true, + "read_events": true, + "connect_events": true, + "search": true, + "reactions": true, + "replies": true, + "mutes": true, + "uploads": true, + "url_enrichment": true, + "custom_events": false, + "push_notifications": true, + "message_retention": "infinite", + "max_message_length": 5000, + "automod": "disabled", + "automod_behavior": "flag", + "blocklist": "profanity_en_2020_v1", + "blocklist_behavior": "block", + "automod_thresholds": {}, + "commands": [ + { + "name": "giphy", + "description": "Post a random gif to the channel", + "args": "[text]", + "set": "fun_set" + } + ] + }, + "name": "test" +} \ No newline at end of file diff --git a/packages/stream_chat/test/src/core/models/event_test.dart b/packages/stream_chat/test/src/core/models/event_test.dart index 7ebd82fa..0cd9a0ad 100644 --- a/packages/stream_chat/test/src/core/models/event_test.dart +++ b/packages/stream_chat/test/src/core/models/event_test.dart @@ -54,5 +54,53 @@ void main() { }, ); }); + + test('copyWith', () { + final event = Event.fromJson(jsonFixture('event.json')); + var newEvent = event.copyWith(); + expect(newEvent.type, 'type'); + expect(newEvent.cid, 'cid'); + expect(newEvent.connectionId, 'connectionId'); + expect(newEvent.createdAt, isA()); + expect(newEvent.me, isA()); + expect(newEvent.user, isA()); + expect(newEvent.isLocal, false); + + newEvent = event.copyWith( + type: 'test', + cid: 'test', + connectionId: 'test', + extraData: {}, + user: User(id: 'test'), + channelId: 'test', + totalUnreadCount: 2, + channelType: 'testtype', + ); + + expect(newEvent.channelType, 'testtype'); + expect(newEvent.totalUnreadCount, 2); + expect(newEvent.type, 'test'); + expect(newEvent.channelId, 'test'); + expect(newEvent.cid, 'test'); + expect(newEvent.connectionId, 'test'); + expect(newEvent.extraData, {}); + expect(newEvent.user!.id, 'test'); + }); + + group('eventChannel', () { + test('should parse json correctly', () { + final eventChannel = + EventChannel.fromJson(jsonFixture('event_channel.json')); + expect(eventChannel.type, 'messaging'); + expect(eventChannel.cid, + 'messaging:!members-v9ktpgmYysZA-MjgC-GMoeEawFHSelkOdTu6JGxFZWU'); + expect(eventChannel.createdBy!.id, 'super-band-9'); + expect(eventChannel.frozen, false); + expect(eventChannel.members!.length, 2); + expect(eventChannel.memberCount, 2); + expect(eventChannel.config, isA()); + expect(eventChannel.name, 'test'); + }); + }); }); } From ec8e385927562a6ee7c812627b330d58f4c55d6f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 17 Jun 2021 14:22:50 +0530 Subject: [PATCH 385/599] feat: Added new docs --- .../docs/Flutter/stream_chat/messages.mdx | 119 ++++++++++++++++++ .../paginating_channel_messages.mdx | 6 - .../stream_chat/paginating_channels.mdx | 35 ++++++ .../docs/Flutter/stream_chat/slow_mode.mdx | 9 ++ 4 files changed, 163 insertions(+), 6 deletions(-) delete mode 100644 docusaurus/docs/Flutter/stream_chat/paginating_channel_messages.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat/paginating_channels.mdx diff --git a/docusaurus/docs/Flutter/stream_chat/messages.mdx b/docusaurus/docs/Flutter/stream_chat/messages.mdx index 90aa8a00..22224fbd 100644 --- a/docusaurus/docs/Flutter/stream_chat/messages.mdx +++ b/docusaurus/docs/Flutter/stream_chat/messages.mdx @@ -4,3 +4,122 @@ sidebar_position: 16 title: Messages --- +Let's dive right into it, the example below shows how to send a simple message using Stream: + +```dart +final message = Message( + text: '@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.' +); + +await channel.sendMessage(message); +``` + +Note how server side SDKs require that you specify `user_id` to indicate who is sending the message. +You can add custom fields to both the message and the attachments. +There's a 5KB limit for the custom fields. +File uploads are uploaded to the CDN so don't count towards this 5KB limit. + +A more complex example for creating a message is shown below: + +```dart +final message = Message( + text: '@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.', + attachments: [ + Attachment( + type: "image", + assetUrl: "https://bit.ly/2K74TaG", + thumbUrl: "https://bit.ly/2Uumxti", + extraData: { + "myCustomField": 123, + } + ), + ], + mentionedUsers: [ + User(id: "josh") + ], + extraData: { + "anotherCustomField": 234, + }, +); + +await channel.sendMessage(message); +``` + +By default Stream’s UI components support the following attachment types: + +* Audio +* Video +* Image +* Text + +You can specify different types as long as you implement the frontend rendering logic to handle them. Common use cases include: + +* Embedding products (photos, descriptions, outbound links, etc.) +* Sharing of a users location +* The React tutorial for Stream Chat explains how to customize the Attachment component. + +### Get a Message + +You can get a single message by its ID using the `getMessage` call: + +```dart +final message = await client.getMessage("message-id"); +``` + +### Update a Message + +You can edit a message by calling updateMessage and including a message with an ID – the ID field is required when editing a message: + +```dart +await client.updateMessage(Message(id: "123", text: "the edited version of my text")); +``` + +### Partial Update + +A partial update can be used to set and unset specific fields when it is necessary to retain additional data fields on the object. +AKA a patch style update. + +```dart +// partial update message text +const text = 'the text was partial updated'; +const updated = await client.partiallyUpdateMessage(originalMessage.id, { + set: { + text + } +}); + +// unset color property +await client.partiallyUpdateMessage(originalMessage.id, { + 'unset': ['color'], +}); + +// set nested property +await client.partiallyUpdateMessage(originalMessage.id, { + 'set': { + 'details.status': 'complete' + }, +}); +``` + +### Delete A Message + +You can delete a message by calling deleteMessage and including a message with an ID. Messages can be soft deleted or hard deleted. Unless specified via the hard parameter, messages are soft deleted. + +```dart +await client.deleteMessage("123"); +``` + +### Soft delete + +1. Can be done client-side by users +2. Message is still returned in the message list and all its data is kept as it is +3. Message type is set to "deleted" +4. Reactions and replies are kept in place + +### Hard delete + +1. Can be done client-side by users but be cautious this action is not recoverable +2. The message is removed from the channel and its data is wiped +3. All reactions are deleted +4. All replies and their reactions are deleted +5. By default messages are soft deleted, this is a great way to keep the channel history consistent. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/paginating_channel_messages.mdx b/docusaurus/docs/Flutter/stream_chat/paginating_channel_messages.mdx deleted file mode 100644 index 25311929..00000000 --- a/docusaurus/docs/Flutter/stream_chat/paginating_channel_messages.mdx +++ /dev/null @@ -1,6 +0,0 @@ ---- -id: paginating_channel_messages -sidebar_position: 15 -title: Paginating Channel Messages ---- - diff --git a/docusaurus/docs/Flutter/stream_chat/paginating_channels.mdx b/docusaurus/docs/Flutter/stream_chat/paginating_channels.mdx new file mode 100644 index 00000000..bf348aaf --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat/paginating_channels.mdx @@ -0,0 +1,35 @@ +--- +id: paginating_channels +sidebar_position: 15 +title: Channel Pagination +--- + +The channel query endpoint allows you to paginate the list of messages, watchers, and members for one channel. +To make sure that you are able to retrieve a consistent list of messages, pagination does not work with simple offset/limit parameters but instead, +it relies on passing the ID of the messages from the previous page. + +For example: say that you fetched the first 100 messages from a channel and want to lead the next 100. +To do this you need to make a channel query request and pass the ID of the oldest message if you are paginating in descending order or the ID of the newest message if paginating in ascending order. + +Use the `id_lt` parameter to retrieve messages older than the provided ID and `id_gt` to retrieve messages newer than the provided ID. + +The terms `id_lt` and `id_gt` stand for ID less than and ID greater than. + +ID-based pagination improves performance and prevents issues related to the list of messages changing while you’re paginating. If needed, you can also use the inclusive versions of those two parameters: id_lte and id_gte. + +```dart +final response = await channel.query( + messagesPagination: PaginationParams(limit: 2, lessThanOrEqual: "123"), + membersPagination: PaginationParams(limit: 2, offset: 0), + watchersPagination: PaginationParams(limit: 2, offset: 0), +); +``` + +For members and watchers, we use limit and offset parameters. + +Notes: + +1. Soon we will create friendlier aliases for `id_lt` and `id_gt`. Our best candidates are before_id and after_id, +let us know if you have any feedback or suggestion! + +2. The maximum number of messages that can be retrieved at once from the API is 300. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx b/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx index 9cd50efd..15ca5ad2 100644 --- a/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx +++ b/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx @@ -4,3 +4,12 @@ sidebar_position: 14 title: Slow Mode --- +Slow mode helps reduce noise on a channel by limiting users to a maximum of 1 message per cooldown interval. + +The cooldown interval is configurable and can be anything between 1 and 120 seconds. For instance, if you enable slow mode and set the cooldown interval to 30 seconds a user will be able to post at most 1 message every 30 seconds. + +Moderators, admins and server-side API calls are not restricted by the cooldown period and can post messages as usual. +Slow mode is disabled by default and can be enabled/disabled by admins and moderators. + +Note: SLOW MODE is in the works for the Flutter SDK + From 0860acb61ef1f0d2f872cc6da283028731472f01 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Jun 2021 14:35:20 +0530 Subject: [PATCH 386/599] fix tests Signed-off-by: Sahil Kumar --- .../test/src/back_button_test.dart | 3 +- .../test/src/channel_header_test.dart | 4 ++ .../test/src/message_action_modal_test.dart | 18 +++------ .../test/src/thread_header_test.dart | 1 + .../test/message_list_core_test.dart | 4 +- .../test/stream_channel_test.dart | 40 ++++++++++++++----- .../test/stream_chat_core_test.dart | 26 ++++++------ 7 files changed, 59 insertions(+), 37 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/back_button_test.dart b/packages/stream_chat_flutter/test/src/back_button_test.dart index 3fae1ace..efb889ac 100644 --- a/packages/stream_chat_flutter/test/src/back_button_test.dart +++ b/packages/stream_chat_flutter/test/src/back_button_test.dart @@ -118,8 +118,9 @@ void main() { final clientState = MockClientState(); when(() => client.state).thenReturn(clientState); + when(() => clientState.totalUnreadCount).thenAnswer((_) => 0); when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(0)); + .thenAnswer((_) => Stream.value(0)); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/src/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel_header_test.dart index 9f6e981d..87f3d028 100644 --- a/packages/stream_chat_flutter/test/src/channel_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_header_test.dart @@ -34,6 +34,7 @@ void main() { .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); when(() => channelState.unreadCountStream) .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ @@ -109,6 +110,7 @@ void main() { .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); when(() => client.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); + when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(1)); @@ -171,6 +173,7 @@ void main() { ]); when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(1)); @@ -375,6 +378,7 @@ void main() { ]); when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(1)); diff --git a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart index 6118e7d2..9b7b3e52 100644 --- a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart @@ -552,10 +552,8 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(() => client.flagMessage(any())).thenThrow(ApiError( - '{}', - 500, - )); + when(() => client.flagMessage(any())) + .thenThrow(StreamChatNetworkError(ChatErrorCode.internalSystemError)); final themeData = ThemeData(); final streamTheme = StreamChatThemeData.fromTheme(themeData); @@ -609,10 +607,8 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(() => client.flagMessage(any())).thenThrow(ApiError( - '{"code":4}', - 400, - )); + when(() => client.flagMessage(any())) + .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); final themeData = ThemeData(); final streamTheme = StreamChatThemeData.fromTheme(themeData); @@ -719,10 +715,8 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(() => channel.deleteMessage(any())).thenThrow(ApiError( - '{}', - 500, - )); + when(() => channel.deleteMessage(any())) + .thenThrow(StreamChatNetworkError(ChatErrorCode.internalSystemError)); final themeData = ThemeData(); final streamTheme = StreamChatThemeData.fromTheme(themeData); diff --git a/packages/stream_chat_flutter/test/src/thread_header_test.dart b/packages/stream_chat_flutter/test/src/thread_header_test.dart index 578b2ad6..2b649be3 100644 --- a/packages/stream_chat_flutter/test/src/thread_header_test.dart +++ b/packages/stream_chat_flutter/test/src/thread_header_test.dart @@ -45,6 +45,7 @@ void main() { ]); when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(1)); diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index 4e34f8f2..98e83f9f 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -300,7 +300,9 @@ void main() { when(() => mockChannel.state.isUpToDate).thenReturn(false); when(() => mockChannel.initialized).thenAnswer((_) async => true); when(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), membersPagination: any(named: 'membersPagination'), messagesPagination: any(named: 'messagesPagination'), preferOffline: any(named: 'preferOffline'), diff --git a/packages/stream_chat_flutter_core/test/stream_channel_test.dart b/packages/stream_chat_flutter_core/test/stream_channel_test.dart index b48c02b1..2b32cf2d 100644 --- a/packages/stream_chat_flutter_core/test/stream_channel_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_channel_test.dart @@ -165,7 +165,9 @@ void main() { when(() => mockChannel.initialized).thenAnswer((_) async => true); final messages = _generateMessages(); when(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: any(named: 'messagesPagination'), membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -183,7 +185,9 @@ void main() { verify(() => mockChannel.initialized).called(1); verify(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: any(named: 'messagesPagination'), membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -228,7 +232,9 @@ void main() { final messages = _generateMessages(); when(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: beforePagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -236,7 +242,9 @@ void main() { )).thenAnswer((_) async => ChannelState(messages: messages)); when(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: afterPagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -259,7 +267,9 @@ void main() { await tester.pumpAndSettle(); verify(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: beforePagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -267,7 +277,9 @@ void main() { )).called(1); verify(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: afterPagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -285,7 +297,9 @@ void main() { ); when(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: updatedBeforePagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -293,7 +307,9 @@ void main() { )).thenAnswer((_) async => ChannelState(messages: messages)); when(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: updatedAfterPagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -303,7 +319,9 @@ void main() { await tester.pumpAndSettle(); verify(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: updatedBeforePagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), @@ -311,7 +329,9 @@ void main() { )).called(1); verify(() => mockChannel.query( - options: any(named: 'options'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), messagesPagination: updatedAfterPagination, membersPagination: any(named: 'membersPagination'), watchersPagination: any(named: 'watchersPagination'), diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index 168454c8..f2b1b514 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -227,7 +227,7 @@ void main() { ); testWidgets( - 'didChangeAppLifecycleState should not call client.connect() ' + 'didChangeAppLifecycleState should not call client.openConnection() ' 'if connection is not available in case the ' 'widget lifestyle changes to AppLifecycleState.resume', (tester) async { @@ -238,8 +238,8 @@ void main() { final event = Event(); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.connect()).thenAnswer((_) async => event); - when(() => mockClient.disconnect()).thenAnswer((_) async => null); + when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -267,7 +267,7 @@ void main() { streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.resumed); - verifyNever(() => mockClient.connect()); + verifyNever(() => mockClient.openConnection()); }); }, ); @@ -327,8 +327,8 @@ void main() { final event = Event(); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.connect()).thenAnswer((_) async => event); - when(() => mockClient.disconnect()).thenAnswer((_) async => null); + when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -348,7 +348,7 @@ void main() { await Future.delayed(const Duration(seconds: 1)); - verify(() => mockClient.connect()).called(1); + verify(() => mockClient.openConnection()).called(1); }); }, ); @@ -376,8 +376,8 @@ void main() { final event = Event(); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.connect()).thenAnswer((_) async => event); - when(mockClient.disconnect).thenAnswer((_) async => null); + when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.connected); @@ -385,7 +385,7 @@ void main() { await Future.delayed(const Duration(seconds: 1)); - verify(() => mockClient.disconnect()).called(1); + verify(() => mockClient.closeConnection()).called(1); }); }, ); @@ -402,8 +402,8 @@ void main() { final event = Event(); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.connect()).thenAnswer((_) async => event); - when(() => mockClient.disconnect()).thenAnswer((_) async => null); + when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -432,7 +432,7 @@ void main() { await Future.delayed(const Duration(seconds: 1)); - verifyNever(() => mockClient.disconnect()); + verifyNever(() => mockClient.closeConnection()); }); }, ); From 875eda8cb5566337bc9bd6fa0e2c88a08171e5eb Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 11:27:31 +0200 Subject: [PATCH 387/599] Update packages/stream_chat/lib/src/core/models/mute.dart Co-authored-by: Sahil Kumar --- packages/stream_chat/lib/src/core/models/mute.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/core/models/mute.dart b/packages/stream_chat/lib/src/core/models/mute.dart index 4b14ad01..857b34a1 100644 --- a/packages/stream_chat/lib/src/core/models/mute.dart +++ b/packages/stream_chat/lib/src/core/models/mute.dart @@ -6,9 +6,7 @@ import 'package:stream_chat/src/core/util/serializer.dart'; part 'mute.g.dart'; /// The class that contains the information about a muted user -@JsonSerializable( - createToJson: false, -) +@JsonSerializable(createToJson: false) class Mute { /// Constructor used for json serialization Mute({ From 6a2efb18e44f9447e4781178fa8ada09ca2fe148 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Jun 2021 15:01:45 +0530 Subject: [PATCH 388/599] Update packages/stream_chat/test/src/core/models/reaction_test.dart --- .../test/src/core/models/reaction_test.dart | 30 ++++--------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/packages/stream_chat/test/src/core/models/reaction_test.dart b/packages/stream_chat/test/src/core/models/reaction_test.dart index cfe9f69e..b603477c 100644 --- a/packages/stream_chat/test/src/core/models/reaction_test.dart +++ b/packages/stream_chat/test/src/core/models/reaction_test.dart @@ -77,34 +77,16 @@ void main() { userId: 'test', ); - expect( - newReaction.type, - 'lol', - ); + expect(newReaction.type, 'lol'); expect( newReaction.createdAt, DateTime.parse('2021-01-28T22:17:31.108742Z'), ); - expect( - newReaction.extraData, - {}, - ); - expect( - newReaction.messageId, - 'test', - ); - expect( - newReaction.score, - 2, - ); - expect( - newReaction.user, - User(id: 'test'), - ); - expect( - newReaction.userId, - 'test', - ); + expect(newReaction.extraData, {}); + expect(newReaction.messageId, 'test'); + expect(newReaction.score, 2); + expect(newReaction.user, User(id: 'test')); + expect(newReaction.userId, 'test'); }); test('merge', () { From a98148e0db4a2f603e2bc36818b879d2e1c8efdd Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Jun 2021 15:01:53 +0530 Subject: [PATCH 389/599] Update packages/stream_chat/test/src/core/models/reaction_test.dart --- .../test/src/core/models/reaction_test.dart | 30 ++++--------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/packages/stream_chat/test/src/core/models/reaction_test.dart b/packages/stream_chat/test/src/core/models/reaction_test.dart index b603477c..518a223e 100644 --- a/packages/stream_chat/test/src/core/models/reaction_test.dart +++ b/packages/stream_chat/test/src/core/models/reaction_test.dart @@ -103,34 +103,16 @@ void main() { ), ); - expect( - newReaction.type, - 'lol', - ); + expect(newReaction.type, 'lol'); expect( newReaction.createdAt, DateTime.parse('2021-01-28T22:17:31.108742Z'), ); - expect( - newReaction.extraData, - {}, - ); - expect( - newReaction.messageId, - 'test', - ); - expect( - newReaction.score, - 2, - ); - expect( - newReaction.user, - User(id: 'test'), - ); - expect( - newReaction.userId, - 'test', - ); + expect(newReaction.extraData, {}); + expect(newReaction.messageId, 'test'); + expect(newReaction.score, 2); + expect(newReaction.user, User(id: 'test')); + expect(newReaction.userId, 'test'); }); }); } From 98d8f759070d57e2a1f80f173fc8731d713c619b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 17 Jun 2021 15:06:01 +0530 Subject: [PATCH 390/599] feat: Added new docs --- .../docs/Flutter/stream_chat/reactions.mdx | 44 +++++++++ .../stream_chat/searching_messages.mdx | 25 +++++ .../stream_chat/threads_and_replies.mdx | 48 ++++++++++ .../Flutter/stream_chat/uploading_files.mdx | 95 +++++++++++++++++++ 4 files changed, 212 insertions(+) diff --git a/docusaurus/docs/Flutter/stream_chat/reactions.mdx b/docusaurus/docs/Flutter/stream_chat/reactions.mdx index a94cbfa3..d2dc114f 100644 --- a/docusaurus/docs/Flutter/stream_chat/reactions.mdx +++ b/docusaurus/docs/Flutter/stream_chat/reactions.mdx @@ -4,3 +4,47 @@ sidebar_position: 18 title: Reactions --- +Stream Chat has built-in support for user Reactions. Common examples are likes, comments, loves, etc. Reactions can be customized so that you are able to use any type of reaction your application requires. + + +Similar to other objects in Stream Chat, reactions allow you to add custom data to the reaction of your choice. This is helpful if you want to customize the reaction logic. + +Note: Custom data for reactions is limited to 1KB. + +```dart +await channel.sendReaction("messageID", "like", extraData: {"customField": 1}); +``` + +### Removing a Reaction + +```dart +await channel.deleteReaction("messageID", "like"); +``` + +### Paginating Reactions + +Messages returned by the APIs automatically include the 10 most recent reactions. +You can also retrieve more reactions and paginate using the following logic: + +```dart +// get the first 10 reactions +await channel.getReactions("messageID", PaginationParams(limit: 10)); + +// get 3 reactions past the first 10 +await channel.getReactions("messageID", PaginationParams(limit: 3, offset:10)); +``` + +### Cumulative (Clap) Reactions + +You can use the Reactions API to build something similar to Medium's clap reactions. +If you are not familiar with this, Medium allows you to clap articles more than once and shows the sum of all claps from all users. + +To do this, you only need to include a score for the reaction (ie. user X clapped 25 times) and the API will return the sum of all reaction scores as well as each user individual scores (ie. clapped 475 times, user Y clapped 14 times). + +```dart +// user claps 5 times on a message +await channel.sendReaction("messageID", "like", score: 5); + +// same user claps 20 times more +await channel.sendReaction("messageID", "like", score: 25); +``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx b/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx index 7ff1188d..1d1e9b41 100644 --- a/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx +++ b/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx @@ -4,3 +4,28 @@ sidebar_position: 20 title: Searching Messages --- +Message search is built-in to the chat API. You can enable and/or disable the search indexing on a per channel type. +The command shown below selects the channels in which John is a member. +Next, it searches the messages in those channels for the keyword “'supercalifragilisticexpialidocious'”: + +```dart +final search = await client.search( + Filter.in_('members', ['john']), + query: 'supercalifragilisticexpialidocious', + paginationParams: PaginationParams( + limit: 2, + offset: 0 + ), +); +``` + +Pagination works via the standard limit and offset parameters. The first argument, filter, uses a MongoDB style query expression. + +We do not run MongoDB on the backend, so only a subset of the standard MongoDB filters are supported. +Additionally, this endpoint can be used to search for messages that have attachments. + +```dart +// Search file of type videos + final response = await channel.search(messageFilter: Filter.in_('attachments.type', ['video'])); + return response; +``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx b/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx index b3dac44c..904b9c8d 100644 --- a/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx +++ b/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx @@ -4,3 +4,51 @@ sidebar_position: 19 title: Threads And Replies --- +Threads and replies provide your users with a way to go into more detail about a specific topic. + +This can be very helpful to keep the conversation organized and reduce noise. +To create a thread you simply send a message with a `parent_id`. Have a look at the example below: + +```dart +final reply = await channel.sendMessage( + Message(text: 'Hey, I am replying to a message!', + parentId: parentID, + showInChannel: false, +)); +``` + +If you specify `show_in_channel`, the message will be visible both in a thread of replies as well as the main channel. +Messages inside a thread can also have reactions, attachments and mention as any other message. + +### Thread Pagination + +When you read a channel you do not receive messages inside threads. +The parent message includes the count of replies which it is usually what apps show as the link to the thread screen. +Reading a thread and paginating its messages works in a very similar way as paginating a channel. + +```dart +// retrieve the first 20 messages inside the thread +await channel.getReplies(parentMessageId, PaginationParams(limit: 20)); + +// retrieve the 20 more messages before the message with id "42" +await channel.getReplies(parentMessageId, PaginationParams(limit: 20, lessThanOrEqual: '42')); +``` + +### Quote Message + +Instead of replying in a thread, it's also possible to quote a message. Quoting a message doesn't result in the creation of a thread; +the message is quoted inline. + +To quote a message, simply provide the `quoted_message_id` field when sending a message: + +```dart +// Create the initial message +await channel.sendMessage(Message(id: 'first_message_id', text: 'The initial message' )); + +// Quote the initial message +final res = await channel.sendMessage(Message( + id: 'message_with_quoted_message', + text: 'This is the first message that quotes another message', + quotedMessageId: 'first_message_id', + )); +``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx b/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx index 9f7cf6ee..419ab7e0 100644 --- a/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx +++ b/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx @@ -4,3 +4,98 @@ sidebar_position: 17 title: Uploading Files --- +The `channel.sendImage` and `channel.sendFile` methods make it easy to upload files. + +This functionality defaults to using the Stream CDN. If you would like, you can easily change the logic to upload to your own CDN of choice. +The maximum file size is 20mb for the Stream Chat CDN. + +```dart +final client = StreamChatClient('api-key'); +var attachment = Attachment(); + +// Upload an image without monitoring send progress +client.sendImage(image, channelId, channelType).then((response) { + // Successful upload, you can now attach this image + // to an message that you then send to a channel + final imageUrl = response.file; + attachment = attachment.copyWith( + type: 'image', + imageUrl: imageUrl, + ); + final message = Message(attachments: [attachment]); + client.sendMessage(message, channelId, channelType); +}).catchError((error, stk) { + // Handle error +}); + +// Upload an file, monitoring the progress with a onSendProgress callback +await client.sendFile( + file, + channelId, + channelType, + onSendProgress: (sent, total) { + // Handle the send progress + attachment = attachment.copyWith( + uploadState: UploadState.inProgress( + uploaded: sent, + total: total, + ), + ); + }, + ).then((response) { + // Successful upload, you can now attach this file + // to an message that you then send to a channel + final fileUrl = response.file; + attachment = attachment.copyWith( + type: 'file', + assetUrl: fileUrl, + uploadState: UploadState.success(), + ); + final message = Message(attachments: [attachment]); + client.sendMessage(message, channelId, channelType); + }).catchError((error, stk) { + // Handle error + attachment = attachment.copyWith( + uploadState: UploadState.failed(error: error), + ); + }); + +// Alternatively you can call the sendMessage directly on the channel +// which will automatically handle all the upload process of the provided +// attachments. +final channel = client.channel(channelType, id: channelId); + +// Creating a message object with multiple local attachments +final message = Message(text: 'Hello', attachments: [ + Attachment( + type: 'image', + file: AttachmentFile(path: 'imagePath/imageName.png'), + ), + Attachment( + type: 'file', + file: AttachmentFile(path: 'filePath/fileName.pdf'), + ), +]); + +// Sending the message to the channel +await channel.sendMessage(message); +``` + +In the code example above, note how the message attachments are created after the files are uploaded. The React components support regular uploads, clipboard pasting, drag and drop, as well as URL enrichment via built-in open-graph scraping. As a bonus, the Stream CDN will automatically handle image resizing for you. + +### Using Your Own CDN + +All 5 SDKs make it easy to use your own CDN for uploads. The code examples below show how to change where files are uploaded: + +```dart +// Set a custom FileUploader implementation when building your client +final client = StreamChatClient( + 'api-key', + attachmentFileUploader: MyFileUploader(), +); +``` + +You'll have to create your own implementation of the `AttachmentFileUploader` interface, +and any upload calls will be sent to that implementation. +Take a look [at the interface](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat/lib/src/attachment_file_uploader.dart#L7) +and [our own default implementation of it](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat/lib/src/attachment_file_uploader.dart#L58) for more info. \ No newline at end of file From 7b4fd8910e06b97c19b960049e78d0caf9316500 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 17 Jun 2021 15:35:28 +0530 Subject: [PATCH 391/599] feat: Added new docs --- .../docs/Flutter/stream_chat/events.mdx | 78 ++++++++++++ .../Flutter/stream_chat/moderation_tools.mdx | 112 ++++++++++++++++++ .../Flutter/stream_chat/pinned_messages.mdx | 58 +++++++++ .../Flutter/stream_chat/silent_messages.mdx | 31 +++++ .../Flutter/stream_chat/typing_indicators.mdx | 54 +++++++++ 5 files changed, 333 insertions(+) diff --git a/docusaurus/docs/Flutter/stream_chat/events.mdx b/docusaurus/docs/Flutter/stream_chat/events.mdx index e171b295..fb32233f 100644 --- a/docusaurus/docs/Flutter/stream_chat/events.mdx +++ b/docusaurus/docs/Flutter/stream_chat/events.mdx @@ -4,3 +4,81 @@ sidebar_position: 25 title: Events --- +Events allow the client to stay up to date with changes to the chat. Examples are a new message, a user's image that updated, a reaction, or a member joining the channel. There are a 4 different type of events. These first 2 types of events you receive if you're connected to Stream, no further action is needed to receive these events: + +Client events: You always receive these events. +Examples are `connection.recovered`, `health.check` and `connection.changed` +Notification events: Notification events notify you when something changed on a channel you are a member of even if you're not explicitly watching that channel. +One example is someone starting a new channel to message you while you are currently in the app. + +User presence events: `queryUsers`, `queryChannels` and `channel.watch()` allow you to specify `presence=True`, +if you specify this option you'll opt in to receiving updates about the users on these channels. +You'll receive events when they go online/offline or their data is updated +Channel events: if you call `queryChannels` with `watch=true`, or you call `channel.watch()` you'll opt-in to receiving events for the given channels. +Examples are new messages, people joining the chat etc. + +### Listening To Events + +As soon as you call watch on a Channel or queryChannels you’ll start to listen to these events. You can hook into specific events: + +```dart +channel.on("message.deleted").listen((Event event) { + print("message ${event.message.id} was deleted"); +}); +``` + +You can also listen to all events at once: + +```dart +channel.on().listen((Event event) { + print("received a new event of type ${event.type}"); +}); +``` + +### Connection Events + +The official SDKs make sure that a connection to Stream is kept alive at all times and that chat state +is recovered when the user's internet connection comes back online. +Your application can subscribe to changes to the connection using client events. + +```dart +client.on('connection.changed', (e) => { + if (e.online) { + print('the connection is up!'); + } else { + print('the connection is down!'); + } +}); +``` + +### Stop Listening for Events + +It is a good practice to unregister event handlers once they are not in use anymore. Doing so will save you from performance degradations coming from memory leaks or even from errors and exceptions (i.e. null pointer exceptions) + +```dart +final subscription = channel.on().listen((Event event) { + print("received a new event of type ${event.type}"); +}); + +subscription.cancel(); +``` + +### Custom Events + +Custom events allow you to build more complex interactions within a channel or with a user. + +#### To a channel + +Users connected to a channel, either as a watcher or member, can send custom events and have them delivered to all users watching the channel. + +```dart +// sends an event for the current user to all connect clients on the channel +await channel.sendEvent( + Event( + type: 'friendship_request', + extraData: { + 'text': 'Hey there, long time no see!', + }, + ), +); +``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx b/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx index 7eb14af2..b7af0413 100644 --- a/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx +++ b/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx @@ -4,3 +4,115 @@ sidebar_position: 23 title: Moderation Tools --- +### Flag + +Any user is allowed to flag a message. +Flagging does not perform any particular action on the chat. +The API will only trigger the related webhook event and make the message appear on your Stream Dashboard Chat Moderation view. + +```dart +await client.flagMessage("messageID"); +``` + +### Mutes + +Any user is allowed to mute another user. +Mutes are stored at user level and returned with the rest of the user information when connectUser is called. +A user will be be muted until the user is unmuted or the mute is expired. + +```dart +await client.muteUser("eviluser"); + +await client.unmuteUser("eviluser"); +``` + +After muting a user messages will still be delivered via web-socket. +Implementing business logic such as hiding messages from muted users or display them differently is left to the developer to implement. + +Messages from muted users are not delivered via push (APN/Firebase) + +### Ban + +Users can be banned from an app entirely or from a channel. When a user is banned, they will not be allowed to post messages until the ban is removed or expired but will be able to connect to Chat and to channels as before. + +Users must be a member of a channel to be banned from that channel. Channel watchers cannot be banned. + +It is also possible to ban the user's last known IP address to prevent creation of new "throw-away" accounts. This type of ban is only applicable on the app level. We do not recommend applying IP ban without reasonable timeout, however this is not restricted. The IP address will be unbanned either after reaching a timeout or with explicit user unban. + +In most cases only admins or moderators are allowed to ban other users from a channel. + +```dart +// ban a user for 60 minutes from all channel +await client.banUser('eviluser', { + 'banned_by_id': userID, // ID of the user who is performing the ban (Server-side auth) + 'timeout': 60, + 'reason': 'Banned for one hour', +}); + +// ban a user and their IP address for 24 hours + await client.banUser('eviluser', { + 'banned_by_id': userID, + 'timeout': 24*60, + 'ip_ban': true, + 'reason': 'Please come back tomorrow', +}); + +// ban a user from the livestream:fortnite channel +await channel.banUser('eviluser', { + 'banned_by_id': userID, + 'reason': 'Profanity is not allowed here', +}); + +// remove ban from channel +await channel.unbanUser('eviluser'); + +// remove global ban +await authClient.unbanUser('eviluser'); +``` + +### Query Banned Users + +Banned users can be retrieved in different ways: + +Using the dedicated query bans endpoint +User Search: you can add the banned:true condition to your search. Please note that this will only return users that were banned at the app-level and not the ones that were banned only on channels. + +```dart +// retrieve the list of banned users + await client.queryUsers(filter: Filter.equal('banned', true), pagination: PaginationParams(limit:10, offset:0)); +``` + +### Shadow Ban + +Users can be shadow banned from a channel, set of channels, or an entire app. When a user is shadow banned, they will still be allowed to post messages, but any message sent during the ban, will have the shadowed: true field set; this will be invisible from the author of the message. + +You will need to implement UI logic for how your application will handle shadowed messages. Having the client hide these messages for everybody other than the user sending them is a common approach. + +```dart +// shadow ban a user from all channels +await client.shadowBan('eviluser'); + +// shadow ban a user from a channel +await channel.shadowBan('eviluser'); + +// remove shadow ban from channel +await channel.removeShadowBan('eviluser'); + +// remove global shadow ban +await client.removeShadowBan('eviluser'); +Administrators can view shadow banned user status in queryChannels(), queryMembers() and queryUsers(). +``` + +### Block Lists + +A list of words you can define to moderate chat messages. +A block list can be assigned to each channel type to either block or flag messages that contain these words. +More information can be found [here](https://getstream.io/chat/docs/react/block_lists/?language=dart). + +### Advanced Chat Moderation + +Advanced Chat Moderation is currently in beta and accepting trial candidates. Please contact support to discuss your options. +Advanced Chat Moderation uses an AI-based classification system to detect various types of bad content. +The tool is powered by a machine learning model that provides a confidence interval (0-1) for a message, +in each of three categories: Spam, Explicit and Toxic. +The model is highly configurable for each channel type and removes the manual work of a human moderator. You can learn more [here](https://getstream.io/chat/docs/react/advanced_moderation_beta/?language=dart). \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx b/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx index fba0ca07..7de1d999 100644 --- a/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx +++ b/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx @@ -4,3 +4,61 @@ sidebar_position: 22 title: Pinned Messages --- +Pinned messages allow users to highlight important messages, make announcements, or temporarily promote content. +Pinning a message is, by default, restricted to certain user roles, but this is flexible. +Each channel can have multiple pinned messages and these can be created or updated with or without an expiration. + +### Pin and unpin a message + +An existing message can be updated to be pinned or unpinned by using the `channel.pinMessage` and `channel.unpinMessage` methods. +Or a new message can be pinned when it is sent by setting the `pinned` and `pin_expires` fields when using `channel.sendMessage`. + +```dart +// create pinned message +final message = await channel + .sendMessage(Message( + text: 'my message', + pinned: true, + pinExpires: DateTime.now().add(Duration(days: 3)), + )) + .then((resp) => resp.message); + +// unpin message +await channel.unpinMessage(message); + +// pin message for 120 seconds +await channel.pinMessage(message, 120); + +// change message expiration to 2077 +await channel.pinMessage(message, DateTime(2077)); + +// remove expiration date from pinned message +await channel.pinMessage(message, null); +``` + +To pin the message user has to have PinMessage permission. +You can find the list of permissions and defaults in Permission Resources and Default Permissions sections + +### Retrieve pinned messages + +You can easily retrieve the last 10 pinned messages from the `channel.pinned_messages` field: + +```dart +// get channel state +final channelState = await channel.query(); + +// get pinned messages from it +final pinnedMessages = channelState.pinnedMessages; +``` + +To learn more about channels you can visit Querying Channels page +Search for all pinned messages + +Stream Chat also provides search filter in case if you need to display more than 10 pinned messages in specific channel. + +```dart +// list all pinned messages of the channel +final response = await channel.search( + messageFilters: Filter.equal('pinned', true), +); +``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx b/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx index c2bb2172..52d05b2b 100644 --- a/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx +++ b/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx @@ -4,3 +4,34 @@ sidebar_position: 21 title: Silent Messages --- +Sometimes you want to add system or transactional messages to channels such as: "your ride is waiting for you", +"James updated the information for the trip", "You and Jane are now matched" and so on. + +You may not want these messages to mark a channel as unread or increase the unread messages for users. + +Silent messages are special messages that don't increase the unread messages count nor mark a channel as unread. +Creating a silent message is very simple, you only need to include the silent field boolean field and set it to true. + +```dart +final text = 'You completed your trip'; + +const message = { + + text: text, + + user: systemUser, + + silent: true, + + attachments: tripAttachments, + +}; +await channel.sendMessage(message); +``` + +Notes: + +1. Existing messages cannot be turned into a silent message or vice versa. + +2. Silent messages do send push notifications by default. To skip our push notification service, +mark the message with `skip_push: true`. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx b/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx index 908854db..8323e46a 100644 --- a/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx +++ b/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx @@ -4,3 +4,57 @@ sidebar_position: 24 title: Typing Indicators --- +All official SDKs support typing events out of the box and are handled on all channels with the typing_events featured enabled. +Typing indicators allow you to show to users who is currently typing in the channel. +This feature can be switched on/off on a channel-type basis using the CLI or directly from the Dashboard. +If you are using one of the official SDK libraries, you will only need to ensure that typing indicators are enabled to get this working. + +If you are building your UI on top of one of our Chat Clients instead, you will need to take care of four things: + +Send an event `typing.start` when the user starts typing +Send an event `typing.stop` after the user stopped typing +Handle the two events and use them to toggle the typing indicator UI +Use `parent_id` field of the event to indicate that typing is happening in a thread + +### Sending start and stop typing events + +```dart +// The Dart client keeps track of the typing state for you. +// Just call `channel.keystroke()` when the user types and +// `channel.stopTyping()` when the user sends the message (or aborts) + +// sends a typing.start event at most once every two seconds +await channel.keystroke(); + +// sends a typing.start event for a particular thread +await channel.keystroke(thread_id); + +// sends the typing.stop event +await channel.stopTyping(); +``` + +When sending events on user input, you should make sure to follow some best-practices to avoid bugs. + +Only send typing.start when the user starts typing +Send typing.stop after a few seconds since the last keystroke + +### Receiving typing indicator events + +```dart +// channels keep track of the users that are currently typing +// the `channel.state.typingEvents` is an immutable object which gets regenerated +// every time a new user is added or removed to this list +print(channel.state.typingEvents); + +// add typing start event handling +channel.on('typing.start', (event) => { + print('${event.user.name} started typing'); +}); + +// add typing stop event handling +channel.on('typing.stop', event => { + print('${event.user.name} stopped typing'); +}); +``` + +Because clients might fail at sending `typing.stop` event all Chat clients periodically prune the list of typing users. \ No newline at end of file From b7efe258c983e506b0dea9fbbd1eeee78346b072 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Jun 2021 16:11:05 +0530 Subject: [PATCH 392/599] reset golden files to previous revisions Signed-off-by: Sahil Kumar --- .../src/goldens/deleted_message_custom.png | Bin 2334 -> 2936 bytes .../test/src/goldens/deleted_message_dark.png | Bin 2262 -> 2785 bytes .../src/goldens/deleted_message_light.png | Bin 2161 -> 2750 bytes .../test/src/goldens/reaction_bubble_2.png | Bin 2937 -> 2920 bytes .../test/src/goldens/system_message_dark.png | Bin 1679 -> 1812 bytes .../test/src/goldens/system_message_light.png | Bin 1746 -> 1866 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png index 693db3c78ee724f41d6376bd5c7ba1eb1385750d..c464bc01de48fcef06c538bd284ed284370f55aa 100644 GIT binary patch delta 2416 zcmXArc~n#97R9U9ieQCW;#h`i3J8i+$YTbzfCOYrVi*#rr3#1;5JJEJSEQ)43sVw8 ztP!xHrXhqpjA0(7JSHK6Kx9UykVL`^CSmAD-(B~wyY4!7pMCcEeK+sGr-Q$rgEWpK zoz9~Z9?uRBqK*;unin+p{-V!{HamRo)L6pP0QrsPtS+0ps8_jN$7K29^hRg0_}j*? z<^`v~XH#ZpZ%;kE@Z`hqv0v*8chg{QiWgE8V@!p3cVu7~nKCFR^XrD;+?J`EyLTkO zDTBK{^E|tA$G*>Z9#;F@OiS%YeXV+$nc91r*^VQS@vv_F`3$EE6}V1C`qut@p?JNJ z$0L!g9{h?k#YU60Y;A1=f`bj!U8(CSbpZhZXn)D(!r;oem;FV3B9Wl1OgwEC{W`%; z*M4L9-J)tk{-CkLaCC4Gb?V@W{olm+u6~K#mC^LmK!1NsteRCre0=5@p$guVSMbUYs$q-f%ZG-rIE}w&TMxC}W2)3i-8&lD zd*O7qlXN$oDmz-M5#5dL9lcRI^L6ZolGyR)O*W1|CI^0q_6rOQ+r=_J)lB`Dt2D%x+DeQ)Z^Rse!C1m#6@XQ~Vu17_A!eFrPM0gwy4L{%$85!A1 zEGsKR*xIVfG*3ZS|1I?M)vHYkYh8+7z-1%t;BSGUtiA7)Z0s{j0|XRB!S>gwXv~z z?CAB}S2;m()XsE)seRrvIy(2!tODoh?(QA{MCoh2T@pIZRi}0a7$7w?G{7XfJ3AlT zB9nXj`z0T7;M0#DKSo;|#Y9GOD=RB4936QYhfG^07L4+R5um;H_V&W!;ywJ%; z6Trdlbd5vH-R@`D-bOP6f(Y zm><|^X$$U3LV{mPT`B>WAg3w-GlpF>FVP^J3 zsZ>%ak(nGEJz2j=anbBBIwYiEX(X4;KH2os_jsr_$=`8(KFxgVFc|d6NS>W8C1qm; zi&U|$A`l4bGpY1CQnfXIrh5KP=VR-Fyu3VWI{-mvz9WxLhk1B-oTRn7QXesyf~qPG z(~?lgVqNU$=%8*^D|)-S{_O3=L`Fpw3IrHS!bmn8xbI3eFMjHG>P1tN^OyVfK~@)6 z#(S$)SK~cAJs%B{q5)e7b#&a?nAp6i*%VQH6SJD?bo{HtnxNe~cYvGA<(>rl3|tU* z=Ax35Ykn$u2zrDg`F#GOBB|{fzq89;OU+1I9Z6NHRA8Z<>!u`?yes*6UbU9S4mVO}skH>(4nDDA^h7EOZcADGR7>7I7k2~LOYjY!#NbM8W zNjg`rU31Z;H1Fkq@@raJ_uUrjbA1@NAQt<|ws*n?mikH{Kq{BQboSrFpil*yn<>r( zK1j&(Ex*6$PQ#NC@|?Z+d-e1?V4buxoU$(&K)oUntG3qR^y$;v`R8yrAm`1d?Nc`^ zVD;}4NvB}M8o=BX0sGD=i0ORBH;gpHCdL?Nt zFE4qupE|fUzup*N2_Tv4saN^mD_lnli=Q8W7!bE(u%K6xuyQ>%76s`gFK2<7$38Q4 zL1VEjfdJv*<;8ja+!=*J0q%>QyEC>?B9VkEHc}847Gc1IARhKlZ h$U5GC0pAKv z4-*p;<;L15EYl^oH9Thj_h-*O6$Oz!wmIeJ=S?D&!PzAxK481cqK19J`T+_?jn;C# z93aJH%bvSZsq__yPgd4f#JISyxHvR8jjS_EO6SJb3wkQo=0-_*=8X1M@zNjv`fMya z_baXO-Y$tuhH`iR?b(KGe$O0Kt(a!2-)mG#Sov@y@bi{9mjp*{rv!c=M$UXyS}Fw+ zw~16y^x}m}!;juH8V%ZpCKk(pTeA-8=;#2#1Ox^3EiS%{m6Y=N?-EBqfB`-tl9F&> z^KYqU%k&iCmHbelR6bm_&BpidADXjH%J28B>Lg)I1W`ZykX9YUOW9O}_V)Dr#o@H$ z>UzrC=n;Aq!vh01e(ZTjm;3knE&8fxlAsX8|Mr4_V(WT=sTgb1zI$R z2bGre6_XanE6IHo39#v{l2gnMskE0Potg0t4Q1uzWa2~5=4|; zfu{IF&0O+OYW;R8V@uXJ*TC?P(K%6l{cT@%Hh|jlSi`OwmAtCc?lVJX1j}zBVQ58o ztia9!fzVOqFbz8dSS=ZPw^d6=a+BcP&}Qw4Y)#8G7*?!LBod<>?AvY+s_t`A!RxsL z!qBmN&@CE;qH#i^FCeri%>_VMbLC~qRu1hYp?q_=x_i@d2GFV2M1%TiQ5w=T5d)5n zO;y^~lW{278;_q=R!=?3GD!%aJ Goqqs9Ee-zw delta 1816 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 Ef*kF!`v3p{ delta 1719 zcmV;o21xng71j}uKyCE<|^YbkM_@IGa4*)pNlTiU$lVAZ$lVAZ$lVAZ2F(iAn;loy|SDQv38b~C1 zvfZ8q(rUGI*lH=!qa8h$9}k1j+Y!SJvo$#EPx$@hPICYR01aUAW`YG*yPGjn_lYH!}0YTeTwi(EFV zzkUDxnT+c^x)^_f1VQ`_e|Opt3=eC3d|dTV1UXH&em(6NClac3n+NssFR4S?2Y)X>S zsgw${v+_JoVHj$qT-MgsR@Y^&>#8t2t1r@NNz$wBZIypYC52HGe{C+C)!^Wu_V@R- zy}hj<2=vcifBoR{rBdmmSKfEW?oF zKJ0qzx{a&fTuP_YfA7VHTkXFG`4qp+`)#qPN~NMJSFR`u!}z3{$>%k2>5|^mY8o9G z(UWpnl2m`3o0I2x`t8vpRVo#Yjg86oeO0T~IDq>PAL{YSiY|ZlnX=QV;9HTCFC}^Wv|QB+qpvNoyM$y8qxo*JYA) z|G|HQ_=Ilm?{|!|FgvTKm5SCkHWWsYwyRZz?U-HHjqh;Bac@_v@t96>T~||6Q}MdT z$iD48eXrH|Eb8~vyzH(g+0RjVnV%PE)5DvTnnZ*0hQU3s1tU&XElfgY7g@pD7RYYl%7 z%k?~!+VgYY_jP-5@qf=4A6eFDG<17$QBNxsmCI#?QKZ7mjQqhtNs8ZDJH8ShZ|gYh zAPDq)b2ENzi|4u>uk-FPu=elW=MQV$U!Z6f+v)Of-<2dm)GMS9V z$H(K7YxAAJUKC!hQVsu?`Op6-ANqxt#v}`|o*_|KpnNW;q~qxM^XJ;#+k1a+pBs&af*{cB*I#RFY)oMocH9-L{hRpmONCK%s$(%Q zFraiQrKzbYy?FIX2L}h{`rXF$XY}$RJuyE&-;@7d!K>sH6~X}(z0(@KRn`aoFUKcx4AwT%tU z7mM;dPxX3TOQlj@|M2XugeexzHKtg=IYuCWfj|HQfdB>q0Sp8J7<&41kQSd2f-YJ; z{)mj81(HZ4xP76kUVlVJ&jRV?hoS(d#YG&GApwe$U;#^$U;!x>_%Fneu2z=3CMo~` N002ovPDHLkV1k_0l*0f3 diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_light.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_light.png index 1f6330eb9bd92be6130bae15872bfe6d51878d88..d5a453ffc0cf157efe7d6a8e84648ab9a4a90530 100644 GIT binary patch delta 2211 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≪w6h+B8hlht+US3X4l8dj^>-B$<`FR+I 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 WMtx2FJ+qtu0000#)RxWP1EP?e> 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 diff --git a/packages/stream_chat_flutter/test/src/goldens/system_message_light.png b/packages/stream_chat_flutter/test/src/goldens/system_message_light.png index 32c2196cf3616acd94887e02b232c13f333e2d99..d82d8d609c2e3f352ef20dc48029787c3ac8cad5 100644 GIT binary patch literal 1866 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 GGywo&aKg+0 From 4182ad35011526870d933da64bfbe729f9e0e940 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 13:04:53 +0200 Subject: [PATCH 393/599] add thread parent indicators --- packages/stream_chat/lib/src/api/channel.dart | 25 ++--- .../lib/src/channel_info.dart | 5 + .../lib/src/channel_preview.dart | 95 ++++++++++--------- .../lib/src/message_input.dart | 5 +- .../lib/src/thread_header.dart | 53 +++++++---- .../lib/src/typing_indicator.dart | 23 ++--- 6 files changed, 118 insertions(+), 88 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index aeda98e7..286a7e07 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'dart:math'; import 'package:collection/collection.dart' - show IterableExtension, ListEquality; + show IterableExtension, ListEquality, MapEquality; import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; @@ -1848,17 +1848,17 @@ class ChannelClientState { } /// Channel related typing users last value - List get typingEvents => _typingEventsController.value; + Map get typingEvents => _typingEventsController.value; /// Channel related typing users stream - Stream> get typingEventsStream => - _typingEventsController.stream.distinct(const ListEquality().equals); + Stream> get typingEventsStream => + _typingEventsController.stream; - final BehaviorSubject> _typingEventsController = - BehaviorSubject.seeded([]); + final BehaviorSubject> _typingEventsController = + BehaviorSubject.seeded({}); final Channel _channel; - final Map _typings = {}; + final Map _typings = {}; void _listenTypingEvents() { if (_channelState.channel?.config.typingEvents == false) { @@ -1872,8 +1872,8 @@ class ChannelClientState { if (event.user != null) { final user = event.user!; if (user.id != _channel.client.state.user?.id) { - _typings[user] = DateTime.now(); - _typingEventsController.add(_typings.keys.toList()); + _typings[user] = event; + _typingEventsController.add(_typings); } } }, @@ -1886,7 +1886,7 @@ class ChannelClientState { final user = event.user!; if (user.id != _channel.client.state.user?.id) { _typings.remove(event.user); - _typingEventsController.add(_typings.keys.toList()); + _typingEventsController.add(_typings); } } }, @@ -1964,13 +1964,14 @@ class ChannelClientState { void _clean() { final now = DateTime.now(); - _typings.forEach((user, lastTypingEvent) { - if (now.difference(lastTypingEvent).inSeconds > 7) { + _typings.forEach((user, event) { + if (now.difference(event.createdAt!).inSeconds > 7) { _channel.client.handleEvent( Event( type: EventType.typingStop, user: user, cid: _channel.cid, + parentId: event.parentId, ), ); } diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index 32dbeb67..a17260ec 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -11,6 +11,7 @@ class ChannelInfo extends StatelessWidget { required this.channel, this.textStyle, this.showTypingIndicator = true, + this.parentId, }) : super(key: key); /// The channel about which the info is to be displayed @@ -22,6 +23,9 @@ class ChannelInfo extends StatelessWidget { /// If true the typing indicator will be rendered if a user is typing final bool showTypingIndicator; + /// Id of the parent message in case of a thread + final String? parentId; + @override Widget build(BuildContext context) { final client = StreamChat.of(context).client; @@ -88,6 +92,7 @@ class ChannelInfo extends StatelessWidget { } return TypingIndicator( + parentId: parentId, alignment: Alignment.center, alternativeWidget: alternativeWidget, style: textStyle, diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index d9170eb8..231b5f94 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -221,59 +221,62 @@ class ChannelPreview extends StatelessWidget { ); } - Widget _buildLastMessage(BuildContext context) => - BetterStreamBuilder?>( - stream: channel.state!.messagesStream, - initialData: channel.state!.messages, - builder: (context, data) { - final lastMessage = - data?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted); - if (lastMessage == null) { - return const SizedBox(); - } + Widget _buildLastMessage(BuildContext context) => Align( + alignment: Alignment.centerLeft, + child: BetterStreamBuilder?>( + stream: channel.state!.messagesStream, + initialData: channel.state!.messages, + builder: (context, data) { + final lastMessage = data + ?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted); + if (lastMessage == null) { + return const SizedBox(); + } - var text = lastMessage.text; - final parts = [ - ...lastMessage.attachments.map((e) { - if (e.type == 'image') { - return '📷'; - } else if (e.type == 'video') { - return 'đŸŽŦ'; - } else if (e.type == 'giphy') { - return '[GIF]'; - } - return e == lastMessage.attachments.last - ? (e.title ?? 'File') - : '${e.title ?? 'File'} , '; - }), - lastMessage.text ?? '', - ]; + var text = lastMessage.text; + final parts = [ + ...lastMessage.attachments.map((e) { + if (e.type == 'image') { + return '📷'; + } else if (e.type == 'video') { + return 'đŸŽŦ'; + } else if (e.type == 'giphy') { + return '[GIF]'; + } + return e == lastMessage.attachments.last + ? (e.title ?? 'File') + : '${e.title ?? 'File'} , '; + }), + lastMessage.text ?? '', + ]; - text = parts.join(' '); + text = parts.join(' '); - final chatThemeData = StreamChatTheme.of(context); - return Text.rich( - _getDisplayText( - text, - lastMessage.mentionedUsers, - lastMessage.attachments, - chatThemeData.channelPreviewTheme.subtitle?.copyWith( + final chatThemeData = StreamChatTheme.of(context); + return Text.rich( + _getDisplayText( + text, + lastMessage.mentionedUsers, + lastMessage.attachments, + chatThemeData.channelPreviewTheme.subtitle?.copyWith( + color: chatThemeData.channelPreviewTheme.subtitle?.color, + fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) + ? FontStyle.italic + : FontStyle.normal), + chatThemeData.channelPreviewTheme.subtitle?.copyWith( color: chatThemeData.channelPreviewTheme.subtitle?.color, fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) ? FontStyle.italic - : FontStyle.normal), - chatThemeData.channelPreviewTheme.subtitle?.copyWith( - color: chatThemeData.channelPreviewTheme.subtitle?.color, - fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) - ? FontStyle.italic - : FontStyle.normal, - fontWeight: FontWeight.bold, + : FontStyle.normal, + fontWeight: FontWeight.bold, + ), ), - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - }, + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.start, + ); + }, + ), ); TextSpan _getDisplayText( diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 7e62f1c3..4f61c9fa 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -700,7 +700,10 @@ class MessageInputState extends State { if (!mounted) { return; } - StreamChannel.of(context).channel.keyStroke().catchError((e) {}); + StreamChannel.of(context) + .channel + .keyStroke(widget.parentMessage?.id) + .catchError((e) {}); setState(() { _messageIsPresent = s.trim().isNotEmpty; diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart index d16aa530..bfde9a4c 100644 --- a/packages/stream_chat_flutter/lib/src/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/thread_header.dart @@ -68,6 +68,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { this.leading, this.actions, this.onTitleTap, + this.showTypingIndicator = true, }) : preferredSize = const Size.fromHeight(kToolbarHeight), super(key: key); @@ -96,9 +97,32 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { /// AppBar actions final List? actions; + /// If true the typing indicator will be rendered + /// if a user is typing in this thread + final bool showTypingIndicator; + @override Widget build(BuildContext context) { final chatThemeData = StreamChatTheme.of(context); + + final defaultSubtitle = subtitle ?? + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'with ', + style: chatThemeData.channelTheme.channelHeaderTheme.subtitle, + ), + Flexible( + child: ChannelName( + textStyle: + chatThemeData.channelTheme.channelHeaderTheme.subtitle, + ), + ), + ], + ); + return AppBar( automaticallyImplyLeading: false, textTheme: Theme.of(context).textTheme, @@ -119,6 +143,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { onTap: onTitleTap, child: SizedBox( height: preferredSize.height, + width: 250, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -128,24 +153,16 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { style: chatThemeData.channelTheme.channelHeaderTheme.title, ), const SizedBox(height: 2), - subtitle ?? - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'with ', - style: chatThemeData - .channelTheme.channelHeaderTheme.subtitle, - ), - Flexible( - child: ChannelName( - textStyle: chatThemeData - .channelTheme.channelHeaderTheme.subtitle, - ), - ), - ], - ), + if (showTypingIndicator) + TypingIndicator( + alignment: Alignment.center, + channel: StreamChannel.of(context).channel, + style: chatThemeData.channelTheme.channelHeaderTheme.subtitle, + parentId: parent.id, + alternativeWidget: defaultSubtitle, + ) + else + defaultSubtitle, ], ), ), diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart index 100cdb53..4c0ad19f 100644 --- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart @@ -12,6 +12,7 @@ class TypingIndicator extends StatelessWidget { this.style, this.alignment = Alignment.centerLeft, this.padding = const EdgeInsets.all(0), + this.parentId, }) : super(key: key); /// Style of the text widget @@ -29,21 +30,21 @@ class TypingIndicator extends StatelessWidget { /// Alignment of the typing indicator final Alignment alignment; + /// Id of the parent message in case of a thread + final String? parentId; + @override Widget build(BuildContext context) { final channelState = channel?.state ?? StreamChannel.of(context).channel.state!; - final altWidget = Align( - key: const Key('alternative'), - alignment: alignment, - child: Container( - child: alternativeWidget ?? const Offstage(), - ), - ); - return BetterStreamBuilder>( - initialData: channelState.typingEvents, - stream: channelState.typingEventsStream, + final altWidget = alternativeWidget ?? const Offstage(); + + return BetterStreamBuilder>( + initialData: channelState.typingEvents.keys, + stream: channelState.typingEventsStream.map((typings) => typings.entries + .where((element) => element.value.parentId == parentId) + .map((e) => e.key)), builder: (context, data) => AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: data.isNotEmpty == true @@ -63,7 +64,7 @@ class TypingIndicator extends StatelessWidget { ), Text( // ignore: lines_longer_than_80_chars - ' ${data[0].name}${data.length == 1 ? '' : ' and ${data.length - 1} more'} ${data.length == 1 ? 'is' : 'are'} typing', + ' ${data.elementAt(0).name}${data.length == 1 ? '' : ' and ${data.length - 1} more'} ${data.length == 1 ? 'is' : 'are'} typing', maxLines: 1, style: style, ), From 5cd9a3be5359b3e65a04c37beac613792ce23d63 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 13:15:01 +0200 Subject: [PATCH 394/599] fix analysis --- packages/stream_chat/lib/src/api/channel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 286a7e07..b2e33084 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'dart:math'; import 'package:collection/collection.dart' - show IterableExtension, ListEquality, MapEquality; + show IterableExtension, ListEquality; import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; From 73d652772518bb089aa8d421843b8f90cb018e11 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Jun 2021 17:09:50 +0530 Subject: [PATCH 395/599] fix analyzer warnings Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/client/client.dart | 2 ++ packages/stream_chat/lib/src/core/models/event.dart | 2 -- packages/stream_chat/test/src/matchers.dart | 1 - packages/stream_chat/test/src/mocks.dart | 3 --- packages/stream_chat_flutter/analysis_options.yaml | 2 +- .../stream_chat_flutter/lib/src/channel_list_view.dart | 4 ++++ .../lib/src/channel_list_core.dart | 10 ++++------ 7 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index ecadfe3b..b9e1c5d7 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1373,6 +1373,7 @@ class ClientState { _userController.add(user); } + /// Update all the [users] with the provided [userList] void updateUsers(List userList) { final newUsers = { ...users, @@ -1382,6 +1383,7 @@ class ClientState { _usersController.add(newUsers); } + /// Update the passed [user] in state void updateUser(User? user) => updateUsers([user]); /// The current user diff --git a/packages/stream_chat/lib/src/core/models/event.dart b/packages/stream_chat/lib/src/core/models/event.dart index 21dd069c..26831555 100644 --- a/packages/stream_chat/lib/src/core/models/event.dart +++ b/packages/stream_chat/lib/src/core/models/event.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:stream_chat/src/core/models/message.dart'; diff --git a/packages/stream_chat/test/src/matchers.dart b/packages/stream_chat/test/src/matchers.dart index c039b416..27cfa43d 100644 --- a/packages/stream_chat/test/src/matchers.dart +++ b/packages/stream_chat/test/src/matchers.dart @@ -1,7 +1,6 @@ import 'package:collection/collection.dart'; import 'package:dio/dio.dart' show MultipartFile; import 'package:stream_chat/src/client/channel.dart'; -import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:stream_chat/src/core/models/channel_state.dart'; import 'package:stream_chat/src/core/models/event.dart'; import 'package:stream_chat/src/core/models/message.dart'; diff --git a/packages/stream_chat/test/src/mocks.dart b/packages/stream_chat/test/src/mocks.dart index 528c8a91..c4a5d64f 100644 --- a/packages/stream_chat/test/src/mocks.dart +++ b/packages/stream_chat/test/src/mocks.dart @@ -3,7 +3,6 @@ import 'package:logging/logging.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/src/client/channel.dart'; import 'package:stream_chat/src/client/client.dart'; -import 'package:stream_chat/src/client/retry_policy.dart'; import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; import 'package:stream_chat/src/core/api/channel_api.dart'; import 'package:stream_chat/src/core/api/device_api.dart'; @@ -16,12 +15,10 @@ import 'package:stream_chat/src/core/http/connection_id_manager.dart'; import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; import 'package:stream_chat/src/core/models/channel_config.dart'; -import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:stream_chat/src/db/chat_persistence_client.dart'; import 'package:stream_chat/src/ws/websocket.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; -import 'db/chat_persistence_client_test.dart'; class MockWebSocketChannel extends Mock implements WebSocketChannel {} diff --git a/packages/stream_chat_flutter/analysis_options.yaml b/packages/stream_chat_flutter/analysis_options.yaml index f0a87ea5..ed0ce983 100644 --- a/packages/stream_chat_flutter/analysis_options.yaml +++ b/packages/stream_chat_flutter/analysis_options.yaml @@ -6,7 +6,7 @@ analyzer: - example/** - lib/src/emoji - lib/**/*.freezed.dart - - test/** +# - test/** linter: rules: 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 f6acf35e..d131c33b 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -113,9 +113,13 @@ class ChannelListView extends StatefulWidget { /// If true listen to changes to this Channel in real time. final bool watch; + /// If true you’ll receive user presence updates via the websocket events final bool presence; + /// Number of members to fetch in each channel final int? memberLimit; + + /// Number of messages to fetch in each channel final int? messageLimit; /// Pagination parameters diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 684fd630..6dc52647 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -98,12 +98,6 @@ class ChannelListCore extends StatefulWidget { /// You can also filter other built-in channel fields. final Filter? filter; - // bool state = true, - // bool watch = true, - // bool presence = false, - // int? memberLimit, - // int? messageLimit, - /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be /// provided. @@ -117,9 +111,13 @@ class ChannelListCore extends StatefulWidget { /// If true listen to changes to this Channel in real time. final bool watch; + /// If true you’ll receive user presence updates via the websocket events final bool presence; + /// Number of members to fetch in each channel final int? memberLimit; + + /// Number of messages to fetch in each channel final int? messageLimit; /// Pagination parameters From a5bc00fe5f61b465a6c59b8f53c3f0b8cd2a4f6e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Jun 2021 17:18:47 +0530 Subject: [PATCH 396/599] add channel.setExtraData tests Signed-off-by: Sahil Kumar --- .../test/src/api/channel_test.dart | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 22d04a43..6883d3b1 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -35,6 +35,45 @@ void main() { return logger; } + group('Non-Initialized Channel', () { + late final client = MockStreamChatClient(); + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + late Channel channel; + + setUpAll(() { + // detached loggers + when(() => client.detachedLogger(any())).thenAnswer((invocation) { + final name = invocation.positionalArguments.first; + return _createLogger(name); + }); + + // client logger + when(() => client.logger).thenReturn(_createLogger('mock-client-logger')); + }); + + setUp(() { + channel = Channel(client, channelType, channelId); + }); + + tearDown(() { + channel.dispose(); + }); + + test('should be able to set `extraData`', () { + expect(channel.extraData.isEmpty, isTrue); + + expect( + () => channel.extraData = {'name': 'test-channel-name'}, + returnsNormally, + ); + + expect(channel.extraData.isEmpty, isFalse); + expect(channel.extraData.containsKey('name'), isTrue); + expect(channel.extraData['name'], 'test-channel-name'); + }); + }); + // TODO : test all persistence related logic in this group group('Initialized Channel with Persistence', () { late final client = MockStreamChatClientWithPersistence(); @@ -145,6 +184,14 @@ void main() { channel.dispose(); }); + test('should throw if trying to set `extraData`', () { + try { + channel.extraData = {'name': 'test-channel-name'}; + } catch (e) { + expect(e, isA()); + } + }); + group('`.sendMessage`', () { test('should work fine', () async { final message = Message(id: 'test-message-id'); From dc4b3f11a4f58785b29dfd4152cb8db30e52e75a Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Jun 2021 17:23:53 +0530 Subject: [PATCH 397/599] flutter format . Signed-off-by: Sahil Kumar --- packages/stream_chat/test/src/mocks.dart | 1 - packages/stream_chat_flutter_core/analysis_options.yaml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/stream_chat/test/src/mocks.dart b/packages/stream_chat/test/src/mocks.dart index c4a5d64f..44078519 100644 --- a/packages/stream_chat/test/src/mocks.dart +++ b/packages/stream_chat/test/src/mocks.dart @@ -19,7 +19,6 @@ import 'package:stream_chat/src/db/chat_persistence_client.dart'; import 'package:stream_chat/src/ws/websocket.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; - class MockWebSocketChannel extends Mock implements WebSocketChannel {} class MockWebSocketSink extends Mock implements WebSocketSink {} diff --git a/packages/stream_chat_flutter_core/analysis_options.yaml b/packages/stream_chat_flutter_core/analysis_options.yaml index f0a87ea5..ed0ce983 100644 --- a/packages/stream_chat_flutter_core/analysis_options.yaml +++ b/packages/stream_chat_flutter_core/analysis_options.yaml @@ -6,7 +6,7 @@ analyzer: - example/** - lib/src/emoji - lib/**/*.freezed.dart - - test/** +# - test/** linter: rules: From c37faddd2ee28db16f3195565b955258be226cf7 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 14:03:35 +0200 Subject: [PATCH 398/599] fix tests --- .../test/src/channel_preview_test.dart | 4 ---- .../test/src/full_screen_media_test.dart | 16 ++++++++-------- packages/stream_chat_flutter/test/src/mocks.dart | 4 ++-- .../test/src/typing_indicator_test.dart | 15 ++++++++------- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/channel_preview_test.dart b/packages/stream_chat_flutter/test/src/channel_preview_test.dart index b913489e..baa82614 100644 --- a/packages/stream_chat_flutter/test/src/channel_preview_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_preview_test.dart @@ -60,10 +60,6 @@ void main() { ) ])); - when(() => channelState.typingEvents).thenReturn([]); - when(() => channelState.typingEventsStream) - .thenAnswer((_) => Stream.value([])); - await tester.pumpWidget(MaterialApp( home: StreamChat( client: client, diff --git a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart index 81c342eb..d418c303 100644 --- a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart +++ b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart @@ -53,15 +53,15 @@ void main() { user: User(id: 'other-user'), ) ])); - - when(() => channelState.typingEvents).thenAnswer((i) => [ - User(id: 'other-user', extraData: {'name': 'demo'}) - ]); + when(() => channelState.typingEvents).thenAnswer((i) => { + User(id: 'other-user', extraData: {'name': 'demo'}): + const Event(type: EventType.typingStart), + }); when(() => channelState.typingEventsStream) - .thenAnswer((i) => Stream.value([ - User(id: 'other-user', extraData: {'name': 'demo'}), - User(id: 'other-user', extraData: {'name': 'demo'}), - ])); + .thenAnswer((i) => Stream.value({ + User(id: 'other-user', extraData: {'name': 'demo'}): + const Event(type: EventType.typingStart), + })); await tester.pumpWidget(MaterialApp( home: StreamChat( diff --git a/packages/stream_chat_flutter/test/src/mocks.dart b/packages/stream_chat_flutter/test/src/mocks.dart index d2b04e11..28e00e4b 100644 --- a/packages/stream_chat_flutter/test/src/mocks.dart +++ b/packages/stream_chat_flutter/test/src/mocks.dart @@ -23,8 +23,8 @@ class MockChannel extends Mock implements Channel { class MockChannelState extends Mock implements ChannelClientState { MockChannelState() { - when(() => typingEvents).thenReturn([]); - when(() => typingEventsStream).thenAnswer((_) => Stream.value([])); + when(() => typingEvents).thenReturn({}); + when(() => typingEventsStream).thenAnswer((_) => Stream.value({})); } } diff --git a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart index cc9af663..75431f44 100644 --- a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart @@ -53,14 +53,15 @@ void main() { ) ])); - when(() => channelState.typingEvents).thenAnswer((i) => [ - User(id: 'other-user', extraData: {'name': 'demo'}) - ]); + when(() => channelState.typingEvents).thenAnswer((i) => { + User(id: 'other-user', extraData: {'name': 'demo'}): + const Event(type: EventType.typingStart), + }); when(() => channelState.typingEventsStream) - .thenAnswer((i) => Stream.value([ - User(id: 'other-user', extraData: {'name': 'demo'}), - User(id: 'other-user', extraData: {'name': 'demo'}), - ])); + .thenAnswer((i) => Stream.value({ + User(id: 'other-user', extraData: {'name': 'demo'}): + const Event(type: EventType.typingStart), + })); await tester.pumpWidget(MaterialApp( home: StreamChat( From 665ac322f30a0f6a4e6473478021f1b7202208d8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 15:04:03 +0200 Subject: [PATCH 399/599] update changelogs and pubspecs --- packages/stream_chat_flutter/CHANGELOG.md | 6 ++++++ packages/stream_chat_flutter/pubspec.yaml | 4 ++-- packages/stream_chat_flutter_core/CHANGELOG.md | 4 ++++ packages/stream_chat_flutter_core/pubspec.yaml | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 170bcf6b..ac8ae565 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.0-nullsafety.7 + +- Minor fixes and improvements +- Updated `stream_chat_core` dependency +- Fixed a bug with connectivity implementation + ## 2.0.0-nullsafety.6 - Minor fixes and improvements diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index e165b136..bcebde99 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.6 +version: 2.0.0-nullsafety.7 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -36,7 +36,7 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 shimmer: ^2.0.0 - stream_chat_flutter_core: ^2.0.0-nullsafety.6 + stream_chat_flutter_core: ^2.0.0-nullsafety.7 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 71ff8f51..bd176c12 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.0-nullsafety.7 + +* Fixed a bug with connectivity implementation + ## 2.0.0-nullsafety.6 * Update llc dependency diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 40a40c39..14fa0f80 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.6 +version: 2.0.0-nullsafety.7 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues From 678945f7ef1ffd706f11d78bdcf9d97726dc098f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Jun 2021 21:31:06 +0530 Subject: [PATCH 400/599] refactor stream_flutter_workflow.yml Signed-off-by: Sahil Kumar --- .github/workflows/scripts/install-flutter.sh | 13 -- .github/workflows/scripts/install-tools.sh | 6 - .../{coverage.sh => remove-from_coverage.sh} | 1 + .github/workflows/stream_flutter_workflow.yml | 137 ++++++++++------ ...ysis_options.yaml => analysis_options.yaml | 0 melos.yaml | 96 ++++++----- packages/stream_chat/peanut.yaml | 3 - .../stream_chat_flutter/analysis_options.yaml | 150 ------------------ .../analysis_options.yaml | 150 ------------------ .../analysis_options.yaml | 150 ------------------ 10 files changed, 141 insertions(+), 565 deletions(-) delete mode 100755 .github/workflows/scripts/install-flutter.sh delete mode 100755 .github/workflows/scripts/install-tools.sh rename .github/workflows/scripts/{coverage.sh => remove-from_coverage.sh} (80%) mode change 100755 => 100644 rename packages/stream_chat/analysis_options.yaml => analysis_options.yaml (100%) delete mode 100644 packages/stream_chat/peanut.yaml delete mode 100644 packages/stream_chat_flutter/analysis_options.yaml delete mode 100644 packages/stream_chat_flutter_core/analysis_options.yaml delete mode 100644 packages/stream_chat_persistence/analysis_options.yaml diff --git a/.github/workflows/scripts/install-flutter.sh b/.github/workflows/scripts/install-flutter.sh deleted file mode 100755 index 247d2797..00000000 --- a/.github/workflows/scripts/install-flutter.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -BRANCH=$1 - -if [ "$BRANCH" == "dev" ] -then - # TODO Flutter dev branch is currently broken so we're unable to test MacOS. - echo "TODO: Skipping macOS testing due to Flutter dev branch issue. Switching branch to stable." - BRANCH=stable -fi - -git clone https://github.com/flutter/flutter.git --depth 1 -b $BRANCH _flutter -echo "::add-path::$GITHUB_WORKSPACE/_flutter/bin" \ No newline at end of file diff --git a/.github/workflows/scripts/install-tools.sh b/.github/workflows/scripts/install-tools.sh deleted file mode 100755 index 087cfecb..00000000 --- a/.github/workflows/scripts/install-tools.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -flutter pub global activate melos -echo "::add-path::$HOME/.pub-cache/bin" -echo "::add-path::$GITHUB_WORKSPACE/_flutter/.pub-cache/bin" -echo "::add-path::$GITHUB_WORKSPACE/_flutter/bin/cache/dart-sdk/bin" \ No newline at end of file diff --git a/.github/workflows/scripts/coverage.sh b/.github/workflows/scripts/remove-from_coverage.sh old mode 100755 new mode 100644 similarity index 80% rename from .github/workflows/scripts/coverage.sh rename to .github/workflows/scripts/remove-from_coverage.sh index edff4ae4..c1190097 --- a/.github/workflows/scripts/coverage.sh +++ b/.github/workflows/scripts/remove-from_coverage.sh @@ -3,4 +3,5 @@ # Fast fail the script on failures. set -e +pub global activate remove_from_coverage pub global run remove_from_coverage:remove_from_coverage -f coverage/lcov.info -r '\.g\.dart$' -r '\.freezed\.dart$' \ No newline at end of file diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 95f48ee9..a8650e65 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -17,89 +17,124 @@ jobs: timeout-minutes: 15 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: "Git Checkout" + uses: actions/checkout@v2 with: fetch-depth: 0 - - name: 'Install Flutter' - run: ./.github/workflows/scripts/install-flutter.sh stable - - name: 'Install Tools' + - name: "Install Flutter" + uses: subosito/flutter-action@v1 + with: + channel: 'stable' + - name: "Install Tools" run: | - ./.github/workflows/scripts/install-tools.sh + flutter pub global activate melos flutter pub global activate tuneup - - name: 'Bootstrap Workspace' - run: melos bootstrap --verbose - - name: 'Dart Analyze' + - name: "Bootstrap Workspace" + run: melos bootstrap + - name: "Dart Analyze" run: | - melos exec -c 3 --ignore="*example*" -- \ - tuneup check - - name: 'Pub Check' + melos run analyze + - name: "Pub Check" if: github.ref == 'refs/heads/master' run: | - melos exec -c 1 --no-private --ignore="*example*" -- \ - pub publish --dry-run + melos run lint:pub + format: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v2 + - name: "Git Checkout" + uses: actions/checkout@v2 with: fetch-depth: 0 - - name: 'Install Flutter' - run: ./.github/workflows/scripts/install-flutter.sh stable - - name: 'Install Tools' + - name: "Install Flutter" + uses: subosito/flutter-action@v1 + with: + channel: 'stable' + - name: "Install Tools" + run: flutter pub global activate melos + - name: "Bootstrap Workspace" + run: melos bootstrap + - name: "Melos Format" + run: melos run format + - name: "Validate Formatting" run: | - ./.github/workflows/scripts/install-tools.sh - - name: 'Bootstrap Workspace' - run: melos bootstrap --verbose - - name: 'Dart' - run: | - melos exec -c 1 -- \ - flutter format . ./.github/workflows/scripts/validate-formatting.sh test: runs-on: macos-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v2 + - name: "Git Checkout" + uses: actions/checkout@v2 with: fetch-depth: 0 - - name: 'Install Flutter' - run: ./.github/workflows/scripts/install-flutter.sh stable - - name: 'Install Tools' - run: | - ./.github/workflows/scripts/install-tools.sh - flutter pub global activate coverage - flutter pub global activate remove_from_coverage - - name: 'Bootstrap Workspace' - run: melos bootstrap --verbose - - name: 'Dart Test' - run: | - cd packages/stream_chat - flutter pub run test --coverage coverage/ - format_coverage --lcov --in=coverage/ --out=coverage/lcov.info --packages=.packages --report-on=lib - - name: 'Flutter Test' - run: | - melos exec -c 3 --flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ - flutter test --coverage - - name: CodeCov - run: | - melos exec -c 3 --fail-fast --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ - "\$MELOS_ROOT_PATH/.github/workflows/scripts/coverage.sh" - bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }} - - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 + - name: "Install Flutter" + uses: subosito/flutter-action@v1 + with: + channel: 'stable' + - name: "Install Tools" + run: flutter pub global activate melos + - name: "Bootstrap Workspace" + run: melos bootstrap + - name: "Flutter Test" + run: melos run test:all + - name: "Collect Coverage" + run: melos run coverage:ignore-file + - name: "Upload Coverage" + uses: codecov/codecov-action@v1 + with: + token: ${{secrets.CODECOV_TOKEN}} + files: packages/*/coverage/lcov.info + - name: "Stream Chat Coverage Check" + uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat/coverage/lcov.info min_coverage: 40 + - name: "Stream Chat Persistence Coverage Check" - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_persistence/coverage/lcov.info min_coverage: 95 - - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 + - name: "Stream Chat Flutter Core Coverage Check" + uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_flutter_core/coverage/lcov.info min_coverage: 90 - - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 + - name: "Stream Chat Flutter Coverage Check" + uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_flutter/coverage/lcov.info min_coverage: 35 + +# test: +# runs-on: macos-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@v2 +# with: +# fetch-depth: 0 +# - name: 'Install Flutter' +# run: ./.github/workflows/scripts/install-flutter.sh stable +# - name: 'Install Tools' +# run: | +# ./.github/workflows/scripts/install-tools.sh +# flutter pub global activate coverage +# flutter pub global activate remove_from_coverage +# - name: 'Bootstrap Workspace' +# run: melos bootstrap --verbose +# - name: 'Dart Test' +# run: | +# cd packages/stream_chat +# flutter pub run test --coverage coverage/ +# format_coverage --lcov --in=coverage/ --out=coverage/lcov.info --packages=.packages --report-on=lib +# - name: 'Flutter Test' +# run: | +# melos exec -c 3 --flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ +# flutter test --coverage +# - name: CodeCov +# run: | +# melos exec -c 3 --fail-fast --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ +# "\$MELOS_ROOT_PATH/.github/workflows/scripts/coverage.sh" +# bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }} + diff --git a/packages/stream_chat/analysis_options.yaml b/analysis_options.yaml similarity index 100% rename from packages/stream_chat/analysis_options.yaml rename to analysis_options.yaml diff --git a/melos.yaml b/melos.yaml index 3fcad03f..17a896c0 100644 --- a/melos.yaml +++ b/melos.yaml @@ -1,4 +1,4 @@ -name: stream_chat_dart +name: stream_chat_flutter versioning: mode: independent @@ -7,56 +7,68 @@ packages: - packages/** scripts: + lint:all: + run: melos run analyze && melos run format + description: Run all static analysis checks - # - Requires `pub global activate tuneup`. - analyze: > - melos exec -c 1 --fail-fast -- \ - pub global run tuneup check + analyze: + run: | + melos exec -c 4 --ignore="*example*" -- \ + dart analyze --fatal-infos . + description: | + Run `dart analyze` in all packages. + - Note: you can also rely on your IDEs Dart Analysis / Issues window. - format: pub global run flutter_plugin_tools format + format: + run: flutter format --set-exit-if-changed . + description: | + Run `flutter format --set-exit-if-changed .` in all packages. + lint:pub: + run: | + melos exec -c 4 --no-private --ignore="*example*" -- \ + pub publish --dry-run + description: | + Run `pub publish --dry-run` in all packages. + - Note: you can also rely on your IDEs Dart Analysis / Issues window. - build:examples:ios: > - melos exec -c 1 --scope="*example*" --fail-fast -- \ - flutter build ios --no-codesign + generate:all: + run: melos run generate:dart && melos run generate:flutter + description: Build all generated files for Dart & Flutter packages in this project. + generate:dart: + run: melos exec -c 1 --depends-on="build_runner" --no-flutter -- "dart run build_runner build --delete-conflicting-outputs" + description: Build all generated files for Dart packages in this project. - build:examples:android: > - melos exec -c 1 --scope="*example*" --fail-fast -- \ - flutter build apk + generate:flutter: + run: melos exec -c 1 --depends-on="build_runner" --flutter -- "flutter pub run build_runner build --delete-conflicting-outputs" + description: Build all generated files for Flutter packages in this project. - # Build any plugin example apps that have MacOS support. - # - Requires `flutter config --enable-macos-desktop` enabled. - # - Requires `flutter channel master && flutter upgrade`. - build:examples:macos: > - melos exec -c 1 --scope="*example*" --dir-exists=macos --fail-fast -- \ - flutter build macos + test:all: + run: melos run test:dart --no-select && melos run test:flutter --no-select + description: Run all Dart & Flutter tests in this project. + test:dart: + run: melos exec -c 1 --fail-fast -- "flutter test --coverage" + description: Run Dart tests for a specific package in this project. + select-package: + flutter: false + dir-exists: test - test:dart: > - melos exec -c 1 --fail-fast --no-flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ - flutter pub run test + test:flutter: + run: melos exec -c 1 --fail-fast -- "flutter test --coverage" + description: Run Flutter tests for a specific package in this project. + select-package: + flutter: true + dir-exists: test - test:flutter: > - melos exec -c 1 --fail-fast --flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ - flutter test - - test:web: > - melos exec -c 1 --fail-fast --dir-exists=test --scope="*web*" -- \ - flutter test --platform=chrome - - - lint:pub: > - melos exec -c 5 --fail-fast --no-private --ignore="*example*" -- \ - pub publish --dry-run - - - postclean: > - melos exec -- \ - rm -rf ./build ./android/.gradle ./ios/.symlinks ./ios/Pods ./android/.idea ./.idea ./.dart-tool/build - -dev_dependencies: - pedantic: 1.9.2 + coverage:ignore-file: + run: | + melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from_coverage.sh" + description: Removes all the ignored files from the coverage report. + select-package: + dir-exists: coverage environment: - sdk: ">=2.12.0 <3.0.0" \ No newline at end of file + sdk: '>=2.12.0 <3.0.0' + flutter: '>=1.22.4 <2.0.0' \ No newline at end of file diff --git a/packages/stream_chat/peanut.yaml b/packages/stream_chat/peanut.yaml deleted file mode 100644 index 97d20f52..00000000 --- a/packages/stream_chat/peanut.yaml +++ /dev/null @@ -1,3 +0,0 @@ -# Configuration for https://pub.dev/packages/peanut -directories: - - example/web diff --git a/packages/stream_chat_flutter/analysis_options.yaml b/packages/stream_chat_flutter/analysis_options.yaml deleted file mode 100644 index f0a87ea5..00000000 --- a/packages/stream_chat_flutter/analysis_options.yaml +++ /dev/null @@ -1,150 +0,0 @@ -analyzer: - enable-experiment: - - extension-methods - exclude: - - lib/**/*.g.dart - - example/** - - lib/src/emoji - - lib/**/*.freezed.dart - - test/** - -linter: - rules: - # these rules are documented on and in the same order as - # the Dart Lint rules page to make maintenance easier - # https://github.com/dart-lang/linter/blob/master/example/all.yaml - - always_use_package_imports - - avoid_empty_else - - avoid_relative_lib_imports - - avoid_slow_async_io - - avoid_types_as_parameter_names - - cancel_subscriptions - - close_sinks - - control_flow_in_finally - - empty_statements - - hash_and_equals - - invariant_booleans - - iterable_contains_unrelated_type - - list_remove_unrelated_type - - literal_only_boolean_expressions - - no_adjacent_strings_in_list - - no_duplicate_case_values - - no_logic_in_create_state - - prefer_void_to_null - - test_types_in_equals - - throw_in_finally - - unnecessary_statements - - unrelated_type_equality_checks - - omit_local_variable_types - - use_key_in_widget_constructors - - valid_regexps - - always_declare_return_types - - always_require_non_null_named_parameters - - annotate_overrides - - avoid_bool_literals_in_conditional_expressions - - avoid_catching_errors - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - - avoid_positional_boolean_parameters - - avoid_private_typedef_functions - - avoid_redundant_argument_values - - avoid_return_types_on_setters - - avoid_returning_null_for_void - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_unnecessary_containers - - avoid_unused_constructor_parameters - - await_only_futures - - camel_case_extensions - - camel_case_types - - cascade_invocations - - - constant_identifier_names - - curly_braces_in_flow_control_structures - - directives_ordering - - empty_catches - - empty_constructor_bodies - - exhaustive_cases - - file_names - - implementation_imports - - join_return_with_assignment - - leading_newlines_in_multiline_strings - - library_names - - library_prefixes - - lines_longer_than_80_chars - - missing_whitespace_between_adjacent_strings - - non_constant_identifier_names - - null_closures - - one_member_abstracts - - only_throw_errors - - package_api_docs - - package_prefixed_library_names - - parameter_assignments - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists - - prefer_asserts_with_message - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - - prefer_const_constructors_in_immutables - - prefer_const_declarations - - prefer_const_literals_to_create_immutables - - prefer_constructors_over_static_methods - - prefer_contains - - prefer_equal_for_default_values - - prefer_expression_function_bodies - - prefer_final_fields - - prefer_final_in_for_each - - prefer_final_locals - - prefer_function_declarations_over_variables - - prefer_generic_function_type_aliases - - prefer_if_elements_to_conditional_expressions - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds - - prefer_int_literals - - prefer_interpolation_to_compose_strings - - prefer_is_empty - - prefer_is_not_empty - - prefer_is_not_operator - - prefer_null_aware_operators - - prefer_single_quotes - - prefer_spread_collections - - prefer_typing_uninitialized_variables - - provide_deprecation_message - - public_member_api_docs - - recursive_getters - - sized_box_for_whitespace - - slash_for_doc_comments - - sort_child_properties_last - - sort_constructors_first - - sort_unnamed_constructors_first - - - type_annotate_public_apis - - type_init_formals - - unnecessary_await_in_return - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - - unnecessary_lambdas - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - - unnecessary_nullable_for_final_variable_declarations - - unnecessary_parenthesis - - unnecessary_raw_strings - - unnecessary_string_escapes - - unnecessary_string_interpolations - - unnecessary_this - - use_is_even_rather_than_modulo - - use_late_for_private_fields_and_variables - - use_rethrow_when_possible - - use_setters_to_change_properties - - use_to_and_as_if_applicable - - package_names - - sort_pub_dependencies - - - cast_nullable_to_non_nullable - - unnecessary_null_checks - - tighten_type_of_initializing_formals - - null_check_on_nullable_type_parameter diff --git a/packages/stream_chat_flutter_core/analysis_options.yaml b/packages/stream_chat_flutter_core/analysis_options.yaml deleted file mode 100644 index f0a87ea5..00000000 --- a/packages/stream_chat_flutter_core/analysis_options.yaml +++ /dev/null @@ -1,150 +0,0 @@ -analyzer: - enable-experiment: - - extension-methods - exclude: - - lib/**/*.g.dart - - example/** - - lib/src/emoji - - lib/**/*.freezed.dart - - test/** - -linter: - rules: - # these rules are documented on and in the same order as - # the Dart Lint rules page to make maintenance easier - # https://github.com/dart-lang/linter/blob/master/example/all.yaml - - always_use_package_imports - - avoid_empty_else - - avoid_relative_lib_imports - - avoid_slow_async_io - - avoid_types_as_parameter_names - - cancel_subscriptions - - close_sinks - - control_flow_in_finally - - empty_statements - - hash_and_equals - - invariant_booleans - - iterable_contains_unrelated_type - - list_remove_unrelated_type - - literal_only_boolean_expressions - - no_adjacent_strings_in_list - - no_duplicate_case_values - - no_logic_in_create_state - - prefer_void_to_null - - test_types_in_equals - - throw_in_finally - - unnecessary_statements - - unrelated_type_equality_checks - - omit_local_variable_types - - use_key_in_widget_constructors - - valid_regexps - - always_declare_return_types - - always_require_non_null_named_parameters - - annotate_overrides - - avoid_bool_literals_in_conditional_expressions - - avoid_catching_errors - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - - avoid_positional_boolean_parameters - - avoid_private_typedef_functions - - avoid_redundant_argument_values - - avoid_return_types_on_setters - - avoid_returning_null_for_void - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_unnecessary_containers - - avoid_unused_constructor_parameters - - await_only_futures - - camel_case_extensions - - camel_case_types - - cascade_invocations - - - constant_identifier_names - - curly_braces_in_flow_control_structures - - directives_ordering - - empty_catches - - empty_constructor_bodies - - exhaustive_cases - - file_names - - implementation_imports - - join_return_with_assignment - - leading_newlines_in_multiline_strings - - library_names - - library_prefixes - - lines_longer_than_80_chars - - missing_whitespace_between_adjacent_strings - - non_constant_identifier_names - - null_closures - - one_member_abstracts - - only_throw_errors - - package_api_docs - - package_prefixed_library_names - - parameter_assignments - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists - - prefer_asserts_with_message - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - - prefer_const_constructors_in_immutables - - prefer_const_declarations - - prefer_const_literals_to_create_immutables - - prefer_constructors_over_static_methods - - prefer_contains - - prefer_equal_for_default_values - - prefer_expression_function_bodies - - prefer_final_fields - - prefer_final_in_for_each - - prefer_final_locals - - prefer_function_declarations_over_variables - - prefer_generic_function_type_aliases - - prefer_if_elements_to_conditional_expressions - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds - - prefer_int_literals - - prefer_interpolation_to_compose_strings - - prefer_is_empty - - prefer_is_not_empty - - prefer_is_not_operator - - prefer_null_aware_operators - - prefer_single_quotes - - prefer_spread_collections - - prefer_typing_uninitialized_variables - - provide_deprecation_message - - public_member_api_docs - - recursive_getters - - sized_box_for_whitespace - - slash_for_doc_comments - - sort_child_properties_last - - sort_constructors_first - - sort_unnamed_constructors_first - - - type_annotate_public_apis - - type_init_formals - - unnecessary_await_in_return - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - - unnecessary_lambdas - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - - unnecessary_nullable_for_final_variable_declarations - - unnecessary_parenthesis - - unnecessary_raw_strings - - unnecessary_string_escapes - - unnecessary_string_interpolations - - unnecessary_this - - use_is_even_rather_than_modulo - - use_late_for_private_fields_and_variables - - use_rethrow_when_possible - - use_setters_to_change_properties - - use_to_and_as_if_applicable - - package_names - - sort_pub_dependencies - - - cast_nullable_to_non_nullable - - unnecessary_null_checks - - tighten_type_of_initializing_formals - - null_check_on_nullable_type_parameter diff --git a/packages/stream_chat_persistence/analysis_options.yaml b/packages/stream_chat_persistence/analysis_options.yaml deleted file mode 100644 index f0a87ea5..00000000 --- a/packages/stream_chat_persistence/analysis_options.yaml +++ /dev/null @@ -1,150 +0,0 @@ -analyzer: - enable-experiment: - - extension-methods - exclude: - - lib/**/*.g.dart - - example/** - - lib/src/emoji - - lib/**/*.freezed.dart - - test/** - -linter: - rules: - # these rules are documented on and in the same order as - # the Dart Lint rules page to make maintenance easier - # https://github.com/dart-lang/linter/blob/master/example/all.yaml - - always_use_package_imports - - avoid_empty_else - - avoid_relative_lib_imports - - avoid_slow_async_io - - avoid_types_as_parameter_names - - cancel_subscriptions - - close_sinks - - control_flow_in_finally - - empty_statements - - hash_and_equals - - invariant_booleans - - iterable_contains_unrelated_type - - list_remove_unrelated_type - - literal_only_boolean_expressions - - no_adjacent_strings_in_list - - no_duplicate_case_values - - no_logic_in_create_state - - prefer_void_to_null - - test_types_in_equals - - throw_in_finally - - unnecessary_statements - - unrelated_type_equality_checks - - omit_local_variable_types - - use_key_in_widget_constructors - - valid_regexps - - always_declare_return_types - - always_require_non_null_named_parameters - - annotate_overrides - - avoid_bool_literals_in_conditional_expressions - - avoid_catching_errors - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - - avoid_positional_boolean_parameters - - avoid_private_typedef_functions - - avoid_redundant_argument_values - - avoid_return_types_on_setters - - avoid_returning_null_for_void - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_unnecessary_containers - - avoid_unused_constructor_parameters - - await_only_futures - - camel_case_extensions - - camel_case_types - - cascade_invocations - - - constant_identifier_names - - curly_braces_in_flow_control_structures - - directives_ordering - - empty_catches - - empty_constructor_bodies - - exhaustive_cases - - file_names - - implementation_imports - - join_return_with_assignment - - leading_newlines_in_multiline_strings - - library_names - - library_prefixes - - lines_longer_than_80_chars - - missing_whitespace_between_adjacent_strings - - non_constant_identifier_names - - null_closures - - one_member_abstracts - - only_throw_errors - - package_api_docs - - package_prefixed_library_names - - parameter_assignments - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists - - prefer_asserts_with_message - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - - prefer_const_constructors_in_immutables - - prefer_const_declarations - - prefer_const_literals_to_create_immutables - - prefer_constructors_over_static_methods - - prefer_contains - - prefer_equal_for_default_values - - prefer_expression_function_bodies - - prefer_final_fields - - prefer_final_in_for_each - - prefer_final_locals - - prefer_function_declarations_over_variables - - prefer_generic_function_type_aliases - - prefer_if_elements_to_conditional_expressions - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds - - prefer_int_literals - - prefer_interpolation_to_compose_strings - - prefer_is_empty - - prefer_is_not_empty - - prefer_is_not_operator - - prefer_null_aware_operators - - prefer_single_quotes - - prefer_spread_collections - - prefer_typing_uninitialized_variables - - provide_deprecation_message - - public_member_api_docs - - recursive_getters - - sized_box_for_whitespace - - slash_for_doc_comments - - sort_child_properties_last - - sort_constructors_first - - sort_unnamed_constructors_first - - - type_annotate_public_apis - - type_init_formals - - unnecessary_await_in_return - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - - unnecessary_lambdas - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - - unnecessary_nullable_for_final_variable_declarations - - unnecessary_parenthesis - - unnecessary_raw_strings - - unnecessary_string_escapes - - unnecessary_string_interpolations - - unnecessary_this - - use_is_even_rather_than_modulo - - use_late_for_private_fields_and_variables - - use_rethrow_when_possible - - use_setters_to_change_properties - - use_to_and_as_if_applicable - - package_names - - sort_pub_dependencies - - - cast_nullable_to_non_nullable - - unnecessary_null_checks - - tighten_type_of_initializing_formals - - null_check_on_nullable_type_parameter From ff197e4200b354e63b7470128f9d0c14f0b10cb9 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Jun 2021 21:36:20 +0530 Subject: [PATCH 401/599] remove commented commands Signed-off-by: Sahil Kumar --- .github/workflows/stream_flutter_workflow.yml | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index a8650e65..1de175d8 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -9,8 +9,6 @@ on: branches: - master - develop - paths-ignore: - - 'docs/**' jobs: analyze: @@ -105,36 +103,4 @@ jobs: uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_flutter/coverage/lcov.info - min_coverage: 35 - -# test: -# runs-on: macos-latest -# timeout-minutes: 15 -# steps: -# - uses: actions/checkout@v2 -# with: -# fetch-depth: 0 -# - name: 'Install Flutter' -# run: ./.github/workflows/scripts/install-flutter.sh stable -# - name: 'Install Tools' -# run: | -# ./.github/workflows/scripts/install-tools.sh -# flutter pub global activate coverage -# flutter pub global activate remove_from_coverage -# - name: 'Bootstrap Workspace' -# run: melos bootstrap --verbose -# - name: 'Dart Test' -# run: | -# cd packages/stream_chat -# flutter pub run test --coverage coverage/ -# format_coverage --lcov --in=coverage/ --out=coverage/lcov.info --packages=.packages --report-on=lib -# - name: 'Flutter Test' -# run: | -# melos exec -c 3 --flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ -# flutter test --coverage -# - name: CodeCov -# run: | -# melos exec -c 3 --fail-fast --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ -# "\$MELOS_ROOT_PATH/.github/workflows/scripts/coverage.sh" -# bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }} - + min_coverage: 35 \ No newline at end of file From 6b302dd494321786337bd77f9afe2da641ff8114 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 18:16:59 +0200 Subject: [PATCH 402/599] Update stream_flutter_workflow.yml --- .github/workflows/stream_flutter_workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 1de175d8..daa29977 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -90,7 +90,7 @@ jobs: path: packages/stream_chat/coverage/lcov.info min_coverage: 40 - name: "Stream Chat Persistence Coverage Check" - - uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 + uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_persistence/coverage/lcov.info min_coverage: 95 @@ -103,4 +103,4 @@ jobs: uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_flutter/coverage/lcov.info - min_coverage: 35 \ No newline at end of file + min_coverage: 35 From 0b1ef44c0e5f1d5d20a2369c00597c930c90ec51 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 18:35:35 +0200 Subject: [PATCH 403/599] Update melos.yaml --- melos.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/melos.yaml b/melos.yaml index 17a896c0..f50c136a 100644 --- a/melos.yaml +++ b/melos.yaml @@ -66,9 +66,7 @@ scripts: run: | melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from_coverage.sh" description: Removes all the ignored files from the coverage report. - select-package: - dir-exists: coverage environment: sdk: '>=2.12.0 <3.0.0' - flutter: '>=1.22.4 <2.0.0' \ No newline at end of file + flutter: '>=1.22.4 <2.0.0' From 312364c3c44c82f1d80b4030d03265caf95b4f15 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 18:43:43 +0200 Subject: [PATCH 404/599] fix permission --- .github/workflows/scripts/remove-from_coverage.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .github/workflows/scripts/remove-from_coverage.sh diff --git a/.github/workflows/scripts/remove-from_coverage.sh b/.github/workflows/scripts/remove-from_coverage.sh old mode 100644 new mode 100755 From 6f1764c85a7c91b3a9ac3ea1bcb41ba934033b12 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Jun 2021 22:24:15 +0530 Subject: [PATCH 405/599] fix stream_flutter_workflow.yml Signed-off-by: Sahil Kumar --- .github/workflows/stream_flutter_workflow.yml | 1 - analysis_options.yaml | 10 +++++----- melos.yaml | 2 +- .../lib/src/message_actions_modal.dart | 1 - 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index daa29977..6e87981f 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -26,7 +26,6 @@ jobs: - name: "Install Tools" run: | flutter pub global activate melos - flutter pub global activate tuneup - name: "Bootstrap Workspace" run: melos bootstrap - name: "Dart Analyze" diff --git a/analysis_options.yaml b/analysis_options.yaml index ed0ce983..bbf5cf50 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,11 +2,11 @@ analyzer: enable-experiment: - extension-methods exclude: - - lib/**/*.g.dart - - example/** - - lib/src/emoji - - lib/**/*.freezed.dart -# - test/** + - packages/*/lib/**/*.g.dart + - packages/*/example/** + - packages/*/lib/src/emoji + - packages/*/lib/**/*.freezed.dart + - packages/*/test/** linter: rules: diff --git a/melos.yaml b/melos.yaml index 17a896c0..d9bc6945 100644 --- a/melos.yaml +++ b/melos.yaml @@ -64,7 +64,7 @@ scripts: coverage:ignore-file: run: | - melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from_coverage.sh" + melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from_coverage.sh" --no-select description: Removes all the ignored files from the coverage report. select-package: dir-exists: coverage 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 5b4102a1..11818887 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:ui'; import 'package:flutter/foundation.dart'; From 20b41ef3fe716a721e7bce52b28be1717048f6b2 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Jun 2021 22:53:39 +0530 Subject: [PATCH 406/599] change remove-from_coverage.sh name to remove-from-coverage.sh Signed-off-by: Sahil Kumar --- .../{remove-from_coverage.sh => remove-from-coverage.sh} | 0 analysis_options.yaml | 2 -- melos.yaml | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) rename .github/workflows/scripts/{remove-from_coverage.sh => remove-from-coverage.sh} (100%) diff --git a/.github/workflows/scripts/remove-from_coverage.sh b/.github/workflows/scripts/remove-from-coverage.sh similarity index 100% rename from .github/workflows/scripts/remove-from_coverage.sh rename to .github/workflows/scripts/remove-from-coverage.sh diff --git a/analysis_options.yaml b/analysis_options.yaml index bbf5cf50..a5d8631e 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,6 +1,4 @@ analyzer: - enable-experiment: - - extension-methods exclude: - packages/*/lib/**/*.g.dart - packages/*/example/** diff --git a/melos.yaml b/melos.yaml index d9bc6945..0318ec81 100644 --- a/melos.yaml +++ b/melos.yaml @@ -64,7 +64,7 @@ scripts: coverage:ignore-file: run: | - melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from_coverage.sh" --no-select + melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from-coverage.sh" --no-select description: Removes all the ignored files from the coverage report. select-package: dir-exists: coverage From de4a6806702e8c42e33abbf23dc32a30568937c5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 19:36:44 +0200 Subject: [PATCH 407/599] fix permission --- .github/workflows/scripts/remove-from-coverage.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .github/workflows/scripts/remove-from-coverage.sh diff --git a/.github/workflows/scripts/remove-from-coverage.sh b/.github/workflows/scripts/remove-from-coverage.sh old mode 100644 new mode 100755 From c3ae85a282d66f92e8c39c5831355621cb9f0ca4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 18 Jun 2021 10:47:11 +0200 Subject: [PATCH 408/599] fix tests --- melos.yaml | 2 ++ .../test/src/mapper/event_mapper_test.dart | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/melos.yaml b/melos.yaml index 75a59320..9a39193b 100644 --- a/melos.yaml +++ b/melos.yaml @@ -66,6 +66,8 @@ scripts: run: | melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from-coverage.sh" --no-select description: Removes all the ignored files from the coverage report. + select-package: + dir-exists: coverage environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart index ffa8ee62..3182162b 100644 --- a/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/event_mapper_test.dart @@ -1,7 +1,7 @@ -import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/mapper/event_mapper.dart'; +import 'package:test/test.dart'; import '../utils/date_matcher.dart'; @@ -22,7 +22,7 @@ void main() { final event = entity.toEvent(); expect(event, isA()); expect(event.type, type); - expect(event.createdAt, isSameDateAs(now)); + expect(event.createdAt.toUtc(), isSameDateAs(now.toUtc())); expect(event.me!.id, ownUser.id); expect(event.totalUnreadCount, entity.totalUnreadCount); expect(event.unreadChannels, entity.unreadChannels); From 3eb336e93dd68b9dee0d5ce224f973613ab77ef4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 18 Jun 2021 11:04:53 +0200 Subject: [PATCH 409/599] fix analysis --- packages/stream_chat/lib/src/client/channel.dart | 2 +- .../stream_chat_flutter/test/src/full_screen_media_test.dart | 4 ++-- .../stream_chat_flutter/test/src/typing_indicator_test.dart | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 91ea4949..e173c703 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1888,7 +1888,7 @@ class ChannelClientState { void _clean() { final now = DateTime.now(); _typings.forEach((user, event) { - if (now.difference(event.createdAt!).inSeconds > 7) { + if (now.difference(event.createdAt).inSeconds > 7) { _channel.client.handleEvent( Event( type: EventType.typingStop, diff --git a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart index d418c303..3e6f1743 100644 --- a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart +++ b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart @@ -55,12 +55,12 @@ void main() { ])); when(() => channelState.typingEvents).thenAnswer((i) => { User(id: 'other-user', extraData: {'name': 'demo'}): - const Event(type: EventType.typingStart), + Event(type: EventType.typingStart), }); when(() => channelState.typingEventsStream) .thenAnswer((i) => Stream.value({ User(id: 'other-user', extraData: {'name': 'demo'}): - const Event(type: EventType.typingStart), + Event(type: EventType.typingStart), })); await tester.pumpWidget(MaterialApp( diff --git a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart index 75431f44..67c80b1e 100644 --- a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart @@ -55,12 +55,12 @@ void main() { when(() => channelState.typingEvents).thenAnswer((i) => { User(id: 'other-user', extraData: {'name': 'demo'}): - const Event(type: EventType.typingStart), + Event(type: EventType.typingStart), }); when(() => channelState.typingEventsStream) .thenAnswer((i) => Stream.value({ User(id: 'other-user', extraData: {'name': 'demo'}): - const Event(type: EventType.typingStart), + Event(type: EventType.typingStart), })); await tester.pumpWidget(MaterialApp( From 24931f7f309ebb4d46682f57d51b1bed7fb74d76 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 18 Jun 2021 11:21:53 +0200 Subject: [PATCH 410/599] fix coverage --- .github/workflows/stream_flutter_workflow.yml | 2 +- melos.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 6e87981f..90986ed8 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -77,7 +77,7 @@ jobs: - name: "Flutter Test" run: melos run test:all - name: "Collect Coverage" - run: melos run coverage:ignore-file + run: melos run coverage:ignore-file --no-select - name: "Upload Coverage" uses: codecov/codecov-action@v1 with: diff --git a/melos.yaml b/melos.yaml index 9a39193b..11f1d4d8 100644 --- a/melos.yaml +++ b/melos.yaml @@ -64,7 +64,7 @@ scripts: coverage:ignore-file: run: | - melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from-coverage.sh" --no-select + melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from-coverage.sh" description: Removes all the ignored files from the coverage report. select-package: dir-exists: coverage From 0fcea3f546caf5ee4e9e10fe1c215b03d5c11642 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 18 Jun 2021 15:27:38 +0530 Subject: [PATCH 411/599] handle offline user connect case Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/client/client.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index b9e1c5d7..e861ca19 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -289,6 +289,10 @@ class StreamChatClient { final event = await openConnection(); return event; } catch (e, stk) { + if (e is StreamChatNetworkError && e.isRetriable) { + final event = await _chatPersistenceClient?.getConnectionInfo(); + if (event != null) return event; + } logger.severe('error connecting user : ${ownUser.id}', e, stk); rethrow; } From 4bfda7553d1ea48fa1c3faddc05f7a5c11f62b4c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 18 Jun 2021 15:33:05 +0530 Subject: [PATCH 412/599] fix stream_flutter_workflow.yml Signed-off-by: Sahil Kumar --- .github/workflows/stream_flutter_workflow.yml | 5 ++--- analysis_options.yaml | 14 ++++++-------- melos.yaml | 6 ++++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index daa29977..1022e0ee 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -26,7 +26,6 @@ jobs: - name: "Install Tools" run: | flutter pub global activate melos - flutter pub global activate tuneup - name: "Bootstrap Workspace" run: melos bootstrap - name: "Dart Analyze" @@ -78,7 +77,7 @@ jobs: - name: "Flutter Test" run: melos run test:all - name: "Collect Coverage" - run: melos run coverage:ignore-file + run: melos run coverage:ignore-file --no-select - name: "Upload Coverage" uses: codecov/codecov-action@v1 with: @@ -103,4 +102,4 @@ jobs: uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_flutter/coverage/lcov.info - min_coverage: 35 + min_coverage: 35 \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index f0a87ea5..4b73086c 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,12 +1,10 @@ analyzer: - enable-experiment: - - extension-methods exclude: - - lib/**/*.g.dart - - example/** - - lib/src/emoji - - lib/**/*.freezed.dart - - test/** + - packages/*/lib/**/*.g.dart + - packages/*/example/** + - packages/*/lib/src/emoji + - packages/*/lib/**/*.freezed.dart + - packages/*/test/** linter: rules: @@ -147,4 +145,4 @@ linter: - cast_nullable_to_non_nullable - unnecessary_null_checks - tighten_type_of_initializing_formals - - null_check_on_nullable_type_parameter + - null_check_on_nullable_type_parameter \ No newline at end of file diff --git a/melos.yaml b/melos.yaml index f50c136a..1fb51c38 100644 --- a/melos.yaml +++ b/melos.yaml @@ -64,9 +64,11 @@ scripts: coverage:ignore-file: run: | - melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from_coverage.sh" + melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from-coverage.sh" description: Removes all the ignored files from the coverage report. + select-package: + dir-exists: coverage environment: sdk: '>=2.12.0 <3.0.0' - flutter: '>=1.22.4 <2.0.0' + flutter: '>=1.22.4 <2.0.0' \ No newline at end of file From 69b0b86bc151c3497fe09ff359f6686feff72b46 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 18 Jun 2021 15:35:03 +0530 Subject: [PATCH 413/599] change remove-from_coverage.sh to remove-from-coverage.sh Signed-off-by: Sahil Kumar --- .../scripts/{remove-from_coverage.sh => remove-from-coverage.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/scripts/{remove-from_coverage.sh => remove-from-coverage.sh} (100%) mode change 100755 => 100644 diff --git a/.github/workflows/scripts/remove-from_coverage.sh b/.github/workflows/scripts/remove-from-coverage.sh old mode 100755 new mode 100644 similarity index 100% rename from .github/workflows/scripts/remove-from_coverage.sh rename to .github/workflows/scripts/remove-from-coverage.sh From c6a4a99503e21067cb7a7524d7dc6fc0334a0e03 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 17 Jun 2021 19:36:44 +0200 Subject: [PATCH 414/599] fix permission --- .github/workflows/scripts/remove-from-coverage.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .github/workflows/scripts/remove-from-coverage.sh diff --git a/.github/workflows/scripts/remove-from-coverage.sh b/.github/workflows/scripts/remove-from-coverage.sh old mode 100644 new mode 100755 From 675bffe7b9b99077346add5db677987c80dfe266 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 18 Jun 2021 16:10:34 +0530 Subject: [PATCH 415/599] fix offline user connection, add tests Signed-off-by: Sahil Kumar --- .../stream_chat/lib/src/client/client.dart | 2 +- .../lib/src/core/error/stream_chat_error.dart | 3 + .../stream_chat/test/src/api/client_test.dart | 125 ++++++++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index e861ca19..3cc3ad0d 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -289,7 +289,7 @@ class StreamChatClient { final event = await openConnection(); return event; } catch (e, stk) { - if (e is StreamChatNetworkError && e.isRetriable) { + if (e is StreamWebSocketError && e.isRetriable) { final event = await _chatPersistenceClient?.getConnectionInfo(); if (event != null) return event; } 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 a78e9390..46302b4d 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 @@ -45,6 +45,9 @@ class StreamWebSocketError extends StreamChatError { /// Response body. please refer to [ErrorResponse]. final ErrorResponse? data; + /// + bool get isRetriable => data == null; + @override List get props => [...super.props, code]; diff --git a/packages/stream_chat/test/src/api/client_test.dart b/packages/stream_chat/test/src/api/client_test.dart index b0b30c83..13783865 100644 --- a/packages/stream_chat/test/src/api/client_test.dart +++ b/packages/stream_chat/test/src/api/client_test.dart @@ -330,6 +330,131 @@ void main() { ); }); + group('Fake web-socket connection function with failure and persistence', () { + const apiKey = 'test-api-key'; + late final api = FakeChatApi(); + late final persistence = MockPersistenceClient(); + + late StreamChatClient client; + + setUpAll(() { + // fallback values + registerFallbackValue(FakeUser()); + }); + + setUp(() { + final ws = FakeWebSocketWithConnectionError(); + client = StreamChatClient(apiKey, chatApi: api, ws: ws) + ..chatPersistenceClient = persistence; + }); + + tearDown(() { + client.dispose(); + }); + + test( + '`.connectUser` should connect successfully if persistence contains event', + () async { + final user = User(id: 'test-user-id'); + final token = Token.development(user.id).rawValue; + + final event = Event( + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user)); + when(persistence.getConnectionInfo).thenAnswer((_) async => event); + + final res = await client.connectUser(user, token); + expect(res, isNotNull); + expect(res.connectionId, 'test-connection-id'); + expect(res.me?.id, user.id); + + verify(persistence.getConnectionInfo).called(1); + verifyNoMoreInteractions(persistence); + }, + ); + + test( + '`.connectUserWithProvider` should connect successfully if persistence contains event', + () async { + final user = User(id: 'test-user-id'); + Future tokenProvider(String userId) async { + expect(userId, user.id); + return Token.development(userId).rawValue; + } + + final event = Event( + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user)); + when(persistence.getConnectionInfo).thenAnswer((_) async => event); + + final res = await client.connectUserWithProvider(user, tokenProvider); + expect(res, isNotNull); + expect(res.connectionId, 'test-connection-id'); + expect(res.me?.id, user.id); + + verify(persistence.getConnectionInfo).called(1); + verifyNoMoreInteractions(persistence); + }, + ); + + test( + '`.connectGuestUser` should connect successfully if persistence contains event', + () async { + final user = User(id: 'test-user-id'); + final token = Token.development(user.id).rawValue; + + final event = Event( + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user)); + when(persistence.getConnectionInfo).thenAnswer((_) async => event); + + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) + .thenAnswer( + (_) async => ConnectGuestUserResponse() + ..user = user + ..accessToken = token, + ); + + final res = await client.connectGuestUser(user); + expect(res, isNotNull); + expect(res.connectionId, 'test-connection-id'); + expect(res.me?.id, user.id); + + verify(persistence.getConnectionInfo).called(1); + verifyNoMoreInteractions(persistence); + verify(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) + .called(1); + verifyNoMoreInteractions(api.guest); + }, + ); + + test( + '`.connectAnonymousUser` should connect successfully if persistence contains event', + () async { + final user = User(id: 'test-user-id'); + + when(persistence.getConnectionInfo).thenAnswer( + (invocation) async => Event( + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ), + ); + + final res = await client.connectAnonymousUser(); + expect(res, isNotNull); + expect(res.connectionId, 'test-connection-id'); + expect(res.me?.id, user.id); + + verify(persistence.getConnectionInfo).called(1); + verifyNoMoreInteractions(persistence); + }, + ); + }); + group('Client with connected user with persistence', () { const apiKey = 'test-api-key'; late final api = FakeChatApi(); From 940a51691312fb7b5c850ef227a5e03683814590 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 18 Jun 2021 13:04:28 +0200 Subject: [PATCH 416/599] fix action --- .github/workflows/scripts/remove-from-coverage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scripts/remove-from-coverage.sh b/.github/workflows/scripts/remove-from-coverage.sh index c1190097..9ef430ac 100755 --- a/.github/workflows/scripts/remove-from-coverage.sh +++ b/.github/workflows/scripts/remove-from-coverage.sh @@ -3,5 +3,5 @@ # Fast fail the script on failures. set -e -pub global activate remove_from_coverage +pub global activate remove_from_coverage --overwrite pub global run remove_from_coverage:remove_from_coverage -f coverage/lcov.info -r '\.g\.dart$' -r '\.freezed\.dart$' \ No newline at end of file From f75f78401991cbcaf94f7ffcdf4e09266b5b5152 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 18 Jun 2021 20:24:24 +0530 Subject: [PATCH 417/599] feat: Added new docs --- docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx | 6 ++++++ .../Flutter/stream_chat_flutter_core/channel_list_core.mdx | 6 ++++++ .../docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx | 5 +++++ .../Flutter/stream_chat_flutter_core/message_list_core.mdx | 5 +++++ .../stream_chat_flutter_core/message_search_bloc.mdx | 5 +++++ .../stream_chat_flutter_core/message_search_list_core.mdx | 5 +++++ docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx | 6 ++++++ .../Flutter/stream_chat_flutter_core/stream_chat_core.mdx | 5 +++++ .../docs/Flutter/stream_chat_flutter_core/users_bloc.mdx | 5 +++++ 9 files changed, 48 insertions(+) create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx new file mode 100644 index 00000000..b4141769 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx @@ -0,0 +1,6 @@ +--- +id: setup +sidebar_position: 2 +title: Setup +--- + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx new file mode 100644 index 00000000..1ed9078a --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx @@ -0,0 +1,6 @@ +--- +id: channel_list_core +sidebar_position: 4 +title: Channel List Core +--- + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx new file mode 100644 index 00000000..959360ba --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx @@ -0,0 +1,5 @@ +--- +id: channels_bloc +sidebar_position: 7 +title: Channels Bloc +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx new file mode 100644 index 00000000..989d0852 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx @@ -0,0 +1,5 @@ +--- +id: message_list_core +sidebar_position: 5 +title: Message List Core +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx new file mode 100644 index 00000000..4084b087 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx @@ -0,0 +1,5 @@ +--- +id: message_search_list_block +sidebar_position: 8 +title: Message Search List Bloc +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx new file mode 100644 index 00000000..3b665bdf --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx @@ -0,0 +1,5 @@ +--- +id: message_search_list_core +sidebar_position: 6 +title: Message Search List Core +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx new file mode 100644 index 00000000..b4141769 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx @@ -0,0 +1,6 @@ +--- +id: setup +sidebar_position: 2 +title: Setup +--- + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx new file mode 100644 index 00000000..f77c6fe2 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx @@ -0,0 +1,5 @@ +--- +id: stream_chat_core +sidebar_position: 3 +title: Stream Chat Core +--- diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx new file mode 100644 index 00000000..0c903148 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx @@ -0,0 +1,5 @@ +--- +id: users_bloc +sidebar_position: 9 +title: Users Bloc +--- \ No newline at end of file From 6b26ce691564b56ef165e03271a1577d9de28e3c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 21 Jun 2021 09:54:13 +0200 Subject: [PATCH 418/599] fixes #483: Keyboard covers input text box when editing message --- .../lib/src/message_actions_modal.dart | 83 ++++++++++--------- 1 file changed, 43 insertions(+), 40 deletions(-) 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 f763585b..41f15c81 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -625,48 +625,51 @@ class _MessageActionsModalState extends State { topRight: Radius.circular(16), ), ), - builder: (context) => StreamChannel( - channel: channel, - child: Flex( - direction: Axis.vertical, - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.all(8), - child: StreamSvgIcon.edit( - color: streamChatThemeData.colorTheme.greyGainsboro, + builder: (context) => Padding( + padding: MediaQuery.of(context).viewInsets, + child: StreamChannel( + channel: channel, + child: Flex( + direction: Axis.vertical, + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: StreamSvgIcon.edit( + color: streamChatThemeData.colorTheme.greyGainsboro, + ), ), - ), - const Text( - 'Edit Message', - style: TextStyle(fontWeight: FontWeight.bold), - ), - IconButton( - visualDensity: VisualDensity.compact, - icon: StreamSvgIcon.closeSmall(), - onPressed: Navigator.of(context).pop, - ), - ], + const Text( + 'Edit Message', + style: TextStyle(fontWeight: FontWeight.bold), + ), + IconButton( + visualDensity: VisualDensity.compact, + icon: StreamSvgIcon.closeSmall(), + onPressed: Navigator.of(context).pop, + ), + ], + ), ), - ), - if (widget.editMessageInputBuilder != null) - widget.editMessageInputBuilder!(context, widget.message) - else - MessageInput( - editMessage: widget.message, - preMessageSending: (m) { - FocusScope.of(context).unfocus(); - Navigator.pop(context); - return m; - }, - ), - ], + if (widget.editMessageInputBuilder != null) + widget.editMessageInputBuilder!(context, widget.message) + else + MessageInput( + editMessage: widget.message, + preMessageSending: (m) { + FocusScope.of(context).unfocus(); + Navigator.pop(context); + return m; + }, + ), + ], + ), ), ), ); From 7c16f3bb9b43297852ea4af42700c8fb4580a753 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 21 Jun 2021 09:54:27 +0200 Subject: [PATCH 419/599] fix animation --- .../stream_chat_flutter/lib/src/message_input.dart | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 4f61c9fa..962d237b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -251,7 +251,6 @@ class MessageInputState extends State { late final FocusNode _focusNode; bool _inputEnabled = true; bool _messageIsPresent = false; - bool _animateContainer = true; bool _commandEnabled = false; OverlayEntry? _commandsOverlay, _mentionsOverlay, _emojiOverlay; late Iterable _emojiNames; @@ -939,10 +938,6 @@ class MessageInputState extends State { } Widget _buildFilePickerSection() { - if (!_openFilePickerSection) { - return const Offstage(); - } - final _attachmentContainsFile = _attachments.values.any((it) => it.type == 'file'); @@ -975,8 +970,7 @@ class MessageInputState extends State { } return AnimatedContainer( - duration: - _animateContainer ? const Duration(milliseconds: 300) : Duration.zero, + duration: const Duration(milliseconds: 300), height: _openFilePickerSection ? _filePickerSize : 0, child: Material( color: _streamChatTheme.colorTheme.whiteSmoke, @@ -1034,7 +1028,6 @@ class MessageInputState extends State { GestureDetector( onVerticalDragUpdate: (update) { setState(() { - _animateContainer = false; _filePickerSize = (_filePickerSize - update.delta.dy).clamp( _kMinMediaPickerSize, MediaQuery.of(context).size.height / 1.7, @@ -1672,7 +1665,6 @@ class MessageInputState extends State { onPressed: () async { if (_openFilePickerSection) { setState(() { - _animateContainer = false; _openFilePickerSection = false; _filePickerSize = _kMinMediaPickerSize; }); @@ -1718,7 +1710,6 @@ class MessageInputState extends State { if (_openFilePickerSection) { setState(() { - _animateContainer = true; _openFilePickerSection = false; _filePickerSize = _kMinMediaPickerSize; }); From 366e77ceb76cfd7dda066874900214969fa6f3f6 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 16 Jun 2021 10:48:19 +0200 Subject: [PATCH 420/599] fix reconnection logic --- .../stream_chat_flutter_core/lib/src/stream_chat_core.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index aa23a959..e80472cd 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -125,7 +125,8 @@ class StreamChatCoreState extends State _isConnectionAvailable = result != ConnectivityResult.none; if (!_isInForeground) return; if (_isConnectionAvailable) { - if (client.wsConnectionStatus == ConnectionStatus.disconnected) { + if (client.wsConnectionStatus == ConnectionStatus.disconnected && + user != null) { client.openConnection(); } } else { From ae3a7e99040090c93063936b3b28d9a0056984b5 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 21 Jun 2021 16:05:33 +0530 Subject: [PATCH 421/599] wrap WebSocketChannelError errors into StreamWebSocketError. Signed-off-by: Sahil Kumar --- .../lib/src/core/error/stream_chat_error.dart | 11 +++++++++++ packages/stream_chat/lib/src/ws/websocket.dart | 14 ++++++++++++++ 2 files changed, 25 insertions(+) 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 46302b4d..f517bea4 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 @@ -1,6 +1,10 @@ +import 'dart:io'; + import 'package:equatable/equatable.dart'; import 'package:stream_chat/src/core/error/chat_error_code.dart'; +import 'package:stream_chat/src/ws/websocket.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; /// class StreamChatError with EquatableMixin implements Exception { @@ -32,6 +36,13 @@ class StreamWebSocketError extends StreamChatError { return StreamWebSocketError(message, data: data); } + /// + factory StreamWebSocketError.fromWebSocketChannelError( + WebSocketChannelException error) { + final message = error.message ?? ''; + return StreamWebSocketError(message); + } + /// int? get code => data?.code; diff --git a/packages/stream_chat/lib/src/ws/websocket.dart b/packages/stream_chat/lib/src/ws/websocket.dart index 4c7c91ed..f9d8a210 100644 --- a/packages/stream_chat/lib/src/ws/websocket.dart +++ b/packages/stream_chat/lib/src/ws/websocket.dart @@ -325,6 +325,20 @@ class WebSocket with TimerHelper { void _onConnectionError(error, [stacktrace]) { _logger?.warning('Error occurred', error, stacktrace); + StreamWebSocketError wsError; + if (error is WebSocketChannelException) { + wsError = StreamWebSocketError.fromWebSocketChannelError(error); + } else { + wsError = StreamWebSocketError(error.toString()); + } + + final completer = connectionCompleter; + // complete with error if not yet completed + if (completer != null && !completer.isCompleted) { + // complete the connection with error + completer.completeError(wsError); + } + // resetting connect, reconnect request flag _resetRequestFlags(); From 7059e2fe21e6385e3272fef50e5e3f24f99f2be4 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 21 Jun 2021 17:09:51 +0530 Subject: [PATCH 422/599] remove unused imports Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/core/error/stream_chat_error.dart | 3 --- 1 file changed, 3 deletions(-) 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 f517bea4..13ccef2a 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 @@ -1,8 +1,5 @@ -import 'dart:io'; - import 'package:equatable/equatable.dart'; import 'package:stream_chat/src/core/error/chat_error_code.dart'; -import 'package:stream_chat/src/ws/websocket.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; From c5fc82415411012aa9757697770d1fdaf3d3c3f6 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 21 Jun 2021 09:54:13 +0200 Subject: [PATCH 423/599] fixes #483: Keyboard covers input text box when editing message --- .../lib/src/message_actions_modal.dart | 83 ++++++++++--------- 1 file changed, 43 insertions(+), 40 deletions(-) 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 11818887..6f57e7df 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -625,48 +625,51 @@ class _MessageActionsModalState extends State { topRight: Radius.circular(16), ), ), - builder: (context) => StreamChannel( - channel: channel, - child: Flex( - direction: Axis.vertical, - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.all(8), - child: StreamSvgIcon.edit( - color: streamChatThemeData.colorTheme.greyGainsboro, + builder: (context) => Padding( + padding: MediaQuery.of(context).viewInsets, + child: StreamChannel( + channel: channel, + child: Flex( + direction: Axis.vertical, + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: StreamSvgIcon.edit( + color: streamChatThemeData.colorTheme.greyGainsboro, + ), ), - ), - const Text( - 'Edit Message', - style: TextStyle(fontWeight: FontWeight.bold), - ), - IconButton( - visualDensity: VisualDensity.compact, - icon: StreamSvgIcon.closeSmall(), - onPressed: Navigator.of(context).pop, - ), - ], + const Text( + 'Edit Message', + style: TextStyle(fontWeight: FontWeight.bold), + ), + IconButton( + visualDensity: VisualDensity.compact, + icon: StreamSvgIcon.closeSmall(), + onPressed: Navigator.of(context).pop, + ), + ], + ), ), - ), - if (widget.editMessageInputBuilder != null) - widget.editMessageInputBuilder!(context, widget.message) - else - MessageInput( - editMessage: widget.message, - preMessageSending: (m) { - FocusScope.of(context).unfocus(); - Navigator.pop(context); - return m; - }, - ), - ], + if (widget.editMessageInputBuilder != null) + widget.editMessageInputBuilder!(context, widget.message) + else + MessageInput( + editMessage: widget.message, + preMessageSending: (m) { + FocusScope.of(context).unfocus(); + Navigator.pop(context); + return m; + }, + ), + ], + ), ), ), ); From 1ffb878b15aea169f536b264e562093c3ef20106 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 21 Jun 2021 09:54:27 +0200 Subject: [PATCH 424/599] fix animation --- .../stream_chat_flutter/lib/src/message_input.dart | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 4f61c9fa..962d237b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -251,7 +251,6 @@ class MessageInputState extends State { late final FocusNode _focusNode; bool _inputEnabled = true; bool _messageIsPresent = false; - bool _animateContainer = true; bool _commandEnabled = false; OverlayEntry? _commandsOverlay, _mentionsOverlay, _emojiOverlay; late Iterable _emojiNames; @@ -939,10 +938,6 @@ class MessageInputState extends State { } Widget _buildFilePickerSection() { - if (!_openFilePickerSection) { - return const Offstage(); - } - final _attachmentContainsFile = _attachments.values.any((it) => it.type == 'file'); @@ -975,8 +970,7 @@ class MessageInputState extends State { } return AnimatedContainer( - duration: - _animateContainer ? const Duration(milliseconds: 300) : Duration.zero, + duration: const Duration(milliseconds: 300), height: _openFilePickerSection ? _filePickerSize : 0, child: Material( color: _streamChatTheme.colorTheme.whiteSmoke, @@ -1034,7 +1028,6 @@ class MessageInputState extends State { GestureDetector( onVerticalDragUpdate: (update) { setState(() { - _animateContainer = false; _filePickerSize = (_filePickerSize - update.delta.dy).clamp( _kMinMediaPickerSize, MediaQuery.of(context).size.height / 1.7, @@ -1672,7 +1665,6 @@ class MessageInputState extends State { onPressed: () async { if (_openFilePickerSection) { setState(() { - _animateContainer = false; _openFilePickerSection = false; _filePickerSize = _kMinMediaPickerSize; }); @@ -1718,7 +1710,6 @@ class MessageInputState extends State { if (_openFilePickerSection) { setState(() { - _animateContainer = true; _openFilePickerSection = false; _filePickerSize = _kMinMediaPickerSize; }); From c4ee8d34b06d8bb6ad08c0ad0652879c1ffe2525 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 21 Jun 2021 15:29:45 +0200 Subject: [PATCH 425/599] fix gh action --- .github/workflows/scripts/remove-from-coverage.sh | 3 +-- .github/workflows/stream_flutter_workflow.yml | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/scripts/remove-from-coverage.sh b/.github/workflows/scripts/remove-from-coverage.sh index 9ef430ac..c6850e89 100755 --- a/.github/workflows/scripts/remove-from-coverage.sh +++ b/.github/workflows/scripts/remove-from-coverage.sh @@ -3,5 +3,4 @@ # Fast fail the script on failures. set -e -pub global activate remove_from_coverage --overwrite -pub global run remove_from_coverage:remove_from_coverage -f coverage/lcov.info -r '\.g\.dart$' -r '\.freezed\.dart$' \ No newline at end of file +pub global run remove_from_coverage:remove_from_coverage -f coverage/lcov.info -r '\.g\.dart$' -r '\.freezed\.dart$' diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 1022e0ee..3a6c1650 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -71,7 +71,9 @@ jobs: with: channel: 'stable' - name: "Install Tools" - run: flutter pub global activate melos + run: | + flutter pub global activate melos + pub global activate remove_from_coverage - name: "Bootstrap Workspace" run: melos bootstrap - name: "Flutter Test" From c7d77c8cd93798f20b0600e0c1adfa72c2918f43 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 21 Jun 2021 15:29:45 +0200 Subject: [PATCH 426/599] fix gh action --- .github/workflows/scripts/remove-from-coverage.sh | 3 +-- .github/workflows/stream_flutter_workflow.yml | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/scripts/remove-from-coverage.sh b/.github/workflows/scripts/remove-from-coverage.sh index c1190097..c6850e89 100755 --- a/.github/workflows/scripts/remove-from-coverage.sh +++ b/.github/workflows/scripts/remove-from-coverage.sh @@ -3,5 +3,4 @@ # Fast fail the script on failures. set -e -pub global activate remove_from_coverage -pub global run remove_from_coverage:remove_from_coverage -f coverage/lcov.info -r '\.g\.dart$' -r '\.freezed\.dart$' \ No newline at end of file +pub global run remove_from_coverage:remove_from_coverage -f coverage/lcov.info -r '\.g\.dart$' -r '\.freezed\.dart$' diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 1022e0ee..3a6c1650 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -71,7 +71,9 @@ jobs: with: channel: 'stable' - name: "Install Tools" - run: flutter pub global activate melos + run: | + flutter pub global activate melos + pub global activate remove_from_coverage - name: "Bootstrap Workspace" run: melos bootstrap - name: "Flutter Test" From 00e184f4225f3a09483802cd08944c0700bcbde4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 22 Jun 2021 12:54:22 +0200 Subject: [PATCH 427/599] show modal using the nearest navigator --- packages/stream_chat_flutter/lib/src/message_widget.dart | 2 ++ packages/stream_chat_flutter/lib/src/utils.dart | 2 ++ packages/stream_chat_flutter/pubspec.yaml | 6 ------ packages/stream_chat_flutter_core/pubspec.yaml | 4 ---- packages/stream_chat_persistence/pubspec.yaml | 4 ---- 5 files changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 7fb092d6..753f201c 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -908,6 +908,7 @@ class _MessageWidgetState extends State final channel = StreamChannel.of(context).channel; showDialog( + useRootNavigator: false, context: context, barrierColor: _streamChatTheme.colorTheme.overlay, builder: (context) => StreamChannel( @@ -959,6 +960,7 @@ class _MessageWidgetState extends State void _showMessageReactionsModalBottomSheet(BuildContext context) { final channel = StreamChannel.of(context).channel; showDialog( + useRootNavigator: false, context: context, barrierColor: _streamChatTheme.colorTheme.overlay, builder: (context) => StreamChannel( diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index e98112fc..d923b752 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -30,6 +30,7 @@ Future showConfirmationDialog( }) { final chatThemeData = StreamChatTheme.of(context); return showModalBottomSheet( + useRootNavigator: false, backgroundColor: chatThemeData.colorTheme.white, context: context, shape: const RoundedRectangleBorder( @@ -114,6 +115,7 @@ Future showInfoDialog( }) { final chatThemeData = StreamChatTheme.of(context); return showModalBottomSheet( + useRootNavigator: false, backgroundColor: theme?.colorTheme.white ?? chatThemeData.colorTheme.white, context: context, shape: const RoundedRectangleBorder( diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 4ef84ebc..bcebde99 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -53,12 +53,6 @@ flutter: - animations/ uses-material-design: true -dependency_overrides: - stream_chat: - path: ../stream_chat - stream_chat_flutter_core: - path: ../stream_chat_flutter_core - dev_dependencies: flutter_test: sdk: flutter diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index ff218936..14fa0f80 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -18,10 +18,6 @@ dependencies: rxdart: ^0.27.0 stream_chat: ^2.0.0-nullsafety.6 -dependency_overrides: - stream_chat: - path: ../stream_chat - dev_dependencies: fake_async: ^1.2.0 flutter_test: diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index ff1a8579..9d141198 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -20,10 +20,6 @@ dependencies: sqlite3_flutter_libs: ^0.5.0 stream_chat: ^2.0.0-nullsafety.5 -dependency_overrides: - stream_chat: - path: ../stream_chat - dev_dependencies: build_runner: ^2.0.1 mocktail: ^0.1.1 From 725c42db37e96d615c29aca38ab9915294d2d0e9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 22 Jun 2021 17:35:54 +0530 Subject: [PATCH 428/599] feat: Added new docs --- .../channels_bloc.mdx | 73 ++++++++++++++++++- .../stream_chat_flutter_core/introduction.mdx | 43 ++++++++++- .../message_search_bloc.mdx | 23 +++++- .../stream_chat_flutter_core/users_bloc.mdx | 22 +++++- 4 files changed, 157 insertions(+), 4 deletions(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx index 959360ba..b0014eb0 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx @@ -2,4 +2,75 @@ id: channels_bloc sidebar_position: 7 title: Channels Bloc ---- \ No newline at end of file +--- + +`ChannelsBloc` is a widget dedicated to the management of a channel list with pagination. +`ChannelsBloc` is used together with `ChannelListCore` to manage a list of +Channels with pagination, re-ordering, querying and other operations +associated with Channels. + +`ChannelsBloc` can be accessed at anytime by using the static of method +using Flutter's `BuildContext`. + +```dart +var _channelsBloc = ChannelsBloc.of(context); +``` + +The `ChannelsBloc` widget encapsulates common functionality related to channel lists such as fetching +the existing channels and querying new channels and also supplies them down the widget tree. + +The widget is required for the respective core widget (`ChannelListCore`) to fetch channels and hence +must be above the core widget in the tree. + +Here is a basic implementation of `ChannelsBloc`: + +```dart +ChannelsBloc( + child: // Further Widget Tree +), +``` + +The `ChannelsBloc` widget allows three customisations: + +#### Lock Channels Order + +ChannelsBloc may change the order of channels when new messages arrive. To lock this order, we can +set the `lockChannelsOrder` property to true. + +```dart +ChannelsBloc( + lockChannelsOrder: true, + child: // Further Widget Tree +), +``` + +#### Set custom channel order + +We can decide the order of the channels in the list by supplying a comparator to the `channelsComparator` +parameter: + +```dart +ChannelsBloc( + channelsComparator: (a, b) { + return a.createdAt!.millisecondsSinceEpoch > + b.createdAt!.millisecondsSinceEpoch + ? 1 + : -1; + }, + child: // Further Widget Tree +), +``` + +#### Decide if channel should be added on new message event + +When a new message arrives, a `message.new` event is created. We can decide if we want to add the channel +to the list using the `shouldAddChannel` parameter which is a callback supplying the event data: + +```dart +ChannelsBloc( + shouldAddChannel: (event) { + return event.message!.extraData['priority'] == '1'; + }, + child: // Further Widget Tree +), +``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx index fc8f385e..5fd9c17f 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx @@ -2,4 +2,45 @@ id: introduction sidebar_position: 1 title: Introduction ---- \ No newline at end of file +--- + +This package provides business logic to fetch common things required for integrating Stream Chat into your application. +The core package allows more customisation and hence provides business logic but no UI components. +Please use the stream_chat_flutter package for the full fledged suite of UI components or stream_chat for the low-level client. + +The package primarily contains three types of classes: + +* Business Logic Components +* Core Components +* Core Controllers + +### Business Logic Components + +These components allow you to have the maximum and lower-level control of the queries being executed. + +The BLoCs we provide are: + +* ChannelsBloc +* MessageSearchBloc +* UsersBloc + +### Core Components + +Core components usually are an easy way to fetch data associated with Stream Chat which are decoupled from UI and often expose UI builders. Data fetching can be controlled with the controllers of the respective core components. + +* ChannelListCore (Fetch a list of channels) +* MessageListCore (Fetch a list of messages from a channel) +* MessageSearchListCore (Fetch a list of search messages) +* UserListCore (Fetch a list of users) +* StreamChatCore (This is different from the other core components - it is a version of StreamChat decoupled from theme and initialisations.) + +### Core Controllers + +Core Controllers are supplied to respective CoreList widgets which allows reloading and pagination of data whenever needed. + +* ChannelListController +* MessageListController +* MessageSearchListController +* ChannelListController + +This section goes into the individual core package widgets and their functional use. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx index 4084b087..b79e3183 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx @@ -2,4 +2,25 @@ id: message_search_list_block sidebar_position: 8 title: Message Search List Bloc ---- \ No newline at end of file +--- + +`MessageSearchBloc` is used to manage a list of messages with pagination. +This class can be used to load messages, perform queries, etc. + +`MessageSearchBloc` can be accessed at anytime by using the static of method +using Flutter's BuildContext. + +```dart +var _searchBloc = MessageSearchBloc.of(context); +``` + +The `MessageSearchBloc` widget encapsulates common functionality related to searching for messages +across channels and also supplies them down the widget tree. + +Here is a basic implementation of `ChannelsBloc`: + +```dart +MessageSearchBloc( + child: // Further Widget Tree +), +``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx index 0c903148..cf971e16 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx @@ -2,4 +2,24 @@ id: users_bloc sidebar_position: 9 title: Users Bloc ---- \ No newline at end of file +--- + +Widget dedicated to the management of a users list with pagination. + +`UsersBloc` can be accessed at anytime by using the static of method +using Flutter's `BuildContext`. + +```dart +var _userBloc_ = UsersBloc.of(context); +``` + +The `UsersBloc` widget encapsulates common functionality related to searching for users +and also supplies them down the widget tree. + +Here is a basic implementation of `UsersBloc`: + +```dart +UsersBloc( + child: // Further Widget Tree +), +``` \ No newline at end of file From c987e8b4887a6c22a5c66695a48d788960083b38 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 22 Jun 2021 14:20:43 +0200 Subject: [PATCH 429/599] add comments --- .../stream_chat/lib/src/client/client.dart | 26 +++++++++---------- .../lib/src/client/retry_policy.dart | 6 +++++ .../lib/src/core/api/channel_api.dart | 4 +-- .../lib/src/core/api/device_api.dart | 4 +-- .../lib/src/core/api/general_api.dart | 4 +-- .../lib/src/core/api/guest_api.dart | 6 ++--- .../lib/src/core/api/message_api.dart | 4 +-- .../lib/src/core/api/moderation_api.dart | 4 +-- .../lib/src/core/api/responses.dart | 10 +++---- .../lib/src/core/api/stream_chat_api.dart | 24 ++++++++--------- .../lib/src/core/api/user_api.dart | 4 +-- .../src/core/http/connection_id_manager.dart | 12 ++++----- .../http/interceptor/auth_interceptor.dart | 10 +++---- .../connection_id_interceptor.dart | 3 +-- .../http/interceptor/logging_interceptor.dart | 21 +++++---------- .../src/core/http/stream_chat_dio_error.dart | 4 +-- .../stream_chat/lib/src/core/http/token.dart | 19 +++++++------- .../lib/src/core/http/token_manager.dart | 15 ++++++----- 18 files changed, 89 insertions(+), 91 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 3cc3ad0d..b579f030 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -6,33 +6,31 @@ import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/client/channel.dart'; -import 'package:stream_chat/src/core/util/utils.dart'; -import 'package:stream_chat/src/core/error/error.dart'; -import 'package:stream_chat/src/location.dart'; -import 'package:stream_chat/src/ws/connection_status.dart'; +import 'package:stream_chat/src/client/retry_policy.dart'; import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; import 'package:stream_chat/src/core/api/requests.dart'; import 'package:stream_chat/src/core/api/responses.dart'; -import 'package:stream_chat/src/client/retry_policy.dart'; -import 'package:stream_chat/src/ws/websocket.dart'; import 'package:stream_chat/src/core/api/stream_chat_api.dart'; +import 'package:stream_chat/src/core/error/error.dart'; import 'package:stream_chat/src/core/http/connection_id_manager.dart'; import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/http/token.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; -import 'package:stream_chat/src/db/chat_persistence_client.dart'; -import 'package:stream_chat/src/event_type.dart'; import 'package:stream_chat/src/core/models/attachment_file.dart'; import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:stream_chat/src/core/models/channel_state.dart'; import 'package:stream_chat/src/core/models/event.dart'; +import 'package:stream_chat/src/core/models/filter.dart'; +import 'package:stream_chat/src/core/models/member.dart'; import 'package:stream_chat/src/core/models/message.dart'; import 'package:stream_chat/src/core/models/own_user.dart'; import 'package:stream_chat/src/core/models/user.dart'; - -import 'package:stream_chat/src/core/models/filter.dart'; - -import 'package:stream_chat/src/core/models/member.dart'; +import 'package:stream_chat/src/core/util/utils.dart'; +import 'package:stream_chat/src/db/chat_persistence_client.dart'; +import 'package:stream_chat/src/event_type.dart'; +import 'package:stream_chat/src/location.dart'; +import 'package:stream_chat/src/ws/connection_status.dart'; +import 'package:stream_chat/src/ws/websocket.dart'; /// Handler function used for logging records. Function requires a single /// [LogRecord] as the only parameter. @@ -137,10 +135,10 @@ class StreamChatClient { late final RetryPolicy _retryPolicy; - // sync state of the channels present inside state, defaults to false + /// sync state of the channels present inside state, defaults to false bool _synced = false; - // the last dateTime at the which all the channels were synced + /// the last dateTime at the which all the channels were synced DateTime? _lastSyncedAt; /// The retry policy options getter diff --git a/packages/stream_chat/lib/src/client/retry_policy.dart b/packages/stream_chat/lib/src/client/retry_policy.dart index fbebc696..5b7812ab 100644 --- a/packages/stream_chat/lib/src/client/retry_policy.dart +++ b/packages/stream_chat/lib/src/client/retry_policy.dart @@ -2,6 +2,12 @@ import 'package:stream_chat/src/client/client.dart'; import 'package:stream_chat/src/core/error/error.dart'; /// The retry options +/// When sending/updating/deleting a message any temporary error will trigger the retry policy +/// The retry policy exposes 2 methods +/// - shouldRetry: returns a boolean if the request should be retried +/// - retryTimeout: How many milliseconds to wait till the next attempt +/// +/// maxRetryAttempts is a hard limit on maximum retry attempts before giving up class RetryPolicy { /// Instantiate a new RetryPolicy RetryPolicy({ 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 9da05df0..7660c125 100644 --- a/packages/stream_chat/lib/src/core/api/channel_api.dart +++ b/packages/stream_chat/lib/src/core/api/channel_api.dart @@ -9,9 +9,9 @@ import 'package:stream_chat/src/core/models/event.dart'; import 'package:stream_chat/src/core/models/filter.dart'; import 'package:stream_chat/src/core/models/message.dart'; -/// +/// Defines the api dedicated to channel operations class ChannelApi { - /// + /// Initialize a new channel api ChannelApi(this._client); final StreamHttpClient _client; 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 69f509f8..2d2b9d7b 100644 --- a/packages/stream_chat/lib/src/core/api/device_api.dart +++ b/packages/stream_chat/lib/src/core/api/device_api.dart @@ -19,9 +19,9 @@ extension PushProviderX on PushProvider { }[this]!; } -/// +/// Defines the api dedicated to device operations class DeviceApi { - /// + /// Initialize a new device api DeviceApi(this._client); final StreamHttpClient _client; diff --git a/packages/stream_chat/lib/src/core/api/general_api.dart b/packages/stream_chat/lib/src/core/api/general_api.dart index d26ffc7d..8eeeaa5f 100644 --- a/packages/stream_chat/lib/src/core/api/general_api.dart +++ b/packages/stream_chat/lib/src/core/api/general_api.dart @@ -6,9 +6,9 @@ import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/models/filter.dart'; import 'package:stream_chat/src/core/models/member.dart'; -/// +/// Defines the api dedicated to general operations class GeneralApi { - /// + /// Initialize a new general api GeneralApi(this._client); final StreamHttpClient _client; diff --git a/packages/stream_chat/lib/src/core/api/guest_api.dart b/packages/stream_chat/lib/src/core/api/guest_api.dart index 438a637f..b6727902 100644 --- a/packages/stream_chat/lib/src/core/api/guest_api.dart +++ b/packages/stream_chat/lib/src/core/api/guest_api.dart @@ -2,14 +2,14 @@ import 'package:stream_chat/src/core/api/responses.dart'; import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/models/user.dart'; -/// +/// Defines the api dedicated to guest users operations class GuestApi { - /// + /// Initialize a new guest api GuestApi(this._client); final StreamHttpClient _client; - /// + /// Returns the information about guest user Future getGuestUser(User user) async { final response = await _client.post( '/guest', diff --git a/packages/stream_chat/lib/src/core/api/message_api.dart b/packages/stream_chat/lib/src/core/api/message_api.dart index 22f7bfef..f50cb4a1 100644 --- a/packages/stream_chat/lib/src/core/api/message_api.dart +++ b/packages/stream_chat/lib/src/core/api/message_api.dart @@ -3,9 +3,9 @@ import 'package:stream_chat/src/core/api/responses.dart'; import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/models/message.dart'; -/// +/// Defines the api dedicated to messages operations class MessageApi { - /// + /// Initialize a new message api MessageApi(this._client); final StreamHttpClient _client; diff --git a/packages/stream_chat/lib/src/core/api/moderation_api.dart b/packages/stream_chat/lib/src/core/api/moderation_api.dart index 22c58c35..533fbf63 100644 --- a/packages/stream_chat/lib/src/core/api/moderation_api.dart +++ b/packages/stream_chat/lib/src/core/api/moderation_api.dart @@ -1,9 +1,9 @@ import 'package:stream_chat/src/core/api/responses.dart'; import 'package:stream_chat/src/core/http/stream_http_client.dart'; -/// +/// Defines the api dedicated to moderation operations class ModerationApi { - /// + /// Initialize a new moderation api ModerationApi(this._client); final StreamHttpClient _client; diff --git a/packages/stream_chat/lib/src/core/api/responses.dart b/packages/stream_chat/lib/src/core/api/responses.dart index ccf03822..e06a213a 100644 --- a/packages/stream_chat/lib/src/core/api/responses.dart +++ b/packages/stream_chat/lib/src/core/api/responses.dart @@ -1,5 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/client/client.dart'; +import 'package:stream_chat/src/core/error/error.dart'; import 'package:stream_chat/src/core/models/channel_model.dart'; import 'package:stream_chat/src/core/models/channel_state.dart'; import 'package:stream_chat/src/core/models/device.dart'; @@ -9,7 +10,6 @@ import 'package:stream_chat/src/core/models/message.dart'; import 'package:stream_chat/src/core/models/reaction.dart'; import 'package:stream_chat/src/core/models/read.dart'; import 'package:stream_chat/src/core/models/user.dart'; -import 'package:stream_chat/src/core/error/error.dart'; part 'responses.g.dart'; @@ -20,17 +20,17 @@ class _BaseResponse { /// Model response for [StreamChatNetworkError] data @JsonSerializable() class ErrorResponse extends _BaseResponse { - /// + /// The http error code int? code; - /// + /// The message associated to the error code String? message; - /// + /// The backend error code @JsonKey(name: 'StatusCode') int? statusCode; - /// + /// A detailed message about the error String? moreInfo; /// Create a new instance from a json diff --git a/packages/stream_chat/lib/src/core/api/stream_chat_api.dart b/packages/stream_chat/lib/src/core/api/stream_chat_api.dart index 13342160..bcf041c1 100644 --- a/packages/stream_chat/lib/src/core/api/stream_chat_api.dart +++ b/packages/stream_chat/lib/src/core/api/stream_chat_api.dart @@ -1,21 +1,21 @@ import 'package:logging/logging.dart'; +import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; import 'package:stream_chat/src/core/api/channel_api.dart'; import 'package:stream_chat/src/core/api/device_api.dart'; import 'package:stream_chat/src/core/api/general_api.dart'; import 'package:stream_chat/src/core/api/guest_api.dart'; import 'package:stream_chat/src/core/api/message_api.dart'; import 'package:stream_chat/src/core/api/moderation_api.dart'; +import 'package:stream_chat/src/core/api/user_api.dart'; import 'package:stream_chat/src/core/http/connection_id_manager.dart'; import 'package:stream_chat/src/core/http/stream_http_client.dart'; -import 'package:stream_chat/src/core/api/user_api.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; -import 'package:stream_chat/src/core/api/attachment_file_uploader.dart'; export 'device_api.dart' show PushProvider; -/// +/// ApiClient that wraps every other specific api class StreamChatApi { - /// + /// Initialize a new stream chat api StreamChatApi( String apiKey, { StreamHttpClient? client, @@ -38,42 +38,42 @@ class StreamChatApi { UserApi? _user; - /// + /// Api dedicated to users operations UserApi get user => _user ??= UserApi(_client); GuestApi? _guest; - /// + /// Api dedicated to guest operations GuestApi get guest => _guest ??= GuestApi(_client); MessageApi? _message; - /// + /// Api dedicated to message operations MessageApi get message => _message ??= MessageApi(_client); ChannelApi? _channel; - /// + /// Api dedicated to channel operations ChannelApi get channel => _channel ??= ChannelApi(_client); DeviceApi? _device; - /// + /// Api dedicated to device operations DeviceApi get device => _device ??= DeviceApi(_client); ModerationApi? _moderation; - /// + /// Api dedicated to moderation operations ModerationApi get moderation => _moderation ??= ModerationApi(_client); GeneralApi? _general; - /// + /// Api dedicated to general operations GeneralApi get general => _general ??= GeneralApi(_client); AttachmentFileUploader? _fileUploader; - /// + /// Class responsible for uploading images and files to a given channel AttachmentFileUploader get fileUploader => _fileUploader ??= StreamAttachmentFileUploader(_client); } diff --git a/packages/stream_chat/lib/src/core/api/user_api.dart b/packages/stream_chat/lib/src/core/api/user_api.dart index 04c72284..61159731 100644 --- a/packages/stream_chat/lib/src/core/api/user_api.dart +++ b/packages/stream_chat/lib/src/core/api/user_api.dart @@ -6,9 +6,9 @@ import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/models/filter.dart'; import 'package:stream_chat/src/core/models/user.dart'; -/// +/// Defines the api dedicated to users operations class UserApi { - /// + /// Initialize a new user api UserApi(this._client); final StreamHttpClient _client; diff --git a/packages/stream_chat/lib/src/core/http/connection_id_manager.dart b/packages/stream_chat/lib/src/core/http/connection_id_manager.dart index 55fa1896..59dcb1b6 100644 --- a/packages/stream_chat/lib/src/core/http/connection_id_manager.dart +++ b/packages/stream_chat/lib/src/core/http/connection_id_manager.dart @@ -1,26 +1,26 @@ // ignore_for_file: use_setters_to_change_properties -/// +/// Handles the connection id of the websocket connection class ConnectionIdManager { - /// + /// Initialize a new connection id manager ConnectionIdManager({ String? connectionId, }) : _connectionId = connectionId; String? _connectionId; - /// + /// Get the current connection id String? get connectionId => _connectionId; - /// + /// True if there is a connection id bool get hasConnectionId => _connectionId != null; - /// + /// Set the connection id void setConnectionId(String connectionId) { _connectionId = connectionId; } - /// + /// Clear the connection id void reset() { _connectionId = null; } diff --git a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart index 3097c2b1..9947eefa 100644 --- a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart +++ b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart @@ -1,20 +1,20 @@ import 'package:dio/dio.dart'; import 'package:stream_chat/src/core/api/responses.dart'; +import 'package:stream_chat/src/core/error/error.dart'; import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart'; import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/http/token.dart'; - import 'package:stream_chat/src/core/http/token_manager.dart'; -import 'package:stream_chat/src/core/error/error.dart'; -/// +/// Authentication interceptor that refreshes the token if +/// an auth error is received class AuthInterceptor extends Interceptor { - /// + /// Initialize a new auth interceptor AuthInterceptor(this._client, this._tokenManager); final StreamHttpClient _client; - /// + /// The token manager used in the client final TokenManager _tokenManager; @override diff --git a/packages/stream_chat/lib/src/core/http/interceptor/connection_id_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/connection_id_interceptor.dart index 78a3147f..59e1c4bc 100644 --- a/packages/stream_chat/lib/src/core/http/interceptor/connection_id_interceptor.dart +++ b/packages/stream_chat/lib/src/core/http/interceptor/connection_id_interceptor.dart @@ -1,8 +1,7 @@ import 'package:dio/dio.dart'; - import 'package:stream_chat/src/core/http/connection_id_manager.dart'; -/// +/// Interceptor that injects the connection id in the request params class ConnectionIdInterceptor extends Interceptor { /// ConnectionIdInterceptor(this.connectionIdManager); 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 1f5687a8..f78b46ea 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 @@ -5,26 +5,26 @@ import 'dart:math' as math; import 'package:dio/dio.dart'; -/// +/// Step where we're logging enum InterceptStep { - /// + /// Request request, - /// + /// Response response, - /// + /// Error error, } -/// +/// Function used to print the log typedef LogPrint = void Function(InterceptStep step, Object object); void _defaultLogPrint(InterceptStep step, Object object) => print(object); -/// +/// Interceptor dedicated to logging class LoggingInterceptor extends Interceptor { - /// + /// Initialize a new logging interceptor LoggingInterceptor({ this.request = true, this.requestHeader = false, @@ -310,13 +310,6 @@ class LoggingInterceptor extends Interceptor { }); } -/* bool _canFlattenMap(Map map) => - map.values.where((dynamic val) => val is Map || val is List).isEmpty && - map.toString().length < maxWidth; - - bool _canFlattenList(List list) => - list.length < 10 && list.toString().length < maxWidth;*/ - void _printMapAsTable( void Function(Object) logPrint, Map? map, { diff --git a/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart b/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart index 987c9d67..a8ee0988 100644 --- a/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart +++ b/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart @@ -1,9 +1,9 @@ import 'package:dio/dio.dart'; import 'package:stream_chat/src/core/error/error.dart'; -/// +/// Error class specific to StreamChat and Dio class StreamChatDioError extends DioError { - /// + /// Initialize a stream chat dio error StreamChatDioError({ required this.error, required RequestOptions requestOptions, diff --git a/packages/stream_chat/lib/src/core/http/token.dart b/packages/stream_chat/lib/src/core/http/token.dart index 26532e80..7d746286 100644 --- a/packages/stream_chat/lib/src/core/http/token.dart +++ b/packages/stream_chat/lib/src/core/http/token.dart @@ -5,21 +5,22 @@ import 'package:jose/jose.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/src/core/util/utils.dart'; -/// +/// A function which can be used to request a Stream Chat API token from your +/// own backend server typedef GuestTokenProvider = Future Function(User user); -/// +/// Authentication type enum AuthType { - /// + /// JWT token jwt, - /// + /// Anonymous user anonymous, } -/// +/// Extension for returning the AuthType as a string extension AuthTypeX on AuthType { - /// + /// Returns the AuthType as a string String get raw => { AuthType.jwt: 'jwt', AuthType.anonymous: 'anonymous', @@ -71,13 +72,13 @@ class Token extends Equatable { return Token.fromRawValue(rawToken); } - /// + /// Authentication type of this token final AuthType authType; - /// + /// String value of the token final String rawValue; - /// + /// User id associated with this token final String userId; @override diff --git a/packages/stream_chat/lib/src/core/http/token_manager.dart b/packages/stream_chat/lib/src/core/http/token_manager.dart index 88e9746d..e5af0ddc 100644 --- a/packages/stream_chat/lib/src/core/http/token_manager.dart +++ b/packages/stream_chat/lib/src/core/http/token_manager.dart @@ -1,12 +1,13 @@ import 'package:stream_chat/src/core/http/token.dart'; /// A function which can be used to request a Stream Chat API token from your -/// own backend server. Function requires a single [userId]. +/// own backend server. +/// Function requires a single [userId]. typedef TokenProvider = Future Function(String userId); -/// +/// Handles common token operations class TokenManager { - /// + /// Initialize a new token manager TokenManager({ String? userId, Token? token, @@ -25,10 +26,10 @@ class TokenManager { /// User id to which this TokenManager is configured to String? get userId => _userId; - /// + /// True if it's a static token bool get isStatic => _type == 'static'; - /// + /// Set a token or a token provider Future setTokenOrProvider( String userId, { Token? token, @@ -58,7 +59,7 @@ class TokenManager { return loadToken(); } - /// + /// Returns the token refreshing the existing one if [refresh] is true Future loadToken({bool refresh = false}) async { assert( _userId != null && _type != null, @@ -71,7 +72,7 @@ class TokenManager { return _token!; } - /// + /// Resets the token manager void reset() { _userId = null; _token = null; From 6d17472d076d7d98a5bd3203c232dddb29f098e8 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 22 Jun 2021 18:07:15 +0530 Subject: [PATCH 430/599] feat: Added new docs --- .../Flutter/stream_chat_flutter_core/stream_chat_core.mdx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx index f77c6fe2..c0f10e6c 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx @@ -3,3 +3,11 @@ id: stream_chat_core sidebar_position: 3 title: Stream Chat Core --- + +`StreamChatCore` is a version of `StreamChat` found in stream_chat_flutter that is decoupled from +theme and initialisations. + +Widget used to provide information about the chat to the widget tree. +This Widget is used to react to life cycle changes and system updates. +When the app goes into the background, the websocket connection is kept +alive for two minutes before being terminated. \ No newline at end of file From 4a336815ae14266198ff699314a250db5b4e8689 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 22 Jun 2021 14:47:21 +0200 Subject: [PATCH 431/599] fix showdialog in image gallery --- packages/stream_chat_flutter/lib/src/image_header.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/image_header.dart index 434a73c5..3d156b16 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/image_header.dart @@ -111,6 +111,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { final channel = StreamChannel.of(context).channel; final result = await showDialog( + useRootNavigator: false, context: context, barrierColor: StreamChatTheme.of(context).colorTheme.overlay, builder: (context) => StreamChannel( From c75a0388bbe089af36e5adbd29d25b18fe7dabb0 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 23 Jun 2021 12:38:18 +0530 Subject: [PATCH 432/599] feat: Added new docs --- .../docs/Flutter/assets/chat_basics.png | Bin 0 -> 28913 bytes .../docs/Flutter/basics/introduction.mdx | 30 ++++++++++++++---- .../Flutter/stream_chat/channel_members.mdx | 2 ++ .../Flutter/stream_chat/creating_channels.mdx | 2 ++ .../deleting_and_hiding_channel.mdx | 2 ++ .../docs/Flutter/stream_chat/events.mdx | 2 ++ .../docs/Flutter/stream_chat/introduction.mdx | 1 + .../docs/Flutter/stream_chat/invites.mdx | 2 ++ .../docs/Flutter/stream_chat/messages.mdx | 2 ++ .../Flutter/stream_chat/moderation_tools.mdx | 2 ++ .../Flutter/stream_chat/muting_channels.mdx | 2 ++ .../stream_chat/paginating_channels.mdx | 2 ++ .../Flutter/stream_chat/pinned_messages.mdx | 2 ++ .../Flutter/stream_chat/querying_channels.mdx | 2 ++ .../Flutter/stream_chat/querying_users.mdx | 1 + .../docs/Flutter/stream_chat/reactions.mdx | 2 ++ .../stream_chat/searching_messages.mdx | 2 ++ docusaurus/docs/Flutter/stream_chat/setup.mdx | 1 + .../Flutter/stream_chat/silent_messages.mdx | 2 ++ .../docs/Flutter/stream_chat/slow_mode.mdx | 2 ++ .../stream_chat/threads_and_replies.mdx | 2 ++ .../Flutter/stream_chat/typing_indicators.mdx | 2 ++ .../Flutter/stream_chat/updating_channels.mdx | 2 ++ .../Flutter/stream_chat/uploading_files.mdx | 2 ++ .../Flutter/stream_chat/user_presence.mdx | 2 ++ .../docs/Flutter/stream_chat/user_types.mdx | 1 + .../Flutter/stream_chat/watching_channels.mdx | 2 ++ .../stream_chat_flutter_core/introduction.mdx | 29 +++++++++++++++-- .../stream_chat_flutter_core/setup.mdx | 2 ++ 29 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 docusaurus/docs/Flutter/assets/chat_basics.png diff --git a/docusaurus/docs/Flutter/assets/chat_basics.png b/docusaurus/docs/Flutter/assets/chat_basics.png new file mode 100644 index 0000000000000000000000000000000000000000..4eb0aa67639ff65c4331c28c45737a04b88be8f1 GIT binary patch literal 28913 zcmc$`1yEhh*X|h-5(vRvLxAA!4#C~s-8HxqEV#RSAh=5i?ryheuvQ>!-Xz4Z|6~)ay}kMOZkr*EUFex^or}qYZcL=z^Qb377k8r*rp52Sd)h zjG<@SoPqRrf`~{@FDw`=iBE!0FIe{HxBsX2t|)=$@Bgd!|Du`yH2+^U^Plhk51Ui` z)I$XkZ*&xqG=`PFB-aVQ{Y^ep-S5iq4lVpx0MTPPi|3$^MRkpoI|7m{*0w#)M?ylvuedq<>xQ8=Z*q@uD(Hr9;_3K_DIF|=%HR+`L!Ehbev?%W4FWHM zz(T|QT_2%N*%=7mF9qam7qm|4v0lh%JDU+laN|vH7mIRyh^BvrqnBNoqF4w6TY@1f z`l5`a)UO%73xMHG?>N^A3LbP}{>Xom`~%N`V|8-bwlwULGdt~VPpQe)(_O@&v|YXo zpgW5Uoy8+dYa)NM?p)wC6k|)icY*c9*yvJPbn#!x|Gk-(d>?&7q>r8(%Q?dWW;tFC zx1U~hI^?9$kIpMgB%-|Rj{R;DPn7PA%);nehnC5KU!I}`rf0&BHkpkO-G)Z+btuLg z0x*6;{l4rRhDx*9b+OW_x-E3w9)xpDUfI^ui+g|U$7+^UiofTdB83sZG-GII*g7Di zO&~nE75%2{csaXJn0czgx39{tGff=bE#xvzYb@Hv1tOc_zMFcc=2S7i|ESyf*_!Xk zuVC2~84O_vq=lMG{N6DlN30eE$nK4Q<#U`@7xbG_HV#RN~dt{sNgY&{EQ zX9{!5O3xB900zUL;@|^IhR3yh$(gfT-HYqYsaunY@*O7zB z?=~I3RBtK*3;3O-qmS~3QfA^v?CqnBQ;T}0+$Xraxe_wrU$8Nur7wNvk~g>1P;tZf za{Kn`5Z|NrmUzYzbM6hklMoSUQu3$VZa>I72W*|Afihd80aQZRC%oG*^mht1B>{N& zqD{MV_8tB-STe(st7<64BH3lHz8w!)k;|)MzoJzk7tHrm374&|LN)lo)OPhjuxq>e zacgkRk0$E=(Q*(=0?Xi9l&RDQfn~e()!||lmt*0F{M^>u%0DOX+$YA6c*7D1lOv*G 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 Nky5yqdFQG7{{c%u2zdYi literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/basics/introduction.mdx b/docusaurus/docs/Flutter/basics/introduction.mdx index 5a656d1e..b3280a78 100644 --- a/docusaurus/docs/Flutter/basics/introduction.mdx +++ b/docusaurus/docs/Flutter/basics/introduction.mdx @@ -4,15 +4,11 @@ id: introduction sidebar_position: 1 title: Introduction To Stream Chat For Flutter --- +Exploring The Basics Of Stream Chat ![](../assets/sdk_title.png) -Stream Chat allows you to build real-time chat messaging in less time. -Rapidly ship in-app messaging with our highly reliable chat infrastructure and feature-rich SDKs. -Drive in-app conversion, engagement, and retention. - -The Flutter SDK enables you to build any type of chat or messaging experience for Android, iOS, Web -and Desktop. +Stream Chat is a service that helps you easily build a full chat experience in your Flutter (and more) apps. This section of the documentation focuses on our Flutter SDK which helps you easily ship high quality messaging experiences in apps and programs built with the [Flutter toolkit made @@ -37,12 +33,34 @@ We recommend building prototypes using the full UI package since it contains UI integrated with Stream's API. [stream_chat_flutter](https://pub.dev/packages/stream_chat_flutter) is the fastest way to get up and running using Stream chat in your app. +The Flutter SDK enables you to build any type of chat or messaging experience for Android, iOS, Web +and Desktop. + If you're building a very custom UI and would prefer a more lean package, our core package will be suited to this use case. Core allows you to build custom, expressive UIs while retaining the benefits of our full Flutter SDK. APIs for accessing and controlling users, sending messages, etc are seamlessly integrated into this package and accessible via providers and builders. +Before going into the docs, let's take a small detour to look at how the elements of Stream Chat are structured. + +There are two core elements in chat, Users and Channels. +Channels are groups of one or more users that can message each other. +In an app, you need to have a user connected to query channels. + +There is no specific distinction between a chat between two people and a group chat, +but there is a way to create a unique chat between a certain number of people by creating a distinct channel. + +![](../assets/chat_basics.png) + +In essence, a normal two-person chat would be a distinct channel created with two members (you cannot add or delete members in this channel), whereas a group created with two people would simply be a non distinct channel (possible to add or remove members). + +Note: It is also possible to add more than two people in a distinct channel which retains the same add/removal properties and resembles the Slack DMs where you can DM two or more people as well. + +In summary, if you were creating a Whatsapp-like app, the first screen would be a list of channels - which on opening would show a list of messages that were sent by the users in the Channel. + +While this is a simplistic overview of the service, thankfully the Flutter SDK handles the UI and more time consuming things (media upload, offline storage, theming, etc.) for us. + Before reading the docs, consider trying our [online API tour](https://getstream.io/chat/get_started/), it is a nice way to learn how the API works. It's in-browser so Javascript-based but the ideas are pretty much the same as Dart. diff --git a/docusaurus/docs/Flutter/stream_chat/channel_members.mdx b/docusaurus/docs/Flutter/stream_chat/channel_members.mdx index 750a9ac7..e14b0c10 100644 --- a/docusaurus/docs/Flutter/stream_chat/channel_members.mdx +++ b/docusaurus/docs/Flutter/stream_chat/channel_members.mdx @@ -4,6 +4,8 @@ sidebar_position: 10 title: Channel Members --- +Manipulating Members In A Channel + ### Adding & Removing Channel Members Using the addMembers() method adds the given users as members, while removeMembers() removes them. diff --git a/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx b/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx index 9d273cb1..99b29907 100644 --- a/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx +++ b/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx @@ -4,6 +4,8 @@ sidebar_position: 6 title: Creating Channels --- +Create Channels In Your App + Both channel channel.query and channel.watch methods ensure that a channel exists and create one otherwise. If all you need is to ensure that a channel exists, you can use channel.create. Typically you use channel.watch client side and channel.create for a server side integration. diff --git a/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx b/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx index 0630aded..2a1d044f 100644 --- a/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx +++ b/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx @@ -4,6 +4,8 @@ sidebar_position: 13 title: Deleting And Hiding A Channel --- +Remove Channels By Deleting Or Hiding + ### Deleting a Channel You can delete a Channel using the delete method. This marks the channel as deleted and hides all the content. diff --git a/docusaurus/docs/Flutter/stream_chat/events.mdx b/docusaurus/docs/Flutter/stream_chat/events.mdx index fb32233f..979fca4c 100644 --- a/docusaurus/docs/Flutter/stream_chat/events.mdx +++ b/docusaurus/docs/Flutter/stream_chat/events.mdx @@ -4,6 +4,8 @@ sidebar_position: 25 title: Events --- +Understand Low Level Messaging Events + Events allow the client to stay up to date with changes to the chat. Examples are a new message, a user's image that updated, a reaction, or a member joining the channel. There are a 4 different type of events. These first 2 types of events you receive if you're connected to Stream, no further action is needed to receive these events: Client events: You always receive these events. diff --git a/docusaurus/docs/Flutter/stream_chat/introduction.mdx b/docusaurus/docs/Flutter/stream_chat/introduction.mdx index 628c8a1b..9681571e 100644 --- a/docusaurus/docs/Flutter/stream_chat/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat/introduction.mdx @@ -3,6 +3,7 @@ id: introduction sidebar_position: 1 title: Introduction --- +Exploring the Basics of the stream_chat package (LLC)

Official Dart Client for Stream Chat

diff --git a/docusaurus/docs/Flutter/stream_chat/invites.mdx b/docusaurus/docs/Flutter/stream_chat/invites.mdx index 430970cd..20f536cc 100644 --- a/docusaurus/docs/Flutter/stream_chat/invites.mdx +++ b/docusaurus/docs/Flutter/stream_chat/invites.mdx @@ -4,6 +4,8 @@ sidebar_position: 11 title: Invites --- +Invite A User To Join A Channel + ### Inviting Users Stream Chat provides the ability to invite users to a channel via the `channel` method with the `invites` array. diff --git a/docusaurus/docs/Flutter/stream_chat/messages.mdx b/docusaurus/docs/Flutter/stream_chat/messages.mdx index 22224fbd..f5608d45 100644 --- a/docusaurus/docs/Flutter/stream_chat/messages.mdx +++ b/docusaurus/docs/Flutter/stream_chat/messages.mdx @@ -4,6 +4,8 @@ sidebar_position: 16 title: Messages --- +Send, Update And Delete Messages + Let's dive right into it, the example below shows how to send a simple message using Stream: ```dart diff --git a/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx b/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx index b7af0413..749bd62a 100644 --- a/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx +++ b/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx @@ -4,6 +4,8 @@ sidebar_position: 23 title: Moderation Tools --- +Moderation Tools For A Better Chat Experience + ### Flag Any user is allowed to flag a message. diff --git a/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx b/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx index 29874091..b7e114d3 100644 --- a/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx +++ b/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx @@ -4,6 +4,8 @@ sidebar_position: 12 title: Muting Channels --- +Muting Channels For Users + Messages added to a channel will not trigger push notifications, nor change the unread count for the users that muted it. By default, mutes stay in place indefinitely until the user removes it; however, you can optionally set an expiration time. diff --git a/docusaurus/docs/Flutter/stream_chat/paginating_channels.mdx b/docusaurus/docs/Flutter/stream_chat/paginating_channels.mdx index bf348aaf..d9c0d461 100644 --- a/docusaurus/docs/Flutter/stream_chat/paginating_channels.mdx +++ b/docusaurus/docs/Flutter/stream_chat/paginating_channels.mdx @@ -4,6 +4,8 @@ sidebar_position: 15 title: Channel Pagination --- +Understanding Pagination For The Channel List + The channel query endpoint allows you to paginate the list of messages, watchers, and members for one channel. To make sure that you are able to retrieve a consistent list of messages, pagination does not work with simple offset/limit parameters but instead, it relies on passing the ID of the messages from the previous page. diff --git a/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx b/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx index 7de1d999..77579e4e 100644 --- a/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx +++ b/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx @@ -4,6 +4,8 @@ sidebar_position: 22 title: Pinned Messages --- +Pinning A Message In A Channel + Pinned messages allow users to highlight important messages, make announcements, or temporarily promote content. Pinning a message is, by default, restricted to certain user roles, but this is flexible. Each channel can have multiple pinned messages and these can be created or updated with or without an expiration. diff --git a/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx b/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx index 3cd4d446..68445bfe 100644 --- a/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx +++ b/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx @@ -4,6 +4,8 @@ sidebar_position: 7 title: Querying Channels --- +More About Fetching A List Of Channels + If you’re building a similar application to Facebook Messenger or Intercom, you’ll want to show a list of Channels. The Chat API supports MongoDB style queries to make this easy to implement. diff --git a/docusaurus/docs/Flutter/stream_chat/querying_users.mdx b/docusaurus/docs/Flutter/stream_chat/querying_users.mdx index 83dc9177..883d4218 100644 --- a/docusaurus/docs/Flutter/stream_chat/querying_users.mdx +++ b/docusaurus/docs/Flutter/stream_chat/querying_users.mdx @@ -3,6 +3,7 @@ id: querying_users sidebar_position: 4 title: Querying Users --- +Querying Users Across Channels The Query Users method allows you to search for users and see if they are online/offline. The example below shows how you can retrieve the details for 3 users in one API call: diff --git a/docusaurus/docs/Flutter/stream_chat/reactions.mdx b/docusaurus/docs/Flutter/stream_chat/reactions.mdx index d2dc114f..5ca279bd 100644 --- a/docusaurus/docs/Flutter/stream_chat/reactions.mdx +++ b/docusaurus/docs/Flutter/stream_chat/reactions.mdx @@ -4,6 +4,8 @@ sidebar_position: 18 title: Reactions --- +Adding And Removing User Reactions + Stream Chat has built-in support for user Reactions. Common examples are likes, comments, loves, etc. Reactions can be customized so that you are able to use any type of reaction your application requires. diff --git a/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx b/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx index 1d1e9b41..76873897 100644 --- a/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx +++ b/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx @@ -4,6 +4,8 @@ sidebar_position: 20 title: Searching Messages --- +Searching For Messages Across Channels + Message search is built-in to the chat API. You can enable and/or disable the search indexing on a per channel type. The command shown below selects the channels in which John is a member. Next, it searches the messages in those channels for the keyword “'supercalifragilisticexpialidocious'”: diff --git a/docusaurus/docs/Flutter/stream_chat/setup.mdx b/docusaurus/docs/Flutter/stream_chat/setup.mdx index b4141769..fe5d9c7b 100644 --- a/docusaurus/docs/Flutter/stream_chat/setup.mdx +++ b/docusaurus/docs/Flutter/stream_chat/setup.mdx @@ -4,3 +4,4 @@ sidebar_position: 2 title: Setup --- +Understanding Setup \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx b/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx index 52d05b2b..d14dc4a8 100644 --- a/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx +++ b/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx @@ -4,6 +4,8 @@ sidebar_position: 21 title: Silent Messages --- +Pushing Silent Messages + Sometimes you want to add system or transactional messages to channels such as: "your ride is waiting for you", "James updated the information for the trip", "You and Jane are now matched" and so on. diff --git a/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx b/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx index 15ca5ad2..4ec75c2e 100644 --- a/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx +++ b/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx @@ -4,6 +4,8 @@ sidebar_position: 14 title: Slow Mode --- +Using Slow Mode For Reduced Message Traffic + Slow mode helps reduce noise on a channel by limiting users to a maximum of 1 message per cooldown interval. The cooldown interval is configurable and can be anything between 1 and 120 seconds. For instance, if you enable slow mode and set the cooldown interval to 30 seconds a user will be able to post at most 1 message every 30 seconds. diff --git a/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx b/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx index 904b9c8d..99c04d56 100644 --- a/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx +++ b/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx @@ -4,6 +4,8 @@ sidebar_position: 19 title: Threads And Replies --- +Creating And Paginating Threads + Threads and replies provide your users with a way to go into more detail about a specific topic. This can be very helpful to keep the conversation organized and reduce noise. diff --git a/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx b/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx index 8323e46a..73c04951 100644 --- a/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx +++ b/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx @@ -4,6 +4,8 @@ sidebar_position: 24 title: Typing Indicators --- +Adding Typing Indicators To Your App + All official SDKs support typing events out of the box and are handled on all channels with the typing_events featured enabled. Typing indicators allow you to show to users who is currently typing in the channel. This feature can be switched on/off on a channel-type basis using the CLI or directly from the Dashboard. diff --git a/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx b/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx index a4de1fcd..0d65110d 100644 --- a/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx +++ b/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx @@ -4,6 +4,8 @@ sidebar_position: 9 title: Updating Channels --- +Exploring Multiple Ways To Update A Channel + There are two ways to update a channel using the Stream API - a partial or full update. A partial update will retain any custom key-value data, whereas a complete update is going to remove any that are unspecified in the API request. diff --git a/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx b/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx index 419ab7e0..2266e8e9 100644 --- a/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx +++ b/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx @@ -4,6 +4,8 @@ sidebar_position: 17 title: Uploading Files --- +Uploading Files And Using A Custom CDN + The `channel.sendImage` and `channel.sendFile` methods make it easy to upload files. This functionality defaults to using the Stream CDN. If you would like, you can easily change the logic to upload to your own CDN of choice. diff --git a/docusaurus/docs/Flutter/stream_chat/user_presence.mdx b/docusaurus/docs/Flutter/stream_chat/user_presence.mdx index f33cb4a3..ce6ff24a 100644 --- a/docusaurus/docs/Flutter/stream_chat/user_presence.mdx +++ b/docusaurus/docs/Flutter/stream_chat/user_presence.mdx @@ -4,6 +4,8 @@ sidebar_position: 5 title: User Presence --- +Check If A User Is Online / Check Last Active + User presence allows you to show when a user was last active and if they are online right now. Whenever you read a user the data will look like this: diff --git a/docusaurus/docs/Flutter/stream_chat/user_types.mdx b/docusaurus/docs/Flutter/stream_chat/user_types.mdx index 7a5721bc..6b273a1b 100644 --- a/docusaurus/docs/Flutter/stream_chat/user_types.mdx +++ b/docusaurus/docs/Flutter/stream_chat/user_types.mdx @@ -4,3 +4,4 @@ sidebar_position: 3 title: User Types --- +Diving Into User Types \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx b/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx index e6cc10bf..c379d324 100644 --- a/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx +++ b/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx @@ -4,6 +4,8 @@ sidebar_position: 8 title: Watching Channels --- +More About Watching A Channel + The call to channel.watch does a few different things in one API call: * It creates the channel if it doesn't exist yet (if this user has the right permissions to create a channel) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx index 5fd9c17f..b9fc7c22 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx @@ -4,9 +4,24 @@ sidebar_position: 1 title: Introduction --- +Understanding The Core Package Of The Flutter SDK + This package provides business logic to fetch common things required for integrating Stream Chat into your application. The core package allows more customisation and hence provides business logic but no UI components. -Please use the stream_chat_flutter package for the full fledged suite of UI components or stream_chat for the low-level client. +Please use the `stream_chat_flutter` package for the full fledged suite of UI components or `stream_chat` for the low-level client. + +### Background + +In the early days of the Flutter SDK, the SDK was only split into the LLC (`stream_chat`) and +the UI package (`stream_chat_flutter`). With this you could use a fully built interface with the UI package +or a fully custom interface with the LLC. However, we soon recognised the need for a third intermediary +package which made tasks like building and modifying a list or channels or messages easy but without +the complexity of using low level components. The Core package (`stream_chat_flutter_core`) is a manifestation +of the same idea and allows you to build an interface with Stream Chat without having to deal with +low level code and architecture as well as implementing your own theme and UI effortlessly. + +We will now explore the components of this intermediary package and understand how it helps you build +the experience you want your users to have. The package primarily contains three types of classes: @@ -18,6 +33,10 @@ The package primarily contains three types of classes: These components allow you to have the maximum and lower-level control of the queries being executed. +In BLoCs, the basic functionalities - such as queries for messages, channels or queries - are bundled up +and passed along down the tree. Using a BLoC allows you to either create your own way to fetch and +build UIs or use an inbuilt Core widget to do the work such as queries, pagination, etc for you. + The BLoCs we provide are: * ChannelsBloc @@ -26,7 +45,13 @@ The BLoCs we provide are: ### Core Components -Core components usually are an easy way to fetch data associated with Stream Chat which are decoupled from UI and often expose UI builders. Data fetching can be controlled with the controllers of the respective core components. +Core components usually are an easy way to fetch data associated with Stream Chat. +Core components use functions exposed by the respective BLoCs (the ChannelListCore uses the ChannelsBloc) +and use the respective controllers for various operations. Unlike heavier components from the UI +package, core components are decoupled from UI and they expose builders instead to help you build +a fully custom interface. + +Data fetching can be controlled with the controllers of the respective core components. * ChannelListCore (Fetch a list of channels) * MessageListCore (Fetch a list of messages from a channel) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx index b4141769..68ca94c9 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx @@ -4,3 +4,5 @@ sidebar_position: 2 title: Setup --- +Understanding Setup + From ce14b958e2de440e1be0a37e8f3971989dfe1ace Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 23 Jun 2021 12:54:12 +0530 Subject: [PATCH 433/599] feat: Added new docs --- .../stream_chat_flutter/adding_local_data_persistence.mdx | 6 ++++++ .../stream_chat_flutter/adding_push_notifcations.mdx | 6 ++++++ .../docs/Flutter/stream_chat_flutter/channel_list_view.mdx | 6 ++++++ .../Flutter/stream_chat_flutter/customising_widgets.mdx | 6 ++++++ .../docs/Flutter/stream_chat_flutter/message_input.mdx | 6 ++++++ .../docs/Flutter/stream_chat_flutter/message_list_view.mdx | 6 ++++++ .../Flutter/stream_chat_flutter/stream_chat_and_theming.mdx | 6 ++++++ .../Flutter/stream_chat_flutter_core/channel_list_core.mdx | 2 +- .../docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx | 2 +- .../Flutter/stream_chat_flutter_core/message_list_core.mdx | 2 +- .../stream_chat_flutter_core/message_search_bloc.mdx | 2 +- .../stream_chat_flutter_core/message_search_list_core.mdx | 2 +- .../Flutter/stream_chat_flutter_core/stream_chat_core.mdx | 2 +- .../docs/Flutter/stream_chat_flutter_core/users_bloc.mdx | 2 +- 14 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/adding_local_data_persistence.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/adding_push_notifcations.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/customising_widgets.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/stream_chat_and_theming.mdx diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/adding_local_data_persistence.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/adding_local_data_persistence.mdx new file mode 100644 index 00000000..10abc63d --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/adding_local_data_persistence.mdx @@ -0,0 +1,6 @@ +--- +id: local_data_persistence +sidebar_position: 9 +title: Adding Local Data Persistence +--- + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/adding_push_notifcations.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/adding_push_notifcations.mdx new file mode 100644 index 00000000..b9d87913 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/adding_push_notifcations.mdx @@ -0,0 +1,6 @@ +--- +id: adding_push_notifications +sidebar_position: 8 +title: Adding Push Notifications +--- + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx new file mode 100644 index 00000000..d1c7ec9e --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx @@ -0,0 +1,6 @@ +--- +id: channel_list_view +sidebar_position: 4 +title: ChannelListView +--- + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/customising_widgets.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/customising_widgets.mdx new file mode 100644 index 00000000..29edd4ca --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/customising_widgets.mdx @@ -0,0 +1,6 @@ +--- +id: customising_widgets +sidebar_position: 7 +title: Customising Widgets +--- + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx new file mode 100644 index 00000000..575a2ee5 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx @@ -0,0 +1,6 @@ +--- +id: message_input +sidebar_position: 6 +title: Message Input +--- + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx new file mode 100644 index 00000000..c68c9bcb --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx @@ -0,0 +1,6 @@ +--- +id: message_list_view +sidebar_position: 5 +title: MessageListView +--- + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/stream_chat_and_theming.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/stream_chat_and_theming.mdx new file mode 100644 index 00000000..5f0350e6 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/stream_chat_and_theming.mdx @@ -0,0 +1,6 @@ +--- +id: stream_chat_and_theming +sidebar_position: 3 +title: StreamChat And Theming +--- + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx index 1ed9078a..1133dcd5 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx @@ -1,6 +1,6 @@ --- id: channel_list_core sidebar_position: 4 -title: Channel List Core +title: ChannelListCore --- diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx index b0014eb0..b44a7df3 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx @@ -1,7 +1,7 @@ --- id: channels_bloc sidebar_position: 7 -title: Channels Bloc +title: ChannelsBloc --- `ChannelsBloc` is a widget dedicated to the management of a channel list with pagination. diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx index 989d0852..08c8ddbb 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx @@ -1,5 +1,5 @@ --- id: message_list_core sidebar_position: 5 -title: Message List Core +title: MessageListCore --- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx index b79e3183..b80dec61 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx @@ -1,7 +1,7 @@ --- id: message_search_list_block sidebar_position: 8 -title: Message Search List Bloc +title: MessageSearchListBloc --- `MessageSearchBloc` is used to manage a list of messages with pagination. diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx index 3b665bdf..95d6c8b4 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx @@ -1,5 +1,5 @@ --- id: message_search_list_core sidebar_position: 6 -title: Message Search List Core +title: MessageSearchListCore --- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx index c0f10e6c..d8ab6653 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx @@ -1,7 +1,7 @@ --- id: stream_chat_core sidebar_position: 3 -title: Stream Chat Core +title: StreamChatCore --- `StreamChatCore` is a version of `StreamChat` found in stream_chat_flutter that is decoupled from diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx index cf971e16..9bff8dba 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx @@ -1,7 +1,7 @@ --- id: users_bloc sidebar_position: 9 -title: Users Bloc +title: UsersBloc --- Widget dedicated to the management of a users list with pagination. From 0e1ed9b66fc276f2df314bec86933ea7e828e818 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 23 Jun 2021 13:18:19 +0530 Subject: [PATCH 434/599] feat: Added new docs --- .../docs/Flutter/basics/introduction.mdx | 2 +- .../stream_chat_flutter_core/introduction.mdx | 8 ++++++++ .../stream_chat_core.mdx | 18 ++++++++++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/Flutter/basics/introduction.mdx b/docusaurus/docs/Flutter/basics/introduction.mdx index b3280a78..e39f82cc 100644 --- a/docusaurus/docs/Flutter/basics/introduction.mdx +++ b/docusaurus/docs/Flutter/basics/introduction.mdx @@ -59,7 +59,7 @@ Note: It is also possible to add more than two people in a distinct channel whic In summary, if you were creating a Whatsapp-like app, the first screen would be a list of channels - which on opening would show a list of messages that were sent by the users in the Channel. -While this is a simplistic overview of the service, thankfully the Flutter SDK handles the UI and more time consuming things (media upload, offline storage, theming, etc.) for us. +While this is a simplistic overview of the service, the Flutter SDK handles the UI and more time consuming things (media upload, offline storage, theming, etc.) for you. Before reading the docs, consider trying our [online API tour](https://getstream.io/chat/get_started/), it is a nice way to learn how the API works. diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx index b9fc7c22..146bfa62 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx @@ -63,6 +63,14 @@ Data fetching can be controlled with the controllers of the respective core comp Core Controllers are supplied to respective CoreList widgets which allows reloading and pagination of data whenever needed. +Unlike the UI package, the Core package allows a fully custom user interface built with the data. This +in turn provides a few challenges: we do not know implicitly when to paginate your list or reload your data. + +While this is handled out of the box in the UI package since the List implementation is inbuilt, a controller +needs to be used in the core package notifying the core components to reload or paginate the data existing +currently. For this, each core component has a respective controller which you can use to call the +specific function (reload / paginate) whenever such an event is triggered through / needed in your UI. + * ChannelListController * MessageListController * MessageSearchListController diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx index d8ab6653..91d7cf65 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx @@ -7,7 +7,21 @@ title: StreamChatCore `StreamChatCore` is a version of `StreamChat` found in stream_chat_flutter that is decoupled from theme and initialisations. -Widget used to provide information about the chat to the widget tree. +`StreamChatCore` is used to provide information about the chat client to the widget tree. This Widget is used to react to life cycle changes and system updates. When the app goes into the background, the websocket connection is kept -alive for two minutes before being terminated. \ No newline at end of file +alive for two minutes before being terminated. + +Like the StreamChat widget in the higher level UI package, the StreamChatCore widget should +be on the top level before using any Stream functionality: + +```dart +return MaterialApp( + title: 'Stream Chat Core Example', + home: HomeScreen(), + builder: (context, child) => StreamChatCore( + client: client, + child: child!, + ), + ); +``` From bb642842f9bfa039eaea8c7bb39402e31291acdd Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 23 Jun 2021 12:22:17 +0200 Subject: [PATCH 435/599] fix message widget not update when message is updated --- .../lib/src/message_widget.dart | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 753f201c..a976547f 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -413,39 +413,37 @@ class _MessageWidgetState extends State bool get showTimeStamp => widget.showTimestamp; - late final bool isMessageRead = widget.readList?.isNotEmpty == true; + bool get isMessageRead => widget.readList?.isNotEmpty == true; bool get showInChannel => widget.showInChannelIndicator; bool get hasQuotedMessage => widget.message.quotedMessage != null; - late final bool isSendFailed = - widget.message.status == MessageSendingStatus.failed; + bool get isSendFailed => widget.message.status == MessageSendingStatus.failed; - late final bool isUpdateFailed = + bool get isUpdateFailed => widget.message.status == MessageSendingStatus.failed_update; - late final bool isDeleteFailed = + bool get isDeleteFailed => widget.message.status == MessageSendingStatus.failed_delete; - late final bool isFailedState = - isSendFailed || isUpdateFailed || isDeleteFailed; + bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed; - late final bool isGiphy = + bool get isGiphy => widget.message.attachments.any((element) => element.type == 'giphy') == - true; - - late final bool isOnlyEmoji = widget.message.text?.isOnlyEmoji == true; - - late final bool hasNonUrlAttachments = widget.message.attachments - .where((it) => it.ogScrapeUrl == null) - .isNotEmpty == true; - late final bool hasUrlAttachments = + bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true; + + bool get hasNonUrlAttachments => widget.message.attachments + .where((it) => it.ogScrapeUrl == null) + .isNotEmpty; + + bool get hasUrlAttachments => widget.message.attachments.any((it) => it.ogScrapeUrl != null) == true; - late final bool showBottomRow = showThreadReplyIndicator || + bool get showBottomRow => + showThreadReplyIndicator || showUsername || showTimeStamp || showInChannel || @@ -1177,7 +1175,7 @@ class _MessageWidgetState extends State ); } - late final bool isPinned = widget.message.pinned; + bool get isPinned => widget.message.pinned; Color? _getBackgroundColor() { if (hasQuotedMessage) { From 151054b2c662922238fc8258b8196de695c93245 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 23 Jun 2021 12:30:10 +0200 Subject: [PATCH 436/599] null check --- packages/stream_chat_flutter/lib/src/message_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 962d237b..39211116 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -798,7 +798,7 @@ class MessageInputState extends State { setState(() { _commandEnabled = true; }); - _commandsOverlay!.remove(); + _commandsOverlay?.remove(); _commandsOverlay = null; } else { _commandsOverlay = _buildCommandsOverlayEntry(); From db801f4cdbc9626a3128c518b0e1e04860d161d3 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 23 Jun 2021 17:18:48 +0530 Subject: [PATCH 437/599] add support for messageListView header/footer Signed-off-by: Sahil Kumar --- .../lib/src/message_list_view.dart | 110 +++++++++++++----- 1 file changed, 80 insertions(+), 30 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index aae9d193..c7966c3d 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1,3 +1,4 @@ +// ignore_for_file: lines_longer_than_80_chars import 'dart:async'; import 'dart:math'; @@ -147,6 +148,8 @@ class MessageListView extends StatefulWidget { this.messageHighlightColor, this.onShowMessage, this.showConnectionStateTile = false, + this.headerBuilder, + this.footerBuilder, this.loadingBuilder, this.emptyBuilder, this.systemMessageBuilder, @@ -235,6 +238,12 @@ class MessageListView extends StatefulWidget { /// Function called when messages are fetched final Widget Function(BuildContext, List)? messageListBuilder; + /// + final WidgetBuilder? headerBuilder; + + /// + final WidgetBuilder? footerBuilder; + /// Function used to build a loading widget final WidgetBuilder? loadingBuilder; @@ -407,6 +416,12 @@ class _MessageListViewState extends State { break; } + final itemCount = messages.length + // total messages + 2 + // top + bottom loading indicator + 2 + // header + footer + 1 // parent message + ; + return InfoTile( showMessage: widget.showConnectionStateTile && showStatus, tileAnchor: Alignment.topCenter, @@ -448,16 +463,49 @@ class _MessageListViewState extends State { itemScrollController: _scrollController, reverse: true, addAutomaticKeepAlives: false, - itemCount: - messages.length + 2 + (_isThreadConversation ? 1 : 0), + itemCount: itemCount, + + // Item Count -> 8 (1 parent, 2 header+footer, 2 top+bottom, 3 messages) + // eg: |Type| rev(|Index(item)|) rev(|Index(separator)|) |Index(item)| |Index(separator)| + // ParentMessage -> 7 (count-1) + // Separator(ThreadSeparator) -> 6 (count-2) + // Header -> 6 (count-2) + // Separator(Header -> 8??52) -> 5 (count-3) + // TopLoader -> 5 (count-3) + // Separator(2||8) -> 4 (count-4) + // Message -> 4 (count-4) + // Separator(2||8) -> 3 (count-5) + // Message -> 3 (count-5) + // Separator(2||8) -> 2 (count-6) + // Message -> 2 (count-6) + // Separator(2||8) -> 1 (count-7) + // BottomLoader -> 1 (count-7) + // Separator(Footer -> 8??30) -> 0 (count-8) + // Footer -> 0 (count-8) + separatorBuilder: (context, i) { - if (i == messages.length) return const Offstage(); - if (i == 0) return const SizedBox(height: 30); - if (i == messages.length + 1) { + if (i == itemCount - 2) { + if (widget.parentMessage == null) { + return const Offstage(); + } return _buildThreadSeparator(); } + if (i == itemCount - 3) { + if (widget.headerBuilder == null) { + return const SizedBox(height: 52); + } + return const SizedBox(height: 8); + } + if (i == 0) { + if (widget.footerBuilder == null) { + return const SizedBox(height: 30); + } + return const SizedBox(height: 8); + } - final message = messages[i]; + if (i == 1 || i == itemCount - 4) return const Offstage(); + + final message = messages[i - 2]; final nextMessage = messages[i - 1]; if (!Jiffy(message.createdAt.toLocal()).isSame( nextMessage.createdAt.toLocal(), @@ -494,40 +542,51 @@ class _MessageListViewState extends State { return const SizedBox(height: 2); }, itemBuilder: (context, i) { - if (i == messages.length + 2) { - if (widget.parentMessageBuilder != null) { - return widget.parentMessageBuilder!( - context, - widget.parentMessage, - ); - } else { - return buildParentMessage(widget.parentMessage!); - } + if (i == itemCount - 1) { + if (widget.parentMessage == null) return const Offstage(); + return widget.parentMessageBuilder?.call( + context, + widget.parentMessage, + ) ?? + buildParentMessage(widget.parentMessage!); } - if (i == messages.length + 1) { + + if (i == itemCount - 2) { + return widget.headerBuilder?.call(context) ?? + const Offstage(); + } + + if (i == itemCount - 3) { return _buildLoadingIndicator( streamChannel!, QueryDirection.top, ); } - if (i == 0) { + + if (i == 1) { return _buildLoadingIndicator( streamChannel!, QueryDirection.bottom, ); } - final message = messages[i - 1]; + + if (i == 0) { + return widget.footerBuilder?.call(context) ?? + const Offstage(); + } + + final message = messages[i - 2]; Widget messageWidget; - if (i == 1) { + if (i == 2) { messageWidget = _buildBottomMessage( context, message, messages, streamChannel!, ); - } else if (i == messages.length - 1) { + } else if (i == messages.length - 4) { messageWidget = _buildTopMessage( context, message, @@ -578,7 +637,6 @@ class _MessageListViewState extends State { child: Padding( padding: const EdgeInsets.all(8), child: Text( - // ignore: lines_longer_than_80_chars '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', textAlign: TextAlign.center, style: _streamTheme.channelTheme.channelHeaderTheme.subtitle, @@ -1238,15 +1296,7 @@ class _LoadingIndicator extends StatelessWidget { ), ), builder: (context, data) { - if (!data) { - if (!isThreadConversation && direction == QueryDirection.top) { - return const SizedBox( - height: 52, - width: double.infinity, - ); - } - return const Offstage(); - } + if (!data) return const Offstage(); return const Center( child: Padding( padding: EdgeInsets.all(8), From 3b39e84e8fccaa98788b76b4f429a85308939df2 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 23 Jun 2021 17:44:07 +0530 Subject: [PATCH 438/599] fix bottomMessageIndex Signed-off-by: Sahil Kumar --- .../lib/src/message_list_view.dart | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index c7966c3d..b5008054 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -575,24 +575,26 @@ class _MessageListViewState extends State { const Offstage(); } - final message = messages[i - 2]; + final topMessageIndex = itemCount - 4; + const bottomMessageIndex = 2; + final message = messages[i - 2]; Widget messageWidget; - if (i == 2) { - messageWidget = _buildBottomMessage( - context, - message, - messages, - streamChannel!, - ); - } else if (i == messages.length - 4) { + if (i == topMessageIndex) { messageWidget = _buildTopMessage( context, message, messages, streamChannel, ); + } else if (i == bottomMessageIndex) { + messageWidget = _buildBottomMessage( + context, + message, + messages, + streamChannel!, + ); } else { if (widget.messageBuilder != null) { messageWidget = Builder( From 2ad71f16e317280b252d8f4adfe77cff0f527755 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 23 Jun 2021 19:24:37 +0200 Subject: [PATCH 439/599] fix messagelistview not rendering when user is not a member --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 976aacb0..7b9a672e 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:math'; +import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -813,7 +814,7 @@ class _MessageListViewState extends State { final currentUser = StreamChat.of(context).user; final members = StreamChannel.of(context).channel.state?.members ?? []; final currentUserMember = - members.firstWhere((e) => e.user!.id == currentUser!.id); + members.firstWhereOrNull((e) => e.user!.id == currentUser!.id); return MessageWidget( showReplyMessage: false, @@ -866,7 +867,8 @@ class _MessageListViewState extends State { textBuilder: widget.textBuilder, usernameBuilder: widget.usernameBuilder, onLinkTap: widget.onLinkTap, - showPinButton: widget.pinPermissions.contains(currentUserMember.role), + showPinButton: currentUserMember != null && + widget.pinPermissions.contains(currentUserMember.role), ); } From 2816124c553e10e5b0e6ada087498ed5a2d357a9 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 24 Jun 2021 18:19:52 +0530 Subject: [PATCH 440/599] fix floatingDate, messageWidgetBuilder index Signed-off-by: Sahil Kumar --- .../lib/src/message_list_view.dart | 90 +++++++++---------- .../lib/src/better_stream_builder.dart | 7 +- 2 files changed, 46 insertions(+), 51 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index b5008054..3194827d 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -396,6 +396,12 @@ class _MessageListViewState extends State { _messageListLength = newMessagesListLength; + final itemCount = messages.length + // total messages + 2 + // top + bottom loading indicator + 2 + // header + footer + 1 // parent message + ; + return Stack( alignment: Alignment.center, children: [ @@ -416,12 +422,6 @@ class _MessageListViewState extends State { break; } - final itemCount = messages.length + // total messages - 2 + // top + bottom loading indicator - 2 + // header + footer - 1 // parent message - ; - return InfoTile( showMessage: widget.showConnectionStateTile && showStatus, tileAnchor: Alignment.topCenter, @@ -470,15 +470,15 @@ class _MessageListViewState extends State { // ParentMessage -> 7 (count-1) // Separator(ThreadSeparator) -> 6 (count-2) // Header -> 6 (count-2) - // Separator(Header -> 8??52) -> 5 (count-3) + // Separator(Header -> 8??T -> 0||52) -> 5 (count-3) // TopLoader -> 5 (count-3) - // Separator(2||8) -> 4 (count-4) + // Separator(0) -> 4 (count-4) // Message -> 4 (count-4) // Separator(2||8) -> 3 (count-5) // Message -> 3 (count-5) // Separator(2||8) -> 2 (count-6) // Message -> 2 (count-6) - // Separator(2||8) -> 1 (count-7) + // Separator(0) -> 1 (count-7) // BottomLoader -> 1 (count-7) // Separator(Footer -> 8??30) -> 0 (count-8) // Footer -> 0 (count-8) @@ -492,6 +492,7 @@ class _MessageListViewState extends State { } if (i == itemCount - 3) { if (widget.headerBuilder == null) { + if (_isThreadConversation) return const Offstage(); return const SizedBox(height: 52); } return const SizedBox(height: 8); @@ -505,8 +506,8 @@ class _MessageListViewState extends State { if (i == 1 || i == itemCount - 4) return const Offstage(); - final message = messages[i - 2]; - final nextMessage = messages[i - 1]; + final message = messages[i - 1]; + final nextMessage = messages[i - 2]; if (!Jiffy(message.createdAt.toLocal()).isSame( nextMessage.createdAt.toLocal(), Units.DAY, @@ -575,8 +576,9 @@ class _MessageListViewState extends State { const Offstage(); } - final topMessageIndex = itemCount - 4; - const bottomMessageIndex = 2; + final topMessageIndex = itemCount - + 4; // 7 -> parent // 6 -> header // 5 -> loader + const bottomMessageIndex = 2; // 1 -> loader // 0 -> footer final message = messages[i - 2]; Widget messageWidget; @@ -587,6 +589,7 @@ class _MessageListViewState extends State { message, messages, streamChannel, + i, ); } else if (i == bottomMessageIndex) { messageWidget = _buildBottomMessage( @@ -594,6 +597,7 @@ class _MessageListViewState extends State { message, messages, streamChannel!, + i, ); } else { if (widget.messageBuilder != null) { @@ -621,7 +625,8 @@ class _MessageListViewState extends State { }, ), if (widget.showScrollToBottom) _buildScrollToBottom(), - if (widget.showFloatingDateDivider) _buildFloatingDateDivider(), + if (widget.showFloatingDateDivider) + _buildFloatingDateDivider(itemCount), ], ); } @@ -647,42 +652,34 @@ class _MessageListViewState extends State { ); } - Positioned _buildFloatingDateDivider() => Positioned( + Positioned _buildFloatingDateDivider(int itemCount) => Positioned( top: 20, child: BetterStreamBuilder>( initialData: _itemPositionListener.itemPositions.value, stream: _itemPositionStream, comparator: (a, b) { - if (a == null) { + if (a == null || b == null) { return false; } - final aTop = _getTopElement(a)?.index; - final bTop = _getTopElement(b)?.index; + final aTop = _getTopElementIndex(a); + final bTop = _getTopElementIndex(b); return aTop == bTop; }, builder: (context, values) { - final items = _itemPositionListener.itemPositions.value; - if (items.isEmpty || messages.isEmpty) { - return const SizedBox(); + if (values.isEmpty || messages.isEmpty) { + return const Offstage(); } - var index = _getTopElement(values)?.index; + final index = _getTopElementIndex(values); - if (index == null || index > messages.length) { - return const SizedBox(); - } - - if (index == messages.length) { - index = max(index - 1, 0); + if (index == null || index <= 2 || index >= itemCount - 3) { + return const Offstage(); } + final message = messages[index - 2]; return widget.dateDividerBuilder != null - ? widget.dateDividerBuilder!( - messages[index].createdAt.toLocal(), - ) - : DateDivider( - dateTime: messages[index].createdAt.toLocal(), - ); + ? widget.dateDividerBuilder!(message.createdAt.toLocal()) + : DateDivider(dateTime: message.createdAt.toLocal()); }, ), ); @@ -691,16 +688,13 @@ class _MessageListViewState extends State { StreamChannelState? channel, QueryDirection direction) => _messageListController.paginateData!(direction: direction); - ItemPosition? _getTopElement(Iterable values) { - final inView = - values.where((ItemPosition position) => position.itemLeadingEdge < 0.9); - - if (inView.isEmpty) { - return null; - } - - return inView.reduce((ItemPosition max, ItemPosition position) => - position.itemLeadingEdge > max.itemLeadingEdge ? position : max); + int? _getTopElementIndex(Iterable values) { + final inView = values.where((position) => position.itemLeadingEdge < 1); + if (inView.isEmpty) return null; + return inView + .reduce((max, position) => + position.itemLeadingEdge > max.itemLeadingEdge ? position : max) + .index; } Widget _buildScrollToBottom() => StreamBuilder>( @@ -796,6 +790,7 @@ class _MessageListViewState extends State { Message message, List messages, StreamChannelState? streamChannel, + int index, ) { Widget messageWidget; if (widget.messageBuilder != null) { @@ -813,7 +808,7 @@ class _MessageListViewState extends State { ), ); } else { - messageWidget = buildMessage(message, messages, messages.length - 1); + messageWidget = buildMessage(message, messages, index); } return messageWidget; } @@ -823,6 +818,7 @@ class _MessageListViewState extends State { Message message, List messages, StreamChannelState streamChannel, + int index, ) { Widget messageWidget; if (widget.messageBuilder != null) { @@ -840,7 +836,7 @@ class _MessageListViewState extends State { ), ); } else { - messageWidget = buildMessage(message, messages, 0); + messageWidget = buildMessage(message, messages, index); } return VisibilityDetector( @@ -952,7 +948,7 @@ class _MessageListViewState extends State { final userId = StreamChat.of(context).user!.id; final isMyMessage = message.user!.id == userId; - final nextMessage = index - 2 >= 0 ? messages[index - 2] : null; + final nextMessage = index - 3 >= 0 ? messages[index - 3] : null; final isNextUserSame = nextMessage != null && message.user!.id == nextMessage.user!.id; 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 ddf342dc..871a8a1d 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 @@ -25,7 +25,7 @@ class BetterStreamBuilder extends StatefulWidget { final T initialData; /// Comparator used to check if the new data is different than the last one - final bool Function(T?, T)? comparator; + final bool Function(T?, T?)? comparator; /// Builder that builds based on the new snapshot final Widget Function(BuildContext context, T data) builder; @@ -96,9 +96,8 @@ class _BetterStreamBuilderState extends State> { void _onEvent(T event) { _lastError = null; - final isEqual = widget.comparator != null - ? widget.comparator!(_lastEvent, event) - : event == _lastEvent; + final isEqual = + widget.comparator?.call(_lastEvent, event) ?? event == _lastEvent; if (!isEqual) { if (mounted) { setState(() {}); From c169b887af526f4d097e8fc5bf2dc7bc004b6e7d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 24 Jun 2021 18:24:56 +0530 Subject: [PATCH 441/599] add docs Signed-off-by: Sahil Kumar --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 3194827d..85c441cd 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -238,10 +238,10 @@ class MessageListView extends StatefulWidget { /// Function called when messages are fetched final Widget Function(BuildContext, List)? messageListBuilder; - /// + /// Function used to build a header widget final WidgetBuilder? headerBuilder; - /// + /// Function used to build a footer widget final WidgetBuilder? footerBuilder; /// Function used to build a loading widget From 5325b7cb5ed55f72f53fcac81cfacbf4d9cafd93 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 24 Jun 2021 18:25:59 +0530 Subject: [PATCH 442/599] fix analyzer warning Signed-off-by: Sahil Kumar --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 85c441cd..52ea3ab9 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1,6 +1,5 @@ // ignore_for_file: lines_longer_than_80_chars import 'dart:async'; -import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; From 87568235d560b5ca2ef127df2a8c522548d98228 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 25 Jun 2021 10:22:49 +0200 Subject: [PATCH 443/599] add copyWith to Messagewidget --- .../lib/src/message_widget.dart | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index a976547f..cc0f0d3c 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -397,6 +397,117 @@ class MessageWidget extends StatefulWidget { /// Customize onTap on attachment final void Function(Message message, Attachment attachment)? onAttachmentTap; + /// Creates a copy of [MessageWidget] with specified attributes overridden. + MessageWidget copyWith({ + Key? key, + void Function(User)? onMentionTap, + void Function(Message)? onThreadTap, + void Function(Message)? onReplyTap, + Widget Function(BuildContext, Message)? editMessageInputBuilder, + Widget Function(BuildContext, Message)? textBuilder, + Widget Function(BuildContext, Message)? usernameBuilder, + void Function(BuildContext, Message)? onMessageActions, + Message? message, + MessageTheme? messageTheme, + bool? reverse, + ShapeBorder? shape, + ShapeBorder? attachmentShape, + BorderSide? borderSide, + BorderSide? attachmentBorderSide, + BorderRadiusGeometry? borderRadiusGeometry, + BorderRadiusGeometry? attachmentBorderRadiusGeometry, + EdgeInsetsGeometry? padding, + EdgeInsets? textPadding, + EdgeInsetsGeometry? attachmentPadding, + DisplayWidget? showUserAvatar, + bool? showSendingIndicator, + bool? showReactions, + bool? allRead, + bool? showThreadReplyIndicator, + bool? showInChannelIndicator, + void Function(User)? onUserAvatarTap, + void Function(String)? onLinkTap, + bool? showReactionPickerIndicator, + List? readList, + ShowMessageCallback? onShowMessage, + ValueChanged? onReturnAction, + bool? showUsername, + bool? showTimestamp, + bool? showReplyMessage, + bool? showThreadReplyMessage, + bool? showEditMessage, + bool? showCopyMessage, + bool? showDeleteMessage, + bool? showResendMessage, + bool? showFlagButton, + bool? showPinButton, + bool? showPinHighlight, + Map? customAttachmentBuilders, + bool? translateUserAvatar, + OnQuotedMessageTap? onQuotedMessageTap, + void Function(Message)? onMessageTap, + List? customActions, + void Function(Message message, Attachment attachment)? onAttachmentTap, + }) => + MessageWidget( + key: key ?? this.key, + onMentionTap: onMentionTap ?? this.onMentionTap, + onThreadTap: onThreadTap ?? this.onThreadTap, + onReplyTap: onReplyTap ?? this.onReplyTap, + editMessageInputBuilder: + editMessageInputBuilder ?? this.editMessageInputBuilder, + textBuilder: textBuilder ?? this.textBuilder, + usernameBuilder: usernameBuilder ?? this.usernameBuilder, + onMessageActions: onMessageActions ?? this.onMessageActions, + message: message ?? this.message, + messageTheme: messageTheme ?? this.messageTheme, + reverse: reverse ?? this.reverse, + shape: shape ?? this.shape, + attachmentShape: attachmentShape ?? this.attachmentShape, + borderSide: borderSide ?? this.borderSide, + attachmentBorderSide: attachmentBorderSide ?? this.attachmentBorderSide, + borderRadiusGeometry: borderRadiusGeometry ?? this.borderRadiusGeometry, + attachmentBorderRadiusGeometry: attachmentBorderRadiusGeometry ?? + this.attachmentBorderRadiusGeometry, + padding: padding ?? this.padding, + textPadding: textPadding ?? this.textPadding, + attachmentPadding: attachmentPadding ?? this.attachmentPadding, + showUserAvatar: showUserAvatar ?? this.showUserAvatar, + showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator, + showReactions: showReactions ?? this.showReactions, + allRead: allRead ?? this.allRead, + showThreadReplyIndicator: + showThreadReplyIndicator ?? this.showThreadReplyIndicator, + showInChannelIndicator: + showInChannelIndicator ?? this.showInChannelIndicator, + onUserAvatarTap: onUserAvatarTap ?? this.onUserAvatarTap, + onLinkTap: onLinkTap ?? this.onLinkTap, + showReactionPickerIndicator: + showReactionPickerIndicator ?? this.showReactionPickerIndicator, + readList: readList ?? this.readList, + onShowMessage: onShowMessage ?? this.onShowMessage, + onReturnAction: onReturnAction ?? this.onReturnAction, + showUsername: showUsername ?? this.showUsername, + showTimestamp: showTimestamp ?? this.showTimestamp, + showReplyMessage: showReplyMessage ?? this.showReplyMessage, + showThreadReplyMessage: + showThreadReplyMessage ?? this.showThreadReplyMessage, + showEditMessage: showEditMessage ?? this.showEditMessage, + showCopyMessage: showCopyMessage ?? this.showCopyMessage, + showDeleteMessage: showDeleteMessage ?? this.showDeleteMessage, + showResendMessage: showResendMessage ?? this.showResendMessage, + showFlagButton: showFlagButton ?? this.showFlagButton, + showPinButton: showPinButton ?? this.showPinButton, + showPinHighlight: showPinHighlight ?? this.showPinHighlight, + customAttachmentBuilders: + customAttachmentBuilders ?? this.attachmentBuilders, + translateUserAvatar: translateUserAvatar ?? this.translateUserAvatar, + onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap, + onMessageTap: onMessageTap ?? this.onMessageTap, + customActions: customActions ?? this.customActions, + onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap, + ); + @override _MessageWidgetState createState() => _MessageWidgetState(); } From dd7fafb3cff20bbbd263bff4a5adeab0adf95005 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 25 Jun 2021 10:39:03 +0200 Subject: [PATCH 444/599] use MessageWidget.copyWith in reactions modal --- .../lib/src/message_reactions_modal.dart | 60 ++----------------- .../lib/src/message_widget.dart | 33 ++++++---- 2 files changed, 27 insertions(+), 66 deletions(-) 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 842fe79d..0292a05a 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -1,7 +1,6 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart'; @@ -15,17 +14,16 @@ class MessageReactionsModal extends StatelessWidget { const MessageReactionsModal({ Key? key, required this.message, + required this.messageWidget, required this.messageTheme, this.showReactions = true, - this.messageShape, - this.attachmentShape, this.reverse = false, - this.showUserAvatar = DisplayWidget.show, this.onUserAvatarTap, - this.attachmentBorderRadiusGeometry, - this.textBuilder, }) : super(key: key); + /// Widget that shows the message + final Widget messageWidget; + /// Message to display reactions of final Message message; @@ -38,24 +36,9 @@ class MessageReactionsModal extends StatelessWidget { /// Flag to show reactions on message final bool showReactions; - /// Enum to change user avatar config - final DisplayWidget showUserAvatar; - - /// [ShapeBorder] to apply to message - final ShapeBorder? messageShape; - - /// [ShapeBorder] to apply to attachment - final ShapeBorder? attachmentShape; - /// Callback when user avatar is tapped final void Function(User)? onUserAvatarTap; - /// [BorderRadius] to apply to attachments - final BorderRadius? attachmentBorderRadiusGeometry; - - /// Customize the MessageWidget textBuilder - final Widget Function(BuildContext context, Message message)? textBuilder; - @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; @@ -77,8 +60,6 @@ class MessageReactionsModal extends StatelessWidget { final divFactor = message.attachments.isNotEmpty == true ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); - final hasFileAttachment = - message.attachments.any((it) => it.type == 'file') == true; final numberOfReactions = StreamChatTheme.of(context).reactionIcons.length; final shiftFactor = @@ -110,38 +91,7 @@ class MessageReactionsModal extends StatelessWidget { ), const SizedBox(height: 8), IgnorePointer( - child: MessageWidget( - key: const Key('MessageWidget'), - reverse: reverse, - message: message.copyWith( - text: message.text!.length > 200 - ? '${message.text!.substring(0, 200)}...' - : message.text, - ), - messageTheme: messageTheme, - showReactions: false, - showUsername: false, - showUserAvatar: showUserAvatar, - showTimestamp: false, - translateUserAvatar: false, - showSendingIndicator: false, - shape: messageShape, - attachmentShape: attachmentShape, - padding: const EdgeInsets.all(0), - attachmentBorderRadiusGeometry: attachmentBorderRadiusGeometry - ?.mirrorBorderIfReversed(reverse: !reverse), - attachmentPadding: EdgeInsets.all( - hasFileAttachment ? 4 : 2, - ), - textPadding: EdgeInsets.symmetric( - vertical: 8, - horizontal: message.text!.isOnlyEmoji ? 0 : 16.0, - ), - showReactionPickerIndicator: showReactions && - (message.status == MessageSendingStatus.sent), - textBuilder: textBuilder, - showPinHighlight: false, - ), + child: messageWidget, ), if (message.latestReactions?.isNotEmpty == true) ...[ const SizedBox(height: 8), diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index cc0f0d3c..aea2f8b9 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -500,7 +500,7 @@ class MessageWidget extends StatefulWidget { showPinButton: showPinButton ?? this.showPinButton, showPinHighlight: showPinHighlight ?? this.showPinHighlight, customAttachmentBuilders: - customAttachmentBuilders ?? this.attachmentBuilders, + customAttachmentBuilders ?? attachmentBuilders, translateUserAvatar: translateUserAvatar ?? this.translateUserAvatar, onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap, onMessageTap: onMessageTap ?? this.onMessageTap, @@ -1075,18 +1075,29 @@ class _MessageWidgetState extends State builder: (context) => StreamChannel( channel: channel, child: MessageReactionsModal( - textBuilder: widget.textBuilder, - attachmentBorderRadiusGeometry: - widget.attachmentBorderRadiusGeometry as BorderRadius?, - showUserAvatar: - widget.message.user!.id == channel.client.state.user!.id - ? DisplayWidget.gone - : DisplayWidget.show, + messageWidget: widget.copyWith( + key: const Key('MessageWidget'), + message: widget.message.copyWith( + text: widget.message.text!.length > 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.user!.id + ? DisplayWidget.gone + : DisplayWidget.show, + ), onUserAvatarTap: widget.onUserAvatarTap, messageTheme: widget.messageTheme, - messageShape: widget.shape ?? _getDefaultShape(context), - attachmentShape: - widget.attachmentShape ?? _getDefaultAttachmentShape(context), reverse: widget.reverse, message: widget.message, showReactions: widget.showReactions, From 1ceabb90f1f44e87163783984d2aa519d8e18227 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 25 Jun 2021 10:44:28 +0200 Subject: [PATCH 445/599] use MessageWidget.copyWith in actions modal --- .../lib/src/message_actions_modal.dart | 67 ++----------------- .../lib/src/message_widget.dart | 53 ++++++--------- 2 files changed, 26 insertions(+), 94 deletions(-) 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 6f57e7df..296b1e59 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -15,6 +15,7 @@ class MessageActionsModal extends StatefulWidget { const MessageActionsModal({ Key? key, required this.message, + required this.messageWidget, required this.messageTheme, this.showReactions = true, this.showDeleteMessage = true, @@ -27,18 +28,15 @@ class MessageActionsModal extends StatefulWidget { this.showThreadReplyMessage = true, this.showFlagButton = true, this.showPinButton = true, - this.showPinHighlight = false, - this.showUserAvatar = DisplayWidget.show, this.editMessageInputBuilder, - this.messageShape, - this.attachmentShape, this.reverse = false, this.customActions = const [], - this.attachmentBorderRadiusGeometry, this.onCopyTap, - this.textBuilder, }) : super(key: key); + /// Widget that shows the message + final Widget messageWidget; + /// Builder for edit message final Widget Function(BuildContext, Message)? editMessageInputBuilder; @@ -84,30 +82,12 @@ class MessageActionsModal extends StatefulWidget { /// Flag for showing pin action final bool showPinButton; - /// Display Pin Highlight - final bool showPinHighlight; - /// Flag for reversing message final bool reverse; - /// [ShapeBorder] to apply to the widget - final ShapeBorder? messageShape; - - /// [ShapeBorder] to apply to attachment - final ShapeBorder? attachmentShape; - - /// Enum for displaying user avatar - final DisplayWidget showUserAvatar; - - /// [BorderRadius] for attachment border - final BorderRadius? attachmentBorderRadiusGeometry; - /// List of custom actions final List customActions; - /// Customize the MessageWidget textBuilder - final Widget Function(BuildContext context, Message message)? textBuilder; - @override _MessageActionsModalState createState() => _MessageActionsModalState(); } @@ -142,9 +122,6 @@ class _MessageActionsModalState extends State { ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); - final hasFileAttachment = - widget.message.attachments.any((it) => it.type == 'file') == true; - final streamChatThemeData = StreamChatTheme.of(context); final numberOfReactions = streamChatThemeData.reactionIcons.length; @@ -178,41 +155,7 @@ class _MessageActionsModalState extends State { ), const SizedBox(height: 8), IgnorePointer( - child: MessageWidget( - key: const Key('MessageWidget'), - reverse: widget.reverse, - attachmentBorderRadiusGeometry: widget - .attachmentBorderRadiusGeometry - ?.mirrorBorderIfReversed(reverse: !widget.reverse), - message: widget.message.copyWith( - text: widget.message.text!.length > 200 - // ignore: lines_longer_than_80_chars - ? '${widget.message.text!.substring(0, 200)}...' - : widget.message.text, - ), - messageTheme: widget.messageTheme, - showReactions: false, - showUsername: false, - showReplyMessage: false, - showUserAvatar: widget.showUserAvatar, - attachmentPadding: EdgeInsets.all( - hasFileAttachment ? 4 : 2, - ), - showTimestamp: false, - translateUserAvatar: false, - padding: const EdgeInsets.all(0), - textPadding: EdgeInsets.symmetric( - vertical: 8, - horizontal: widget.message.text!.isOnlyEmoji ? 0 : 16.0, - ), - showReactionPickerIndicator: widget.showReactions && - (widget.message.status == MessageSendingStatus.sent), - showSendingIndicator: false, - shape: widget.messageShape, - attachmentShape: widget.attachmentShape, - showPinHighlight: false, - textBuilder: widget.textBuilder, - ), + child: widget.messageWidget, ), const SizedBox(height: 8), Padding( diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index aea2f8b9..49788f80 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -1023,19 +1023,30 @@ class _MessageWidgetState extends State builder: (context) => StreamChannel( channel: channel, child: MessageActionsModal( - textBuilder: widget.textBuilder, + messageWidget: widget.copyWith( + key: const Key('MessageWidget'), + message: widget.message.copyWith( + text: widget.message.text!.length > 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.user!.id + ? DisplayWidget.gone + : DisplayWidget.show, + ), onCopyTap: (message) => Clipboard.setData(ClipboardData(text: message.text)), - attachmentBorderRadiusGeometry: - widget.attachmentBorderRadiusGeometry as BorderRadius?, - showUserAvatar: - widget.message.user!.id == channel.client.state.user!.id - ? DisplayWidget.gone - : DisplayWidget.show, messageTheme: widget.messageTheme, - messageShape: widget.shape ?? _getDefaultShape(context), - attachmentShape: widget.attachmentShape ?? - _getDefaultAttachmentShape(context), reverse: widget.reverse, showDeleteMessage: widget.showDeleteMessage || isDeleteFailed, message: widget.message, @@ -1106,28 +1117,6 @@ class _MessageWidgetState extends State ); } - ShapeBorder _getDefaultAttachmentShape(BuildContext context) { - final hasFiles = - widget.message.attachments.any((it) => it.type == 'file') == true; - return RoundedRectangleBorder( - side: hasFiles - ? widget.attachmentBorderSide ?? - BorderSide( - color: _streamChatTheme.colorTheme.greyWhisper, - ) - : BorderSide.none, - borderRadius: widget.attachmentBorderRadiusGeometry ?? BorderRadius.zero, - ); - } - - ShapeBorder _getDefaultShape(BuildContext context) => RoundedRectangleBorder( - side: widget.borderSide ?? - BorderSide( - color: _streamChatTheme.colorTheme.greyWhisper, - ), - borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero, - ); - Widget _parseAttachments() { final attachmentGroups = >{}; From 8b71b32bdbf5ae5e34d8ee1199793596eb518ad9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 25 Jun 2021 11:23:52 +0200 Subject: [PATCH 446/599] use MessageWidget.copyWith in messagelistview --- .../lib/src/message_list_view.dart | 112 +++++------------- .../lib/src/message_widget.dart | 15 ++- 2 files changed, 42 insertions(+), 85 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 7eeba3e4..525f7313 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -56,32 +56,35 @@ typedef ReplyTapCallback = void Function(Message); class MessageDetails { /// Constructor for creating [MessageDetails] MessageDetails( - BuildContext context, + String currentUserId, this.message, List messages, this.index, + this.defaultMessageWidget, ) { - isMyMessage = message.user?.id == StreamChat.of(context).user?.id; + isMyMessage = message.user?.id == currentUserId; isLastUser = index + 1 < messages.length && message.user?.id == messages[index + 1].user?.id; isNextUser = index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id; } + late final MessageWidget defaultMessageWidget; + /// True if the message belongs to the current user - bool? isMyMessage; + late final bool isMyMessage; /// True if the user message is the same of the previous message - bool? isLastUser; + late final bool isLastUser; /// True if the user message is the same of the next message - bool? isNextUser; + late final bool isNextUser; /// The message - Message message; + late final Message message; /// The index of the message - int index; + late final int index; } /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview.png) @@ -576,46 +579,21 @@ class _MessageListViewState extends State { const Offstage(); } - final topMessageIndex = itemCount - - 4; // 7 -> parent // 6 -> header // 5 -> loader const bottomMessageIndex = 2; // 1 -> loader // 0 -> footer final message = messages[i - 2]; Widget messageWidget; - if (i == topMessageIndex) { - messageWidget = _buildTopMessage( - context, - message, - messages, - streamChannel, - i, - ); - } else if (i == bottomMessageIndex) { + if (i == bottomMessageIndex) { messageWidget = _buildBottomMessage( context, message, messages, streamChannel!, - i, + i - 2, ); } else { - if (widget.messageBuilder != null) { - messageWidget = Builder( - key: ValueKey('MESSAGE-${message.id}'), - builder: (context) => widget.messageBuilder!( - context, - MessageDetails( - context, - message, - messages, - i, - ), - messages), - ); - } else { - messageWidget = buildMessage(message, messages, i); - } + messageWidget = buildMessage(message, messages, i - 2); } return messageWidget; }, @@ -785,34 +763,6 @@ class _MessageListViewState extends State { isThreadConversation: _isThreadConversation, ); - Widget _buildTopMessage( - BuildContext context, - Message message, - List messages, - StreamChannelState? streamChannel, - int index, - ) { - Widget messageWidget; - if (widget.messageBuilder != null) { - messageWidget = Builder( - key: const ValueKey('TOP-MESSAGE'), - builder: (_) => widget.messageBuilder!( - context, - MessageDetails( - context, - message, - messages, - messages.length - 1, - ), - messages, - ), - ); - } else { - messageWidget = buildMessage(message, messages, index); - } - return messageWidget; - } - Widget _buildBottomMessage( BuildContext context, Message message, @@ -820,24 +770,7 @@ class _MessageListViewState extends State { StreamChannelState streamChannel, int index, ) { - Widget messageWidget; - if (widget.messageBuilder != null) { - messageWidget = Builder( - key: ValueKey('BOTTOM-MESSAGE-${message.id}'), - builder: (_) => widget.messageBuilder!( - context, - MessageDetails( - context, - message, - messages, - 0, - ), - messages, - ), - ); - } else { - messageWidget = buildMessage(message, messages, index); - } + final messageWidget = buildMessage(message, messages, index); return VisibilityDetector( key: ValueKey('BOTTOM-MESSAGE-${message.id}'), @@ -1015,7 +948,7 @@ class _MessageListViewState extends State { final currentUserMember = members.firstWhere((e) => e.user!.id == currentUser!.id); - Widget child = MessageWidget( + Widget messageWidget = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), message: message, reverse: isMyMessage, @@ -1130,6 +1063,21 @@ class _MessageListViewState extends State { showPinButton: widget.pinPermissions.contains(currentUserMember.role), ); + if (widget.messageBuilder != null) { + messageWidget = widget.messageBuilder!( + context, + MessageDetails( + userId, + message, + messages, + index, + messageWidget as MessageWidget, + ), + messages, + ); + } + + var child = messageWidget; if (!message.isDeleted && !message.isSystem && !message.isEphemeral && diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 49788f80..de86873d 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -572,7 +572,7 @@ class _MessageWidgetState extends State super.build(context); final avatarWidth = widget.messageTheme.avatarTheme?.constraints.maxWidth ?? 40; - final leftPadding = + final bottomRowPadding = widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; return Material( @@ -627,7 +627,8 @@ class _MessageWidgetState extends State crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: [ - if (widget.showUserAvatar == + if (!widget.reverse && + widget.showUserAvatar == DisplayWidget.show && widget.message.user != null) ...[ _buildUserAvatar(), @@ -753,6 +754,13 @@ class _MessageWidgetState extends State ), ), ), + if (widget.reverse && + widget.showUserAvatar == + DisplayWidget.show && + widget.message.user != null) ...[ + _buildUserAvatar(), + const SizedBox(width: 4), + ] ], ), if (showBottomRow) @@ -763,7 +771,8 @@ class _MessageWidgetState extends State if (showBottomRow) Padding( padding: EdgeInsets.only( - left: leftPadding, + left: !widget.reverse ? bottomRowPadding : 0, + right: widget.reverse ? bottomRowPadding : 0, bottom: isPinned && widget.showPinHighlight ? 6.0 : 0.0, ), From e1a9e4d8789527152577adcdf0765d095d397fb2 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 25 Jun 2021 11:30:19 +0200 Subject: [PATCH 447/599] use MessageWidget.copyWith in parentMessageBuilder --- .../lib/src/message_list_view.dart | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 525f7313..f6c6d60d 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -26,9 +26,12 @@ typedef MessageBuilder = Widget Function( ); /// Widget builder for parent message +/// [defaultMessageWidget] is the default [MessageWidget] configuration +/// Use [defaultMessageWidget.copyWith] to easily customize it typedef ParentMessageBuilder = Widget Function( BuildContext, Message?, + MessageWidget defaultMessageWidget, ); /// Widget builder for system message @@ -69,6 +72,8 @@ class MessageDetails { index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id; } + /// Default [MessageWidget] configuration + /// use [defaultMessageWidget.copyWith] to easily customize it late final MessageWidget defaultMessageWidget; /// True if the message belongs to the current user @@ -548,11 +553,7 @@ class _MessageListViewState extends State { itemBuilder: (context, i) { if (i == itemCount - 1) { if (widget.parentMessage == null) return const Offstage(); - return widget.parentMessageBuilder?.call( - context, - widget.parentMessage, - ) ?? - buildParentMessage(widget.parentMessage!); + return buildParentMessage(widget.parentMessage!); } if (i == itemCount - 2) { @@ -804,7 +805,7 @@ class _MessageListViewState extends State { final currentUserMember = members.firstWhereOrNull((e) => e.user!.id == currentUser!.id); - return MessageWidget( + final defaultMessageWidget = MessageWidget( showReplyMessage: false, showResendMessage: false, showThreadReplyMessage: false, @@ -858,6 +859,16 @@ class _MessageListViewState extends State { showPinButton: currentUserMember != null && widget.pinPermissions.contains(currentUserMember.role), ); + + if (widget.parentMessageBuilder != null) { + return widget.parentMessageBuilder!.call( + context, + widget.parentMessage, + defaultMessageWidget, + ); + } + + return defaultMessageWidget; } Widget buildMessage( From f3a5db159423875c5a992e8a44289b2bfa365a28 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 25 Jun 2021 12:02:33 +0200 Subject: [PATCH 448/599] fix tests --- .../test/src/message_action_modal_test.dart | 21 +++++++ .../src/message_reactions_modal_test.dart | 55 ++++--------------- 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart index 9b7b3e52..953434de 100644 --- a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart @@ -38,6 +38,10 @@ void main() { id: 'user-id', ), ), + messageWidget: const Text( + 'test', + key: Key('MessageWidget'), + ), messageTheme: streamTheme.ownMessageTheme, ), ), @@ -86,6 +90,10 @@ void main() { ), ), messageTheme: streamTheme.ownMessageTheme, + messageWidget: const Text( + 'test', + key: Key('MessageWidget'), + ), ), ), ), @@ -123,6 +131,7 @@ void main() { client: client, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), message: Message( text: 'test', user: User( @@ -178,6 +187,7 @@ void main() { client: client, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), onReplyTap: (m) { tapped = true; }, @@ -223,6 +233,7 @@ void main() { client: client, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), onThreadReplyTap: (m) { tapped = true; }, @@ -272,6 +283,7 @@ void main() { channel: channel, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), message: Message( text: 'test', user: User( @@ -320,6 +332,7 @@ void main() { channel: channel, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), editMessageInputBuilder: (context, m) => const Text('test'), message: Message( text: 'test', @@ -371,6 +384,7 @@ void main() { channel: channel, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), onCopyTap: (m) => tapped = true, message: Message( text: 'test', @@ -420,6 +434,7 @@ void main() { channel: channel, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), message: Message( status: MessageSendingStatus.failed, text: 'test', @@ -469,6 +484,7 @@ void main() { channel: channel, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), message: Message( status: MessageSendingStatus.failed_update, text: 'test', @@ -516,6 +532,7 @@ void main() { channel: channel, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), message: Message( id: 'testid', text: 'test', @@ -571,6 +588,7 @@ void main() { channel: channel, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), message: Message( id: 'testid', text: 'test', @@ -626,6 +644,7 @@ void main() { channel: channel, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), message: Message( id: 'testid', text: 'test', @@ -679,6 +698,7 @@ void main() { channel: channel, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), message: Message( id: 'testid', text: 'test', @@ -734,6 +754,7 @@ void main() { channel: channel, child: SizedBox( child: MessageActionsModal( + messageWidget: const Text('test'), message: Message( id: 'testid', text: 'test', diff --git a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart index 6bdb72b0..9452e7e6 100644 --- a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart @@ -34,6 +34,10 @@ void main() { client: client, streamChatThemeData: streamTheme, child: MessageReactionsModal( + messageWidget: const Text( + 'test', + key: Key('MessageWidget'), + ), message: message, messageTheme: streamTheme.ownMessageTheme, ), @@ -43,28 +47,9 @@ void main() { await tester.pump(const Duration(milliseconds: 1000)); - expect(find.byType(MessageWidget), findsOneWidget); - final messageWidget = - tester.widget(find.byType(MessageWidget)); - expect(messageWidget.message, message); - expect(messageWidget.messageTheme, streamTheme.ownMessageTheme); - expect(messageWidget.attachmentBorderRadiusGeometry, null); - expect(messageWidget.showUserAvatar, DisplayWidget.show); - expect(messageWidget.reverse, false); - expect(messageWidget.attachmentShape, null); - expect(messageWidget.shape, null); - expect(messageWidget.onUserAvatarTap, null); - expect(messageWidget.showReactions, false); - expect(messageWidget.showUsername, false); - expect(messageWidget.showThreadReplyIndicator, false); - expect(messageWidget.showTimestamp, false); - expect(messageWidget.translateUserAvatar, false); - expect(messageWidget.showSendingIndicator, false); - expect(find.byType(ReactionBubble), findsNothing); - //only one avatar (the message one) - expect(find.byType(UserAvatar), findsOneWidget); + expect(find.byType(UserAvatar), findsNothing); }, ); @@ -105,14 +90,14 @@ void main() { client: client, streamChatThemeData: streamTheme, child: MessageReactionsModal( + messageWidget: const Text( + 'test', + key: Key('MessageWidget'), + ), message: message, messageTheme: streamTheme.ownMessageTheme, - showUserAvatar: DisplayWidget.gone, reverse: true, - attachmentBorderRadiusGeometry: BorderRadius.circular(1), - attachmentShape: const RoundedRectangleBorder(), showReactions: false, - messageShape: const RoundedRectangleBorder(), onUserAvatarTap: onUserAvatarTap, ), ), @@ -121,27 +106,7 @@ void main() { await tester.pump(const Duration(milliseconds: 1000)); - expect(find.byType(MessageWidget), findsOneWidget); - final messageWidget = - tester.widget(find.byType(MessageWidget)); - expect(messageWidget.message, message); - expect(messageWidget.messageTheme, streamTheme.ownMessageTheme); - expect(messageWidget.attachmentBorderRadiusGeometry, - BorderRadius.circular(1)); - expect(messageWidget.showUserAvatar, DisplayWidget.gone); - expect(messageWidget.reverse, true); - expect(messageWidget.showReactions, false); - expect(messageWidget.attachmentShape, const RoundedRectangleBorder()); - expect(messageWidget.shape, const RoundedRectangleBorder()); - expect(messageWidget.showReactions, false); - expect(messageWidget.showUsername, false); - expect(messageWidget.showThreadReplyIndicator, false); - expect(messageWidget.showTimestamp, false); - expect(messageWidget.translateUserAvatar, false); - expect(messageWidget.showSendingIndicator, false); - - final userAvatar = tester.widget(find.byType(UserAvatar)); - expect(userAvatar.onTap, onUserAvatarTap); + expect(find.byKey(const Key('MessageWidget')), findsOneWidget); expect(find.byType(ReactionBubble), findsOneWidget); expect(find.byType(UserAvatar), findsOneWidget); From 155706c8fd8b089f9a094ebe7773cdb80c997234 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 25 Jun 2021 14:01:35 +0200 Subject: [PATCH 449/599] remove unnecessary late --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index f6c6d60d..bdc9d069 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -74,7 +74,7 @@ class MessageDetails { /// Default [MessageWidget] configuration /// use [defaultMessageWidget.copyWith] to easily customize it - late final MessageWidget defaultMessageWidget; + final MessageWidget defaultMessageWidget; /// True if the message belongs to the current user late final bool isMyMessage; @@ -86,10 +86,10 @@ class MessageDetails { late final bool isNextUser; /// The message - late final Message message; + final Message message; /// The index of the message - late final int index; + final int index; } /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview.png) From 1c3dbb1595d590bb6519198815cd84f28367f006 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 25 Jun 2021 14:18:56 +0200 Subject: [PATCH 450/599] move messagewidget in the builder --- .../example/lib/tutorial-part-5.dart | 1 + .../stream_chat_flutter/lib/src/message_list_view.dart | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart index 7a75c037..8f1118dc 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart @@ -93,6 +93,7 @@ class ChannelPage extends StatelessWidget { BuildContext context, MessageDetails details, List messages, + MessageWidget _, ) { final message = details.message; final isCurrentUser = StreamChat.of(context).user!.id == message.user!.id; 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 bdc9d069..74942259 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -19,10 +19,13 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:visibility_detector/visibility_detector.dart'; /// Widget builder for message +/// [defaultMessageWidget] is the default [MessageWidget] configuration +/// Use [defaultMessageWidget.copyWith] to easily customize it typedef MessageBuilder = Widget Function( BuildContext, MessageDetails, List, + MessageWidget defaultMessageWidget, ); /// Widget builder for parent message @@ -63,7 +66,6 @@ class MessageDetails { this.message, List messages, this.index, - this.defaultMessageWidget, ) { isMyMessage = message.user?.id == currentUserId; isLastUser = index + 1 < messages.length && @@ -72,10 +74,6 @@ class MessageDetails { index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id; } - /// Default [MessageWidget] configuration - /// use [defaultMessageWidget.copyWith] to easily customize it - final MessageWidget defaultMessageWidget; - /// True if the message belongs to the current user late final bool isMyMessage; @@ -1082,9 +1080,9 @@ class _MessageListViewState extends State { message, messages, index, - messageWidget as MessageWidget, ), messages, + messageWidget as MessageWidget, ); } From 56bce0957cc9d9257cf0bb8f182e7c541bbdde78 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 26 Jun 2021 12:41:52 +0200 Subject: [PATCH 451/599] fix payload and improve tests --- .../lib/src/core/api/general_api.dart | 3 +- .../test/src/core/api/general_api_test.dart | 44 ++++++++++++++----- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/packages/stream_chat/lib/src/core/api/general_api.dart b/packages/stream_chat/lib/src/core/api/general_api.dart index 8eeeaa5f..c6243148 100644 --- a/packages/stream_chat/lib/src/core/api/general_api.dart +++ b/packages/stream_chat/lib/src/core/api/general_api.dart @@ -79,16 +79,17 @@ class GeneralApi { queryParameters: { 'payload': jsonEncode({ 'type': channelType, + 'filter_conditions': filter ?? {}, if (channelId != null) 'id': channelId else if (members != null) 'members': members, if (sort != null) 'sort': sort, - if (filter != null) 'filter': filter, if (pagination != null) ...pagination.toJson(), }), }, ); + return QueryMembersResponse.fromJson(response.data); } } diff --git a/packages/stream_chat/test/src/core/api/general_api_test.dart b/packages/stream_chat/test/src/core/api/general_api_test.dart index 8844262f..bf7a3018 100644 --- a/packages/stream_chat/test/src/core/api/general_api_test.dart +++ b/packages/stream_chat/test/src/core/api/general_api_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:dio/dio.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/src/core/api/general_api.dart'; @@ -149,11 +151,22 @@ void main() { (index) => Member(userId: 'test-user-id=$index'), ); - when(() => - client.get(path, queryParameters: any(named: 'queryParameters'))) - .thenAnswer((_) async => successResponse(path, data: { - 'members': [...members.map((it) => it.toJson())] - })); + final payload = jsonEncode({ + 'type': channelType, + 'filter_conditions': filter, + 'id': channelId, + 'sort': sort, + ...pagination.toJson(), + }); + + when(() => client.get( + path, + queryParameters: { + 'payload': payload, + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'members': [...members.map((it) => it.toJson())] + })); final res = await generalApi.queryMembers( channelType, @@ -185,11 +198,22 @@ void main() { (index) => Member(userId: 'test-user-id=$index'), ); - when(() => - client.get(path, queryParameters: any(named: 'queryParameters'))) - .thenAnswer((_) async => successResponse(path, data: { - 'members': [...members.map((it) => it.toJson())] - })); + final payload = jsonEncode({ + 'type': channelType, + 'filter_conditions': filter, + 'members': members, + 'sort': sort, + ...pagination.toJson(), + }); + + when(() => client.get( + path, + queryParameters: { + 'payload': payload, + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'members': [...members.map((it) => it.toJson())] + })); final res = await generalApi.queryMembers( channelType, From 9cc6ef124c7f90f5e5c224ffd4f728a99e1d136d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 26 Jun 2021 13:25:06 +0200 Subject: [PATCH 452/599] better channel api tests --- .../lib/src/core/api/channel_api.dart | 2 +- .../test/src/core/api/channel_api_test.dart | 201 ++++++++++++++---- .../test/src/core/api/general_api_test.dart | 43 +++- 3 files changed, 192 insertions(+), 54 deletions(-) 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 7660c125..68f6c4f8 100644 --- a/packages/stream_chat/lib/src/core/api/channel_api.dart +++ b/packages/stream_chat/lib/src/core/api/channel_api.dart @@ -76,7 +76,7 @@ class ChannelApi { // pagination ...paginationParams.toJson() - }) + }), }, ); return QueryChannelsResponse.fromJson(response.data); diff --git a/packages/stream_chat/test/src/core/api/channel_api_test.dart b/packages/stream_chat/test/src/core/api/channel_api_test.dart index 2152cee0..ab530ff1 100644 --- a/packages/stream_chat/test/src/core/api/channel_api_test.dart +++ b/packages/stream_chat/test/src/core/api/channel_api_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:dio/dio.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/src/core/api/channel_api.dart'; @@ -76,11 +78,24 @@ void main() { const path = '$channelPath/query'; final channelState = _generateChannelState(channelId, channelType); - when(() => client.post(path, data: any(named: 'data'))) - .thenAnswer((_) async => successResponse( - path, - data: channelState.toJson(), - )); + + final data = { + 'state': true, + 'watch': false, + 'presence': false, + 'data': channelData, + 'messages': messagePagination, + 'members': membersPagination, + 'watchers': watchersPagination, + }; + + when(() => client.post( + path, + data: data, + )).thenAnswer((_) async => successResponse( + path, + data: channelState.toJson(), + )); final res = await channelApi.queryChannel( channelType, @@ -115,13 +130,34 @@ void main() { const path = '/channels'; final channelState = _generateChannelState(channelId, channelType); - when(() => client.get(path, queryParameters: any(named: 'queryParameters'))) - .thenAnswer((_) async => successResponse( - path, - data: { - 'channels': [channelState.toJson()] - }, - )); + + final payload = jsonEncode({ + // default options + 'state': true, + 'watch': true, + 'presence': false, + + // passed options + 'sort': sort, + 'filter_conditions': filter, + 'member_limit': memberLimit, + 'message_limit': messageLimit, + + // pagination + ...const PaginationParams().toJson() + }); + + when(() => client.get( + path, + queryParameters: { + 'payload': payload, + }, + )).thenAnswer((_) async => successResponse( + path, + data: { + 'channels': [channelState.toJson()] + }, + )); final res = await channelApi.queryChannels( filter: filter, @@ -166,11 +202,18 @@ void main() { extraData: data, ); - when(() => client.post(path, data: any(named: 'data'))) - .thenAnswer((_) async => successResponse(path, data: { - 'channel': channelModel.toJson(), - 'message': message.toJson(), - })); + when(() => client.post( + path, + data: any( + named: 'data', + that: wrapMatcher((Map v) => + containsPair('data', data).matches(v, {}) && + contains('message').matches(v, {})), + ), + )).thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + 'message': message.toJson(), + })); final res = await channelApi.updateChannel( channelId, @@ -235,11 +278,16 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post(path, data: any(named: 'data'))) - .thenAnswer((_) async => successResponse(path, data: { - 'channel': channelModel.toJson(), - 'message': message.toJson(), - })); + when(() => client.post( + path, + data: { + 'accept_invite': true, + 'message': message, + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + 'message': message.toJson(), + })); final res = await channelApi.acceptChannelInvite( channelId, @@ -264,11 +312,16 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post(path, data: any(named: 'data'))) - .thenAnswer((_) async => successResponse(path, data: { - 'channel': channelModel.toJson(), - 'message': message.toJson(), - })); + when(() => client.post( + path, + data: { + 'reject_invite': true, + 'message': message, + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + 'message': message.toJson(), + })); final res = await channelApi.rejectChannelInvite( channelId, @@ -293,11 +346,16 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post(path, data: any(named: 'data'))) - .thenAnswer((_) async => successResponse(path, data: { - 'channel': channelModel.toJson(), - 'message': message.toJson(), - })); + when(() => client.post( + path, + data: { + 'invites': memberIds, + 'message': message, + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + 'message': message.toJson(), + })); final res = await channelApi.inviteChannelMembers( channelId, @@ -323,11 +381,16 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post(path, data: any(named: 'data'))) - .thenAnswer((_) async => successResponse(path, data: { - 'channel': channelModel.toJson(), - 'message': message.toJson(), - })); + when(() => client.post( + path, + data: { + 'add_members': memberIds, + 'message': message, + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + 'message': message.toJson(), + })); final res = await channelApi.addMembers( channelId, @@ -353,11 +416,16 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post(path, data: any(named: 'data'))) - .thenAnswer((_) async => successResponse(path, data: { - 'channel': channelModel.toJson(), - 'message': message.toJson(), - })); + when(() => client.post( + path, + data: { + 'remove_members': memberIds, + 'message': message, + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'channel': channelModel.toJson(), + 'message': message.toJson(), + })); final res = await channelApi.removeMembers( channelId, @@ -381,7 +449,7 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/event'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + when(() => client.post(path, data: {'event': event})).thenAnswer( (_) async => successResponse(path, data: {})); final res = await channelApi.sendEvent(channelId, channelType, event); @@ -432,8 +500,14 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/hide'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'clear_history': false, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.hideChannel(channelId, channelType); @@ -443,6 +517,33 @@ void main() { verifyNoMoreInteractions(client); }); + test('hideChannel with clear_history: true', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + + final path = '${_getChannelUrl(channelId, channelType)}/hide'; + + when( + () => client.post( + path, + data: { + 'clear_history': true, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); + + final res = await channelApi.hideChannel( + channelId, + channelType, + clearHistory: true, + ); + + expect(res, isNotNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + test('showChannel', () async { const channelId = 'test-channel-id'; const channelType = 'test-channel-type'; @@ -467,8 +568,14 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/read'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post( + path, + data: { + 'message_id': messageId, + }, + )) + .thenAnswer( + (_) async => successResponse(path, data: {})); final res = await channelApi.markRead( channelId, diff --git a/packages/stream_chat/test/src/core/api/general_api_test.dart b/packages/stream_chat/test/src/core/api/general_api_test.dart index bf7a3018..afb3f6aa 100644 --- a/packages/stream_chat/test/src/core/api/general_api_test.dart +++ b/packages/stream_chat/test/src/core/api/general_api_test.dart @@ -35,10 +35,17 @@ void main() { final events = List.generate(3, (index) => Event(type: 'test-event-type-$index')); - when(() => client.post(path, data: any(named: 'data'))) - .thenAnswer((_) async => successResponse(path, data: { - 'events': [...events.map((it) => it.toJson())] - })); + final data = { + 'channel_cids': cids, + 'last_sync_at': lastSyncAt.toUtc().toIso8601String(), + }; + + when(() => client.post( + path, + data: data, + )).thenAnswer((_) async => successResponse(path, data: { + 'events': [...events.map((it) => it.toJson())] + })); final res = await generalApi.sync(cids, lastSyncAt); @@ -87,8 +94,20 @@ void main() { const path = '/search'; + final payload = jsonEncode({ + 'filter_conditions': filter, + 'sort': sort, + 'query': query, + ...pagination.toJson(), + }); + when( - () => client.get(path, queryParameters: any(named: 'queryParameters')), + () => client.get( + path, + queryParameters: { + 'payload': payload, + }, + ), ).thenAnswer((_) async => successResponse(path, data: {'results': []})); final res = await generalApi.searchMessages( @@ -115,8 +134,20 @@ void main() { const path = '/search'; + final payload = jsonEncode({ + 'filter_conditions': filter, + 'sort': sort, + 'message_filter_conditions': messageFilter, + ...pagination.toJson(), + }); + when( - () => client.get(path, queryParameters: any(named: 'queryParameters')), + () => client.get( + path, + queryParameters: { + 'payload': payload, + }, + ), ).thenAnswer((_) async => successResponse(path, data: {'results': []})); final res = await generalApi.searchMessages( From 1a817a3282d635448ac2350853e8eea64eec448b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 26 Jun 2021 13:26:25 +0200 Subject: [PATCH 453/599] better device api tests --- .../test/src/core/api/device_api_test.dart | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/test/src/core/api/device_api_test.dart b/packages/stream_chat/test/src/core/api/device_api_test.dart index f12f04f5..7d4f59fd 100644 --- a/packages/stream_chat/test/src/core/api/device_api_test.dart +++ b/packages/stream_chat/test/src/core/api/device_api_test.dart @@ -26,8 +26,15 @@ void main() { const path = '/devices'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post( + path, + data: { + 'id': deviceId, + 'push_provider': pushProvider.name, + }, + )) + .thenAnswer( + (_) async => successResponse(path, data: {})); final res = await deviceApi.addDevice(deviceId, pushProvider); @@ -69,7 +76,10 @@ void main() { const path = '/devices'; when( - () => client.delete(path, queryParameters: any(named: 'queryParameters')), + () => client.delete( + path, + queryParameters: {'id': deviceId}, + ), ).thenAnswer((_) async => successResponse(path, data: {})); final res = await deviceApi.removeDevice(deviceId); From ff8ff6d7ceefa616c4eae9a2e264c973e306d22c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 26 Jun 2021 13:27:09 +0200 Subject: [PATCH 454/599] better guest api tests --- .../test/src/core/api/guest_api_test.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat/test/src/core/api/guest_api_test.dart b/packages/stream_chat/test/src/core/api/guest_api_test.dart index 6c6386fa..7db74b4a 100644 --- a/packages/stream_chat/test/src/core/api/guest_api_test.dart +++ b/packages/stream_chat/test/src/core/api/guest_api_test.dart @@ -26,11 +26,13 @@ void main() { const path = '/guest'; - when(() => client.post(path, data: any(named: 'data'))) - .thenAnswer((_) async => successResponse(path, data: { - 'access_token': accessToken, - 'user': user.toJson(), - })); + when(() => client.post( + path, + data: {'user': user}, + )).thenAnswer((_) async => successResponse(path, data: { + 'access_token': accessToken, + 'user': user.toJson(), + })); final res = await guestApi.getGuestUser(user); From 9857db0635bf58b13b5d8cbf77982130bbe91ab2 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 26 Jun 2021 13:37:40 +0200 Subject: [PATCH 455/599] better message api tests --- .../stream_chat/lib/src/client/channel.dart | 4 +- .../stream_chat/lib/src/client/client.dart | 4 +- .../lib/src/core/api/message_api.dart | 4 +- .../test/src/core/api/message_api_test.dart | 162 ++++++++++++++---- 4 files changed, 139 insertions(+), 35 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index e173c703..eee18356 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -989,11 +989,11 @@ class Channel { /// List the reactions for a message in the channel Future getReactions( String messageId, { - PaginationParams? options, + PaginationParams? pagination, }) => _client.getReactions( messageId, - options: options, + pagination: pagination, ); /// Retrieves a list of messages by ID diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index b579f030..93949df3 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1149,11 +1149,11 @@ class StreamChatClient { /// Get all the reactions for a [messageId] Future getReactions( String messageId, { - PaginationParams? options, + PaginationParams? pagination, }) => _chatApi.message.getReactions( messageId, - options: options, + pagination: pagination, ); /// Update the given message diff --git a/packages/stream_chat/lib/src/core/api/message_api.dart b/packages/stream_chat/lib/src/core/api/message_api.dart index f50cb4a1..17269820 100644 --- a/packages/stream_chat/lib/src/core/api/message_api.dart +++ b/packages/stream_chat/lib/src/core/api/message_api.dart @@ -143,12 +143,12 @@ class MessageApi { /// Get all the reactions for a [messageId] Future getReactions( String messageId, { - PaginationParams? options, + PaginationParams? pagination, }) async { final response = await _client.get( '/messages/$messageId/reactions', queryParameters: { - if (options != null) ...options.toJson(), + if (pagination != null) ...pagination.toJson(), }, ); return QueryReactionsResponse.fromJson(response.data); diff --git a/packages/stream_chat/test/src/core/api/message_api_test.dart b/packages/stream_chat/test/src/core/api/message_api_test.dart index d7fa93cd..1aacb6d2 100644 --- a/packages/stream_chat/test/src/core/api/message_api_test.dart +++ b/packages/stream_chat/test/src/core/api/message_api_test.dart @@ -27,10 +27,15 @@ void main() { const path = '/channels/$channelType/$channelId/message'; - when(() => client.post(path, data: any(named: 'data'))) - .thenAnswer((_) async => successResponse(path, data: { - 'message': message.toJson(), - })); + when(() => client.post( + path, + data: { + 'message': message, + 'skip_push': false, + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'message': message.toJson(), + })); final res = await messageApi.sendMessage(channelId, channelType, message); @@ -41,6 +46,37 @@ void main() { verifyNoMoreInteractions(client); }); + test('sendMessage with skipPush: true', () async { + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + final message = Message(id: 'test-message-id', text: 'test-message-text'); + + const path = '/channels/$channelType/$channelId/message'; + + when(() => client.post( + path, + data: { + 'message': message, + 'skip_push': true, + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'message': message.toJson(), + })); + + final res = await messageApi.sendMessage( + channelId, + channelType, + message, + skipPush: true, + ); + + expect(res, isNotNull); + expect(res.message.id, message.id); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + test('getMessagesById', () async { const channelId = 'test-channel-id'; const channelType = 'test-channel-type'; @@ -53,10 +89,12 @@ void main() { (index) => Message(id: 'test-message-id-$index'), ); - when(() => client.get(path, queryParameters: any(named: 'queryParameters'))) - .thenAnswer((_) async => successResponse(path, data: { - 'messages': [...messages.map((it) => it.toJson())], - })); + when(() => client.get( + path, + queryParameters: {'ids': messageIds.join(',')}, + )).thenAnswer((_) async => successResponse(path, data: { + 'messages': [...messages.map((it) => it.toJson())], + })); final res = await messageApi.getMessagesById( channelId, @@ -97,7 +135,10 @@ void main() { final path = '/messages/${message.id}'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( + when(() => client.post( + path, + data: {'message': message}, + )).thenAnswer( (_) async => successResponse(path, data: {'message': message.toJson()}), ); @@ -169,8 +210,17 @@ void main() { const path = '/messages/$messageId/action'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post( + path, + data: { + 'id': channelId, + 'type': channelType, + 'form_data': formData, + 'message_id': messageId, + }, + )) + .thenAnswer( + (_) async => successResponse(path, data: {})); final res = await messageApi.sendAction( channelId, @@ -195,11 +245,17 @@ void main() { final message = Message(id: messageId); final reaction = Reaction(type: reactionType, messageId: messageId); - when(() => client.post(path, data: any(named: 'data'))) - .thenAnswer((_) async => successResponse(path, data: { - 'message': message.toJson(), - 'reaction': reaction.toJson(), - })); + when(() => client.post( + path, + data: { + 'reaction': Map.from(extraData) + ..addAll({'type': reactionType}), + 'enforce_unique': false, + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'message': message.toJson(), + 'reaction': reaction.toJson(), + })); final res = await messageApi.sendReaction( messageId, @@ -216,6 +272,44 @@ void main() { verifyNoMoreInteractions(client); }); + test('sendReaction with enforceUnique: true', () async { + const messageId = 'test-message-id'; + const reactionType = 'test-reaction-type'; + const extraData = {'test-key': 'test-data'}; + + const path = '/messages/$messageId/reaction'; + + final message = Message(id: messageId); + final reaction = Reaction(type: reactionType, messageId: messageId); + + when(() => client.post( + path, + data: { + 'reaction': Map.from(extraData) + ..addAll({'type': reactionType}), + 'enforce_unique': true, + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'message': message.toJson(), + 'reaction': reaction.toJson(), + })); + + final res = await messageApi.sendReaction( + messageId, + reactionType, + extraData: extraData, + enforceUnique: true, + ); + + expect(res, isNotNull); + expect(res.message.id, messageId); + expect(res.reaction.messageId, messageId); + expect(res.reaction.type, reactionType); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + test('deleteReaction', () async { const messageId = 'test-message-id'; const reactionType = 'test-reaction-type'; @@ -247,12 +341,16 @@ void main() { ), ); - when(() => client.get(path, queryParameters: any(named: 'queryParameters'))) - .thenAnswer((_) async => successResponse(path, data: { - 'reactions': [...reactions.map((it) => it.toJson())] - })); + when(() => client.get( + path, + queryParameters: { + ...const PaginationParams().toJson(), + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'reactions': [...reactions.map((it) => it.toJson())] + })); - final res = await messageApi.getReactions(messageId, options: options); + final res = await messageApi.getReactions(messageId, pagination: options); expect(res, isNotNull); expect(res.reactions.length, reactions.length); @@ -277,10 +375,12 @@ void main() { language: translatedMessageText, }); - when(() => client.post(path, data: any(named: 'data'))) - .thenAnswer((_) async => successResponse(path, data: { - 'message': translatedMessage.toJson(), - })); + when(() => client.post( + path, + data: {'language': language}, + )).thenAnswer((_) async => successResponse(path, data: { + 'message': translatedMessage.toJson(), + })); final res = await messageApi.translateMessage(messageId, language); @@ -306,10 +406,14 @@ void main() { ), ); - when(() => client.get(path, queryParameters: any(named: 'queryParameters'))) - .thenAnswer((_) async => successResponse(path, data: { - 'messages': [...messages.map((it) => it.toJson())] - })); + when(() => client.get( + path, + queryParameters: { + ...options.toJson(), + }, + )).thenAnswer((_) async => successResponse(path, data: { + 'messages': [...messages.map((it) => it.toJson())] + })); final res = await messageApi.getReplies(parentId, options: options); From ddfe015513168270f0a8ccc815645b81a0a818f9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 26 Jun 2021 13:41:41 +0200 Subject: [PATCH 456/599] better moderation api tests --- .../lib/src/core/http/stream_http_client.dart | 7 +- .../src/core/api/moderation_api_test.dart | 88 +++++++++++++++---- 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client.dart b/packages/stream_chat/lib/src/core/http/stream_http_client.dart index 4cf5b0e0..406138c3 100644 --- a/packages/stream_chat/lib/src/core/http/stream_http_client.dart +++ b/packages/stream_chat/lib/src/core/http/stream_http_client.dart @@ -3,18 +3,17 @@ import 'dart:async'; import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; +import 'package:stream_chat/src/core/error/error.dart'; +import 'package:stream_chat/src/core/http/connection_id_manager.dart'; import 'package:stream_chat/src/core/http/interceptor/auth_interceptor.dart'; import 'package:stream_chat/src/core/http/interceptor/connection_id_interceptor.dart'; import 'package:stream_chat/src/core/http/interceptor/logging_interceptor.dart'; import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; -import 'package:stream_chat/src/core/error/error.dart'; -import 'package:stream_chat/src/location.dart'; import 'package:stream_chat/src/core/platform_detector/platform_detector.dart'; +import 'package:stream_chat/src/location.dart'; import 'package:stream_chat/version.dart'; -import 'package:stream_chat/src/core/http/connection_id_manager.dart'; - part 'stream_http_client_options.dart'; /// This is where we configure the base url, headers, diff --git a/packages/stream_chat/test/src/core/api/moderation_api_test.dart b/packages/stream_chat/test/src/core/api/moderation_api_test.dart index fb2a1f29..69d85fd9 100644 --- a/packages/stream_chat/test/src/core/api/moderation_api_test.dart +++ b/packages/stream_chat/test/src/core/api/moderation_api_test.dart @@ -24,8 +24,12 @@ void main() { const path = '/moderation/mute'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: {'target_id': userId}, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.muteUser(userId); @@ -40,8 +44,12 @@ void main() { const path = '/moderation/unmute'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: {'target_id': userId}, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.unmuteUser(userId); @@ -57,8 +65,15 @@ void main() { const path = '/moderation/mute/channel'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post( + path, + data: { + 'channel_cid': channelCid, + 'expiration': expiration.inMilliseconds, + }, + )) + .thenAnswer( + (_) async => successResponse(path, data: {})); final res = await moderationApi.muteChannel( channelCid, @@ -76,8 +91,12 @@ void main() { const path = '/moderation/unmute/channel'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post( + path, + data: {'channel_cid': channelCid}, + )) + .thenAnswer( + (_) async => successResponse(path, data: {})); final res = await moderationApi.unmuteChannel(channelCid); @@ -92,8 +111,14 @@ void main() { const path = '/moderation/flag'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post( + path, + data: { + 'target_message_id': messageId, + }, + )) + .thenAnswer( + (_) async => successResponse(path, data: {})); final res = await moderationApi.flagMessage(messageId); @@ -108,8 +133,14 @@ void main() { const path = '/moderation/unflag'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post( + path, + data: { + 'target_message_id': messageId, + }, + )) + .thenAnswer( + (_) async => successResponse(path, data: {})); final res = await moderationApi.unflagMessage(messageId); @@ -124,8 +155,11 @@ void main() { const path = '/moderation/flag'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post(path, data: { + 'target_user_id': userId, + })) + .thenAnswer( + (_) async => successResponse(path, data: {})); final res = await moderationApi.flagUser(userId); @@ -140,8 +174,14 @@ void main() { const path = '/moderation/unflag'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post( + path, + data: { + 'target_user_id': userId, + }, + )) + .thenAnswer( + (_) async => successResponse(path, data: {})); final res = await moderationApi.unflagUser(userId); @@ -157,8 +197,12 @@ void main() { const path = '/moderation/ban'; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post(path, data: { + 'target_user_id': targetUserId, + ...options, + })) + .thenAnswer( + (_) async => successResponse(path, data: {})); final res = await moderationApi.banUser(targetUserId, options: options); @@ -175,7 +219,13 @@ void main() { const path = '/moderation/ban'; when( - () => client.delete(path, queryParameters: any(named: 'queryParameters')), + () => client.delete( + path, + queryParameters: { + 'target_user_id': targetUserId, + ...options, + }, + ), ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.unbanUser(targetUserId, options: options); From c7ea25c67268499b24fa7895f032ab3ff1ece048 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 26 Jun 2021 13:44:06 +0200 Subject: [PATCH 457/599] better user api tests --- .../test/src/core/api/user_api_test.dart | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat/test/src/core/api/user_api_test.dart b/packages/stream_chat/test/src/core/api/user_api_test.dart index ebf0e343..6eecbee1 100644 --- a/packages/stream_chat/test/src/core/api/user_api_test.dart +++ b/packages/stream_chat/test/src/core/api/user_api_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:dio/dio.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/src/core/api/requests.dart'; @@ -32,10 +34,16 @@ void main() { final users = List.generate(3, (index) => User(id: 'test-user-id-$index')); - when(() => client.get(path, queryParameters: any(named: 'queryParameters'))) - .thenAnswer((_) async => successResponse(path, data: { - 'users': [...users.map((it) => it.toJson())] - })); + when(() => client.get(path, queryParameters: { + 'payload': jsonEncode({ + 'presence': presence, + 'sort': sort, + 'filter_conditions': filter, + ...pagination.toJson(), + }), + })).thenAnswer((_) async => successResponse(path, data: { + 'users': [...users.map((it) => it.toJson())] + })); final res = await userApi.queryUsers( presence: presence, @@ -60,8 +68,10 @@ void main() { final updatedUsers = {for (final user in users) user.id: user}; - when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: { + when(() => client.post(path, data: { + 'users': updatedUsers, + })).thenAnswer((_) async => successResponse(path, + data: { 'users': updatedUsers .map((key, value) => MapEntry(key, value.toJson())) })); From d1f12e2f264ab0219b213bdf4e98af33aba1b9ad Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 26 Jun 2021 14:07:07 +0200 Subject: [PATCH 458/599] add cache in analyze --- .github/workflows/stream_flutter_workflow.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 3a6c1650..1ca294df 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -2,6 +2,7 @@ name: stream_flutter_workflow env: ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' + flutter_version: "2.2.2" on: pull_request: @@ -19,7 +20,14 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 + - name: Cache Flutter dependencies + id: flutter-cache + uses: actions/cache@v2 + with: + path: /opt/hostedtoolcache/flutter + key: ${{ env.flutter_version }}-flutter - name: "Install Flutter" + if: steps.flutter-cache.outputs.cache-hit != 'true' uses: subosito/flutter-action@v1 with: channel: 'stable' From e657b131ee27fca771603473f5a8441ff78ab335 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 26 Jun 2021 14:12:40 +0200 Subject: [PATCH 459/599] use flutter version in flutter action --- .github/workflows/stream_flutter_workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 1ca294df..69f6d989 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -30,7 +30,7 @@ jobs: if: steps.flutter-cache.outputs.cache-hit != 'true' uses: subosito/flutter-action@v1 with: - channel: 'stable' + flutter-version: ${{ env.flutter_version }} - name: "Install Tools" run: | flutter pub global activate melos From d4cb197086228c4092882213d5c2adc1b1c55017 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 26 Jun 2021 14:13:38 +0200 Subject: [PATCH 460/599] do not skip flutter step --- .github/workflows/stream_flutter_workflow.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 69f6d989..0285326a 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -21,13 +21,11 @@ jobs: with: fetch-depth: 0 - name: Cache Flutter dependencies - id: flutter-cache uses: actions/cache@v2 with: path: /opt/hostedtoolcache/flutter key: ${{ env.flutter_version }}-flutter - name: "Install Flutter" - if: steps.flutter-cache.outputs.cache-hit != 'true' uses: subosito/flutter-action@v1 with: flutter-version: ${{ env.flutter_version }} From 333f5a00d9cbbe6e9fc4bda3e0f43586b7ce56cb Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 26 Jun 2021 14:15:42 +0200 Subject: [PATCH 461/599] use cache in all jobs --- .github/workflows/stream_flutter_workflow.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 0285326a..d747f463 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -50,10 +50,15 @@ jobs: 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: - channel: 'stable' + flutter-version: ${{ env.flutter_version }} - name: "Install Tools" run: flutter pub global activate melos - name: "Bootstrap Workspace" @@ -72,10 +77,15 @@ jobs: 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: - channel: 'stable' + flutter-version: ${{ env.flutter_version }} - name: "Install Tools" run: | flutter pub global activate melos From 78da059d859919ab75e302c6b182c837b9e4ecf5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 26 Jun 2021 14:22:20 +0200 Subject: [PATCH 462/599] use macos path for test --- .github/workflows/stream_flutter_workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index d747f463..6817b269 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -80,7 +80,7 @@ jobs: - name: Cache Flutter dependencies uses: actions/cache@v2 with: - path: /opt/hostedtoolcache/flutter + path: /Users/runner/hostedtoolcache/flutter key: ${{ env.flutter_version }}-flutter - name: "Install Flutter" uses: subosito/flutter-action@v1 From ea8ee7ac6f3c852978846e492bfd2f845f70022e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 28 Jun 2021 10:57:07 +0200 Subject: [PATCH 463/599] add userAvatarBuilder --- .../lib/src/message_widget.dart | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index de86873d..aed32a03 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -89,6 +89,7 @@ class MessageWidget extends StatefulWidget { this.onLinkTap, this.onMessageActions, this.onShowMessage, + this.userAvatarBuilder, this.editMessageInputBuilder, this.textBuilder, this.onReturnAction, @@ -274,6 +275,8 @@ class MessageWidget extends StatefulWidget { /// Function called on long press final void Function(BuildContext, Message)? onMessageActions; + final Widget Function(BuildContext, User)? userAvatarBuilder; + /// The message final Message message; @@ -448,6 +451,7 @@ class MessageWidget extends StatefulWidget { void Function(Message)? onMessageTap, List? customActions, void Function(Message message, Attachment attachment)? onAttachmentTap, + Widget Function(BuildContext, User)? userAvatarBuilder, }) => MessageWidget( key: key ?? this.key, @@ -506,6 +510,7 @@ class MessageWidget extends StatefulWidget { onMessageTap: onMessageTap ?? this.onMessageTap, customActions: customActions ?? this.customActions, onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap, + userAvatarBuilder: userAvatarBuilder ?? this.userAvatarBuilder, ); @override @@ -1231,13 +1236,14 @@ class _MessageWidgetState extends State 2 : 0, ), - child: UserAvatar( - user: widget.message.user!, - onTap: widget.onUserAvatarTap, - constraints: widget.messageTheme.avatarTheme!.constraints, - borderRadius: widget.messageTheme.avatarTheme!.borderRadius, - showOnlineStatus: false, - ), + child: widget.userAvatarBuilder?.call(context, widget.message.user!) ?? + UserAvatar( + user: widget.message.user!, + onTap: widget.onUserAvatarTap, + constraints: widget.messageTheme.avatarTheme!.constraints, + borderRadius: widget.messageTheme.avatarTheme!.borderRadius, + showOnlineStatus: false, + ), ); Widget _buildTextBubble() { From b8fd33a629f4560edaf5b183fcdfd314fc290aa3 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 28 Jun 2021 11:54:23 +0200 Subject: [PATCH 464/599] fix new lines in list --- packages/stream_chat_flutter/lib/src/message_text.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index e5abede0..6d006085 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -29,7 +29,7 @@ class MessageText extends StatelessWidget { @override Widget build(BuildContext context) { - final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\\\n'); + final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\n\n'); final themeData = Theme.of(context); return MarkdownBody( From 0e263ee26def9d75ab245406acf1f7d388887535 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 28 Jun 2021 12:31:43 +0200 Subject: [PATCH 465/599] update melos.yaml --- melos.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/melos.yaml b/melos.yaml index 1fb51c38..6232c3fa 100644 --- a/melos.yaml +++ b/melos.yaml @@ -56,7 +56,7 @@ scripts: dir-exists: test test:flutter: - run: melos exec -c 1 --fail-fast -- "flutter test --coverage" + run: melos exec -c 3 --fail-fast -- "flutter test --coverage" description: Run Flutter tests for a specific package in this project. select-package: flutter: true From 5912edc611a1065fbc79b953eaa77327fb4749f9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 28 Jun 2021 12:32:58 +0200 Subject: [PATCH 466/599] add doc comment --- packages/stream_chat_flutter/lib/src/message_widget.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index aed32a03..7c95bf0a 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -275,6 +275,7 @@ class MessageWidget extends StatefulWidget { /// Function called on long press final void Function(BuildContext, Message)? onMessageActions; + /// Widget builder for building user avatar final Widget Function(BuildContext, User)? userAvatarBuilder; /// The message From e38381483a840e26d24879e14d90867ab395fc81 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 28 Jun 2021 16:03:33 +0530 Subject: [PATCH 467/599] deprecate baseUrl in StreamChatClient Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/client/client.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 93949df3..9276ce22 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -65,7 +65,7 @@ class StreamChatClient { LogHandlerFunction? logHandlerFunction, RetryPolicy? retryPolicy, Location? location, - String? baseURL, + @Deprecated('Use location to change baseUrl instead') String? baseURL, Duration connectTimeout = const Duration(seconds: 6), Duration receiveTimeout = const Duration(seconds: 6), StreamChatApi? chatApi, From 5e7891e523bb77a93a3fd63524617b908962f5d6 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 28 Jun 2021 16:04:25 +0530 Subject: [PATCH 468/599] remove unused `showScrollToBottom` property from `MessageListCore` Signed-off-by: Sahil Kumar --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 1 - .../stream_chat_flutter_core/lib/src/message_list_core.dart | 5 ----- 2 files changed, 6 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 74942259..f4ab2e01 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -368,7 +368,6 @@ class _MessageListViewState extends State { (context, list) => _buildListView(list), messageListController: _messageListController, parentMessage: widget.parentMessage, - showScrollToBottom: widget.showScrollToBottom, errorWidgetBuilder: widget.errorWidgetBuilder ?? (BuildContext context, Object error) => Center( child: Text( diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 2a257fe6..2a259e83 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -68,7 +68,6 @@ class MessageListCore extends StatefulWidget { required this.emptyBuilder, required this.messageListBuilder, required this.errorWidgetBuilder, - this.showScrollToBottom = true, this.parentMessage, this.messageListController, this.messageFilter, @@ -94,10 +93,6 @@ class MessageListCore extends StatefulWidget { /// event of a connection failure. final ErrorBuilder errorWidgetBuilder; - /// If true will show a scroll to bottom message when there are new messages - /// and the scroll offset is not zero. - final bool showScrollToBottom; - /// If the current message belongs to a `thread`, this property represents the /// first message or the parent of the conversation. final Message? parentMessage; From 5e706ecba2fc610fc7538b4c9c056e9684b1e743 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 28 Jun 2021 16:05:10 +0530 Subject: [PATCH 469/599] chore: prepare stream_chat for v2.0.0-nullsafety.7 Signed-off-by: Sahil Kumar --- packages/stream_chat/CHANGELOG.md | 52 ++++++++++++++++++++++++++----- packages/stream_chat/pubspec.yaml | 2 +- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index a3bdc6ce..9fbd97bd 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,35 @@ +## 2.0.0-nullsafety.7 + +đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.6` +- `ConnectUserWithProvider` now requires `tokenProvider` as a required param. (Removed from the constructor) +- `client.disconnect()` is now divided into two different functions + - `client.closeConnection()` -> for closing user websocket connection. + - `client.disconnectUser()` -> for disconnecting user and resetting client state. +- `client.devToken()` now returns a `Token` model instead of `String`. +- `ApiError` is removed in favor of `StreamChatError` + - `StreamChatError` -> parent type for all the stream errors. + - `StreamWebSocketError` -> for user websocket related errors. + - `StreamChatNetworkError` -> for network related errors. +- `client.queryChannels()`, `channel.query()` options param is removed in favor of individual params + - `option.state` -> bool state + - `option.watch` -> bool watch + - `option.presence` -> bool presence + +✅ Added +- New `Location` enum is introduced for easily changing the client location/baseUrl. +- New `client.openConnection()` and `client.closeConnection()` is introduced to connect/disconnect user ws connection. + +🔄 Changed +- `baseURL` is now deprecated in favor of using `Location` to change data location. + +🐞 Fixed +- Client does not return without internet connection [#369](https://github.com/GetStream/stream-chat-flutter/issues/369) + +## 2.0.0-nullsafety.6 + +- Fix thread reply not working with attachments +- Minor fixes + ## 2.0.0-nullsafety.6 - Fix thread reply not working with attachments @@ -57,7 +89,8 @@ - Save pinned messages in offline storage - Minor fixes - `StreamClient.QueryChannels` now returns a Stream and fetches the channels from storage before calling the api -- Added `StreamClient.QueryChannelsOnline` and `StreamClient.QueryChannelsOffline` to fetch channels only from online or offline +- Added `StreamClient.QueryChannelsOnline` and `StreamClient.QueryChannelsOffline` to fetch channels only from online or + offline ## 1.2.0-beta @@ -68,7 +101,8 @@ ## 1.1.0-beta - Fixed minor bugs -- Add support for custom attachment upload [docs here](https://getstream.io/chat/docs/flutter-dart/file_uploads/?language=dart) +- Add support for custom attachment + upload [docs here](https://getstream.io/chat/docs/flutter-dart/file_uploads/?language=dart) - Add support for asynchronous attachment upload ## 1.0.3-beta @@ -78,7 +112,8 @@ ## 1.0.2-beta -- Deprecated `setUser`, `setGuestUser`, `setUserWithProvider` in favor of `connectUser`, `connectGuestUser`, `connectUserWithProvider` +- Deprecated `setUser`, `setGuestUser`, `setUserWithProvider` in favor of `connectUser`, `connectGuestUser` + , `connectUserWithProvider` - Optimised reaction updates - i.e., Update first call Api later. ## 1.0.1-beta @@ -88,9 +123,11 @@ ## 1.0.0-beta - 🛑 **BREAKING** Renamed `Client` to less generic `StreamChatClient` -- 🛑 **BREAKING** Segregated the persistence layer into separate package [stream_chat_persistence](https://pub.dev/packages/stream_chat_persistence) +- 🛑 **BREAKING** Segregated the persistence layer into separate + package [stream_chat_persistence](https://pub.dev/packages/stream_chat_persistence) - 🛑 **BREAKING** Moved `Client.backgroundKeepAlive` to [core package](https://pub.dev/packages/stream_chat_core) -- 🛑 **BREAKING** Moved `Client.showLocalNotification` to [core package](https://pub.dev/packages/stream_chat_core) and renamed it to `StreamChatCore.onBackgroundEventReceived` +- 🛑 **BREAKING** Moved `Client.showLocalNotification` to [core package](https://pub.dev/packages/stream_chat_core) and + renamed it to `StreamChatCore.onBackgroundEventReceived` - Removed `flutter` dependency. This is now a pure Dart package đŸĨŗ - Minor improvements and bugfixes @@ -145,7 +182,8 @@ ## 0.2.20 -- Return offline data only if the backend is unreachable. This avoids the glitch of the ChannelListView because we cannot sort by custom properties. +- Return offline data only if the backend is unreachable. This avoids the glitch of the ChannelListView because we + cannot sort by custom properties. ## 0.2.19 @@ -159,7 +197,7 @@ ## 0.2.17+1 -- Do not retry messages when server returns error +- Do not retry messages when server returns error ## 0.2.17 diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 1b6b91ae..4901adf0 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat homepage: https://getstream.io/ description: The official Dart client for Stream Chat, a service for building chat applications. -version: 2.0.0-nullsafety.6 +version: 2.0.0-nullsafety.7 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues From 05396a5bd2f0e6bfc4f49016daa635110553c044 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 28 Jun 2021 16:15:59 +0530 Subject: [PATCH 470/599] chore: prepare stream_chat_persistence for v2.0.0-nullsafety.7 Signed-off-by: Sahil Kumar --- packages/stream_chat_persistence/CHANGELOG.md | 5 +++++ packages/stream_chat_persistence/pubspec.yaml | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index aa7c7513..38742b1e 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0-nullsafety.7 + +* Update llc dependency +* Minor fixes and improvements + ## 2.0.0-nullsafety.5 * Update llc dependency diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 9d141198..e5fb8fe6 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_persistence homepage: https://github.com/GetStream/stream-chat-flutter description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.5 +version: 2.0.0-nullsafety.7 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -18,7 +18,7 @@ dependencies: path: ^1.8.0 path_provider: ^2.0.1 sqlite3_flutter_libs: ^0.5.0 - stream_chat: ^2.0.0-nullsafety.5 + stream_chat: ^2.0.0-nullsafety.7 dev_dependencies: build_runner: ^2.0.1 From d5cc22fd10c5f656383fb3d85f2c1a1136084a05 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 28 Jun 2021 16:35:05 +0530 Subject: [PATCH 471/599] chore(stream_chat): add queryUser breaking change in CHANGELOG.md Signed-off-by: Sahil Kumar --- packages/stream_chat/CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 9fbd97bd..0c67f735 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -11,9 +11,11 @@ - `StreamWebSocketError` -> for user websocket related errors. - `StreamChatNetworkError` -> for network related errors. - `client.queryChannels()`, `channel.query()` options param is removed in favor of individual params - - `option.state` -> bool state - - `option.watch` -> bool watch - - `option.presence` -> bool presence + - `options.state` -> bool state + - `options.watch` -> bool watch + - `options.presence` -> bool presence +- `client.queryUsers()` options param is removed in favor of individual params + - `options.presence` -> bool presence ✅ Added - New `Location` enum is introduced for easily changing the client location/baseUrl. From 80217c9d587be3df95984c442e986ffe0b21affb Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 28 Jun 2021 16:38:23 +0530 Subject: [PATCH 472/599] chore(stream_chat): add queryUser breaking change in CHANGELOG.md Signed-off-by: Sahil Kumar --- packages/stream_chat_flutter_core/CHANGELOG.md | 10 ++++++++++ packages/stream_chat_flutter_core/pubspec.yaml | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index bd176c12..7af9b177 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.0.0-nullsafety.8 + +đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.7` +- `channelsBloc.queryChannels()`, `ChannelListCore` options param/property is removed in favor of individual params/properties + - `options.state` -> bool state + - `options.watch` -> bool watch + - `options.presence` -> bool presence +- `usersBloc.queryUsers()`, `UserListCore` options param/property is removed in favor of individual params/properties + - `options.presence` -> bool presence + ## 2.0.0-nullsafety.7 * Fixed a bug with connectivity implementation diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 14fa0f80..a231caab 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.7 +version: 2.0.0-nullsafety.8 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -16,7 +16,7 @@ dependencies: sdk: flutter meta: ^1.3.0 rxdart: ^0.27.0 - stream_chat: ^2.0.0-nullsafety.6 + stream_chat: ^2.0.0-nullsafety.7 dev_dependencies: fake_async: ^1.2.0 From a3a88aead10d21dc33936a6bce8cb8e7d171f1af Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 28 Jun 2021 13:08:51 +0200 Subject: [PATCH 473/599] fix user list error handling --- .../stream_chat_flutter/lib/src/user_list_view.dart | 12 +++++------- .../lib/src/user_list_core.dart | 5 +++-- 2 files changed, 8 insertions(+), 9 deletions(-) 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 aa1886b7..f915fcb0 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -128,7 +128,7 @@ class UserListView extends StatefulWidget { final int crossAxisCount; /// The builder that will be used in case of error - final Widget Function(Error error)? errorBuilder; + final ErrorBuilder? errorBuilder; /// The builder that will be used to build the list final Widget Function(BuildContext context, List users)? @@ -153,8 +153,8 @@ class _UserListViewState extends State @override Widget build(BuildContext context) { final child = UserListCore( - errorBuilder: widget.errorBuilder as Widget Function(Object)? ?? - (err) => _buildError(err as Error), + errorBuilder: widget.errorBuilder ?? + (BuildContext context, Object err) => _buildError(err), emptyBuilder: widget.emptyBuilder ?? (context) => _buildEmpty(), loadingBuilder: widget.loadingBuilder ?? (context) => LayoutBuilder( @@ -194,12 +194,10 @@ class _UserListViewState extends State bool get isListAlreadySorted => widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false; - Widget _buildError(Error error) { - print(error.stackTrace); - + Widget _buildError(Object error) { var message = error.toString(); if (error is DioError) { - final dioError = error as DioError; + final dioError = error; if (dioError.type == DioErrorType.response) { message = dioError.message; } else { diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 6377686b..e7deb356 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter_core/src/users_bloc.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// /// [UserListCore] is a simplified class that allows fetching users while @@ -76,7 +77,7 @@ class UserListCore extends StatefulWidget { final UserListController? userListController; /// The builder that will be used in case of error - final Widget Function(Object error) errorBuilder; + final ErrorBuilder errorBuilder; /// The builder that will be used to build the list final Widget Function(BuildContext context, List users) listBuilder; @@ -170,7 +171,7 @@ class UserListCoreState extends State stream: _buildUserStream(), builder: (context, snapshot) { if (snapshot.hasError) { - return widget.errorBuilder(snapshot.error!); + return widget.errorBuilder(context, snapshot.error!); } if (!snapshot.hasData) { return widget.loadingBuilder(context); From bcd8630235cb2d20338f74c0308f14a55a0a2f8e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 28 Jun 2021 16:40:43 +0530 Subject: [PATCH 474/599] chore(stream_chat): update version.dart Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/version.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index 3e6a44cd..cb3d587f 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header // ignore: constant_identifier_names -const PACKAGE_VERSION = '2.0.0-nullsafety.6'; +const PACKAGE_VERSION = '2.0.0-nullsafety.7'; From f43c14dd5d083ffd9ae92e3a823520f160e458fb Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 28 Jun 2021 16:48:58 +0530 Subject: [PATCH 475/599] feat: Added new docs --- .../stream_chat_flutter/message_list_view.mdx | 37 +++++++++++++ .../channel_list_core.mdx | 49 +++++++++++++++++ .../message_list_core.mdx | 53 ++++++++++++++++++- .../message_search_list_core.mdx | 29 +++++++++- 4 files changed, 166 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx index c68c9bcb..36cf7e39 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx @@ -4,3 +4,40 @@ sidebar_position: 5 title: MessageListView --- +The MessageListView shows the list of messages of the current channel. + +```dart +class ChannelPage extends StatelessWidget { + const ChannelPage({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ChannelHeader(), + body: Column( + children: [ + Expanded( + child: MessageListView( + threadBuilder: (_, parentMessage) { + return ThreadPage( + parent: parentMessage, + ); + }, + ), + ), + MessageInput(), + ], + ), + ); + } +} +``` + +Make sure to have a [StreamChannel] ancestor in order to +provide the information about the channels. +The widget uses a [ListView.custom] to render the list of channels. + +The widget components render the ui based on the first +ancestor of type [StreamChatTheme]. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx index 1133dcd5..7200def2 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx @@ -4,3 +4,52 @@ sidebar_position: 4 title: ChannelListCore --- +`ChannelListCore` is a simplified class that allows fetching a list of +channels while exposing UI builders. + +This allows you to construct your own UI while not having to +worry about the specific logic of fetching channels in your app. + +A `ChannelListController` is used to reload and paginate data. + +```dart +class ChannelListPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: ChannelListCore( + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + errorBuilder: (err) { + return Center( + child: Text('An error has occured'), + ); + }, + emptyBuilder: (context) { + return Center( + child: Text('Nothing here...'), + ); + }, + emptyBuilder: (context) { + return Center( + child: CircularProgressIndicator(), + ); + }, + listBuilder: (context, list) { + return ChannelPage(list); + } + ), + ); + } +} +``` + +Make sure to have a `StreamChatCore` ancestor in order to provide the +information about the channels. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx index 08c8ddbb..b77f2002 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx @@ -2,4 +2,55 @@ id: message_list_core sidebar_position: 5 title: MessageListCore ---- \ No newline at end of file +--- + +`MessageListCore` is a simplified class that allows fetching a list of +messages while exposing UI builders. + +This allows you to construct your own UI while not having to +worry about the specific logic of fetching messages in a channel. + +A `MessageListController` is used to paginate data. + +```dart +class ChannelPage extends StatelessWidget { + const ChannelPage({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + Expanded( + child: MessageListCore( + emptyBuilder: (context) { + return Center( + child: Text('Nothing here...'), + ); + }, + loadingBuilder: (context) { + return Center( + child: CircularProgressIndicator(), + ); + }, + messageListBuilder: (context, list) { + return MessagesPage(list); + }, + errorWidgetBuilder: (context, err) { + return Center( + child: Text('Error'), + ); + }, + ), + ), + ], + ), + ); + } +} +``` + +Make sure to have a `StreamChannel` ancestor in order to provide the +information about the channels. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx index 95d6c8b4..b9c42bd0 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx @@ -2,4 +2,31 @@ id: message_search_list_core sidebar_position: 6 title: MessageSearchListCore ---- \ No newline at end of file +--- + + +`MessageSearchListCore` is a simplified class that allows searching for + messages across channels while exposing UI builders. + A `MessageSearchListController` is used to load and paginate data. + +```dart +class MessageSearchPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: MessageSearchListCore( + messageQuery: _channelQuery, + filters: { + 'members': { + r'$in': [user.id] + } + }, + paginationParams: PaginationParams(limit: 20), + ), + ); + } +} +``` + +Make sure to have a `MessageSearchBloc` ancestor in order to provide the +information about the messages. \ No newline at end of file From 6017a184d68b6b0dcd8ba506f247f9349ce5c430 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 28 Jun 2021 16:50:42 +0530 Subject: [PATCH 476/599] chore(stream_chat_flutter): prepare for v2.0.0-nullsafety.8 Signed-off-by: Sahil Kumar --- packages/stream_chat_flutter/CHANGELOG.md | 52 ++++++++++++------- .../lib/src/user_list_view.dart | 2 +- packages/stream_chat_flutter/pubspec.yaml | 4 +- .../lib/src/user_list_core.dart | 2 +- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index ac8ae565..f007a0e6 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.0.0-nullsafety.8 + +đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.7` +- `ChannelListCore` options property is removed in favor of individual properties + - `options.state` -> bool state + - `options.watch` -> bool watch + - `options.presence` -> bool presence +- `UserListCore` options property is removed in favor of individual properties + - `options.presence` -> bool presence + ## 2.0.0-nullsafety.7 - Minor fixes and improvements @@ -139,7 +149,8 @@ ## 0.2.20+2 -- Added `shouldAddChannel` to ChannelsBloc in order to check if a channel has to be added to the list when a new message arrives +- Added `shouldAddChannel` to ChannelsBloc in order to check if a channel has to be added to the list when a new message + arrives ## 0.2.20+1 @@ -358,27 +369,27 @@ ## 0.2.1-alpha+1 -- Removed the additional `Navigator` in `StreamChat` widget. - It was added to make the app have the `StreamChat` widget as ancestor in every route. - Now the recommended way to add `StreamChat` to your app is using the `builder` property of your `MaterialApp` widget. - Otherwise you can use it in the usual way, but you need to add a `StreamChat` widget to every route of your app. - Read [this issue](https://github.com/GetStream/stream-chat-flutter/issues/47) for more information. +- Removed the additional `Navigator` in `StreamChat` widget. It was added to make the app have the `StreamChat` widget + as ancestor in every route. Now the recommended way to add `StreamChat` to your app is using the `builder` property of + your `MaterialApp` widget. Otherwise you can use it in the usual way, but you need to add a `StreamChat` widget to + every route of your app. Read [this issue](https://github.com/GetStream/stream-chat-flutter/issues/47) for more + information. ```dart @override - Widget build(BuildContext context) { - return MaterialApp( - theme: ThemeData.light(), - darkTheme: ThemeData.dark(), - themeMode: ThemeMode.system, - builder: (context, widget) { - return StreamChat( - child: widget, - client: client, - ); - }, - home: ChannelListPage(), - ); +Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + themeMode: ThemeMode.system, + builder: (context, widget) { + return StreamChat( + child: widget, + client: client, + ); + }, + home: ChannelListPage(), + ); ``` - Fix reaction bubble going below previous message on iOS @@ -462,7 +473,8 @@ - Add gesture (vertical drag down) to close the keyboard -- Add keyboard type parameters (set it to TextInputType.text to show the submit button that will even close the keyboard) +- Add keyboard type parameters (set it to TextInputType.text to show the submit button that will even close the + keyboard) The property showVideoFullScreen was added mainly because of this issue brianegan/chewie#261 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 aa1886b7..58181774 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -83,7 +83,7 @@ class UserListView extends StatefulWidget { /// Direction can be ascending or descending. final List? sort; - /// + /// If true you’ll receive user presence updates via the websocket events final bool? presence; /// Pagination parameters diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index bcebde99..342914d4 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.7 +version: 2.0.0-nullsafety.8 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -36,7 +36,7 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 shimmer: ^2.0.0 - stream_chat_flutter_core: ^2.0.0-nullsafety.7 + stream_chat_flutter_core: ^2.0.0-nullsafety.8 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 6377686b..348e822e 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -98,7 +98,7 @@ class UserListCore extends StatefulWidget { /// created_at or member_count. Direction can be ascending or descending. final List? sort; - /// + /// If true you’ll receive user presence updates via the websocket events final bool? presence; /// Pagination parameters From 8052edf09c37445134c090c5e06ed950f346f045 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 28 Jun 2021 15:20:50 +0200 Subject: [PATCH 477/599] fix tests --- .../test/user_list_core_test.dart | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/stream_chat_flutter_core/test/user_list_core_test.dart b/packages/stream_chat_flutter_core/test/user_list_core_test.dart index 6f21123f..95077e5c 100644 --- a/packages/stream_chat_flutter_core/test/user_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/user_list_core_test.dart @@ -43,7 +43,7 @@ void main() { listBuilder: (_, __) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), ); await tester.pumpWidget(userListCore); @@ -62,7 +62,7 @@ void main() { listBuilder: (_, __) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), ); final mockClient = MockClient(); @@ -91,7 +91,7 @@ void main() { listBuilder: (_, __) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), userListController: controller, ); @@ -125,7 +125,8 @@ void main() { listBuilder: (_, __) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (Object error) => Container(key: errorWidgetKey), + errorBuilder: (BuildContext context, Object error) => + Container(key: errorWidgetKey), ); final mockClient = MockClient(); @@ -170,7 +171,7 @@ void main() { listBuilder: (_, __) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Container(key: emptyWidgetKey), - errorBuilder: (Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), ); final mockClient = MockClient(); @@ -215,7 +216,7 @@ void main() { listBuilder: (_, __) => Container(key: listWidgetKey), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), ); final mockClient = MockClient(); @@ -273,7 +274,7 @@ void main() { ), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), groupAlphabetically: true, ); @@ -341,7 +342,7 @@ void main() { ), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), pagination: pagination, groupAlphabetically: true, ); @@ -446,7 +447,7 @@ void main() { ), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), pagination: pagination.copyWith(limit: limit), groupAlphabetically: true, ); From 9641926f0700c24122f3518a4a2449e87b258987 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 28 Jun 2021 15:31:36 +0200 Subject: [PATCH 478/599] add golden test --- .../test/src/goldens/message_text.png | Bin 0 -> 10173 bytes .../test/src/message_text_test.dart | 55 ++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 packages/stream_chat_flutter/test/src/goldens/message_text.png diff --git a/packages/stream_chat_flutter/test/src/goldens/message_text.png b/packages/stream_chat_flutter/test/src/goldens/message_text.png new file mode 100644 index 0000000000000000000000000000000000000000..dda158b267997036a5c7573fc56fe52d0813412d GIT binary patch literal 10173 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 client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ + 'name': 'test', + })); + when(() => channel.extraData).thenReturn({ + 'name': 'test', + }); + + final messageText = '''a message. +with multiple lines +and a list: +- a. okasd +- b lllll + +cool.'''; + + await tester.pumpWidgetBuilder( + materialAppWrapper()(SimpleFrame( + child: StreamChannel( + channel: channel, + child: Scaffold( + body: MessageText( + message: Message( + text: messageText, + ), + messageTheme: streamTheme.otherMessageTheme, + ), + ), + ), + )), + surfaceSize: const Size(500, 500), + ); + await screenMatchesGolden(tester, 'message_text'); + }, + ); } From bde90618947adc1493c468f0b73b7b66cdbde8d8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 29 Jun 2021 10:10:47 +0200 Subject: [PATCH 479/599] update changelogs --- packages/stream_chat/CHANGELOG.md | 18 ++++++++---------- packages/stream_chat_flutter/CHANGELOG.md | 22 +++++++++++++++++++++- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 0c67f735..d68ef27f 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,6 +1,6 @@ ## 2.0.0-nullsafety.7 -đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.6` +đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.6` - `ConnectUserWithProvider` now requires `tokenProvider` as a required param. (Removed from the constructor) - `client.disconnect()` is now divided into two different functions - `client.closeConnection()` -> for closing user websocket connection. @@ -11,21 +11,19 @@ - `StreamWebSocketError` -> for user websocket related errors. - `StreamChatNetworkError` -> for network related errors. - `client.queryChannels()`, `channel.query()` options param is removed in favor of individual params - - `options.state` -> bool state - - `options.watch` -> bool watch - - `options.presence` -> bool presence -- `client.queryUsers()` options param is removed in favor of individual params - - `options.presence` -> bool presence + - `option.state` -> bool state + - `option.watch` -> bool watch + - `option.presence` -> bool presence -✅ Added +✅ Added - New `Location` enum is introduced for easily changing the client location/baseUrl. - New `client.openConnection()` and `client.closeConnection()` is introduced to connect/disconnect user ws connection. -🔄 Changed +🔄 Changed - `baseURL` is now deprecated in favor of using `Location` to change data location. -🐞 Fixed -- Client does not return without internet connection [#369](https://github.com/GetStream/stream-chat-flutter/issues/369) +🐞 Fixed +- [#369](https://github.com/GetStream/stream-chat-flutter/issues/369): Client does not return without internet connection ## 2.0.0-nullsafety.6 diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index f007a0e6..7955e22a 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,12 +1,32 @@ ## 2.0.0-nullsafety.8 -đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.7` +đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.7` - `ChannelListCore` options property is removed in favor of individual properties - `options.state` -> bool state - `options.watch` -> bool watch - `options.presence` -> bool presence - `UserListCore` options property is removed in favor of individual properties - `options.presence` -> bool presence +- `MessageBuilder` and `ParentMessageBuilder` signature is now +```dart +typedef MessageBuilder = Widget Function( + BuildContext, + MessageDetails, + List, + MessageWidget defaultMessageWidget, +); +``` +the last parameter is the default `MessageWidget` +You can call `.copyWith` to customize just a subset of properties + +✅ Added +- TypingIndicator now has a property called `parentId` to show typing indicator specific to threads +- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView header/footer + +🐞 Fixed +- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing message +- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case +- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload ## 2.0.0-nullsafety.7 From afb7b1f3ad7221b0c4f0132af03bf8a0f0296364 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 29 Jun 2021 10:15:08 +0200 Subject: [PATCH 480/599] update changelogs --- packages/stream_chat_flutter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 7955e22a..98530bc2 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -27,6 +27,7 @@ You can call `.copyWith` to customize just a subset of properties - [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing message - Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case - [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload +- `MessageListView` not rendering if the user is not a member of the channel ## 2.0.0-nullsafety.7 From cd4892683b0d0664b04afbe5d19af9f6d590c61b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 29 Jun 2021 10:27:54 +0200 Subject: [PATCH 481/599] update changelogs --- packages/stream_chat_flutter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 98530bc2..bb379565 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -22,6 +22,7 @@ You can call `.copyWith` to customize just a subset of properties ✅ Added - TypingIndicator now has a property called `parentId` to show typing indicator specific to threads - [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView header/footer +- `MessageWidget` accepts a `userAvatarBuilder` 🐞 Fixed - [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing message From 29f234f69bdc2ef0890d313e47691b53e0e54844 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 29 Jun 2021 11:09:27 +0200 Subject: [PATCH 482/599] update error builder --- .../lib/src/user_list_view.dart | 63 +++++++------------ 1 file changed, 23 insertions(+), 40 deletions(-) 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 f915fcb0..85666413 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -194,50 +194,33 @@ class _UserListViewState extends State bool get isListAlreadySorted => widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false; - Widget _buildError(Object error) { - var message = error.toString(); - if (error is DioError) { - final dioError = error; - if (dioError.type == DioErrorType.response) { - message = dioError.message; - } else { - message = 'Check your connection and retry'; - } - } - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text.rich( - const TextSpan( - children: [ - WidgetSpan( - child: Padding( - padding: EdgeInsets.only( - right: 2, + Widget _buildError(Object error) => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text.rich( + const TextSpan( + children: [ + WidgetSpan( + child: Padding( + padding: EdgeInsets.only( + right: 2, + ), + child: Icon(Icons.error_outline), ), - child: Icon(Icons.error_outline), ), - ), - TextSpan(text: 'Error loading channels'), - ], + TextSpan(text: 'Error loading users'), + ], + ), + style: Theme.of(context).textTheme.headline6, ), - style: Theme.of(context).textTheme.headline6, - ), - Padding( - padding: const EdgeInsets.only( - top: 16, + TextButton( + onPressed: () => _userListController.loadData!(), + child: const Text('Retry'), ), - child: Text(message), - ), - TextButton( - onPressed: () => _userListController.loadData!(), - child: const Text('Retry'), - ), - ], - ), - ); - } + ], + ), + ); Widget _buildEmpty() => LayoutBuilder( builder: (context, viewportConstraints) => SingleChildScrollView( From 81b69e95bf1b9bea80470844a02a372994fa5753 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 29 Jun 2021 11:29:42 +0200 Subject: [PATCH 483/599] accept list controllers as params in ui package listviews --- .../lib/src/channel_list_view.dart | 10 +++++++++- .../lib/src/message_list_view.dart | 8 +++++++- .../lib/src/message_search_list_view.dart | 11 +++++++++-- .../stream_chat_flutter/lib/src/user_list_view.dart | 9 ++++++++- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index d131c33b..ddf3e753 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -89,6 +89,7 @@ class ChannelListView extends StatefulWidget { this.onMoreDetailsPressed, this.onDeletePressed, this.swipeActions, + this.channelListController, }) : super(key: key); /// If true a default swipe to action behaviour will be added to this widget @@ -187,6 +188,12 @@ class ChannelListView extends StatefulWidget { /// List of actions for slidable final List? swipeActions; + /// A [ChannelListController] allows reloading and pagination. + /// Use [ChannelListController.loadData] and + /// [ChannelListController.paginateData] respectively for reloading and + /// pagination. + final ChannelListController? channelListController; + @override _ChannelListViewState createState() => _ChannelListViewState(); } @@ -194,7 +201,8 @@ class ChannelListView extends StatefulWidget { class _ChannelListViewState extends State { final _slideController = SlidableController(); - final _channelListController = ChannelListController(); + late final _channelListController = + widget.channelListController ?? ChannelListController(); @override Widget build(BuildContext context) { 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 74942259..e5063228 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -172,6 +172,7 @@ class MessageListView extends StatefulWidget { this.usernameBuilder, this.showFloatingDateDivider = true, this.threadSeparatorBuilder, + this.messageListController, }) : super(key: key); /// Function used to build a custom message widget @@ -296,6 +297,10 @@ class MessageListView extends StatefulWidget { /// Builder used to build the thread separator in case it's a thread view final WidgetBuilder? threadSeparatorBuilder; + /// A [MessageListController] allows pagination. + /// Use [ChannelListController.paginateData] pagination. + final MessageListController? messageListController; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -347,7 +352,8 @@ class _MessageListViewState extends State { bool _inBetweenList = false; - final MessageListController _messageListController = MessageListController(); + late final MessageListController _messageListController = + widget.messageListController ?? MessageListController(); @override Widget build(BuildContext context) => MessageListCore( 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 8b8a8dec..c27e2c90 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 @@ -68,6 +68,7 @@ class MessageSearchListView extends StatefulWidget { this.errorBuilder, this.loadingBuilder, this.childBuilder, + this.messageSearchListController, }) : super(key: key); /// Message String to search on @@ -127,13 +128,19 @@ class MessageSearchListView extends StatefulWidget { /// The builder that will be used in case of loading final WidgetBuilder? loadingBuilder; + /// A [MessageSearchListController] allows reloading and pagination. + /// Use [MessageSearchListController.loadData] and + /// [MessageSearchListController.paginateData] respectively for reloading and + /// pagination. + final MessageSearchListController? messageSearchListController; + @override _MessageSearchListViewState createState() => _MessageSearchListViewState(); } class _MessageSearchListViewState extends State { - final MessageSearchListController _messageSearchListController = - MessageSearchListController(); + late final MessageSearchListController _messageSearchListController = + widget.messageSearchListController ?? MessageSearchListController(); @override Widget build(BuildContext context) => MessageSearchListCore( 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 85666413..452cebfd 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -64,6 +64,7 @@ class UserListView extends StatefulWidget { this.emptyBuilder, this.loadingBuilder, this.listBuilder, + this.userListController, }) : assert( crossAxisCount == 1 || groupAlphabetically == false, 'Cannot group alphabetically when crossAxisCount > 1', @@ -140,6 +141,11 @@ class UserListView extends StatefulWidget { /// The builder used when the channel list is empty. final WidgetBuilder? emptyBuilder; + /// A [UserListController] allows reloading and pagination. + /// Use [UserListController.loadData] and [UserListController.paginateData] + /// respectively for reloading and pagination. + final UserListController? userListController; + @override _UserListViewState createState() => _UserListViewState(); } @@ -148,7 +154,8 @@ class _UserListViewState extends State with WidgetsBindingObserver { bool get _isListView => widget.crossAxisCount == 1; - final UserListController _userListController = UserListController(); + late final UserListController _userListController = + widget.userListController ?? UserListController(); @override Widget build(BuildContext context) { From a9c7abb0d697ba52fa13fd3bec8e66ca243b25dd Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 29 Jun 2021 11:43:28 +0200 Subject: [PATCH 484/599] use getters --- packages/stream_chat_flutter/lib/src/channel_list_view.dart | 5 +++-- packages/stream_chat_flutter/lib/src/message_list_view.dart | 5 +++-- .../lib/src/message_search_list_view.dart | 5 +++-- packages/stream_chat_flutter/lib/src/user_list_view.dart | 5 +++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index ddf3e753..86712c8a 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -201,8 +201,9 @@ class ChannelListView extends StatefulWidget { class _ChannelListViewState extends State { final _slideController = SlidableController(); - late final _channelListController = - widget.channelListController ?? ChannelListController(); + final _defaultController = ChannelListController(); + ChannelListController get _channelListController => + widget.channelListController ?? _defaultController; @override Widget build(BuildContext context) { 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 e5063228..ad068c4d 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -352,8 +352,9 @@ class _MessageListViewState extends State { bool _inBetweenList = false; - late final MessageListController _messageListController = - widget.messageListController ?? MessageListController(); + final _defaultController = MessageListController(); + MessageListController get _messageListController => + widget.messageListController ?? _defaultController; @override Widget build(BuildContext context) => MessageListCore( 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 c27e2c90..50c1d47f 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 @@ -139,8 +139,9 @@ class MessageSearchListView extends StatefulWidget { } class _MessageSearchListViewState extends State { - late final MessageSearchListController _messageSearchListController = - widget.messageSearchListController ?? MessageSearchListController(); + final _defaultController = MessageSearchListController(); + MessageSearchListController get _messageSearchListController => + widget.messageSearchListController ?? _defaultController; @override Widget build(BuildContext context) => MessageSearchListCore( 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 452cebfd..e027989e 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -154,8 +154,9 @@ class _UserListViewState extends State with WidgetsBindingObserver { bool get _isListView => widget.crossAxisCount == 1; - late final UserListController _userListController = - widget.userListController ?? UserListController(); + final _defaultController = UserListController(); + UserListController get _userListController => + widget.userListController ?? _defaultController; @override Widget build(BuildContext context) { From 418c9ae64ec6976ce893378abdad4f58d016ea83 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 29 Jun 2021 11:45:59 +0200 Subject: [PATCH 485/599] use late --- packages/stream_chat_flutter/lib/src/channel_list_view.dart | 2 +- packages/stream_chat_flutter/lib/src/message_list_view.dart | 2 +- .../stream_chat_flutter/lib/src/message_search_list_view.dart | 2 +- packages/stream_chat_flutter/lib/src/user_list_view.dart | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 86712c8a..b14e0b75 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -201,7 +201,7 @@ class ChannelListView extends StatefulWidget { class _ChannelListViewState extends State { final _slideController = SlidableController(); - final _defaultController = ChannelListController(); + late final _defaultController = ChannelListController(); ChannelListController get _channelListController => widget.channelListController ?? _defaultController; 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 ad068c4d..e46f0d16 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -352,7 +352,7 @@ class _MessageListViewState extends State { bool _inBetweenList = false; - final _defaultController = MessageListController(); + late final _defaultController = MessageListController(); MessageListController get _messageListController => widget.messageListController ?? _defaultController; 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 50c1d47f..1a67702f 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 @@ -139,7 +139,7 @@ class MessageSearchListView extends StatefulWidget { } class _MessageSearchListViewState extends State { - final _defaultController = MessageSearchListController(); + late final _defaultController = MessageSearchListController(); MessageSearchListController get _messageSearchListController => widget.messageSearchListController ?? _defaultController; 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 e027989e..07b76f62 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -154,7 +154,7 @@ class _UserListViewState extends State with WidgetsBindingObserver { bool get _isListView => widget.crossAxisCount == 1; - final _defaultController = UserListController(); + late final _defaultController = UserListController(); UserListController get _userListController => widget.userListController ?? _defaultController; From dde047b0e9bfdb1dcb5961faddc7dc1228c9fd9a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 29 Jun 2021 12:00:40 +0200 Subject: [PATCH 486/599] fix message and user list core controller setup --- .../lib/src/message_search_list_core.dart | 20 +++++++++++++++---- .../lib/src/user_list_core.dart | 19 ++++++++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index a1eb5d68..636a879f 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -114,15 +114,22 @@ class MessageSearchListCoreState extends State { if (newMessageSearchBloc != _messageSearchBloc) { _messageSearchBloc = newMessageSearchBloc; loadData(); - if (widget.messageSearchListController != null) { - widget.messageSearchListController!.loadData = loadData; - widget.messageSearchListController!.paginateData = paginateData; - } } super.didChangeDependencies(); } + void _setupController() { + widget.messageSearchListController!.loadData = loadData; + widget.messageSearchListController!.paginateData = paginateData; + } + + @override + void initState() { + super.initState(); + _setupController(); + } + @override Widget build(BuildContext context) => _buildListView(_messageSearchBloc!); @@ -176,6 +183,11 @@ class MessageSearchListCoreState extends State { oldWidget.paginationParams?.toJson().toString()) { loadData(); } + + if (widget.messageSearchListController != + oldWidget.messageSearchListController) { + _setupController(); + } } } diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index e7deb356..05a8432d 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -128,14 +128,21 @@ class UserListCoreState extends State if (newUsersBloc != _usersBloc) { _usersBloc = newUsersBloc; loadData(); - if (widget.userListController != null) { - widget.userListController!.loadData = loadData; - widget.userListController!.paginateData = paginateData; - } } super.didChangeDependencies(); } + @override + void initState() { + super.initState(); + _setupController(); + } + + void _setupController() { + widget.userListController!.loadData = loadData; + widget.userListController!.paginateData = paginateData; + } + @override Widget build(BuildContext context) => _buildListView(); @@ -212,6 +219,10 @@ class UserListCoreState extends State oldWidget.pagination?.toJson().toString()) { loadData(); } + + if (widget.userListController != oldWidget.userListController) { + _setupController(); + } } } From ba927bf37928b1beb7d96e0e037530aeefb4cb84 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 29 Jun 2021 12:20:37 +0200 Subject: [PATCH 487/599] add null check --- .../lib/src/message_search_list_core.dart | 6 ++++-- .../stream_chat_flutter_core/lib/src/user_list_core.dart | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index 636a879f..d354daec 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -120,8 +120,10 @@ class MessageSearchListCoreState extends State { } void _setupController() { - widget.messageSearchListController!.loadData = loadData; - widget.messageSearchListController!.paginateData = paginateData; + if (widget.messageSearchListController != null) { + widget.messageSearchListController!.loadData = loadData; + widget.messageSearchListController!.paginateData = paginateData; + } } @override diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 05a8432d..db6b3586 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -139,8 +139,10 @@ class UserListCoreState extends State } void _setupController() { - widget.userListController!.loadData = loadData; - widget.userListController!.paginateData = paginateData; + if (widget.userListController != null) { + widget.userListController!.loadData = loadData; + widget.userListController!.paginateData = paginateData; + } } @override From be76d25aa308ad89dfe553b96c638cf68232ae9b Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 29 Jun 2021 16:20:29 +0530 Subject: [PATCH 488/599] update changelogs Signed-off-by: Sahil Kumar --- packages/stream_chat/CHANGELOG.md | 35 +++++++++++-------- packages/stream_chat_flutter/CHANGELOG.md | 26 ++++++++------ .../stream_chat_flutter_core/CHANGELOG.md | 14 ++++---- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index d68ef27f..b82d3025 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,29 +1,36 @@ ## 2.0.0-nullsafety.7 -đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.6` +đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.6` + - `ConnectUserWithProvider` now requires `tokenProvider` as a required param. (Removed from the constructor) -- `client.disconnect()` is now divided into two different functions - - `client.closeConnection()` -> for closing user websocket connection. - - `client.disconnectUser()` -> for disconnecting user and resetting client state. +- `client.disconnect()` is now divided into two different functions + - `client.closeConnection()` -> for closing user websocket connection. + - `client.disconnectUser()` -> for disconnecting user and resetting client state. - `client.devToken()` now returns a `Token` model instead of `String`. - `ApiError` is removed in favor of `StreamChatError` - - `StreamChatError` -> parent type for all the stream errors. - - `StreamWebSocketError` -> for user websocket related errors. - - `StreamChatNetworkError` -> for network related errors. + - `StreamChatError` -> parent type for all the stream errors. + - `StreamWebSocketError` -> for user websocket related errors. + - `StreamChatNetworkError` -> for network related errors. - `client.queryChannels()`, `channel.query()` options param is removed in favor of individual params - - `option.state` -> bool state - - `option.watch` -> bool watch - - `option.presence` -> bool presence + - `option.state` -> bool state + - `option.watch` -> bool watch + - `option.presence` -> bool presence +- `client.queryUsers()` options param is removed in favor of individual params + - `option.presence` -> bool presence + +✅ Added -✅ Added - New `Location` enum is introduced for easily changing the client location/baseUrl. - New `client.openConnection()` and `client.closeConnection()` is introduced to connect/disconnect user ws connection. -🔄 Changed +🔄 Changed + - `baseURL` is now deprecated in favor of using `Location` to change data location. -🐞 Fixed -- [#369](https://github.com/GetStream/stream-chat-flutter/issues/369): Client does not return without internet connection +🐞 Fixed + +- [#369](https://github.com/GetStream/stream-chat-flutter/issues/369): Client does not return without internet + connection ## 2.0.0-nullsafety.6 diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index bb379565..3407178c 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,31 +1,37 @@ ## 2.0.0-nullsafety.8 -đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.7` +đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.7` + - `ChannelListCore` options property is removed in favor of individual properties - `options.state` -> bool state - `options.watch` -> bool watch - `options.presence` -> bool presence -- `UserListCore` options property is removed in favor of individual properties +- `UserListView` options property is removed in favor of individual properties - `options.presence` -> bool presence - `MessageBuilder` and `ParentMessageBuilder` signature is now + ```dart typedef MessageBuilder = Widget Function( - BuildContext, - MessageDetails, - List, - MessageWidget defaultMessageWidget, -); + BuildContext, + MessageDetails, + List, + MessageWidget defaultMessageWidget, + ); ``` + the last parameter is the default `MessageWidget` You can call `.copyWith` to customize just a subset of properties -✅ Added +✅ Added + - TypingIndicator now has a property called `parentId` to show typing indicator specific to threads - [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView header/footer - `MessageWidget` accepts a `userAvatarBuilder` -🐞 Fixed -- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing message +🐞 Fixed + +- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing + message - Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case - [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload - `MessageListView` not rendering if the user is not a member of the channel diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 7af9b177..4453659e 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,13 +1,15 @@ ## 2.0.0-nullsafety.8 đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.7` -- `channelsBloc.queryChannels()`, `ChannelListCore` options param/property is removed in favor of individual params/properties - - `options.state` -> bool state - - `options.watch` -> bool watch - - `options.presence` -> bool presence + +- `channelsBloc.queryChannels()`, `ChannelListCore` options param/property is removed in favor of individual + params/properties + - `options.state` -> bool state + - `options.watch` -> bool watch + - `options.presence` -> bool presence - `usersBloc.queryUsers()`, `UserListCore` options param/property is removed in favor of individual params/properties - - `options.presence` -> bool presence - + - `options.presence` -> bool presence + ## 2.0.0-nullsafety.7 * Fixed a bug with connectivity implementation From 3c4881daed70a974db83eea67960f4ead0e13201 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 29 Jun 2021 18:20:06 +0530 Subject: [PATCH 489/599] feat: Added new docs --- .../stream_chat_flutter/channel_list_view.mdx | 31 +++++++++++++ .../stream_chat_flutter/introduction.mdx | 3 +- .../stream_chat_flutter/message_input.mdx | 44 +++++++++++++++++++ .../stream_chat_flutter/message_list_view.mdx | 6 ++- 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx index d1c7ec9e..a88c617d 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx @@ -4,3 +4,34 @@ sidebar_position: 4 title: ChannelListView --- +![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view.png) +![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view_paint.png) + +It shows the list of current channels. + +```dart +class ChannelListPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: ChannelListView( + filter: Filter.in_('members', [StreamChat.of(context).user.id]), + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), + ), + ); + } +} +``` + + +Make sure to have a [StreamChat] ancestor in order to provide the +information about the channels. +The widget uses a [ListView.custom] to render the list of channels. + +The widget components render the ui based on the first ancestor of +type [StreamChatTheme]. +Modify it to change the widget appearance. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx index fc8f385e..d143e9fd 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx @@ -2,4 +2,5 @@ id: introduction sidebar_position: 1 title: Introduction ---- \ No newline at end of file +--- + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx index 575a2ee5..16719619 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx @@ -4,3 +4,47 @@ sidebar_position: 6 title: Message Input --- +The `MessageInput` widget deals with common elements related to sending a message such as a text field for typing text, +adding media to a message, quoting a message and more. The message input uses a stream channel ancestor to send a message. +The message input can be used along with a message list view to display messages. + +The message input also has commands such as Giphy and more which can be added. It can also be used to add custom attachments to your messages. +The message input is customisable and the orientation of the icons the text field and the send button can be changed. + +Widget used to enter the message and add attachments + +```dart +class ChannelPage extends StatelessWidget { + const ChannelPage({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ChannelHeader(), + body: Column( + children: [ + Expanded( + child: MessageListView( + threadBuilder: (_, parentMessage) { + return ThreadPage( + parent: parentMessage, + ); + }, + ), + ), + MessageInput(), + ], + ), + ); + } +} +``` + +You usually put this widget in the same page of a [MessageListView] +as the bottom widget. + +The widget renders the ui based on the first ancestor of +type [StreamChatTheme]. +Modify it to change the widget appearance. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx index 36cf7e39..c3a3efc2 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx @@ -4,7 +4,11 @@ sidebar_position: 5 title: MessageListView --- -The MessageListView shows the list of messages of the current channel. +The `MessageListView` shows the list of messages of the current channel. It has inbuilt support for +common messaging functionality: displaying and editing messages, adding / modifying reactions, support +for quoting messages, pinning messages, and more. + +An example of how you can use the MessageListView is: ```dart class ChannelPage extends StatelessWidget { From bc0a70ad2530f967b0b10f61d75936cccedd5946 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 29 Jun 2021 16:12:51 +0200 Subject: [PATCH 490/599] rename colors --- .../example/lib/tutorial-part-3.dart | 4 +- .../example/lib/tutorial-part-6.dart | 4 +- .../lib/src/attachment/attachment_title.dart | 2 +- .../attachment_upload_state_builder.dart | 10 +- .../lib/src/attachment/attachment_widget.dart | 8 +- .../lib/src/attachment/file_attachment.dart | 25 +- .../lib/src/attachment/giphy_attachment.dart | 31 +-- .../lib/src/attachment/image_attachment.dart | 4 +- .../lib/src/attachment_actions_modal.dart | 24 +- .../lib/src/back_button.dart | 2 +- .../lib/src/channel_bottom_sheet.dart | 16 +- .../lib/src/channel_image.dart | 12 +- .../lib/src/channel_info.dart | 2 +- .../lib/src/channel_list_header.dart | 6 +- .../lib/src/channel_list_view.dart | 32 +-- .../lib/src/date_divider.dart | 2 +- .../lib/src/deleted_message.dart | 4 +- .../lib/src/group_image.dart | 4 +- .../lib/src/image_footer.dart | 13 +- .../lib/src/image_header.dart | 4 +- .../lib/src/info_tile.dart | 4 +- .../lib/src/media_list_view.dart | 11 +- .../lib/src/mention_tile.dart | 4 +- .../lib/src/message_actions_modal.dart | 18 +- .../lib/src/message_input.dart | 129 +++++----- .../lib/src/message_list_view.dart | 16 +- .../lib/src/message_reactions_modal.dart | 4 +- .../lib/src/message_search_list_view.dart | 6 +- .../lib/src/message_widget.dart | 18 +- .../lib/src/option_list_tile.dart | 4 +- .../lib/src/quoted_message_widget.dart | 4 +- .../lib/src/reaction_bubble.dart | 4 +- .../lib/src/reaction_picker.dart | 2 +- .../lib/src/sending_indicator.dart | 2 +- .../lib/src/stream_chat.dart | 4 +- .../lib/src/stream_chat_theme.dart | 231 +++++++++--------- .../lib/src/swipeable.dart | 5 +- .../lib/src/system_message.dart | 2 +- .../lib/src/upload_progress_indicator.dart | 2 +- .../lib/src/url_attachment.dart | 4 +- .../lib/src/user_avatar.dart | 8 +- .../lib/src/user_item.dart | 6 +- .../lib/src/user_list_view.dart | 8 +- .../stream_chat_flutter/lib/src/utils.dart | 17 +- .../lib/src/video_thumbnail_image.dart | 4 +- .../example/lib/main.dart | 8 +- 46 files changed, 379 insertions(+), 355 deletions(-) diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart index fc55f6d9..6789a69d 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart @@ -101,12 +101,12 @@ class ChannelListPage extends StatelessWidget { StreamChatTheme.of(context).channelPreviewTheme.title!.copyWith( color: StreamChatTheme.of(context) .colorTheme - .black + .textHighEmphasis .withOpacity(opacity), ), ), subtitle: Text(subtitle), - trailing: channel.state!.unreadCount! > 0 + trailing: channel.state!.unreadCount > 0 ? CircleAvatar( radius: 10, child: Text(channel.state!.unreadCount.toString()), diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart index c49ffaf1..e4f5b8a5 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart @@ -50,9 +50,9 @@ class MyApp extends StatelessWidget { ), ), otherMessageTheme: MessageTheme( - messageBackgroundColor: colorTheme.black, + messageBackgroundColor: colorTheme.textHighEmphasis, messageText: TextStyle( - color: colorTheme.white, + color: colorTheme.barsBg, ), avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(8), diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart index ba369e5d..b068a8f9 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart @@ -35,7 +35,7 @@ class AttachmentTitle extends StatelessWidget { attachment.title!, overflow: TextOverflow.ellipsis, style: messageTheme.messageText?.copyWith( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: StreamChatTheme.of(context).colorTheme.accentPrimary, fontWeight: FontWeight.bold, ), ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart index 825def58..b1b29c4c 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 @@ -129,7 +129,7 @@ class _PreparingState extends StatelessWidget { alignment: Alignment.topRight, child: _IconButton( icon: StreamSvgIcon.close( - color: StreamChatTheme.of(context).colorTheme.white, + color: StreamChatTheme.of(context).colorTheme.barsBg, ), onPressed: () => channel.cancelAttachmentUpload(attachmentId), ), @@ -169,7 +169,7 @@ class _InProgressState extends StatelessWidget { alignment: Alignment.topRight, child: _IconButton( icon: StreamSvgIcon.close( - color: StreamChatTheme.of(context).colorTheme.white, + color: StreamChatTheme.of(context).colorTheme.barsBg, ), onPressed: () => channel.cancelAttachmentUpload(attachmentId), ), @@ -208,7 +208,7 @@ class _FailedState extends StatelessWidget { children: [ _IconButton( icon: StreamSvgIcon.retry( - color: theme.colorTheme.white, + color: theme.colorTheme.barsBg, ), onPressed: () { channel.retryAttachmentUpload(messageId, attachmentId); @@ -228,7 +228,7 @@ class _FailedState extends StatelessWidget { child: Text( 'UPLOAD ERROR', style: theme.textTheme.footnote.copyWith( - color: theme.colorTheme.white, + color: theme.colorTheme.barsBg, ), ), ), @@ -247,7 +247,7 @@ class _SuccessState extends StatelessWidget { backgroundColor: StreamChatTheme.of(context).colorTheme.overlayDark, maxRadius: 12, child: StreamSvgIcon.check( - color: StreamChatTheme.of(context).colorTheme.white, + color: StreamChatTheme.of(context).colorTheme.barsBg, ), ), ); 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 ee79d959..e92087b5 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart @@ -75,12 +75,14 @@ class AttachmentError extends StatelessWidget { child: Container( width: size?.width, height: size?.height, - color: - StreamChatTheme.of(context).colorTheme.accentRed.withOpacity(.1), + color: StreamChatTheme.of(context) + .colorTheme + .accentError + .withOpacity(.1), child: Center( child: Icon( Icons.error_outline, - color: StreamChatTheme.of(context).colorTheme.black, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart index 7ec5fb04..99c3ec40 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -54,10 +54,10 @@ class FileAttachment extends AttachmentWidget { width: size?.width ?? 100, height: 56, decoration: BoxDecoration( - color: colorTheme.white, + color: colorTheme.barsBg, borderRadius: BorderRadius.circular(12), border: Border.all( - color: colorTheme.greyWhisper, + color: colorTheme.borders, ), ), child: Row( @@ -141,8 +141,8 @@ class FileAttachment extends AttachmentWidget { final colorTheme = StreamChatTheme.of(context).colorTheme; return Shimmer.fromColors( - baseColor: colorTheme.greyGainsboro, - highlightColor: colorTheme.whiteSmoke, + baseColor: colorTheme.disabled, + highlightColor: colorTheme.inputBg, child: image, ); }, @@ -217,7 +217,7 @@ class FileAttachment extends AttachmentWidget { preparing: () => Padding( padding: const EdgeInsets.all(8), child: _buildButton( - icon: StreamSvgIcon.close(color: theme.colorTheme.white), + icon: StreamSvgIcon.close(color: theme.colorTheme.barsBg), fillColor: theme.colorTheme.overlayDark, onPressed: () => channel.cancelAttachmentUpload(attachmentId), ), @@ -225,7 +225,7 @@ class FileAttachment extends AttachmentWidget { inProgress: (_, __) => Padding( padding: const EdgeInsets.all(8), child: _buildButton( - icon: StreamSvgIcon.close(color: theme.colorTheme.white), + icon: StreamSvgIcon.close(color: theme.colorTheme.barsBg), fillColor: theme.colorTheme.overlayDark, onPressed: () => channel.cancelAttachmentUpload(attachmentId), ), @@ -233,15 +233,15 @@ class FileAttachment extends AttachmentWidget { success: () => Padding( padding: const EdgeInsets.all(8), child: CircleAvatar( - backgroundColor: theme.colorTheme.accentBlue, + backgroundColor: theme.colorTheme.accentPrimary, maxRadius: 12, - child: StreamSvgIcon.check(color: theme.colorTheme.white), + child: StreamSvgIcon.check(color: theme.colorTheme.barsBg), ), ), failed: (_) => Padding( padding: const EdgeInsets.all(8), child: _buildButton( - icon: StreamSvgIcon.retry(color: theme.colorTheme.white), + icon: StreamSvgIcon.retry(color: theme.colorTheme.barsBg), fillColor: theme.colorTheme.overlayDark, onPressed: () => channel.retryAttachmentUpload( message.id, @@ -253,7 +253,8 @@ class FileAttachment extends AttachmentWidget { if (message.status == MessageSendingStatus.sent) { trailingWidget = IconButton( - icon: StreamSvgIcon.cloudDownload(color: theme.colorTheme.black), + icon: StreamSvgIcon.cloudDownload( + color: theme.colorTheme.textHighEmphasis), visualDensity: VisualDensity.compact, splashRadius: 16, onPressed: () { @@ -272,7 +273,7 @@ class FileAttachment extends AttachmentWidget { final theme = StreamChatTheme.of(context); final size = attachment.file?.size ?? attachment.extraData['file_size']; final textStyle = theme.textTheme.footnote.copyWith( - color: theme.colorTheme.grey, + color: theme.colorTheme.textLowEmphasis, ); return attachment.uploadState.when( preparing: () => Text(fileSize(size), style: textStyle), @@ -282,7 +283,7 @@ class FileAttachment extends AttachmentWidget { showBackground: false, padding: EdgeInsets.zero, textStyle: textStyle, - progressIndicatorColor: theme.colorTheme.accentBlue, + progressIndicatorColor: theme.colorTheme.accentPrimary, ), success: () => Text(fileSize(size), style: textStyle), failed: (_) => Text('UPLOAD ERROR', style: textStyle), diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart index 1572061e..f14abffc 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -51,7 +51,7 @@ class GiphyAttachment extends AttachmentWidget { mainAxisSize: MainAxisSize.min, children: [ Card( - color: StreamChatTheme.of(context).colorTheme.white, + color: StreamChatTheme.of(context).colorTheme.barsBg, elevation: 2, clipBehavior: Clip.hardEdge, shape: const RoundedRectangleBorder( @@ -83,7 +83,7 @@ class GiphyAttachment extends AttachmentWidget { style: TextStyle( color: StreamChatTheme.of(context) .colorTheme - .black + .textHighEmphasis .withOpacity(0.5), ), overflow: TextOverflow.ellipsis, @@ -117,7 +117,7 @@ class GiphyAttachment extends AttachmentWidget { Container( color: StreamChatTheme.of(context) .colorTheme - .black + .textHighEmphasis .withOpacity(0.2), width: double.infinity, height: 0.5, @@ -141,7 +141,7 @@ class GiphyAttachment extends AttachmentWidget { .copyWith( color: StreamChatTheme.of(context) .colorTheme - .black + .textHighEmphasis .withOpacity(0.5), ), ), @@ -152,7 +152,7 @@ class GiphyAttachment extends AttachmentWidget { width: 0.5, color: StreamChatTheme.of(context) .colorTheme - .black + .textHighEmphasis .withOpacity(0.2), height: 50, ), @@ -173,7 +173,7 @@ class GiphyAttachment extends AttachmentWidget { .copyWith( color: StreamChatTheme.of(context) .colorTheme - .black + .textHighEmphasis .withOpacity(0.5), ), maxLines: 1, @@ -185,7 +185,7 @@ class GiphyAttachment extends AttachmentWidget { width: 0.5, color: StreamChatTheme.of(context) .colorTheme - .black + .textHighEmphasis .withOpacity(0.2), height: 50, ), @@ -203,7 +203,7 @@ class GiphyAttachment extends AttachmentWidget { style: TextStyle( color: StreamChatTheme.of(context) .colorTheme - .accentBlue, + .accentPrimary, fontWeight: FontWeight.bold, ), ), @@ -226,7 +226,7 @@ class GiphyAttachment extends AttachmentWidget { StreamSvgIcon.eye( color: StreamChatTheme.of(context) .colorTheme - .black + .textHighEmphasis .withOpacity(0.5), size: 16, ), @@ -241,7 +241,7 @@ class GiphyAttachment extends AttachmentWidget { .copyWith( color: StreamChatTheme.of(context) .colorTheme - .black + .textHighEmphasis .withOpacity(0.5)), ), ], @@ -306,8 +306,8 @@ class GiphyAttachment extends AttachmentWidget { final colorTheme = StreamChatTheme.of(context).colorTheme; return Shimmer.fromColors( - baseColor: colorTheme.greyGainsboro, - highlightColor: colorTheme.whiteSmoke, + baseColor: colorTheme.disabled, + highlightColor: colorTheme.inputBg, child: image, ); }, @@ -322,7 +322,7 @@ class GiphyAttachment extends AttachmentWidget { child: Material( color: StreamChatTheme.of(context) .colorTheme - .black + .textHighEmphasis .withOpacity(.5), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -335,13 +335,14 @@ class GiphyAttachment extends AttachmentWidget { child: Row( children: [ StreamSvgIcon.lightning( - color: StreamChatTheme.of(context).colorTheme.white, + color: StreamChatTheme.of(context).colorTheme.barsBg, size: 16, ), Text( 'GIPHY', style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.white, + color: + StreamChatTheme.of(context).colorTheme.barsBg, fontWeight: FontWeight.bold, fontSize: 11, ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart index c677435a..20174567 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart @@ -103,8 +103,8 @@ class ImageAttachment extends AttachmentWidget { ); final colorTheme = StreamChatTheme.of(context).colorTheme; return Shimmer.fromColors( - baseColor: colorTheme.greyGainsboro, - highlightColor: colorTheme.whiteSmoke, + baseColor: colorTheme.disabled, + highlightColor: colorTheme.inputBg, child: image, ); }, diff --git a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart index 363fac45..ea189b91 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart @@ -72,7 +72,7 @@ class AttachmentActionsModal extends StatelessWidget { 'Reply', StreamSvgIcon.iconCurveLineLeftUp( size: 24, - color: theme.colorTheme.grey, + color: theme.colorTheme.textLowEmphasis, ), () { Navigator.pop(context, ReturnActionType.reply); @@ -83,7 +83,7 @@ class AttachmentActionsModal extends StatelessWidget { 'Show in Chat', StreamSvgIcon.eye( size: 24, - color: theme.colorTheme.black, + color: theme.colorTheme.textHighEmphasis, ), onShowMessage, ), @@ -93,7 +93,7 @@ class AttachmentActionsModal extends StatelessWidget { 'Save ${message.attachments[currentIndex].type == 'video' ? 'Video' : 'Image'}', StreamSvgIcon.iconSave( size: 24, - color: theme.colorTheme.grey, + color: theme.colorTheme.textLowEmphasis, ), () { final attachment = message.attachments[currentIndex]; @@ -144,7 +144,7 @@ class AttachmentActionsModal extends StatelessWidget { 'Delete', StreamSvgIcon.delete( size: 24, - color: theme.colorTheme.accentRed, + color: theme.colorTheme.accentError, ), () { final channel = StreamChannel.of(context).channel; @@ -165,7 +165,7 @@ class AttachmentActionsModal extends StatelessWidget { ..maybePop(); } }, - color: theme.colorTheme.accentRed, + color: theme.colorTheme.accentError, ), ] .map((e) => Align( @@ -175,7 +175,7 @@ class AttachmentActionsModal extends StatelessWidget { .insertBetween( Container( height: 1, - color: theme.colorTheme.greyWhisper, + color: theme.colorTheme.borders, ), ), ), @@ -196,7 +196,7 @@ class AttachmentActionsModal extends StatelessWidget { }) => Material( key: key, - color: StreamChatTheme.of(context).colorTheme.white, + color: StreamChatTheme.of(context).colorTheme.barsBg, child: InkWell( onTap: onTap, child: Padding( @@ -241,7 +241,7 @@ class AttachmentActionsModal extends StatelessWidget { width: 182, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), - color: theme.colorTheme.white, + color: theme.colorTheme.barsBg, ), child: Center( child: progress == null @@ -249,7 +249,7 @@ class AttachmentActionsModal extends StatelessWidget { height: 100, width: 100, child: StreamSvgIcon.error( - color: theme.colorTheme.greyGainsboro, + color: theme.colorTheme.disabled, ), ) : progress.toProgressIndicatorValue == 1.0 @@ -258,7 +258,7 @@ class AttachmentActionsModal extends StatelessWidget { height: 160, width: 160, child: StreamSvgIcon.check( - color: theme.colorTheme.greyGainsboro, + color: theme.colorTheme.disabled, ), ) : SizedBox( @@ -271,14 +271,14 @@ class AttachmentActionsModal extends StatelessWidget { value: progress.toProgressIndicatorValue, strokeWidth: 8, valueColor: AlwaysStoppedAnimation( - theme.colorTheme.accentBlue, + theme.colorTheme.accentPrimary, ), ), Center( child: Text( '${progress.toPercentage}%', style: theme.textTheme.headline.copyWith( - color: theme.colorTheme.grey, + color: theme.colorTheme.textLowEmphasis, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/back_button.dart b/packages/stream_chat_flutter/lib/src/back_button.dart index e8fe7e31..6bd3e0cc 100644 --- a/packages/stream_chat_flutter/lib/src/back_button.dart +++ b/packages/stream_chat_flutter/lib/src/back_button.dart @@ -44,7 +44,7 @@ class StreamBackButton extends StatelessWidget { padding: const EdgeInsets.all(14), child: StreamSvgIcon.left( size: 24, - color: StreamChatTheme.of(context).colorTheme.black, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), ), if (showUnreads) diff --git a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart index eaabb7e2..368bfbd8 100644 --- a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart @@ -32,7 +32,7 @@ class _ChannelBottomSheetState extends State { final isOwner = userAsMember.role == 'owner'; return Material( - color: _streamChatThemeData.colorTheme.white, + color: _streamChatThemeData.colorTheme.barsBg, clipBehavior: Clip.antiAlias, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -146,7 +146,7 @@ class _ChannelBottomSheetState extends State { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.user( - color: _streamChatThemeData.colorTheme.grey, + color: _streamChatThemeData.colorTheme.textLowEmphasis, ), ), title: 'View Info', @@ -157,7 +157,7 @@ class _ChannelBottomSheetState extends State { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.userRemove( - color: _streamChatThemeData.colorTheme.grey, + color: _streamChatThemeData.colorTheme.textLowEmphasis, ), ), title: 'Leave Group', @@ -176,11 +176,11 @@ class _ChannelBottomSheetState extends State { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.delete( - color: _streamChatThemeData.colorTheme.accentRed, + color: _streamChatThemeData.colorTheme.accentError, ), ), title: 'Delete Conversation', - titleColor: _streamChatThemeData.colorTheme.accentRed, + titleColor: _streamChatThemeData.colorTheme.accentError, onTap: () async { setState(() { _showActions = false; @@ -195,7 +195,7 @@ class _ChannelBottomSheetState extends State { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.closeSmall( - color: _streamChatThemeData.colorTheme.grey, + color: _streamChatThemeData.colorTheme.textLowEmphasis, ), ), title: 'Cancel', @@ -224,7 +224,7 @@ class _ChannelBottomSheetState extends State { question: 'Are you sure you want to delete this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.delete( - color: _streamChatThemeData.colorTheme.accentRed, + color: _streamChatThemeData.colorTheme.accentError, ), ); final channel = _streamChannelState.channel; @@ -242,7 +242,7 @@ class _ChannelBottomSheetState extends State { question: 'Are you sure you want to leave this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.userRemove( - color: _streamChatThemeData.colorTheme.accentRed, + color: _streamChatThemeData.colorTheme.accentError, ), ); if (res == true) { diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index d3e028ee..9ac07033 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -110,8 +110,8 @@ class ChannelImage extends StatelessWidget { .channelPreviewTheme.avatarTheme?.constraints, onTap: onTap != null ? (_) => onTap!() : null, selected: selected, - selectionColor: - selectionColor ?? chatThemeData.colorTheme.accentBlue, + selectionColor: selectionColor ?? + chatThemeData.colorTheme.accentPrimary, selectionThickness: selectionThickness, )); } else { @@ -132,7 +132,7 @@ class ChannelImage extends StatelessWidget { onTap: onTap, selected: selected, selectionColor: - selectionColor ?? chatThemeData.colorTheme.accentBlue, + selectionColor ?? chatThemeData.colorTheme.accentPrimary, selectionThickness: selectionThickness, ); } @@ -144,7 +144,7 @@ class ChannelImage extends StatelessWidget { constraints: constraints ?? chatThemeData.channelPreviewTheme.avatarTheme?.constraints, decoration: BoxDecoration( - color: chatThemeData.colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentPrimary, ), child: Stack( alignment: Alignment.center, @@ -157,7 +157,7 @@ class ChannelImage extends StatelessWidget { child: Text( data.containsKey('name') ? data['name'][0] : '', style: TextStyle( - color: chatThemeData.colorTheme.white, + color: chatThemeData.colorTheme.barsBg, fontWeight: FontWeight.bold, ), ), @@ -189,7 +189,7 @@ class ChannelImage extends StatelessWidget { child: Container( constraints: constraints ?? chatThemeData.ownMessageTheme.avatarTheme?.constraints, - color: selectionColor ?? chatThemeData.colorTheme.accentBlue, + color: selectionColor ?? chatThemeData.colorTheme.accentPrimary, child: Padding( padding: EdgeInsets.all(selectionThickness), child: child, diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index 07920b95..3be26ec8 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -143,7 +143,7 @@ class ChannelInfo extends StatelessWidget { child: Text( 'Try Again', style: textStyle?.copyWith( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: StreamChatTheme.of(context).colorTheme.accentPrimary, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel_list_header.dart index 5a4275a6..0db31f80 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -154,7 +154,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { Color? color; switch (status) { case ConnectionStatus.connected: - color = chatThemeData.colorTheme.accentBlue; + color = chatThemeData.colorTheme.accentPrimary; break; case ConnectionStatus.connecting: color = Colors.grey; @@ -209,7 +209,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { return Text( 'Stream Chat', style: chatThemeData.textTheme.headlineBold.copyWith( - color: chatThemeData.colorTheme.black, + color: chatThemeData.colorTheme.textHighEmphasis, ), ); } @@ -262,7 +262,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { style: chatThemeData.channelListHeaderTheme.title?.copyWith( fontSize: 16, fontWeight: FontWeight.bold, - color: chatThemeData.colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentPrimary, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index b14e0b75..316d7513 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -290,7 +290,7 @@ class _ChannelListViewState extends State { padding: const EdgeInsets.all(8), child: StreamSvgIcon.message( size: 136, - color: chatThemeData.colorTheme.greyGainsboro, + color: chatThemeData.colorTheme.disabled, ), ), Padding( @@ -309,7 +309,7 @@ class _ChannelListViewState extends State { 'How about sending your first message to a friend?', textAlign: TextAlign.center, style: chatThemeData.textTheme.body.copyWith( - color: chatThemeData.colorTheme.grey, + color: chatThemeData.colorTheme.textLowEmphasis, ), ), ), @@ -327,7 +327,7 @@ class _ChannelListViewState extends State { child: Text( 'Start a chat', style: chatThemeData.textTheme.bodyBold.copyWith( - color: chatThemeData.colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentPrimary, ), ), ), @@ -362,8 +362,8 @@ class _ChannelListViewState extends State { final chatThemeData = StreamChatTheme.of(context); if (widget.crossAxisCount > 1) { return Shimmer.fromColors( - baseColor: chatThemeData.colorTheme.greyGainsboro, - highlightColor: chatThemeData.colorTheme.whiteSmoke, + baseColor: chatThemeData.colorTheme.disabled, + highlightColor: chatThemeData.colorTheme.inputBg, child: Column( children: [ const SizedBox(height: 4), @@ -391,12 +391,12 @@ class _ChannelListViewState extends State { ); } else { return Shimmer.fromColors( - baseColor: chatThemeData.colorTheme.greyGainsboro, - highlightColor: chatThemeData.colorTheme.whiteSmoke, + baseColor: chatThemeData.colorTheme.disabled, + highlightColor: chatThemeData.colorTheme.inputBg, child: ListTile( leading: Container( decoration: BoxDecoration( - color: chatThemeData.colorTheme.white, + color: chatThemeData.colorTheme.barsBg, shape: BoxShape.circle, ), constraints: const BoxConstraints.tightFor( @@ -412,7 +412,7 @@ class _ChannelListViewState extends State { alignment: Alignment.centerLeft, child: Container( decoration: BoxDecoration( - color: chatThemeData.colorTheme.white, + color: chatThemeData.colorTheme.barsBg, borderRadius: BorderRadius.circular(11), ), constraints: const BoxConstraints.tightFor( @@ -429,7 +429,7 @@ class _ChannelListViewState extends State { alignment: Alignment.centerLeft, child: Container( decoration: BoxDecoration( - color: chatThemeData.colorTheme.white, + color: chatThemeData.colorTheme.barsBg, borderRadius: BorderRadius.circular(11), ), constraints: const BoxConstraints.expand( @@ -441,7 +441,7 @@ class _ChannelListViewState extends State { Container( margin: const EdgeInsets.only(left: 16), decoration: BoxDecoration( - color: chatThemeData.colorTheme.white, + color: chatThemeData.colorTheme.barsBg, borderRadius: BorderRadius.circular(11), ), constraints: const BoxConstraints.tightFor( @@ -488,7 +488,7 @@ class _ChannelListViewState extends State { final channelsBloc = ChannelsBloc.of(context); final onTap = _getChannelTap(context); final chatThemeData = StreamChatTheme.of(context); - final backgroundColor = chatThemeData.colorTheme.whiteSmoke; + final backgroundColor = chatThemeData.colorTheme.inputBg; if (i < channels.length) { final channel = channels[i]; @@ -549,7 +549,7 @@ class _ChannelListViewState extends State { IconSlideAction( color: backgroundColor, iconWidget: StreamSvgIcon.delete( - color: chatThemeData.colorTheme.accentRed, + color: chatThemeData.colorTheme.accentError, ), onTap: widget.onDeletePressed != null ? () { @@ -565,7 +565,7 @@ class _ChannelListViewState extends State { 'Are you sure you want to delete this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.delete( - color: chatThemeData.colorTheme.accentRed, + color: chatThemeData.colorTheme.accentError, ), ); if (res == true) { @@ -576,7 +576,7 @@ class _ChannelListViewState extends State { ], child: DecoratedBox( decoration: BoxDecoration( - color: chatThemeData.colorTheme.whiteSnow, + color: chatThemeData.colorTheme.appBg, ), child: widget.channelPreviewBuilder?.call(context, channel) ?? ChannelPreview( @@ -664,7 +664,7 @@ class _ChannelListViewState extends State { errorBuilder: (context, err) => Container( color: StreamChatTheme.of(context) .colorTheme - .accentRed + .accentError .withOpacity(.2), child: const Padding( padding: EdgeInsets.symmetric(vertical: 16), diff --git a/packages/stream_chat_flutter/lib/src/date_divider.dart b/packages/stream_chat_flutter/lib/src/date_divider.dart index 185683be..8fff2ade 100644 --- a/packages/stream_chat_flutter/lib/src/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/date_divider.dart @@ -55,7 +55,7 @@ class DateDivider extends StatelessWidget { child: Text( dayInfo, style: chatThemeData.textTheme.footnote.copyWith( - color: chatThemeData.colorTheme.white, + color: chatThemeData.colorTheme.barsBg, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart index 9a4e0ae2..011b0c72 100644 --- a/packages/stream_chat_flutter/lib/src/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart @@ -39,8 +39,8 @@ class DeletedMessage extends StatelessWidget { side: borderSide ?? BorderSide( color: Theme.of(context).brightness == Brightness.dark - ? chatThemeData.colorTheme.white.withAlpha(24) - : chatThemeData.colorTheme.black.withAlpha(24), + ? chatThemeData.colorTheme.barsBg.withAlpha(24) + : chatThemeData.colorTheme.textHighEmphasis.withAlpha(24), ), ), child: Padding( diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index c6778a91..3513bda2 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -51,7 +51,7 @@ class GroupImage extends StatelessWidget { constraints: constraints ?? streamChatTheme.ownMessageTheme.avatarTheme?.constraints, decoration: BoxDecoration( - color: streamChatTheme.colorTheme.accentBlue, + color: streamChatTheme.colorTheme.accentPrimary, ), child: Flex( direction: Axis.vertical, @@ -119,7 +119,7 @@ class GroupImage extends StatelessWidget { BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( - color: selectionColor ?? streamChatTheme.colorTheme.accentBlue, + color: selectionColor ?? streamChatTheme.colorTheme.accentPrimary, height: 64, width: 64, child: Padding( diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/image_footer.dart index 0a6571cc..4b32afd8 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/image_footer.dart @@ -88,7 +88,7 @@ class _ImageFooterState extends State { context: context, removeTop: true, child: BottomAppBar( - color: chatThemeData.colorTheme.white, + color: chatThemeData.colorTheme.barsBg, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -100,7 +100,7 @@ class _ImageFooterState extends State { IconButton( icon: StreamSvgIcon.iconShare( size: 24, - color: chatThemeData.colorTheme.black, + color: chatThemeData.colorTheme.textHighEmphasis, ), onPressed: () async { final attachment = @@ -144,7 +144,7 @@ class _ImageFooterState extends State { ), IconButton( icon: StreamSvgIcon.iconGrid( - color: chatThemeData.colorTheme.black, + color: chatThemeData.colorTheme.textHighEmphasis, ), onPressed: () => _showPhotosModal(context), ), @@ -160,7 +160,7 @@ class _ImageFooterState extends State { showModalBottomSheet( context: context, barrierColor: chatThemeData.colorTheme.overlay, - backgroundColor: chatThemeData.colorTheme.white, + backgroundColor: chatThemeData.colorTheme.barsBg, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -199,7 +199,7 @@ class _ImageFooterState extends State { alignment: Alignment.centerRight, child: IconButton( icon: StreamSvgIcon.close( - color: chatThemeData.colorTheme.black, + color: chatThemeData.colorTheme.textHighEmphasis, ), onPressed: () => Navigator.maybePop(context), ), @@ -262,7 +262,8 @@ class _ImageFooterState extends State { boxShadow: [ BoxShadow( blurRadius: 8, - color: chatThemeData.colorTheme.black + color: chatThemeData + .colorTheme.textHighEmphasis .withOpacity(0.3), ), ], diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/image_header.dart index 3d156b16..92f4fa10 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/image_header.dart @@ -59,7 +59,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { leading: showBackButton ? IconButton( icon: StreamSvgIcon.close( - color: chatThemeData.colorTheme.black, + color: chatThemeData.colorTheme.textHighEmphasis, size: 24, ), onPressed: onBackPressed, @@ -70,7 +70,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { if (message.type != 'ephemeral') IconButton( icon: StreamSvgIcon.iconMenuPoint( - color: chatThemeData.colorTheme.black, + color: chatThemeData.colorTheme.textHighEmphasis, ), onPressed: () { _showMessageActionModalBottomSheet(context); diff --git a/packages/stream_chat_flutter/lib/src/info_tile.dart b/packages/stream_chat_flutter/lib/src/info_tile.dart index 1e935942..965d4fed 100644 --- a/packages/stream_chat_flutter/lib/src/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/info_tile.dart @@ -46,8 +46,8 @@ class InfoTile extends StatelessWidget { childAnchor: childAnchor ?? Alignment.bottomCenter, portal: Container( height: 25, - color: - backgroundColor ?? chatThemeData.colorTheme.grey.withOpacity(0.9), + color: backgroundColor ?? + chatThemeData.colorTheme.textLowEmphasis.withOpacity(0.9), child: Center( child: Text( message, 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 fe0070f9..77359f8b 100644 --- a/packages/stream_chat_flutter/lib/src/media_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/media_list_view.dart @@ -90,8 +90,8 @@ class _MediaListViewState extends State { ? 1.0 : 0.0, child: Container( - color: - chatThemeData.colorTheme.black.withOpacity(0.5), + color: chatThemeData.colorTheme.textHighEmphasis + .withOpacity(0.5), alignment: Alignment.topRight, padding: const EdgeInsets.only( top: 8, @@ -99,10 +99,11 @@ class _MediaListViewState extends State { ), child: CircleAvatar( radius: 12, - backgroundColor: chatThemeData.colorTheme.white, + backgroundColor: chatThemeData.colorTheme.barsBg, child: StreamSvgIcon.check( size: 24, - color: chatThemeData.colorTheme.black, + color: + chatThemeData.colorTheme.textHighEmphasis, ), ), ), @@ -124,7 +125,7 @@ class _MediaListViewState extends State { child: Text( media.videoDuration.format(), style: TextStyle( - color: chatThemeData.colorTheme.white, + color: chatThemeData.colorTheme.barsBg, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/mention_tile.dart b/packages/stream_chat_flutter/lib/src/mention_tile.dart index 99b4b962..d9469215 100644 --- a/packages/stream_chat_flutter/lib/src/mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/mention_tile.dart @@ -76,7 +76,7 @@ class MentionTile extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: chatThemeData.textTheme.footnoteBold.copyWith( - color: chatThemeData.colorTheme.grey, + color: chatThemeData.colorTheme.textLowEmphasis, ), ), ], @@ -90,7 +90,7 @@ class MentionTile extends StatelessWidget { left: 8, ), child: StreamSvgIcon.mentions( - color: chatThemeData.colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentPrimary, ), ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart index 296b1e59..3e653187 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -165,7 +165,7 @@ class _MessageActionsModalState extends State { child: SizedBox( width: mediaQueryData.size.width * 0.75, child: Material( - color: streamChatThemeData.colorTheme.whiteSnow, + color: streamChatThemeData.colorTheme.appBg, clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -197,7 +197,7 @@ class _MessageActionsModalState extends State { ].insertBetween( Container( height: 1, - color: streamChatThemeData.colorTheme.greyWhisper, + color: streamChatThemeData.colorTheme.borders, ), ), ), @@ -270,7 +270,7 @@ class _MessageActionsModalState extends State { context, title: 'Flag Message', icon: StreamSvgIcon.flag( - color: streamChatThemeData.colorTheme.accentRed, + color: streamChatThemeData.colorTheme.accentError, size: 24, ), question: @@ -287,7 +287,7 @@ class _MessageActionsModalState extends State { await showInfoDialog( context, icon: StreamSvgIcon.flag( - color: theme.colorTheme.accentRed, + color: theme.colorTheme.accentError, size: 24, ), details: 'The message has been reported to a moderator.', @@ -300,7 +300,7 @@ class _MessageActionsModalState extends State { await showInfoDialog( context, icon: StreamSvgIcon.flag( - color: theme.colorTheme.accentRed, + color: theme.colorTheme.accentError, size: 24, ), details: 'The message has been reported to a moderator.', @@ -337,7 +337,7 @@ class _MessageActionsModalState extends State { context, title: 'Delete message', icon: StreamSvgIcon.flag( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: StreamChatTheme.of(context).colorTheme.accentError, size: 24, ), question: 'Are you sure you want to permanently delete this\nmessage?', @@ -363,7 +363,7 @@ class _MessageActionsModalState extends State { showInfoDialog( context, icon: StreamSvgIcon.error( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: StreamChatTheme.of(context).colorTheme.accentError, size: 24, ), details: 'The operation couldn\'t be completed.', @@ -540,7 +540,7 @@ class _MessageActionsModalState extends State { child: Row( children: [ StreamSvgIcon.circleUp( - color: streamChatThemeData.colorTheme.accentBlue, + color: streamChatThemeData.colorTheme.accentPrimary, ), const SizedBox(width: 16), Text( @@ -585,7 +585,7 @@ class _MessageActionsModalState extends State { Padding( padding: const EdgeInsets.all(8), child: StreamSvgIcon.edit( - color: streamChatThemeData.colorTheme.greyGainsboro, + color: streamChatThemeData.colorTheme.disabled, ), ), const Text( diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 39211116..a0ff93cf 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -334,7 +334,7 @@ class MessageInputState extends State { Padding( padding: const EdgeInsets.all(8), child: StreamSvgIcon.reply( - color: _streamChatTheme.colorTheme.greyGainsboro, + color: _streamChatTheme.colorTheme.disabled, ), ), const Text( @@ -401,7 +401,8 @@ class MessageInputState extends State { border: _sendAsDm ? null : Border.all( - color: _streamChatTheme.colorTheme.black.withOpacity(.5), + color: _streamChatTheme.colorTheme.textHighEmphasis + .withOpacity(.5), width: 2, ), borderRadius: BorderRadius.circular(3), @@ -410,8 +411,8 @@ class MessageInputState extends State { child: Material( borderRadius: BorderRadius.circular(3), color: _sendAsDm - ? _streamChatTheme.colorTheme.accentBlue - : _streamChatTheme.colorTheme.white, + ? _streamChatTheme.colorTheme.accentPrimary + : _streamChatTheme.colorTheme.barsBg, child: InkWell( onTap: () { setState(() { @@ -426,7 +427,7 @@ class MessageInputState extends State { : CrossFadeState.showSecond, firstChild: StreamSvgIcon.check( size: 16, - color: _streamChatTheme.colorTheme.white, + color: _streamChatTheme.colorTheme.barsBg, ), secondChild: const SizedBox( height: 16, @@ -442,7 +443,8 @@ class MessageInputState extends State { child: Text( 'Also send as direct message', style: _streamChatTheme.textTheme.footnote.copyWith( - color: _streamChatTheme.colorTheme.black.withOpacity(0.5), + color: _streamChatTheme.colorTheme.textHighEmphasis + .withOpacity(0.5), ), ), ), @@ -582,7 +584,7 @@ class MessageInputState extends State { isDense: true, hintText: _getHint(), hintStyle: _streamChatTheme.messageInputTheme.inputTextStyle!.copyWith( - color: _streamChatTheme.colorTheme.grey, + color: _streamChatTheme.colorTheme.textLowEmphasis, ), border: const OutlineInputBorder( borderSide: BorderSide( @@ -620,7 +622,7 @@ class MessageInputState extends State { constraints: BoxConstraints.tight(const Size(64, 24)), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - color: _streamChatTheme.colorTheme.accentBlue, + color: _streamChatTheme.colorTheme.accentPrimary, ), alignment: Alignment.center, child: Row( @@ -834,12 +836,12 @@ class MessageInputState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), - color: _streamChatTheme.colorTheme.white, + color: _streamChatTheme.colorTheme.barsBg, clipBehavior: Clip.hardEdge, child: Container( constraints: BoxConstraints.loose(const Size.fromHeight(400)), decoration: BoxDecoration( - color: _streamChatTheme.colorTheme.white, + color: _streamChatTheme.colorTheme.barsBg, borderRadius: BorderRadius.circular(8)), child: ListView( padding: const EdgeInsets.all(0), @@ -855,14 +857,14 @@ class MessageInputState extends State { horizontal: 8, ), child: StreamSvgIcon.lightning( - color: _streamChatTheme.colorTheme.accentBlue, + color: _streamChatTheme.colorTheme.accentPrimary, ), ), Text( 'Instant Commands', style: TextStyle( - color: - _streamChatTheme.colorTheme.black.withOpacity(.5), + color: _streamChatTheme.colorTheme.textHighEmphasis + .withOpacity(.5), ), ) ], @@ -902,7 +904,7 @@ class MessageInputState extends State { color: _streamChatTheme // ignore: lines_longer_than_80_chars .colorTheme - .grey, + .textLowEmphasis, ), ), ], @@ -946,24 +948,29 @@ class MessageInputState extends State { switch (index) { case 0: return _attachments.isEmpty - ? streamChatThemeData.colorTheme.accentBlue + ? streamChatThemeData.colorTheme.accentPrimary : (!_attachmentContainsFile - ? streamChatThemeData.colorTheme.accentBlue - : streamChatThemeData.colorTheme.black.withOpacity(0.2)); + ? streamChatThemeData.colorTheme.accentPrimary + : streamChatThemeData.colorTheme.textHighEmphasis + .withOpacity(0.2)); case 1: return _attachmentContainsFile - ? streamChatThemeData.colorTheme.accentBlue + ? streamChatThemeData.colorTheme.accentPrimary : (_attachments.isEmpty - ? streamChatThemeData.colorTheme.black.withOpacity(0.5) - : streamChatThemeData.colorTheme.black.withOpacity(0.2)); + ? streamChatThemeData.colorTheme.textHighEmphasis + .withOpacity(0.5) + : streamChatThemeData.colorTheme.textHighEmphasis + .withOpacity(0.2)); case 2: return _attachmentContainsFile && _attachments.isNotEmpty - ? streamChatThemeData.colorTheme.black.withOpacity(0.2) - : streamChatThemeData.colorTheme.black.withOpacity(0.5); + ? streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(0.2) + : streamChatThemeData.colorTheme.textHighEmphasis + .withOpacity(0.5); case 3: return _attachmentContainsFile && _attachments.isNotEmpty - ? streamChatThemeData.colorTheme.black.withOpacity(0.2) - : streamChatThemeData.colorTheme.black.withOpacity(0.5); + ? streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(0.2) + : streamChatThemeData.colorTheme.textHighEmphasis + .withOpacity(0.5); default: return Colors.black; } @@ -973,7 +980,7 @@ class MessageInputState extends State { duration: const Duration(milliseconds: 300), height: _openFilePickerSection ? _filePickerSize : 0, child: Material( - color: _streamChatTheme.colorTheme.whiteSmoke, + color: _streamChatTheme.colorTheme.inputBg, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -1036,7 +1043,7 @@ class MessageInputState extends State { }, child: DecoratedBox( decoration: BoxDecoration( - color: _streamChatTheme.colorTheme.white, + color: _streamChatTheme.colorTheme.barsBg, borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), @@ -1052,7 +1059,7 @@ class MessageInputState extends State { height: 4, child: DecoratedBox( decoration: BoxDecoration( - color: _streamChatTheme.colorTheme.whiteSmoke, + color: _streamChatTheme.colorTheme.inputBg, borderRadius: BorderRadius.circular(4), ), ), @@ -1066,7 +1073,7 @@ class MessageInputState extends State { Expanded( child: DecoratedBox( decoration: BoxDecoration( - color: _streamChatTheme.colorTheme.white, + color: _streamChatTheme.colorTheme.barsBg, borderRadius: BorderRadius.circular(8), ), child: _PickerWidget( @@ -1153,7 +1160,7 @@ class MessageInputState extends State { ); case 'ban': return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, child: StreamSvgIcon.iconUserDelete( size: 16, @@ -1162,7 +1169,7 @@ class MessageInputState extends State { ); case 'flag': return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, child: StreamSvgIcon.flag( size: 14, @@ -1171,7 +1178,7 @@ class MessageInputState extends State { ); case 'imgur': return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, child: ClipOval( child: StreamSvgIcon.imgur( @@ -1181,7 +1188,7 @@ class MessageInputState extends State { ); case 'mute': return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, child: StreamSvgIcon.mute( size: 16, @@ -1190,7 +1197,7 @@ class MessageInputState extends State { ); case 'unban': return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, child: StreamSvgIcon.userAdd( size: 16, @@ -1199,7 +1206,7 @@ class MessageInputState extends State { ); case 'unmute': return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, child: StreamSvgIcon.volumeUp( size: 16, @@ -1208,7 +1215,7 @@ class MessageInputState extends State { ); default: return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentBlue, + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, child: StreamSvgIcon.lightning( size: 16, @@ -1248,7 +1255,7 @@ class MessageInputState extends State { final child = Card( margin: const EdgeInsets.all(8), elevation: 2, - color: _streamChatTheme.colorTheme.white, + color: _streamChatTheme.colorTheme.barsBg, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), @@ -1256,7 +1263,7 @@ class MessageInputState extends State { child: Container( constraints: BoxConstraints.loose(const Size.fromHeight(240)), decoration: BoxDecoration( - color: _streamChatTheme.colorTheme.white, + color: _streamChatTheme.colorTheme.barsBg, ), child: FutureBuilder>( future: queryMembers ?? Future.value(members), @@ -1272,7 +1279,7 @@ class MessageInputState extends State { .where((it) => it.user != null) .map( (m) => Material( - color: _streamChatTheme.colorTheme.white, + color: _streamChatTheme.colorTheme.barsBg, child: InkWell( onTap: () { if (m.user != null) { @@ -1359,7 +1366,7 @@ class MessageInputState extends State { child: Card( margin: const EdgeInsets.all(8), elevation: 2, - color: _streamChatTheme.colorTheme.white, + color: _streamChatTheme.colorTheme.barsBg, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), @@ -1374,7 +1381,7 @@ class MessageInputState extends State { offset: Offset(0, -4), ), ], - color: _streamChatTheme.colorTheme.white, + color: _streamChatTheme.colorTheme.barsBg, ), child: ListView.builder( padding: const EdgeInsets.all(0), @@ -1390,15 +1397,16 @@ class MessageInputState extends State { padding: const EdgeInsets.symmetric(horizontal: 8), child: StreamSvgIcon.smile( - color: - _streamChatTheme.colorTheme.accentBlue, + color: _streamChatTheme + .colorTheme.accentPrimary, ), ), Flexible( child: Text( 'Emoji matching "$query"', style: TextStyle( - color: _streamChatTheme.colorTheme.black + color: _streamChatTheme + .colorTheme.textHighEmphasis .withOpacity(.5), ), ), @@ -1570,11 +1578,12 @@ class MessageInputState extends State { onPressed: () { setState(() => _attachments.remove(attachment.id)); }, - fillColor: _streamChatTheme.colorTheme.black.withOpacity(.5), + fillColor: + _streamChatTheme.colorTheme.textHighEmphasis.withOpacity(.5), child: Center( child: StreamSvgIcon.close( size: 24, - color: _streamChatTheme.colorTheme.white, + color: _streamChatTheme.colorTheme.barsBg, ), ), ), @@ -1609,8 +1618,8 @@ class MessageInputState extends State { errorWidget: (_, obj, trace) => getFileTypeImage(attachment.extraData['other'] as String?), placeholder: (context, _) => Shimmer.fromColors( - baseColor: _streamChatTheme.colorTheme.greyGainsboro, - highlightColor: _streamChatTheme.colorTheme.whiteSmoke, + baseColor: _streamChatTheme.colorTheme.disabled, + highlightColor: _streamChatTheme.colorTheme.inputBg, child: Image.asset( 'images/placeholder.png', fit: BoxFit.cover, @@ -1651,7 +1660,7 @@ class MessageInputState extends State { return IconButton( icon: StreamSvgIcon.lightning( color: s.isNotEmpty - ? _streamChatTheme.colorTheme.greyGainsboro + ? _streamChatTheme.colorTheme.disabled : (_commandsOverlay != null ? _streamChatTheme.messageInputTheme.actionButtonColor : _streamChatTheme.messageInputTheme.actionButtonIdleColor), @@ -2058,7 +2067,7 @@ class MessageInputState extends State { void _showErrorAlert(String description) { showModalBottomSheet( - backgroundColor: _streamChatTheme.colorTheme.white, + backgroundColor: _streamChatTheme.colorTheme.barsBg, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -2072,7 +2081,7 @@ class MessageInputState extends State { height: 26, ), StreamSvgIcon.error( - color: _streamChatTheme.colorTheme.accentRed, + color: _streamChatTheme.colorTheme.accentError, size: 24, ), const SizedBox( @@ -2096,7 +2105,8 @@ class MessageInputState extends State { height: 36, ), Container( - color: _streamChatTheme.colorTheme.black.withOpacity(.08), + color: + _streamChatTheme.colorTheme.textHighEmphasis.withOpacity(.08), height: 1, ), Row( @@ -2108,8 +2118,8 @@ class MessageInputState extends State { }, child: Text( 'OK', - style: _streamChatTheme.textTheme.bodyBold - .copyWith(color: _streamChatTheme.colorTheme.accentBlue), + style: _streamChatTheme.textTheme.bodyBold.copyWith( + color: _streamChatTheme.colorTheme.accentPrimary), ), ), ], @@ -2250,12 +2260,12 @@ class __PickerWidgetState extends State<_PickerWidget> { }, child: Container( constraints: const BoxConstraints.expand(), - color: widget.streamChatTheme.colorTheme.whiteSmoke, + color: widget.streamChatTheme.colorTheme.inputBg, alignment: Alignment.center, child: Text( 'Add more files', style: TextStyle( - color: widget.streamChatTheme.colorTheme.accentBlue, + color: widget.streamChatTheme.colorTheme.accentPrimary, fontWeight: FontWeight.bold, ), ), @@ -2273,7 +2283,7 @@ class __PickerWidgetState extends State<_PickerWidget> { PhotoManager.openSetting(); }, child: Container( - color: widget.streamChatTheme.colorTheme.whiteSmoke, + color: widget.streamChatTheme.colorTheme.inputBg, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -2282,13 +2292,14 @@ class __PickerWidgetState extends State<_PickerWidget> { 'svgs/icon_picture_empty_state.svg', package: 'stream_chat_flutter', height: 140, - color: widget.streamChatTheme.colorTheme.greyGainsboro, + color: widget.streamChatTheme.colorTheme.disabled, ), Text( // ignore: lines_longer_than_80_chars 'Please enable access to your photos \nand videos so you can share them with friends.', style: widget.streamChatTheme.textTheme.body.copyWith( - color: widget.streamChatTheme.colorTheme.grey), + color: + widget.streamChatTheme.colorTheme.textLowEmphasis), textAlign: TextAlign.center, ), const SizedBox(height: 6), @@ -2296,7 +2307,7 @@ class __PickerWidgetState extends State<_PickerWidget> { child: Text( 'Allow access to your gallery', style: widget.streamChatTheme.textTheme.bodyBold.copyWith( - color: widget.streamChatTheme.colorTheme.accentBlue, + color: widget.streamChatTheme.colorTheme.accentPrimary, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index e6bf7a17..6e61612b 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -368,7 +368,8 @@ class _MessageListViewState extends State { child: Text( 'No chats here yet...', style: _streamTheme.textTheme.footnote.copyWith( - color: _streamTheme.colorTheme.black.withOpacity(.5)), + color: _streamTheme.colorTheme.textHighEmphasis + .withOpacity(.5)), ), ), messageListBuilder: widget.messageListBuilder ?? @@ -380,7 +381,8 @@ class _MessageListViewState extends State { child: Text( 'Something went wrong', style: _streamTheme.textTheme.footnote.copyWith( - color: _streamTheme.colorTheme.black.withOpacity(.5)), + color: _streamTheme.colorTheme.textHighEmphasis + .withOpacity(.5)), ), ), ); @@ -710,7 +712,7 @@ class _MessageListViewState extends State { clipBehavior: Clip.none, children: [ FloatingActionButton( - backgroundColor: _streamTheme.colorTheme.white, + backgroundColor: _streamTheme.colorTheme.barsBg, onPressed: () { if (unreadCount > 0) { streamChannel!.channel.markRead(); @@ -729,7 +731,7 @@ class _MessageListViewState extends State { } }, child: StreamSvgIcon.down( - color: _streamTheme.colorTheme.black, + color: _streamTheme.colorTheme.textHighEmphasis, ), ), if (showUnreadCount) @@ -1106,7 +1108,7 @@ class _MessageListViewState extends State { widget.onMessageSwiped?.call(message); }, backgroundIcon: StreamSvgIcon.reply( - color: _streamTheme.colorTheme.accentBlue, + color: _streamTheme.colorTheme.accentPrimary, ), child: child, ), @@ -1122,7 +1124,7 @@ class _MessageListViewState extends State { child = TweenAnimationBuilder( tween: ColorTween( begin: highlightColor, - end: colorTheme.white.withOpacity(0), + end: colorTheme.barsBg.withOpacity(0), ), duration: const Duration(seconds: 3), onEnd: () => initialMessageHighlightComplete = true, @@ -1252,7 +1254,7 @@ class _LoadingIndicator extends StatelessWidget { stream: stream, initialData: false, errorBuilder: (context, error) => Container( - color: streamTheme.colorTheme.accentRed.withOpacity(.2), + color: streamTheme.colorTheme.accentError.withOpacity(.2), child: const Center( child: Text('Error loading messages'), ), diff --git a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart index 0292a05a..3d9a907e 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -142,7 +142,7 @@ class MessageReactionsModal extends StatelessWidget { Widget _buildReactionCard(BuildContext context, User? user) { final chatThemeData = StreamChatTheme.of(context); return Card( - color: chatThemeData.colorTheme.white, + color: chatThemeData.colorTheme.barsBg, clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -224,7 +224,7 @@ class MessageReactionsModal extends StatelessWidget { messageTheme.reactionsBorderColor ?? Colors.transparent, backgroundColor: messageTheme.reactionsBackgroundColor ?? Colors.transparent, - maskColor: chatThemeData.colorTheme.white, + maskColor: chatThemeData.colorTheme.barsBg, tailCirclesSpacing: 1, highlightOwnReactions: false, ), diff --git a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart index 1a67702f..f364d0f2 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 @@ -199,7 +199,7 @@ class _MessageSearchListViewState extends State { Widget _separatorBuilder(BuildContext context, int index) => Container( height: 1, - color: StreamChatTheme.of(context).colorTheme.greyWhisper, + color: StreamChatTheme.of(context).colorTheme.borders, ); Widget _listItemBuilder( @@ -224,7 +224,7 @@ class _MessageSearchListViewState extends State { return Container( color: StreamChatTheme.of(context) .colorTheme - .accentRed + .accentError .withOpacity(.2), child: const Padding( padding: EdgeInsets.symmetric(vertical: 16), @@ -294,7 +294,7 @@ class _MessageSearchListViewState extends State { child: Text( '${items.length} results', style: TextStyle( - color: chatThemeData.colorTheme.grey, + color: chatThemeData.colorTheme.textLowEmphasis, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 7c95bf0a..c9dfd9d4 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -222,7 +222,7 @@ class MessageWidget extends StatefulWidget { final border = RoundedRectangleBorder( side: attachmentBorderSide ?? BorderSide( - color: StreamChatTheme.of(context).colorTheme.greyWhisper, + color: StreamChatTheme.of(context).colorTheme.borders, ), borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, ); @@ -749,7 +749,7 @@ class _MessageWidgetState extends State child: CustomPaint( painter: ReactionBubblePainter( _streamChatTheme - .colorTheme.white, + .colorTheme.barsBg, Colors.transparent, Colors.transparent, tailCirclesSpace: 1, @@ -835,14 +835,14 @@ class _MessageWidgetState extends State mainAxisSize: MainAxisSize.min, children: [ StreamSvgIcon.eye( - color: chatThemeData.colorTheme.grey, + color: chatThemeData.colorTheme.textLowEmphasis, size: 16, ), const SizedBox(width: 8), Text( 'Only visible to you', style: chatThemeData.textTheme.footnote - .copyWith(color: chatThemeData.colorTheme.grey), + .copyWith(color: chatThemeData.colorTheme.textLowEmphasis), ), ], ); @@ -1218,7 +1218,7 @@ class _MessageWidgetState extends State Text( widget.readList!.length.toString(), style: style.copyWith( - color: _streamChatTheme.colorTheme.accentBlue, + color: _streamChatTheme.colorTheme.accentPrimary, ), ), const SizedBox(width: 2), @@ -1292,7 +1292,7 @@ class _MessageWidgetState extends State Text( 'Pinned by ${pinnedByMe ? 'You' : pinnedBy.name}', style: TextStyle( - color: _streamChatTheme.colorTheme.grey, + color: _streamChatTheme.colorTheme.textLowEmphasis, fontSize: 13, fontWeight: FontWeight.w400, ), @@ -1310,7 +1310,7 @@ class _MessageWidgetState extends State } if (hasUrlAttachments) { - return _streamChatTheme.colorTheme.blueAlice; + return _streamChatTheme.colorTheme.linkBg; } if (isOnlyEmoji) { @@ -1366,7 +1366,7 @@ class _ThreadParticipants extends StatelessWidget { child: Container( decoration: BoxDecoration( shape: BoxShape.circle, - color: _streamChatTheme.colorTheme.white, + color: _streamChatTheme.colorTheme.barsBg, ), padding: const EdgeInsets.all(1), child: UserAvatar( @@ -1395,7 +1395,7 @@ class _ThreadReplyPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint() - ..color = color ?? StreamChatTheme.of(context!).colorTheme.greyGainsboro + ..color = color ?? StreamChatTheme.of(context!).colorTheme.disabled ..style = PaintingStyle.stroke ..strokeWidth = 1 ..strokeCap = StrokeCap.round; diff --git a/packages/stream_chat_flutter/lib/src/option_list_tile.dart b/packages/stream_chat_flutter/lib/src/option_list_tile.dart index 10ae1b5f..16bf9e97 100644 --- a/packages/stream_chat_flutter/lib/src/option_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/option_list_tile.dart @@ -46,11 +46,11 @@ class OptionListTile extends StatelessWidget { return Column( children: [ Container( - color: separatorColor ?? chatThemeData.colorTheme.greyGainsboro, + color: separatorColor ?? chatThemeData.colorTheme.disabled, height: 1, ), Material( - color: tileColor ?? chatThemeData.colorTheme.white, + color: tileColor ?? chatThemeData.colorTheme.barsBg, child: SizedBox( height: 63, child: InkWell( 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 43620640..22ca4d4c 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -154,7 +154,7 @@ class QuotedMessageWidget extends StatelessWidget { color: _getBackgroundColor(context), border: showBorder ? Border.all( - color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + color: StreamChatTheme.of(context).colorTheme.disabled, ) : null, borderRadius: BorderRadius.only( @@ -280,7 +280,7 @@ class QuotedMessageWidget extends StatelessWidget { Color? _getBackgroundColor(BuildContext context) { if (_containsScrapeUrl) { - return StreamChatTheme.of(context).colorTheme.blueAlice; + return StreamChatTheme.of(context).colorTheme.linkBg; } return messageTheme.messageBackgroundColor; } diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index a3b2052f..9e279344 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -141,8 +141,8 @@ class ReactionBubble extends StatelessWidget { Icons.help_outline_rounded, size: 16, color: (!highlightOwnReactions || reaction.user?.id == userId) - ? chatThemeData.colorTheme.accentBlue - : chatThemeData.colorTheme.black.withOpacity(.5), + ? chatThemeData.colorTheme.accentPrimary + : chatThemeData.colorTheme.textHighEmphasis.withOpacity(.5), ), ); } diff --git a/packages/stream_chat_flutter/lib/src/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reaction_picker.dart index 5101cbb7..3e1e5e50 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_picker.dart @@ -49,7 +49,7 @@ class _ReactionPickerState extends State final child = Material( borderRadius: BorderRadius.circular(24), - color: chatThemeData.colorTheme.white, + color: chatThemeData.colorTheme.barsBg, clipBehavior: Clip.hardEdge, child: Padding( padding: const EdgeInsets.symmetric( diff --git a/packages/stream_chat_flutter/lib/src/sending_indicator.dart b/packages/stream_chat_flutter/lib/src/sending_indicator.dart index 88560432..7f44e5b8 100644 --- a/packages/stream_chat_flutter/lib/src/sending_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/sending_indicator.dart @@ -25,7 +25,7 @@ class SendingIndicator extends StatelessWidget { if (isMessageRead) { return StreamSvgIcon.checkAll( size: size, - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: StreamChatTheme.of(context).colorTheme.accentPrimary, ); } if (message.status == MessageSendingStatus.sent) { diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 64acae98..c050d8cb 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -101,8 +101,8 @@ class StreamChatState extends State { return Theme( data: materialTheme.copyWith( primaryIconTheme: streamTheme.primaryIconTheme, - accentColor: streamTheme.colorTheme.accentBlue, - scaffoldBackgroundColor: streamTheme.colorTheme.white, + accentColor: streamTheme.colorTheme.accentPrimary, + scaffoldBackgroundColor: streamTheme.colorTheme.barsBg, ), child: StreamChatCore( client: client, diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index 91ed2537..72b04a80 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -114,7 +114,7 @@ class StreamChatThemeData { final defaultTheme = StreamChatThemeData(brightness: theme.brightness); final customizedTheme = StreamChatThemeData.fromColorAndTextTheme( defaultTheme.colorTheme.copyWith( - accentBlue: theme.accentColor, + accentPrimary: theme.accentColor, ), defaultTheme.textTheme, ); @@ -216,8 +216,9 @@ class StreamChatThemeData { ColorTheme colorTheme, TextTheme textTheme, ) { - final accentColor = colorTheme.accentBlue; - final iconTheme = IconThemeData(color: colorTheme.black.withOpacity(.5)); + final accentColor = colorTheme.accentPrimary; + final iconTheme = + IconThemeData(color: colorTheme.textHighEmphasis.withOpacity(.5)); return StreamChatThemeData.raw( textTheme: textTheme, colorTheme: colorTheme, @@ -231,7 +232,7 @@ class StreamChatThemeData { ), ), channelPreviewTheme: ChannelPreviewTheme( - unreadCounterColor: colorTheme.accentRed, + unreadCounterColor: colorTheme.accentError, avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), constraints: const BoxConstraints.tightFor( @@ -244,7 +245,7 @@ class StreamChatThemeData { color: const Color(0xff7A7A7A), ), lastMessageAt: textTheme.footnote.copyWith( - color: colorTheme.black.withOpacity(.5), + color: colorTheme.textHighEmphasis.withOpacity(.5), ), indicatorIconSize: 16, ), @@ -256,7 +257,7 @@ class StreamChatThemeData { width: 40, ), ), - color: colorTheme.white, + color: colorTheme.barsBg, title: textTheme.headlineBold, ), channelTheme: ChannelTheme( @@ -268,7 +269,7 @@ class StreamChatThemeData { width: 40, ), ), - color: colorTheme.white, + color: colorTheme.barsBg, title: textTheme.headlineBold, subtitle: textTheme.footnote.copyWith( color: const Color(0xff7A7A7A), @@ -276,15 +277,17 @@ class StreamChatThemeData { ), ), ownMessageTheme: MessageTheme( - messageAuthor: textTheme.footnote.copyWith(color: colorTheme.grey), + messageAuthor: + textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), messageText: textTheme.body, - createdAt: textTheme.footnote.copyWith(color: colorTheme.grey), + createdAt: + textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), replies: textTheme.footnoteBold.copyWith(color: accentColor), - messageBackgroundColor: colorTheme.greyGainsboro, - reactionsBackgroundColor: colorTheme.white, - reactionsBorderColor: colorTheme.greyWhisper, - reactionsMaskColor: colorTheme.whiteSnow, - messageBorderColor: colorTheme.greyGainsboro, + messageBackgroundColor: colorTheme.disabled, + reactionsBackgroundColor: colorTheme.barsBg, + reactionsBorderColor: colorTheme.borders, + reactionsMaskColor: colorTheme.appBg, + messageBorderColor: colorTheme.disabled, avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), constraints: const BoxConstraints.tightFor( @@ -297,18 +300,20 @@ class StreamChatThemeData { ), ), otherMessageTheme: MessageTheme( - reactionsBackgroundColor: colorTheme.greyGainsboro, - reactionsBorderColor: colorTheme.white, - reactionsMaskColor: colorTheme.whiteSnow, + reactionsBackgroundColor: colorTheme.disabled, + reactionsBorderColor: colorTheme.barsBg, + reactionsMaskColor: colorTheme.appBg, messageText: textTheme.body, - createdAt: textTheme.footnote.copyWith(color: colorTheme.grey), - messageAuthor: textTheme.footnote.copyWith(color: colorTheme.grey), + createdAt: + textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), + messageAuthor: + textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), replies: textTheme.footnoteBold.copyWith(color: accentColor), messageLinks: TextStyle( color: accentColor, ), - messageBackgroundColor: colorTheme.white, - messageBorderColor: colorTheme.greyWhisper, + messageBackgroundColor: colorTheme.barsBg, + messageBorderColor: colorTheme.borders, avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), constraints: const BoxConstraints.tightFor( @@ -320,23 +325,23 @@ class StreamChatThemeData { messageInputTheme: MessageInputTheme( borderRadius: BorderRadius.circular(20), sendAnimationDuration: const Duration(milliseconds: 300), - actionButtonColor: colorTheme.accentBlue, - actionButtonIdleColor: colorTheme.grey, - expandButtonColor: colorTheme.accentBlue, - sendButtonColor: colorTheme.accentBlue, - sendButtonIdleColor: colorTheme.greyGainsboro, - inputBackground: colorTheme.white, + actionButtonColor: colorTheme.accentPrimary, + actionButtonIdleColor: colorTheme.textLowEmphasis, + expandButtonColor: colorTheme.accentPrimary, + sendButtonColor: colorTheme.accentPrimary, + sendButtonIdleColor: colorTheme.disabled, + inputBackground: colorTheme.barsBg, inputTextStyle: textTheme.body, idleBorderGradient: LinearGradient( colors: [ - colorTheme.greyGainsboro, - colorTheme.greyGainsboro, + colorTheme.disabled, + colorTheme.disabled, ], ), activeBorderGradient: LinearGradient( colors: [ - colorTheme.greyGainsboro, - colorTheme.greyGainsboro, + colorTheme.disabled, + colorTheme.disabled, ], ), ), @@ -347,7 +352,7 @@ class StreamChatThemeData { final theme = StreamChatTheme.of(context); return StreamSvgIcon.loveReaction( color: highlighted - ? theme.colorTheme.accentBlue + ? theme.colorTheme.accentPrimary : theme.primaryIconTheme.color!.withOpacity(.5), size: size, ); @@ -359,7 +364,7 @@ class StreamChatThemeData { final theme = StreamChatTheme.of(context); return StreamSvgIcon.thumbsUpReaction( color: highlighted - ? theme.colorTheme.accentBlue + ? theme.colorTheme.accentPrimary : theme.primaryIconTheme.color!.withOpacity(.5), size: size, ); @@ -371,7 +376,7 @@ class StreamChatThemeData { final theme = StreamChatTheme.of(context); return StreamSvgIcon.thumbsDownReaction( color: highlighted - ? theme.colorTheme.accentBlue + ? theme.colorTheme.accentPrimary : theme.primaryIconTheme.color!.withOpacity(.5), size: size, ); @@ -383,7 +388,7 @@ class StreamChatThemeData { final theme = StreamChatTheme.of(context); return StreamSvgIcon.lolReaction( color: highlighted - ? theme.colorTheme.accentBlue + ? theme.colorTheme.accentPrimary : theme.primaryIconTheme.color!.withOpacity(.5), size: size, ); @@ -395,7 +400,7 @@ class StreamChatThemeData { final theme = StreamChatTheme.of(context); return StreamSvgIcon.wutReaction( color: highlighted - ? theme.colorTheme.accentBlue + ? theme.colorTheme.accentPrimary : theme.primaryIconTheme.color!.withOpacity(.5), size: size, ); @@ -572,17 +577,17 @@ class TextTheme { class ColorTheme { /// Initialise with light theme ColorTheme.light({ - this.black = const Color(0xff000000), - this.grey = const Color(0xff7a7a7a), - this.greyGainsboro = const Color(0xffdbdbdb), - this.greyWhisper = const Color(0xffecebeb), - this.whiteSmoke = const Color(0xfff2f2f2), - this.whiteSnow = const Color(0xfffcfcfc), - this.white = const Color(0xffffffff), - this.blueAlice = const Color(0xffe9f2ff), - this.accentBlue = const Color(0xff005FFF), - this.accentRed = const Color(0xffFF3842), - this.accentGreen = const Color(0xff20E070), + this.textHighEmphasis = const Color(0xff000000), + this.textLowEmphasis = const Color(0xff7a7a7a), + this.disabled = const Color(0xffdbdbdb), + this.borders = const Color(0xffecebeb), + this.inputBg = const Color(0xfff2f2f2), + this.appBg = const Color(0xfffcfcfc), + this.barsBg = const Color(0xffffffff), + this.linkBg = const Color(0xffe9f2ff), + this.accentPrimary = const Color(0xff005FFF), + this.accentError = const Color(0xffFF3842), + this.accentInfo = const Color(0xff20E070), this.highlight = const Color(0xfffbf4dd), this.overlay = const Color.fromRGBO(0, 0, 0, 0.2), this.overlayDark = const Color.fromRGBO(0, 0, 0, 0.6), @@ -604,17 +609,17 @@ class ColorTheme { /// Initialise with dark theme ColorTheme.dark({ - this.black = const Color(0xffffffff), - this.grey = const Color(0xff7a7a7a), - this.greyGainsboro = const Color(0xff2d2f2f), - this.greyWhisper = const Color(0xff1c1e22), - this.whiteSmoke = const Color(0xff13151b), - this.whiteSnow = const Color(0xff070A0D), - this.white = const Color(0xff101418), - this.blueAlice = const Color(0xff00193D), - this.accentBlue = const Color(0xff005FFF), - this.accentRed = const Color(0xffFF3742), - this.accentGreen = const Color(0xff20E070), + this.textHighEmphasis = const Color(0xffffffff), + this.textLowEmphasis = const Color(0xff7a7a7a), + this.disabled = const Color(0xff2d2f2f), + this.borders = const Color(0xff1c1e22), + this.inputBg = const Color(0xff13151b), + this.appBg = const Color(0xff070A0D), + this.barsBg = const Color(0xff101418), + this.linkBg = const Color(0xff00193D), + this.accentPrimary = const Color(0xff005FFF), + this.accentError = const Color(0xffFF3742), + this.accentInfo = const Color(0xff20E070), this.borderTop = const Effect( sigmaX: 0, sigmaY: -1, @@ -657,37 +662,37 @@ class ColorTheme { }) : brightness = Brightness.dark; /// - final Color black; + final Color textHighEmphasis; /// - final Color grey; + final Color textLowEmphasis; /// - final Color greyGainsboro; + final Color disabled; /// - final Color greyWhisper; + final Color borders; /// - final Color whiteSmoke; + final Color inputBg; /// - final Color whiteSnow; + final Color appBg; /// - final Color white; + final Color barsBg; /// - final Color blueAlice; + final Color linkBg; /// - final Color accentBlue; + final Color accentPrimary; /// - final Color accentRed; + final Color accentError; /// - final Color accentGreen; + final Color accentInfo; /// final Effect borderTop; @@ -719,17 +724,17 @@ class ColorTheme { /// Copy with theme ColorTheme copyWith({ Brightness brightness = Brightness.light, - Color? black, - Color? grey, - Color? greyGainsboro, - Color? greyWhisper, - Color? whiteSmoke, - Color? whiteSnow, - Color? white, - Color? blueAlice, - Color? accentBlue, - Color? accentRed, - Color? accentGreen, + Color? textHighEmphasis, + Color? textLowEmphasis, + Color? disabled, + Color? borders, + Color? inputBg, + Color? appBg, + Color? barsBg, + Color? linkBg, + Color? accentPrimary, + Color? accentError, + Color? accentInfo, Effect? borderTop, Effect? borderBottom, Effect? shadowIconButton, @@ -741,17 +746,17 @@ class ColorTheme { }) => brightness == Brightness.light ? ColorTheme.light( - black: black ?? this.black, - grey: grey ?? this.grey, - greyGainsboro: greyGainsboro ?? this.greyGainsboro, - greyWhisper: greyWhisper ?? this.greyWhisper, - whiteSmoke: whiteSmoke ?? this.whiteSmoke, - whiteSnow: whiteSnow ?? this.whiteSnow, - white: white ?? this.white, - blueAlice: blueAlice ?? this.blueAlice, - accentBlue: accentBlue ?? this.accentBlue, - accentRed: accentRed ?? this.accentRed, - accentGreen: accentGreen ?? this.accentGreen, + textHighEmphasis: textHighEmphasis ?? this.textHighEmphasis, + textLowEmphasis: textLowEmphasis ?? this.textLowEmphasis, + disabled: disabled ?? this.disabled, + borders: borders ?? this.borders, + inputBg: inputBg ?? this.inputBg, + appBg: appBg ?? this.appBg, + barsBg: barsBg ?? this.barsBg, + linkBg: linkBg ?? this.linkBg, + accentPrimary: accentPrimary ?? this.accentPrimary, + accentError: accentError ?? this.accentError, + accentInfo: accentInfo ?? this.accentInfo, borderTop: borderTop ?? this.borderTop, borderBottom: borderBottom ?? this.borderBottom, shadowIconButton: shadowIconButton ?? this.shadowIconButton, @@ -762,17 +767,17 @@ class ColorTheme { bgGradient: bgGradient ?? this.bgGradient, ) : ColorTheme.dark( - black: black ?? this.black, - grey: grey ?? this.grey, - greyGainsboro: greyGainsboro ?? this.greyGainsboro, - greyWhisper: greyWhisper ?? this.greyWhisper, - whiteSmoke: whiteSmoke ?? this.whiteSmoke, - whiteSnow: whiteSnow ?? this.whiteSnow, - white: white ?? this.white, - blueAlice: blueAlice ?? this.blueAlice, - accentBlue: accentBlue ?? this.accentBlue, - accentRed: accentRed ?? this.accentRed, - accentGreen: accentGreen ?? this.accentGreen, + textHighEmphasis: textHighEmphasis ?? this.textHighEmphasis, + textLowEmphasis: textLowEmphasis ?? this.textLowEmphasis, + disabled: disabled ?? this.disabled, + borders: borders ?? this.borders, + inputBg: inputBg ?? this.inputBg, + appBg: appBg ?? this.appBg, + barsBg: barsBg ?? this.barsBg, + linkBg: linkBg ?? this.linkBg, + accentPrimary: accentPrimary ?? this.accentPrimary, + accentError: accentError ?? this.accentError, + accentInfo: accentInfo ?? this.accentInfo, borderTop: borderTop ?? this.borderTop, borderBottom: borderBottom ?? this.borderBottom, shadowIconButton: shadowIconButton ?? this.shadowIconButton, @@ -787,17 +792,17 @@ class ColorTheme { ColorTheme merge(ColorTheme? other) { if (other == null) return this; return copyWith( - black: other.black, - grey: other.grey, - greyGainsboro: other.greyGainsboro, - greyWhisper: other.greyWhisper, - whiteSmoke: other.whiteSmoke, - whiteSnow: other.whiteSnow, - white: other.white, - blueAlice: other.blueAlice, - accentBlue: other.accentBlue, - accentRed: other.accentRed, - accentGreen: other.accentGreen, + textHighEmphasis: other.textHighEmphasis, + textLowEmphasis: other.textLowEmphasis, + disabled: other.disabled, + borders: other.borders, + inputBg: other.inputBg, + appBg: other.appBg, + barsBg: other.barsBg, + linkBg: other.linkBg, + accentPrimary: other.accentPrimary, + accentError: other.accentError, + accentInfo: other.accentInfo, highlight: other.highlight, overlay: other.overlay, overlayDark: other.overlayDark, diff --git a/packages/stream_chat_flutter/lib/src/swipeable.dart b/packages/stream_chat_flutter/lib/src/swipeable.dart index 5895e8b5..041111e4 100644 --- a/packages/stream_chat_flutter/lib/src/swipeable.dart +++ b/packages/stream_chat_flutter/lib/src/swipeable.dart @@ -151,9 +151,8 @@ class _SwipeableState extends State with TickerProviderStateMixin { decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, + color: + StreamChatTheme.of(context).colorTheme.disabled, ), ), child: widget.backgroundIcon, diff --git a/packages/stream_chat_flutter/lib/src/system_message.dart b/packages/stream_chat_flutter/lib/src/system_message.dart index b847eb3c..11c80300 100644 --- a/packages/stream_chat_flutter/lib/src/system_message.dart +++ b/packages/stream_chat_flutter/lib/src/system_message.dart @@ -32,7 +32,7 @@ class SystemMessage extends StatelessWidget { textAlign: TextAlign.center, softWrap: true, style: theme.textTheme.captionBold.copyWith( - color: theme.colorTheme.grey, + color: theme.colorTheme.textLowEmphasis, ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart b/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart index 9905141a..b3fe510d 100644 --- a/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart @@ -59,7 +59,7 @@ class UploadProgressIndicator extends StatelessWidget { '${_percentage.toInt()}%', style: textStyle ?? theme.textTheme.footnote.copyWith( - color: theme.colorTheme.white, + color: theme.colorTheme.barsBg, ), ), ], diff --git a/packages/stream_chat_flutter/lib/src/url_attachment.dart b/packages/stream_chat_flutter/lib/src/url_attachment.dart index 4944c73c..5d5ca489 100644 --- a/packages/stream_chat_flutter/lib/src/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/url_attachment.dart @@ -60,7 +60,7 @@ class UrlAttachment extends StatelessWidget { borderRadius: const BorderRadius.only( topRight: Radius.circular(16), ), - color: chatThemeData.colorTheme.blueAlice, + color: chatThemeData.colorTheme.linkBg, ), child: Padding( padding: const EdgeInsets.only( @@ -71,7 +71,7 @@ class UrlAttachment extends StatelessWidget { child: Text( hostDisplayName, style: chatThemeData.textTheme.bodyBold.copyWith( - color: chatThemeData.colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentPrimary, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/user_avatar.dart b/packages/stream_chat_flutter/lib/src/user_avatar.dart index ecf1783f..31e07f0a 100644 --- a/packages/stream_chat_flutter/lib/src/user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/user_avatar.dart @@ -70,7 +70,7 @@ class UserAvatar extends StatelessWidget { constraints: constraints ?? streamChatTheme.ownMessageTheme.avatarTheme?.constraints, decoration: BoxDecoration( - color: streamChatTheme.colorTheme.accentBlue, + color: streamChatTheme.colorTheme.accentPrimary, ), child: hasImage ? CachedNetworkImage( @@ -95,7 +95,7 @@ class UserAvatar extends StatelessWidget { child: Container( constraints: constraints ?? streamChatTheme.ownMessageTheme.avatarTheme?.constraints, - color: selectionColor ?? streamChatTheme.colorTheme.accentBlue, + color: selectionColor ?? streamChatTheme.colorTheme.accentPrimary, child: Padding( padding: EdgeInsets.all(selectionThickness), child: avatar, @@ -115,7 +115,7 @@ class UserAvatar extends StatelessWidget { alignment: onlineIndicatorAlignment, child: Material( type: MaterialType.circle, - color: streamChatTheme.colorTheme.white, + color: streamChatTheme.colorTheme.barsBg, child: Container( margin: const EdgeInsets.all(2), constraints: onlineIndicatorConstraints ?? @@ -125,7 +125,7 @@ class UserAvatar extends StatelessWidget { ), child: Material( shape: const CircleBorder(), - color: streamChatTheme.colorTheme.accentGreen, + color: streamChatTheme.colorTheme.accentInfo, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/user_item.dart b/packages/stream_chat_flutter/lib/src/user_item.dart index e9d9f7c0..a13b544e 100644 --- a/packages/stream_chat_flutter/lib/src/user_item.dart +++ b/packages/stream_chat_flutter/lib/src/user_item.dart @@ -75,7 +75,7 @@ class UserItem extends StatelessWidget { ), trailing: selected ? StreamSvgIcon.checkSend( - color: chatThemeData.colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentPrimary, ) : null, title: Text( @@ -92,8 +92,8 @@ class UserItem extends StatelessWidget { user.online == true ? 'Online' : 'Last online ${Jiffy(user.lastActive).fromNow()}', - style: chatTheme.textTheme.footnote - .copyWith(color: chatTheme.colorTheme.black.withOpacity(.5)), + style: chatTheme.textTheme.footnote.copyWith( + color: chatTheme.colorTheme.textHighEmphasis.withOpacity(.5)), ); } } diff --git a/packages/stream_chat_flutter/lib/src/user_list_view.dart b/packages/stream_chat_flutter/lib/src/user_list_view.dart index f64f3c76..923e0334 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -285,7 +285,7 @@ class _UserListViewState extends State final chatThemeData = StreamChatTheme.of(context); return Container( key: ValueKey('HEADER-$header'), - color: chatThemeData.colorTheme.black.withOpacity(0.05), + color: chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.05), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), child: Text( @@ -293,7 +293,7 @@ class _UserListViewState extends State style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14.5, - color: chatThemeData.colorTheme.grey, + color: chatThemeData.colorTheme.textLowEmphasis, ), ), ), @@ -385,7 +385,7 @@ class _UserListViewState extends State return Container( color: StreamChatTheme.of(context) .colorTheme - .accentRed + .accentError .withOpacity(.2), child: const Padding( padding: EdgeInsets.symmetric(vertical: 16), @@ -408,6 +408,6 @@ class _UserListViewState extends State Widget _separatorBuilder(context, i) => Container( height: 1, - color: StreamChatTheme.of(context).colorTheme.greyWhisper, + color: StreamChatTheme.of(context).colorTheme.borders, ); } diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index d923b752..aee46800 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -31,7 +31,7 @@ Future showConfirmationDialog( final chatThemeData = StreamChatTheme.of(context); return showModalBottomSheet( useRootNavigator: false, - backgroundColor: chatThemeData.colorTheme.white, + backgroundColor: chatThemeData.colorTheme.barsBg, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -75,7 +75,7 @@ Future showConfirmationDialog( child: Text( cancelText, style: chatThemeData.textTheme.bodyBold.copyWith( - color: chatThemeData.colorTheme.black + color: chatThemeData.colorTheme.textHighEmphasis .withOpacity(0.5)), ), ), @@ -91,7 +91,7 @@ Future showConfirmationDialog( child: Text( okText, style: chatThemeData.textTheme.bodyBold.copyWith( - color: chatThemeData.colorTheme.accentRed), + color: chatThemeData.colorTheme.accentError), ), ), ), @@ -116,7 +116,8 @@ Future showInfoDialog( final chatThemeData = StreamChatTheme.of(context); return showModalBottomSheet( useRootNavigator: false, - backgroundColor: theme?.colorTheme.white ?? chatThemeData.colorTheme.white, + backgroundColor: + theme?.colorTheme.barsBg ?? chatThemeData.colorTheme.barsBg, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -147,8 +148,8 @@ Future showInfoDialog( height: 36, ), Container( - color: theme?.colorTheme.black.withOpacity(.08) ?? - chatThemeData.colorTheme.black.withOpacity(.08), + color: theme?.colorTheme.textHighEmphasis.withOpacity(.08) ?? + chatThemeData.colorTheme.textHighEmphasis.withOpacity(.08), height: 1, ), Center( @@ -159,8 +160,8 @@ Future showInfoDialog( child: Text( okText, style: TextStyle( - color: theme?.colorTheme.black.withOpacity(0.5) ?? - chatThemeData.colorTheme.accentBlue, + color: theme?.colorTheme.textHighEmphasis.withOpacity(0.5) ?? + chatThemeData.colorTheme.accentPrimary, fontWeight: FontWeight.w400, ), ), diff --git a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart index 23cc0e35..2286bc3d 100644 --- a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart +++ b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart @@ -94,8 +94,8 @@ class _VideoThumbnailImageState extends State { constraints: const BoxConstraints.expand(), child: widget.placeholderBuilder?.call(context) ?? Shimmer.fromColors( - baseColor: _streamChatTheme.colorTheme.greyGainsboro, - highlightColor: _streamChatTheme.colorTheme.whiteSmoke, + baseColor: _streamChatTheme.colorTheme.disabled, + highlightColor: _streamChatTheme.colorTheme.inputBg, child: Image.asset( 'images/placeholder.png', fit: BoxFit.cover, diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index e7ef400a..e12d2177 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -203,9 +203,9 @@ class _MessageScreenState extends State { final channel = StreamChannel.of(context).channel; return Scaffold( appBar: AppBar( - title: StreamBuilder>( - initialData: channel.state?.typingEvents, - stream: channel.state?.typingEventsStream, + title: StreamBuilder>( + initialData: channel.state?.typingEvents.keys, + stream: channel.state?.typingEventsStream.map((it) => it.keys), builder: (context, snapshot) { if (snapshot.hasData && snapshot.data!.isNotEmpty) { return Text('${snapshot.data!.first.name} is typing...'); @@ -344,7 +344,7 @@ extension on Channel { String? get name { final _channelName = extraData['name']; if (_channelName != null) { - return _channelName; + return _channelName as String; } else { return cid; } From 3a6bb102b951ef3a530553eb8b8a71ca2913d82c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 30 Jun 2021 17:29:33 +0530 Subject: [PATCH 491/599] fix: removed stream_chat docs --- .../docs/Flutter/stream_chat/_category_.json | 4 - .../Flutter/stream_chat/channel_members.mdx | 35 ----- .../Flutter/stream_chat/creating_channels.mdx | 47 ------- .../deleting_and_hiding_channel.mdx | 39 ------ .../docs/Flutter/stream_chat/events.mdx | 86 ------------ .../docs/Flutter/stream_chat/introduction.mdx | 23 ---- .../docs/Flutter/stream_chat/invites.mdx | 63 --------- .../docs/Flutter/stream_chat/messages.mdx | 127 ------------------ .../Flutter/stream_chat/moderation_tools.mdx | 120 ----------------- .../Flutter/stream_chat/muting_channels.mdx | 64 --------- .../stream_chat/paginating_channels.mdx | 37 ----- .../Flutter/stream_chat/pinned_messages.mdx | 66 --------- .../Flutter/stream_chat/querying_channels.mdx | 41 ------ .../Flutter/stream_chat/querying_users.mdx | 71 ---------- .../docs/Flutter/stream_chat/reactions.mdx | 52 ------- .../stream_chat/searching_messages.mdx | 33 ----- docusaurus/docs/Flutter/stream_chat/setup.mdx | 7 - .../Flutter/stream_chat/silent_messages.mdx | 39 ------ .../docs/Flutter/stream_chat/slow_mode.mdx | 17 --- .../stream_chat/threads_and_replies.mdx | 56 -------- .../Flutter/stream_chat/typing_indicators.mdx | 62 --------- .../Flutter/stream_chat/updating_channels.mdx | 48 ------- .../Flutter/stream_chat/uploading_files.mdx | 103 -------------- .../Flutter/stream_chat/user_presence.mdx | 94 ------------- .../docs/Flutter/stream_chat/user_types.mdx | 7 - .../Flutter/stream_chat/watching_channels.mdx | 116 ---------------- 26 files changed, 1457 deletions(-) delete mode 100644 docusaurus/docs/Flutter/stream_chat/_category_.json delete mode 100644 docusaurus/docs/Flutter/stream_chat/channel_members.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/creating_channels.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/events.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/introduction.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/invites.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/messages.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/muting_channels.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/paginating_channels.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/querying_channels.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/querying_users.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/reactions.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/searching_messages.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/setup.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/silent_messages.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/slow_mode.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/updating_channels.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/uploading_files.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/user_presence.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/user_types.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat/watching_channels.mdx diff --git a/docusaurus/docs/Flutter/stream_chat/_category_.json b/docusaurus/docs/Flutter/stream_chat/_category_.json deleted file mode 100644 index 900c09b4..00000000 --- a/docusaurus/docs/Flutter/stream_chat/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Stream Chat (LLC)", - "position": 2 -} \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/channel_members.mdx b/docusaurus/docs/Flutter/stream_chat/channel_members.mdx deleted file mode 100644 index e14b0c10..00000000 --- a/docusaurus/docs/Flutter/stream_chat/channel_members.mdx +++ /dev/null @@ -1,35 +0,0 @@ ---- -id: channel_members -sidebar_position: 10 -title: Channel Members ---- - -Manipulating Members In A Channel - -### Adding & Removing Channel Members - -Using the addMembers() method adds the given users as members, while removeMembers() removes them. - -```dart -await channel.addMembers(["thierry", "josh"]); -await channel.removeMembers(["tommaso"]); -``` - -You can optionally include a message object that client-side SDKs will use to populate a system message. -This works for both add and remove members: - -```dart -// using client-side client -await channel.addMembers(['tommaso'], Message(text: 'Tommaso joined')); -``` - -### Leaving a channel - -It is possible for user to leave the channel without moderator-level permissions. -Make sure channel members have `RemoveOwnChannelMembership` permission. - -```dart -// remove own channel membership -await channel.removeMembers(['my_user_id']); -``` - diff --git a/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx b/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx deleted file mode 100644 index 99b29907..00000000 --- a/docusaurus/docs/Flutter/stream_chat/creating_channels.mdx +++ /dev/null @@ -1,47 +0,0 @@ ---- -id: creating_channels -sidebar_position: 6 -title: Creating Channels ---- - -Create Channels In Your App - -Both channel channel.query and channel.watch methods ensure that a channel exists and create one otherwise. -If all you need is to ensure that a channel exists, you can use channel.create. -Typically you use channel.watch client side and channel.create for a server side integration. -Channels are unique either by the specified channel id or by the list of members. - -### Creating a Channel Using a Channel ID - -This is typically the best approach if you already have some sort of object in your database that the channel is associated with. -As an example if you're building a live-streaming service like Twitch you have a channel for every streamer in your database. - -```dart -final channel = client.channel( - "messaging", - id: "travel", - extraData: { - "name": "Founder Chat", - "image": "http://bit.ly/2O35mws", - "members": ["thierry", "tommaso"], - }, -); -``` - -Note: Note that for client side integrations you'll typically prefer to use channel.watch() instead of channel.create. -Channel.watch both creates the channel and watches it at the same time. - -### Creating a Channel for a List of Members - -Channels can be used to create conversations between users. -In most cases, you want conversations to be unique and make sure that a group of users have only one channel. - -You can achieve this by leaving the channel ID empty and provide channel type and members. -When you do so, the API will ensure that only one channel for the members you specified exists (the order of the members does not matter). - -Note: You cannot add/remove members for channels created this way. - -```dart -client.channel("messaging", extraData: {"members": ["thierry", "tommaso"]}); -``` - diff --git a/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx b/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx deleted file mode 100644 index 2a1d044f..00000000 --- a/docusaurus/docs/Flutter/stream_chat/deleting_and_hiding_channel.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -id: deleting_and_hiding_channel -sidebar_position: 13 -title: Deleting And Hiding A Channel ---- - -Remove Channels By Deleting Or Hiding - -### Deleting a Channel - -You can delete a Channel using the delete method. This marks the channel as deleted and hides all the content. - -```dart -await channel.delete(); -``` - -Note: If you recreate this channel, it will show up empty. Recovering old messages is not currently supported via the Stream Chat API. - -### Hiding a Channel - -Hiding a channel will remove it from query channel requests for that user until a new message is added. -Please keep in mind that hiding a channel is only available to members of that channel. - -Optionally you can also clear the entire message history of that channel for the user. -This way, when a new message is received, it will be the only one present in the channel. - -```dart -// hides the channel until a new message is added there -await channel.hide(); - -// shows a previously hidden channel -await channel.show(); - -// hide the channel and clear the message history -await channel.hide(clearHistory: true); -``` - -Note: You can still retrieve the list of hidden channels using the { "hidden" : true } query parameter. - diff --git a/docusaurus/docs/Flutter/stream_chat/events.mdx b/docusaurus/docs/Flutter/stream_chat/events.mdx deleted file mode 100644 index 979fca4c..00000000 --- a/docusaurus/docs/Flutter/stream_chat/events.mdx +++ /dev/null @@ -1,86 +0,0 @@ ---- -id: events -sidebar_position: 25 -title: Events ---- - -Understand Low Level Messaging Events - -Events allow the client to stay up to date with changes to the chat. Examples are a new message, a user's image that updated, a reaction, or a member joining the channel. There are a 4 different type of events. These first 2 types of events you receive if you're connected to Stream, no further action is needed to receive these events: - -Client events: You always receive these events. -Examples are `connection.recovered`, `health.check` and `connection.changed` -Notification events: Notification events notify you when something changed on a channel you are a member of even if you're not explicitly watching that channel. -One example is someone starting a new channel to message you while you are currently in the app. - -User presence events: `queryUsers`, `queryChannels` and `channel.watch()` allow you to specify `presence=True`, -if you specify this option you'll opt in to receiving updates about the users on these channels. -You'll receive events when they go online/offline or their data is updated -Channel events: if you call `queryChannels` with `watch=true`, or you call `channel.watch()` you'll opt-in to receiving events for the given channels. -Examples are new messages, people joining the chat etc. - -### Listening To Events - -As soon as you call watch on a Channel or queryChannels you’ll start to listen to these events. You can hook into specific events: - -```dart -channel.on("message.deleted").listen((Event event) { - print("message ${event.message.id} was deleted"); -}); -``` - -You can also listen to all events at once: - -```dart -channel.on().listen((Event event) { - print("received a new event of type ${event.type}"); -}); -``` - -### Connection Events - -The official SDKs make sure that a connection to Stream is kept alive at all times and that chat state -is recovered when the user's internet connection comes back online. -Your application can subscribe to changes to the connection using client events. - -```dart -client.on('connection.changed', (e) => { - if (e.online) { - print('the connection is up!'); - } else { - print('the connection is down!'); - } -}); -``` - -### Stop Listening for Events - -It is a good practice to unregister event handlers once they are not in use anymore. Doing so will save you from performance degradations coming from memory leaks or even from errors and exceptions (i.e. null pointer exceptions) - -```dart -final subscription = channel.on().listen((Event event) { - print("received a new event of type ${event.type}"); -}); - -subscription.cancel(); -``` - -### Custom Events - -Custom events allow you to build more complex interactions within a channel or with a user. - -#### To a channel - -Users connected to a channel, either as a watcher or member, can send custom events and have them delivered to all users watching the channel. - -```dart -// sends an event for the current user to all connect clients on the channel -await channel.sendEvent( - Event( - type: 'friendship_request', - extraData: { - 'text': 'Hey there, long time no see!', - }, - ), -); -``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/introduction.mdx b/docusaurus/docs/Flutter/stream_chat/introduction.mdx deleted file mode 100644 index 9681571e..00000000 --- a/docusaurus/docs/Flutter/stream_chat/introduction.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -id: introduction -sidebar_position: 1 -title: Introduction ---- -Exploring the Basics of the stream_chat package (LLC) - -

Official Dart Client for Stream Chat

- -The official Dart client for Stream Chat, a service for building chat applications. -This library can be used on any Dart project and on both mobile and web apps with Flutter. - -The Low Level Client ([stream_chat]('https://pub.dev/packages/stream_chat')) provides the basic -bindings for integrating Stream Chat into your Flutter app with the ability to connect users, -listening to events, send and receive messages, channel CRUD operations, etc. -This allows you to control and customise your interface completely while using the -Stream Chat backend. - -Check out our Core package ([stream_chat_flutter_core]('https://pub.dev/packages/stream_chat_flutter_core')) -for easier implementations of common uses such as retrieving channels, -retrieving channel message lists, seraching for messages, etc. - -This section goes into detail on using the LLC and various things you can achieve using it. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/invites.mdx b/docusaurus/docs/Flutter/stream_chat/invites.mdx deleted file mode 100644 index 20f536cc..00000000 --- a/docusaurus/docs/Flutter/stream_chat/invites.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -id: invites -sidebar_position: 11 -title: Invites ---- - -Invite A User To Join A Channel - -### Inviting Users - -Stream Chat provides the ability to invite users to a channel via the `channel` method with the `invites` array. -Upon invitation, the end-user will receive a notification that they were invited to the specified channel. - -See the following for an example on how to invite a user by adding an invites array containing the user ID: - -```dart -final invite = client.channel("messaging", id: "awesome-chat", - extraData: { - "name": "Founder Chat", - "members": ["thierry", "tommaso", "nick"], - "invites": ["nick"], - }); - -await invite.create(); -``` - -### Accepting an Invite - -In order to accept an invite, you must use call the `acceptInvite` method. -The `acceptInvite` method accepts and object with an optional `message` property. -Please see below for an example of how to call `acceptInvite`: - -```dart -final channel = client.channel("messaging", id: "awesome-chat"); -await channel.acceptInvite(); -``` - -### Rejecting an Invite - -To reject an invite, call the `rejectInvite` method. -This method does not require a user ID as it pulls the user ID from the current session in store from the `connectUser` call. - -```dart -await channel.rejectInvite(); -``` - -### Query for Accepted Invites - -Querying for accepted invites is done via the `queryChannels` method. -This allows you to return a list of accepted invites with a single call. See below for an example: - -```dart -final invites = await client.queryChannels(filter: {"invite": "accepted"}); -``` - -### Query for Rejected Invites - -Similar to querying for accepted invites, you can query for rejected invites with `queryChannels`. -See below for an example: - -```dart -final rejected = await client.queryChannels(filter: {"invite": "rejected"}); -``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/messages.mdx b/docusaurus/docs/Flutter/stream_chat/messages.mdx deleted file mode 100644 index f5608d45..00000000 --- a/docusaurus/docs/Flutter/stream_chat/messages.mdx +++ /dev/null @@ -1,127 +0,0 @@ ---- -id: messages -sidebar_position: 16 -title: Messages ---- - -Send, Update And Delete Messages - -Let's dive right into it, the example below shows how to send a simple message using Stream: - -```dart -final message = Message( - text: '@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.' -); - -await channel.sendMessage(message); -``` - -Note how server side SDKs require that you specify `user_id` to indicate who is sending the message. -You can add custom fields to both the message and the attachments. -There's a 5KB limit for the custom fields. -File uploads are uploaded to the CDN so don't count towards this 5KB limit. - -A more complex example for creating a message is shown below: - -```dart -final message = Message( - text: '@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.', - attachments: [ - Attachment( - type: "image", - assetUrl: "https://bit.ly/2K74TaG", - thumbUrl: "https://bit.ly/2Uumxti", - extraData: { - "myCustomField": 123, - } - ), - ], - mentionedUsers: [ - User(id: "josh") - ], - extraData: { - "anotherCustomField": 234, - }, -); - -await channel.sendMessage(message); -``` - -By default Stream’s UI components support the following attachment types: - -* Audio -* Video -* Image -* Text - -You can specify different types as long as you implement the frontend rendering logic to handle them. Common use cases include: - -* Embedding products (photos, descriptions, outbound links, etc.) -* Sharing of a users location -* The React tutorial for Stream Chat explains how to customize the Attachment component. - -### Get a Message - -You can get a single message by its ID using the `getMessage` call: - -```dart -final message = await client.getMessage("message-id"); -``` - -### Update a Message - -You can edit a message by calling updateMessage and including a message with an ID – the ID field is required when editing a message: - -```dart -await client.updateMessage(Message(id: "123", text: "the edited version of my text")); -``` - -### Partial Update - -A partial update can be used to set and unset specific fields when it is necessary to retain additional data fields on the object. -AKA a patch style update. - -```dart -// partial update message text -const text = 'the text was partial updated'; -const updated = await client.partiallyUpdateMessage(originalMessage.id, { - set: { - text - } -}); - -// unset color property -await client.partiallyUpdateMessage(originalMessage.id, { - 'unset': ['color'], -}); - -// set nested property -await client.partiallyUpdateMessage(originalMessage.id, { - 'set': { - 'details.status': 'complete' - }, -}); -``` - -### Delete A Message - -You can delete a message by calling deleteMessage and including a message with an ID. Messages can be soft deleted or hard deleted. Unless specified via the hard parameter, messages are soft deleted. - -```dart -await client.deleteMessage("123"); -``` - -### Soft delete - -1. Can be done client-side by users -2. Message is still returned in the message list and all its data is kept as it is -3. Message type is set to "deleted" -4. Reactions and replies are kept in place - -### Hard delete - -1. Can be done client-side by users but be cautious this action is not recoverable -2. The message is removed from the channel and its data is wiped -3. All reactions are deleted -4. All replies and their reactions are deleted -5. By default messages are soft deleted, this is a great way to keep the channel history consistent. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx b/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx deleted file mode 100644 index 749bd62a..00000000 --- a/docusaurus/docs/Flutter/stream_chat/moderation_tools.mdx +++ /dev/null @@ -1,120 +0,0 @@ ---- -id: moderation_tools -sidebar_position: 23 -title: Moderation Tools ---- - -Moderation Tools For A Better Chat Experience - -### Flag - -Any user is allowed to flag a message. -Flagging does not perform any particular action on the chat. -The API will only trigger the related webhook event and make the message appear on your Stream Dashboard Chat Moderation view. - -```dart -await client.flagMessage("messageID"); -``` - -### Mutes - -Any user is allowed to mute another user. -Mutes are stored at user level and returned with the rest of the user information when connectUser is called. -A user will be be muted until the user is unmuted or the mute is expired. - -```dart -await client.muteUser("eviluser"); - -await client.unmuteUser("eviluser"); -``` - -After muting a user messages will still be delivered via web-socket. -Implementing business logic such as hiding messages from muted users or display them differently is left to the developer to implement. - -Messages from muted users are not delivered via push (APN/Firebase) - -### Ban - -Users can be banned from an app entirely or from a channel. When a user is banned, they will not be allowed to post messages until the ban is removed or expired but will be able to connect to Chat and to channels as before. - -Users must be a member of a channel to be banned from that channel. Channel watchers cannot be banned. - -It is also possible to ban the user's last known IP address to prevent creation of new "throw-away" accounts. This type of ban is only applicable on the app level. We do not recommend applying IP ban without reasonable timeout, however this is not restricted. The IP address will be unbanned either after reaching a timeout or with explicit user unban. - -In most cases only admins or moderators are allowed to ban other users from a channel. - -```dart -// ban a user for 60 minutes from all channel -await client.banUser('eviluser', { - 'banned_by_id': userID, // ID of the user who is performing the ban (Server-side auth) - 'timeout': 60, - 'reason': 'Banned for one hour', -}); - -// ban a user and their IP address for 24 hours - await client.banUser('eviluser', { - 'banned_by_id': userID, - 'timeout': 24*60, - 'ip_ban': true, - 'reason': 'Please come back tomorrow', -}); - -// ban a user from the livestream:fortnite channel -await channel.banUser('eviluser', { - 'banned_by_id': userID, - 'reason': 'Profanity is not allowed here', -}); - -// remove ban from channel -await channel.unbanUser('eviluser'); - -// remove global ban -await authClient.unbanUser('eviluser'); -``` - -### Query Banned Users - -Banned users can be retrieved in different ways: - -Using the dedicated query bans endpoint -User Search: you can add the banned:true condition to your search. Please note that this will only return users that were banned at the app-level and not the ones that were banned only on channels. - -```dart -// retrieve the list of banned users - await client.queryUsers(filter: Filter.equal('banned', true), pagination: PaginationParams(limit:10, offset:0)); -``` - -### Shadow Ban - -Users can be shadow banned from a channel, set of channels, or an entire app. When a user is shadow banned, they will still be allowed to post messages, but any message sent during the ban, will have the shadowed: true field set; this will be invisible from the author of the message. - -You will need to implement UI logic for how your application will handle shadowed messages. Having the client hide these messages for everybody other than the user sending them is a common approach. - -```dart -// shadow ban a user from all channels -await client.shadowBan('eviluser'); - -// shadow ban a user from a channel -await channel.shadowBan('eviluser'); - -// remove shadow ban from channel -await channel.removeShadowBan('eviluser'); - -// remove global shadow ban -await client.removeShadowBan('eviluser'); -Administrators can view shadow banned user status in queryChannels(), queryMembers() and queryUsers(). -``` - -### Block Lists - -A list of words you can define to moderate chat messages. -A block list can be assigned to each channel type to either block or flag messages that contain these words. -More information can be found [here](https://getstream.io/chat/docs/react/block_lists/?language=dart). - -### Advanced Chat Moderation - -Advanced Chat Moderation is currently in beta and accepting trial candidates. Please contact support to discuss your options. -Advanced Chat Moderation uses an AI-based classification system to detect various types of bad content. -The tool is powered by a machine learning model that provides a confidence interval (0-1) for a message, -in each of three categories: Spam, Explicit and Toxic. -The model is highly configurable for each channel type and removes the manual work of a human moderator. You can learn more [here](https://getstream.io/chat/docs/react/advanced_moderation_beta/?language=dart). \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx b/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx deleted file mode 100644 index b7e114d3..00000000 --- a/docusaurus/docs/Flutter/stream_chat/muting_channels.mdx +++ /dev/null @@ -1,64 +0,0 @@ ---- -id: muting_channels -sidebar_position: 12 -title: Muting Channels ---- - -Muting Channels For Users - -Messages added to a channel will not trigger push notifications, nor change the unread count for the users that muted it. - -By default, mutes stay in place indefinitely until the user removes it; however, you can optionally set an expiration time. - -The list of muted channels and their expiration time is returned when the user connects. - -### Channel Mute - -```dart -// mute channel for current user -await channel.mute(); - -// mute a channel for 2 weeks -await channel.mute(expiration: Duration(days: 15)); - -// mute a channel for 10 seconds -await channel.mute(expiration: Duration(seconds: 10)); - -// check if channel is muted -channel.isMuted; -``` - -### Query Muted Channels - -Muted channels can be filtered or excluded by using the `muted` in your query channels filter. - -```dart -// retrieve all channels excluding muted ones -await client.queryChannels( - filter: { - 'members': { - r'$in': [userId], - }, - 'muted': false, - } - ); - -// retrieve all muted channels -await client.queryChannels( - filter: { - 'muted': true, - } - ); -``` - -### Remove a Channel Mute - -Whenever you need to unmute, you are able to. - -```dart -// unmute channel for current user -await channel.unmute(); - -// unmute channel for a user (server-side) -await channel.unmute({ user_id: userId }); -``` diff --git a/docusaurus/docs/Flutter/stream_chat/paginating_channels.mdx b/docusaurus/docs/Flutter/stream_chat/paginating_channels.mdx deleted file mode 100644 index d9c0d461..00000000 --- a/docusaurus/docs/Flutter/stream_chat/paginating_channels.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -id: paginating_channels -sidebar_position: 15 -title: Channel Pagination ---- - -Understanding Pagination For The Channel List - -The channel query endpoint allows you to paginate the list of messages, watchers, and members for one channel. -To make sure that you are able to retrieve a consistent list of messages, pagination does not work with simple offset/limit parameters but instead, -it relies on passing the ID of the messages from the previous page. - -For example: say that you fetched the first 100 messages from a channel and want to lead the next 100. -To do this you need to make a channel query request and pass the ID of the oldest message if you are paginating in descending order or the ID of the newest message if paginating in ascending order. - -Use the `id_lt` parameter to retrieve messages older than the provided ID and `id_gt` to retrieve messages newer than the provided ID. - -The terms `id_lt` and `id_gt` stand for ID less than and ID greater than. - -ID-based pagination improves performance and prevents issues related to the list of messages changing while you’re paginating. If needed, you can also use the inclusive versions of those two parameters: id_lte and id_gte. - -```dart -final response = await channel.query( - messagesPagination: PaginationParams(limit: 2, lessThanOrEqual: "123"), - membersPagination: PaginationParams(limit: 2, offset: 0), - watchersPagination: PaginationParams(limit: 2, offset: 0), -); -``` - -For members and watchers, we use limit and offset parameters. - -Notes: - -1. Soon we will create friendlier aliases for `id_lt` and `id_gt`. Our best candidates are before_id and after_id, -let us know if you have any feedback or suggestion! - -2. The maximum number of messages that can be retrieved at once from the API is 300. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx b/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx deleted file mode 100644 index 77579e4e..00000000 --- a/docusaurus/docs/Flutter/stream_chat/pinned_messages.mdx +++ /dev/null @@ -1,66 +0,0 @@ ---- -id: pinned_messages -sidebar_position: 22 -title: Pinned Messages ---- - -Pinning A Message In A Channel - -Pinned messages allow users to highlight important messages, make announcements, or temporarily promote content. -Pinning a message is, by default, restricted to certain user roles, but this is flexible. -Each channel can have multiple pinned messages and these can be created or updated with or without an expiration. - -### Pin and unpin a message - -An existing message can be updated to be pinned or unpinned by using the `channel.pinMessage` and `channel.unpinMessage` methods. -Or a new message can be pinned when it is sent by setting the `pinned` and `pin_expires` fields when using `channel.sendMessage`. - -```dart -// create pinned message -final message = await channel - .sendMessage(Message( - text: 'my message', - pinned: true, - pinExpires: DateTime.now().add(Duration(days: 3)), - )) - .then((resp) => resp.message); - -// unpin message -await channel.unpinMessage(message); - -// pin message for 120 seconds -await channel.pinMessage(message, 120); - -// change message expiration to 2077 -await channel.pinMessage(message, DateTime(2077)); - -// remove expiration date from pinned message -await channel.pinMessage(message, null); -``` - -To pin the message user has to have PinMessage permission. -You can find the list of permissions and defaults in Permission Resources and Default Permissions sections - -### Retrieve pinned messages - -You can easily retrieve the last 10 pinned messages from the `channel.pinned_messages` field: - -```dart -// get channel state -final channelState = await channel.query(); - -// get pinned messages from it -final pinnedMessages = channelState.pinnedMessages; -``` - -To learn more about channels you can visit Querying Channels page -Search for all pinned messages - -Stream Chat also provides search filter in case if you need to display more than 10 pinned messages in specific channel. - -```dart -// list all pinned messages of the channel -final response = await channel.search( - messageFilters: Filter.equal('pinned', true), -); -``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx b/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx deleted file mode 100644 index 68445bfe..00000000 --- a/docusaurus/docs/Flutter/stream_chat/querying_channels.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -id: querying_channels -sidebar_position: 7 -title: Querying Channels ---- - -More About Fetching A List Of Channels - -If you’re building a similar application to Facebook Messenger or Intercom, you’ll want to show a list of Channels. -The Chat API supports MongoDB style queries to make this easy to implement. - -You can query channels based on built-in fields as well as any custom field you add to channels. -Multiple filters can be combined using AND, OR logical operators, each filter can use its comparison (equality, inequality, greater than, greater or equal, etc.). You can find the complete list of supported operators in the query syntax section of the docs. - -As an example, let's say that you want to query the last conversations I participated in sorted by `last_message_at`. - -Here’s an example of how you can query the list of channels: - -```dart -final filter = Filter.in_('members', ['thierry']); - -final sort = [SortOption("last_message_at", direction: SortOption.DESC)]; - -final channels = await client.queryChannels( - filter: filter, - sort: sort, - options: { - "watch": true, - "state": true, - }, -); - -channels.forEach((Channel c) { - print("${c.extraData['name']} ${c.cid}"); -}); -``` - -The query channels endpoint will only return channels that the user can read, -you should make sure that the query uses a filter that includes such logic. -For example: messaging channels are readable only to their members, -such requirement can be included in the query filter (see below). \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/querying_users.mdx b/docusaurus/docs/Flutter/stream_chat/querying_users.mdx deleted file mode 100644 index 883d4218..00000000 --- a/docusaurus/docs/Flutter/stream_chat/querying_users.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -id: querying_users -sidebar_position: 4 -title: Querying Users ---- -Querying Users Across Channels - -The Query Users method allows you to search for users and see if they are online/offline. -The example below shows how you can retrieve the details for 3 users in one API call: - -```dart -final _result = client.queryUsers( - filter: Filter.in_('id', ['john', 'jack', 'jessie']), -); -``` - -You can also add sort options and pagination to the query: - -```dart -final _result = client.queryUsers( - filter: Filter.in_('id', ['john', 'jack', 'jessie']), - sort: [SortOption('last_active')], - pagination: PaginationParams( - offset: 0, - limit: 20, - ), -); -``` - -You can filter and sort on the custom fields you've set for your user, the user id, and when the user was last active. - -The options for the queryUser method are presence, limit, and offset. -If presence is true this makes sure you receive the user.presence.changed event when a user goes online or offline. - -### Querying using the autocomplete operator - -You can autocomplete the results of your user query by username and/or ID. - -### By Custom Field - -```dart -final _result = client.queryUsers( - filter: Filter.autoComplete('name', 'ro'), -); -``` -This would return an array of any matching users, such as: - -```json -[ - { - "id": "userID", - "name": "Curiosity Rover" - }, - { - "id": "userID2", - "name": "Roxy" - }, - { - "id": "userID3", - "name": "Roxanne" - } -] -``` - -### By ID - -```dart -final _result = client.queryUsers( - filter: Filter.autoComplete('id', 'USER_ID'), -); -``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/reactions.mdx b/docusaurus/docs/Flutter/stream_chat/reactions.mdx deleted file mode 100644 index 5ca279bd..00000000 --- a/docusaurus/docs/Flutter/stream_chat/reactions.mdx +++ /dev/null @@ -1,52 +0,0 @@ ---- -id: reactions -sidebar_position: 18 -title: Reactions ---- - -Adding And Removing User Reactions - -Stream Chat has built-in support for user Reactions. Common examples are likes, comments, loves, etc. Reactions can be customized so that you are able to use any type of reaction your application requires. - - -Similar to other objects in Stream Chat, reactions allow you to add custom data to the reaction of your choice. This is helpful if you want to customize the reaction logic. - -Note: Custom data for reactions is limited to 1KB. - -```dart -await channel.sendReaction("messageID", "like", extraData: {"customField": 1}); -``` - -### Removing a Reaction - -```dart -await channel.deleteReaction("messageID", "like"); -``` - -### Paginating Reactions - -Messages returned by the APIs automatically include the 10 most recent reactions. -You can also retrieve more reactions and paginate using the following logic: - -```dart -// get the first 10 reactions -await channel.getReactions("messageID", PaginationParams(limit: 10)); - -// get 3 reactions past the first 10 -await channel.getReactions("messageID", PaginationParams(limit: 3, offset:10)); -``` - -### Cumulative (Clap) Reactions - -You can use the Reactions API to build something similar to Medium's clap reactions. -If you are not familiar with this, Medium allows you to clap articles more than once and shows the sum of all claps from all users. - -To do this, you only need to include a score for the reaction (ie. user X clapped 25 times) and the API will return the sum of all reaction scores as well as each user individual scores (ie. clapped 475 times, user Y clapped 14 times). - -```dart -// user claps 5 times on a message -await channel.sendReaction("messageID", "like", score: 5); - -// same user claps 20 times more -await channel.sendReaction("messageID", "like", score: 25); -``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx b/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx deleted file mode 100644 index 76873897..00000000 --- a/docusaurus/docs/Flutter/stream_chat/searching_messages.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -id: searching_messages -sidebar_position: 20 -title: Searching Messages ---- - -Searching For Messages Across Channels - -Message search is built-in to the chat API. You can enable and/or disable the search indexing on a per channel type. -The command shown below selects the channels in which John is a member. -Next, it searches the messages in those channels for the keyword “'supercalifragilisticexpialidocious'”: - -```dart -final search = await client.search( - Filter.in_('members', ['john']), - query: 'supercalifragilisticexpialidocious', - paginationParams: PaginationParams( - limit: 2, - offset: 0 - ), -); -``` - -Pagination works via the standard limit and offset parameters. The first argument, filter, uses a MongoDB style query expression. - -We do not run MongoDB on the backend, so only a subset of the standard MongoDB filters are supported. -Additionally, this endpoint can be used to search for messages that have attachments. - -```dart -// Search file of type videos - final response = await channel.search(messageFilter: Filter.in_('attachments.type', ['video'])); - return response; -``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/setup.mdx b/docusaurus/docs/Flutter/stream_chat/setup.mdx deleted file mode 100644 index fe5d9c7b..00000000 --- a/docusaurus/docs/Flutter/stream_chat/setup.mdx +++ /dev/null @@ -1,7 +0,0 @@ ---- -id: setup -sidebar_position: 2 -title: Setup ---- - -Understanding Setup \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx b/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx deleted file mode 100644 index d14dc4a8..00000000 --- a/docusaurus/docs/Flutter/stream_chat/silent_messages.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -id: silent_messages -sidebar_position: 21 -title: Silent Messages ---- - -Pushing Silent Messages - -Sometimes you want to add system or transactional messages to channels such as: "your ride is waiting for you", -"James updated the information for the trip", "You and Jane are now matched" and so on. - -You may not want these messages to mark a channel as unread or increase the unread messages for users. - -Silent messages are special messages that don't increase the unread messages count nor mark a channel as unread. -Creating a silent message is very simple, you only need to include the silent field boolean field and set it to true. - -```dart -final text = 'You completed your trip'; - -const message = { - - text: text, - - user: systemUser, - - silent: true, - - attachments: tripAttachments, - -}; -await channel.sendMessage(message); -``` - -Notes: - -1. Existing messages cannot be turned into a silent message or vice versa. - -2. Silent messages do send push notifications by default. To skip our push notification service, -mark the message with `skip_push: true`. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx b/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx deleted file mode 100644 index 4ec75c2e..00000000 --- a/docusaurus/docs/Flutter/stream_chat/slow_mode.mdx +++ /dev/null @@ -1,17 +0,0 @@ ---- -id: slow_mode -sidebar_position: 14 -title: Slow Mode ---- - -Using Slow Mode For Reduced Message Traffic - -Slow mode helps reduce noise on a channel by limiting users to a maximum of 1 message per cooldown interval. - -The cooldown interval is configurable and can be anything between 1 and 120 seconds. For instance, if you enable slow mode and set the cooldown interval to 30 seconds a user will be able to post at most 1 message every 30 seconds. - -Moderators, admins and server-side API calls are not restricted by the cooldown period and can post messages as usual. -Slow mode is disabled by default and can be enabled/disabled by admins and moderators. - -Note: SLOW MODE is in the works for the Flutter SDK - diff --git a/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx b/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx deleted file mode 100644 index 99c04d56..00000000 --- a/docusaurus/docs/Flutter/stream_chat/threads_and_replies.mdx +++ /dev/null @@ -1,56 +0,0 @@ ---- -id: threads_and_replies -sidebar_position: 19 -title: Threads And Replies ---- - -Creating And Paginating Threads - -Threads and replies provide your users with a way to go into more detail about a specific topic. - -This can be very helpful to keep the conversation organized and reduce noise. -To create a thread you simply send a message with a `parent_id`. Have a look at the example below: - -```dart -final reply = await channel.sendMessage( - Message(text: 'Hey, I am replying to a message!', - parentId: parentID, - showInChannel: false, -)); -``` - -If you specify `show_in_channel`, the message will be visible both in a thread of replies as well as the main channel. -Messages inside a thread can also have reactions, attachments and mention as any other message. - -### Thread Pagination - -When you read a channel you do not receive messages inside threads. -The parent message includes the count of replies which it is usually what apps show as the link to the thread screen. -Reading a thread and paginating its messages works in a very similar way as paginating a channel. - -```dart -// retrieve the first 20 messages inside the thread -await channel.getReplies(parentMessageId, PaginationParams(limit: 20)); - -// retrieve the 20 more messages before the message with id "42" -await channel.getReplies(parentMessageId, PaginationParams(limit: 20, lessThanOrEqual: '42')); -``` - -### Quote Message - -Instead of replying in a thread, it's also possible to quote a message. Quoting a message doesn't result in the creation of a thread; -the message is quoted inline. - -To quote a message, simply provide the `quoted_message_id` field when sending a message: - -```dart -// Create the initial message -await channel.sendMessage(Message(id: 'first_message_id', text: 'The initial message' )); - -// Quote the initial message -final res = await channel.sendMessage(Message( - id: 'message_with_quoted_message', - text: 'This is the first message that quotes another message', - quotedMessageId: 'first_message_id', - )); -``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx b/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx deleted file mode 100644 index 73c04951..00000000 --- a/docusaurus/docs/Flutter/stream_chat/typing_indicators.mdx +++ /dev/null @@ -1,62 +0,0 @@ ---- -id: typing_indicators -sidebar_position: 24 -title: Typing Indicators ---- - -Adding Typing Indicators To Your App - -All official SDKs support typing events out of the box and are handled on all channels with the typing_events featured enabled. -Typing indicators allow you to show to users who is currently typing in the channel. -This feature can be switched on/off on a channel-type basis using the CLI or directly from the Dashboard. -If you are using one of the official SDK libraries, you will only need to ensure that typing indicators are enabled to get this working. - -If you are building your UI on top of one of our Chat Clients instead, you will need to take care of four things: - -Send an event `typing.start` when the user starts typing -Send an event `typing.stop` after the user stopped typing -Handle the two events and use them to toggle the typing indicator UI -Use `parent_id` field of the event to indicate that typing is happening in a thread - -### Sending start and stop typing events - -```dart -// The Dart client keeps track of the typing state for you. -// Just call `channel.keystroke()` when the user types and -// `channel.stopTyping()` when the user sends the message (or aborts) - -// sends a typing.start event at most once every two seconds -await channel.keystroke(); - -// sends a typing.start event for a particular thread -await channel.keystroke(thread_id); - -// sends the typing.stop event -await channel.stopTyping(); -``` - -When sending events on user input, you should make sure to follow some best-practices to avoid bugs. - -Only send typing.start when the user starts typing -Send typing.stop after a few seconds since the last keystroke - -### Receiving typing indicator events - -```dart -// channels keep track of the users that are currently typing -// the `channel.state.typingEvents` is an immutable object which gets regenerated -// every time a new user is added or removed to this list -print(channel.state.typingEvents); - -// add typing start event handling -channel.on('typing.start', (event) => { - print('${event.user.name} started typing'); -}); - -// add typing stop event handling -channel.on('typing.stop', event => { - print('${event.user.name} stopped typing'); -}); -``` - -Because clients might fail at sending `typing.stop` event all Chat clients periodically prune the list of typing users. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx b/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx deleted file mode 100644 index 0d65110d..00000000 --- a/docusaurus/docs/Flutter/stream_chat/updating_channels.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -id: updating_channels -sidebar_position: 9 -title: Updating Channels ---- - -Exploring Multiple Ways To Update A Channel - -There are two ways to update a channel using the Stream API - a partial or full update. -A partial update will retain any custom key-value data, whereas a complete update is going to remove any that are unspecified in the API request. - -### Partial Update - -A partial update can be used to set and unset specific fields when it is necessary to retain additional custom data fields on the object. -AKA a patch style update. - -```dart -// Here's a channel with some custom field data that might be useful -var channel = client.channel(type, id: id, extraData: { - "source": "user", - "source_detail" :{ "user_id": 123 }, - "channel_detail" :{ "topic": "Plants and Animals", "rating": "pg" } -}); - -// let's change the source of this channel -await channel.updatePartial({ "set" :{ "source": "system" } }); - -// since it's system generated we no longer need source_detail -await channel.updatePartial({ "unset": ["source_detail"] }); - -// and finally update one of the nested fields in the channel_detail -await channel.updatePartial({ "set": { "channel_detail.topic": "Nature" } }); - -// and maybe we decide we no longer need a rating -await channel.updatePartial({ "unset": ["channel_detail.rating"] }); -``` - -### Full Update (overwrite) - -The update function updates all of the channel data. -Any data that is present on the channel and not included in a full update will be deleted. - -```dart -await channel.update({ - "name": "myspecialchannel", - "color": "green", -}, Message(text: "Thierry changed the channel color to green")); -``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx b/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx deleted file mode 100644 index 2266e8e9..00000000 --- a/docusaurus/docs/Flutter/stream_chat/uploading_files.mdx +++ /dev/null @@ -1,103 +0,0 @@ ---- -id: uploading_files -sidebar_position: 17 -title: Uploading Files ---- - -Uploading Files And Using A Custom CDN - -The `channel.sendImage` and `channel.sendFile` methods make it easy to upload files. - -This functionality defaults to using the Stream CDN. If you would like, you can easily change the logic to upload to your own CDN of choice. -The maximum file size is 20mb for the Stream Chat CDN. - -```dart -final client = StreamChatClient('api-key'); -var attachment = Attachment(); - -// Upload an image without monitoring send progress -client.sendImage(image, channelId, channelType).then((response) { - // Successful upload, you can now attach this image - // to an message that you then send to a channel - final imageUrl = response.file; - attachment = attachment.copyWith( - type: 'image', - imageUrl: imageUrl, - ); - final message = Message(attachments: [attachment]); - client.sendMessage(message, channelId, channelType); -}).catchError((error, stk) { - // Handle error -}); - -// Upload an file, monitoring the progress with a onSendProgress callback -await client.sendFile( - file, - channelId, - channelType, - onSendProgress: (sent, total) { - // Handle the send progress - attachment = attachment.copyWith( - uploadState: UploadState.inProgress( - uploaded: sent, - total: total, - ), - ); - }, - ).then((response) { - // Successful upload, you can now attach this file - // to an message that you then send to a channel - final fileUrl = response.file; - attachment = attachment.copyWith( - type: 'file', - assetUrl: fileUrl, - uploadState: UploadState.success(), - ); - final message = Message(attachments: [attachment]); - client.sendMessage(message, channelId, channelType); - }).catchError((error, stk) { - // Handle error - attachment = attachment.copyWith( - uploadState: UploadState.failed(error: error), - ); - }); - -// Alternatively you can call the sendMessage directly on the channel -// which will automatically handle all the upload process of the provided -// attachments. -final channel = client.channel(channelType, id: channelId); - -// Creating a message object with multiple local attachments -final message = Message(text: 'Hello', attachments: [ - Attachment( - type: 'image', - file: AttachmentFile(path: 'imagePath/imageName.png'), - ), - Attachment( - type: 'file', - file: AttachmentFile(path: 'filePath/fileName.pdf'), - ), -]); - -// Sending the message to the channel -await channel.sendMessage(message); -``` - -In the code example above, note how the message attachments are created after the files are uploaded. The React components support regular uploads, clipboard pasting, drag and drop, as well as URL enrichment via built-in open-graph scraping. As a bonus, the Stream CDN will automatically handle image resizing for you. - -### Using Your Own CDN - -All 5 SDKs make it easy to use your own CDN for uploads. The code examples below show how to change where files are uploaded: - -```dart -// Set a custom FileUploader implementation when building your client -final client = StreamChatClient( - 'api-key', - attachmentFileUploader: MyFileUploader(), -); -``` - -You'll have to create your own implementation of the `AttachmentFileUploader` interface, -and any upload calls will be sent to that implementation. -Take a look [at the interface](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat/lib/src/attachment_file_uploader.dart#L7) -and [our own default implementation of it](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat/lib/src/attachment_file_uploader.dart#L58) for more info. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/user_presence.mdx b/docusaurus/docs/Flutter/stream_chat/user_presence.mdx deleted file mode 100644 index ce6ff24a..00000000 --- a/docusaurus/docs/Flutter/stream_chat/user_presence.mdx +++ /dev/null @@ -1,94 +0,0 @@ ---- -id: user_presence -sidebar_position: 5 -title: User Presence ---- - -Check If A User Is Online / Check Last Active - -User presence allows you to show when a user was last active and if they are online right now. -Whenever you read a user the data will look like this: - -```json -{ - id: 'unique_user_id', - online: true, - status: 'Eating a veggie burger...', - last_active: '2019-01-07T13:17:42.375Z' -} -``` - -Notes: - -1. The online field indicates if the user is online. The status field stores text indicating the current user status. -2. The last_active field is updated when a user connects and then refreshed every 15 minutes. - -### Invisible - -To mark a user invisible simply set the invisible property to true. You can also set a custom status message at the same time: - -```dart -await client.connectUser( - User( - id: 'super-band-9', - extraData: { - 'invisible': true, - }, - ), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', - ); -``` - -Notes: - -1. When invisible is set to true, the current user will appear as offline to other users. - -### Listening to Presence Changes - -Of course, you want to listen to the user presence changes. This allows you to show a user as offline when they leave and update their status in real time. -These 3 endpoints allow you to watch user presence: - -```dart -// If you pass presence: true to channel.watch it will watch the list of user presence changes. -// Note that you can listen to at most 10 users using this API call -final channel = client.channel( - 'messaging', - id: 'flutterdevs', - extraData: { - 'name': 'Flutter devs', - }, -); - - -await channel.watch({ - 'presence': true, -}); - -// queryChannels allows you to listen to the members of the channels that are returned -// so this does the same thing as above and listens to online status changes for john and jack - -final filter = Filter.in_('members', ['john']); - -final sort = [SortOption("last_message_at", direction: SortOption.DESC)]; - -final channels = await client.queryChannels( - filter: filter, - sort: sort, - options: { - 'presence': true, - }, -); - -// queryUsers allows you to listen to user presence changes for john and jack -final result = await client.queryUsers( - filter: Filter.in_('id', ['john', 'jack', 'jessie']), - sort: [SortOption('last_active')], - pagination: PaginationParams( - offset: 0, - limit: 20, - ), - options: { - 'presence': true, - }, -); -``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/user_types.mdx b/docusaurus/docs/Flutter/stream_chat/user_types.mdx deleted file mode 100644 index 6b273a1b..00000000 --- a/docusaurus/docs/Flutter/stream_chat/user_types.mdx +++ /dev/null @@ -1,7 +0,0 @@ ---- -id: user_types -sidebar_position: 3 -title: User Types ---- - -Diving Into User Types \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx b/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx deleted file mode 100644 index c379d324..00000000 --- a/docusaurus/docs/Flutter/stream_chat/watching_channels.mdx +++ /dev/null @@ -1,116 +0,0 @@ ---- -id: watching_channels -sidebar_position: 8 -title: Watching Channels ---- - -More About Watching A Channel - -The call to channel.watch does a few different things in one API call: - -* It creates the channel if it doesn't exist yet (if this user has the right permissions to create a channel) -* It queries the channel state and returns members, watchers and messages -* It watches the channel state and tells the server that you want to receive events when anything in this channel changes -* To start watching a channel - -The examples below show how to watch a channel. Note that you need to be connected as a user before you can watch a channel. - -```dart -final state = await channel.watch(); -``` - -### Watchers vs Members - -The concepts of watchers vs members can require a bit of clarification: - -* Members: a permanent association between a user and a channel. If the user is online and not watching the channel they will receive a notification event, if they are offline they will receive a push notification. -* Watchers: the list of watchers is temporary. It's anyone who is currently watching the channel. - -Being able to send messages, and otherwise engage with a channel as a non-member requires certain permissions. -For example, we have pre-configured permissions on our livestream channel type to allow non-members to interact, -but in the messaging channel type, only members of the channel can interact. - -### Watching Multiple Channels - -The default queryChannels API returns channels and starts watching them. -There is no need to also use channel.watch on the channels returned from queryChannels - -```dart -// first let’s create a filter to make messaging channels that include a specific user -final filter = Filter.in_('members', [user_id]); - -// we can also define a sort order of most recent messages first -final sort = [SortOption("last_message_at", direction: SortOption.DESC)]; - -// finally, we can query for those channels, automatically watching them for the -// currently connected user -final channels = await client.queryChannels( - filter: filter, - sort: sort, - options: { - "watch": true, - "state": true, - }, -); -``` - -### Stop Watching a Channel - -To stop receiving channel events: - -```dart -// we can also stop watching a channel -final stopWatching = await channel.stopWatching(); -``` - -### Watcher Count - -To get the watcher count of a channel: - -```dart -// create a new channel of type “livestream” with name “watch-this-channel” -final channel = client.channel("livestream", id: "watch-this-channel"); - -// retrieve our channels -channel.query(); - -// each channel object has a state collection with a watcher_count property - return channel.state.watcherCount; -``` - -### Paginating Channel Watchers with channel.query - -```dart -// create a new channel of type “livestream” with name “watch-this-channel” -final channel = client.channel("livestream", id: "watch-this-channel"); - -// now query the newly created channel for watchers, retrieving the first 5 -final result = await channel.query( - watchersPagination: PaginationParams( - limit: 5, - offset: 0, - ), - ); - - return result.watchers; -``` - -### Listening to Changes in Watchers - -A user already watching the channel can listen to users starting and stopping watching the channel with the realtime events: - -```dart -final channel = client.channel("livestream", id: "watch-this-channel"); - -await channel.watch(); - -// handle watch started event -channel - .on("user.watching.start") - .listen((event) => print('${event.user.id} started watching')); - -// handle watch stopped event -channel - .on("user.watching.stop") - .listen((event) => print('${event.user.id} stopped watching')); -``` From 495351b756d7adb2425ce143872d6a6e9f1dcfe9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 1 Jul 2021 10:27:44 +0200 Subject: [PATCH 492/599] remove proxy props from `MessageListView` --- .../lib/src/message_list_view.dart | 49 ++----------------- .../lib/src/message_list_core.dart | 8 +-- 2 files changed, 7 insertions(+), 50 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 6e61612b..69f10d68 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -142,7 +142,6 @@ class MessageListView extends StatefulWidget { this.parentMessage, this.threadBuilder, this.onThreadTap, - this.onReplyTap, this.dateDividerBuilder, this.scrollPhysics = const ClampingScrollPhysics(), this.initialScrollIndex, @@ -152,7 +151,6 @@ class MessageListView extends StatefulWidget { this.onMessageSwiped, this.highlightInitialMessage = false, this.messageHighlightColor, - this.onShowMessage, this.showConnectionStateTile = false, this.headerBuilder, this.footerBuilder, @@ -160,16 +158,11 @@ class MessageListView extends StatefulWidget { this.emptyBuilder, this.systemMessageBuilder, this.messageListBuilder, - this.errorWidgetBuilder, + this.errorBuilder, this.messageFilter, - this.customAttachmentBuilders, this.onMessageTap, this.onSystemMessageTap, - this.onAttachmentTap, - this.onLinkTap, this.pinPermissions = const [], - this.textBuilder, - this.usernameBuilder, this.showFloatingDateDivider = true, this.threadSeparatorBuilder, this.messageListController, @@ -222,9 +215,6 @@ class MessageListView extends StatefulWidget { /// Called when message item gets swiped final OnMessageSwiped? onMessageSwiped; - /// - final ReplyTapCallback? onReplyTap; - /// If true the list will highlight the initialMessage if there is any. /// /// Also See [StreamChannel] @@ -233,9 +223,6 @@ class MessageListView extends StatefulWidget { /// Color used while highlighting initial message final Color? messageHighlightColor; - /// Callback when show message is tapped - final ShowMessageCallback? onShowMessage; - /// Flag for showing tile on header final bool showConnectionStateTile; @@ -262,16 +249,11 @@ class MessageListView extends StatefulWidget { /// This parameter can be used to display an error message to /// users in the event /// of a connection failure. - final ErrorBuilder? errorWidgetBuilder; + final ErrorBuilder? errorBuilder; /// Predicate used to filter messages final bool Function(Message)? messageFilter; - /// Attachment builders for the default message widget - /// Please change this in the [MessageWidget] if you are using a - /// custom implementation - final Map? customAttachmentBuilders; - /// Called when any message is tapped except a system message /// (use [onSystemMessageTap] instead) final OnMessageTap? onMessageTap; @@ -279,18 +261,6 @@ class MessageListView extends StatefulWidget { /// Called when system message is tapped final OnMessageTap? onSystemMessageTap; - /// Customize onTap on attachment - final void Function(Message message, Attachment attachment)? onAttachmentTap; - - /// Customize the MessageWidget textBuilder - final Widget Function(BuildContext context, Message message)? textBuilder; - - /// Customize the MessageWidget usernameBuilder - final Widget Function(BuildContext context, Message message)? usernameBuilder; - - /// Callback for when link is tapped - final void Function(String link)? onLinkTap; - /// A List of user types that have permission to pin messages final List pinPermissions; @@ -376,7 +346,7 @@ class _MessageListViewState extends State { (context, list) => _buildListView(list), messageListController: _messageListController, parentMessage: widget.parentMessage, - errorWidgetBuilder: widget.errorWidgetBuilder ?? + errorBuilder: widget.errorBuilder ?? (BuildContext context, Object error) => Center( child: Text( 'Something went wrong', @@ -823,7 +793,6 @@ class _MessageListViewState extends State { showUsername: !isMyMessage, padding: const EdgeInsets.all(8), showSendingIndicator: false, - onThreadTap: _onThreadTap, borderRadiusGeometry: BorderRadius.only( topLeft: const Radius.circular(16), bottomLeft: @@ -841,7 +810,6 @@ class _MessageListViewState extends State { messageTheme: isMyMessage ? _streamTheme.ownMessageTheme : _streamTheme.otherMessageTheme, - onShowMessage: widget.onShowMessage, onReturnAction: (action) { switch (action) { case ReturnActionType.none: @@ -852,16 +820,12 @@ class _MessageListViewState extends State { break; } }, - customAttachmentBuilders: widget.customAttachmentBuilders, onMessageTap: (message) { if (widget.onMessageTap != null) { widget.onMessageTap!(message); } FocusScope.of(context).unfocus(); }, - textBuilder: widget.textBuilder, - usernameBuilder: widget.usernameBuilder, - onLinkTap: widget.onLinkTap, showPinButton: currentUserMember != null && widget.pinPermissions.contains(currentUserMember.role), ); @@ -1004,7 +968,6 @@ class _MessageListViewState extends State { showFlagButton: !isMyMessage, borderSide: borderSide, onThreadTap: _onThreadTap, - onReplyTap: widget.onReplyTap, attachmentBorderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(attachmentBorderRadius), bottomLeft: isMyMessage @@ -1055,7 +1018,6 @@ class _MessageListViewState extends State { : _streamTheme.otherMessageTheme, readList: readList, allRead: allRead, - onShowMessage: widget.onShowMessage, onReturnAction: (action) { switch (action) { case ReturnActionType.none: @@ -1066,17 +1028,12 @@ class _MessageListViewState extends State { break; } }, - customAttachmentBuilders: widget.customAttachmentBuilders, onMessageTap: (message) { if (widget.onMessageTap != null) { widget.onMessageTap!(message); } FocusScope.of(context).unfocus(); }, - onAttachmentTap: widget.onAttachmentTap, - textBuilder: widget.textBuilder, - usernameBuilder: widget.usernameBuilder, - onLinkTap: widget.onLinkTap, showPinButton: widget.pinPermissions.contains(currentUserMember.role), ); diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 2a259e83..dc90cc91 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -40,7 +40,7 @@ import 'package:stream_chat_flutter_core/src/typedef.dart'; /// messageListBuilder: (context, list) { /// return MessagesPage(list); /// }, -/// errorWidgetBuilder: (context, err) { +/// errorBuilder: (context, err) { /// return Center( /// child: Text('Error'), /// ); @@ -67,7 +67,7 @@ class MessageListCore extends StatefulWidget { required this.loadingBuilder, required this.emptyBuilder, required this.messageListBuilder, - required this.errorWidgetBuilder, + required this.errorBuilder, this.parentMessage, this.messageListController, this.messageFilter, @@ -91,7 +91,7 @@ class MessageListCore extends StatefulWidget { /// /// This parameter can be used to display an error message to users in the /// event of a connection failure. - final ErrorBuilder errorWidgetBuilder; + final ErrorBuilder errorBuilder; /// If the current message belongs to a `thread`, this property represents the /// first message or the parent of the conversation. @@ -144,7 +144,7 @@ class MessageListCoreState extends State { growable: false, ), ), - errorBuilder: widget.errorWidgetBuilder, + errorBuilder: widget.errorBuilder, loadingBuilder: widget.loadingBuilder, builder: (context, data) { final messageList = data?.reversed.toList(growable: false) ?? []; From 8ceff3ab62332c681b28f8e40ecb45d2c1df9c68 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 1 Jul 2021 10:47:30 +0200 Subject: [PATCH 493/599] fix test and example --- .../example/lib/main.dart | 2 +- .../test/message_list_core_test.dart | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index e12d2177..2de50c1a 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -269,7 +269,7 @@ class _MessageScreenState extends State { }, ); }, - errorWidgetBuilder: (BuildContext context, error) { + errorBuilder: (BuildContext context, error) { print(error.toString()); return Center( child: SizedBox( diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index 98e83f9f..18e166be 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -72,7 +72,7 @@ void main() { messageListBuilder: (_, __) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), ); await tester.pumpWidget(messageListCore); @@ -91,7 +91,7 @@ void main() { messageListBuilder: (_, __) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), ); final mockChannel = MockChannel(); @@ -123,7 +123,7 @@ void main() { messageListBuilder: (_, __) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), messageListController: controller, ); @@ -159,7 +159,7 @@ void main() { messageListBuilder: (_, __) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), messageListController: controller, ); @@ -210,7 +210,7 @@ void main() { messageListBuilder: (_, __) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorWidgetBuilder: (BuildContext context, Object error) => Offstage( + errorBuilder: (BuildContext context, Object error) => Offstage( key: errorWidgetKey, ), ); @@ -252,7 +252,7 @@ void main() { messageListBuilder: (_, __) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(key: emptyWidgetKey), - errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), ); final mockChannel = MockChannel(); @@ -292,7 +292,7 @@ void main() { messageListBuilder: (_, __) => Offstage(key: listWidgetKey), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), ); final mockChannel = MockChannel(); @@ -346,7 +346,7 @@ void main() { ), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), ); final mockChannel = MockChannel(); @@ -393,7 +393,7 @@ void main() { ), loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), - errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => Offstage(), parentMessage: parentMessage, ); From 469e481b0d776140c4a2bdaeacc4dcfeca088a34 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 2 Jul 2021 14:09:40 +0530 Subject: [PATCH 494/599] feat: Added new docs --- .../docs/Flutter/assets/channel_preview.png | Bin 0 -> 897000 bytes .../stream_chat_flutter/channel_list_view.mdx | 52 +++++++++++++++--- .../stream_chat_flutter/introduction.mdx | 7 +++ 3 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 docusaurus/docs/Flutter/assets/channel_preview.png diff --git a/docusaurus/docs/Flutter/assets/channel_preview.png b/docusaurus/docs/Flutter/assets/channel_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..39f5629bb80aadc659e5744484543058d57236c1 GIT binary patch literal 897000 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 P!sQ51UFYO)o|#D{Ysmf@4`~<{6~tY0YQwLO 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

p^{| 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

>l%3ul}$uj>BHY>gZkB#Laj27H56neKL^m zt5-Rd-H+3E>H3IGocv0uw3AIHcFK z11VitCSh80v8Vx2?RIW!XOTs2ipXLh+p8QgJIzQJcZ~COSd*}d*`G;a$)8E#L}O<8 z(!pZ{jC^C1`=0p3P6{S~7P@bp^1=xJMKQCEx!M^3IU7Udk(2+y3T`Aav+or_cL#7r z*@QRjhc-FUKbA)cr6nf$tCc#8x`PUrFw~ppJY=&84@2#5`5i`LOLUK7 zI1<4gUPC0*xp3h|v533E)K~g#h4>{?!P5C|7@Z)9#1e?=`?5oRAzxK~qk;!^a=RJj zK|pqcfzHTA?AZjj+`N+f0c8OlGkS`y|7&F51t^cZszJnSC7$r`cK@1o*qVWj@pA(X zaFhZ$+i5JKv}4mGb)C~F4w@QCQJ@wjh(0wX#`}1iYqEfSc(wpIh320U0}#CZ0$Zy@ zjG&X77>NesK;OcbZfiGukYGt=>o@4gYf=JkJ~mjZ~sByqSeWk%Wq{G@2JOO7ohepH~&Hk9_u>H ze&zBRptx83dnNW}nsHINP=o6WQdApJG`IShkr0Tto5D6R*z%ef6(F2&bt<(Wgju7^plhQ}GuI z%)`%_AY7-rT|w+)0*kJ)Drb&_Q>P>gMoM~%a7&R)8QYtHwNl|8>;DxK>@9qV37*Aa zsplQspF~6jQ_{@k!3u5q_!mnl->?Z9&GeO)TDhH|?br%*pG%Bb;=A^qt^Cc8?WpqZ zAxD>kyn!1(2+N6OovcOJsZeF-%)1UtU4*AS@aK*Y=Ip<}EjAO=7u0qMXteNpc_e=>p#(yYnKLweP5`cc4T z_NfPg2^A3DP5=`uu!_gMekYpZ`GODc`Au%n%kJ)MaE$u%S?7PgeUF)*L&reAtnvHo zB7K6^FHJXkOWEUqDhVWst>|g(;R3x_>>H$fOpmj)&x0NXVVrP=j@UgXw%Ae+iZHUqrXuA&mTOebNSw?;?@Izx zyjSW~zHb{=Lkmrgo3w0$O+^iMdvo7{du$r$q$E9@Q8g?hz$labFi~;xvU4w!bb0tM zM6k$DL@?}(hs@!!qslO*s?CrL10G_bvBLyiw1w(nf^PKR3A*_INYJq`%Qd-op%cQ@ zxxrH}xZNbD9;%_!idx&_6F{~_Rt_lDbqAJd=iGk>2*LSs@7Tlo?;%o&6l(Y<{g&-Y zE+%upw4L)HPMb8)Z4R>Txq+5nG`;G-ca6}>Pq!ewQ{`&~$~w%r&D4kfG`{}zHLG!p z(u13~dMJ5h#g~sIM{=-2^wJ}B_IQIlH%njl(d|snA*^ce{el-}`lNa@Vj9U7+hTzLHUX3r6vr{YD(G!;E$u(=;cd`Hia9qO5!Ai=@Jk1B>bm zYuP`cz$z872ncAb726|HCsx+exju!7iOebBo-up+i*brIq2(qP20IYL%64HACK$j4 zMuj5%k9p*RP?_m;{RN(N?4*izQ(*KSi>ovGHxqcc{*xPl(Z@?kkS4@{xH|PUFd#{g zT%RC6Y5F)NlFA~S4kE=G;MSxhfaSeuqE_UOrq?v+3>xb|L)5zh-RE~Uzvr*ccJkO+ zyGHNi7Fhh>@?4d-F}&zMPFnZvvV3)xHU*&~swsM1+;VabJ-F8`jrIR!+yMthc;0HF z=bc#Z)iKhis4vSh{qPnA?GG|WTYg#>m#Ev8NiFjpb=tDl)3xcg``ufwbr*ZckL4Yd zd1bM|{vAL%bg2A2fRuE(QAN%R%l#yC_Jr&+Hbv-6CmAI0*6aj3LN@D2R>72X6)K2^ zcHpV~BSHs!1Q=9_=gJAmZqlyi@&}#GcAJy-(!Yqn1quuLnpRV=JNB3WaKZEjEpAT@CD;-N z+T9zfTb&zTVCI?(PJ!?eQ`t=S%~&GKkBHhic^986`T3=~_M3Nsd{*+|)$;N1EgSky zIoGHHL90rgE7?_8iE@$k1 z7zJYwwb6v%!;XLdP6#|zv6`lxG0sXlSb;~kXERr*ZyF5e-m~mY?d9{_M1Xh<`|eh& z2gQdxU%iSfMG@!hi)DX_$AH;soT)M@u4}@yTPaAm#Dw-jkDpYJch*BfQye?zbVsi{ zJdC~_s|2D4bAco-*@po#hJ^5`cGk3Y%VQ2Mt##aB=*dY3Fd18H%}yLZh%5 zb$W!zY8&=7fi&=-okEek;9*jXqBwLvGDs%21tE_Q>Q?b7PJQ-y`7L|wfLJ5%_7Uj< z)t58~)4C5_vyZ`vRS&~kIoX*m4#F^bbhkg-UCm?u*5=~+C06d;2i_6i@oLvIHFqcS zo{{IdFI~60i%ypVR`=iOm>X>JIo~EfdtJY`71#oAi9rXo|8vZQIt6ThTAg0f)ui~! z%M3lx%4rE13Exxz;~5%DyDiItp_GEkdS>wC*qEfxJL~`dIP2kAS#cM`D|=aW1la-k z0ke-}%PCkRI=YTQ)b)&w)fIl?fGxda+)?ur%bn@vbsiTuEZ#{C@wl!JYxQ<~wRGsHLkH;(60;b9>NT3Pf< z+lc15&&=gJX;eg7jZn@9w-RhJQa?iynRJ)4-{k5xsSa4yi+uY6LV?0T(4WvezQwBm_Tlla{C zoF9)x`Tom@?>zZbaE$r(uJ4so9)^E_n;aZdlzB)44!FzKiw87d->0RPE83GiSXJ|d zCp}gX4A}~bdEq+&v?Crk?Hv785&EN39X}}($j%UhuFy7oLYJ3NS?EAgWvgU4Hbr>26k=k=?d@zXJQ>5Pg5GV_Po1KlWXE}H z=5IeefDCgw=xTMJ>iRlT#M4~8t4>u(8FRtt0tpU?OTf_QA#8_c;k6)dw) zUZ=4wGQzMkg_aN5s$4vopOL8sjD>G^GGB{_yw1=S6EFnp+>N|IU8wpOvr45iJXSJC zG$YSFag1Xd9P{T$QhYHz==j3!*y5#nu1J^%E4Mw5BAA68ZHQ^B{&@8OLxS4AM&u)6#m^VMB>K8Y=xw#4c` z_)Qkb{Qkoi?|1v|*NsV&OU^2HY^Q7s(Gh7_>L|4m-Ic7gv2yQWtg3wv1ZLkuoRJdI zHMu@=Lc~DnbJmXI@F5kLA|N>~_UcD+!P3v-5kWLgr=|D0wCEyKPsK84b#mFG3>Lls zwF$rXzSebbj$_b)r!isWqVx^|(>icG?>=IPZtS=aGyhlt<0p`)J_elH#?99#p^@Af z9fmKw9`aC+&wNhpe0}F5w}jL=j0PV~a8nBTy1{BwH^x__9C-)@zMJf|0VfsY4c0QJ zU}&2%BpFDAOLoM8TS0VUt4H(;=s!*Hxri|^>rGSJVx+yz+`XR+=k_NFx~_~Go{hW1 z_^7P&ea-t`Fa9%mOY~EDs{T%0155AByMip-oq-?D^T$qbOvZb*;q!)7?U;H5SHY6Z zi?nt`q>Fpvf0tg~(HD`NeD*A>C;1DNsuSs|NxdxkTi_Hrd^vgnTg(8Mz(E%LfC(Hz z;ZqJ4L8%4oLh_b#_vE?F_L;9P9&h0-c~3Ab=orB(0)bgnqUS8wIytg-MMRp$*$G{N zK?bHFC4FD9SIdz|o`WV_wccsY_aVz7^JEfifj%aa&@q{h;ha>XDwrj$WYU0O@q8+< zBe8kcbec%R8tn?$Kysj7V}o%DFJBJbxd_fed$SIc5$A5Gnx*pdJcNhUqt_z-js(8+ z7bGzAN%lQT@dym5lTv0+A>NKnWU&z}KQOoHR&1R-$9t&JWUyfchiQXdJL_s3h$Xpg z4vF%cM8R*-U$jms1_C3~tXc`lS9fNzlgG3ESen-Pk7`<3iN2op8KzJGc^a$gUlIYD z;GsJ{He;CM%HD-SB^E1T@u4eTz@PL8RKWf7QNKVAJY?piUHK=<9~SApQc=yd8CvA0 zFVBVk(;xq^?n_K*S-$FB1!h6REG7|8Fjl_=AB{^-4KEj_q3T7)wLYUl&AK@NB5;;} zH197WuoY4gmJa7$F-;4A7{hBcX+_o$qR4?z*);+*IjH&DLk%NYwn~Nm^)K5Eu5RJx zidPC4IXI_*h2Uh8AH-u(84PNvPHYinTr?#IiQ+c{O;xD9PO_a6kQu?xd`9nVwA8Uj zw#a!GuQu~05ph&*4qj*sQS%T8ETSYN-ss!dN##Skhp!{kz5deoph*dlG#-AkU1r;v zme3401>wZt-ZgLb599^|}4k35aJ!G00Td)<5{ zkq>rbJ664#_8?rmxcNZ&RBBJTrPbS?G+?Pbi0J*C-!x!LKm)cwR(J6F$?N<?-L|l+qX{$m%Kj=UNU@vi zpgByOY8uZ*I8r$v7T`zJ#)gwvM_cs_8NSR zv=ivT)-?R+F!84Rp%)JQUA#(vYNpkyKH;)!>9FYX+4&2cZw5bSwR(b2Cok~)h2kN3 zuZr3kRPdD1lWGXkV=l3Z%j^N{jGU9?@ItvLqZn(*6d=Pbg@{AcJrNryUHQ5CbV?&+Cs?v zg8bg>blhE1kA92*cpX7Zfj#x0T&Jr-*R$TD{yu%A*$G$6$EJ5tld9@jW)us;ZV3_G zNC=3=nhq)jMAm*yf~8LQ5${oD++Z9gXoh-eoJl-2QV*AxyM4Pc+TFP$&D?pF-P=jR z*pxgd+E{|MaC$%tQ(cdtGLd%n2m*QmR^9`5^tn4EY{(*&wRZ=(rkm9<8!_$5%HTgbgP&`rpkIw&!7O>Ys`o^o((P}igOR2B6);WMvMVB zkY0-cwORDO6N#6MhPg8ubNb&IdzQJ3$$ngqQm;Ds#lxEA$Ka&b{Miq^uwLh^{)@63 zI}1|XqczKDb{dZJ#r@^5$H4_wgrVFIq9$jtwx_waDdJKeLUu+!b_Wqd3)RNKm*ax1 z#$WZBUw_t6qI5yWQ1i*-s~?$uGTx;h({iaMeF~Z^X4>_3IrvuQC%pMli(m3$-b@tR zo8wW{J<#-mJLdtx=6*YyXlL3+zfpW|a`x~7%X5h5f_j2lGzia`jD7R9*4_$K#d51t zbBl*w9W~tA(Lt*m-}Cf^(nK>|>F6!nM;>hf^fCNvEZu zk@W~d-y%+`?5m$059i!B52X}31OW#z{RROuXxfOqnIft@#Z6dm)Y^OTX{ky{dEH`2 zFkpyvw=qRsUAGnbHwT!Zh<(K149JR*T9AH5N95WRKTxSv*_g1~hMBXs!qx~}AxZx- z$P+UK>CQ=o*LKHj7tC~~AG!~)EA)p_yp{NCwwnv@AG((_k^MMnfq!ATYerR zH&0>2&=Zf(Qtz18(XQ4-M)~=aHa{M6{>A_o1bzY-V4-Cmb8fR}fC2vJ;r7+33gYLJ zd3FOK=G&5_>HM9D%agm?`|?B9E(=%?6ZiGA+eGLb^cOP=yg8bs_tG!Bt1T(7S2**P zCZ4hl{iNrum{ji0oTF@L`f}NHXZk`t&_K5OK>dbKCaq|4+-cJbr}$GXEw1RWqgy7O zUxV`h2lmqe(L+KxhFr{v+SUi2wS7w|$^z!%jeb-=?eTc_+rCAQ?2dIlj0ceyK*mh0 zXZ3)Wj)xKjil3e2g<=KZ&T!*ZBn6o~UD&BG#x|J~G>NB@w#A-VSn}2gM-Lc$o2ZV; zFwVV8o7BcBd2L7Y79p9k5#(w22-Sek8-Nh=*u%!Jw?>Em+>aq*xx@{He%s@tNPy4l z)=`9L>17vxSH1Rj8E$`<`(+Ae-6i4W+4YZhw>S~Gv%zMqo}jw0;hbl%K@{#x-q@9p z+l3Bbl$1FdS5Lc5ukcl3P47(W{khV1Wrqf}8v*4Xp5Iz{Ud=zl_|NYG7z%1r<_3a* zLjIJGSHu31YLe%E=hcRT?e#g@w!SI26$dDBpGjHBI(lW9T3(;uM57m+?0V&cV@e0A zr+bCG%kcK%i`=s?eP=M2`vKvZYxWr8Ck3H zC*0QZC*1Zn7Y#n0mg(N)v~N$)Eic?V_k|v!ptf}Tz=KFHnC_yVhRg|iKOV{m8Kj%)WzN>4Yi`5D)?aX2B!Jr%ys`)h z@(v405WKKsAxH>E%Zr>Iy=Y&e>J_?%{1a~5!FX?R9DOiO2w{eXi$8Cw9YpEu(4=H9;ICsQPW;| zS#8Lwm~=(us_alm66~>qDuu%u?UM(lt5e2_O=5D}M+P~j2aluHvV@V+Rd^nO;+{b~ ztr!O(8MZedoQCH98x+q;DXywhugy*)`Q}*k8?1Kl z0!K!gzzJC?ByS)@ef>zRh-60-4`h8nZ1%J~`(8JVxAfVZ=9#aSkddafe0M*Hii|0P z^(sm-lZY|oH+k2?TPX-;{3UZ=fzQTMd!c1$|1E0EvxV`62iJUO?fy`%_&L@k%6nRU zH!B}0qfQSUt%@i?w5(ZZzq-s>Cl!^%`Er!X`73+vY3l@j6S1<`?tgBG7{i`bA|lsz z_r2?%q+0}vZZW|U!!A#KE9A;}ZHMlKUeZ$wO)K%pWD*9WJ(I?>H1W?Z=P`UlrPe4BU2RdzYc*)v&KGAny-F^5SWS*kFy# z`?W4%=v2My5pJVdi#>7?w>|y&&xiK-rdnW+4{4T><3flbQavShH&ARaX6uT_OG731 zbj!NMzF$q}8A7UDsT&^eE*=fJBcAfZN(O>g!6Nm<yK+XZ2`*^YdJ%$15v8d{5D40~{F} z>?_o-?oQ^JclljWT_Ss{<2JI5&I{bSn_W4PJ@W1*DHNIo3&~%jfr!Kw4M#D-26OM( zX2Mccp1UFw&w3b1a{4qjnb{pDX}As_>XlFI_|9_0VScRvoNy75R^{H-5fQjl;*S{m5)q~rGCc>;$*psKa5|RP zHtse-wqB=wfd`M*hrmGPtW3hgQH6pp9`zWBhz zj;T7(1j3tUCraB+m%UBIfwqJZnJe4FFYctF4F4pZl;iq|Mj(2lFEJjdt zKAbDs#qjFVubVw_*8l#9CRg;X+r=i;TA44VW6?e*zk4O`JAQ%akQVjmmVqSvW7JN} zrRx(Ti>0TVcwvOlNGLsiqt4HDED^(df$z_XtG_9bmslnCCqp!Qj(HZ$1RN-|I7`F% zfLL=ee7>U2@8lfG2sj^0HKR@7CEXWXH`NysfNKX3HPy)#R!Tpar5KSil>cDeaNE_UKLQO6H)>c!{W& z!0418C}G3I95qPRXlm3^f7j^(fSQ#*+A7ni&7m&)c>mFnrIWV0-}OExlFZz7VZvi^ z&ZaxO|9rB?%glgpPf8tq;S~@kNpB;id?!BFvU+x%N-@L#w8}f}7)K6&&!Ni;v}9`2 z?!U6xK9%<2Dr=mN_nk`T0w6kHHKDZ|wTa)XB5V!YS8w7wv4Usr$u^O>O76Xhadb|B zT*%Z2seldE{C3N`>3oYZ_g4qFM+{d1IKSrrv-~&+q{jJmOng$>Br8IMpd~uj``~R}G-}LZNnH7kX;5jNnkknACAEwflJ#8S6Sd@S6WH*eP)Ufs zfQjiz+tbp-veUryy^@0C)jQ`TBlXl~ql0X;Pfp5b&pLfoGgQXND_r%KCGkwwjcKg# zLMoXMc7Fg(%eq$|+OFZ{Z{&G1(PQbe|4Fb37q==J9Su}{d0bn>UN$!FqkIWfRPZ0- zF+)%9aZ@7cuhes^Vn%XrYWp>iM9m=I^5HF&u6;aDE*Mbn7o3* z13J4p9zWKy&zB(C_K&kt%PEeF?o1oJybvFlCz9d5C(c0 zy70$d$Am?vusO35p9s>Tkr*JoTFYW=VcI$6TkOwVrhuV3wpZ~$=HUr40Tq1NIR-Ia z9rG3nO(FET$ME8Ch>pGiu*z55&iHdxG+Ai8Uaa!*JQ0QhJ=sFJfTPjfs#IgY-RS(7}P#+kBwK}_N?a# zi{RIMvn+6@rsLmsd5KsCuXO!ebtJX1%du2|I{1ScfpmPvcl=?`{lcp+;FI2&1IR(= zu1`0uk9n_;3oagMl&->}U*5##qQ4DLXIp-MnxPL0?EpO+R7fR;? zDOIZHFjdED+why*%&*xr$W%}!jgF?_XlEVv@`)TN&eTYG*8SwScDJaCWI(PR725&J zr_0`xewwyD>$FohD@KA`4{`G_yW7VDY--1CQ*m%M$L6q*i`Ev?{>q}0fR|@dra+&? z)L~oplc3jX^)VMa8rsXH8La>=@23Ex8H6iMe?4sK+R?Y^v6i6!PSz?NNVi8LXKfZ# zu_Yt0GbE1MTJOdHVZJ$?8>U$^cW6rNK{Xk|SQoa{*a)T5BJ>j$#)eApcgZHw!kp*4 zSND2f`HNMxO0)BI?44xVeSQX;dyWnRWjKBNdtAiGh}B#t)aYpw=sAle&4h}Iy)DN5QM^ZMJ|!JOvbRnSHw$pu@?4eG z4oWGw>5@ijsR)LNEkIpX*aXlN*DA@Aw5nWXiaxdOL=>#aqw+}Oc1^w-B^N*E2u#RO zEk2m|Bm*V{&SR1Z+k&kbCPgmntH)(jqh4MueZ3crWL){d{qHs8Qb7C~?D|fEo~({{ zLBxfnfSbX$kc@fWw>-0R7Z=j{-E|_iHkSCCgjbO;omyCU#w9vUk+KqFIt_0=5c1bBL)}^H z^n(nKxm~l4y$grC`ga-1eEq7+Exe;k&c4-3rPpxF>A_wrmUJ2_x)_6cEmn})L{Wz9fWS%4mrOfAKe9}u_30$W2c3g zX}i9Yx{O}gmz{3qG1@%)g>K6yuBsxl;qJJ}*KPK}k>@(>;pr2M!Aoh?HO}UZJWGLN zW8G9-85VB?e>HX7em{}v_3RRhC1{nToBeyM1QhHUPJ7|`$1j%+Pl)*l?d+gmD(EL4 zp0Kb+?$2y3n2dlsKyVrCEytP5OC2P0o|BLQzU)mADBFP+A^=k!?K&(@DiEBlE9cCp ze~JOPDuK@o58L`ewBh^RyQYRkvMz#YqV9FFU4EoEDr!#Ss!(I*tS~^->oIYLQok{& z_zu6wYCScBh3XV|^6j5w9myNKe_{}bbi;}-Zx3^VXf0s+%nqbSw=pHsAAEZMFi}EI zf#93){#I)YigPgjmTO-zw!3YQtBEh}RRnTnD0rncoD`}26?VLG6#n&!mJ=0=*;IkF zVX;3yoqrS!2O$>XyxeGCdI1gxUb^Y`?mKz_nV09Sv7K=SN}-G2$Ard9RhLWlo~khB z)^hVupzN+A$8*rmB8z@c_zQGJMW3H5$;jt7q~Mli*s!R6y|4m^fQ+8-{#3o9+~xhj zT*!@rHPYU4_;=kuf%yRgSjHOzxBuas{`yn=r>k3Kj3NgU_!au7A9qR`%K`j89J^zk zKS(5X)N}0jGaz0@SR**PLFIWW?GwD8<9ynq+#l7p=|JCwAFJx8Q*^EdzlsuhzT9PG z(fH%nlN7p#`EXv*4s5W=vz`W<8~f*#LX@_Ko~UGGT^FQY6yF4_ydKzzdO8(CsJD91 z4f65QCc-wyFo=tctzN9KcWLzWC?aAW=8=e-n(DdW&t#qAV1fTgx}FMk54NI#S|Mm^ zWT4@spo3ym^$91ZKid}UpDDJ%!=SlE4+)_tU;hS2R!dTM!lwghp#E<<)Rt3wdRsXx z^zQmbvC~&#<@t_Nqnu*zH{*g8=3q$DiXJb+&53o45YCW5sWZOAP&oG4OiR92t*WJB zwv2+ye{!Sp1DMGzp5+NyJFaU_fhid(D}Nd`2mbx|(u?v7psDyA#;6!;Q6+!>-4jgi zluZfjBsR+aG&TwVXX*b(&rrFBccHCSv~|s0Rkb?Z06Xvz(zR8qGV=>ry5n`HFo%^R zCwb$R%F&bkPx~!#uaB_h65OgTjYGL*$7MXaz^KKd`{ULVNH+D^kq64?03>0z3?&J+ z4^Z-x4L!OrG+kH$B&i_%qCS)GCWd0>ky^l@MDd6yy=Dj*Q+*#Uaz#GdQ~&S z#DbTmRYn3B4;+89lI^ep9WIchMnLq|w&1U5QB3R z&2TwJ$eq$|k3Cfi5uH#?4@6i{3c~<~8ZKO_r;NC5^{wrT!1rd+@}b^KWGaV{kte{WvS@};JF0f# z`6s*&MkFbrb_C8_U|R%U4HdUhx1FU~%T*Yd$R^zYuja6(on$5^OpzaxkPZi(UHoH;6p3CToSdM> z)*zOEPzwPOt90dR~?i{ zoDmD6W(XweD2eR!sK9^t!ppiTOikume=v|&%82Xa#-#OdGASdEKr;lZF+c<{eJkv2 zbO;wK4d}y}r3e6pvn=W?q!6{kqVuNQ|qEr-|V zRa%1&Y{x@-0qc2_bA`Pvy;QG+3ohT{#*yV-wYVQai;(#L#^#?A_!xv3D&(1cp5x&% z-@d|uG@JDX;l6)S^U>H3j=GitQyFmI<*&|!tX5w7&bA#k zN+vHIPmy_U=T6T=FXb7>#JSls3Gq^3kMDask&s>v5m+DAXsB=<72i>|fzj{Yyl`vf zW_eHB8OYh$)MOA7(xJz%Lt@n=BeOBvdR}=(FJhjER=)#4^CB=I8T&Ry?Ts+nUy?0p zsOQ`%s9^U8aRSPM1^zynWH#xl1)5pl(Bi7ee>*=vL=sCB*}e3+H_?B&u~Q9*HR^fN zj|(!pBE^H%$a*7^aDId7m9%cHF)jO&?(ej$Q_wOUvBmy>Ew2CbdwlfOEg1#360yn& zyb_11O(6(k`5w#1SFVozalBErsk~bZM`ai2UPhN5`=e{Bnsb3x^Pe>z6}_v{i)d#~#{z7AkB9xKeGha6Wr$1&(nEPA63~x# zz>tJ5^xmzvA+5mcIEG+jigMx0ue#BVVFv#F_c?}A{O=miO*Egxu*kF&NS+f>&HSan zWj!4pvG>G^o>{*!j_CR@m^Bu@rd9z<+#F*ZZwiF&6K}Rml}26BlO{*S$*u(kzHg2T{~5!=OOZF> z@qYSMc5*ko|LX$S&CNZ3SJI>~W1rO|6U!sL<&*9JHh5$vuhGs>y%-DM4ihuXE8)9K zLq|5e-^?0=hNE%+yrcbp3vbPf$>`8Q_I zjF(DYFU|WOYXA0M)PBr^!+7{|>RJ1{$SBgVx$~65X)Ornli-kt{MJ)&A84Hg>Fo5x zqYntbA2js@%ij_RPq_WCOjB+x&Z!yiu3DJAl(MknVGE%!mp9u)ZsTe%NT~t_E}e93 z9W596g^;&niLBSZ@tkt|j{B>p+`jVl9n_R20TYMu;Kb7hb}d;==1dkuro4<5A4USo ze9%@Yb{LZiwj{_}aFYB^$5 z49g=!4gjQh&P%C9n4Y|A$6U?r!TRV;nvm9fW_7dh3C zCKvVJNe%sWe|XYlu?MGEyXlu%~_iQUiyvK-&z8RVXu5OE#U}~&@hBqG9f7x z`0G3}b0Ab=JuJ2{q_$jV`Ov{3qI`u$%UjM^S{<3=U*|%g_<{n2DcG_uZ(Ey!I(-Gb z9kRafeGI`LNSEva>$#5E8j;vyge$Mp9YuGh6}~(%13Epy5};*V{i1!DFgEA1d3cVK z*}zbDMSs818>P)3K9ou4W2+A-gQN9zgJ1-Izbcss%%wYy){orne6q+Y247hAK@ClG zyhV(jH^f^AkGQD!8i2n&m2);@QZu2T1%r&%1Jlilmiv>0Vz}dz41yGK zk&8hlT{WV_ihTR9t)aTW1WD?|aufqxCdI&xD4xBl)!QJ1PJ1?>SeUbzW;T_GFs5Oz zL!imswqpiK!U0Dr92zEaini`N5T)gDT$37$d%2DU=`1OVU0=;_w7sc|GxqqTl9C`HWI!j48!K-+f3M{x9tQ&VSf_)Bj@k2R2XH{c3Z2OxQ0bxPCyNk!=r6ffT#-)+xI$ z0h6l9+~o@>*-~b^MrpH!duS+T#YBo(>~DO6`QhH#bqVt&s;Y4CAZKAC9t7G?@J-q> ziHFlS|M5gbyEif@2q&j)Bolff$v4wuOD@PntsMCdwn+NMd$~hIAnaR1%mZ8`4+Q-t zf>JhTFxxIpTR8rWq&ed1^>=`x?vOA;5!fc^VR~j-oG!f6AhVGMTN5KtuxS2{F8ew1 z?u~v0Wr@b)o<+NUY0dHC@6%(TBAxfI8Cp^<{nSC)~^sy(?cs+a|Q2Sp3-8zu*2RUso~{I{te!?#5+zQaY+)wVxGIaccC7Fi%|??}MM&M$bpH z0r`g#sm%oIeKAHjV-tP%sJpyQ2(oaddGYoqm_h-vn4q3^S;AXwe~bh79y=dk_NOd= zTqmTi&cobFgNR|@G{TWI)F#7uSIV^6cI0id+Oa$b^KHlZln$T7g3c1}u)y)Q&c3Uo_(J zb&YA@$_zEQBC5{b#YaV%M$qVH+b|EEBq*iv=s?ZMble1X0$z*zWk09?L+$HLJLO_l z1xGYc1~I?XDQvW6r%@JKGlDLPaihuzBd!yh{YBb>?gaw@*z8b33hHXs-r_@wnVyet zOfGV9as2K2V#@VJovhb2M~rj=Z}ZNz%K-1`1;*3qIAq^Yr0{LzE6gCW#n>twCv+|Q zxm2t;A%gVTrC7dU8M&S(7ur)savdytkhEf8PKgv?`_NBVUOOY}MsX@)Dz>{*&yM^) zi*kocpMd*`v6Rr?O8e^H-2N02S_qE4&8^gE+ANl-<-$A7@quC0IMM`SxNcCbHt=>s z$67bx6vSq4#1OF7c(yoy2KzQMmP@)!r`-P9pWObdaFB3uiKk+ZgFdU=u}PgVPKoM9 zwJ_x-<_olP*7ZU6q-(rB&Fy`&&^A9fe<`m}>$wVyhf*vIpw!boyXXkqD+1ClH>nG# z-Exq#`ze899TO2eZ3jW$rfMeNBmoNG|sWKA(LM*?2yUOcMV!a)S( z?-vP=%Jmbl(gh#njmT!zR(dgXS)(u^HGVyHjZjVH@nj=2|0U0CQc%YZVA-k==bZgakue@2*&`d= zmjpWCi8L#Rg8_aPLr7zvR~BcmQAnRF6_x%=zUg!_^72*+Q#JP8QcMP?B;P;TeO>ZX zcK?eUazz`U`wZZBak$%U!P-Xto{3T_clW2lF3jDARYlDQ2IIWb!jSH;ORNZ<4dd=F z@{HEreAJYBOdE4OQ&5s2pw4Jw(s6+udGj-``mi8`72&Nf1)%zn9lK^c^45RO5OO5bR)o z)Zp`h)7(Y}mHnPg#8xdYSq-S|=X61?G!<2rQ19EvW1og;9P5li2q9<2BGes?Qy&tt zT6?pZcQE<%8OzeM5A6HW%9d!IsH1^C!=%X zYrHO0-y_l4F3dM6fZ5(j)tO!KI$!BZ$Mh$=Kl|tkf}H1;rX!fE^IqCscw@1A%I<6P zpQ!EqplQu;F#I#*=SFomhI(c~U}{%%-6k~9?GSZAA2*LwupN32XA|C>RayQoc3%gu z`%=BY)&~UzdX1BBw_VFrJS*#?+T^_%6F1JUs6s4uE~+bsE^2n%YV`P9f$rx z!&m$fX~l7RpQP2eB$t|P)@5KB?LFe~i!y(^?By<&YLeN|)!;4doWZ{o$ezuW;da|X z{xGNX@!JmXfDatzFdYq+XU|LWcl?0b-gMyF@?w&YP3mzB@u}NhuLW71wwOu!7rcMx zZ*Kd-#T2-&!fb0}VusRkAm0v=ITz0L!~1-`S!6ISSfnJqC2}s8XS7-ofPzF?y^t5n zGfkq-$%8EpTUcyTG&uq7SJ=LHsgo|s3uv6;3ZPD#jieK*t(VPndaWa887QyQhsi}d z(%0Z`rHSOr8hUN}Hs2@#%0kCxnq>hU*d+Na^7bZ|-Bs5g^_0Ahk{&PvKgc0P(bZk|-9y3k^#4KpI|FL^x?S8Kh5S8gpiLa2^Sn=v| z8A3%ROdE>t=G+W?UOWUd=t?Adl#wqsP@;Zhld+iOOw^mcZ4^!z19m@UgO6h4Ou*>S zEl`-gKgFgj5oC)YUs+C z_O(VO@$e|}F3rMl$71?NL2?$?W6sZbVt$~W1z)-*;%L^wp0JL^G#cs$d1%H`2X`M$ z*|2B`u=Bm6)X%|YiA5LAxS6+Z+o@KOkXp#4JHw1;+tl#3Je?sy$)z-;H0ur%3z>#QBv{|l^tD%)8q>}$c@Yi<#ho&sggvg-oo$%<2P8Z4?ihQ)Q z2F!qO1b;H*4r08Q%So~zY(>6s<+?ncUe?RV^oh=C>b=^R^0Hf_^^W!?yi;Hj!=BcB?e*i8_Mk znvQ?w;GHN(-6Cf5mZbAvX=|LJhs_SxKYUDUIH0d@V+f0Bk!c=UoKGL9leYKVIO~6v z(WTrGvpkzRDIk70Pe4XJgy;4fdZ8lwM*%vtkgW;>^*!YWDZdzy7N- z{mE~H!jo=$#}i39IV6i+TSiZhE{>T5<{su7UkRg->*u->l>K98C8wX;<%9D1t8eJj zWr9u8M4sd0vs%LXM0`O@DIt1qyMG+m0Xl$bWKPB5UGDqWJUqm~4?SfJ>?`0+q6OM^)U006t7fLDh3lDBM<$y}aJRz}FV66c$^JCU#sMM8vd(hOZ@Im~2e0hRD__2{AawHlhQX1~{5k#)-4R`U_=_&1 zCYp+^A5Dn>3$k6Nm3G$oo7`UFx7_}RpEw-g`z_r3({O%A#6wb%;F$Gkm|LTV43pg- z>eI9NgD3R9#c~9WYn6@(5;N3Hh-ef`Bv`N4LHG>|b2ZO0Iflj33ChODFokq8WtrpJ zu}U7~4uvwDvbtwj{fXFZ1YXyUavRrvPe0dVLG!f17i&|vFkM*ds0oalka`OJx+s7H~wIh}^ zlf0niL3+{(T%67S{OHF}o(nx6kA>Wk`O{VT6ZjXgEV-b?)>^}!9a6sfa)8a|=m~Gg zk#^sm9n`R?^i@?mt^Iw&d+!ZKi-JqzQD3{C zGNlP&1os8|g-;;qMEl1Ir-SSj#f3od=q@SQx3H{KsKxaddk|--!{k4d1iC9*qvi^BQ$x};^-uu4P zBVT`oj9V=BM*1V^vXD35uS)HnV{E5>cW2;%YNOe#fx^G+i6kx#0Kfh2t_#^epe98ClXC?O z|8Y5)UeP$HaB0%cKPERw^zj|Jn&=~)B7Y*d*Zxy*uW&hfdAma2h4z6Om#dl4+;W&4 zYs_8-dYoNkKSc4=aNkyV6Azr=vqQ43G-A{K_gU^isQ+>Sh+#w;-IsTT3*$CK@NV@N zipfJ~>FZ+x8RWu)x~Jqs)syP9Gw|Y}gg8${_k!WxHp|70LH-ww4G=%qVr?Jds`X%h zc97LEN7MEmSiuz83lU99&51IdWYljDaVaQAtjAtSzn?%ZnaxK!#lk(HP@{_QHvVtj zz0x=!`VGRE9_yJlcW{xP=um3TVimvKk8l1_OIBV?@sqMxqu#TB5tv8g#Wg`baV}-NIoiJP>g)0NOkpXnp9ni zG@*`pKw|6zN5>|Us-a*DOVri<_=t2+ba?^)My{d{^MX)1c4f|le!z9b-Rn|E%vD~X&n;RivwM9luBSCMZVsSkPcAR2?jnAH)|xxT8q2FgHq&nw4Q(AKV>z5Q0n zK0iNkIigsWv555(rPd-_7;kwAFAsXSaTOTv0j-lo>HhZNTekhH|m~x>wSmey8hUoXVKTG&{+(u5o8gnD( zSOD<}Y|X4JPKHF$&i(dO+t&`aU#I&dyB45b*4q zw74}XDn{*0B_qAu5Y3{LTdfQvw&Ee~u=2*c7q^Wz#7TjXK@TGZ#@#sL9oXA$2*Dj{%|?0UcTWyV!2KVsT~$+e;qzMJ=kh-*n3tPywZhd zTYJ(5?=L<{i!+hFI<>%SuF~~&%*T2#3)`WcZ;^A`^a2{>GhTWx)vekU%KYsx`qxHK z8_-Z~Y^i=pRT!EBJpbCCJUP|$Ul#|iC4{5$+%s;yC8dS_BaSNw_Ns)+|^#? zDzHw(_JMDyUm)vEMhTZ8ICNL9d>A;4G96yjakZYhBPi*;T`-Agh1w7WH3ca(^j> zcc|9S1|D~`+u8(~)Zp_MkvHcjJ&|A@Vn^|Pd2|dFN3rUE;rzXuOrx$#2GgUUat79Q zd&J(rN8#9yK8i&!^gEvO9aBf~xv|9T&PSg!xkhioODBkE(RTT72~QLaj=&!Zhvm$_2N+jZF${v_$|4x zR!tAdsLe?kaW+1$qOkMxUVR7t6G2B8(py4q@i2P`V5qsU{z@(Jnh4)!C91owh*_9? zb9rhQ!~7%^eU@wOD7p6y5pAc8S=t*qH{HNx6sSd=6pCAW0MEYwHimn{Z3CXnEa<$f3bGn&PSuoHB1&_Po7QXHmJ1|GdV0tZkn~Je`e6! zvy7Y%*>mJEZr>uF{^v_P#Go!9`e5b&ugYXMIa%M-1lg{&RPJvreq))V%~-N9I1Bu~ zBC3tWR}8C<1yr_58KkUks;ahYf>#dk-Um`!7{zV7O_q*YRBmrA^yc6H>>NSB@F>J= zGLWP%&G_EK@#t_uF+Q$BZwq?DNjL9K;eYYoSD$$AcU=A?N z`Q{`64kZqbfnJ|D z)hmgJ3~GcUaW+f6z}1pQ&?-YpJ83=w2NB_j(6+FaKz1&FX5yr~ynYB%D!r+Yjo4vxufRn%wfqLO8)i+jD;unffQ z*7(bY|I(y_5eOxCmY|)xW_H~#NV5Ai@!qRXTs&Bm#nUq9-v z>=iyg>b;ROH0gcO9cYF>Pg^L1lEd=KwpR(LY#uclREwjtE%XGpb1KNu!X8K;!f%0} z)s?MKPyhH+eg7NfkGI408Y^rKofH&WRCTY=B11T9)~BUt1E3mji!m|11U;A0w_<_% zo&?d|0-;jd!mR=N_;dHl*|TdmwwUhUsk?Al1oH6Pexr%e*GahyvWK011HBD_ArI&8 z-l#B>#oaDa7S&t+HN8cQTyKQ@m-#*O} z;GFTdMlmPRnq1XJ8bI=&oLO6W0m)wuM(J-i<$roKruE4qCD9>6wcGKhk@>C_7lWgE zS+o!S5g_?dfaGU7A^ClZ{)gmGf6x^E%abL6@j0KE^`KE>ZXwsd6_Ud|2{m8-YEzf! zZrbKV;4MoeSsQ?jvjQz~vrv(0va%UvC0!xJwkV%6-{p=AVSm7H zLs*?>ZRpzydX|1H{Tk9EV1<%ApsL2P7o)KrflxO#)#X_!^5F~)e2VXCvZaDTOwr7l zFy9tv0`3&#$G;`?6Z>i)e;BjtT!5TQhKm9*xN2}Tyb>~Q-Hr}sPVHqV|M`|b^O>Z| zKb-ep2~V8&Lcn6px3sdS@<1|P{B81{S{T#xoX$6fULQ(it0NC{%&nP!zKK@b>ImqP zZ>YKUi0Mq=54aVKt&>$xR+kIv>?PB0)M}MW-oNd9c4UL~?$tnL;O-TEJ~p;88GrVC zfX@8h-mj$`%db<;r;M`(AD!Z~;P$5{ikZW=&t|csB`|U!bJA}Mdypc6xRf{o=i3!H z6KNoNs_*wT6RPeeG8Y#WkBo;oJEk-cY+0-IVAhofdNyY!TE(hqs(*w0e_8Kc=#Gy_ z-i=)T56Exu)EQXseScf;3*Oi!&lJ&OR-`*y>4tw^p44AB1^L}#-wz<4Ag%6p`4hIwj`JSId5XQ-avc{(Iq;OAVe*6f z-|q8pPeAdz8tzK1?;RfJe~WH^_v<&s&m14Qntd)O6B&7&3AFbvb7hRieSKzkZ>n;T zXY?QQLfirN1MoUNe*tY4-GJD^7qt~{?GmMEW278^Bm6(1CkVf+*{69v^Vk;ElwhdF z+&a!@(^84aUW)xGN*O_^C*^uMGm7*0fO7|T?ohf#Aeve)Ey~w7=>ZrD&9ZS3sfrII zBL#`f*kC#}T&cvlVLo5#5S(&&!F6O~-NDD`wE1&4D;UW|UAAaE$CZ#RI{Mtmq}Eiz zd1$hCqD`wbqm<~*WLUkP*x^v?&_NrakT4&Q#+h=Oxi%s-=czaJ1W3*GA->u#z*cMm z-kip)d6mF>Yl?vTE;xpEC z2lm~l;AjKa(n>0Isph^Jg`!`>3QX_C6os;`MOK|OO$z^yrU}^hrwSxZ;0G6xd-_Fp z4YH{-eyA{~3>fjkpUdPGM=v1**|IYft9gyzZSQCH3%S`@(8l@?g2YWzP_zSsLP$o} zWLx(Xl1?7-Gx5}VRQJ6<^Xl@vdXOPS((Nx?p?9xPl`pV3qj#w#BrK5(8r`txk%^Ve zNY~GR(}~z$mzwP+2t!_VM$}=k@urBFw6rG1SXe16E>I=+Vs!`O_(2mv0YAEQ%96&? zE61in_D4KoTCT9{k?bsGL?aJHN18G+UVzdD?LQ7zq$W+!WaAQyyM2t+6S4>L{Mm*X z@>M@~;)re{t&Q&C=|EP&YBa7h+Oh3yq2qrreq#WV>nAAtnsueiKJ% zSnL4tJ$oEG@0)M?LQ;C1&zY?DS3%BZTF5OsQb7Q)AD-i5x8L2ZXklGIbywV{infeD3%LX6(D@#V5g}HV8ec8|yUQeEZ9e%!c5T@AGd? z?DTQI#;TaujkA7O(`_I$9B968+wq&{cT;iS-I<)`3V%)1$XIy>dTZ}eG}Cb+q`DEj zC(e(Sjl(Z*p4UKlm( ziO&mF@rX&Xt9lJF$ScDdzEYBxBd%eGg3ULFg0<&t#Sa@`C{u_FAkU%%gyZl&a8W_C zx;B5`4wVy{U+S}{wH;_CQgMPXfYck;%)khv@pFZkw`nG0VIqN*L|xzsfG%Ah=AIbC z*o;syr}v&3@_CFIXHbpzZ+ZLoA8Cz{_2>S$!_qd5?P>w5?vH<6b;CW2z`UuwJ`}y{ zt{UlVgPzWlgJ=sMDFsJC%~uGe^q*_IQkD3{nPKwX>^a_%I~STn%>&q+Ahh$6yA6!d z5(IHE29w!kRgfibZ}#Ql9M9xM)pNgSPcPRafqUhw{a7ZWOuz-Pav|PB1Z>4g-di@> zd~;lUw!hUqiA^ey4YpLUU-&j0u+qUzVM@ua4w$9m;eZ58Athcnki@OCpqp1@)0_bG zZ5#6+p0RNZXL#cJ&ZHv#f1&&t_nk#$9oG?|K`A#!4y7lCW(pM9AT)^&rd);pxAT5m z7#JD;fVE!j*jU~|?Wo=U)ZmMU&_9AP68`^$9W2I%d#Glf82CMKTe8~UJ zqUG}HzPC-R!Dm`5$iF8b+!?H4KgiI%TOwVq`8kMrs<$ND-SU95EPx&icHHEoVvF1E z`ri7zVM+?HQRWY9%zz1>%tj?<(68r~e{zQRhypeumQ|6AD+JBxYt-ZHrl_G6L#C{y zz(-8VQl#LFkO`DzJN|BqMa3WfR(kD(?hUrT7Q<_!=KJ~2l^@;ygJ14__fQ<8oqR{MC~vaV znZ&+?hm2;qyNt|je?V^Y+Z^g3lwe!GOUnnG1j8_cZC5vIJ~V$;j<{-kUM+rb#gX>W zRNXN_pliwZbnGomU0qpkmo~AtZpPHRXr%NQqG;mk8{otOGrkEp=HjVYOpiY+tb8a?+e}?Pij;m7cZOu>5zSN_iUW2~G>FdQBggHQqys$dHl`#~;8YVeX_ z&*0Zy{h76l?;sI4ESZy*@)o#oFS#D?9?!Vrd;|Hb7Zd}D*sI=RosxE)0)0idQlNoN zctcyuDr|F8j`ntT>BgodexQ*&SToQV>wmx{~dK8N5_80zMuI-2fa z+D+UG>gE?_8{T|LZ+ml$b6^MkLPq+qE{aO5WsrI4yEG$Aq@&?L zCgG{emG&R}=MGGD4n~@6k`8`KiwsoY>Y<1AP4jN6^Q$ja-HSFD-D4#;?1z@59L^{u z2$nc5$m>)%?zQWTAB!ALvmZ}45oxc#13b%iy8C~gJDv z3)cq~iOmr=C+$7U3ge*&njYir`Db_qv1j?hF9Y#kSfJ`GMr@yMa`J<$*pizK(1F^R z{Cm&shL_F#(>!?;C%>oju(S%5hA&Y=s}Zpf=1R=&j%h`%@37su9?xDIkH$EsS?pxi zn+9ZlH!A-3j6#!gi)UGzVU*??`R7F9u}jy9gstAooDM0JN}e10b4cNO_@3(DhZID> zkb(k&3ZhtkJPGHs%6J_{1Qk7~iza;`dNpOYuplAqs*^{gqnEEzbC^wlri@}lW26BQ zwMgRERPx#s>LhiY4E7wE@4w(7G$8AB9M{56Q7+z)Ud3IZKK%5(LU@+k*G3U3fp!)4 zUn6%6`ad!!f;}4$4sCb;{PMgTp=iXS4K1AcqQYxv_P7Hw>RH~Wy-F_8H< z&cdMOFu6@wL!i##bW`mfKR)BAa`S78hy^wkQf*|_#t^kEE8MW~`ys^>Q?>>~Zyp`F z_div8^Lp4XMowicSv@A8;Ca0OTrWR5o3%q-qOV^Jqhb|4@~dO|yRY`}PK1El;gC?k zUJdYdMpC)gkYw}^@lca!(?x2(w?f(oOTsxrb54z3Erx9{G zd5}yr5z#c-7px=fL7EyD%exo*mE{17R4N^m3amwkO|+s2^hH>di6C#Hx4nz)lN}~fRR(6zUVQbmZp_x|aj=+wG?HH;6U6_$ zUt4Phi_Sm%p09a`a^Fz4|78-;<5%uWs-&eHx2^t5pCM>T;4db8^5@ADmPaodxrRq; z-@Lqt%z6(ZBHw-ikp*F=kp*QJ1xubAtaxXUWM7tif#=8tLl2qt<3(RLzAI~LK6YvR z`F8o`NxI_JqF{u0NMBiV_e;Kc>CKcD(~7=3q(MEL8LzL$EpL^!d0@^XX>Gr{e;MK; zrPGi$>F!E{j^Vn*5L3NY9`PDb@``dQUXT^+4RN)xmoRH|$Al5r?m~yxxs)i%rYzr$ zUVLF#J~?`s$zP65VvGLDco+Xdfduv@(SKB>v^#}TQ45Y;v&)E!;_JZCKi;|tc-4Cl z0&4IW3~5oWKWHaC>Lz7%n=^DXz#1p*7ze_|7M}#Z7%<2ubs1`)z%S|?`--Z_acS6O(gU5n4}6#6UZ;FDNqu|a#^Gl4P3V)=ZNL{*Du z^FA?fB-AY=Y1LAr9vp?uz0C7t{-Fqt@`MvQX)-~AH$Js^JV_WD%uH#Maeq%o?O32< z)W8iP5#YMVlOsQgn387W*^ECI(|U7cD=5VmGKP7h#3verC1uj1bDyg_2{{OJYR26j zox$05PgaxKStjWS7ZyChY|u=No_qI`8RN?IT35nh%inwVvP}vP34PxsYi#TG7MdQ_ z<(&!9+469DYt2T(!KlR7`}j3dNx!XL3W>kVTV6x zyV_4ST7}I1G`bulIHf~qk~bp`+SYn`N6k13t((HZdI!l z+I@q%6`zi{$`_7m&}&GUpo?y-%sPh)Nyo;cYfW+7;a)E4za-Z}gjS7MWX*q6I=OxJ zpNors$q7qxbz9fWN8)ivOMzHEpDKsFPz{0OBC&&_((Aqn`(@Rx7grO!@9>^7@L!H7 zF?e%9P}0g2)Duj~VjC(N0wHWWTC>~z(!(n0VKP_duF~n4xO{t7FQNY~Zz*Zu9W&)N z@;U!iYRnii2-1jWmEeAZT^0b}3?GUC3~IK%wi z7rnm(fJLOnylz3sgI+G^Nvd;IhAHHNSlg1*I=#?*m_D`5**S(*oVbvDi#N4o;HK( zn~FsuF>!3sm-LFe6(0nythTuk{CvkeDaq*W&<~mUbE2b?m0&ek48JK>+b#PzV)V-{ zAPy{$^hX1hM+4KAM|rg+I8P|f(`9>r79A$``9n~?`_U4_(y3iTUE(w51;*4FvBBiN zF)d#+D^G#k+iEGEAME--6pWi`L9)C3dg1ow&PC!IRwh?9ZP2`dnIbF3fwedYetJ5} zZ6WITXBlHcR`fXDlk~pZ_6u;QHuKk5SuN$2u{C}q+pHrL{gRNwPjpmLyE7=7VNkzk z*66pkzm#uj>CH7Ftfs`H%fm~z8QoGWti8SNJ1AzlS?i?5HGvIr05eE!aV_n~`#Sg~ zT89E$5`NRY)YS~v2I3Es@I|hd)nR|2Kf}fLdsb{_c}I*0e)ALd8W+`JbGhuer6-9^H4t!@E++Hz zUr6>TRa>hG?0VuJI%9lK`l*rv%j-UFau-T@w0V~}lRI?3+qb{Az~yfPFg%x^LNc?$ zjJ9QzNdE$afs+ZD?_=<;MQOp}=NI^&ly_jrtwgp%gf`rvZM=||S6j>ggUZLXd$ zT#cTL4>63odE9V0ZLh8;m`1~u8&=xg7DvE$9ZE&r$>7$D@e2{4#)WY*+LvCJI-E({ z-aw>;`742AJJbbt=+)Q|Nf7Gv<0se=)ZEaXQET0;ns0tS_r1w>Ge)`6$JUb?mNzSK zuPklg9A~~Si>tq-#6Z%%cFhC3jn1YOCedgAkFd84i|T#Xzd;04Vgy8*K`E7z?x6%k zK`c530qKyC97>dKlrBLr=8o77cyl1bK7;wCYH?eQ^lG^}YX22vf$MKC*V1pZr@OIuh0bYAR6+OdpQP|+a z!7tC=L%ml%MOw>m8AN`QJGmPfDOla=$|lB&-wHS{iA-)>=8jar1}oTj6m|`Al|Lu3 zSwCwJz=DiK&$G`f0zCT1^(qdLHO+6)i3%r)MZ%V!t_31Mm zLzc94vdpXCz2hMEcv;K8aSCVBLO6}gE{YB*vMhV2=7b8gqVzkdra|kz8PSpyFg+?y zuHvC&aqqBkU#?eJVj9qwTe+)!^4+cR)1K1Bk9VCx`7}(Bp}|QI zGxS9$&*RHunTqYI(TE@xf)%HwN24)S@Lw4p!3Pu9<{$D%^b#9*V87kRC2Al4sa)IcqUank$~$eAf^7wENlv$1==_b5?fW8 z&En8U(Rmwt+m$k$QQ|(g95*mrm0ygcMYSFUYE}r_8ql~c!Ygm*ci(WPvvP}W=`sR>6)J{1)RlBU%Se3 zWN7f2sg!#(8YggA{dEt&yJzavyYaK2*skDCq3N^6@q>*>U}M5-8!e!LO_bM>K0;E& z_I9DtXQ`H13n1(fX8py_G6qgEsts4@APaO;OM+>lx-&MtHeEJ`#y%TgBY}u8o8SEHe7S4JZtFTV?!GOt&$H@1$U1Jkd7KvS zD=iId2+lEhtVcrlGgZ&Yjc|%?~eDhv==WNWE0M+uYEfv8TUGg2cN_rCGqfih=t)1 z^H>%~=&hqzuxvB->p0=L{m-)VzA+A;MY?RM^YkWLhu*(6m>zd700`Xp3BKT*7JA#Q zH2q-rl<*U;pQ#TJ;V!Ek{CrlgMtuBrV%z8MS**&0{Ce4~DON>&e7V0h(tD-7nBP_0 z9EfqYg*PVX6TdyoA-k-@na24xJA{l0#>sBj|Iyfz!UWmVBv9`6UtuX+f`^>)M7$Ka z*(q4vrx89F^4d;LLz8}i4QoVP%IfDVxJt>{9j0*f3_M{WJQ7v{}J^e&Xdie2Xn@y(Fs&IhVGkR# zXH&p><4ow@T=3Q>q)`z44Z<04ejeeh_)H5+PkIN=dXMgC&3UY`uRqJ%DsCvI--90N zK%2b!-Ljgh1FmLY^Y5c6(8aeYV99|K|L_Mz3%jAt0vYTl^L1u&^TRZ&0%O63?Q%fX za@KUlYufD9{TMpTe09KY*Nq|8ciX~<e_=uDitQRn9=g`dI_|A@BPdRr?E z_RgA`s0#K_;GSc`kC!QF+(qy1R-6Lr)d27dJPL@PaT7UpXphosOAJt-1`S(}DX7{I zz9OV6P&UJ)TR`@V@e{yFid18>UySvoq@4sg`~9?~Hqr;xX)t-uiwc;2uQ{&})V|1{Rp5V8Be)*ufEqT56ie zZE)ns$qdYJIAh_~7U3doz6u1-+?ieHMmIs^4^TL+3T?0etUR0-k0xekg)VX8=~NsZ zK%<)EF<8$h*e1{!K%s*Pmc&f>6HYodiG{zCXKrZ2%4sICkwxZJR=vr(7kU+dcF&82 zm_XyEjS?fIX9M%;+zn-#cQC()_Z!wyMZQsO4|+`!JyH3P{qxu79h!=d>5hVHiE@)0 ztBh*T`fpMlWHbVe*snHbfAxDfNhID?O|A4f`@0!2p?@(nmx|we@U6>Vr%Af|H+_My zDQ-Af5dRx`!e>P0v=V%-R0#WV>Sgp+*_v*9z+4rcK8MNgxUhITT2EAmUSj5F528tK zvK1rt@n4o(wH#o8Nz~ZEZA6;RcbuV(g!4V;Sam zrX(C?3|51n#ZZseY3Gn zmB3)X&{2?6Yx0?k9GCFI-szt7e$u2j+IjXR_pWF4_kjF*>tVEt*ft#?SD-ioE?!Md zyE~F&=YWq(M2gts+Q*3`RNuEoBkgo^Jc8H2p01X?oYqf5_|w36J)N{Tf>8k&^+8e`T;hutA1Uc@X@UPzI%et3!Gxn8}?yY-ezTJ7z-k z2*2(~am9+4qq`^EI_p8i5ABb2?-M*RSBq_f*N3MYAG#|hu(r6HGiV>=wn(+nLDN{c ztI4oNlM(CzZPEV*1~qAxM1t8ws1?a#qv1o6wkWp1Ck}9^SCz$BrMhOcp0VHZrhPx` zi^P`SeD=rJ`86RM{>w>AeGfHt4!(*=ikFTgqz)Tr)CcKY@wGQ4WL6qD+ru%{A0|%h zL+ZVS>fw4aG|*?d-N3i>`qy8Oe<_7X&J_Z15r*>n12x*uBG=Nz6`P`8>q<-TW}%Ye zPHnkzcW1pwed2{I&ziinP3A3KY5(uc5M7;%xskb%(r22)F<$2DY0B)6eQcV14!+?J zTTEfY?cll4ijac^8n&Z;V3uCOK_)ES_-O!QX=7S*b}>#gIosM_I}>LSvjjG<5;e^| zNwmy(QhNjgp0~=mr$+an!(T37ZSO3z`qvlvdU3w_X*J5z;vhfs>Pryl$#1=C^)HPQ z)28>&SyE6LHDlWI2lDeHM=6uPshN$^>~+U$n}K$Y!nCeP=41cCzstI6QJ}2T^zLK8 z`BIu4Iw~yk{oXt!SmsJOcHqTipB|4y+FM|BU7s2z+1Y)(J^EN}R6+k|T%X#B&$*~V z!b>6PTi#xE`;P2Od$IxIsmiIEg`ZH8BFM)rDKv4hB*3* zZQKGTTurN*Z(ln7SR0IKOTg$@c#s4_A0WwoX5)q0~K)|OTA5(9mgrLhHO;im1_ z^NH9hb!6ea{oih#@=f86- z555WhZw!=O0Wmu70KhHo6RRz2>cz*}ye=VGK;9B(Glk=yvrO&(Uk;R^<}~2(h!FBQ z_3mP#OW<69c?3c1zE2(C3P8|Gol}rI%Ij3fL}72Y-<%0znDzp=yp1&SiQ9>sZZIi> zGP2lNcyJbAi7f>*4D+6h&#oiw;MX&5PH7=#Vf~}g;dvW*#?d$X(r_O}nuT_?Sa-OS zSACYQoWJWa25i;%+fUcdSnx(H^+9JJ*kxbuqes1l9=(p-!yY*+6ub&v+mX?I=p|kr z?M#oJbFlmxg3zLl27NfS;#3P(;#9Nf+GHtl_Wwfy?!qLQk@m67p+Y)pUl+l;_xC+#<^odXiUF@?ZAfhqT3Eq`@4)rfK1^82gyQTBLPnbHApJ=LzUN~RY*|Cuq@Pn+$JaE#6KzPv*d>jh5`kNaB- zli3>>!01WKnim%U5!q0Zt7jyH7=25b=r zM^df|K^E01ZQP%S<^of}+-0`-@VyCf?M!AMFxkU`v)P;l{O1_YQid<>hQiKK~d=Rft$Z)#EK{AL zVLzZk4D;vm^Rv1)Y?Zy~E5{wDiN+(i(7OdI+xVURu9Q!5vXf!nlRc(LcD)y^6P#-J z-K$~)Fo@jmWgq?5qW?@EB`wW>DH`F){8GV{JwQtHY4w^tpSj@FTFt^wiRaU!VR5*2 zp&s9STqjGCL-Vvg%i+Mw<=*NwfK&XevSaFdhDC2hTBt?=^^%=L|7U>7wSC@7oMol4 zIg1)X5wV~6$IMOtm8!*i<qy0_o2Jka}Se>X;{ z&p+#Bn#{1)=O<>`-BX-a{$h6@MOPSiie%pXPfV@{KMBwaZJC!aIL}kSY0YD_97m9U%@#@sxDu!whEAa@>G?51j(=)Nk=@w`ka4)^ za`O1XEkQKC<8s@tKwGng3q?N;_LFO@dH@IKml9QSngADZUD^WgN3f2+B8wJcrCCT z5MLGYkCIjw`P`}QP(fqRY_4FT&AApvCFOrMsL|-vdNySTXGMldKab_fOl1*{PFHyS z$}maKZiiZj5*e}kEI-k6rY<7k$&gWvcal=t4LgNY+_3KjK0KUN=&%IOR(;Sw|FH<5 zhp`4^<*WAjHyy>ys~@fAT-?bVJjn{V%A|K9O)(E%M`x(~1275AlHbXhyX7S{R<)jLFS_QPEb~h9bL=`H$ z7S51dr^B!X2LFZnAg5KA+kKx1NAq|i-^2aY2*y}0gTuVG1^O9n*dpEZi3bm}-AdHz zU;q|Gc%=^3+HY_Ds~4KOV-1YW1&x!7Oi2aS|LV=gSC8~6|B$)=a@4UY4;!aIq*REQ7 z>?_=@2Jp|hKfbR{tpEV@6o^-nwP69kH_$c-^uh`XkH1VN@I1bkCH5fduRODIyH}V| zcw0LkvivS_tdp}S1|Ky^QkFgSX?C4QS0tvZwWge#53N0(!5^mO6EjC<>cBANs{P6#F@vYs1?*v6(EQE{tJ9|l9{OJ zb)_bL?BmQ;=p>X9E*2^DiTSNuni#J&cu{v= z%#|(+s7pBd^;)o3yhJ0uf_*1r1=ukzAj65)s(=oc_l0IvU(pF_xx7O|9<>w8X04Q-06eab@{+vWW*U@MmurHd`3?=Q0vcpD4Rq z+~n$1yAZMAijVdG?zuby*k{6T^6cPskbb*93oh?x^}m z#J>a`$*eO_jM9(wD*Mn2ZeEtr1&+)ES`!WB~n1!aV5QhL)K=h0AL|!uHc=IuZ zP!M3cE8c%$8&qykGWEpo$HPa#@9xb!Vt8w8Dy@}N5tX!@Cslvv@kzGH`m?1|{ zvn6)#JK58l8FjY>Q{$x8JFT}RKksTUUI_06A@7 zNI9kt4P6T?pyIR>7bq4&a^ zK2v2uO2WLm;`7u?eq^#UNCML3%i^!(cvGwRc49j0h6c8a^z@RCN2=Z-lAi~e1y%cm z^nPiU3H~dkE$xTKYkb3BT=%d4;<_8RM%AbK#0&pHyMgY|jdtCHJGsCp$hx{o{%p!a zJ7@A77-^R^z1B!vmLBl#U$k!l8bzF9m_kTdT03Vhd#blCTay+QsDM|@Z4QU5sa`$N zjb`k-QlfI?l_39YV#sD)Z@%=k3Y1T+xCM{J0qg!V%*Bh zAE*QuhV-2P>Dnwo+p2ERKkZ}Ru|YDtr(>)Tm>0UyPDFm!CDzFIeKWBTcZDQY@!>zMnz@R?XqYxe5!b;Wk_&DK6nj48A`iRYRO+utE{eNm%L zWyvW~7zxMsF$@N8x&2%iH!4M35%xCa%V@0o7bJJOl(}emhn*rQZe^M#BXdIZ5>7rP zH0fXpGt@Kh?Dej8o;9N#@I-L?2vCV%5D{?#WX3J z2bW9I`aN#KPc`#1-_iE|{bI^K25elCjYqM0^3UOqYJ^Nrr^W@hS90%+Kb!3P^qZUd|bT^8y+ zvhr1DdXDLF{diS=oYtEni)xe!9g+ULS&I zhqTw9B?g8uqeSN3j^5G0TP;7Gt&kYT7Z-_qBL^B!-fDC4Huw1)BO_8QI+LBXe&=b} zjf%;NFQpTnzRXdqgj-#$&QSvutl-lx)0Zb?TN=X5Y>unp{)ym_r zMRlGv6|#OIQ#FrE>p^oZG7=uNB`J&90xM0{;W+ePl(Yy?@+G9*;1K>&_KH0l>)t!GBr7H?4LVr= zz%bcW+NKV%r zC!73vjU9vixH82PB0gt=c~J#p*VI7IaO3&Mr3w;j%J#yiwQr2s7PfEZON=Cry)Bb4 zD&M5AruhCHJYn8r@6XOctdh z?cHlvphhXJ#gg7-hpCF*PG_F`gtF@l`VP{^CN(T_nh_zv{0z1&*u}Xcv`c_J8WiktQKc#&=r`z$7 z{`lKb>vNi!2ijS~VQpqU!>H97QKtm%A8m^YR2hk$x}2-<9jyq=mj~Zeez-_HE-FpP z;;kSnqW{x(|M1mOrTS;V?@mYmv?75ke@L*p55Ce=((%ABmSHye9oEx-&2wrq6cm7M zr*Kxs{RR}jRfv!pKJUw_FA$uX%vW@)U~LSrw#DwMN6Y0sLQpSL-E>F*p3B&1FnSPrUAQUp@G@g$lFq@y*6XBWgKA`oYS8gY@|s2ZLA3 zLws6Pr$6;=mt45lR))COj<})w+>s@x^(96UkcqOz^$$X@S4Mtc&}!ev`Cb;26F|bpB+G^{k3ck$m6b=DTv#S7p9-F*Qn#jiPxmW7DV~|6u%4;lEP6 zyCl2QJ1$eOA!iF7d-`8~czj8S+1*2OjM;hm+oApVKcBLBq&^At;My?>hVo1En?;-8 zqZ7a<*27^GaT?UdKD~ZT4GJD%nO!x_aPDxPOTs|j|`{}6!5XZer_M4jg)Qx zK-@u;c_$+Q)pfJ}CYVjJ`gndiVuz>JB;a!6i^1&DcK_MF&oY0frAZS^U=*R6FIOX++-S9X#ScQ_p56 ztV+-T_VU2MS*^aUn@EC{*dHFWm(g{AetlT5`WdHu9LK31W4shO+Xsv{qHeT^Jt`7c z(&&4FZf>d}Ebk)MRqbK=du zMo4m@7bw<(f1+ui%oLWeAB|shH5{53>uc1L*Z_&FUPZI`vniqEuY0hnsnO??T)%Q$~Z9`QV7of*QD=J{$9B*Imf0LSHmLd+y{|nD1aP z`)Dyi{$yi^^1sK#x;rPwg}a_czd8Y*`g;(4lQF7O5Nlz8y`Y1*#zAKhy--!(FP|4* zT^Jbl3dS;g^PfkozjV`V+;AQP)BB#gA)hRi(hG~%Env}}H#eFNo%hKf?TWwsevC_# zWI4zcEnsh7Q**Q{u72F!!8$Op$x4(-mnO|EH`(5_pV&PZ?eFMPQes*ST;x%cU5TZ{mqOScVXZ&WOFv+;4KsAm*Gw4 z<)iNICJmCkRA)29na!yY6TQwn(%Z^OK5>5_lq5zCf~RnFlWV2+KVz{7o&EDcg5z<` zG1$;ua_vSpNj>%?@QdwmXWNr~^Cef2|QXs%5>|Q%ek#a||2x;aVn3;ZSD#9P8~>ck80C?vg_LOMS$9bYejG zvjU&%#1`j5f^B=0x+2=G`1UOk3)a6rq`yC>fB&3lrnLD2b?qkUS=fxkpWp!dN>yg= z$wJSLimb2fN+!s?0@pRoF$D&w6hP=ll-%Ch%)a@JURZ~-Nq>PNqVZj%hug(i6~ufqcd+FRz8o&vfi zwKG{XmypK1=A#nM6D(6E35%^|DLs%42dgFl@M4xp?(|ji4F1`5xZYWwI(dH#4PZr) zw@{W$A{1vuK`3L9E^Av-kQgFj^Md8I4H2JXV~W;uWedaVDrM@k^#?Wr%AuITFc;>O z#49zrnWOVpJ8Z1&w3%Aq;{V%`0x4;}o+G)U?3!mxi|Qw4_Z*%v2%a=!Syk|yK`$I_ zq0(mlV)gz{DuzB;zFIH$anEr8z1n8fC~!`g>K91SEBD~??iEf!^hY!Gs$Z)URFaK(%))yG7h&beE>#)%VTQDVjfI+52T~Rh zDOL~D?qV@+*HVuqh606arG1xg_mDOG2p*dLfxKca>x$M8tg?p<1}AQGPPi3wG!LDK zOR6dCtJ+;}j&~#i>{swWQ5j$Z>m=xwYq>Pwx#D*AYc@g&knV7G^FJCK@@^R8oWa|G zD}~J}_4QP(#agjPx70t6qDK1|E^)qLRFY%H1RuxzM{}BI* zCZXF6FC~_w!jeld2Q&PN`h1JI^yR_|2>LM5W?Ux%`jo0!$1koA;G?~wK^z5wL4lEhrFZHJgA3Y*kWRkCHeM0jK zR`#^}Yyplb7Tu!eL_UvZM%*r$kqHoGQmD{)tt&2FGk(4)Y_Dh{%f5qgjzXGdO-NzQ zL>1hIYS3f1AdrbaS2s7n5D2lKQrhe*48h8Xjw}=YJ$yldqohi+a z;Pk?$fcwF*>pf|YkHk)4(wx-S#m47@BEJ9r-LwHIOit9l!oaJ1t9YZG;pt54wZs#t zjsE_u;xjBibM3Xocci=0UrOCfbfuJtIQ#ZVp>ThZ(d$vf`07(N$0z;NMWUWTvI8cS`%*_n=a* z2czK5lgBt@{$XcVzTuZcuJ3w$D0|qrzOO1}Fe6U6oY?mhg_xqkZ$$m9xdHOB-CKi( zSG>{G%^NIit*s+jkn2;}!PQaR5HyUU@(9;X4e2y{OI3ZZEz_=Li+LjYEY~ zl}dPqilc^TDqRAi=2y2~aC~|dYs_+Sx(LvGL?qk4w2V=9Y-sXywujbLLArbbWB!Quy}>j&I~oONv-yn zK74>9OfroB*sMoQwi{Rg|DSgOtOruQH~6!YeMQW|w?@BKed_Fiy#QlM55IwO0W#q& zT<8ktL2R`gi0soXkR|cit>iiDI((>P`+n!zr49o*&inxT;dYP$Mp5p1wh|=}udDBX ziMS*BJ`i!K-eg4OmpXMcBt@JhCyJfgh={$N9w+N%hP>ooISzKoiMB94`w2=ym@-Mg ziR{PsKYpm~YQqZih&7|SR}EPU+a%>A9f-__#@`+03E-o&z6Rb!R-QF z?p|(Xo-%*-y;`x16}f&#WxzalNh$Cf2wQAqg7uFd{IIIH|Ml%tVh~4`4BXk)FK>-F zG!&FGD>;rbgy2hG>HmTRE?gZf4j-_hM0q{I@<8-u4~7`@jkbvw{Z)Xa(ZR8r&j_E1 zB-4ZQBbdUs9!HQ`f5X48xK0uy6RUJHk2;Hn4)mueqV{qnh0`7F96i#Nw&orHwMwIk zSjf$g^z|hDKzII;ONuyTE-W1R{Es7Bqloye|G66eej(Uz&xq&x8RWZ$=l3Pok6ZkD z1|{KwqG1MLy}FOhLHJual{9`rO7msc^$xW^Dy$;tg-48O=wcV6{Vf^^OwvrM6MdrQ zzbq)tAuKgwXe5`D7A|^X{qU)!oBl7BcwCq26YcI0YFwb0&%{Q1f#IM^KAQ~27m--f za-9l|el_qNf+yZ4g4GEB`E0=o#L)MS`HvXKSNUM`)T^j}w@on8DeSUTLJ&)g3NIsK z&;}F}MC-~UZgW(+8=;E0BrSIz7C}n(r_7sUs6Ba~gB&{L zTUsT4(|&sr`}`02)7{o!{lOx6IWi_9kBJ&-m8H-=W0T-d^fSH+kn zbTB`|=}h}VLS)5?(U2Sm>m!2#$lV1s&-fMp~Hg`L&2uiY!^`0lTU?irK){(cg@#JgGP zXPNNwf3FJ;zS0PKskfUiJKb~w(NG+8eJ=p7+i>~ULNyRS^wOB-h2>?NK<>T-2VC~y z2=K(~^Z&#gD+J#*=kUogX;@?ueuW)W0m+7W-{Wf0Sss%&7)rc^2^oC%6vKT-izkKlOeM~7? zSSpoE6ZeXOmyLZ_R?_pX6ulwSvs#s_rp{x1(B_%Uh=P~KlDW#r*u~f3k2nj_Mrta@ zNhE8j)>#`KyED>-iT~q}{&$bkt+~{>w8C+Ev}2!8>(W9{f1;k*R=qP_f4H?6!I;K^ zON;*wCF3l{)DTi5_{@18HN1Qq=`&A|>K!80qOaj0<(E4{wG#%RH0?zia-1PH4wy2G z)%THkr3zxRP_9CzTr-t=v14P z03DN>5JiC%4NP<(kZsk43gk^$2O`%G+9kj7-5^&Dc2qaHiR5vx)atw5#HV3cWX#@T zpHzvQsG`cdsvi4Fl-HsR*pd7eHG+xX6okeEGc4oOI~!Y3ePA%|!$6USZ=LwYE^tcl zK6#R;zJ%oRxDWn`UIhNrx|drMNCSEXQK|B|GcuUNCznizA$@f?=e&zQ6-JJBBuF0KaMrH+SW@xWs3z}N$XFpmt z=(OQO=^*gU9{-ou+kzpZK3hj4}WM{+EvQ?E+M~kbQFN( z&rrd@XTu~s@cz9uoan)8xBEm^JJ-3pZd(u$56awPsPUoZ!H`?Id0pxgG%IM~AP*Sq zA|hwsoEG9t4$XquIBaXJ(2X<>w%yEf^~Q;-!>Y~;ynmFi`wx=j?7TK26cyF)cXejK zv8K;BPz(%TPi_qw7jJ~GgV+Amrdh#km)&hvgq~u zn5%}Tj8E>Q!8n3}X`Jr*kBV2!*DZUaEv~oI3%l$`BjVQ}kD3Oyy9|Cx8He9(OCMBr{nu-%UVxd% zucg+ka=1APVwD*NLBUiGO|zX+>$ywG@Stmr9tv@sZs2ph5<;z@6IP^vrI2`#x7i0# z_QXu$oVH;Ww6D6FoFaKQ)iS+w1Ej?;q9GJRI}+<@)CN z8%C@W-p z#7J`SH+GzZ`OAtpWN_TY@?!oAB-j_w7Mz6kGO&ZmyIDJC;gp>_0&@yT;S$Z{eT(k| z9s*m_R?uLm{gEmdyWDEv;7;UcPodTs988^~jm5?3GO%r%sMK(P*(+V|S5J`}ESp*W zHmCsY(n0l)DN-RFSQ7ZeC2X*hpM?dr(oJdXyu*C6A9TjBl29Dzr8+HE!;`9<@$^?1 zIf|^H#Q~J}ifO~d7yww5LnZJ-uGY;(EOX z9t?s^bU0NNe)&&JqOes3BtJUqrrpT9EQ9)Rh8Ggd)5sxw7wAlLI*nn37gou?d(2QK z$1!`KP7nUk-|CCecf17#m+&+us4r`_q5eZbq+2X2Z29y0x2&j88U^|U!kqdY$EPY4 zR(Ap=Z4zExA&&K@Vpc?MmO4pd%19p<#AXr2-lD88(q1kM4`)rwDr1Ag%N6Y-6(r1a zK}hX56-tMRUJXOGaqOFi4+OC!7NOLwX^cd$)|CA*oqoft`rq)5p+~Tku19d@SS1IK ziQ9(8m_J5$);;9=$H_fzzMMzpoGV9-Ox$j#?qxQc#rM2d_eZb~=%nIDMI|kALZL01 zN=dZ>h?}yQLGnLJOXSRy-+O=q{BSS`kz6O$8wBDYfkVN1a1?IS2-tEP%-_lS$vJ7& zGp}mK2Fbkt(T1+)A>)gTd~dA@JQQQh;aI+2u-?|lgZaW+KSVWN5=|9+c5QRxvs}mP6YPb^V0X@Uk4-q?_kUlj|J|>2t1f}$P>RQ2 zkNs>NnQtd@D{O?$W7-7X4eT5fY&y4T>YdbQ(kheq%n`WQj@o(!GF}=U_Ohpz`XzTu zN(AfqJ8@vwdwSg*S6cnYi%l?^48SiEdTI@4r5@TtsAXThZ$qSCA-v6@6lfY>v=)EiLqrDypQ~b zTebjE45TRCY$a z{LDNEC4B+TV%hyg*I!#QywUyDOO29*sh#elPIvxw{!P~`rQRQ2VOZB|Is&~RHabN$ zj>d;+^i*iF)A-Ytv&fchQ~Z9i(9nSDf7rcz=&`-lkddZE`VvOk+V~biDB11CamXvJ z8VhSmbDnXe!fJ6igctvv8x04K2KXE_MrZD-TbeMn-8XmvpN* zY_iJU+Vkxz^jTYQENfI-Z+tD_1Cw%Q>X0(5KO}U?FHr}JNkVmBW+t*|=Lm+x23>RY z!kNwPSG7s{V09Kyg@`LZoYMfzu|jN>I*t zkUczu_>|8sLS)PVA3#~)=9W zCUOTw264q0o+H1BET)4brh-0ox0}jzErN{}8$gF<97RW)!IMPZ*mh*&M>+;(2h9z$ zBDqT0s^Oto7uPtl7hd-j%O?2qPpW;2VkFp!K)H}sWfnSSrWBI?r|2hSKa2Yo>V+;HRh%NN)GC9<5|N5YkU@0;gQmR7^CW2r{_H=+g35oFi zx|O5pJ*iR;SJ$3!ujNBO| z3r(OOZJ!VpZyj)wXeatmcv|fRa$i98;dIO7Zj@0d&&ksL%x5FoFL}Mb9^dlXQON&h zea$4(B9OX#ig4q}UZ_%U3?qi$xXB0i2LK`2fsH#VMddS|ysWC7)Fv>9BK7)5JQd_T z)zvG|{!O-7H#;-ChOiv5MV_{Z1r@Z3gFH*GF2|Z0KCT9xDpwod+{hM}F>^-+X|}A= z6)JtJ3Mvt-UcS^i@(r?lS6z?k^am?tts1DV9;a%p8pNeb!cVPVFF-Bbef*dxR++f# zOu4lkxmcrGg<3bVY`I1aG~_FtYi4qmgrDmR!XTI_XE6q zKSK#*^m?7HC*vKz>o{Vl#oi)nt3vJQC$JGz*WTtK11u_;gHzaewX-%?OHj`Do&>p& zaVkN6$0SBymt!CMRir^_GZXKFr1&bMdRI2*qfu5&XOXu23+>1EZ#~j9=2x%LNO;xx{!;^m_i51lN#^`U^8!ortFABS(k=Y6B4y0bp7oQZ#aW=UNJm14 z&o^F`y(9Lt7#2?bzMtIZo|9JYtvXXedmOp6d{2FMw@6r>>&owLb+8{M9QXwu%F!L5 zub=yaFNym|le1I}{aiiDq*Ncg#Uu(J zPX9)id9}2G*L|}ELqAa55xb?kK(WV7)@C_O)4K-{5ykWRpAv)YPcf53XI*^<5PU)~?rZfk+471GvJ5${z!fgV< zjFe0{A8UB1USCbb1*F!zeB^rIzrS)fXt7@CnmXBMSe@fy@XF5cUk0_fSQ{D8_xH{a)Z}Y%846slKC)irq9jM5@yW09m zcx9nkNu4n2INvPF-gZAg&a}r*3Lkg=AV9U3S0#!`h3-_BoEl{tGZ9B@hU_vtr>$RG zh~#I5bC4Ksl?+s3--TwFKK0$6i6cQI4fJo-xW7GTPCJWw1u=6#lkBYe)3j7Yz9x1c zT6$E2L(~W&6l?m_p$M`DC~kzInvaaDocT{b^ZdJ~lpv&+Ddi0*)P$Y&f|2jGK3{vb zDjBC}Nb@pNi@e$3y72b}@HLr2V(MJTAg4O4>@G$5M5)@`R2qm;T9^p7pki`AnDz(? zMwQwgw6V*cSK-m&tn6mZSYhgY0ynTa4se?!LOw%yucGO>$w@}Iam=7g{sNQGw2DrIg9nithsb$0JbFy`O!dvPj=M%`xoqQ#OCtifAY^TwOncWr7t=gG)G10Z*L4 zrVAs78;QODp){+wbYYj(AlbM;rE;xZ`-BB`8t`W7y6@3;xK5PruK+($2h7b%j?02~ zELUQ?+OEin%F`R;{vTam8P;Um$IYBBfQo|1tsosz(w(CwB`s2- zgeZu#bjK(O86pGeX7oT}4#wWg=gs4Ie7|xWlmFNmP(1UJbxRVM%(g;x}ilviEsWP zG|=hw5)%(D+5g(6wmV`O8D4(8X$|lu3^p5A|$O;lyncH+W zCW5Ey+$kkr>Ec>WsMGAsfTtbTrG*I42>b8^96ewwaV-hjbNQiPtX1|BiJ9K`pyF8N zOC%(>(r%~&gmHu=>?LPH)h`@G$21QaC`Db~$h_%j4>HCP(_CjQ{FXKEFG=`;25ZKA z5fx|%_NLuZ*vtVPq#9hs$o5mniow&D*2G1@Z8NN8vv7RQ>$y*DlZU@3^Wyb+Mq96B zI3KmW8nDB8=5Y-Nt8&$8>$rV7uwBvlXoye>C=ut4h8)_}AVJd>3xiIN5HQ@g>lg zr|W?8qtf*+?|bO(PE$YR`cRK9%6bE!VJ?rJpTO|RkJn$Z3N{|(y|y|Fzc?Md<(}9gl~s3f~p-WqB%cCaEOv0SAZAM7tfJn1@;Ms%r%&p<#Gj zc8wC>5f#$NysJYnbWlp-XHrKLWT|}2;g`6KchbnzbHp={rz(OIu)G*kyq;L$biH4?tAFf{`n=Sp=1GE}EY?!^ebsdI-@Xlou%|26(IO0)A+ zt=Pq&fzrC!#=wB*B;3!Ng-rtzCV7u&Ln7`SjV?;^BEQtR4 z>+!~gXXotvjJ6UGvE$a`f$;(3exjX?q6u%j%WIh9o&H6*Yj%h9D$I_M@jwFTqfwK; zecqmg{n6^r{IMq`E}o!+yPigJk|zyNT^wXUlf$>?pUNZ={j)&`*OA}d{wb&;y7b^i zA4vfuAYk zxZcnTQbjS(BmEX?Zjq?SB3zOZk`6(yg#*lTRoQsFJC3>t>pX{y&+}ij^gUD z{c$$=Nfmxizi?N3>x^`7_fnWRSLh(SvnahkfhKo!lIzz%t;-B)Q8s3}k+Mo|vXyp` zbz41HcM*k1Tyr}!VnQ5NpBD^GVM%_eW}+8)(= z`zB5UZ06g84l26?vQJ-@d(FqL=#>QCh~DeQ z^{<4j9}}Pzkk{(`LV;ZDIX$#ru=v{F?9kTkq#g(-kF-5WXUpN@n0`Xb7v9+Bu~B7t zDoLl6*p*6vFQlFbIV!Hv$c>kCh;{k{)_|D77Hgn!)_+?iaZA)M*2F=RHBdI=6a$PV%UX|52h^FTUz4Ge&`zS9X_~y|DixX%uZw>iH zTDfAAm&TcHA*&8)xU{JaeCJ$wx~7{wZI^l!zP(c#H3h($012}TaKW5zS&7H;CG6i0 z^jChfPnxXUzRhqDF3Pm_unhAm>4i%kTG9UoM8cqqUV+h;nh+0jUm+D$-4MxXSyZvs z4pNGmkEblq&4`_S7hAvL{+Cs>pXEQQ-UK_jBjq({^R3dPG|2nssl&4txfqMnX6g5y2L@wHlUQtCG^B0jwXzp|%V0#C*G(R}|wB4!;`9VoBB2J>`xzgXI;*ZGh3 z?3_l>Kohy;JTAk36S~$%3zfOvx5dX#C5K+REG}^H;@~6p07d2KmGm{rPPvfI(SB>9 zI=KgDoRr(bb5RLL)kuWQ^W9{X1j=o@z_~Gw$c@U#Cu|Cb;oKf035KL_Kz)!i%bZE& zoLCayQX#jIE_Ex^MH}Pk%rhz5XYS@^XHHPD22HrrsL^wCgEAxZS2?*LP&_}^aE?^t zex6e3q6JF>GR7{*j|iO?N^uPmmq?;1J9HE!j?zCZ*3uzj&66!VV^3!n{!zOY1Y@klw$$#?dypbO}XmO$6ZN}FE=3d8RdrcCP@%0H}vn!BCbxVmrj zaJxUc(GhU8GLYU>Y>@vU?)AXcz#-Lj?|-hmu9F-xUso!|THT7Fa=rfgr>>=#nEB_K zR2cyjULCQ6EBz&&=(sHZ7apMK;^6SGFAynFz6%J@DGF+}ZR^GXuZPxvESzUgiw-w& z-cz-%iMY!F8UN@}>}ePuXsnx(viX+}lv5nXeI{N{Gj#v30r*O*7m0LSGCo;IX1;FMEtGhrIi{HmsFzm$GrYyBMk>(kG}Diy@Py1ohph zID!JSB@|ak7`VNJu;|)!jh=!Q!zpRFBF@U{6HMA*c@;9Vs$p^% zZoJLy z7+)UmxJ-2ve(SetaqM=z`EnN!w==|(Elr%SESDvOl!-I*2-)hx;`y!_51oReKcJj4cRHvXh->WYbu8)J+@ z>c;Ixzi12ZRmH#n>76_;S#AjWynOCw1Cksv{c$P>G33O+eCp2`y*!bwLq-)c+x_?O zE#^gSb%zn1;kpTgNVUn_a+BFVrub3xau&XP^y2 z|9*Vxm0HW&84B7l)&-Z}y8}GE@cqx_SHA!>bC1eb~hOH&ZlQ?o|OA z1cFrX&%8bD`Ky(rgK%Gp!R#E0h(YWe?anFJ-}@VS@b04GUKf)M!>@JSkVeW#@H<81 z@Ek>5_q&Yn=*`g?+>kNaG<)k(yeXxgx-;RB0dNZhZ$G28vU;zdeWmI?ML*!Od=cJM z+Ig0_yeWHKXZRb^@3brCXZ_-u+o|4(m|B3i^kwn5+yM|6>qO7f-beUHsZ3k!BBF%; zR)9sjRJD5rACjG6eC(0&Ga@e0(NpS4F&aVa5>J#i6H0+W+WR}Y^0mfb8HVQovM0=z z>7aX()OXW-9AABFlLZuB2B~(x&Pffz)ESmscM8OcT$&K+Q}g6CYH;);I&~Nl;=r1T zbjH$UtP8Q5q12hR8_(;wrKKBJX&@%nQ6W@H_Ewm+du&JM{tDu%<24hq7NP8Y6Gj|C z&?PwFC-&&=>w1;d zjZ`6txXeV=3yz)tzB8*7zR)8_s|=iLqH)f*#4ZiPG#e;nGi;6t!zrU}JHch)< zzpmYw7)0csQ97R12QSp=QyI0d|W1J4LQV{?6 za zb((=TG>d+D*1EpFUj8OCv%!C<=fM*aeFLN zb0Zy_WB)J9)kQgp=uk-|&B8Z{N4a=kn}0Ck1@)f=$ZOI1MSC^<9p&TP{e~~4Xo99n z>%0*_ELNi!WgobvQi}fqj&=(X{^YdHYy!rl9l$})IEC1GwB$HSbN1_$Y%YIeFu;II z#(6`!N4&t2r7<|}tl4;M$@|CVcn>&mJktM`*!X%##mAZs+s#^M%c`=#)pffwWZ4Tj z@4dzDLNU|E11&yzPOi`!D9#~eE?bcV8xZ7jA6~ttBY_SZC4b;cuSY0^|KZ0%rGiH= z^hU}S(w?0#Ky2fIQo$6KcWK6PbL1*jD_VVFG!GodXXrZ$mD2fq@9Ng%US^=-Y%kyI zJ^pyDdMU9&K#F!dzAD?^_yH8R&eQV-UC}j`s(}auRR%(^Q2JrzQsX)=z_-|j`x87B|Gw)lg1;M& z=Z}>EOx4($fvIj^cpXu8w--TbX0Tv;w4nvx3Icj zDg~Z3MQRO0p3H$_WN&wCwoO*uoTz~0g2?S!b|yOXt6`!_i}kvmUvh4nw@qHgBuMc_ z7SalvmxwwBeJ@t4^I7gXn;idKtc(*eZKL-NNp8@DwjaMdccBFGP#pGp?`uTqiat9G zF3n6wRRvkT+oTJ>7G!7kxb)M^6cV#?ZZLF@i&s}$mvwQh=Z=8pzUrLP;Pp>(W&Np z+O@|yeckPXcAxC_ZOC5Q$5&q$Z@=wajKAxC!7RrKB{Kacql>wvg6dAAQSVI~(P8f@ z_eGwl5yi&zf6?wWU*NYfH7{6;2g|G(8J9wI-v0*z8gD(S86e%Y{maMCgN1l#PC49< zW1np#shyr7=2A-CHy_*4iV7on>I8XWQ71DDC+T+QDi~kI7aLN6M-#s=GQ*WVph_IK7r^^_ zTCYM|O}TpO9~aMFv=Oe@!^8GAdpM1kEvParM2QU3FkF9X*w>+AKf^3&<3TA=#(WXn z^LqE}Yj2d|X?^*LZ`UCajxf=Nc72EluRcAD=c4nOMUZ>0 z@eB^q(A^6U)#65FGD+G?d;N@KSx({)@+Q6~YK*o?ba?fa{?Z(Lt+ZQA zMA?x=+oug&C0Vn1)r@6p8S*+-x>EcZAQ34K*_&Uja&aR*a$Gu z*mAE#US$WxRyKM$xO`w|ZT=ll3261|#25I^*3LsrV(Nt7Df)jFWq9p%?k?+=RaZ}L zTc0M4A%FXaDEX)&PjV{MKrnim#hznZqIMBBg z0GD4*`#^zb4F~7heGt1seWHbPQDx=uAjuFzr3pzR$G8g853rc>jncy%#hY<${(R8tdW{O@hX#x6B7k*;vp31>wPA^(f!@s7YmhY-V;+F}v zE6**QmhCGMl(cH_2^mbSK{I+aPg1uL1qUdR8O?5KxZdg ze!Wv>Our)OdGBw+wfu8?Td#MvT|)wo^_36x#qhh7Nzoszmg8mhRyJ0WkwSZg%V)5! z8t|5TY^b{m|K<9kfBI=_xAFHKrYvql&>OnbHOKJHI9OH?-bo7XCu98jQ@F1=28TAXWVfAUbI%> zQL0_R+7!zs%91L1YA9)WA%)7e=YASXw|@7)*W!D>uf*%gJq2C;7Hyijl+)s%sy)Lz z|T zq5!!%er~*~%Qz|Wti={R`y(=QZHywUnb=ij>9aNcmFt-<@@T?3YH^QFH(TC&<6-^p zr3rR<>{sjq&_VK+06QE5SvjyPNp@s=mR!YJ&0;Kl+EL|sZV8qY^)~pS(vqbRB4m>K z>ArhBt!b^aE3vF&^)|Pv@nZGSWWQZJ&%}^-#XWAT3ebki5?j)m5nOslu$hJWm61rr zZ^72?sL0_r?;mp0*n&Ek=iY~b4!ECHLow&jdvuVv#5QTM9vGdY}hO7v@Br_;?#Bx+KXVu|;Sz4Hnh_mS+4B0vnS1#(_CJB-7?d7$lI78{T zjN4qut@>4kjY7ZZtuC0CIbUb%r5(AbpegKL+sr+TdKz7&H_i4GD!R~OLe12qp8MX& zuks&R_x)#e4CO76<(>HV>>>=JutTFPE2T&Hp^E^LECw`=?HnV|_FU}VUSaxQj*b7z zEIkbyD^f8L`3|I~qD+r7+s^JgFqnOsqxdj6j(6t*n&)$PZl@RbD2(sM-dOxlW*!&X z60~-^DJ3=Is2oU9-)Rfrg?e% zu=pA-4BIljWm2A&GBo~Oa0l@P)OJYVQzG{yk@!6-$SHT~6dM&)wr7e#3mDjYFR0S= z8JT+I8EC*b?GG0iY2sX$ey%k_z&!?R@UsI4Ct~qAfiE{Y_^<6?P3iy? z`Hm>Vhgy+vW1RP~9RPF--dDjQ-)C`kG= zaO|beMkuw%56w7QBa{QV9x?}Pv9*EVxi2AKjAx5nZ(*IVl1XkO#8V+3p=Z;z;?>FDv#-2?Kxp1M|<( z8u`(y;KXwsT+qXX{L!+S@$_KX3|euc5WLkcGaXM92i$2nG&pZ;q}3FYqU*H8lujh- zLuq9VRiWmDp}VNlPl>dg@Z7S!W%OZ`YgrnoTEj)y-VwC>(YVfqoU@qSlXblHv&s6` z>H}YM*^pL+kOj5q7Y~<63J(oy1qE)cE^VX<{n}gp#UQHlqVsY(iSgVFF_gvcoWHDG z;E{>%`eMZAkpZC%w(d(yKtS260EU6}PB8a0^E)$%2?d`WeDE)s;HoCd*-5NX@c%J5 z{omI`$UID$JgU6i`a7$wVQC|fGmI3AI%sNn#ylW)aNMSZxj$`NZ8KWXnr=a>p2e)5 zfm~+Dfe2RP%8c1yM;}eWY9wJYY>|#j+B-|yY3p0I@(E#7R|zEvk&Rj^y&J;S6N2E< zS(m$@ZV|~%b&D4kCt5#dHw1|r#an`TtwYttt|79K+hlA}lVbl}GA>=7FfnM(>13wL z%5xNtWC@{?rwTY4ruR?EO!0qDQ=uvF$x+b*G$|#&6rfQ=!6Kx0jD^e@C%X);Oc*lI zQ&I1$TrRlAd>(0Gsc6`zc*i;HaG~P~V7#$v{Re+}yiNlBxRu#C$kS_L0VJg?E>Dtt zk|m~i@S0KgkLiXL3Fv;MZq@Fjv?hc5KJXm110*69q=Z(PMDwNVd=}e5DVL1PX@3U$ zZeq;d(Yq#}84SAmTejRyZuSB(Z{npqIla^>e!)uFyl&DxMG&fWx}SMg&E`Rf4?p88 z5%lZX$ioI23A~)I=(XJE(GZge%-VcZNkB>@`#}=poGNxz_V?LfAjR`IQsy)&S?BL> zQs|oWe>e?MlX`adthob>>&eIUpw>1@%~Z9&b)BWHwF8gercmhM-TSB8_0>{GthAN_ zsL>721LgV!j@KqEo*q6&9r}VggFRJN^f?N?J7VewKmuIm(HmO6+zYUovh}CyLrECu zd}Rmt4xFPU56rSms2Gbn>bN02iRp#z9d2{-1X1+?iS!$x$s-1wOmeprpDp*iVRxdE z$C4xsaG@v8!?B2in6*gyThcw>>056htD~U2GDt@vzC_kAi3P?D61i6SQG}J+f#w5! zx29I6Mr{Q%<0Lo~p34)IcgQzX(J2N~Jj^p)YKtG~x!h{b`HS|+9>2MHsq!--;E^M$ zebt|nk~}uZSzS(NUOVs8mu;s z7U~$#K(3M7ZNiFm?nUl(s1($jFx6c(6&a4Xph@)w9^uh)vEZM8BP$sh--cKRcNVP59m=fFz@M7Zs&YMoIPV+esx-*1*KIPRZln(PaxYAZw52yB1xnIt-}o-@~2|@ z;8fMkn|*wOPFO~)!>{ByQlu?q2I2kWeBIZwJM9AhRK~*!3@t8p;#i*|*;~!Sd1?BV zRID5qioKTE0;O9k@Qn~h>md4zx3DCwbRxa9p^zP*xrk~B<%S!YZsk=1f1J#X6{|SQ zt5l65_9Z)%(>Vf*lEi_wqP3+fvXuKIUV=;}J;;*Yrf*)W1=qDaTi8(ZUF?eSpf`$6 zawHiDj&a43^fP}(f_L)Y#unSxt3lpyrLd>Db()?-)ix)ucYS5jzAZe>LG7*d?eXM> zXOZPIAmidCHKJ$jQlI%*Ii)7+XNBdnw}12STxO|fNL2E}aLr=LJ+7xcul$zU4mJbc zDoAqN%_bi!vnu#*;RTk$T0Tm}c(2pBVX3saM%UNbyKXb>1(6UY1j(eBuU~htONq)1 z1{m)6V#@@`8JXc-+|Zr+LLrZ6HCJdnw^0N5l2=}`x=?S=EQ10S2N$nyV1TkcRS7rB ze%Zo%nc9&ZcMWZ)J2T0})o{yS!6^(@Uv6%s4NUSD9peP^mS1dO!m& zMAy8q%O)7m-g#crKEWEPI4K5o^uCq=4GYH<;)CU$Ry`#isXT|`aA@w51sdpf*ey|M zs^Gkz#7m(Z&xjF{!4;8;1Pw(ihs|JKbPs)|{}NLYa8=1kmmGG8Xa(~vFhQ}1R%|zY z<_YbS0-m!NFd}X@q{X@yuw1R?qkL!lk7M~GzFPMt#!E^qVMQ-^uzF%2`R!fYj3o<| z1abs+@Z7^6KZ&@3;8O@tgme={=(XO5xzn|6m_(Vr7*A>0vJu-w?kB^1eSwxTu50Um z*=~3;_;dS!uPEz(rx@93 zW)AK*HyY8Lr?L$rT6<1Wx@giGO`ACtX2Zw?ewZ@5!FNR#*N6c5e?w%XNzwsYq?;;@ zkk3Ij76;U286f=32V+zFlW8117w%bt$gRe%E3H9(Sb)zC+2|}xywXY*P~6@Mj0xjz zPm^MY%j2ukYPIN|fS%!#Qi2au&9!HH3}g(^ttU}~vKEUBOcwF*P_gw+!y?!#LuX#7 zH0&jy=(ZnUBEh|gBJsh2glkCN@VpIVR9 zA+25bR|}gPWzj`gA*43tCP$t4HlW@rGc%PX=22Ov z!PXAQP7Ezj994&cx=Iz*N={h@jtbd1wa%w*bzf926;rc@UiNoO8;Cdu!dsw&v+8bn zw7q6kFvPHdIZbJHI77Ib)5`CoaQOK;a(KH;nxs?iPr^DIf8W){vN8AgmaNvNfbTG9 zO18f=9+UPE0TIKlhMPP0{O&&+_j|a@!`J*92Zp-5F{;}s_~{op0C1_B^Ri{Z3%H!C zHnBGT0%CBmpW$w5{Ytco{d+`Vb#Q>9IQkPqxzDEGHMbBMJyeq9M~uy4Gr0L+VKoy<~eH*x-`|P3X#|Zrrr&M!ja?%<`y1 zr5x`gC|uqmN&y=hWPbqiA2%K-pL~pXqYlb&H65!b-kv?8tQPV(?sN7|avE@d7J{-m ztz1sH{F^5V6B>FBy!;|Ay#Bf`L!RQl{b4l|eKnxsL|s(7-9wNY!$dtxFWBkSf&~kM zK2-|3&=+5B?ic!-S8Ej+KyxX;iFg(E>xZ%5VQrGM7)USX)bMoZW|VW=ugH($x? z%2@;i|C`&9!CXx^ai#u&=Z==XKzmyShj*}74pme%@jQE}-9D^t?GiyS^ zo4K6z{A<;=EdZJ|GiEBoPhs;n4I2zDx4#@yUo~GuRKHnXc9u1PFUM1_uLHUgb~kL! zI(Gw?$PW{z4eF;}BfW4eJ>18FinASx<|Ouvw~?9=KgCmQt`Uu`F8BTd5pWrW0U1DE<^DsCjUlJKAwg!r zu3fv7ZR0v`$EDAywUke#x(-fdU(rG)i+i*qwuHsxwVjaKE(AbW6*TD=+9Kk# zjP1M^47h@NZn0L1SQ-DbOb~h9?Jkm5E^?ZtN#A}<16pn0@7`~(lbxWJouC!1pe97X zxpL{MCB5>d79bv83z=NOWJ&6$&n7E15vEWgmaJQmk<=c<5iiq1_dqy%&+GT?K!`&+QHvpZR=>`^Eq?7A@C0aS)T$0 zS<;0LwD#EYLlzhzr+WTMuJ?`fjY~*c_~&;wznD;}tOIESaAlj>!K5N6(7)$nxMN;ZDiSJRc_QUcKmJ2b?OaI3)O_ErE^eU@sqLHGzOW z^&*Jy8jNQ+m(|>nGT5HawA1Fdb5`CutE^Q{)w&NdYOU=%0CIYt(-MhNY|DMsqjnWf zSSQ~Z|Li-o;2aIF6cCj5a}Jvf*9(F`nSX!_L<9Esz~&(q2UBRaflD+B%Pxd?b?b76mFLrFUp!>a; z<7};Hv0?5q)9CH`_T|yE`x?)&xU(Ei`F?DQ{@a3=g^!p;-LKmXPkgly)3*fX+HS=I-M5Okz0X^$8$}hjg<=w&j7u4ZbdpoVOQ7%X;mVE2Q54vz$}yAj z9@EN;;^4*qp3>$cim1dWekUP8fyc8s{$jA&c;0QXW6ocil(V&dRx=UPsj!b^tvUgZ zXygmHCNFaM^>vl}OPgyOAwD~^EJNdOc9PLh#6M`e#e0-|_rULh;jIi=Xok0yqX)56 zjxqt-#AjCOAF|P$6{hiC6)!`61G?x8GX~S#_>aMZqch-=ewcJ3$vUQ?s?#knDc$ya? z$PrNcWLPptB%Ur{bq%XwY%_7E?&O16^Z*;{bpN3%9AD?JRz)5F2s`uUH>J&T4wq-& zdhHWqcKSP0FTEbsqm9=REG2YSXQaH&Nf#ULzL3V(uja&e{bKS(OgeT0I{zu%a&Xs@Q8IT!$4Ob+5E4N*qmg-W1KKCt1Z$({WS-rl5~# z;ZRtB-Z6*^q>LZH2x3VBzFt%-J>&3JFFliWn6JghrP{r|j?1A+e)xyH(yGiw5})Edg-yKDywPEVYrBd z5h|!-w1?YpQ>bsjU!oL7BwB$#@yUgaoV)}wJQy)4#$X>^UELXv)myI8FV{2~Ki-xM z61M)QbF+jdDlv3_^@^GQko+vD_m4@aSMBzVwQuvUwJ-5shJjb}+WD`kZbM++sc!{?|1gz)OXuohW{9t>kO<|xn9)3TQ^DR;^zSvcHnk;EvD>_ zC*)ZVait)6;P~>1P@j4#I&ngKRRCeV`}OE3p8*W1=yeAYV4@|59P5AvQBBHDOKT`k zY>;7#A!}hrR^z5Eq2W&>P3++!{wa_+uCSh#h?aVi-Rr88VS+X1rFst%k9=r zv9q74?v@EK%&9#UehKb#Z9C6QtFjvQP&Y7kk`>N{oA7%$)(dA>o}bJgPG?qv)wUWI zN45FT(ZK+N&~RuMs_RJ({*pG=Z^E>JeGtc}AAt&2%F~LKqIXI)4DBm6!4tld8)@ z>eYNPzwxHo$8frbB+#2Mn_O~B>mS1q;*VkIKbXMy_al?3kk@Uy@5iZN8}Fivt?aG* zZ13N_i7XqZ5Ht(oZ*nyPG!a7nXz*s?V&brtP7adnyAVpZd*Dg`#7#@tKJh|iukBo~ z+j$<;pjZqNYp1@W>OLQ4PL)}~?Yu)$8?WzDO?c-Y9uJ$D?C@2|fA>4WzK|gaYTKw@0Vpn)2PX zp9YLynjX@FrQWI?HE7TVwVbX{N=}C6PKS01vi7&{=D)d``v2y1{eiLTD_Q(DiV7S5 z$Q5SHPrJ<{mqvCeYR7G(<$3DAXnvF11hTPB3NBoq!Ko`JiQh$2d23v+4M)Xzn;yD8 zxba(bj^Z@cSYuol{P_=IZ#Vo_PDuek|E`>c4vmRNSvv;m`Ml|XD!!Eok{ggnLaD^ zprZ?>(#tzpM|ixe9HlR&Tx9PD%!9pC1vAGuI&a`QUGF~J_|-cQHXS@~2LI&!Ua>+( z&GXCU2oDb0qKX%6*toPMhlQ22D--sIoZ9wRrg2^1U_0HM4bfqV^JQ4AqCJv$_bBhf zs*Yg+#~-{)N5wmAA$x310B+{Wow6+X->Xo)9t#n#r96o`_6_m%709NM3wI2uhrN~L z*1O3WrpjC1F`itSV>IU+YhVC&7W;mL(D+WnG10}(5za!7J{q~G5fwy`#8I{;S%g841p_|ZT9 zAwkcMI^Zrj|1>DGO5Q>SWlb||0(fMYU~q%JE{;!uW3|pxE*FDFw|%GG0~Mp6FU2tJ zwa@NTnf*&`KmBC>Wu{c~clm5JBwISKs0rT;R`sKdknatal>UFc78WCRrhj_26n>QS zPLth`D%WW7Q57+jI+T)Ys69Dy4M81yQdZi$^pQMdBfGC(|J!tg9KP7)_WlHLvv%x| zcSAz_KDBj`m0EwSJo?dzMg2EU&O~qQlafqrB{bU3iaVT zl1ACi-FgV*y5NJMP@w%NKkEE(yunk2zECJ0{HA__J&}lCP!BCrFanah|&ge2yK6ivn+o^^Jji-MrtApEIPrXv_Dt zP=whr){#%7VgelX4oc10aPl+w`aBmbr;q#g`KR;=4eLMs09m{f}h? z6MElAT^8#1Nb9gP)D{tLIl`v&?MhYxFXg4ycwwf&P|UkL#A>dfp0N? z3CV$I!9g2IDq36zPw4Ku)m+MDVC@f2O3_FMZn*XuYUg9HQLtVD%*rq6cP9{}z&pf7 z1?O_d-UOUX^sP12j;{AnFYGV)X}oO3Vj!W?1;1+yxvVTPlZstLs-$(UOfGd+tA|lI zP0qQ#I2yWXCfGXa=VXoJ@%Cg(;8d-MaU0Rfq|)Pv8)JH3W#OC{%E!itdpPo`$xX(s zj4QpH$$qu>-(6-ytEGG9JvHzdOB@D5#(SuuJRgBkwCYl?E`{7O)>Ci3?PEf>GSeSoF1D<@^_t%Jm2Y|9%3LXcwL&0^%QEDbfqj`w#+KlV6hR!QkfC zY@iKld9sEFw((IfkIldK3|;#|c~z&|`7RS}xRua0ot!4`mLmqNW|p2+zi6wj`02PS z2CDm3-up&CM&Y7JMb3rw{7QczMum8V3N+NF;|7LZT=Ia*YXrpfKtUp?_KA6*IO-h+ zaX83#8%@hemey?Z)qry0Rza*EQu7^YPh3$BXl~wu8H-IWvWM)sDTYS+CwqzX7B0@G zH;PVNX%CIKJPkrLDitc;tEOLA1@R(aZ=d&Fd(|yt=SP1f*A)6RD3s^M{3v^AHWZ?y zGPl3Lkt6n<2zp`nt?z*8*#cLJcUG}aS6DeM>NGk3mSm7MxGyTQaJuFsG&^`f6*J0q z%l%+v!$gJ}LrphzV^d3@=jB6- zUGFu2!9RHUp=U&FLyY`N>^f{)$!%VxJo(yxUt>fyAp&tJ(RXfNWbQk;E##LBW|wqU zcyeiczf1eYX+MQKgyK3;Upd7+^=n-@T0`NcloFE?ZQa!ou>%bIivyOAu3?(&W*Cd! zBc7SYdZqOk3=q-^{~jpm(jm_|9QFH|y_+|!%VhE6`p5rRuq7|VWG97zz37acf9Sz@ zPUM^o)${X306Kv9gC5z?ZovAi(sN$#NZ4<(!UrR$bk*Yi;*tT+I3X7l^s{Osop`R6 z$TThJMBryf7!#dH&P#Q-YMn(%H+7eb&pCshTn+o@-d+!l?%p-}-+Z1A7TCGl)q5iE zyM=W19qfkv_y+Es1iueC%}X34c0Hy~Je`Qn(-pVa*@zt*OJD~gN;JNu-o%y)r`fcn z=9hDGAg2w591r(ssspx|91q$<_js^Q(Zw3AH?o46HYGt+~tk zd`%SLID{-RcUIuK-g9GO`1n{^Y{jrndNOMzcrx)2A-i(%&EZ~9#w%?~^MD>gZJt4j zJp85}Yyl`DEgSu=yEFuR2tf|UU`e-~wuI5p02$$RK9D`+vIUe0Pt}jT>L@{?^6)ym zpxZ{sUEkzBtLTY*U=Aem?BjVVfh7752qF;;%^mHDEG%s(DmN#Fw&w1^Le~{WI_gg1 zM34HqRaqMVONvjA!5ndeV~(9*KzGNgCJa2LW^4$H);Qp`&d-KkKoBOd20K7v)|nMB zt>4hsm20H8d|e~AgyGXlx++SdB9)c_RwV0K(aPy?VM*LXzQPU+o(pqP_zvg#6;|k? zy##A*Sc-apC_6mtmyMY*0><+x=fJ`O++2{nndQ*7ChfzQsp{!)>*%U*biHIqO;+2b z@=E&1^uw_WHw(>Vfic3nE}?fSla;koMQdTV!Q$!T%&_#>Ia1JykoiyYz8J;B_fK=@ z*59I;hV#!w(erGyE-AEDoh%Dd>Q7h8(dq75J`kn3x!tAb110H?SA0WG>i}<5Yw9N> z&8KCQ`N?!|i5CkCO@`^n$k~s?i}#Hmy@_E&v!R@3zI@DTQaoPHQ`hB=S6)SO_zEh) z>5(j17@8>h`WIS~2bNBRByC4^_49yuFqckodD3dQ@i5<|B9@2U-nr85X6;#@>vWD> zv3hc~U|Gx6RwGjf@vH{OQx$}8Zlz%D8IYv}4E}u}=O1H334JDOhD{ z`&gUg$;)c`LH^F5uLz`Zpvu80>4>WTS7-yS$8ZrRBfs5YN0)mODXtm`qnh;FH?+3n zt}u3t)lo2svDu3#+v_Mz2rjt*_>YDJ`>qB(oBVX#4XA@!!~~jUy1zP|;D9>{-+6V` z$qV@<@du$xGmU`|{RFqzy<^2OoeQ>Lu!$4WZAL{iS^HIFhW~=^<;F@H$;*)iDCFC*?)tOGF>N zpG{+apfgHwnyr?b?YlMlE?bG5*qeaJiYKEs4%SCQ*l;=V@!5=S8H*1=_Y2P>LTw#a zuvflseCe@k`a1f=9IwKE{G8K|MC%a^rQ?OR6Z7*&6Kjd*4XZ}PJ~J&dFu}1s7}lH} zV@V4zg2rO~ZM z0bX8NR%I2pq|+~ZT~2}?p;tLERBuxLTC^{N*_TG~Esc+u=_NGh?fZpG zM;~~F4o+tSRQ{2(6qSo%gS$ZTuOibtw1hs1QC=E&A$}9I8jW3BY9{|?Od`k`%*P03 z*GR7cYxaPqm(P9GC{_IM^NN)|UldNem|$JMEOktp{q{2J$s41L^O+O) zqO_^z8xhFiIQF#mLd24sgC^S@OFc-9Z29DkEoV0k;!jgIA3+8P;ywdNUf_&DLi zY8WeM4FbQP-Bk5LB79lMcWS54-#7dX9#d zZdZvFG@tw-)Y%^B=s3x{pFZnViCiC8JYQZ$Uy2mD!z4Vqgw0H><27yIrj#*ehzKYN zV>WcUw(lBPzcRDok7tZ$uX zP+zJ}S_Q7uSjL1=S4_RCi=n_+zx=12*Ui&0IbzDD6$2A&BmNA3%Acs{A@1T3u)6#; zh1TNiiZtBE0rpSNNA@@IVPJKbWd8X1CVG$DWQRnUv~#tm-vUE~R6yun`#DLc5o-hE z9HOl{#D#okz;zohH3Yt|ctB+hSZIQz15NiW=xlGwICp&S%Zq{0^(3CPt8fk1D0;tK zycd$ub+4BvQ47xe>heWa|DXq9Ii>g|D0LuebJWjj>E#LwqGyxMRcO=vpmi zOX97|HY{~Zq($Y+(A+M{;V#>Q53)VT9qBkh#qFd@%>wT1O|itHeV)qMw*MIz@Qr&O zcg4&m9?a}6_2#~Tj-`R#9ku~*W73aegYcIr6K~FzpLhk@eftDBpyvPwbe&4Py0@g8 zX@WJ&jW#m(-RHvNENxq@Cyjr)^w!OShLyCb{|8%N8P|05_C1&|k(h)uOh6H(y9bC! zg9Xyv-5mo2L|T!S4ukFvi6J4SFc>kK(J^4;p83C@=f(Zp_vd`}65gEOIp?~**Ejyx zy{Y{#*Sdc35alzu8@OqjNm%arz2__u%vGH$h6_{y8r^FW{hSr6iH#4PKj;>!ZQCx| z8I|JF6O?J(L_^2pxi9_w;a^7|JFDZOZ38kYkWJ<#NP0m&lcod2C*_0#*aAmwZ7m3h zvvwk*1Q6d6xk^W18*9wy@S{OR$7u3vddD4=QG!UtG+nt~CAwh1+(dsj_{t#j<*?aj zY`J#ea}NawS)xoA%pC)tZNm>m>faL64%Zs@6e^3k?!I?q?XT0_tjXstPmp-_gY?0X zHo-s-em6~MGYKz)rpnaEzx#>XS&3#=2M=do z;Ih9U(r16xdw;E|#ro8%tzW7wwULa-B>O!zcOzFM2?c#!3WJA6*^+Ti>+qkZF;Rc5 zUZv`wiyVSSbVi9&3i#X&;YJUATr(^Df za-QN>vFTIDA+iS)5ID) zS2wtwc9J^lw>tanTe^$$CEs6tdsoy#!$#8n{OKbofdcFN3mRsMBAr{spl&vQE=pp7 zt6K{4k=idPY2ZoG>=Hr!XLnQf!rnni-hAqK^{vJGhs()G`5A7c9(P$5yx0C&?NyQ1 zN6{ar#-1Y~qwS+m$C298g<-ILyFUd9gliYZ0qPYID5U&!xjCcVlK^$+h#;R02|cxx zKIIr6JG{wA$hQBTbvESXl=IT@ zmMHmoXNcyP#%unV2ck}@W`$_c&As^*^w)(Js{pocQK13Z;1lu##sO}+TDPQ=jnk`2 z<3_RI)gFjyo9efxBl7=ab$zo&4TiP~U2nyjWV^JXZ61pd#lNNxZG0U@-vG~bL-dFk z+DX;Mb;FBBy_B;{ z9Hfv-K%f;PFOwOg*vux!5OKN|ggLJ`G5B<}rgxQ`VLb^OJxUB_gsK^ip%g58Tsk%F z63F$j*Qd_ZP*{RJ3ZI}pfJdt-a;gE0@CTG!+P5HgAI+*3;hjLHWW3aaN+RGL7XUNT z%VJIMo{CtG1d`TGN+$XIpY9XUwpGDi-G?z9&MB8xau$t03=#ZvPf<)xX&t%O;k^6Q z&M}3t6j=oRVlclQkgS{44F66R7%UIYN_@5E8^Jo*8ZeKCR@S#grvlHkN_wI%7M+Uj z)KqvF6)jtois}CPI-xO6eD1e-?=K}m+B%zywg=mznuo=H-X|pUnO|SKYdPV?I;wPJv&AA=YOo> zk6~G^9g^)Ae=D_eFLH6U8BX@YEIoVl$&CiW@tw!3`S;LlzGhdQ8m>YH1<0s$2aRe2 z$i~&Q5r>H!?~m)_L?zObh6lfdd}A`+g%2Hn;CzSG5r6&Q<8r*lQ1ppMa*z1-$z0>s z9?bA$c8jU+qCkvOnGbfGJlmg^I4?rD(}M4N^hVvW0Co(u(qA*&a|JLxMsaNG+5v*C z5CEre%b{ag%t0cLPj_+{IFTGioDh14Z=IC9irMF&hj@=(Ly^Mp6bQ+xLl7wwUry06 zN?|g7P<8H35*}zMP6Jp15155TR-TT^_&xgV^*}36`!-dd4QwxBrKq5%UqRG`WxLRp zH2?t0Gfjj_LD}9l+;p+fm21E93KEFt*rxBQ292Kb+RDP8lHNBBKTn8Fy}nQ3=Y%bc zBt`-Aloo1=vrkZL8?^ox4dxKOS);n3^CQr=!JvdYLWSIKE1EWwSljA-EwtZ6za&*M z{;D?)XGwiz#b!EYRj)c?KA+R881mTbSw68YXnBGQ9rRV7waKwmcR@wMLDV`*IzTh} z{sU^wh*2)ih2fjz-yZ_h-?S`$;GP&Vy*avNl5dmuKbF}#S=G@s5Q3G*bK(|?Y*ksa zIYr8`6Umztb7%rzt1Ae%j&Av@?!MWX=ZNuJ<|XIKbE$Q1Vb5_IMj|*nub%LAn%C^L zEu1AF!Q6~b&!eIz2}j6%;uB3}^Y6r+UZ(!~_3OujdVQwadm>z{u)xAiKvyl9OS z!TE#WB;>GlddAMfvQ_ zGAMlO>bq80wY;*1+jcuc+qffv9j#e%ZV`k|84*Bh5>S! zAxosD@5K{MJWu}m+F#9645)*Gu^KC(x<3y9^IT1K2-agN@*UaV{BYbbC_Qv|($mB* zhfJ7z9l|p>nsgFSU8PIR(w>Yi(|MnbXz)g!odA!WFVFTD{Z|K)aWuQM&cowws@;Tn zqc%gRgY+ST)a5(J!gdEqyJ`H+KNTrhkfrsSKIF_W7el<^FbF&|K`S#P?S0@pDdKeq zMu5uEb*X)H26suNmJ*Fc*mCWrG+EF61nxw4fSDygBOpV&ZBWJp-=kz_bEMCO`$frT z-ka($ey1#u3l4$dtUCcE#QD4ATY_3)hc~qTSC%~;JU!0piIWcLp5FPgf#ojR22F0= znXPw4#$#~JQ)EsfB8n4W+vZ}zAR1VUwu6W zEj(@Kje!^nbK}02wWbt|8gX9z7+8f^`L|9SEm4~MYQK)!u3Y51;5DF8B#1(O)FbYF zN1W$JkYK-_{IJ3W8NIK=_bh=`WHKW+-!*eADL?Y}x>JhKslB8hY^AWC71}+sqi%=F zffRrtRoX+D6r&yo0OW8x>!Qb)bLAqP7o8nXqAYwb`zg0&RosBW8B`Jen;}{VAFbG9>pa5-2pzYVnsmr%taJ0pAYIT*gW(RMbB%v$R~3x^*=~DJ z75>mIZ^MqU&F9ql^4Aa!-yU<=SEtgX8>gnREvWK%>Z&c3nxM&|uGsQ*&4Qe+Iol=X zUB=v|D^ILs^6AdZCyLsS#KJQQD{#$x6|xRDQ1g<4_;)d6ID#W9x9`Z*MLpnp+cbCK zAWHmCBiEYO-_HMK9|_ujN?tFuE_`0VSIJxcNg#$OxL)?lKzY_zaME@+;l`C@}tXNXVt}&KlQT+^L1~NrDY2;YL?@j{yw@<$#KiVMgGM_Ws1Os zIl9MQyNj4t36kumMnPe4Bd|fIj11h736m@;@4G0og8&BsJ#q^1eFrQUk>G8|3*75C z-^BY&H?j=PunGZ*D1z>Wv(x4RtS!2Ot5k|CqxNo*#z$xq5HRd?t-$1K$szZVwH@Fj zZQ9F2h3JIEiG~(}fpq1UK!(+7@;yQ_cND}QNj=;DfqWCtV%^D;sz)q2G)8!8eTgjv z08{*7`%UeM3Ec0PI(E(Ipze6uPN7%e#2wyqX~&*^Qa&pG1-|(l5pSb3DdRFf0atnI z&%T`0o+N8cLhe|u9~N9)NngvTpJ+aelA@rSzxyKajZ*r2>!X^C)VQd0&DZzKncF05 zW=!5Ia=b4OJ+66`J5mz3*Sz7NGaNW6oO?dpz`o%5syZ|5|6i9T#0idOxNq9QPuh<@ zS86ma1UPDS*)xWo*$RV)F8jAeo|!p$@r;;u)hUf(ZF9!5v~E?i;Dm7~_*C;@l8K81 z)x&iNVxIs{W?30#$;{H-Vi38=$0Aal4J)QB>3z@*MJz46Xzq{ZI&rb2d>(iTgQu|A z#Y2{Ce|8yI>V3O>4%a!MF`33X{tMLbl3hmUIAMcrFfx*24=Ob*&FFHjkqIicw6`6Sx_+_0gvuw32KzzH zT-qVf11Mn1JD0YqP5Ul#{nPfR#>Ixq-uE&MgP%0!B_#7QqKzJt&WlV|4-tcLJBs?) zsv!Hu-iNFg35lO5^OU9==EX<6oMu>+m^{LHL;#Ymm^8bUUDdf9uiyR~UpgHQ3nDl0 zt%j+qxYYer-{gicu5d1Te?C%CgWsjmr$tlj2ko?MhxBCRgU+j=I(3)(oy)?0L z1&ASZV7)McSBv&C97LW)^8$b|8mI#Af3hA2HFlh{_mPD2+O1no5;0y1fu#p#JI8!* zsA__`-nHxFn{;hRRx$6zn5Lpnp^J7v6aHOn4?3?I|7)8Y{HwYUKH+jt^{F8t?`C)( zQHF*3Twu&+;)LyQTgnM&PksZ5NYE>QG33^OvKP~zXLH0l^jn`snfbE(BPr-M1%Aj) zb}*SCLe1fJJwD<)S7)VAO>WDDPk3}~dV>4af7{W2`|D;p)a5;GoMv*FIb9Tbg(=O- z97wde`l5VXn}~~6eqPt2RGH_}j~kY25{y~?<+Ku7ew28Rueqi0r5rGnKvNQzhwagZ zdt$bV?s4muG#{^*ur%&r@gD-?w6p5U2=Zfv{hN%^e61=}ofD0;Og#)NQ2I`Jk%)Xd z_RkjVb(uM<4RWmNmkc?J*bcIEtjm6P(Gz+Br~!y|FBw7%`I2rsyKiT%OA3p%g$_v2 z8UPG?9k|X1R_LHUWF>5d{e+7b*hc7~G!&cgt#^d(a+pwooGRrP2OnISYH54}Hue!y z699_uonYVpMAaTLe{{d6hZ6-PNnmkpP+q)w9DrSI%vLE6%}v3ECqnk4{U5S0OPK{u zS$B5L93=AUR$||kjM~WN?{?@VPF*ft*Rh^<=Sy=Q+Y$Jo5PIc}oY$-G%^VyYMu_m5 z+H7HwI;*qZJul4rryq&p5SuiSca3kcRSiaxJ}A-S^24I??P{qAE%ckKlex!v?$L1G zVD}E6Ex}*B&-&9)dwe0O)G1$^A2lXoRA+khF|A0v^L(U*zS1sOr|5sQ5~z-kVaC$F zC5=YzN*80%&?4pA0=$C)&RovVB?rI;Z2EaD+>@IuE?#(f$D%$t^^-TIQpezDq?XIP z1#Ug71sPv3O_{B3i62X3b3*gVA^DRZs_&^D=U!(uIxT#kHrXE&=JDN{D&vnCk=0K> zctF(SgsJ@`<16yzUB>S#3VeqsADg3C!*kr+L*AZ3bjD1nk0FB!>?B!++0GqH!-+<# z5ed%Ai$rWzfNe@(gUs6ozF>gy{v(-{oI-YBN&up8-{`(N&lrJULeGtTexRpJacK3Z z%w5!d0@a5Zy=xCL*P(|}E(RaBBW~?kJbmoSP>lrILdAA^%C9GFfk~ifI5B33tw*7O zWqTPSb>!j5v2aLXeW1yWd`fxI zTl(#EUya*sMl$jFUA1V~hbNbukCdqDifuE7aBBh(17UC!{B&lRa5jM~-NW|F z`F*X>1KhR3Y^Q5y^wO8FG9RP#mRa)cS=6;4p=ZP^kxDU%@taG{7x^Onz^~Db71{$D zF5%@q3s?Rx^_C<*PUb!S-Gz=9&?;IRik@6nik(;pz6^2OKHZgR@__gD_SQRe7RR6G z#4>$-Sq0chol>I2I&A{#FykOM=RnLj5zqe5+jTVp=D|*%JKDI{3qgnqL+n6}f-p-U z%y2bboUnIQvS=m3q(4%&E9k7+*0io6QueZON5i97>sML;lXR2i$&I}93W`N~C5(mu z178K2u$o!%D94fo<;rL^pi>_k#x#O*2qUgje3HW`$g`ZF3Opck1}E$)A^-V9KHWf} z^aL6C&d`Viu$7%;m=i>Q`;~yZQ|i5MyhSNBQ;!X?2wQG)Wd3ZGR*RW!A)?5APxa{p^-=~Cb z?LD_rdZ={T<^%PD3%8l#5)$u{%{alY=T40$T8B&M`i}c;rrQD?PH;T3U$;j(FV`^z zbRAr*7}3INwK9gU3=-uGdDx>K$c;cSTHRD_F1EuNDJI(+=qu%SU`AuD^`q9I4F3(7 z-52hvoVdE}F{N)?Ov>LTVC*wvjh;~F*ZlFeGGTdGp4d3zJ3|wlH43k0tG=`TkKQ}` zO-E2RPkvCMLzzJO%gVmM9$T~1@hSU)#jVFOzvPa=u>ytZlf!a%EGC#s_5&2gKI$F> zu&D~ix_V_v4iDDqs#$2YEJba11g;K@8xQt0^+QZQFMIpZne`qk&{3%|%3S2sPAF3t z%ou2`0_b#j@YC0WO<+mwEsSA<-Bf@1<|w%x z*vaB4Hy?~GOF2{}b1}X$OVd-6UY|6W_COd{>{7Of+Qx?@Lj&i}w||>d*>y{P3VbxF z&|br58!0N#g83yBnU+m|vp!T^5n})QPqr5}opm;F_?!KkUD=eH1R5rZ{I$dY&Uj8! zfAN|%L?N-#U26Y%a`#~X71v_PpX5#KzMIq+QODn^uKq*&#T(GG=6d?cUmClA$g*$9 z4q*GEA*Z!y*?oWQmaXqUFnKb@(r?mZmR)dP_c5)Nw2t`0PRs=?gE36-Lp@JmTJ7@p zW!D~S$3#a$HxkX&zjU1nL<6J@7tu+GQ?X5lf&@?SL z2DWhSDXL@Nzb7Iy8u>$B5;#yxa{ag7@@DKx#JAf>)w($SQP-fFzbXA&6)_vm%mE&g z8WkecT3g@Ax-+t(^)vFL2;B@{Sf#z?H|Qq@{DfTM&YfoM1B$gDX~<&&RZU7loJ1{Rl`0u}?qXSgl@ydLZ1K#<~gM z_=~A-Vbcv}z2gHj6&T zD2*Imb}6g?Dnduu@=Y)wKAu^ahLF722~{(({;sCVcK;ox#k`K7&&w^19+HPw zKha(#F~6UoGL3$pi#g3DRrn3vL+g)T5b?%7F#gXa9zw%_z1on=H7NuUhHOYL#t;rC z{#35l|5sX#4gr)hlwxU1O69KcabWuaZzp&s&)U9xrJN?40}AE66)DvYTBb(dk)^}? zF_!xqRW^`X4!61Vr4=cv(7}T5EPePr-~gWYWodewwV@dTB#Xh&{-}+AFWAf_j9q~z(arN_9~%+0=} zTY`wUV{Fq*)SK)QBH{?bTq*1J?xj09e_$lD(6HZ~^+unC?4M24=3ei#2$iVVD>)Kt zy(Q+FG6^|fL;qtCZ-l{0heGQAbxCfcqW~iM3d+%-b>aPU>DOoYtG-L9>&YMOkfTp% z%?rK`6IV5ht3Xcp;-8oOx@8mJB{YpKG+0AZL>mQ#-<$A%Hc%3G<#Mqd?EJ1KhC8b= zUG=zOYgnhJ33~qKAVW&9-uHNYWO{^!u|)@ro-fyFHcfWc?E`4Ar_VLP0P%#xUf~5* zgcK0BSPG_~(G8Ts5cQ|HA52nX#`IyXZL1M0)U0MW)aE1{RDOMbTk?TOLr7Jb_fkY5 z=q^|YQ4h`+^!4HyxbG@^;e?Te*Flh!++8&XyR{fwXuv5!g7GDt6urDz#O(*M&!m2# z9!$oRDD{Ih>;eV8cwu4Q9+jmV5ukZ|S^SCavXNzT3}ya_FzPxhN`oP7H>HK|9U{?s z3>f{J7zJ17L9OIYdBI=Kx*2UMfYeOK+B@-ye6p+6`+7TYuj=sVwp}?VKR@ivlYz1c zXSG7^{(jc3*OTxQ;)iNn@|9{V^H{}pqK%e6->7)Sw;)n}P2I)lf$?dgr0CP<_geEO z@7@C6vE=TSnVb!YwbI(#a;1-&dNf9FCTH`lR-CFC@1CKkL6du&z0n(7%3_1R!2giV z6_eR@eagA`Q(97qr+o>kw5a!g_rOory4r<+8ma$asa`P0*&s`qw2rV_l#o2-`oD0V zNooAN_Mc{ip>PwT)Obz%%c1#4r?2}}yA^t(PQwt!N%WHb!wdoIhnK;*xW0hbRA=`E zaqk5~$})d>Ca1Srd25OUd?>W!u)hh9v>(NbE~Vk{M{i&=&3O46WMD*RHXY19>?~6^ zoSP@8<4!ssNxzv&$tY32_Lv?);e~X|za@Q;?Cairs;LaB;+y0fDzG(8Ri?eqw<@!U zcO5sFv_BT9fEQ{TZJIMl!-5r*!P3M#1-5QHC55CA!_(2AzwWo4Efs$UjFm@_18@hR zJA+naugvf>lxM$FWgt#N(la};U}m9fP!DKcs8~j9j3DU6V=3%vciQ!goX6Ohw_^?| z|5Sc`@X&i25jdDd5*{784-~oNUQ6N^zwDATV{MtLC6ifYmh&1?s12P=0x55VFk+2b zywDSZ#sqJj5mie!p3&<(%!{&=odz2Y%|%vHd@gUyp4D+k5VLj!YK#VUNnFKY9snnE zn@q%CL%l_!?f3NJw6W8eRM1y2O{d)kwU9}`x_tTlZTl0v!{L5g6&vyg|x@PaF{O-aAs<^5fR(nKRm}^w}6aCP$crl83mgf%r;qAA| z7rOL1$o_4Q)wiq0jI6pMdela)nZm|aOUke9~Ta1pwy}LIKLr2zgqlw4!^D&j|H5Md5idyYW8ahG96r1h0_rQUbN{ zx<4N)eoT_+>_y&hzhs`({D9XC8d44(l?d9t<9r+ux(p~IO#pl46Ev94_Jpc}yA>{1 zSm^7TR0QOo&SYyq-W}clWHoe~oH-Mm<~Br>c{`CZTuIyHv+YB9Yc?%Nt0)@Ah)^6G}!6Sn-{WNu7!Law&-}G!k!+Etb|RmaM{4K!SLRK0-neEu|te>&zH_))(gIV z>hsf$bFp6zM-Z}!M622jnBTtjqUZX1fVUBolg(7`ojT#iY0-Cg@tJF z4GKI#YKr|Ccg9ncQNQM)a;q9~P-V!(shNrpD#+xz;o` z(97ysBdH+KOBH{gWMhF%+jJMk;Q5{uQqjk}CT(9Ik73p!JFkyD-8C40-U<^tPFzY} z>J_#=-O0FGN?<|yZda2uqT%`FRA!=NKg?d3|L$X^QmWL;ADts%{lp*!2xzi*vzw~s z^%_&6%}d;^am@%}v8XXMxMH8w_&O_5ZQF`fp7I#W*dHg{lwy4ZV)Rt;VvexRD^!nV zHe=e87BjdCggO}Jq|HJ+5Ou5`#i1AL`72Ofl-Lj;B>)L>7pb0$Vs^n@<*{+G^rzZwfZ*f2j6E=cn9-X< z=+D~hvG;JXxu42u&>QETn_)U`E4%!R1et)-#ZLa6jf+T9I;h{Tj}N&w$jHlQ{ko$- zDwYd)Kl9SMpAUa5CunXtEbDSCSyM9Z3Fh4Mj2pE8d&o+^Z7KZDOIs7dM9q1owWAtc z=OhHXO=*|U=9QJoG$8VYwbg3mecH1>e@t7f>;rC&b93>o*VDz`+fyfTI{z9fl$JHD zkt29rI5)<$)wKm(a`Amj`g7m+H^OfFBVAYL$CH+jO9aL?Q@~}e>2RU7(0df$c1MxD zXllB-*q;1HqrohJLnT;b-11lq8haLK?FqN`h3`NAf%GcOEt#}TE2AjwNk^gqeB{{jQ!A?Zss0|D9 z-s3BdXyZ%4#Zz^rUdPrJ#*%1zongrqw@Rq*NM`lJp;T9yAnc3TR^P;TQC2VV(N7rY z)aq8lE{>C){65U9Y-0bw7N!^tj#|1)%)v+#@jYB)6X7k^|G+uutcmqth^m+8TS-{> z1Y(fJNwuLq-j4%05Lry6%Jomi%9``5KRHo;F zj{4Z{24)KAKV4IU@T?pVN^g2z(5;H2|1UKzep;HN-lpa%+Ok8L#eSTfR2 zrG?o_B5(KKzjm*RpqX^4#>!LHuAHhWL9!$8;nnI#!2(8Bu>S7HYx(1)CpL0*G^|*8 zHpN_WCIqoE=n|$)wh54e&S-eHVUqbzx(PM`7ubzwM80GQ`6&E=#4NGH9}^P`#pel+ zM{6yHpqQA`cDpS@$E!nw!fT!Xd`OSB6A%&ewc8s!VVagT`yInp{=q(Hk(~fQL>BJ; zYq;rh`3wC7@>4=dVf5#%sBf<{`BJOuUc5Y3x|?eE7$p);6LPvUzWYM{KqvryqxIJ5 z@gcdD;}KFwgL^g#PS=lzSq?D$;vIt5zW1zY`SoAj)~Wk<7bMugOxJE!q;;AxE%>-&by@x6!h+xa z!g-6YC{XSv8QyG9&e(sgv!oIKkI-3fjqk0TUXd1k$(hAYPn0fbLEbc`SX{{pwaM`Q z*;QJ@$_4pJbxovV!|J#FZ^F;*7mP{-YDeX+EbjK7rDs(-{z9a)3JQp9Af|Yc6;nt9 zd_@a)j~DOYJzW==;9Abibqs)W2r>eU-BiS@3Y?yb~D(XEZB|)OxpHv0`{7} z`U}sTDUCyC|Il6>R7JADpk2)1&`tUsAfwbko^t0Lq%v}pn++Tgy5#9&;;RBp|{gi^m@VV8?+%SZa`6)wZ*d5IT@dD z;>1^kz}vH7cGGBE50VtYRiP`ViE-Eb)HjwR-_v#QQ~JFR57krgBSHmPZkty`v>D60 zD&{q+-aG~C)q~{qsR*u~chq&!50s0g=-dU&2Kq6#JY{XyUno8Z8ZUf& zIPAi;WmPuq`lk?m6d)v9ma?}TUvWp}$EcKaJEoZbuVRpCdJ5Anf7^d{-1iCaNdxnr zjUW#Xp+ip4!XL5Xzi#!nJxgo!KjU8mcLtPniUk?BHXV9;Yi&qWa`JonfBhXFFer4v zV~E$?aZwq1M7e}K`7�y})#GS~VjpDm0$rH?^rhpjT;XuyeHe5~kk~^sMj{y%OrU zE=2m8I-bPqi`k7}k-J|9bhY{E*CDgAAaqt}iX7#}Ujfn+pDCcBOUJp5ZqJT`ZcmUh z1$He>vXB&9m!mjQBT7-2^z=nZGal&_YHIozG3v7a6iOa|(*m z2SEYg`{nETV7~-ghEoFBeNwN>=qOgHV!7@2bNx|qXWi}+E(VwwxaX^(4}ba{CGk}n z1pa#V-EU%vhB>CH&Yj*o!e4w~LXyb!Ug_?HPMI#|l%xCpazl8)J%T(J8cB31f-mrH zs#H4{8&%4A;m)`NH73lGc~2x5*(>K2l=WO4(n1P(3Us3D@Gg&bJ|5CbD#nL%OR&nN zG^TV%?cT>bTO{!Cwvp2En_S70e|R44r#l=)PWO1Tc_je(D<$WWiik#4x!Qp7eD+a{ zP2w@pOI<4uhpi?~y(eE^bd3|e8FZ#f;tGRwF{FG!jiKclx!iwHg_$#Q9d0V!%@DtK zeJh2dGE<){7A`JTjDKiB)nnjB=TL<$dL;Y!KeV}jc5u|LzF+_QH1j{tMu|`t81EHv zF+JJ3a8iL8#a)da0+oB033gS1t=ET@@SnccS1hio6L%z7?ajF0IGPviMkBz2zqhk9 zB1~SDvkxz)t7i{hacBcYzhy|v#iPqL7{?&^G{S9Bcxg!+<9h264(L2gH*&23t7|)nIn%7%&6|>pl&(2F;Lx zN&em0j%)oys97i$R9a8I-IN%LpZjkyHikM?5x87B&vah18P1l-3CI%?-y7@=|McR~ z$EKlMfpQwciq zD9-ZxF`gq!?SYc=?2z!`e?LL6gKAg{6X~7tJ6*hEkS|a`sMB)rlXOzu>?I&o#suP5+`L zhh>2JMBAqXoaGhEsKCG^#VfYR|Mf(X7#*LRrI8(KZ;~pGRHVcZpp)41s z%@-hOVaBeh+l5vSj=m$ZnoK5^=*+IW7+9ff)V1f%bkT`Lovx^)Jmd z1ouR&H|GXF4^UF3s4|8V#y6F380n23A%y*M$EuC}P-9~RwzVu*1lAB{E@!Jjz?JnQ z;(OUv6nA1CCTl@OEcO#~L;T0Ot0UULMJGY1s%>DROdruUnvg;V-n(2`mf%W2;?=lt@>IJ;CMzogJ#-k;oMX5FCiTRc2bTQyqF) z()k7%QTN2A{0<+tZn=S|l$cn+QnFrgjdWRG{mZ8mRVQP{ujvWrEP6j=e)SkWh-0W| zfBivVP^tr>(m?P*EewS9I`d+l{`y>uU;bUI$*F(E+Wcb&cM}u81 z)=Y2KFffoz-a>`Q>w+ic|NqJ4vxRMZ!&CM}(f{t%$wzYMzr0rB#YKOZ4Z6EZmhc|t zT(%0`?CKLppJM~_O>|_1q?kQ+5!voI)qdRhIl!|n+$JmsTsW;G-s<-KI8kt(P}DRL zJhpY=h#%`YR`zcxFd!i+GTmlLZrl>r+E!$iT&FQDV++A&?w5 z*LJKOz@4@CNHys;m(T9&R4Kn<()aEziQo3hV9A+l zE9ln-O4uL1IsCGg*%gZ#f#GF6) zh<&*f`Vy3k#OM0(${u`FpJ=|=`@gTRz-wZ9isk`1)1xqjd8ta7Oh>cqFL^U6PgX4fOLl+=BIXnYb{E z98|O!j|Xg9Z;6$&GESeQf-n=d3-GIWcA)1&w=Kts<9*QM&f-pl4lFw|Q!B=AxB6nB+eGD-VCVo9Mc|_lq@)-R7WJH;k2`Z_UH6S1D zDw{v)qzEjFEhESz;9y8k21)N{;IvDJ7~RGU+!tO3HVY z+<_r;oE1G~P)-03KE!6b2fAQsX^GT}&FEVr16gkS#g2Nywu3~6V_HRq!#wLCuu1uMCT#tixa5MQd) z83+Q&Lj3#0Ts`p^hkjYOCCtwhP=O4^lWQ~ab4#2Uao>M%xqovLu0hOxXNa>#F1Gj4 zge~+qqt0{Vw;$=0Ew%N3dr~DG-P7!t2Wpwit<&S)YaJ_XFed!(2=^-L{=9k~% zaN}#ds&yK|%ZJ2*-DXsI%@ONtR}y!RiAA5?3aqf>#!p@)19Dap3|%i5@I-Qw5e`nw z^C`%O;P{5?SpZr7&-DT#-r(bR4N8a>wpUj6O_ozOtd%%fUXIJ<~n zT>=P)tq$E65;_G@Wz1IRPcH*l69O%|J+ZxuxWepjiQtUA1+W zE_$(H2}vv=b$v;49S2mP5K6i_dcXy~+qA-uhTeuUs7aoFh6)k8UFnTN$4jfaVpa%A zK@&dRyNg~1J5Bc(C4%$f`Xo++J}dlwgx6(^-n4d^x2D$4-%MQs=WFnkRMWFvcT;xUN)|m(!)Kp!T2AsY{k0 zh-`G#b`3-#C;q-Q{c?lUzJ3$1_QT{}fEA;AGNLngG`_ID1$6!AHx%tRa^&K(>F z%XFO*cV5f%UjOdGTXroLHFjR_zFcj6#%dFb*6I7hb}7h!S|5$ z3@Fdh2tR+)iN)*r#p24K6K?caw<2ql*yTJ^A@S6<&zeC%L|t%H{TIq zfzK=>2RFIELw`ly{eo@cr5~M1h93)H9L6Oi05OkAMYHHp8Ju22kiJaq;bwASqZ6LD zs5@fWk5AeVuGs$iw#SS_*lBptF5?m5aN6c%<}|a2EdQsaGcZ?y-@_~{jo1j= z&^o7*rOA-UhJfCc;Jn?$tPXTBR6BRR=&t~sgZf?n^(>a_t(<$Z3dK#L_o@8X#TTB_&*}1o1mt!Ytb56?i*gOyW=yJXEJ5L-8@grRU35KTg z^{h#J9XgA_hs2qp8Akn{U(Dn8_fmr`>aTp6$2kHHr6qzbV&fN8{tbFdS+eoSRCF@O z2c5DI|MMZ!9KM##f~L+~Ma@Sz) zcE!ka!#6t|m7Vwm6Up7Vmc5qk34DQN+W(|5_Z3@`=fRs5rq%3e#6RMg&F`*!#b_NY zF61H_Z@7qPaGLQ*WWv=1czvt5Ci`K_9NI|ce%X)n^p>V}l_p%azMskQuE{%mMNXEz zQ2~9P8n;$j(YX}0&mtRO)!5Nl6!i$DDtPr#g{}|Mq-+V0ska=rW^Yc$%hW}C5b{#CC3mup2h$YZMBH90(rS8UlkmNgjuYAcpmtU4m@ zY^WLqk*-51P~>z6Pwqhcoj$wMH{vFUo6rrah8J)#v*tObyiTp3fXF{l6A-3fL=s%6 z+K2PMPP?ChW?qd-)C{JY?EbaO%|Sg-^I24LW~&g2V` zE_+4b^l7S(_6~bPWSl$3xwR->la; zhEn+#y^LJ7muGkhKIBZlbWAp*k4GlfJ&DRnP%|h?N&ZP0+^u1+YF-v(K1w8_^&srT zML0YwOp0XmtEAh3{Hf)0tWqKAj8@td`h=tjxoGfca;Q`GH)fH_BbYZr;|5oOa-=w9r$5S;MX;@I_1l0wjqZ92*-tX!d zMD__{@ekl%%+}jZsf0d}Zndv?VpLT_^b0%thIq!0cn9DxIvrPC@usS28Fs z!jPEJXUw-6z$&yb2o)+0-5zhTW3P+H_ojG_kzKVtEAI^eu78?*l6aI5nJNjnkv&s{ z2O-_(4%9*q@Nfu#;gUT320;NujW>?e)bk!YlQOJ41(DtkcoBxkHlx}EqO30RF6C<= z2W{FV9JZO-6g%NrTH7N+?2+0#RWGf&pz=c`BTc!Qwq zy`l5JY{iSyKg1vQpOV2ZBKd>N|A}lu)gsedEgw0st?vzZoV*lvJF|u5uP3lHmh9u% zOK99x?r1U28xMQ)wrGC*xs&ptE-qYq11rpw)sb83c{QYVu+}pql0P5^4|tIK_R9*1 zS!K<%$JMBjdGbP4@4NfYdo!6fZIN6JR^;g0F;6LU-w9hqH~AgDD(5sB1O}XawF{P2 zqL&p2dD`fE*s|q3%OxftcQi-&sU%FXRP>7&DB~na^hHxOj~Ky4NU^Ah*LXMI@e_kf z+cPye)PWPV{{2&}a_f0to>Z{;^`S=c@+_ z#V29U;2`GW2|Ae|d0?C($nLQe3Sy$#ND*`;t(%{3DYpsZu+7tsE2d}@3b{BK=Lm5V z+wrGnjHEtJjg(LmR?085@x$+*!GgM(CCe=0E@;f|uZ!}=15T@el`mIErfKZ` zf}~7eMW4*2mzYpl*jdScLo>ureQ}yPa;JO?4>pXU{Y>n^!il_Hb2E1j7Zg}#)ck4) z3;7sw1rL#ty%0=P1&3mLEcMq4lwyf(s8H9H_}eHLPmd_M+oJ|@Eh|GUb?{_$$jZY} zAi~#wpx<@g)5rjJx-360{pxBsaMgE0(2BKv8K#ix1q^>Kw^?Np!G~+|vUVOzcYgBo z+m*&EvoK$kWxtD}ukA?4Cpt|Bs$CQ6O&6{+mhpD)3UaJRe$waFva^#q@7XApfrD+I zJS`$d2MdHALA8406S^^u_3Z1SEusnc1o=Y_V<)*!x>kus%1w${I+7B^JDgWe4LT~L zw-N!3X(fY|3=+HyC|&KG(&9ktiGM{J=6nQF2c+c{l!l$b0W@xZXGH z8xus7Ng|2pErMtvqK+9QAw-u%)FFEG-VH%SCq#)JEeV3?qJ|ivjo!_uqxWGj<(_silRxYxS>-FuH^t-bfP&+9yo<8y>9AB8l-^Emc<_>LqKU`Bls62CR>QtIGjVx)XG zJR(=NtxFCe!zr2ai4@wo>a|q5S7w{Ysm2r4qe4#DKQjS0*hd)_Jo>ee3++S{hZzL^ z*l?QXTgB{&X?OXipNglk`=t9fSaCYT@#$QGn%cMt*9_&^-V(XD;x=+z~2zAy*fCcjFUv_A;P@ zI&$R4d2J|ZU^B^I?q3YG$GX#zaQy3S`04vnGJ4g(3N~+fmt;oi{*^o|`P}0Kc0axa z&}9*H7VX1-QboI$5ka-kq})RoDRqUAf=4%_Ub>+(|*{w$rhmh zpyF-|x$70B3rVC)1@9L(_ByY2C7Hxm7x z1gzR5YG&=V3obTymsK%|ncuvOn)=^##+7RC8TWd`{okPdm4DsBQO8G&t~b93bc8!{ zp5#*(Q~uz8f{ij^ycGO6R}izXA0i!0;(MusB=5>>m)DH?pUI1v zo%Z%q^R=L+4uj`>VwN%(B&MX}U- zeP1b@*X=h4I@*4{_htFdR`+9}xHrutB}3uWp51T%z&y+aZgW$1kjt4yJS_<}M%J{R zVGy)rnUjZG<+nnO3nfSI24(AL9uRh6#C;j`?_NP1Gdr`EPQX!$t-@)k#g~|W zqPXmPfS?}#t1`oR%f^IaF_>WR$~N(n&xUceE0W;H967=KQlh>^9-dd~h?9ptO&!r| zJsiA!Cw`2%@hs-Spm|IhvNrW>f*|-CJ%_0yUV!bR4m31c8dat)Ta3-6Fg+6)w34TW zaq(Y2wChfd;HMV+RksX+`*9_YhQ?E51Cdl?w!MU;WcMR;jwaDJIvB{ z>cQxHF0NpQjmwS^3NY6}&==!j{u>?$GTiH_Ait8i_TAI7YRh1^6Jf`|lbLxqI5;$A zAe4PuxfHtISzjH_794&?t+25d2u*4TJ{Sc`IGs}ODc`QwY|xq8JPXgokGYKQs%J@l zyL-FsO{>LXOH)6V$Fd5lEqqjLxSlol@m`*dyzY{dhw{7zUt$_E90h5Zw%Yuiz2*IidRTsh7o27#v2 zqN*gw&^3KUpBS=+6kDGIv^?z(m465!^}|-qR!h1$h4(&{N5pc~87N)LmqkZ^VSyqM9-PsH4G**W0hAu%1RM z11Oo__=0wrA~VpA?Hr{%D#Vbb$QIlZC&9A&LJfAJ7Vv4?cQkK1Uh9@HS&R%qFe3Xw z_77#cKU^Fy2CC67$GeZ(&Pbd(i)`v!Ykmih8X)Ai?EKhe{QdSk^H^QKz6()Aely&t zo4jSAO|?$~S*<7?WJ7UZ2#%yQ_~gWt%~ViK_&>eiw#sUM(Tf&a6oYlBBMU?$V5KIo zF!wPgoD;`&Z7}QQhhl-@WAmQ#rH>SU`tq6ORL4+{qKRL7@^TgCZ;*zIek%I8)Vcwi zi)j%eJqzFSPHeTsuLBc)9DCOOb=Aw|*EJnsD9DFQQ)g%@(8UO?LP`yVF=1C$QBa3n zr5P^&Dbj*?@*FZ5zBjF*Fegp#zwTCB&nrnrOc)N3EzV519(+=Tz7t4w5~dGi^KUqH zWlh8A`LZEeKlzMUJ$n5fEKV=%r$#NV$Pfd(dWhklpoU+D3NwauP1pHHXBcqUxMS6@zoA;LT4RHa~G4 zTKEMY)4R`l^QurhwF1xk01bd?HaS6g;H7JfxHxw{=gF6Qym~t|^GOt{Ef?An^`SY@ z3lDnO1}XQ_7P(r*zNe{>JyJGz9HYvzaZ+nFRqVTBvOTl^q2br`iiwr#loBYDc#hMr za#J#Hw9s4TwnuL^&#dGCM1mY6bs)NJhU$h%Z^ z!U!k2HOM@W#JF-j&=#GS9CNpE_0S!W+-S+-xW3}agg`8gj^lP&MGw)D@V zV1er||LtZoWC5mP=eW&X`NM2w7m?4|^JUfhPiB8ih|Zm4{xYANbe>yo*5H2Sx-)W2 z2Q1so3)DGt|9J?F;|PS!9#(>n^opwLt{04PDTMS_Z`7Xtz@k4xKoTF<|60c7yO%Ok zt-xXg#3vKo13Av@(EcrzpPzqzl#c!_2}BoPE$o!4uHh6XFnxp*c^>~#hSsr~QKNgj zZ_n&DI8BMDU!H9Rx;MP)nirtWs*88DY6Dy+lcB!y{FaivmX+w0A=R)@dN$^5#V+lp z&Kiaq&Yd@9wFNF@9=#?EbJNOqK3hE*V)eo%+-458D(bchW`rl+@^ek#={G$2L_U6C6A^fuFC?29<&iON7L7VHm% z$Ya(7c1W5};9t4i<{T^QZ*Ux7`_;JaRDwu|A}~FT9@3%v{}w) z^F_c9!K)YF?q?f!O-@0fk14cF82T(?Q2&|V)ukZp$2u$hO7b%#}? z(x?@8)L(dT;?tUt`6eZ-vFVi??Wim9)D`}nI|knJJ#)^>Z;UeirOV32zbWeL!QWXC zjq$OcL~a#mxN%dpuO_}3eh$4D6==a48bOCOT2q!ve_dB`X=0CG>Am(9j$d7Q!DW2I z^(0g^nJuH@de_;4_*xpLpaAOd{(~6TmiVpPAYN5C;{k@5%_xC-*Qvw$<83*TXcoRK z<5}J)QlU)hy*~zI@3+PeEA2P0ZZnGfxfPS>lDJWP19!dm@nhrLIt<@n2OXV~)pk5T z)J#)+9B=n8Dg=6R+wK=#%MBdmxUInK>mdS{ax3Y9fnDr=L#Ad(8tLh?ojgIR74*8; z)7yg_%Q06(HO27PwU~l#FtgU&e9vi8bIZ&5h5fxGn9r`!&aTyIl`=Bf6Foe3?Ki;W zgk7p&o4euO?zp%m-+&gWiC}Hjr1;zPJwP>|40KW3o91PIB4TAOm<|ll0WxFBboB3H`kKW^THje1i5Lg| zL(WN8u&adbpg15+{px}?8FMeEPov2P0}|lD+QQfFBFL?s!yCzhENPpqe&*;eO4K_W zT{!j1iJn!*!^qk;QP|Q%x<-e3E29Q1zz$Fz-r|oZY_4#E8w3`hOeo9X^LUr(s*v55 zrsEz4Hc-)}*;!0{CLIHpo!0&3HzsbF$mqsgkKBZ#`^AXCS_`+O_2)j%nvKV%Q=vbZ zpN+D=6H6gciJwOS*#~~Gz?&T8B29iA&ZH-0S{INHGEs;8HmYCT%Fl9+tktT+Imr%Gkf^GK7=^FpLe-+CI(rjhXZMXxCD|PQjUD6I1gG?g*{eHtcf^; z5GOEa`^pt3xrG|>ungzn65YvLlnc5YqA^E|$6>M#nL%QDbPvjR7n{9VUDZG9>N!`w z;&uL{a!njm^Hk%;-Og(Ak|ql!M~(_w-rml_;h;W`-VEwis9I4P$(t~vrd}f~xq=m+ zmhu$an5@$pUil#Y^Hpsc)3Yy)M3 z^m)o4Kk13AHexW_AwzXr=US=2eS$_ue9L=(iPehuC^k56gwk>!nC=K#NyMl5p{i#R zf=9(L8&<4}>3(GW2;aDR8|b8`=Eh8WZqFwv`R-Q7KXL(xJ~6=vW#uZK%1A9vQa?~ns#&a7C@8RI$1v{Hf^4tm*urz zoFCf>!?Kr)zUH+D;G1;JVg6y=60Kimk&w7{axg1mnN_fdNMDT&kZ9H|yI3EZcu{EJ zlnExNfp@6q;gItpgqV=BVw$T%;% zf##}p_cmsPOsFC->S zCt$xD^)!w5!3u!C;9gUAup8nblA4SK1t4#|azXk~$54X(55qZh|GFt|&Z#+c@v}+x z)6g4T6qj#e52M3ja}NjGHUZP(!&1Bbm(d9h#ZC^iY@x35<%T^F_Y5lJDX9?yVlqwI zJOcm~#ulP{e#AUHGZeQiCb=4$&w{PjzJFrMkr|qcJJE2ga3jwazW#ki+~(>R!dE%% zPMa7p58)Wr+JaC*-oO2H<-QvyZMlg2fo^yLi zuSR#~>yF6v$j zb+S%jU89~`5_`~M3D0}DQcUl5>FqdY1AUVdoS&$$?mf-L(Bb|D5PT^~!h;rb_Q$e^ z#-YOw=qA0LPIVkVuPwk64uAf+f2gbizvPnKAo)b@T|ZGE&o(ud;-9b1>aorXCqq(m zWQGK5OEJ^~(@4(~xIPG#?>YLK1cM`Mo|L9nu#ToAL49cEFHrcPWaZ&Y{i6ceJ289* z8f~0au~0Y#vO1r3s4ZUynME3tsBwZVf|q^6dM-}mwXh_n38AwhQEQd0AMc%aai3$h z>{489D!1KdjYK*a8Xk_#)Fu01XG3Ro>`Tmx@79N^->d|D1F~~r*~;i)b$s5qxkEd;7+MVf zMvxW4O_qNq1`tR~cq{1!x+0}jk0VDe7dMVBH=B?;yPGF6n9I_Ci#bw`fVf@2Ogu78 zUs~f0D2Xi<^w+$p;#uJCyiTD{wm>)}&ie=4S)wSpaY!=;HO72C3@i}+;%LZ_65x{|+Zhqm6^01~#IeJ((g2e#hsKk~4TeN=ITW z89?%4DqMDR$epz6V9)wo<#{4SMd}aKVsnGv6>h)Yu;OGzklUZ^j**lXO;0B;so9Ol z*g9Ol*%50G8d+RlaIaI`wocnJmTeB{YOW*MemPf`>{0@fnEz&EjJ4G4MToAtR}ZeD zZt{hBRC^b!eT@-^7v3FPvrPIU(JP(Zam0D|s`ccd6?`Skv9`iV=D`aVuR9)Yc4&-e zOh}k56TKx%YnStvO{to%&%WjvoY2;;{5!HM=Mqr!^~2n-;05LE;ludFB`J9$Hp|` zv%f&|*@6G)Gb`0|17COwLbON35D8HZCkzeSjhlmCcw8W*8%*}_F3EtXIlrC%XZ{_# z+b-P8Pk}1O>+jLbc&3$o+XEyW+~(c z(zt(&CBKmNx&-L5N^1i#okZPH?N}vzEP#XWl|5Yy=w-}Xxz2*}8>j)o**D)>57wCB zs{A=Kx|TTEtuIzW_hcgKFXPy%=lFx&I_fq^sBNMIF-?%?9o1?i{C1rf=pP`E&D7Rz0tS-*PVr-Bmf_{4B7R1=@bT*Q(vk zTDv(>af9WrsPR?$&zBJl$ck9<$>avqBXn5Lt2<8P^q*;24#-!@8|q^f>`PXd+#F@? zyL;!aRe?@P6hdqZq?aP0%V$Jw@~A#a*y3EDA!}ftt;U<3Ne+Mb3Ea)_Yf^1e>H{rX zzBjVMVN#sk{~(NNe_D}x<_h}CWrw`&X(#nGe=M+m6aRD4J+?)sT!MeS5SnXI4@4dz zb~udyUZkv8%%P+Iu|umRH5>DZ%r|@Isd|$2AKZv}-je$1S?^LF4~_2Z6Xa2?Xpu}Q zqD2-AUw^IDzz$bNo=$yz(5hGYgp^VT_o(Y~oGfp{Mm!L1`JK|hy{2=c;cQi!^XU@k z+QW$7?*+qjU$xvldSsPADgFGhRO2&V!>`6sJ_&5SuqqlakM@!vkPg{6ksk4D%+o`7Y2Gr zwqD><+Kr#7;R%CXML}<6SiNz2aHt%wwk594uWz*I<-Yrc+giMM&^Nf?1T}uewTC#b zQESnuY?MSFT)M_-aqeN|77B1r}NH%1u}W$#^#W0H7PE9fB)tA(=zK*(p2aG zl(Aj6@Ric>4Xid>14|Mz(Qb78`>>_iNkAOCv7*u-AxN;^}r5A{y&%00(6aJsG1E!rI~%}^66 zVnII1H4LYnmDCoSPj-<_;f5rA!cvR>@#J@^MRuoj@DjI zC(5siG;WZZPxtL`jp`@lP1Wm$oY)tiFOgMT4J%_g82&ua7{fyqaY|NvKH`e zUm>4&xA13|rxlKCNw^OxzxzdDNWBECpVJ3vcBblr>Drzf;L+iWtL43hLz;uca)aUj zikn;6vB3CBi6FmF=GU1^?U;+P``?~X&h#w=;J;1fd7LZ_Yk`%v&WOTxYORD-#x;%r ze2%=Nbo_=?epA3#QO58i#tu>UagtM1>xOOHH9_~B*6t*7xH6Bni6n>GB<0 zb1hha#A`l#ALsnKrveC;in|Oo;amu*0@agSCrKlu2djVA#Qg*A*s&2qG5ugK_z;aY z8(DL)v)9O@idyo)`VkTm`bL>Ex4|qq^*oPVRf2PRz=4#AX`>tdBgySocK1;b!5>|D zt8EWEeppLtIuzMNSVqyxi8UlZ0|euxm#8OkOUtZG0q&iNn_3$1gpFIjA2E3-gt5OA z=3&lpdMN2+ki)QQ^67q-p%YViRn{e?@zs+8ALdo(PW(tuj7lNfIV`w^H2i|7w4=y!^q!HW5}_CS&nOG3t4E$#+7rV7b1p51J%^+ug*{RTTgU2BS5#%n$lxr4Ll@v! zQ`N`i_!c)4O}@BYK?Yfe%A~|(L8Pi3T@zsxMJP|g#(4tAHpv!9EVCxx#H&O%~ zJ$1+xUVg3&&=hi31SHv*f6%d?29!qHW$nd|_gq1Gc_mXivCh|$V7_c5jsf*a+}sb$ zCTYbm*52u)+VAC3Q;)lsK6&_=w4*EtOTm2vG7R#U=m_^C&eTb+1!Xm=hwSoX6~SY( z{5}^L6v;Ch!vM~VtglZZKX8ygrDmTG(G)u?=yvW8z@C8hH4h1$QE;$~%UqbtXG8~k z_=9Or36}SBmSKIp*dgakN7flOXSVgpRR~At-py^ zXR#kU?53kjI!)#_FyS`W02tHtg-gv?=K3FbN#pD^wwDY?u|!T;uMFCz-bRJhmW z99$=SbXkwNL(_CK1s+1k`JatNl8pABr7wCVDj#*IFv%4JJih9KAGm9c8lmW|vE=uu z+{~(Unf5d#r5;?0VNd`B8>{C-od{G{7ubCSo^YvL2%SA7ob=1x-yz$x;{1HpM=0R6 zYA0#Z2+4pX%kW_;#B94iB7QFI5-Z^Ofz*=nb;^IYMf?8VN5oO6`*j)JH5x!XK5R(o zEKHJ}m5dQ~@;A647>n3tlx4c684g_N|Aqo!m3jzTXMLe@D>dZ~z4_Eg!VZWgb{CxYN=g6{O@}BsiS(a!uR_z*#l>t)A36@oUTRMov!Q zbW>Z~#WE!1?3d25+Dr4HOCB*a{_O5K$b`^ra`t+@k}Ts5AU`vSoZoa$Xvx4s41j!< zIiEkIMHRG{mqb~mS5Pv};B8$CWOzoxQzI~(wI`d|dr+g*I9B;-sw$t5pBLxAEGlh^Ka-a(PC0-mcOUR&5z_+-e>DqPcI zhfdCy@q#OCN`8(#>PL77d2z19MhtH_11glO35BW~78oHfil4N3?3#4CvJn+Au@ekZ{&3}b!z0Oeyv=4@tHCGjlKXe#Z0mQpcV9s z$X&n7MEuUw{v%W$MRkX06(xd)u6V~K*x*FqNUvP`(%3cJqoLAce{a>EZ(he|w@*C? zTGaLAb*8Yi?R^QNUm2P+sBfb)oO;DkRhC$-5j40Renq63GY>4#t5`Y1Fi5YOgM_Bl&RQ(Kn@Xr|z-WYD&9 zG%^93qU5(-`xqN zz>;!?rhayX!tXcj_Km&PB^J%g- zic&nRFiY{ zmx%JQDyj#LaNk!lvo|xGyFiY2wpY8>gav!mszeiYe&UaEd>79jt~ul~2A?&MkhkqS zd%L;WdC(mYVN3nXvj$Y%{SdAskJ0rvx_-&;O>M@xN5#+6YtCN2nx3Cuy#=k!9<5fn zCs0XVPb0`j?5ccz^{S%6+%bCC$9%P4QEA7yb*KXUwp*w$W2r{O-t@gdB zLNcQtYtjQXlXEN*h9f<2Ykw!SA0N{cF{U36dk_X6G9M#zg6KD$&^sr>Q#Yv?y6o<0 zegFOB!y!+60N6_M=Hr4WxFhGb+%3U%WZ>m5f5CnC?%%?YgKCAr_I|gJ`*YJsDI)9u zwKTNZ=l)Sr3>tK?xlh1vZM`R{g(J=@TO5zt-x0Z4J6dy5gUw}{Zhvigo*1nKu(S9A zd&6sP+gmZ;hUdMBkRs#7v+l))Pnc~bhV6{hXFtbOYAxGLhe;6zF(LyPpCt4E^bIok;v z@*_i~3V$1UojARHa^)vFEjV@e0ow8m_jY8a5$D+ucR zC0bcGvfRT5r3%g#tSmuZ-wLhXJb@j3>o))weJP+Gw2rAiC=Nj!6~LZjoq7Q5SUF-Z z;l9x!bX~j{_%6?SbEPm5LYN@_Y9N^fmvJEI!vKBN!D@@^v316g&pHi46QFMyi~WN1 zHow36nW`C*k?|_TM&Q#rLVI1I+4RMOGKkUNGKarVS(=cP%0~+Rr;>?~HTU9i!%(HT z%G*Lx)7>Wg3C4ELf8^cED%34nU;EZM6uQeh3N1#*5=AaOw(>DwFm8xJu+#j|Y>e;n zDADBNe+LgniT&9AyRYT#D&MO?TI!c`KFr#h{eA+AY<+2W=n6a`{JVa#E&IwO?O>wE znCgnp&SX(^l-)TzF@V4O?Am4%DO);fG!=6 zZ_VifsdnR+mwdtPN1?6p)-$N@f#q<3@P zgph7VRGXA=-BWhrtS-1{bPDW6&T{q!OM&>qkq`DswgODGog`^c%gG&KO0(MpD1(;r z8p-#$J26oqu7#%}$&YOhq;J}Hn~}-Y`IgA{c5)$e0Gup}-?F9hM^3?4pGnB*zPppC zZ?e_A!hGw(Gc;c;=cZARAXDv5=Oe0}pPNvH6 zii0&2l*lP)=X6vj#{R3j3>0KPUR?oOjW69+wi*jLL#_~<7jGZ;3kL^5e)98|@+Xp1 zF;INvJu3!ii(js9Er?e3`wGlvO6BWLK&xrGM#hT#a~cuD=w@A%$jHZ>_NJzxt-C&~ zPb}W5nt?i0D&PDzsEk#@%O)R0RUbBla(6us%L4_!Ds!olVYat7bNLc2vW)Oa5gC25 zFEgnxcE(#}3+vS}?t1f7_WL!pdAl@*`7UV zmoU*K$_gTD<)CIShIpUEGDun(FtGOA@=9R!--9=E1X*n~O5%hn#c-PPRZfL=*?&=- zEZ$O1iYuhl7>-cR4yhA4zeFZ>`+BXMq`w?KNc()=y=zs4Z1z1`aKQLxkS`CW%^<(# z(X{{8Y0eLDkgq*rMl|RqkWF`bG!fhkmdR?#=hIoZ2aw#FR!Y+kLBxdJY~KA1+2vQo z)GDzf+D=nd)Gkq>-gp%QWLPZ;nZ6Cb~^9l$!-7w?~K4~9wq((EL<#jmFrt)@%JZ9?gsdH)d9q&KU3#WJfh6h@S z9^2d-N2$K$yPMgoZ0BHOJn*#WibgGs_NoL6F`da!e)w6hX&{FxD$C*s*=_r`F+eLD zo5QU2`efVhuagM$Wwu}}QC##pGK~Q3fl^UK5v4MKpPdIP3tPX-7^JOKVn}l@5jZrV za1A>bB9W=XGB;CM+V=0Xueh|@Z&cMRdOz~r_K!-@O#j} zjh_;BlCbF!wcg^w;H_>zej+m}&sEJan4lBm>*LYME#A_O5#h9OveOiDyRV+=Q zS2SCB$=L?d!0URrPUB0B!GfR0Al`T@bUZahn~)-ZO%1ubhVMIo60`4j`FC>R!=0fV zKU1gO^BV!`6!~RwN8F$2*G69cxRg8(1^xSkg}N73!u;x0WSzPUb8kX=xdFf`m%wPK zVw9Y?{=D-k zsHsBifzA0B(}61w=ncqUTBuhgr829t(Oosc=@_HW^~a7&hy4RD{|1l8J#l(Db$>1Y zZ%yhVAM_9Z<^wtKJ!$8Pt%Xt2F;iS+z?ZX0IdI>Lk;7u*X4p$0gA*x6v2nrB~3i3Sm0Z(N~lHLYcQGi>D6NOJWkl2>-WSjie`LdhXBbP^=%whz)D?B7F({ zfvjkVoAyFSzh5E)@OX1`VEIwrkIG!_oQ4a=O|FME2`_N_ulnYH4Ywbw884c3wBV0w zh9Q?zOvBKi+n-`fb@D>ogM;x#2S}S0!f#gp&lDNuWM|LSddZ}Wn7yh-ydCJt*k!j+ zP8&i{v-DRL4&m3?Es!R!|j!X(Gig((#x{e8S%s9)! zx!({8GSGH!)i)r6uC(oHwIHy4i`LWZQWNiM*Q&E4ATl7SwK~3GeUs@P2xbq;iioS*w#ZwhEe40* zVZf)D-UVw*tS?yq(b3k?;S9nZRucL64_Z2;p#}j*GW;jm--VWLI!K_K$lUbNMT5~T zHw6xd-emqAK?!;h7zKkq@=f_q^>h8E@LY^(L_Y+k7J15?HueSJRr)>{V<07J0MzsGh%UHI^vaL{^0B}>Fk)C*N*Dwga` zseyCQYOmhy*TaO38&D@Xn(eheb|m9cd#m5!vikb+@W%-M?0a-VmIPi_b*dJ(8VK`` zdsim2cMLp`O3`#jN8=a{sy5Ktk|pQEZ(-T>q>n`VeZKL|1VZuz3?b`-RcsLYAsXZ? z^!%Fw-oS@_YyQ`bC^D^wU|)t)GMg$&m$@p+X8<@=z+dCHW0#iJMS2v|O1W}Dc#{C+ zH;$j7hw-zl7mmD0#DO_$ntWmU)n9;OM>0A2na(E*?8OWqpZm>wUjDuBei=+Wy;F6| zuq|UOF`SO`LRxzj^X-R6Zz!Pmq}g4U1<-h;WGB~0ce1AU9UuzK#%iYN8cyD=*iQdQ zAIhK8?5ER=@-?8I!|wR#SIBvtK(r;^`}VY?TWc;N191l{P#8{3mOJkV3(Tt@${J+^ zF=Mlje9zwLl@>56$PBVdbS}rsW`@ptx0QJ1R6bakY&b+|{-W)CqJGMOfBfwB>BCPN z6-z~el^5t%$>F@b{p@zX`a*wx0<`8(q6mkTcit zR=a!ZFzv7*uKkxB0jsjycc{*1;gcfpEmJ7|+X}$?vj9ji_0~C^MOm(mW*_G@=xX5djobih-Ol+ zKKr?Lam`KNJ!N#CyjdEaRws*pICWxB=zbTq&kja|?=U=Zf zQ`cAZm%wgvlSz^?6Wg5Y%?@V10#LmL&rPZ*>DihCP#q<(x%V9Vaoo(u0(}`ZBr;IQ z(Kb}&7tGtU=6EgrTz`RH{cil~6|lnecfH(4Yu(yp`54W&Z!v1<>#}Q}1&u>fi9f8$ zC?XS_yuMDk-uTL$Q=HNyV-cY`L{>Am%TTkRI?Au~=b}acniSoPJIJs^6-pqFQVj?~#$}dYiB;9>B zSpT_Q_S4cao0oEXvadcDpBM%1Uesh0o7O$Pe|wkL;ejWamilpaax3c@(=CLfFY_s5 zD=n+1k^}At%_!}JLUCXTcrj1rhgr?|>^XWH-40s!N=nB~&SOo6+Amk0t-Sy6!J{kb z$xxx%{tg@4dYM*(ybc(D0wEr6ZGDF%ocMQx2Yc|hzb_7lsT9&=@TgmPeyibD`Av1# z#Zbt!sxa;>VZ6YET204ndm=t%z69oPna)ukN1ZEfWgzFXsZBQD+?G7{qx!Dn?)w9? zCM(e2ZJ@vPH&561>|}B>ATOsv5*>f{ZPZPp{o@l?`IAIC88RJqyvkIG+@#>l-mU5B zU$-RXN7$d@TE{BpME(BCKeI3M2@UJ?*;xXndChmJMCN9n zz)$m^|5Sn;Bo!vKoeC^flVji1XqS)w%Jq?6J5h=T3E7c3oq)R_7V=3^z)-6xIX-Vi ze@wCsAjzsm0og-TUxhv9vkJL`v-b7f?{p2h%!0D-aN>4OPi^_dHvXwc1u4>Jv24(Esn@Fts(82EXE6%c}Zs4Y9pWdRn}NF`h1PL-?o}h`6_A zFj5{O+t=@`o^;Dv-_+TiIP5v)pxsq;9ykE=r>0Ak*(Zp$VuA(!J#ef(h^t?#9A|)l^ZduKcPZ-~D&LC~`&Y zrb^5=u{I23hfgcdx6hQcZY1B9JzmkP56KAL-7g{DOwI7fA{xds&vfe%Uv?S_m^|eZ zC8QIEW!CLk+jlGL9C;m>VU7wbhc%OGb3&xEC8i0auMMfZkl~UVlGqKMMfu?*qo)e~ zcdC62dnuj(Q9W^Q3-J1Ug-*!e?#G0qx^uhui3wi(;~|x@mVu0GI)<(?j0Ff(rbYTi zk(XN)G&;ptfPl<~t!%Dt_@T+wGh~_?8*yW$c_iC>k`aw(W@$1Z7wC)=$L{~+9%_x0 z#-@&wyLIjKUB_;8BFE8-S7uzO{iQYzCMLinc=XCqK^E6&9^cU{@8li{&SrBktnjgU zT8#tDnqMw+;iTZ>H@(3c&}+&0Yjz~$*;CR3VYhvl@9MO?0hoS~-rUwnPC|3i$sCbM zT9a$+e$!z;mj`$am;rok6Blxp8NP@@S$413*}d&z*UVv8i;fG@Aj=JZZnTElMk+mJ z2P-;&P#0mPL@j1-Ok!If&`O2MG&A2*`KoD1LnJ-!`J>J^IUfgoLbnsGuaXV=q4Y>n zAUn`B*?xV;ZkCq$>l#=5Th`qAkuIvf3@9J-@$o&YkmRF#WfmOxixeG>MR^6hh>}^R-U1HL!UD86jePt<_|)$yTXJimv*%1=VrIoXJ<36e=;t8 zCMus3E1Sy$Z1#9J%0hl}rvEE8&Vv@;_rAULShx%BZZPieRM9ixfvdPE`~R={uQRh! zsns}{kUmlU;ZPBX!pgo#Fefe5UZ@%-+-hMB#}vXsY{Ud%Zod z+TSnxknC}u^Wb+T_TBuvhsERkFv6)IyL)f_^`X*U)|+l zJhgBWy3op@woJVRc#oOGh-Eewx%bch~E>{JA~v; z4v-RT4kvW1n%*kz>Xpyhd7~jq{tx@?_Sb4pskzOB@Pu3%iY*k~osD_iBR8$r9aG>& z`L;0?QCLr5_KB9!jss`lMU?hY_-i>%IS&^lv`jvEYEQJn7IvY2lYi%97gMzg|4bM$ zH$uH`wRvIBx2E+kb61J*>A|;LLcz)uUXk6TyIAWl)HYBouj=w)qEL>l!!jOWYm9Kkqwu?vCibUy1Yf;HW&t8CLrY!>R3ZGhmLiu zxBFEe_vNfNI}>90aGX1jM1UP*?c5f;!lnbyu38P@w{U^d{ZNj1m~NSWhA!;zk0J3Q z9YQ=eTE7^Un1UsLdYA_vMb-pM8J#Tt32aQFEi!1naqY}kb2OPLzk1|4L`Z~F&LUQ7 zv-JlZ-<&NSBB@j!MUJ``o46il}1y2DD?qxHCrr8+tJApK=7#h7rVMp37Ddf)H?NM(naLQy6R?h;aeF62}qgfpOiB0<;H(v@q z`Si|?p`wT$?Y&SDhn;8C(5a|O5u3UK$mTRzl#4RC*~ToLG2kk40TQ&zmb$eKHGg6o z?CwPttb2Nos+mj*Q|9W`Ibx|&vfU+pMCE4{tT9DEwJx1ELBW!;kgfPhY(JE2En1iiJ50`HZ$aEtOitJDAw22Lo~5y5g|IQ zk!q9^rXs2gDm6|oGqrwQ@SFCX(9iND!A|uulk#+OHD7%whE19Ns$)*==S7A^4GKD9 zx_YnanqEo&HmE1{lbngt2jqG_vP`(NQz+CzE z{gXe$byT(ItKinHw%g8pbvEhcw@8;8%AOPx(+N*uXNTgZN7U+i5m!U-IL&x7l5 zLWZ}_?2PybJY@?o&b4;iOB1QRh3TsuS-vTE$JfNwdkRu)+@!3h5r%1UWb`IS<8Ufg zFAa=flidi!CVh;b4}teA3xjC{zk$w8>p@M4|A(gcjHmkl|HqG!lf(&?#BnOSgpkc4 z)v*;t3a9L{_jZg*-uB9N2uWmzjvYBx9HZ>L_py%SIOG57^Z$LXoD1i6&be@2&*x*^ z$D>m(66IN;5y|Gh!DPLr7VWeWxwYk^_sWI=qt(qM+lXW^|c1Q^2@m?-nZ!pB&*_I_=%H zjIHXOM+SM0mGVP8X|>Go<7&#y$TZ|CD_+>ops%}aex3sK(0hmoFnG7>#>(EX@KM^p zS-!e9!-}Z{7ye64Rm282jDAUqlm>sx<0#kPLHwwcw_Q%ETIOHz`z{5Kv}=O3YsC%m z@B8quZqXk`VMfoWG4vDnGkQPiQJ06cO4H^ybxQyK=>H1kxU1SP6G%YHgg&jZ8^P|z zZzZz^YP4?H7XQsXp1ovO^M@qzy;|StJ1bpDX5rQF8I(yDaFAxD9IXHPR;OBsZm&hY z@~VDPe%vS(4rDfH`iD)TvF#!%v44xd)n0!Ie9H=V zy8GXOQ!L!#YXkbW$GCsn4)xW>a6WuXX$4i{w*YsbrZei!Fld-5$KGcwDN#6LV&WQ# z&pInw`@6-U>eFglc!cSrYV6_(GLUdOpxV&b9GKxp{1wF|kt580aGJINy@RyQKM_(1 z`>UrSM*tlQI)Vxhe;abs8%0!&YB9MEggTEFY2h7f0ypL+{+7wMXB^F4pAEXP`dJT{ z)|;Q!%-(;18%iiQux>z7M8OblHFR=_mvz(V6LezQ7SAMT`qlWJvytgiGV}9X-b<8l z2hI$q4bo7Bk6lRa?#+sW(3$xi-nAV~Zw{VTHTt4yHnr1`#NTw6YOE^<9@6JR&Qdmo zx9GQX8Dg_!0-r~?nBfD=CO(f8X~o2!`;revo=Vm*KzVtF@R598l-~Sg=jg5t^d)gn zb4bpG!S~y_v3%ZB(SH=FNBO%uFkaP=+my%HkK1)@+hUX0ANE0>G%95{tHvNH#E)1K z2M=D|+FG)T#{XC=@=K8LJ#*1oV9!q5&39g6pq{k8o?ymYA|)wma``45!}7+>$hg4o zk%&al)`5=3kFm5bp_u*7LLKev9!`x?-)g{C)*eE6DQAo~@buCm62oabFmx#+=7*l6 zs9))S66&EXx~2Rh8MzP z5^;LMfeUW7uSrc2OfHq2XF(jK#x!HearwlE9F){w({DeyqPxxvPCaK%+P4w_b!&^C)X=Z0mHSaXy0>p?O*+l(A?A((}1Hddt>&M=3`fJ?6@@~k(T4R2@MGW+5K z-_R7cB|ox6#{^urcP1hrIV3!2eeOGt(A7JpU~Dsn!0GlnbBHjHG$lXVRh@U>RIGo2 zt13ioZE*6bHDN!9d1K&K_?bsE_7Y-6XkT{N@7VfVZ4KdDp4#5g;W~=jZbhv=tND;7 zh>$0V&EWe+*H0s`iW}w8E+NlHEuw>t3bfgW5abIX_}pg6rWzvd*>{Z}znk42{WJjw zbQKl-hkvvuvlnrz59uv1d%0pS#NEpyY<}1}1Ft;fh`9T8FsyP-5 zr}{nqE`0&JO+KWVftl0Pz9hD1xA%#W==xZaXDAOBxbGDc>D)J!1dP(Tf+cnq)fuj& zNQW{p<#X@i*tcLBIwq=1w2B8XSj$?^faR`JCgbk1m3)(}ug09;hI)W9+uu@wc6)V} zSI*0MkN8@U!6s~(gpiBjFTx1IT@FrCI~aH6UKO4#QQSakb8rPToN86P1*;7OHDuC)+=#OYj@71eT+FzsFZzwl^qK@BRQZ~6#h9n zWcP2+RaL#;H#7Sb%4yVN3F@{4>ZJFK#PV+#1VFuU2Dt^-8M$g73a!groN z|AN%md~GOHAO`wpolVjwS1F4_g@Yahu97>P*>b4sm73#~Bo3CV?zIc$vOu8QYhZ3y z;@F>Z8_k1O2D$mr+qrd5(Rjt_Hiy17uhX&nd^EzDz>xa6ai&?yCzQ5za{gN(6?-~a zjKOw$hmb#SJaPB*e#7h%{LOL_$7Ey2@iYJ+2BpbiVXKS)08L!l%0~tr_O??fJ}w>$ zD8klj<1S?hJMx21Wnv>63-LGX1)=q6mQT}c5V;PGiIK*Mm*uoN@nal@U#9j)!>Z!8 z=e!=@bQbRtP!`fjV7-2h5hwA+2$UBiw_=0}rHZxNefwOL#zx566$wTg>u92JQzaQU32mM)>Ry{#GUeG+TyYY7L zKetl3=j3YC=gRs+R7}h+U^b`W%xc<+gwlHVOjawF$<+YTuxU^x?Xy5q%I-=paqNd@ zfu(3)MekIZDIKs3+^!+Dn>7fL;}0ZO#Atmdu8o%4Jjj}noohhScCxee zkq<0wPJVNtQPgqN9^Vz?pJo;o7RmvHtzr53!bZnNHLkjuG3jE?RaJG6U3kQKMcuCA zfEETI8+1c=Uc`sK=MD75wa#Y(_R!{JtLn+3SUpEv*$(W~F@*Y~t_|*#5K$&Cs4Tlq z1UV+DiY6R4pKkzIcn}@}k9<|fry!kWTpPqQM3KjfVuXbV`p<#93yppnrE-Mm2a^88 zQAKxo$38S%mUnCTVf3Y%lN9^tJwYq=GoLck*+8FW@AxyMxkm$S56#VB8`Abit0A9u zLJAng_V}-rn{PdmV_S13jXr!Pom<3vXPqa5u$*t*IM=lZfv$y6MF*?JW6$r0_)oOv zG>;@9n9~@qeos*bY`|(@HGj506`}CQRS6$XL?rfA`rjq`V3mpZ)Rson?##H$iNhHQ z3}s`_#FP>`PPh11W+h3txWuu`MkdEu!`7=?7gQ|3fDzylvAizjRRW{meLe4%s|O>N zH-Z!$B*hOK3vJzH8hW{^5s)jmxdM+3Z~teZsRC@A;o8BDZeipF)H?0927kC6H%hk# zsE`$CVyl2Vqu3~{tn>#}VidV@dHmxE_R0m9J@Tz#Y~$PG3cjST>D$NYG^v=_8Tlwi z(XPfQg@?RbtMA$&e9B)29FrPRgu1Il(f{)TSQtl=GgNUCW*PPd8%wC_msr3p7db`i@1ee)mW*Nj+avhhu_OAfsSez zX2+ZETm!>e@S;vGIQmUIZj*BT zp9vXV9t@pzx;G4$f5|)X_A|6(I5=fWYX-I=SqZiT)i@_7HrL1}E;xtyVU9}}uXY(R2FA!YSx1;+4SY5q4; zuNVhh`xt}p*X^2GDtXvg^6t1M`6{_v^^9}Le>+Pkd=bki?i^>Dz0HS-FIJ(NciIe2 z%HN)Eet)siFdWH-2-<^_@)TXxX;%S?6c2bv{_E#(RzoJOL4YjLFAWTa6=?3tu1o-l zruPV#_KpZHu=Th2-WR#`(if@M19PYLntfXNHGEeZ#XGH_)L??T(ARJYy;41bR`uIo zy6JItW55tFv=ezrt+UjeSdoSsuZXY)5E{nbHRS? zA35nwZ`I}vhiQ&X;&R2<%B*|>f~leAL6GCQ&dEteR-W3~D>yqKJ#1vY)a_ZAJ-uZ= zR-)l=D5L6h&zOtX&nma(_E;`k$cxd4s{}1x zcdlsroE5z0GGeS`W|3+IzG=W*!9PC8cFmsTR-vKs5W~<;m#4SXn#a)Nr7F{!NW^PQ z6I-FV>b*iW;`tD-qtNqeO}#n@3%)|9if>(&?5WH*UQbj+YyO&X>S?c){gqOu9|>c- zZ%t=Ec1@g9VyNaeQfVZmt^ag<=65$*;@05%VMmB6X&G^>zne{E>Q&Z0d^!Wwh|JG; zF6`H4bZ-R9D9!oX>~Yr$#GZ3N%RzZUQU*s?@ylhp!up49lbT$xv#FwVTYY+7nWv{_ zPJy637hm*o9iDvWeramlbMcA5Kwn(^1nwbK8jxV zS1`Yx`aiC1?+UW9yfc+yRC^->A~i~x{~zoFFL@cr>}Jt*ibkUdC&Tr9+G*1V>%$`g z$Y*rzZ>|X0m?dh0B+HBXh19_H@C{F(-w0=wUFt}w)Gt=C#hOESpO0#JeZ-1SrhP0A z^hEb)FvvQs!+56QRm3{V27kCg2uA>paXC43#_c3|?eR}AHnnPGZ=mU-Du}YiX?i#~ zY>fNqBz#y_Ugn-WHrV-D%+sp#BSB096QG9ox%6Q=d&cMdw%h8>YEiq)ynyOBHYILX zI|sW*5%A?g(GosE?>#4)R7L&CE+IOmgC1lsnLIXD!)e%U-++b0JgZ^pXL`v)o6eEd zbzFl%7u75>d)kLhaNm8GxKlA>w(ow+1`q-K(rVqiX3vSt-}@Qn8lt$pTA~eGS+SuQ)I`3{Lw^jXFZeg;47dMJmIzI{MJpB6- z+S(M}hM{>i|KHPw>8Jhg8oNS&r@LW1$zul+78e5J%ccJGhMhRTMyuWSh5CNpv_w%e z50F$}U+-ND<2Jk;u20s*g^#u#hazvS58IW6{pe!xLt3knSI-hfIA+q*p6fT6)ZBRm zmD{LV;8lc!T#zJ7b&bp|yoxKSJcU_+uwJ%-?Tbx6o(C$gG}Nq{%`GiqC; z{PyJRW|v`V5iR~dXZi<`N6kV2dv$t2D@mG|> zImrns)8$kqIJODPfTkFuDIqSDd^^}CWH!@ngTM5y-^Q4fEYOzXK=uT%#6HE;7)w?z zv5KwwxhxPJ01L*qtpgJ?RAvj^*4~*sJLR(E0dt>g6=73Zu}|+XXY%|J?KYJ7_>E^& zbMO;Lk@NXoe5!6=;x%=r0yQbU$eC@u8DWp?L<60or+A%o_png?IOVpE*{msUIk@20 z;pE1t7)|4q|E_S%tf#%YG^LCCm&O6kfbW$EHpIg=ck&=B@a!9Rl)|kPXux$}CcQXd z;y7G|c{4A$N2lZ0AK)?wYExkTW|l@VQ~uPvTt4 zIyPTvu@m;=-lYT-az@GdZLImANC#-&sSgH^-~$0K#Sc`|TA1vt?nWPL_l+Y~S)YIX z6)qmf;kR?fxAd%^y{cuw?L^iR^NK2Am947q#@ejyv4inbyYZr8XvE@6Vc+z zWcYauK89t=A6FI5x?J#B?x9}|5>1L=ie>dB{)PL8xQ)3G!H#mWhk#y6Q3|9uW%c;0 zupVt>FOSVLh$;Ihm!UHfrRGP`022OtUez6-lvCm&UaSc3^FD+w_dmR-=;i&fN_`c| z!piMD!Cg9*^10D#tHP;z{54z7hmW&-ak4Rp0HWQsJQh~fkVdI&aGAm4u!NwDEci7p z{qH(y2oAnT>WnD$>WM9SUU@`JE*g=QrQ5)bgEl7kUoW=Z(&Oo(!znpW#^+1Sg}(^n z&qr-ILaHcPf9{-A4Vm`v8)NZfhQKM=S|SoPj4RBL=5}6v!`V?Ky&@03`3t~LHbIir-7fbE-K5pA5LR``pby= zqw1k^$DQ#vwR>>mw(}OhZ2IyIqpKv-+m@V!GNNdeuv99H!3LOH?bT(WHojJ5p_-_- z|DA}2KKM-930F6V#fOGWCwK{RU#>GlPEndSZro@x4W)nk<+-lI_#dv>>l;I8^lQFT zy@T2I_WW${@$PDn^wjglsUKZYJ~T40I7DUH#xfv7K@MHm+;#IxqD1he?h}<``o8tO zbC1xrh0<)SsyxTeAjPg?2l_CKjJ7A8XWO1m)my`i{l9V)A2ey*Z3F8oyZ!4a9cHR} z4QA<*>9yrKC95_JS5207o+xm!F1!VX(17jDnA(KGvgufz;hbQA&&wY(6gq%NOy#a2(_E%8u^)Tf)!tc{;8kTQ*+DVW?X7eeq|$5ez@+ z@OF$I;qLPJbvX}dWSp|_@en@5{Fa>qwsORo`$4pcIFT3xZCX_}*XSx&psSiv?YtK? zLD+&w+6-o-?T^chxcsAI_@1cmEU-yH{wjsxc z@oB|N=XcZR4<;{l&7DqKwc~GMt3`EjXYW^6KD_&uxJLbMV7hRYL&zq)jm7O7<*bab zAe!_qIoz3Bag+JV%>#_DV_Uqb4n=9p;azGxMfm1uzs+`WtAKru{NIQ z+D-n?r)i||zv7FlZyo=EA<6^8qcWY(f;>JV;`I$;Bf7SGqa=XSFbp`;?v6GzNT7xK z4@%U4!ol4lQL+)TqE)Lf#ay)0X`LxVC!h|xdMlPUgtvZw3O0U8eKVL@b^2#hrvE~h z`v54LBX@+))0)mPFdG$gMGn)!(8>@Yl+A&m7BeK1ipo%v^qX)|7~w7X*P%7n{HmrQ}_@M#;9!H#DX^D6}w7$L)4inMsoIAH@)ZiOIQ~* z9$xiLWspk`ZLEDAb?3Ra%JvZlARGp5lIli;F>?ZHtqsc5nBJ<>QLulecg!f^T00Z) z-S{@DENx<)`u#!VtWPyXjH@PYpMEyTVkJ|nkCKdSA^wtx)i@OxZD7dC0+@ly11v%1 zNg(yTOJCCOjJRTHo@X;kFJ4=#Ok`rle3iG>l6|G!->PogTG77Mz~^<-l0$kVDVJL- zWAA}b`x>d8W?ykxm@`~n;p<)2YMN;0*{XYAq!f+rJWuXfDmcd*9t|^Hw%vm>Ie8fz zbsZ*&Z$iHxq7oB(i6!dl{@#3i|4R+^#@1@l9g22|>wymc$fcVG<}W*++Frfxl}Bec zBqRUeD*S-i_DSmIK&A&pt68x7vN6WG+?wNgiA5FI@3#+eUl+MZpHW0yeMIO0(<3lm;_w-!(dlt& z)n;DHI`T#5H@IhEUJa z#V0&qQVnMTfeYd$w2e{8M8pg+{+DeW9k1!~vJRxgx6`&1rxdG->*dQQV1ky4Gv_2N z0M2VH401n6dp7wG+?|M(4ap*O6t?ukWkK2yZHk869{mjE`K%*+|EPGgH2UP)Eo*{1 zd-2;oDpj3B3J!1f@unp`n-Jdb?L8=s_iOeD$~H&)4-~rY=~@)f=_F$f2NQdLB}Q$O zgm7?GcSRWFOXjo$Eu~(PA{M_k97NQy9zLZFe#q9wyisa;jwmHjPz3bjWf9=^hnG!XsFWf)C)1%y2UKU`~=bZ8$&) zD{Ly+uPl8G@_UPB>^u4W`{=>Mf%3sX6t1lL!uvlGjo=lRaoxAR13zEK8mUgoDlgIR zRwjpkP!7>sQ(KLC@e?cYGBU;~cs^e)J8&gH*{1y_55A=vUbXRIVA9K zNll}&p&H>cZq|oU;<;ti-0mcUNbyX{89g;Vc!v-hgcquo#ZfV1#hcYO*-=@Ja=O6~ zdwcs{g9nbsb2V(1qe70bbHzJ{fi)*QEz`+*?y;dy$e?hh_sEgk6|h{@#IFDK)M@bu zR>+&JEBvJ9g3COa-uJ%xelS;Xq$Byn53zwJL$*U^CqJNCuT-$as#t%A)!46XpnZ?R zm2niE{1JE|HE$Dcf3iGy?Udb=;-F)`K(3|oD^B{EpC;r@Py9k}M-VJ((aV%O@PSjS zdd-~WPwxJ%6(}?)+@;^;vwZ9F3e6^K zT&2!KXi2;(s}@-|3T)!PUwE?ph7)PD)|d{*Se-Svif1}cn~OpbXhXES_y3P>YIfD# zdFdkNU%VJ_r%;6Z;PNwXZWs?|n8ix%q@}+5nA{Vn%jVfP{^qF;pHg|b-QPh>#tzwN z6*X%`_4D*JL+7a~E0=XuUp@V2V~8T^a7d2w(;bK%wD*?sw)7AlV6exncy7a$2pCWud>?d$oFsnld0{p zyFmib;{5P3s_6XusP`*~n(=IMXBn^8+BE8`U=g44V!?gvefI6ATz*>|QHovRn+$#v zyOeTrymp*o2;^|;ixpRM(0K#e#3<^SU`f#SwK%8_Wi3ugk$1j!Yn-w56uA4@S|@0uvO5tXY8-a$b=y1W^KTDD^YbhD;o%)i z)k`!j>aA-TY1aD3$jDEOI*pyuf^1iw+?O-{^CVo`@)1%G2aND--sv^-T}rvAQGO`= zy-IGPyxrQA#Qp9M+s3wz=Qnm2U<`j~vuo%)jeyUxw*fXHvJ;T?-d;s7lN-!X zGsI1o-^`yKRJv14R**yP?<8{hmJrz}3MYA(t2FQjAZmE3bi!fOZ&&9B?n^2%P(7VQ z(!A3Vu|y6I=CmkLy7r=C(=!LDjsh>&glwIY>E*l6_Q&pv&}j=92}!o5EO+waj7QHz zlq-8}-x@vAz5M8&$$R(VmdGnM@mRuVakoz_&~ZK5^-H-xFfgP~Zem@S_%zaj8TsC= z54L3{ciI%j6b+bPzEpXCkOa_n4u8K%$C7Jkvsmn3ar z`9NPuon_R!sDwo?py~D;34|2~jD37J9*?rQ_o$7s1+Mp4GVp>TT%f!!2uY?-Z2;ef9^o4wBI8>#(E4Ybh8ZW&r-$% z0^;~o$*wM_-Rp9ve&V$jTc^RLclSUrdxh{btgin>qmXwQ1Rch8caEnub)e*U5kdFRW2g*gc zE{1@iwEd%S7fSo?;ggcGoZ#ejDzH)-Y?c9pO#ABW@&lIu{2z+ZC>%4_dCAimCLc(O zeB0OV*rF>Xq>xTpI$~!|Zwy%8%8uCGqHZ<$!1#Fig0EN|(MY5>s}GuMJ%im;TTaVF2rcZtqc1}Duf_H!IXT8QsD}o==Z48Nhw*#h} zh5ntPF?zfTPHV`FLhpDhhB-P?A!7l5QH~T|&MgQBn}%cXV>K~)uajFpL)Oh9+*=Hp z^1!ahEl(QBviL!hhLFS&g*#3R$_vOZK6&ci6FLg_nGHIgo1p~2cD8qBt>}LtJ;lWV z!R&~VO((k6>cKT7waX7ctiO}bg}TE!ze1i(#g6p4KGiZ6`l?m%=DmWwOm*VLq__q3 z9mzf0A)Xc51CBc~^%L;BWfLFeZ#|)hG$k75U333<>5hxeX-W6VN%0P?kitFrzgul^ zNN(W;km8ms8r5wSwekp|)reotZ_n5DE7E#8L)#~h4;RsnjwM72*w)|>rS*eilXj?l5NqSSO!$rYVVc#io`rdCqwHfJx;Y~|6_v& zm`#8LR}pgQmX4BE7cb+gj*h9QM*wMUP)R9{PsQKYGm!jhUo4RHB}~4hzOOfEz(>5S z%-dq7Kq1{G%~PETQJI9ycq6YKbtj?k{v+1@0Nek5LnLj*Ib+`_S0oCobaKVKXR~y{ z&?-NP>=Brl${9JQ8Tgks21}<)KXfZuzInjD-7vWgQ%dlTtsRLcGH(N?!3ZLD+7i2L zL-r7Nl`PN+Yx%uOTmEUYcKNu<*f9c2a(Ra=Pv4i?Gc`_ayjL z+oRq^VN+1M6;WdB86}b2>!hmdmg4ghzsBCpWzo4AqnPw`_mvsD3Eb*G&i%$5u~GnOuP>EEe9sd`cQc*Vg3eQ6_~b$7$c+ zi5>Iwc$*(3uPb(hqr95z)Zeq3AA+o72FLQ+?G7x?LA_E;m`+3PyySdA`u>DT6*IO~ z@14?~gCXtiSuXZ7um)u2?mqD;+xA%fVFZ1IgDUUiDqV8#mFp%vBn>tkm*D=;srp%QV5oVYErXCt z*O}Ky)!T6FF-nby^~S%3hcQfiBFj=Mi=f(p>$BbfQlhswx0D-`>u_oqwAMH>%5|Ev zJXn}|vPlPEONZLl2?0NH{2#z)P7e^e|_C>Yn7330c-;kMAs9H72(p#Q1u z{_e(gL?Qhu7`t+1?q0sd4<3B{S%0!&rU*XK?|NwHbYiMfWbWOU7iJh~TwH#1QnxIh zmpt6JfAi|y^VjJzu2acb%i}9`g_Nxp^gb0i(Xs^q4>Zq#e?j11B%sa0sSv_(^}@2f z@Bt9!SIYce{?3=O!88H_kYPImKGeywi9~f(CeMjKO$XoGXW&y!|NfGMvj2VhmDjQ> zD~|mM>F)m-N!_xt!o0BzVJ?$Ww+pf!Nr+WBtO=EH-Hi3)5 zkTslH3!_+R^J|5BaPIJZybup!5lxeQ!WbTn%fia zF`3awkNTfEg$G?qq2Cy5W2)-OkJH;s4msrGl#TyWB-J(hI`_H@PrZj@CWD6J_8A|K z3$C0ybN4=LjHc{>LL(q*L5|w_)~;SWxUt+ejhXwQhw-*h`!Mh1B7L!taP9!y(mISA zlym1=??Vnog(I8|Lv-=*VVp8gu4=uvNTmx;v=$AZK^m$ojf@{c3~2aPI*JuIqdC_> z2k>~XJ}Ot0a#kPp%wr&p^2r=kpSN1gcHG_B-92!hoqV05tAe@cuNoTUipS4~PlF*l zvv6hQlLz<4V9d$W*_o4@0@hW}SN8o^H#WlLhD}1`=}XFmF4>%e4-SJkZ<1}6WK-C` zTxGs(nxQ?CjJLk!e&n3d)79E)Yn?x3@=^CD{3~jfUHbXMhO_QlA`AJ?hm)Vo4Z@0w zgxl6D8)m68!cHga|1!bpIlDm!zg{eA+E!^`u( zAA_;)Wwe*mVAblJ@ita7aasOdcfRE_`a>H}Q_Gue^jO+Q;J{o59!PO;$wV#YK0%!P zGr~AzDT5M8Sf69yf6d$;2;VgX((yx97>g<3c}K9ucd$5X*x4}`tSB$4sgxbbl|ko|~L zOQ3g|vi3>x`6p6I6Cs}=u1ka9FAhk>D*RxCt;!lNOm*&us19=+!#SR@QMmH*(&JGS zZYU4HwL++s`Ko~+8p!iR;LV`~G_y%>jUNI)7`dA;KAcwr>hP#sERf72?Ijx&!azzb zYTj!#xeUWO!Mune)Re+0N4WbmYIGD%*s)t2Ec7`TjqoAfy6-M2cBI_(F81wjH{;*D zs)6{q<#9?tkubJscXxX?Cr0=`j;ladm*9L4z6-0S{nMyQEbd~JXn@S@pA!f0qCoSTCf`Dl zClxdome=OK%pCnZmzHs4qu((*S%q01|MlwxiB=8prY`LzO^>9HCI$ZJBQ6Z`&rtTI z-XY=s9ICfo5gCu(^weuhzBTjkSnVCDWR^rcjKBbnHF?t8^l6jrMb;Wj6$R{E>_S-X4CQLdsTH5ODKN`LZHwr#Owwv5D(Mk>cvvv)g)?atSn~e?PO?bBVb4tvE z&-PyW9dio{Gs-rZ?Dw}x`9~0S%g>rnUo3l)D7tj?luu=%H_At=8NZXdIegk;WFWl_@KZ=sg71RQ$*pBO z60EO3B=gfWh}gJhi&5%_WYlCea8yEPf)@)I=f-Iync$-aw*6MwoS+2?ix&y3#%J!R zUi8+^k@!|0nl%)oct;xn2}pLFkQSoc;!ODNn-U(p8p`H78Pe9K|B3O++1lZ31k^wE z9kzd0YObp)RQkinA8|n(_)7TKUT>bR_F;><#W|LM+p-%Q?A59B=kypH&t4M) zb+tYWH7nmlbA-%NN=*71s8;;D{!0Gjn#gYu4kFt{Zz1JL@%~sZM3m7DFcja4dv8-$ z$hviIj?LXmW5By{B;iOZjjtP@_;`z0b$z`C1LIxR{fR#EqJ$JaA z+8FFOI~q{SB6M3mw4p69iA{|Ox;gO>`GQpEd;)aS>(sr5ZxqsQ;|Jq_BaPQ)_%FxQ zRJ+LAe8`1&&4Fy0*i=GnY^?W*142AYz~)1C3^si~=X<_NqI80iyn=$Fg1o$ceM`$R zNJ9Qb<^s>*5BXVv>+OrWqkg3k*eqw4>Wmd3&U&0n)lUJWcg96#S(JLWV+)7Wm6Ema zCx(I#@(N5GjjeAs+*4R`Pz;%F3`G@KzMsCM_d%C2Lbu}Zr)HNL%)R^oiPY1cYJbd} ze~)w3cb-5vsG4E_yTnO7K16!=jK&R9f6=bo3d|v6ub_amM4?&b(EewgVMBKFLpAi% z{PeRg-Bb!lmq>6O*9m)vjm+J)D`Y{+euWF#^!%dk9-#ZS`Bu*~7>JjP4;<{0#t-Zo z8j+F*4-@H9Jg=W5`dNJrmXn*`7E;&XSa6e_Ir(|+ig8QD{x5}MxqV7-#19F7ud5k7 z7w@`!e!D|XQy6j~})0G`me?R1p8%NX$mZ5gP9p;%Yi!8q~GE*Rj zY9Neh8xb{sFK-YZBGStp;Dkf&n!`h_Y>MS+r5%*dE5vY0Q4u6~tV?{vb-J-OONmpK z-)m<;Tn>6U%p?W!pL3jGQ#{4y)XI8c%BY3K8c-bdOVgPa@6H#vT+`%n0VKHpA9OY{U#Wh2(3L2_|m%>RE(O`*1Gq8TfG+Xfqlzb0Y8hx!Q$*E(A2&&XoYVA zT9nAUM*{lJnj`UQ*WEN6G{+vj@6=8vS+XBJTR8Kx@7&L?isSJwqPe(F)x%A0GUau{ zS#=wqO6=uQSo9AkG-slpiiu-?{eHlwcZ2OnTb6X@U@5xdJxXj^Nu{Kga)FA7dv!OS zDYm|?dLHh?I9npix(^wr{zAdp|3|>gVgbl`|D#tglMV*zsqROsQpoBMvx6T(M&ESc zC|~V&4#8D7+qBAb$pTOJKb){3t-E-g?R-!I7V+v4F|XM_9H5O?!UyXHT*}JONON=a zDn;C6b(O1bU;w?{)Y$Exmf(4P&s})ig^`OniqWGz-h8qn1orkf-S9#pm3=O$1cm zhKS4W?vkK@Hsr#MI=8cQbAq zK3DCDlV^9Hd5b7bt1q&7c*uQb-X>+vd+OllNe#%g=D{pAkh+9s3QY+q^;cAe%b7DH2xJ z_c)YIhQcXP1IQnLz~=HGV-}R>1mA6w#i_&DMF7&(Q~KCM!C_FIdk{~*$x<<8zy}em zCaq5~628-JrF&e}lvL2?oG8_JSZDnE?Mg2y;Xz#U?VMJHQc-nf2VRCFYb}k`ZK}yWe%6IkdIl&IQd2 zih(S&MwitFl3eaD+6bp-$3M?Zfa?)V==LS99L%~?P5WDVkL)sdT=NDLmNLs|v+JZ6 zqlF2$j3UM1$J+i&r@v9}XR@q|PySdZ6WjY?-^HC>ESfcIm&hbiBI12n**-^Wsr&q3 zUaE=d{LghMP8sK}^jq!>D81~rI`~x6t)E@|#MKl%kNxhKCAwN>RsBYp2^ z4y5Ggt5AAbN__u{9zh_jId`5FbvUQW1+Rt3wjISmN$4R$dwyB7jpmuLVIfAZJg1Gv zz-l=w1}qqGAs4<0OimUw`1mrLzdLXnk8AKX)Uqi0j}0*eh7w+dbEqnBl6LoB%?9P> zx$xIZLoX&nc|mNFez=CxKyZTcc0M+Dj_FEBTx@tcCTPkHoX&8|vYNL(gso~!!cNnj>z$B68;4In#*uZ~^N3lK>*An7yrk6piPBQpQOzm zB)yYF$Y~8t{oPNy5Q4`|_nw&y?vT*BdWsJz#tzO8_BO5o3sa!Nrb&X8rXrblE48e* z^azR7D)idF{Bf@pvM_as0!k^MFH!a1fes`yXJ6`(LRdWBU(2ZotpWB?A zZ=*W&n1A2)=GJVSuRKSeo^bZ7V9zjXwD$_HLG-Eb-+1Ji1!VtaT7LMt(W`Y-=hXjn zjtVqaeSBln{{UcvBT}!mz^(VyAh*<(6G__HE!&;Ws<|csjl_HNe!J$XYROUuiXz>y z0xgMBHM!k&OS4CMsyfL&C(G<^GmELoy?nh-KebQo$R-IMwux%hUd$$0XBPB28)F*# zw7FKiNq-xwacm%yc_GT;N@gB~_V(tqaA-o6jR)*-rZag+JtjFDyp+{ovA`9E;^l=ToGG}U8R$FRf|-_8#hh0Lim*AJ2HEd6kuI`or+E1>y4Pbvh&%iF=e-fguVfi?{&{O$sLVY&QtR8H{aIAzf;lO{gw^*xlYetVRuP&P%K4O3Wk{Zf|( zU#)vpv5Y!}*KgLEZIz%s9@o)CE63%mkFZayzp3u^ zSh!Bg^n%^|&TYr#X~%WkP2%mq&b~F(;=D@G`QUj@D*Y$1jY;fJX&M(p_@_)>c66-} z;^LpaNJ4wB$J6vi-oouv#*d?yH)X!31^5}gTDs)B_si_R2$de%*_%_N#PS?_>Ves@ z1>eu$KPcX(JT2jn@8PXI#HLg*R#m>NjS5#`(xcBhLCisiDEt z&KT)>_o=vo@98zsiH>Xomjs(9RT9NZyI zG2>)4ZIykDF!ui*v`{1xfUk)O@^a*?tr6xMnzFU;vdknf;4nxEm&Zale-V89S=~3> z_=m?QxenAk7ZT!O%f}`?{PzfPdGbyR77tJb!)bckSV)M38fxPGXVgjOyXd`_;HmnA zGrQaOEna*i0ieYcXGrxt#9WF{k=U{=hS5nA%6qRM{C2CYU`?Z zyPA^{Y$Wf9a9^7m^O%!w<-Ibo<+bJbS0e*_xzQ;khrsRV2oR_%Tfvwhn;4j%{Xe^; zGMdHBqM)VL%6I0@J9{o+49(Qm@euvl(pwP{ayEzML4hu$I@m~MOHG5;uGiKr#5g6& z%7E78NTOi~Rt#zb!Fg=iiZ;iSLH|vv9__^$ERrykIM$FMN;z}`_nK!n@fH7_MJ4+$ z)hN?8_rxV#L3_<1U3tzkzC=hQyg9yio>k6 z?HA-xUrQQ8CdJv@f>{qv_*OH6)uF{%RL1$wh5!b^ioySm64m|Nvwrwu;QWbq_1jGa zFHI_-CsN=usM=o)Ha=DD4ZhAk|6|jOT9>_;Jl(mu3ZFcqXS({hX=YW0nccXX%P>V< zEL-47e7Pe__oa>DhgD~SmNdVP@FPTD7u|Xx5}7yKTBLZ4I?>GbySgo7u|e-?mA}Qa zpF>MPTsD_o`P8wLCBL=>=2e5OQNL{_kv!h<6xxHFDlgtHYrH6!hi1*K)2X3#%Ee z>;I$a%%hsR*7tvV+v>F{R8c`eQbkEEN<_*$q^&JzLHJ&+24zSAB~^wPK$$|$Z5_g# zYFYsq(q0Fu2mz59a+D-Q43H`^M21L05+OiHLP$bR&iS48uHQfW$6DEIoxS(-KF{ZU z_sdgntfP^o8^f;mD&YN{QM?47i|=js{Lg0A+>hfK#t+q0sO!=fBu?GiuqSt`Di$ss zd=|gf&+gOV_>aB4KlS@$?GeEm@4Z*bj{AK{^0}BBxm)<#3C2y|)i(>uhK0I)G=J#i#z-Taipyh+;rXO9YUtoTjcy}k>;mc7-HhK(s&U6@CW^c5N ztqGZ3W#m8BoMBJ^o~H!2Tns4ryAOy|1|z7 z{y7*0Zf=ZT(ubd~I5P5o*x2{W8r~B)r;dHS=C`|6J28tcmJ>DVx3xA;{;}hWqf+|m zb%)+|T`gPx&r6;$UlSJ2N;gLVv5nRDemHqI%wf-scXT4%c>Y`X(Dka@A2<7UeAfsL zfXOOY#ZD^w%I%9=58<9)H)EPHSY-dp-**GOY}I9#D&P}rNzrKT{BgD^ z#HYqsdk^%mhKD1)l7II6kuo)(l6@!h z737u!?H}#kol!A3O-QL|>}rtFcU|tyk9Eq4^7cNG71W|=A~nBoI>Fu+pT&R6rZ1mn z5*yY{9F%!oM<3jKbII*s@$?qvBvnc3l#vp5O08SpkPh&>q&Sk2-mVjj+pR=ZN=R5~K|$nQe1q&lz8t}5vyzMWJOm>^C~tgr|~LD zjJH{B(E-(9Yty-Emz5CZZc8PetXzt!NrO4vlzHa9Dj!g4dA~K_yk(rx4akz&E7e;H zC1bhnFI1(^SF&h!)BE+W?D@9{0YS&7;#bPJl@`M2Aa1b)->=;<{BM7b^p(B2SISOt zqJe*K*R6M?HfCgdn)n{BL%%weUfl#vXp7Qz^26}~^~-TZ(_5~%aMJI1m&WjKjYc3s z{c;U`Ct!r+qB4ky9~^1*cN*Ub7%~LC!l0gK!zlTWPyDzRXvs8ClpAV01`5laI_;d? z?t2OJFJVGt+3I?79R2(jzhKQ#3R@vY@?L%g%}Ny`q>7Qs>d75{Hnr|5^8BTC;}2Kb z`j1+_j$gUnXT=`qV>D$oH!$axzbM?*YqY%g+klU0HY4{e{onWoX^r5tTG15Lc+pvM zJLbPYmgc+nH`sQ%(O{!^2W@yYSTtfY=%_FVsEBGHV{5qqtZowQj6o?^^Bv`>DCD!R zi+uWxymm$9V9<`)X~O#ws6`t7qEPaDB;$63g;02dv6O$Qk@_mwAJJ^j zZ&dih`A)&K(c=gOF}?WR=F)HlMOm1r6*|Sfls~(=spa+=#$4q65MJB;09u>T2p>8B zBm7*^a=+t2|8h!-{lJ+9dE}f2`N|B-Ox4nV;Bl6( zpPTQeGMs0q8>A6zLgx3eD(9{%zkMN0s5FXe!(-O^5FK=-IhU!@`+4&9%7MH(deELu?~g3h;?Ij7IjaL~YYfxCtg9 z0fQLPSE3ikS)%?dc+iwKC=oO@g2?b!ts)yI8ra$$Aoe2afV`_w8RyxAMZNW_F7d+cAB91 zl(2T5g5b=5SDB2lJ=orKow4?`#D-<{J^(nB!*el7c`^^Y zZJe3=J+NS%I}LZp*Bs@NwbW_}Kjj@}<@>?9mGhy(o zh$g_R%Ax6QDNbDr%{?PV>o=8{)Nff-ZzZ$@f@>F$=Frhx_eH~jNb^09`9K|a78dCP zRltg5!g`^qw+We=+EtB7tp)`Fv6k!QTtr zAJU&YX@rGz$M8#hVs(_1v9@oVEE)84?wRIVN*MkYZoJN2`y7yEc1>!A^da*=OcXhLlkn1ZoE6gEjqev-%wgm8tJKpI)^AGGhmg7rJGo$aZ{x zbfAn}HIvIDcGn@%h@aPdAJAHzH2hm|n?z!w4&WZ~5irQ{|i0GYob8YH^ux~e6Z49F%t zMY1IgIiS0CehaZA0^E~xOD^55`O{Q*<*33he&vGt9$LS;j~`0ABoMh? z53SGd!`1}YB*aI^3$cp39@{V}5!A1fG?SP>&DT|#|EkQI)P#45ow^zxVicGI2j(%8 zGZ*TMe+v)je0g;-q1CDwO0W2dRW_d4aKEmYE4dz*w%1fNOPwZpZT61kopS{3dYo$< zi(Eaz`nNZ!T$WEKW*TG5=*LLvc=7}Fh5fg>CoViEaa2-q2ahK?Q)-LwHnKNO?ZoGJ zH(@Wgn~IpMY8-qn`;GDK0yG~@^$RPLZ%dpHRNe8v>>GJ}QqK(^i|(7TrZc)HjA9v% zQCuBu6}z5&BfMB~G%zjOG|Tst{`p zal7Dcv-cM*kC`cfRRe2co$72NHS^}KwuYX@M)UeRUHdF?2b=>+E++UB{f%-vamQ_e zH5N{_mk;rI61&0F0G~n}cxIsXm}3BD9==9ztG^d>shr@Ht7K{X0Kjgut_g|-dci-Y z>VkE1m6jouB%wm1_fF>*vI6=$GdcVCkNI9E3bhw!2sVAv$OGQ%7mYF^lF@>K5>c#eWuW7WEL z7FT6bmqO*(n5ejzV8T@*_3jHL#L(_W&9ESv>MbuDIvSC_VvN1c zCr!Z>K26S)qk2#J$+{#>;T=L`R&VqSGxeQD!q*{`S3MR=@#N>Pul%M=p;ffnPu;)J z)N*6>T=PJWWN)wo%iBIXWsfJvXo~t^O#sa;op7n39E!#1ewAGLu5w~&_!aK$@Oy-? z?qmKdt7qnPyMB#i#Oj#&TZ26w+`~VeL4_h@tX+>kAI4y*o!~awElfDH%rFi+or+2< zmUO|zP3Tc)Dqnx6`D0L5SXZAOAK|x_Wo8hnZ@;NX-R!Sv98z{oY!@hY$MP9>V41R0 zM&Gn}9QTL-LBm@?O$n=*tc_foMwL@}U{Afb+XXD1OGN0%BSZ`^l(YT-TZDIc?Q?mM zC7}5z)$w3vN{4UAqhso}l>xMYo6SMdEEfY<_4;;hI=s|e;s@Sd905ICMXOE2SA5D* zv3r;N`x18_$9p2DC=0sCGrs?I&QGGst1j2SEA`JF8+Ib>CwjWow@KC3pDA?KpD+Q% zqlWdifqqtmH1AZ=%T)r7(}z1%l2aG#4T*~m_QEta!ThJ{K-p-AomMP*Mt~+(mTFui6CHu?dP-di5v6Kvqb1I#fsITsVX^TSzh&>$PH8;Vj&G zSzB-*VfA6YNdKz^?5W;N^ET#W2?!U{8ka|0H&8O5`M5JKy1Nq%ARXU-@O$`Egt3fl zr6qnxM)F~657+h*pIO(Eudo*p_A)V4g6Rgjb(EVX zSkyb^^;7od?8}X*Dj>DL(mVE-@lWf|ywi}>_J>o-q(>pn!s<8GJ6p!2GHLDTLEiIg z-;nPTMtMTcL}}hL2@>oaG8>)R})jB;G|_w;c?tPNzk@Y=X*MNcqw_PoY~edZ!?mrmQz=t zB@Hu9W-a;j6epr1admY01-mrv`^WiJkh=u4`G`K8)-j>weuf83OmOwB2)a-KYwNa( zHKf2?gYe%WkYbvkivnla$OwzQLuGyIeVbF9rQ*BIp4)5`{kDFVgZX0({ao8fj(*?u z=-gr^P>#!i;$BN)`X#rYJ|H;VaFTeN2KttT?J`!5E9_#5gl9?_SXwsJ_5#Tx0;zdz ztZA0ze}$Vx2fZ`RoA4ru4=`xT>88P}2F6L_Ni{Ko=mVG&J_bR=()iT$ODLcuNH7YW z1xM0`<0buMq)CNIkXWZ54-Az6sfyFLiJYk2Es(H*HiKia>Y#GwpmKMdUlaBeEJnwj zgwTAxcF5H>36A0btxgSHybP6xODq^oh6q*ntp*AFG&ligEi^lr+KP@^%0*P_v@lCqDbSI<+2kto3O ztJ!Z=qDD~n3s_;*=vhh$NP#PrYKvJ}iI)IyW=!2UAT+B9U4VYMTa8y!v;~sYiCiEP zIfFlBU}Z+QhZnWCadwXNW5G8Wu{K`u0SMAs&*?P2CG?6*cg9F zIPCtCJ450UsR1vJN{fj5e+(yCI}tOd=b?Fhv}>35i^%&VkxBZ0qa-Nx`A%?R?vE+& z7fka7i?ZW&NLi4lv3{jee~dV{H?ELu(X_6a4ED^v>58Rs%5B2}`YnY0DgS)`6m@vY zpB2IJrd)mbse)0uZIwYz|NLD76TRAa#Wh~qidLU~VHL&G)~~{@V*likg5T+zIAziz zCUKsmX~1Hm2OoLSjMQFZ1#U>rCDJ0O;#ugfcYMd%Ut;|0`|ybeL`M{3X9}=3YS)ru zgD!BUeIzE!hJ1|Goi~snkoayR45TJ>ab03K+9HKJr)!QWP;8bozs82yEqtic^7-NP zU|uh|$g4V^@2M&9N~kGyw+N}4+I+i0kIVdVyIV#hf3vKpqO7i|qhJ(p3p$QqG=-PY ztGQ43g)1)9eHgI#(-K=D}$qj zo5SihNxQjO%{izTWW1twiZQfFTIk zfxQet(}lXVS4Q0Yvrb5pHxT^CdCXQpV_&(U^Z>eU@P)P79rnfodOm0%;x9ZB1bNbQ zMy|}M4xq%%N{5Q?>T5_YID7vwLyB;SEWX1d0arpt|c>>n*D%8-xXA>apyyrm*G zrAv{K`0{#UQ}e$IlXXiOnSbU_r0e4BO>ei?k{*<%lcg+sf0o;RxDUTz%?sWiz%k05 zV{Z@Hxbbb{&pI(#F^jL8evN<4vO7c>YAVc^l;QWoR;7JD`Z%SxN%qlr*#S6fOcVa7 z4|z^CzrUe6j_Z+u#9~cFu315i^~TcGsP}y(lNORS-ErzTF2^kL-^AL22@BZf-e}5; zIF#dwyIi6XIHi2#CpKY5=mCxvrXRdyplI=rB?wdm#$0rS1#hbKz@x+FS4onL0TG;& za~)H=0xMCisM5?qsFEMgQJ@kr=h})_6~6k4x*HjJ*Q6_DO#}c{({=7}y3cG7L3ANd z$59%O#%H%{WM$sV*-PuaVx7d?=ch^<)$MUoR$6vhu1wB~Fp~DuXe}d@5+In9>yXSIkDK!u5F-!Jb`qF)2O00$cA$&j@9o)GZ-I6Be6fYW>h2iQ zb3HZaj_bD3d|lLa8kZ9RO;+RwV>N0gNXmXnZkz@9W5-rqZtpy{${)S{1G7BiX#~^r zx*dTg<52n|1s6+aUtD40H)O)YK~NA<9s;FGiE~VeGcHB>O#Nm-G3;lW{+MH|b$6fz z{&&jAfA%f!4?;|?yw;Hx7|@HVo>f3qK%D0hhp+&}L|KHS%qsB8e_^l(cOTB6`TK!c zKM{#~yh-Nyum+AqE*5EQ*W%VmsP&*frFU^7EzLWqeL%f}!Mvm&rfHQGP@Na67FO+p z=e9UuQlPln9O?4|uE?Q)(PECS@Cs7E63IEVULqo;c>uWw-qnsQkx;!T=|o5t5sVjs zL+A;}o5JTR=b z?s`LwOSTZESMq#n(ZQUzyigjA z;N6f>MAbKZRShMb?JeVmJ{&zV6ADJzMnWOEy;lQrjxdPziGy5Y9&ylxW}4XwzVF1D z!)x`k!@t15)93`55njOVI?-uF;MnAB z{;OE_2b54&<<=@wb^h{TPU6?Mhx~|xCNqfzE~*(*$l$1!SOqTZlHkp7r_8oV4W{NG zeNJuBQkls1`pas=`u#iC_{T$HVe*zy-R^{yj{EAblbZ7`ZHl0TMJM?)S%XvFX2BABjYARP83wS6 zd@!7*%l~)P@bmESw?7-TTSb9}|I3EtqxSpTqcu&o_Vg@I|Ja#Q-RP0^n$gewcMNw{ zOeLIm`d2LZ&Y#LkFk;1XQ%<((Om(KUeJAnJ&izbcR9Q&(#iZ4^v3I(ghqEK@yOvyC zAL)<-X{O8V60-;R*SJBY@s%syWAB!FV&?7%o&}mrrWfw?JB}2o&5E(6d!h>TZ}bD! z65ESYFVh15Nz#&VtzE07TrU&#Bq|Oi4zTMP_(a>=% z6AzT^MW7JDPolcRfXpL;tn_N6l{8%mFfzs^mtjM(hQ&duzez-f_3RYQSpe~qEzj(3 z0#n7x)7~b2q5Eq663)Hn-T3#kp})#KM`s|Q@ue=msyUPh#Vr|Or`TWD7B1(0$~chY zQ5ef45@(nCs<9x^#vr(0eY9_lEPXlUxHUe9j+!C9C0Xz|)*^z(1Aj5omt+ z#VH1p#ag=N+$h%`yi!cq<3hLc{fQmPTXBL8EeVCRTb~3go zZsuh)ljuK9-k3o{e}%<*{7BJ7UhlJ0u9JLv;Z8txqFTSevN=Um;kx1BrZ4o)Na>s6HQOLX7WIJm375~FirzCIwJ)GWD=j26$v(K*2cn}_I zjisr+JYx&#w>wYF-6VuTb)p&DzXF>)fG?_78*8@)L7C3@$*st7c9xsQzHf${TXa_2 zil4*KF6GxDeXL?UJXg#LaO{HWY;w$usu_2sC}v>kIWH^1v7H}IE7J=w+j5`>31nqRGU%oQk|V#apV~N@j7m z!R-Xs6hg(+k;g-Fo0jOFm8b8%+>jKHhTg^+PQS?G$Cq3ikh0>Ykt-5su?f?Bti_$E(bALBj%jXM_nv`xgkoEnWJOt?vdVA;?)v-9cnEiMY>Vb+v*iq z4b#pcIi7(zNY2{sRX8tRYp~oTr#_7njs6P>BdH+WM!6X~ew^jweP%7}+S13GjF9lR z)Z9v^T-^xbT^o+oUql~^>)RVdN#)zE%dvC69uu`<)CZuU%*}l6_vpj|CJU_P)KYrs ziF-7yaME-?EXD$sElzaSlj|BmpZX?qBU=rq!e3Nnd;07t@_DT5>|ysNNoSXi=%?8T z6`DTF40oP_U=mioT^0)d6K=sAcqJtCvZIqiG=N(v4K!JVfP1EbauluYTBh zM!aY=RQBOUp{+pkYouf=9%$|im!BLuG`WJ*8Y6+O|M^&Cr3mha?f**A22B}*h@jm( zYzQa4W@jQaz3Ifb!5_KUeP{TMm+-ixTd4di(U!iEAbvm^Wl#Mz5T}LBH{sSHPJY<+p-X6GiH~ThN{Z{ zv)&xp81Qj#Qu0~dvget0PqOs~n1E_U^2og7SJpd!N0YphQF=Nt+GV+bT@Ue_Y@nsu z?XLNfDAyY{NWaor5>b~bW7Df7{w!l3Ls-eWdaj5_gJyd#K6ELGd%;Fh9}7EW$RB{6 zmV0InKXR`uQ;NJ;RUNIISal~PcJ(&g$~HaRo?+sbih|$1sw0@K1E;Sv>na5Hi-)4) z9P#z5sQ2O2H?`XIQoaBsX)nVKw^Z8cQR^Iyri_cj`i;*cB$Lb=BAaW>qi-JV1u_uP zYUB-K6A~$FLI&Zqn9GAzI&~#dwQ=zF%7^2C6gqju(8HFFiqMSr5}dy)!(upQb4q0W zXld+ST+vaVI=5=A-X~FI&;7J_wRKz2ZpQ?FjTd?Ul!FzmeK;Zt3i1(S@d5r3aRs%G zZnLp{>#%^EJD%vE{|tfufB4xB)94}L4WY(xp`YcJbEOUuIHi&8Hn zk-ys^>(PIXjVNv>zPes+e=Sg!e5gg6^YPm!yOh`CD=JQP5kDUPBP16qKXhMKYHLM= zEx2{?izJnu-xA#4bZo*H z`*QkR1w}|*XCE372fhHmVnLR#tesq;i|CDz)6TY|)UR6O9&HCzOUhO5F0-Xn;I$wf zGe$MM{uqc5E-%}X_UN&UV^-rc>jcjodyh-dSQUgac|BF_;zuXin5>;sF0@SQ!Duuz zhkjAZ>A6PHD$8QXy!z;{=m80}kd+YX9A_cWyg8%VC0+Tg#F~XdK=3<@HN%eYLAnAM zEYr59nt3?VNMH3v=h%u&=}{_GLt1|r+%q^RPBqSK!!mFs_hQm$62LQV-GB3S zn9rPZ0e!Vw*B+*I>k2Oo=!?8uDSn`1&ou#g&=H72qcXgi0el&dB^=ns$bvLGOw;|o zDthsAoAo&>;C({<4IDM?kZb_G&QrD1pXfbI{Ej(1D|0vf^ZTM~`PDn88iRU9JFY$( z-HS6HN5asYdT>$Fk>Vtd^1L?C7-HYNkw={9C)v7tNtSifZ%NkHry!_4jz6tty(wqr z?;>uvK}y*pwY9%TIKfssblG=1 z6F5GBPWiwJG2r40wqwK|lnQc7b;bw4lLAMu@%9-+S{S*@_LgaeV4f#VL?Pnv=99Kl z#-1cAQt$M_i{25cCt$ks?F7)GJ{ zW{88A?_q+_Y@skBSKN<1Wty9=b-mNpM|7lhgp`!+fwa0p{y7dFr{(KPgG+~CW_+0R zw?c`Aca9AvP2Wm{67PLzNwobwv1{!Oac|;@mM~B2ef7sqR~GN@?K*Z0yX5aJj&0d4 z);70Wr1te=($dwLQbKaT52;ey5L=tz+yTdTa>5Bn&i&0MLRyrX4>j;#9yKW_4MO4C z8fqKW9R++)fjoSoSkC^LgisnR{{j-zMiG>!djjVm`j12wa&ZE}fBGAM-_WcAXu0J|`H>VkWXmXHI z*d;B+=dz8lP$Wd0MgE3FL?=*@j_9CV8hpmkggojI1K_GfwG$GXB^}6f*oDNADGh5= zEX%-Isva)28JP#l+3G5jWtSDJl7m-lXC8Ba#o(ob1`8KmMX#|qj{nvRhozy{m)+@4P7EFq|O(?%te;Uk^NS6AK zHos*sx_!l@4V{JGRh0DsKFA8@z>aYvzaxC*RXDn2`sG8mXs08*4* z;oonvlSZn`(*z>VG~g;m!t;jya!hh^s&jhLc*fhk?tMza6Hmo*8r;=w>ua!Ao`&bl z!X4E*1E7Nzs`)BSns3ZVKKH8^uhj_Y2t_0dWH-mFtS5UDoIulKDCX*x^^`7-8LZ+5U`@)I7b-IJNgG|5omUa2S66G`PsUCg+4}$%qT53Q!CY^&-@; z!b$81P%+gQJFmj)W;Hdqp%aTML`v^!O$M$=abZ{7k!FvAU4EdC+hs(Jf^KQe+nAW% zK^!9hz890dQbmMOdk^>pJY$8(IF#s3%-<+IUoHM%+WfMqJ{ly)H`n78gnF{lR_q!>bf*{L} zyK@44s6m9pMuRNDqoQjs2YbiDw+MvHACYA^H7VC+0tDZ{Xc<45Zx6wCCgut+Dd! zygfGaXSAbdlq+TWfc{mnTq7U`@C&D{?^K@49=Ya3Tb;pK#e794ICKW0N#{zRs8z>( z_CT_%5H8Q-qgs}Y@=M00asv6XXJA^?)nXoYZ92J6Yb%-&yBSQfoN(MLA&rpWWL>9Ek0^r-l^sd_Fl0 z8~YLqW?6u1AdQ586@Pmq0EmbzNViBL21rPi&ahxHvjPEydBmJPh!h*tlR9-b6rcvo zHE&|N5p&pj0y%SL!HgDw5m}rA(kq?1!Ugjk^{qbQ#$3Vk8o{%0tlmABDFHtF&RRS7 z&4zMF+TYo;CD^(&CNxtqd?@FcXz|;b!lTB14Sm3t=u33t^UqhuRNr2KV;cqG^zC34 zfqF5vV_wk4=bzsHm;EOoyT&{!+XGv~R%wcUyQtJ!g2RKbY)hfjaHp>HeHXDQBap(x zmuQW%!6P4~rwzt+)qPPR_#ro`$FsaRzNtymO!>>%6no2Zj(pgYa=0K_CYxpK^8-x9 z+Nf0Ncra(~+ITO66&_|;GOz?Q@oB$GKD7>m6ik*Q(vM?o3~t`9FA#)}>8`~WvB=7J z8zwu`(&zq{V{qR{LZ`N=4J4l4ComW*mp|H*;b%5FbEgtDtFQ=ny7$qq1(=Rpqc4Y<<&CI`uY zfnIB6J<#&O2o*D6M6b=hAhj(b&4GCz^wNBUx-mH@@#FBK6fMc zTH#KzPwxjY1c=H6@n`>_S6%%^Ng`E1iP@%=zyu`rg~T-798>pxXpdJ;mkd=v#<_JV z9Taxw8TkM_%Zotfiit5Q398{S;g!-Mldvg9+kfDQY zTpvzr{5tT#;BucRIAK7;M}5IYO?Y&m`B4?PV@GWMHG0jP%QV1$%!Hnw4WSn|->H2g z<_(N3U2MN7z?9ep@W9s@Wm5i*qrU#x7Tv-`l3WCfWoVtE_hh(tj-^A(eRxayW-znO zHA~<4nuMfl;&?VX@0vhUVfSW^{4U+YWV#HmEX~vi_F*SK1!26**4zX&-TIpe{~Lf) zY8Jhf>g;D5G_)gKe{;oaMtZ(^bX4^@7(2F_@LMO!gx`*7VGK&Vd9Mt5v?-yLjs+(~ zM)QYR*8>?mNM>K&1;%^jrRQKvlq`>g@h)4OO8ote(~)5U@*($Y50reEy0ZNiFqLI zR{Yrvy%0uCxG0UNEdN(Pi++N`C!_3w2xyXdpfZV{r&LL?dXb}ZjINIyRv=qnk=uJ) zv}5+H0OeqmUA{AoqgUME1{twFWGs642*8)>v--~ErxJqni^idI-*lnz7> zXDV&ih9jtBdc%G1hC6nNSiw1~!tWSuaXkfG|6FZ8OXrbWkQvt{V}yhnlV<1FZLmcq zEw+S#)eDow=$l6ewryMI2Px{p-GY4dk^YbRu&z@+bVQj;{pelS3q^ZD|TK>6h z_PJ_ngGq7thwe95mp#Q##`DCb*|UlhZ`$O|eFHfWJe0h{|x4wvITXZUrt7k=Jr zlLBe=fB;VsjtlxQlI_Vh$3`SsXy`DQPuB8EUjFsumgfgJ`{_lprP7)pzp!)Uzq5mz!_g zH#BO9_Kq{d&s63_nsR9Y5-xY)G=(8NF1FV-jWS&^MCo}(M206K!UvQQ*7_&kPf-f$ z*7%0S=S~ww=K(2h^u*hrSF3bYM2AR7k%J6#bm0@(UW=Eu+6FhaX}1r_&IgW4)F0LX zw6=rXbF54KS=o&)%k7Kl|72T|ONhtjjb_-o5J!t&nY*o%(|TX!w>gb})NlW!9bTRF zcX(47WPjkOXv+EXHxuY&sM2E-TU4xLN!I7Gf@rqHMiep*yu8}4`?&A?qx~QkV=;6n&*K?1&z4Y_*5C-0%kt)sK zWEjNj=1xyNs&ho){X%_kWopr|r>Qo!9S0a{BeLeWB0^nAcczR~)A>r(>DIqB^(YD{ z7l0kIN{I5$-)V3kx#`~AKnMG zOf_1W$Pw>gA?Rk1SPxm0cQ0)IQUWrb<+@UI{L(*d61a6a9iuH!W+CR8G}fseeJUeuiiF$ z#Bu=Ok9aM6wo_fk0eCz-dJ@pO2KW;CP&EenPew5od5v8x za#^G7;t9lF=TQ7l($Q+w=4_ZJZlJ|$dKbY)SSQ3ecbEBS%dx5V%R1-5<+&1Ka%@2? zUGzy{%^(S!W0T=;<4Gg z^ec;hHr(K+|GZy+yQ$sx&$01k@9g(NqueyYNt5{dwj+>2Q@AThGg`5UQ%ihWnyvYR z{#`gkX&mb0^_&kYb;ofooMm7}W{5+|jxgF~yR%BVVH4=@hJF$k=YR(yR9b6q_7Sbc zF&beNY76%f*+Cv7C!l%COYuO{uzilI>vc#$GU7pKN?frAzs}xVljWNdu_w$k`*L81 z%86DMJ?YfEmvURwT!N+PM8?=Z(pE62poB6PSMh(JeP*MH$QaCmM|M=|lnWNLc}bmv zSk9}_mZqGy!4hy}rpbWAXX#;rV2~IsFW6-^16urUN4!vFUK;~VZOuhm*HG_fWqWrM zrB}R?GmT?5X~@1%yC9=>*~K#uN(-}gF&~PK(7d)c&*0Q0nAQ-aErS-gnG`jjPS}jK z4>09{u-Yg`P1?r-BKn5NT!qi@NeD$3EVlTth}7b_``qWwEm`Qv+Hd2PScExcz_hr~ zID2SmzU5SFp~=M9+OQNHNPJKAI-%v+o8QoGG6nU|@w{RvnP!?#@_g(P>KRB_@l0FM zX244|-41Jy(oB0?{pDw6Brj#!rp%<_1S>|ew29vSTJ?$C)*hgb7oBs*#+EX3wT0ZY zQ3*;3G)VtzTajE&E5P?(e8`R&tGyH9WdD0-0Xs0TCAHr)v!D_M_bdbov8iJnrM-Qu zowUl~U)a7gm-0ksOlo7z##;{YDiLNav26arK0#&pRIL(MpPQS2Xqxgfa;*W2=!`AT zrVv@eFP_YqeT2j_noEmGNOyp{(v(wqNf%0SovixX*hM5pSTzq#StO?uH#kMA5e`9J z4Nl49P+MhA7kSO<#0J1eT9d3bq|C6O#8v3l&p0{{C$eBWLC%WhrO!|Fyh7IEPx9Rg z&C8>1S_Ou3pA|TYokATbDKCQ`9X>xBVmSvLj3fte(5EiFuyfUxCQ%4me^gluMDfZP zV9f9-kvqAZWr(k5M^N@S&>$-1Y@y(}b4`f<6;5TU)>L)*=)DP+!At+!QIg~#LqOA8WAZpcI( ze*B5h11F%;kUx(?rn+;g7eNM#lhrohW=aujv4FMndPtxj03ogEiJL%gnzW*DFx*=B zJSJ*Pxcr3cM7d7hVtoRhA=mX(L3*6mQCs%?t2?bLTn3j+>QyD3k0WbGf#w4S_pq*s zulr||xZdX&kn?W77-E8IVhc5-dpQV#_5|v>Ntd4efT&zzoLJQMpq>~!N_iiH57y7< zWV8=EP8+7;tI(n<#pZUWV&Z<^-|8j{yT-%(rf?~>J=s{EwJzta<6L!aVR{I*H zr~&DVV#yzi4NYqOAN+5mwyGDl;DpiGPYR&?0k@QgP}T((24JNp@&wR~0_x z4oZInOS@3Upbxc8Wz}9vrXP(uvlTB1w&(u@oKleLo;#kHSguwv<*bRLoH2^aP@oE!6gom=r^r+b$ z=$7FjYS9^@pD|kXWh?oIZJYkWY5XKm{8#OD?nRmJ<_EYp4)^>nKo|41T}h*BgBce`TFbr1hdZ>-Q3B@`sO3s)p|;HGTDiQ%&pd z135pxd6(oDqltUF65>{sM?aiIqx3qqq~4l+c(1XeD6W{|_2L|MdR6FRPoyx3xxzDk2BhCh=iy~CbQD4TZSJ8{w%{3Nz95(sz z{-_8suiuzitp5PemL?`m@^3Tqv8_h5iEIT&hyI1k|39MMJs`=m{~!PC>Dg@6q}K8< z53IFvo0U4Jwx{j`K&Fg>p8P8WfN|JmWtcil;%6f8}@weyOT7GzXP<&!Jalu2Gu2h#yP}#ym3Tc6Q&*DGEGP zpl&%_0Z_+UtSyTFgCtY^sn5MAI~8I$!|)6-+ki&J3EQMfiAMffH4Q|nv24meuj#!6 zeDGlH(GS$Q&;NDGn^Nm_BgmvlMS<}y6?3FAZ={l~i+9K_4N+0KtOaVC=1b`;DiGdC z!LFX!p;Pzzr~Ru>Noq*_`skdPFB1hg$!OsqVRp0X=E0(Lvow0HCMfpFyU(uyKkV&l z%U}Gq!H4eRwt;N@;UJgiI}m<0&yb(AmYze2pCFIb}Mgu z=G?vZ_LGLYMTSQ{ecK7dW{bP2o}UMM{7AY|>~wqJkluP#c?IfjQ%^<$*^L11ST|x9-C;zhgd|z#G z3bx8Qzk1`>w-3JL*9Y29&sDwim4rLuU|e|B(tL`j6@q0m@aleK$#$djlW={0MrB`E!P6OrXuIGK@4us?a~gy znpj_jj>t=0Xl*)ozBjof#N=2C8lb<0nwh z(*6y=ZkJ)sMb~c~&N~(MHx}x$aN_rgAL8Q_UxHA23+YPjzc) z9^e-dXuisBqUlb`ZEa`z!H||Eh32|J{Z=w3zypNh>48w z%R>bZ1-AQ|7c3kY=^h9C-L?eVC<-ZErQ)%+`nQ#AmwPf9iMfjN5L14+QM;0ZRn?@S z)BXRnm#QZ`@*6S3c8{FZCvMg7i?zl}@eS;l-ND@StUY*=xhju+vDo|v$q?BKYcvAl zJ~Lk$6ErH+v4B(=n7JSdM#d96Ky?FMAiYOeSVP@^)>C8sGv&qpkL<*^WBIlDNfmYd zw^j`*j!b>zdRzb1bJ5Xngwop~0d9W(mN`KYS|o=PK{opSDEqd@CRh_W*Kh_)>wG&i;)P0{gK zYtuvELvqNR+1wTM zdnT$xaLmDzsX*s2D@}4=I9}CQezm+nqQOe+d6%eWt!dHhuvUUAZx{g<)ZTSp)|a9r zG}=6zKd{K>Q<1^lAYB{P!uBrldYDqxv0Y}pGDY6CHz$+0&Au~v%!5#Wu;n)|T}R0J z{3m%j-rGPn+JA{eaH4t$bgLK*kygZ!Q9>i4&~eVB+qhP_P<<8ep1ci29=X5Dz@M!W zZEN+E7@W50gXsx#Id>gBulP-epiw!+wXgZh3%OdggW=G}>t_pyp=TS;#Qj2e6m-mg z;P1E>Xxm-=gPoy<2k)0BI3{{N%v}Eg+G;W`AH_y1bDXK`%WSkoBDkGng`zujM+$on zw%T&jn!x~9UEJpfb(Ik#Q0(&R-c<|T)b(i8hqU~gDOgq`G*0}Da^_v?bXA_wQ;Rqe z{TEATIel}6?U)fzspMu|W#OcYB}aGGzlFB4M$7av740#DfSw_8LvL1x$cd&T@a)m~ zZy-iPikXS8!shb6K~+-T6tP1MDlQ%a8nq-lYop{Ff`bn}uhRQmzZf@^PC zS~X3NqtOA%sbN!l$}gBfcwVBtnoKrezsYZi*iY1g5e;9d#c~4k(~wNXkq2d*)Bm)?e6Ga@}U|#dNsmcbO;mC&Wc8xLz~1&OZOc z&WU013*ifDgUp~@)Gk@{1yth9@QPfoYw?^#H_4e>`(AoH>Z~G1>qbLwcZ#a>AM{lT zHyLtfsI2oY8;em_!z?2IjWP*k$h!%F_cAoL5bSp9(98P-Z5dn~u|QOY^biBga<`DB zgQJy&M!0L_#4f7eAJ&i$5O6~|$ifgZwzsIjFPGld!BfFrTNn*MZR*}5>W@Sh!)D@t zD^k)fjdV|y1Ghw(wlb+FH3b_a0m9D5|30(!Yhv?%C5>$i?1jOZ6Mr_u6ki?ODd#0d~qwbI1DQT#nJ)xb0pSqU*&Y6{|6cJc|=$ofFmJMy0=0=zMV@uMU zPRI(UQpnNh*n^3)7g^_;t4sxy^#J#ifod$ELQ!C%{iJRUHSDaFF-PhY8~PF9Y|sgZ z@Ff0Gs-pAcH;Id(f_i9Ytfy}V_d(G_!-Rg5m*b-YIjMx5P3V@`p>2fAzzb~+L|PZI z`^m)HObchmQ}1<*I$4nk zLw>c_#4#tA^N28Z&|hTTT%~8oIjNWxx|W)WeGIqaKV{&}zm(DE)37ffgB}w+5KJ}l zeTV4NpAP1_OOi%w3NS+5rc&CLAVZ$iW1l;_Nxk3=oK&h4<(dQmvAp!e#d^1)4j1y- z&9W6vm8!5!JK!c)l`rQu!jD;2k0{W0|90`Q3kMoTduj((Fxn!P?K524Wrnndu;<#2KM(Ru*z7)X!0X%`^s4w8fgQ6 zjE}~JLz5qfAnAuVF>tD2sBY)w?k}dqhCnBiA!OQKWY8MT;xE4xVJ5Rlgq<#+KEzI? zt30^_xW+S>v1y0sbDomoTlTkIxZq3B3`AmoUH*q1EV*u2cT@xo&Wq(GPa6HG4Te0| zaEGKd@?ETM-^RO~5B79iPeMsENVgrJyvgQaS#JbEz zI0a>p*K};`d8bd|x+a{#t7=d8M|Xb8Nf*p~&buzl+p%A81d^xRdlvRewcY2Y%f4qW zot}q_goEeWde8k5MrcTTn(^k`ir@yU)}Ih!2ccu>*z223NM5A+`Rc@Q%$@D1Mv5l- z0~!Ry?eVe1=Xn`-MPy{`{d+bZx=#3R^V`X<>07G(XYu<>x1p!TvRTo0MZRZj=qF;# zU~Z;{ZA!2cfQE&itDh8Cc2Al{FsER&XM-=J))UBv8@G{x^fJeH-p0_^Vx6JY&cW3n zyTve!WiKUR=%)Li?1~N|ARdNc`~Vf{L|Zpd!WcGe4gXajBBfsi-o5{u^(Hegkv659 zorIO^MIjR2h^Sdo<{&X)x(#kT=+Lx@g;(gIWnPEpIsrnZ``>L)v4J6Kj(_Of6trm4 zXT#8ur;{L=eX@|vag-@j?tXMq(uRb~WX!5Fr+ZysHz3b|JjyGO94I?9gGNocp#3~= zEnoneWbr{`{{#@hkpct98>mXr{p0ckojfZN0r&O>tdT7HO{}kf(hqG!zQG>kl%Vt( z-sB(Fz-p0TrzI(<)z00iqfYz&v_n=dcRGFlC&F)n2MO5^EuJ^kcE^_s7p?YO!KcM) zDSq5Jy~R0)+fNJ+?oZT^-4fcMG`UJW7U<%FO`c^xn|@Zc0JeS+aUE6L(GdX&TCQ)3 zy0R&6!jS9wGX8$+LtFX9!u!}%N?TW$&B{Vb_a9}@y{LcI1fQ|7>_V!jvw;1ki9mm! z;A&{BjmbB7VLpm~ETTGrhes`D{8GJpI{$gZaC*6M8g9S}I(v-0xHA^@0RwqlnY;;< zu~AO>|JJ9eGQp2Szc5s0*6U|@Bk#QN_$T-YWJIqT@z-+G4yAXSw)QpYAs_N1y`(N6`~))PXpipq9q&2A=+D9d8a6t1MjE~ZDqr|lZhGu_cf-3a zvbe+Y9C2v`k6x`v=z;88sO1RqZU=2mVSO0jCw>h~lS(hb3!syWzrv1}wn-BVGD#!z z;uI_mhve<%cy$P9@EP>)Def`>#_fYH2h#O34ajR?9*0=U$x_?wz)?7_1wftUf6;UF!?8rXqc-!OI@|*#dri^XO!7Ib>%@Q4&>CcN$o^#dE}2O zZtcla;UIiM?e6C76qza3@+D4wpf*5hR2DI#=NfricB0#oqDtvW-JwoEEzSQA``w zLFMUg3rqbP5k#m$iVRmeh=eBNE_eZ1`>3kW1w%FbJFxHHrR~#k!>tAC2o3-2b5@As z2}I}>T@m-$9Pj8&JpGRJ4cKkYzSVk2o%#< zfaJ`q$}oaUwAf6^-zrv!h{~uix$_`(0XVe05+3Gc`Smh_xO z6f5L67M>h>aU) z%Z~p;%k#8R>w7=t;7k9sY(03kC+TFET~FKE>BEvC_i`Td`LP=Pt1m1Kx z*WDgonv!ZBEbsI4b@)q|!T>qS?6pUMdaJ#>+ldEyVD`E*78DxW&OE4u3li5?rwAXp zwtfz*lhde>e_|u4*hXs8dDptZNIJTQnlSklFe)mHOwC0`mBZ1P9}PK;AXi-F!<}?i zo-|9XT!Mo)wIyCl2Xn>``zj0knmRab4_dD~$vX3>U8C&%N%++mGyfuy?8gOtANv6! zkCW~*Rbe^ePAQy$!3vD6mH3CV`Y%ZUeZzdNEmt~#T7m_gwIp5BI3I9`#CFTT3*9qi7e3~MO+`nPUSGol5PrIV%KKMO ztW+n=Z(G0CbDdbh8!c-be$3aw_ay4 zO81FCPi+V7j3gdv*(4KmVH0Bls=1#pvRv9eYTek|xepG^tPVt7=lCZ!(9F{sk^=u8 z=VBU{@t6&7KdmwH;~nGahAP|gK3RV#D{<_}!IdUaDTHO3og$w0eS>YfbeF89gRpL$ z4_3_Luc~u1T!dBBZeT@(|Nm9cRJI6U!F$ir@TN@nG;;d3bkPlb+yu+aFF<<>4SfLE z%t^MyUw3rc?$`)G0bOVZ@8^+B_<6Ae;&vQ*$-%r>@eM93dP!5G%JXHM8+-v^^B7zo z#sV;AfK~duz^@E+KVTl0!ybaRBaqYcT$lK$J|ebSk-ovnGy*X`-5gc3TdeP41BtowyQAN!HIngWnO-&~DE6KK`0zXUuSX(0i=(r4KBJS=pPZkz{Qeo%-Q z`g8w27@^6@(&K}T^)s6i9fsbJhCzjLqdus;GHlQ96<_c9?f`jF^!XjK`ITRN`pfqr zdlPJmLdbJWQSV-GJ?7G^y>`@2h<8{R-^OCLh%D%JZCtg;q*~s*OZ#q{;Yka#UAcz- z2nYaiQ6Bn^Mr&LzBdM}$k$4j|f-!20#|G2t_f@ChGnnasE=BaAie<~)YTqOr2305L zV#(9d8C{4Ymd2rFTyNS%o^6KIgY5(JtvBM3!$>o%b}9&>FK|bQ$&v4ModvO1gh|NMNuZj+dty#wyUmL~^6+;|GS0b+%lT1}F2L z`>+h(QV%N;PSkn9#sf*No)XX;bddqxhfy8ctIqLRW;{GGwkCGNFnR=j5RrYaAzC5d z8|foqfAndDF~RoOBA;ar`KfJV)R^g0{o_X3T+R}nt-{&IA^;vB1-B9HqA{IDaWncH zNmC=f;{1iVc60O}1i6Bv3cSYtuHD_!`tP8V_gF{=mP@?gnc=;dMcDA4joj2R`Fc`0 z(iV$U?;@#OUkKgtHg^W9?jx^+6JiV{Qy}j;sz@ZT$>DB4_8#|{k?L+$l_71%jEjlv z()G>i5+dI3IBe~=pa{6lIYJFw=)$145j{Ti1(B>o67pVzSi@SLNp)sK;)7>kby%#~ zjG0vNJa|ZR$0wpmKSE^9QqLS4kPN~n#1(pG!>i5ixZ-p*o~qPS7FM!dqOm3z6KOO@ zav`}0JH*@dC>x5)=)6tE9%o8lH83gLTVbA0JDM} z{;16zN)16=FZuaI+vsKSWHB_-YHe-kg5}wZBu~85g(!*%l%SUb+V9eI7&#TAJ${e% zxSxrCZ392E7QZ39_-UzTQ5dIl&nNbes`#|e6q`?fbvaLR^H^E#ZX^B5>2ICCN%nr* zZ{xH5U~4u11<|(4@)R=7k3UI&C0-Zfd%Eu^kpVj3&a~~xf76NW%Fx^@0|v)u0yfgF zes_Tv)kTX->Kp-ir?b9g6{g9lDh>1{WR{taVM2$8a{2do>Ri^FyPPtO_-aKDuk`g@ zIGGwdG6)+!W1Cw^+uW%%x^do7T3FLbf`vi$di6wwdTcyImXo024Z~29nS?ju3+Rj? ziJgp_J9U*Q9ipeOq>6W6@X%M%1dTM}6MAfvZyR7d#qKSQ0(sZaL zU`(+Da!A+FP{Vh`WcnCqKmQ_|F!RS%c>|XG%8zcdQu1UIpLBcCQcfC^y*?K_b^PRs zD)(3af@>u@{k4z0Hj3hoMl+w^d=@h#_1Z77|3W_Rv)$prK&&D0j{P@V$Qo@$nWy5l z)aR+>eT`G4B#bq?G)q*szgn$zOW?Z@X!NH-zeZcUG!Pk8E5kIG4L9kf8uQ$d$#F`k zO%l0XeYM4mYz|LbBG-}V_ zT{mW8W6jO)w$~=A*!A^i>3zhJ4Rf+(hFvp5fx~!*YM9r2?xn8TY_W)(n}LXXTWKj- z?BF>xW`&)%*oyo;cF_0He68Z#_>VNM9g`Gy37%A+9WTPs*4}Q!bZe)!1ThrHG2WL@ zw$KJHh+J&{X|{&R<8#7m#7xir7)cIZit|U`F^n%sUQLVJR&j0kE?DZpEsQKSuAb1Y z-nNCD`QDa9VAv_wlpSu9j=N+gd~)zlmotsC8-F-dnDUcCdPj1*ex0%A!GxF}kmvK$ z{b*pic^qH2s^NG^d0m4b;}h8U`^Q8VqA(z|pu}_Hc#c;DG7x(dwJ@z>`bMezPgS^M z{oBx@JtS=hd56Dk??1M}lz|+UzTTYIv1@~UUz>3yPX`Si6%X~j zboaZTpm|j+KlS)qswS_%lg@qh_%#Ub`^ZH)rF&@&2hpo*{KA?P**$|nnGW{_F4$TNKbGl2|&z9pcSSO3glcAKL7 zzy(bjb5#guWLe^u>(aO}8B6_In5WAF5i}X!i5?eAwy%!gIQ_~NHXh3tTieN}bU1p{O6@y?>uajKL= z@|-6pVttRYU&7oQNl2u0N|Y8183q@Yy1$B|-|Ren+G&xe=ZV7J-gPyOo_=L_7Tqkf z_*o*IeE-0$jpD*<(>bK3zj)q5u_BkH4#XnIMdyox>Zzsd zAg-FfrdJ3V{g0B3V&q63Tu~Y8UWk zghD*Y1nVVnylF#5(kIzyOPa_j!Sp7BNeG9=CRL7Z{85tmOk=n)+|XCB#%v3*;Zr|w{c&9 zq3b0t?Hzoc;;BRz^9)o0dwrLhpm%Yje}l`$JLA(vU8uT=zpWXDO8Td-0kacHXt{)ByeIp@Y&R?aZ{t`=;NBE18eY^~VoU(CHlJdMH3|M@P}y?98VR z)_np70}-qNH9M|HcM1b?kU(loX*km6x(jh9N$VMgP6{%u$*mn!X1((yFx-WH!gyzxvc&cxtR5ceS3N0QOIKl)@+otH*?=jXyg}^H zJT3N*k3xHB|A^4z&@w%&5P3fW|yvS`u8`)LtUanOj%_sFO0yoa7&$8g7_ z>)lUA42+~Dos@Q`_GOXWN-PX-<1yq;!vvFPNen^yUorjU&;^e6b%3D)($tjLOo()q z?LEENcPy!>Pg}KhC7>_oTa-|XGuA=j&U`p$7e1V`7Pgo5X*RbfefJyZy|-9eO+FYs zXa8EIo}h8I&g+yPbzNI_t4d@&d`aHw6kzF`mfRL!EzBBeW!DK^sKR&lI)404LcDP? zJ2tADf$XVA`nM9F$Il9xaSKW^o%->aC~@2n+FGSr6J0V=ff7$}V#>)88VSGhT;HoQ z1WA3}S}7^*;J@w0Y7qJ`Ur7j}%hjUmEOq}@g9r94c~AgnbO}pESem)B`HU97mNyI& z|G()bWs;*Mt8h^{o~G==_L|qGz_(J`NVA#$sZ5D9_RA^5(a6tx;$>owZBj9K5V<2U zqf&Wq?+BkD-<75rHpR%4%zWz0RG2^akgfH(z^Y zh|!-DiOqarJXUA8BC-VqpLO%yQR2C)ICn zbONZ}p?lu)UEmF-FWr|-CC`UOW6I}3RWYPYxZY%S&3MrJA}bp#d%3AMxdnR4F(%JS zDb2|zI=T;UT=goOMMbqmu7Y+ab6bso1iuZ_^?&vB#S_GimIG&jB;Nx4NRlJ*uU_A* zyp${ns`b#&7;3|0XLXv;?cV%+MRIQc7<(f<16#uOMH4S`kR` z%1YwvY%4qo+z(9SewY~*j8km`PGzK-a$Y@{U;2&OVpDyRhiFi>3(l}Owp-hjcM>Lh zb(@UihcxYPu`WZ-E&Ya7osAh!?2>L&ZRM%a3PHVvj=?{>t1Jm!XBCTi$i?mY`Jys zDl#Nqh(MEHJVw8&)5%Hg8AxRFCWOth?QCUx@miGf6^t~91NR;5la^>%Ey~_7qJW2# z;KOh{YmVdlzYr-&MNE0@KI9`G=f0}Z;}u#qho+*YzxJc2YRzt%8fi2a8b{JO$IQz& zDh!yHyq;5gc;bR2b<*L4%iF>B-{PS8DYm9$DMbF2c!6t}qEUTgJ34kPu+#vFR6~;8 zncgh5@Z3pcdIEZhxAuY$B7lyx5EexkRE{O-OeAX^d9VuA_$gZ`$@%PDO5Bs$*ca={ zG?l8m^=4w}S#1k?=*el=X_14Y#YS)9r8b9j`SFky+efa0N_W4;ffz#lgRj*7yseeKj?p42Z4zPU%E9PVA(kwmi3V0) z_!2c#molgMN(|A2FMzv87sfJyfDs zgDKgO0nxP}+1UHd9OGI1#(9mxKyx2MKWL4wgX!{%)h@Dj<^5117b|TCo+1L1-A_^G ziYV8qXC)}|vfkiXq*n`9dKAe>8{<=1bL^SY&Q=$AUcE-X!*KQ%B!5xwDG|GvWxW*T zkmFyss0C*m)VzPeZt=DcEHf=rr`~*&l&@9jg%_hun#IyDW>XKJyqM`l zY7wFaCnhW%)$<(TX>AXz@3oD3i5?#>?eeO?BL zrh(2!xNPmbpPqI4Ctz%0z~?#nTSGw8mOVLPIhP<_ z>boIzE&Zme(zKLCq^1ftpIm-`d_rEM;&+lpatp)L3nf0QwiyTi3sj^B_)B_krmp%Ro zBBren+1}Z|>08S-U;D!y8lOKkkC*rh1e;!3r!ifY`7%o_?J}EUbA6ulhe->|G}>a0 zdO|Vfh~1p~fvKLbc82=}K5``l4LH*UpIvaF&L8?y-7%NeRvFM;{dGBKpeBgBj_lY( zQ;UTZM@pBa&>;uI9E70y0*?03~P+O={ib?e67BDmMT@yV8zC zGXNT{LNt#I5h(v%6-V%4qH8P*QUVg|!>5cl!+I>O(OsnHJbbE|Hp6$wr}2G72G|qt zzb>NVUHSmk#S`J4Li_|CV%Oh=u%t2T``edFV9F6$VUhcO^==!@_=7cUjh$FXmIUojmBw^%)N=7=#V)e0=D9ygl@_(Db3P^lZT)q7XPXVfv+c03(aYgp zJSZF}$rfRL^h9c(AvSm*^&pl5Vb9YwH{-f>PGo8z&vlVi?1cWsPZqU{_G}(^J+uGK z)Pvd!6UPnDD3$&t@zIRO^JaCP{V2ohR9ncsUHC`g+!p!@d-%LH19c}ix|2h#J6dVP zwpC;=pDi4(ED61ZAl_tu1|USkaqUtnj{A_)ZtMqVhf5|#&XJ%kN()h{0=HqTg#3O* zjbaNj#BJcp0>;?FKeVB!T9*Uu^fV5r?f7D_(8U278b*{7FS}?BVzp>$Osv*+N?Dn>FRA|mf?rp@%w>sZbwOSQT!(1j?xtwb$Xdn?yTm>+O-~( zq;C~6A9+dORH2`bM|)~py#0g-nUXUlMms*!KLT~VQFLuRFM0PFL+@Ynk58?`bJ>>I zoA}FTt)@uVUj|EGJoBYn2ELUhKl}bfPdBMO_O?UX)`$Knbw0mFEM08h8o=yDawVI2 zEtL}AzSoGz# z#F4J`ZkO0w;dPAr(y((2U`N(V%Av~lGxvTa$@X%*p&Bd>3>J-g(V&Ubs0XDq#l~`@ z{Ju68n>c-eGt28Xy)!TiH$XD~5CrTyPM(l@&7)%juib+oqLji-LymluN&MbY9@~$M zQ<|fZ6D91_SYc5a0}0SW8o7rB@?z7FVmGEOb{uwRXv@fR9R#-QyO9dh@BN#GA3Te`~OUy7a@5a*BF2U8K?T@Wf5&p_1RysJYu1s-+2@BnGX^+ zp|{lv2zK=3g(AjN5RqtXNkKZCEd%sh2`(?$o@~Junv8wgpes>k$IlA>Q%<2DE|1-X zO@a-ZT^GB&jHVW8YQTT$K>)f;luLQyPW%8Gb;IqM)O~0`b~}fuuzybp*H{N81+wD| z96?1<5cfn$jP_5+Jj|!T1fmt1gj!5_Tyw|vqVv!>muvssBa(hE|A0fVP`GpW-rmVe z#)Uio`}x8ucK7Sp44D2HOSK-#fJ;SGT2ESepHZs!tVm+u^PhCYE7{Z^b)ou$i3_o> zTUne{JgYI*#t2V3vUV%dahp7($YzZnDdf<_Yau!Dp7j#{5-K-0^ILnfT3AT=<34~( zhEbQ20Azw66fdI+M5*+vht;-TqJ4g6a4iZCq zI38mAz|-NY#As%s z#H>Ga%{xSEFT}to92RGO7&##R9hH&4>jV` zoq&q{bAB=eKcSguSqSpUj1PS}p`--ni@I%YrH}&-C%>qO?bZP9dBQnt1LLXTrT%fT zn|U!^L-7{Hb#LW0^pwk$IbBVLlijIYde|7goHsdMkxd1J4#7#%(dXB~W@tU)M49qi zlCKseJ!G7lnEs;t#j`MXCPH^@xa0cjR$&C)doeXKz=2_=M@$rMHN*`KJ&Nf{;YrJ% zh9I%)$Yyk>Z&TaBw?i!=3NkVLI36ySLvm(NNv1KWl=_sbuK5fihqeYsyy}9BHkdEE z7zH*wn>>!XUp*7QGze~2dpMq139%=v+rhrz8Oz97>4IvF;x&zz@LY;@R*GG7o@Te! zS8gZa$NLclxC}+)09U(UX0sD$WT~$)zxzNKmGkaHq3~1DyUaB7x?u} z^uMu%w#XEf`MJJA>r|bgr#=KDT5r7X>2aZ+g*9N6oH#RBO(mwRYRbu1_F$A1e@9YF z!wgH=!3$%=_|HvhjU7KgDxD&6U&2Y{08mB$U^QbzP#TKEmt&uiN z;1hx5l__H^?2SwJv#_^R->X@?7e8hF%^&zE=|MMBbiLf4y1LlH)s}5oLz?}qeCbb& zu&>8++3*jU=_{$crQh26GIlS7AQP{>MAjS^TVmw<0w$Xv&(JX2DG62(JgPDH4@9lj zcIMkc5gKg)plIb4>h5Urt_8iC|B|AqaV7~mh)|m$IzVqr_vA)JCgsDj93D#3t|y27 zyTTA5wU+NidG1(OJ!YSy4QPinR3~=;>l!y@X&sn(=MRJ>yzF#CR;u30m`AQeZ&~z|%-?GMD zXVyv=W%U|G^jF0;N!&f2(YGj`@JC5toh-$D*|K(EICI^0c+4C1ssytUWr5bz7CFpk z+L5V|=UE{;JNnj}kJ=tv0oNR#Ne;Gs5KOjs`}{^`Q_n+&g&K%?XOP|G$GTOY3p#J+ zr6f~dY~pozkG5@>MwU1_F2Oz1O*ICEDHryjUYGVf(m!us%1Z;2FNGDHHE$1N+Lg1h z1Z}+9aIx=dQR+c$DcAh9ycCZ*mM^TgF$61jV;peD7q}s><8^5?a=72`93`dNi6^xl z1RLirZ?Ql_%*Q*uUFy$vjCs5R1!a2GQz8$ z)SubMtH)+BlXija5kJhR*QG=Z-x{t|4da5%yqbJku1pvFB!M^9E)EVgJEMs`_8Itj zkIXMbwon<}gE_CA$xCIxM+@`oxD9Ftp_fn(s%KvJ#z&y9z1msUxAvg1x{1p-PhA;) z5^sv{zIrb~)5K&;ysVqtxqV&tQ)^=A7bCtMbjNbthLdmagoyK#O!fva4V0esd=>92 zC>wb3n{M)_?%JGQ!l32I`hR!SDzRea@)~8;qSDXU+)KLGnXBiPd}ms6*6Ik+Djdt( zJ*NPdBe6ZD6qDNE(+;Nm72Jj^F?_aG1eK`)@k(BmIPXZ#c$$QX*U@L*m39hNGgf=2 zG74U!oolm_P$7hVCt_4XY1d;PDebag7si}z;DVIm1-mokXg%$blYfVlgxlit9CkDL z#@sXw9Tl~)&0z1F3V9ELr`GsWte-Z+CN|)nVD>sO(Zgm_aagbIHIF);gj@Gi+`xm3 z%3+>D#jslXW`UemhdpBTb&8tx7R{&#&4R{4kkJrBWllGeEK(7C(`j_F`Ubm)q+&-S zL~vXtrsuPXf8fR$BI?dfcz9H_@x%IeqKQtni5yvR3$h&b6{@sQ31&)1Nq;%Ljly#I z(X8x5=Xd*Vup_2v+Ix*Rre0^>OjWdAYeGu+IWkO>arD{0Hk!R`Prb5F9ew3|?EcL< ze-hyb1~Jq;(f(wSWHWic%a#k?eQn`U>Q&!ZI2L44*qq}5WDP$TzDSKlp83}r&hG76 z$u5tV74oACONnM%D9vjF!~0Gh{Y$2*$We*(f3-<3W0Ew6_102w?EaMl(4AMQMtsvp zPFN%%#T7bCTlaf0=&hfG12bu5ASD!4q9lE`q{*@-rI&ruz+;L;dYSg@`P@AuSXDtdh4ist5q@&S6dv^|=niTpkhVX14x3l78aiUa~=*7|B~8HtP> zc78)e05~(MqgGMs&{1AmxrPW11)h;cG6(V0t>=n(6{b9fcA4bkn~*Mt9q0B1zKUFi zPQw4@p)i5*@L2>Z#AkW&q8e>N^~$nhKUhM=Uyl7>JWMy6E6(_(!@fyaI(0s{LlI$K z6VS6VTnf^tC#KkB2kz~1%_?$uG6N15PoNhzvoCP@C2nf z8zlR5L}`r{5DCAV7ehO}ji;)6;O?@GH+<`)J)Uf}T}_!$qnR&RiU-2mKUqKWhFx^F z;iSWVEFSQShHlQjPkAPaW0Ej0JLH(78f~xiuU>vL`v8&G(CtZ|b3PFff}o=@GUp3- z(uTLiKka|}XhZN*WH-8Gy5YBABH%O>YH{%fybWBYHu5(Tg3RAAYSXWQaMANraX67>v>YSB6#NOMfbxku3jScls(=h#5jSi5xSet_DrD~E+Ng|fo{ndYhvBGL2cC-x zfCza^yjaIJDD}DV8wEh4za(l0f9<;xmwg~yhP#g_kBTIrnU6{T@br`ax?ZaB+z*Fd7hyxo9y3BL#2*P%4=M+rd*lZwrz;YeCE@17{h|cS@HZ!J zV}Sk^@yr5^o_=^`4fa+D*aK|*Per@uNWW8=J9#h(?PpS)LdhbHvap;XX?2pXVMof| z^VSIHQLL9_Ros?4+0`)zg1G8bBE4Q_44lH`Ng4vb{_bZ#L)YwD=0J;nUF* zXw(TIUW>q|F>72xh8wX)tVYqzsO@(IubQ|i(P3^bhi8R&N)ygd7_|PgX`exts1fV~ zu(xUq-<5`7XM2rh%5JEk{#?&>%M;}^RCY^@&aH-0o43C4lZ!PsYvPcOiNlnlriRy_ zE;`El=~K8Z<080^C)F(bor0|c)_X*lCj`AP_%bq)*n!XYqmKpjWNOmep&M}B7;E+o z@sG1sbA%gn!8-8FMO!>MH_*3v?@qOI?8IfSXAX)A)4_`z^a`yLu0wQMog=x#8ND># zPto2re@li#G}@KdyqcNp*;Z1ejBU6%<)eg%1Jf@<5HX7!{-dE^CItKP(sSK=(S8S+ z^;4$EF^dDmO*yeYs6_9E9?!q7LFV~~L+}Ii1)@NiVI*j2QvLySFv743ELVxSUKuZ9 z57g+CGu?Zw8<&<5G7Ya&+9QVXQDQSpC?uZ2i`|XodS)suGxZDD@XuAkj~bez;b$=v z?zP3R<0PHH#`a>Gb=(APx)mguUPs2>nQClWKEZQO|o0PP3-3)rd1Zapk2nT z-83jQ9Qhm|Td6gi=41L4o~!W+b1~nwJr)V>7E?9xgum&M$DL#UUR7y%@8_S$z1(vY zqJ$Z8cGFXi^*Y-OxOrECl#q4c_38kXR7*-&M+QrYL|qA$0!oP4nzMj)f+3S*W4GCnVeNrnk+-z=>!Rql3gU! zQ*U_I5Q96;mQY8c=!l{*_p>XMWCs3T>?N0j1N;Gajb3eda*548CX?TS+eAeRRWhap zaow8{ekBvA`s6|wyZ3w*BW<;^G3`WXdqv}XCGAfEE|&KhBtN`L?>;y6Y~N4n0)M~L zN7=s6fc7TvNI{x8$o$=P(R-38)}TzW@{>P!;Y7w(k$HvC%p-!>cZ%tu>2G5*6s2nI z?pf}a#80{uM8>Ak!&0#MdMVu*WMl(l!;(PU5ety9uh$UwAsdaU_!KuM-cj)4Hu|F~ z@keDS8_UJr5!nG0kY{NdJb{X|fHuFA+=sqzQO*)i+r*i9V?foy?@4GQL`fqys}_~> zW<84Gcsy;&SBq^jedmN^O3Nig17}7k2*|9vW6S_OTa0yN9|q$Sh7ApkDikH5iuRL} zZ+X@{cBGN8g6Uyym0JYCLO1Ri8D(eYAJB$!n{=2se2ceX(FR0f)yuPId6Z=x%I7Tk zI8E^L)U%isnN-0&^AEc3ze!$hKHxid|GHleyztH?>C&y1KU9jv#4OcZ?CimG!;3nk zL;c9>Dd!==O4fMAb*^m8=1_V|weRRJ{&FWfG;JiLi$fBd=I72jaTV4hs~Fz>RxnoM zHPpNu^TPFN+v|baju7qhfB~9%!mlMhj^CM*3Vq450jwApb>nG0BSU7r#5m|UG41;g zq$%+vo4S-q3x(7<7EEqk0HH2HSsqxnebcXzgfF4Ndbvn~7NmTR9)%dRg-pUL-@&szCowdtdt3)VZ}?4;5PENIi;3QBtK96(v=afJnAgDSA}UI-oK{ ztrA5rD#(psqJDS=O+ondHZH8ziHs-2>|?8J52j{#@;_ zS)l+~x$rHrlXn^h|{`I(*A`m!AOk^N+cJ>K*Ya>Iq4 zMbny~dsPG9MfQ5;Bfh(LeK+hjp0Z-W=dFU@{$(1dwsO;3-IJGOX{WJ&eaDPQDZZM{ z>3_U22(F_voB4&^(MACUNQPNESYeyE2}t)6b|!N}=$NM(cqb6SnF7jr!SD(^!P2&l z2LO2qaGbs5(6|5b2S$5%`Vy&Z*)L`udkCW&g@Ac>Sh%|zs8^}OWIgIsoovyv-{?YE zv+cZcJ|?mppSti|#)HJwz9oiF*nYw#x&4GW>NQ3<_tVgNl~k-lR0t-J5)lyF2kX%* zrEMh5N3K8d9?8bCLEdDBrnDr3^%)$JYn>(8oe;C`QjGso@lyJW9c)x$5o z_#WQ2<)^(B@-7=n{h7%=>&^HbKP_8Pod&I6hIFy#bIDX?6H-IrTLbB80$ZVUV(Z_! zS^#*zX~YAaYO;i{svP*#nE4=JnTJ#!IRVdPE6BJ}j{Gp)K`9LKN`j{ZA;u#8HNf3u zw__r|QPfDPs2nMV1cba#oq3Lvd z%HsJ?_|%V-${RCg9-SYrH&4QL&B!}oOW&h-(^vrX($q(X&4)bqw`{L7dq+L^5^&~b zuQ#|Fg4uHs`NqwpzEOq6(m*9>VhSgUm$*8ZKs8{Qv$@P$oPQxNm7Og|5XVQR~nrwe6s1I6TxWC?zGg`=k+i+zmx&z}X_+@xj`qViYzKtWBd@xk! z5?L;!M|wP^NDgyW8ZFMwi~xWKsOqq z$Ac5ix5$jk!frx|56Zmm3dHk$WgPBBveTC|mJ$P>aqemCQX)M2D%TEEYugNk=VL6n zg~I@9eA==w9?4ASin$F-*{;W$+yuw^Iv6_~cpAA#>Z^U)Py;~L?{eZ1Eyzgp4Hkka z8qnT!JkH{I7an(jQbz>c+w|Z&&nPP^!=)$B{?S+b=Y@T)v)gB_J575C`6UiS(OIqm zK@Gz@4H~(ns>5qb#86}Nf;dht4IRMN^yO$#ZOuJTI%~}q@A-8$+~!+@xus}pgF|Lv zl~id?B6w(PxwSwCa1!9@mRCXbv`R{$41_d*12?$7`}34QOA^7EgvdMgIRQkdhcfkC z${6!GUluVYnZG&cAK*3-_w{;ln-`LV81}n|wffUH6jfci`t7NfPcYy)?l(xG^ouUAT7aF=*t6BM-Z=ZNC%qv~;I!Ac#>>qG4Ma1P zU1UP4J>tY)*?mGu#2Vnq@-766%&%qQ{HFr%o^~!_#m(u`gK&u{CV^lRr;ioJ#C1k` zD=N#r$@UN#=tf$ik`Z6zrE3$E5mNUF8x}}A3RUUNzWvpq1spjDZMhO+2hLeuSu_0= z*E_t9LU_q&`u4dalmZ$`HYW@wLSI&=emil(H01Y(O|Rc?^xU+#rSRVSMu&qDb<47r z3%#Ze-UV0lsvO47*;5L%fRya^D69@}9G49>jhK_j(GxXL4Ov^XB#IA&-M#y>BO2b+ z2U7WKCAFEqE|BZr%KGTcyvpm(WP#|huf$9pu!LyN7?G;&KY)r3y_>1e7_($31v=mD z4|-sz5Pzr322Pp4pv!le{(Uu`s4;_N6Y#m0!yqrXOEk^&Q$$r@3&`OTyyWbb{@5M%aOLi*PUdmJoEjY4){XSC^=4EDg3yY-tog`GK)q0aUTx8 zzSDIiTNlQ<4PQbw3~vEG^Qko)xo(!Mdn#%8vPgT7r4Vkv!nGl4IHWq@N_c6Xx*0T6 za2(C9K;Bqwk8++6UW(dl^NBPw_)sp0S(*ruF1>;xeAdd-{4!u`6WeK zLb%tIaRKMRu}=uKU>2q&2m#lcEV>S_MgM8|$#lb4D+)YjKQFn`#$kl`oHShI17k4% zSnQVcRlf~77ks7J{#~X1h~1Np^6BfDYGwKYtM;PKs!8Xd?VLhKltt@PCPC-L)zBxJ zqDkTbe9PER{(y-U(3ceY5rlPGRuQ%ge-Bd=2kjLlo;`%uV)1e{JX-dN1T6&?mo zO1{cSND(O`1Bf!bGA%p3xt%NWaI&GDC-5iwd^K@Rg=Fg=>m}*mOpUk;0LiCEJHj5o z>^Y=^fX8`?t#d7e!yG!e;pg#U%{9j%)`{wYTq>lIE0uQCmFsoEc6G8oO5sV;>50Cf z+Ojn9$DrPICN5pzSD@(-o)_hBm6e!Qy6I7oPfwQ*y>w`!gE`}xVxCJV{=&*V-G`de zs_Lm0QnNOz;*uR~TH`KGae13g+X2Xu)aT%cvx<_IvSamq9YG>=SYP$Q$ID&y9(q7& zxLu$f^FaX`Y)K4@Yjfvf%UEm~Am|+@O6wRoW$Os>e{bF0N2;$-NuTzIU~pqxHj7n@ zRj4%OddH3w_7+^nl@$u_65@atZS5l+-GHBVUEQTm=_xUA#P;aW0=9%x$BmT{Oc_JT zRm7*qN`PLbq_aD%82oS3E5fx0K3lL-W@HAPbe1nOv6}B$@ZiBje8fzLDu&O9{mfHU=S1WU_Q5mO;SszInVwi|D7U`OLCSZ-1Cx#GeaFcXOBLXZ+*MI;tU6|eyavWg7=>n zW$X$o#aWZM2%M*;OH)smk(f^`L9;b@VF7LQLiF!BPOJe;30q=r9lvTv$9AJ_gPcmZ zSO*HUMx0fQx+xEt04R)5kb4U9Zv8U|noBe_;^j6kQ)bwPfCIc?@&32KIY()NO>N>e z1t<1xY<=U!T2kwg<@7y^|Mog**zh#^Hh}hG>!cJU!1a)xwZuAueXbaN(;{ z=vG{$8gRNTJMv59t^YU4;qu{B$)A238fL%n<&`jsfz7G5*jr<_;^`UZGw&|Mw)*Z~ zQ*h>C_O-KdD+7Nm4xuX~j*m~Aac_QJCSvW^Qk%+QQ}UteG381JSMz|14A6py%XPI-xb| zSdT*NNBnup4urz#rRyw`IN+S(*L+y2zuvNfVd`y@x7Be+o+32d)~9RS@$f|TXLJ9V zYq{Ez+oL7hXpJ7_M)Rvm|k^SP1^HhzuX%QGxS z2q=ugcNdn#tvF^CQ+s;;3+smjcDD3QLFc|7bMfvH-OlXsWJWh*u(hqY-YbBr$BGA& z;kr{#JOOIoD}s)cPl5bQz*+g#s*0=PI*q)M0JC?BnU>JX3Ew^dg|n^Lik18wtO`Pk zS8QXz>_NR!>!?BgO1faL^a22cBeE#`mE^gv3}isoo>#>9s!oqx1tiy<22eE_K})cd z0_+Os8Mv5l4VkeO_%c)2Q50e-mlrT~LMPt%i`va;7*9Krn?qZu2Y2K{KYaSJkNd&u zrq|0&tcnkQVdZaq)5BK*oVJbqAUFPY+Ogegd*z>D7Bm$|TnS6n2U+^OXmK?qKD<+V zd{(ktI{K--&ewCNjZNGLf8N!+DG{kp;{t@=aUlNPtU+eK;RjPHN-$aI{czPt zd<$rgT}yn$?J3dL0-7TGM)*eluGQE|RMtdjjC}ivOTs6DxK31%MR-YNk%7kIrvRb{ zFcCGV=9;!k<+BqggBQrxm3hnljtnua_FN=*2EV_Zc)-BQ1z^2LDGs=m%_%ofvv)pda4+!OsNnmE7w z-qu)4$b&s?6^)?_d(s6n=AK^FQ!%K+uBKi-zxnL72y453zQz^Dng{G2f67*`Z+SLX4LXMEtL=-h0FzE zzJ~Xhk5}`D9F0RXI}>I!)rAM)V8|A&mP3joaeAgf)n^xYdNw;BHgN*?vCni~ZDQ^} zqC6FjMJ0Kyg;#E-+*hv?nKNdpyMHH+Mk(sOxe(yr629`rmcm3(KFkbP!i^x1 zfGday;!hP5SEEhtu$sx}w9k7+^_cF`n2A8_i=z!;V-y6290|x)a6rVZQ)DL~tckE! z#nDz# zCAOKF>I{v{9*R$5^l!Hou(fr1k_W-FRXW$7Sqj;(vE`t+1O|)uJN9~Mz-J4z*=ARb z)q&8s01@>bcjH&AuamxZL!e?5{}dnhtH?9nsY48St%P3FjG zd+xY`J%ZbEUBUA8S9H#RZGiiOMNfpCQjFswl<1N&G=d&>jt5X66`WF>$Aj&7V9bHO za7m|$*dIQM4pEV~U_Zseo<@z#w+(V_-p%+aO4!@D&hL*bu-+6({- zi>xpS#gX7&+%uY9r`YTYr_br|DmmlWnYi}f*;#2Jqpf59C+IB>nYXypDLY|OtuT$u z4hZ&$=|9v9ASghhyQ`6LEcL_EO~4obE2Avm@E1^ z)YR_SVcf<4{?CbP&VBtp>9rl>iRaa>n&B#!z@_fcl+6c(;GpN~bxWw8^RDwOH z)n75RL6IqGi5-z-h0Vj07jv6-8&b8pU@GK}*TSNTr!e!6bpWqDmzMnk)HyYF6cC|{ zA_&pGvTt7)WKSQZCm9QN_By*e_<@>iLw!J>AzQ^|d@6iNh*CyBO<(22--%oo%BOJ4 zi7EHkjEW6AXd``~j{j-}eA*PhVA0tLb{BFpF+WpFpqr0-9%4<;lO?XV)mueMoH#j# zce%pZakY<)cP*CK5cVvp(f~|h zq=IpZrvqKrfCl@U6x{v74}_m=m7&pnyu~mUSq{gfu-goge27`UpDyUfRxyn0a59wE~N+%U7Hu!>A1!G0nx4+m(bk^Eo~9_&;`ip;!LfRYaEih({RY$q8;ecKQ* zOJGhoss$8(W#no5=vDlN?c9~D^W=b4OC%I(Hk)L-0F*w11k9fmSkr4##>Lr55S+xRzIcw$kaJPV(fg<>+56U5I0s3oIX|3!UajqIIl!$iktSH6)`UX2p?y zKdXdZk*AKKMXTqN0yaq8knx6T*o9h|K>)$em(Wn7$z^~>H-z6)+5=RG}&7n`<7*~GxlC11HkJ`pxRHZ`S25Z>K~ssx|l?+>AfjSw^UA@H`RN_m2Q7s z+xqa6+qSgqw3R%q*;uUu-!hg=y_*25lB^blA6Dl9c{WI+bgg$H*RU1hB%mwZ(j0Xw zNybvfzx%xK+Ai|oYmI&18ScAB$dFhVeWLMY{A>`~*80hUi66ETJPnD*)#0ImX5--C zWbZBwq2*w9*PPcyL_+^L&~b$JCgrj0YV;x|$DtOY3eajDp$o1m&kI zY_z#+dS3(g!Nml@L(|!POUNEq41=4?&o%VAZJxG0RdJzsTf~*XaK&!ds}&n1t&5$T zt?k}PHT@-!gejNUuldzF`s|{fbho}Z#Kj-TiJo3?Z^K_a$c~1eOlO?nc1kSSukzz^OX>dWNV@&!2(}e*p@B;WQ?kFssSg!N0lQCKTiK>|+w$ zllu%a&gYIrBBLVrt14-hyRq9>{(k>jvXkQ8Q76cGD6?+DCz8VvgfMPqa=LOgCkSj( zTU$I4blCZfgUM{&NAQPSVe!61vSy2P)&h?BR>cQNOQa2j`ihLzy$C4PwcPYn5a(Hz z^UH7RmdBs_0n$t%>put|rL9Cj0h49kkF5vMGVI{&!O*{RO@6T%DtJt|3g88Z+UrG_ zj5}uN@JI?KR2re|8v*atbDK^J)^{f6*k9j`*|;e6J%2C^9cLy*A#E zDCqU^`Ks*~4?)w{4f0*9Y}PcJ1(uGqBovaM%+JSK*$N7wjxUVeQ(+J$bSdf3-p!Al z+ped7xd^VIoF#5pLYaY~LAos`QuAX|o}_tmd8Nj^MK4=~e*ypF)20w0lt61PRO|Ayv$m%{fNS#v zOP@?AdpBW@TlL4fMsuQPXbRn0GF+!Y*5Fb`x{)Jnl(tX5s7gPJ4)k>E9cNK)rt@I& zx%*`rnJBwcQ!irQmRT4EeEEXABH)An@2yWBd;Nn4DO{iDRj4MS`?{k>mO8w9#CB1% zHmTvsz;BBiAkje5)Tbr~wu`DH0gwUjCq43!fHKtPSGv34EjPmTW-xj8RO4~{_3rH-AtPdm?!?+BR&BS4)ElAy z`v?@-Xh$txhE$w6<*^r=+pX)k)sURqyqa}C{GTxcLE9laH|jf1gt`GBkw|v1tkXVf zB2}y2{>n&B9;tVKRY82;cW){$D|U@;Meg9W<{@I0@1Q6DT`C(jT95IW(nlUg;7;ok z+2vghIY{=q2tZIEF(2?g@{4Gn9sm0=mKxGuBKG&=;bJ1#X8Ni|>S@RDepdgc63wkN zyF7nTvY}bHb8II=(fHQSTkJ~7Rtymn3O8kmL00P3ES=aOR#GCHLN{j+6w?5wMe+GZ>!Q~b>nBE49JQIvrWw!*cFtZ~?VU%x% z_mbrwlf24E7AnKF$$TeUP#tIu34nqrbBG{#3)6|a>CFuwm}HNq0yn(;Iwx!=;5w(J z@L)bqC}bX&A%MOl_Lc((1w3cYHp6o?!Y?Z?o;GU#tc#;Ra}Gi)$7t9Gi}972F?JVP zNf@fCUK&U(VBg*f>zky{d82^SA>jQYVt>Xo6B%jg9xwq06k^3HuQ*;?O7kz0jLa?s zz}#ivnrQu=Fdw&YX~GW%NUmDGv?aUCdhb7mY;ZR=(bciJyLvbLYr&m9&&zpx(Z2e|9O(pRm#` zqf9*H!PymXfjWcrmo@v3D*5lh{r{K$N7Vl>Au}qy|Hm(THI1(cOY^*1g3s7c`UUve N=IOnqVAJ8>{s#!Cz77BY literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx index a88c617d..e72bba0f 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx @@ -4,10 +4,26 @@ sidebar_position: 4 title: ChannelListView --- -![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view.png) -![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view_paint.png) +A Widget For Displaying A List Of Channels -It shows the list of current channels. +Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/ChannelListView-class.html) + +### Background + +Channels are fundamental elements of Stream Chat and constitute shared spaces which allow users to +message each other. 1:1 conversations and groups are both examples of channels, albeit with some (distinct/non-distinct) +differences. Displaying the list of channels that a user is a part of is a pattern that is part of +most messaging apps. + +The `ChannelListView` widget allows displaying a list of channels to a user. By default, this is NOT +ONLY the channels that the user is a part of. This section goes into setting up and using a `ChannelListView` +widget. + +### Basic Example + +Here is a basic example of the `ChannelListView` widget. It consists of the main widget itself, a `Filter` +to filter only the channels that the user is a part of, sorting by last message time, pagination params, +and the widget to use when a particular channel is clicked. ```dart class ChannelListPage extends StatelessWidget { @@ -27,11 +43,29 @@ class ChannelListPage extends StatelessWidget { } ``` +This example by default displays the channels that a user is a part of. Now let's look at customizing +the widget. -Make sure to have a [StreamChat] ancestor in order to provide the -information about the channels. -The widget uses a [ListView.custom] to render the list of channels. +#### Customizing the Channel Preview + +A common aspect of the widget needed to be tweaked according to each app is the Channel Preview (the +Channel tile in the list). To do this, we use the `channelPreviewBuilder` param like this: + +```dart +ChannelListView( + ... + channelPreviewBuilder: (context, channel) { + return ListTile( + tileColor: Colors.amberAccent, + title: Center( + child: ChannelName(), + ), + ); + }, +), +``` + +Which gives you a new Channel preview in the list: + +![](../assets/channel_preview.png) -The widget components render the ui based on the first ancestor of -type [StreamChatTheme]. -Modify it to change the widget appearance. \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx index d143e9fd..952c9460 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx @@ -4,3 +4,10 @@ sidebar_position: 1 title: Introduction --- +Understanding The UI Package Of The Flutter SDK + +The official Flutter components for Stream Chat, a service for building chat applications. + +While the Stream Chat service provides the backend for messaging and the LLC provides an easy way to +use it in your Flutter apps, we wanted to make sure that adding Chat functionality to your app was as quick as possible. + From c617b3040a71723fdada896ee881165e3436f1d7 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 2 Jul 2021 11:58:23 +0200 Subject: [PATCH 495/599] add connectWebsocket property to connectUser calls --- .../stream_chat/lib/src/client/client.dart | 64 +++++++++++++++---- .../stream_chat/test/src/api/client_test.dart | 27 ++------ .../test/stream_chat_core_test.dart | 15 +++-- 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 9276ce22..f8e9ff45 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -220,27 +220,53 @@ class StreamChatClient { /// Connects the current user, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. - Future connectUser(User user, String token) => - _connectUser(user, token: Token.fromRawValue(token)); + /// Pass [connectWebsocket]: false, if you want to connect to websocket + /// at a later stage or use the client in connection-less mode + Future connectUser( + User user, + String token, { + bool connectWebsocket = true, + }) => + _connectUser( + user, + token: Token.fromRawValue(token), + connectWebsocket: connectWebsocket, + ); /// Connects the current user using the [tokenProvider] to fetch the token. /// It returns a [Future] that resolves when the connection is setup. - Future connectUserWithProvider( - User user, TokenProvider tokenProvider) => - _connectUser(user, provider: tokenProvider); + Future connectUserWithProvider( + User user, + TokenProvider tokenProvider, { + bool connectWebsocket = true, + }) => + _connectUser( + user, + provider: tokenProvider, + connectWebsocket: connectWebsocket, + ); /// Connects the current user with an anonymous id, this triggers a connection /// to the API. It returns a [Future] that resolves when the connection is /// setup. - Future connectAnonymousUser() async { + Future connectAnonymousUser({ + bool connectWebsocket = true, + }) async { final token = Token.anonymous(); final user = OwnUser(id: token.userId); - return _connectUser(user, token: token); + return _connectUser( + user, + token: token, + connectWebsocket: connectWebsocket, + ); } /// Connects the current user as guest, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. - Future connectGuestUser(User user) async { + Future connectGuestUser( + User user, { + bool connectWebsocket = true, + }) async { final userId = user.id; final anonymousToken = Token.anonymous(userId: userId); @@ -253,13 +279,18 @@ class StreamChatClient { _tokenManager.reset(); final guestUserToken = Token.fromRawValue(guestUser.accessToken); - return _connectUser(guestUser.user, token: guestUserToken); + return _connectUser( + guestUser.user, + token: guestUserToken, + connectWebsocket: connectWebsocket, + ); } - Future _connectUser( + Future _connectUser( User user, { Token? token, TokenProvider? provider, + bool connectWebsocket = true, }) async { if (_ws.connectionCompleter?.isCompleted == false) { throw const StreamChatError( @@ -268,7 +299,7 @@ class StreamChatClient { ); } - logger.info('connecting user : ${user.id}'); + logger.info('setting user : ${user.id}'); await _tokenManager.setTokenOrProvider( user.id, @@ -279,6 +310,10 @@ class StreamChatClient { final ownUser = OwnUser.fromUser(user); state.user = ownUser; + if (!connectWebsocket) { + return ownUser; + } + try { if (_originalChatPersistenceClient != null) { _chatPersistenceClient = _originalChatPersistenceClient; @@ -289,7 +324,7 @@ class StreamChatClient { } catch (e, stk) { if (e is StreamWebSocketError && e.isRetriable) { final event = await _chatPersistenceClient?.getConnectionInfo(); - if (event != null) return event; + if (event != null) return event.me; } logger.severe('error connecting user : ${ownUser.id}', e, stk); rethrow; @@ -297,7 +332,7 @@ class StreamChatClient { } /// Creates a new WebSocket connection with the current user. - Future openConnection() async { + Future openConnection() async { assert( state.user != null, 'User is not set on client, ' @@ -327,7 +362,8 @@ class StreamChatClient { _ws.connectionStatusStream.skip(1).listen(_connectionStatusHandler); try { - return await _ws.connect(user); + await _ws.connect(user); + return state.user!; } catch (e, stk) { logger.severe('error connecting ws', e, stk); rethrow; diff --git a/packages/stream_chat/test/src/api/client_test.dart b/packages/stream_chat/test/src/api/client_test.dart index 13783865..4db8dfb7 100644 --- a/packages/stream_chat/test/src/api/client_test.dart +++ b/packages/stream_chat/test/src/api/client_test.dart @@ -64,9 +64,7 @@ void main() { final res = await client.connectUser(user, token); expect(res, isNotNull); - expect(res.type, event.type); - expect(res.connectionId, event.connectionId); - expect(res.me, isSameUserAs(user)); + expect(res, isSameUserAs(user)); }); test('`.connectUserWithProvider` should work fine', () async { @@ -93,9 +91,7 @@ void main() { final res = await client.connectUserWithProvider(user, tokenProvider); expect(res, isNotNull); - expect(res.type, event.type); - expect(res.connectionId, event.connectionId); - expect(res.me, isSameUserAs(user)); + expect(res, isSameUserAs(user)); }); group('`.connectGuestUser`', () { @@ -127,9 +123,7 @@ void main() { final res = await client.connectGuestUser(user); expect(res, isNotNull); - expect(res.type, event.type); - expect(res.connectionId, event.connectionId); - expect(res.me, isSameUserAs(user)); + expect(res, isSameUserAs(user)); verify( () => api.guest.getGuestUser(any(that: isSameUserAs(user))), @@ -175,9 +169,6 @@ void main() { final res = await client.connectAnonymousUser(); expect(res, isNotNull); - expect(res.type, EventType.healthCheck); - expect(res.connectionId, 'fake-connection-id'); - expect(res.me, isNotNull); }); group('`.openConnection`', () { @@ -366,8 +357,7 @@ void main() { final res = await client.connectUser(user, token); expect(res, isNotNull); - expect(res.connectionId, 'test-connection-id'); - expect(res.me?.id, user.id); + expect(res, isSameUserAs(user)); verify(persistence.getConnectionInfo).called(1); verifyNoMoreInteractions(persistence); @@ -391,8 +381,7 @@ void main() { final res = await client.connectUserWithProvider(user, tokenProvider); expect(res, isNotNull); - expect(res.connectionId, 'test-connection-id'); - expect(res.me?.id, user.id); + expect(res, isSameUserAs(user)); verify(persistence.getConnectionInfo).called(1); verifyNoMoreInteractions(persistence); @@ -420,8 +409,7 @@ void main() { final res = await client.connectGuestUser(user); expect(res, isNotNull); - expect(res.connectionId, 'test-connection-id'); - expect(res.me?.id, user.id); + expect(res, isSameUserAs(user)); verify(persistence.getConnectionInfo).called(1); verifyNoMoreInteractions(persistence); @@ -446,8 +434,7 @@ void main() { final res = await client.connectAnonymousUser(); expect(res, isNotNull); - expect(res.connectionId, 'test-connection-id'); - expect(res.me?.id, user.id); + expect(res, isSameUserAs(user)); verify(persistence.getConnectionInfo).called(1); verifyNoMoreInteractions(persistence); diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index f2b1b514..178510c2 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -204,7 +204,8 @@ void main() { final event = Event(type: EventType.any); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.openConnection()) + .thenAnswer((_) async => OwnUser(id: 'test')); when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -238,7 +239,8 @@ void main() { final event = Event(); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.openConnection()) + .thenAnswer((_) async => OwnUser(id: 'test')); when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -327,7 +329,8 @@ void main() { final event = Event(); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.openConnection()) + .thenAnswer((_) async => OwnUser(id: 'test')); when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -376,7 +379,8 @@ void main() { final event = Event(); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.openConnection()) + .thenAnswer((_) async => OwnUser(id: 'test')); when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.connected); @@ -402,7 +406,8 @@ void main() { final event = Event(); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()).thenAnswer((_) async => event); + when(() => mockClient.openConnection()) + .thenAnswer((_) async => OwnUser(id: 'test')); when(() => mockClient.closeConnection()).thenAnswer((_) async => null); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); From 7e9944730f94a1d390534b59e06273d7df32da91 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 2 Jul 2021 12:28:36 +0200 Subject: [PATCH 496/599] add tests --- .../stream_chat/test/src/api/client_test.dart | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/packages/stream_chat/test/src/api/client_test.dart b/packages/stream_chat/test/src/api/client_test.dart index 4db8dfb7..db396598 100644 --- a/packages/stream_chat/test/src/api/client_test.dart +++ b/packages/stream_chat/test/src/api/client_test.dart @@ -321,6 +321,93 @@ void main() { ); }); + group('Connect user calls with `connectWebsocket`: false', () { + const apiKey = 'test-api-key'; + late final api = FakeChatApi(); + + late StreamChatClient client; + + setUpAll(() { + // fallback values + registerFallbackValue(FakeUser()); + }); + + setUp(() { + client = StreamChatClient(apiKey, chatApi: api); + }); + + tearDown(() { + client.dispose(); + }); + + test('`.connectUser` should succeed without connecting', () async { + final user = User(id: 'test-user-id'); + final token = Token.development(user.id).rawValue; + + final res = await client.connectUser( + user, + token, + connectWebsocket: false, + ); + expect(res, isSameUserAs(user)); + expect(client.wsConnectionStatus, ConnectionStatus.disconnected); + }); + + test( + '`.connectUserWithProvider` should succeed without connecting', + () async { + final user = User(id: 'test-user-id'); + Future tokenProvider(String userId) async { + expect(userId, user.id); + return Token.development(userId).rawValue; + } + + final res = await client.connectUserWithProvider( + user, + tokenProvider, + connectWebsocket: false, + ); + expect(res, isSameUserAs(user)); + expect(client.wsConnectionStatus, ConnectionStatus.disconnected); + }, + ); + + test('`.connectGuestUser` should succeed without connecting', () async { + final user = User(id: 'test-user-id'); + final token = Token.development(user.id).rawValue; + + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) + .thenAnswer( + (_) async => ConnectGuestUserResponse() + ..user = user + ..accessToken = token, + ); + + final res = await client.connectGuestUser( + user, + connectWebsocket: false, + ); + + expect(res, isSameUserAs(user)); + expect(client.wsConnectionStatus, ConnectionStatus.disconnected); + verify( + () => api.guest.getGuestUser(any(that: isSameUserAs(user))), + ).called(1); + }); + + test( + '`.connectAnonymousUser` should succeed without connecting', + () async { + final res = await client.connectAnonymousUser( + connectWebsocket: false, + ); + + expect(res, isNotNull); + expect(client.wsConnectionStatus, ConnectionStatus.disconnected); + }, + ); + }); + group('Fake web-socket connection function with failure and persistence', () { const apiKey = 'test-api-key'; late final api = FakeChatApi(); From da75fd57a9b68f152ee4601a4ee1bddbf7316edd Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 2 Jul 2021 17:33:19 +0530 Subject: [PATCH 497/599] feat: Added new docs --- .../docs/Flutter/assets/swipe_channel.png | Bin 0 -> 17035 bytes .../docs/Flutter/guides/_category_.json | 4 +++ .../adding_local_data_persistence.mdx | 2 +- .../adding_push_notifcations.mdx | 2 +- .../{other => guides}/introduction.mdx | 0 docusaurus/docs/Flutter/other/_category_.json | 4 --- .../stream_chat_flutter/channel_list_view.mdx | 31 +++++++++++++++++- .../user_list_core.mdx | 5 +++ 8 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 docusaurus/docs/Flutter/assets/swipe_channel.png create mode 100644 docusaurus/docs/Flutter/guides/_category_.json rename docusaurus/docs/Flutter/{stream_chat_flutter => guides}/adding_local_data_persistence.mdx (78%) rename docusaurus/docs/Flutter/{stream_chat_flutter => guides}/adding_push_notifcations.mdx (78%) rename docusaurus/docs/Flutter/{other => guides}/introduction.mdx (100%) delete mode 100644 docusaurus/docs/Flutter/other/_category_.json create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx diff --git a/docusaurus/docs/Flutter/assets/swipe_channel.png b/docusaurus/docs/Flutter/assets/swipe_channel.png new file mode 100644 index 0000000000000000000000000000000000000000..44f13ca594cc7c096582f5f920ae7cdde522eb96 GIT binary patch literal 17035 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 Q&p^mXD2SJfehmEo04V`bP5=M^ literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/guides/_category_.json b/docusaurus/docs/Flutter/guides/_category_.json new file mode 100644 index 00000000..c2af63bb --- /dev/null +++ b/docusaurus/docs/Flutter/guides/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Guides", + "position": 5 +} \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/adding_local_data_persistence.mdx b/docusaurus/docs/Flutter/guides/adding_local_data_persistence.mdx similarity index 78% rename from docusaurus/docs/Flutter/stream_chat_flutter/adding_local_data_persistence.mdx rename to docusaurus/docs/Flutter/guides/adding_local_data_persistence.mdx index 10abc63d..21ec99ce 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/adding_local_data_persistence.mdx +++ b/docusaurus/docs/Flutter/guides/adding_local_data_persistence.mdx @@ -1,6 +1,6 @@ --- id: local_data_persistence -sidebar_position: 9 +sidebar_position: 2 title: Adding Local Data Persistence --- diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/adding_push_notifcations.mdx b/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx similarity index 78% rename from docusaurus/docs/Flutter/stream_chat_flutter/adding_push_notifcations.mdx rename to docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx index b9d87913..20621ca7 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/adding_push_notifcations.mdx +++ b/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx @@ -1,6 +1,6 @@ --- id: adding_push_notifications -sidebar_position: 8 +sidebar_position: 3 title: Adding Push Notifications --- diff --git a/docusaurus/docs/Flutter/other/introduction.mdx b/docusaurus/docs/Flutter/guides/introduction.mdx similarity index 100% rename from docusaurus/docs/Flutter/other/introduction.mdx rename to docusaurus/docs/Flutter/guides/introduction.mdx diff --git a/docusaurus/docs/Flutter/other/_category_.json b/docusaurus/docs/Flutter/other/_category_.json deleted file mode 100644 index acf56fce..00000000 --- a/docusaurus/docs/Flutter/other/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Other", - "position": 5 -} \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx index e72bba0f..379b3a2a 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx @@ -46,7 +46,7 @@ class ChannelListPage extends StatelessWidget { This example by default displays the channels that a user is a part of. Now let's look at customizing the widget. -#### Customizing the Channel Preview +### Customizing the Channel Preview A common aspect of the widget needed to be tweaked according to each app is the Channel Preview (the Channel tile in the list). To do this, we use the `channelPreviewBuilder` param like this: @@ -69,3 +69,32 @@ Which gives you a new Channel preview in the list: ![](../assets/channel_preview.png) +### Adding Swipe Actions + +To add actions (such as delete, more info, etc) when Channel preview is swiped left, set the `swipeToAction` +parameter to `true`. + +```dart +ChannelListView( + ... + swipeToAction: true, +), +``` + +This adds two basic actions - info and delete: + +![](../assets/swipe_channel.png) + +To add custom actions of your own, use the `swipeActions` param: + +```dart +ChannelListView( + ... + swipeToAction: true, + swipeActions: [ + // Your action widgets here + ] +), +``` + + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx new file mode 100644 index 00000000..0f515a12 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx @@ -0,0 +1,5 @@ +--- +id: user_list_core +sidebar_position: 10 +title: UserListCore +--- \ No newline at end of file From 5d351d82e5f7be7d296b5d1f362d7daa47499344 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 2 Jul 2021 15:29:01 +0200 Subject: [PATCH 498/599] add copywith and merge to ownuser --- .../lib/src/core/models/own_user.dart | 64 +++++++++++++++++++ .../stream_chat/lib/src/core/models/user.dart | 7 +- .../test/src/core/models/own_user_test.dart | 38 +++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) 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 7ceffbfa..679abb47 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -3,6 +3,7 @@ import 'package:stream_chat/src/core/models/device.dart'; import 'package:stream_chat/src/core/models/mute.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/src/core/util/serializer.dart'; +import 'package:stream_chat/stream_chat.dart'; part 'own_user.g.dart'; @@ -25,6 +26,7 @@ class OwnUser extends User { bool online = false, Map extraData = const {}, bool banned = false, + List teams = const [], }) : super( id: id, role: role, @@ -34,6 +36,7 @@ class OwnUser extends User { online: online, extraData: extraData, banned: banned, + teams: teams, ); /// Create a new instance from a json @@ -50,8 +53,69 @@ class OwnUser extends User { online: user.online, banned: user.banned, extraData: user.extraData, + teams: user.teams, ); + /// Creates a copy of [OwnUser] with specified attributes overridden. + @override + OwnUser copyWith({ + String? id, + String? role, + DateTime? createdAt, + DateTime? updatedAt, + DateTime? lastActive, + bool? online, + Map? extraData, + bool? banned, + List? teams, + List? channelMutes, + List? devices, + List? mutes, + int? totalUnreadCount, + int? unreadChannels, + }) => + OwnUser( + id: id ?? this.id, + banned: banned ?? this.banned, + role: role ?? this.role, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + lastActive: lastActive ?? this.lastActive, + online: online ?? this.online, + extraData: extraData ?? this.extraData, + teams: teams ?? this.teams, + channelMutes: channelMutes ?? this.channelMutes, + devices: devices ?? this.devices, + mutes: mutes ?? this.mutes, + totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, + unreadChannels: unreadChannels ?? this.unreadChannels, + ); + + /// Returns a new [OwnUser] that is a combination of this ownUser + /// and the given [other] ownUser. + OwnUser merge(OwnUser? other) { + if (other == null) { + return this; + } + + return copyWith( + banned: other.banned, + channelMutes: other.channelMutes, + createdAt: other.createdAt, + devices: other.devices, + extraData: other.extraData, + id: other.id, + lastActive: other.lastActive, + mutes: other.mutes, + online: other.online, + role: other.role, + teams: other.teams, + totalUnreadCount: other.totalUnreadCount, + unreadChannels: other.unreadChannels, + updatedAt: other.updatedAt, + ); + } + /// List of user devices @JsonKey(includeIfNull: false, defaultValue: []) final List devices; diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index c2960af9..1360d14e 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -47,9 +47,10 @@ class User extends Equatable { /// User role @JsonKey( - includeIfNull: false, - toJson: Serializer.readOnly, - defaultValue: []) + includeIfNull: false, + toJson: Serializer.readOnly, + defaultValue: [], + ) final List teams; /// Date of user creation diff --git a/packages/stream_chat/test/src/core/models/own_user_test.dart b/packages/stream_chat/test/src/core/models/own_user_test.dart index edaf022e..7aef859d 100644 --- a/packages/stream_chat/test/src/core/models/own_user_test.dart +++ b/packages/stream_chat/test/src/core/models/own_user_test.dart @@ -41,5 +41,43 @@ void main() { expect(ownUser.banned, user.banned); expect(ownUser.extraData, user.extraData); }); + + test('copyWith', () { + final user = OwnUser.fromJson(jsonFixture('own_user.json')); + var newUser = user.copyWith(); + + expect(newUser.id, user.id); + expect(newUser.role, user.role); + expect(newUser.name, user.name); + + newUser = user.copyWith( + id: 'test', + role: 'test', + extraData: { + 'name': 'test', + }, + ); + + expect(newUser.id, 'test'); + expect(newUser.role, 'test'); + expect(newUser.name, 'test'); + }); + + test('merge', () { + final user = OwnUser.fromJson(jsonFixture('own_user.json')); + final newUser = user.merge(OwnUser( + id: 'test', + role: 'test', + extraData: const { + 'name': 'test', + }, + banned: true, + )); + + expect(newUser.id, 'test'); + expect(newUser.role, 'test'); + expect(newUser.name, 'test'); + expect(newUser.banned, true); + }); }); } From 88e9cd67efd92238c68cd6a7c27c288ea9ce67ec Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 2 Jul 2021 15:29:28 +0200 Subject: [PATCH 499/599] merge persistence storage user with passed one --- packages/stream_chat/lib/src/client/client.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index f8e9ff45..096d674c 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -324,7 +324,7 @@ class StreamChatClient { } catch (e, stk) { if (e is StreamWebSocketError && e.isRetriable) { final event = await _chatPersistenceClient?.getConnectionInfo(); - if (event != null) return event.me; + if (event != null) return event.me?.merge(ownUser) ?? ownUser; } logger.severe('error connecting user : ${ownUser.id}', e, stk); rethrow; From d043d1ca0f73513c25840ad22e682a69cdde853e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 2 Jul 2021 15:33:03 +0200 Subject: [PATCH 500/599] export stream_chat_api --- packages/stream_chat/lib/stream_chat.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat/lib/stream_chat.dart b/packages/stream_chat/lib/stream_chat.dart index 3c2e3d21..bb2a01de 100644 --- a/packages/stream_chat/lib/stream_chat.dart +++ b/packages/stream_chat/lib/stream_chat.dart @@ -13,6 +13,7 @@ export './src/core/api/attachment_file_uploader.dart' export './src/core/api/requests.dart'; export './src/core/api/requests.dart'; export './src/core/api/responses.dart'; +export './src/core/api/stream_chat_api.dart'; export './src/core/error/error.dart'; export './src/core/models/action.dart'; export './src/core/models/attachment.dart'; From 83ddd01f9bab4b7e2a5a5b07d2a2d683415dcb7e Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 2 Jul 2021 19:07:52 +0530 Subject: [PATCH 501/599] feat: Added new docs --- .../docs/Flutter/stream_chat_flutter/channel_header.mdx | 5 +++++ .../Flutter/stream_chat_flutter/channel_list_header.mdx | 5 +++++ .../Flutter/stream_chat_flutter/customising_widgets.mdx | 6 ------ .../docs/Flutter/stream_chat_flutter/message_input.mdx | 2 +- .../stream_chat_flutter/message_search_list_view.mdx | 5 +++++ .../docs/Flutter/stream_chat_flutter/message_widget.mdx | 5 +++++ .../docs/Flutter/stream_chat_flutter/user_list_view.mdx | 6 ++++++ 7 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx delete mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/customising_widgets.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx create mode 100644 docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx new file mode 100644 index 00000000..71bb2306 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx @@ -0,0 +1,5 @@ +--- +id: channel_header +sidebar_position: 10 +title: ChannelHeader +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx new file mode 100644 index 00000000..af581c42 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx @@ -0,0 +1,5 @@ +--- +id: channel_list_header +sidebar_position: 9 +title: ChannelListHeader +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/customising_widgets.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/customising_widgets.mdx deleted file mode 100644 index 29edd4ca..00000000 --- a/docusaurus/docs/Flutter/stream_chat_flutter/customising_widgets.mdx +++ /dev/null @@ -1,6 +0,0 @@ ---- -id: customising_widgets -sidebar_position: 7 -title: Customising Widgets ---- - diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx index 16719619..aeb246d9 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx @@ -1,7 +1,7 @@ --- id: message_input sidebar_position: 6 -title: Message Input +title: MessageInput --- The `MessageInput` widget deals with common elements related to sending a message such as a text field for typing text, diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx new file mode 100644 index 00000000..4ea9f9f9 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx @@ -0,0 +1,5 @@ +--- +id: message_Search_list_view +sidebar_position: 8 +title: MessageSearchListView +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx new file mode 100644 index 00000000..eaafbb7e --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx @@ -0,0 +1,5 @@ +--- +id: message_widget +sidebar_position: 11 +title: MessageWidget +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx new file mode 100644 index 00000000..489cc141 --- /dev/null +++ b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx @@ -0,0 +1,6 @@ +--- +id: user_list_view +sidebar_position: 7 +title: UserListView +--- + From 0e6acd59b64cf66263ed514d12c13bf341342ec0 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 2 Jul 2021 15:43:59 +0200 Subject: [PATCH 502/599] fix test --- packages/stream_chat/test/src/api/client_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/stream_chat/test/src/api/client_test.dart b/packages/stream_chat/test/src/api/client_test.dart index db396598..36a35d56 100644 --- a/packages/stream_chat/test/src/api/client_test.dart +++ b/packages/stream_chat/test/src/api/client_test.dart @@ -521,7 +521,6 @@ void main() { final res = await client.connectAnonymousUser(); expect(res, isNotNull); - expect(res, isSameUserAs(user)); verify(persistence.getConnectionInfo).called(1); verifyNoMoreInteractions(persistence); From b64631efee8ac438215b7e7a8b1e854cb469f09f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 2 Jul 2021 15:55:18 +0200 Subject: [PATCH 503/599] Update packages/stream_chat/lib/stream_chat.dart Co-authored-by: Sahil Kumar --- packages/stream_chat/lib/stream_chat.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/stream_chat.dart b/packages/stream_chat/lib/stream_chat.dart index bb2a01de..571d78f5 100644 --- a/packages/stream_chat/lib/stream_chat.dart +++ b/packages/stream_chat/lib/stream_chat.dart @@ -13,7 +13,7 @@ export './src/core/api/attachment_file_uploader.dart' export './src/core/api/requests.dart'; export './src/core/api/requests.dart'; export './src/core/api/responses.dart'; -export './src/core/api/stream_chat_api.dart'; +export './src/core/api/stream_chat_api.dart' show PushProvider; export './src/core/error/error.dart'; export './src/core/models/action.dart'; export './src/core/models/attachment.dart'; From 1dc23583a66a258d1a3760cbba0953ca528252a4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 2 Jul 2021 15:56:14 +0200 Subject: [PATCH 504/599] return event user --- packages/stream_chat/lib/src/client/client.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 096d674c..2f536916 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -319,8 +319,8 @@ class StreamChatClient { _chatPersistenceClient = _originalChatPersistenceClient; await _chatPersistenceClient!.connect(ownUser.id); } - final event = await openConnection(); - return event; + final res = await openConnection(); + return res; } catch (e, stk) { if (e is StreamWebSocketError && e.isRetriable) { final event = await _chatPersistenceClient?.getConnectionInfo(); @@ -362,8 +362,8 @@ class StreamChatClient { _ws.connectionStatusStream.skip(1).listen(_connectionStatusHandler); try { - await _ws.connect(user); - return state.user!; + final event = await _ws.connect(user); + return event.me!.merge(user); } catch (e, stk) { logger.severe('error connecting ws', e, stk); rethrow; From bbf4f26c1d9f9d0409885674ce81602ed40b775f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 5 Jul 2021 20:59:28 +0530 Subject: [PATCH 505/599] feat: Added new docs --- .../docs/Flutter/basics/introduction.mdx | 2 +- .../stream_chat_flutter/channel_list_view.mdx | 9 ++- .../stream_chat_flutter/message_list_view.mdx | 70 +++++++++++++++++-- 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/docusaurus/docs/Flutter/basics/introduction.mdx b/docusaurus/docs/Flutter/basics/introduction.mdx index e39f82cc..457fd5e6 100644 --- a/docusaurus/docs/Flutter/basics/introduction.mdx +++ b/docusaurus/docs/Flutter/basics/introduction.mdx @@ -2,7 +2,7 @@ slug: / id: introduction sidebar_position: 1 -title: Introduction To Stream Chat For Flutter +title: About The Flutter SDK --- Exploring The Basics Of Stream Chat diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx index 379b3a2a..f513d00a 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx @@ -92,7 +92,14 @@ ChannelListView( ... swipeToAction: true, swipeActions: [ - // Your action widgets here + SwipeAction( + color: Colors.blue, + iconWidget: Icon(Icons.add), + onTap: (channel) { + // Things to do on icon tap + }, + ), + // Other actions here ] ), ``` diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx index c3a3efc2..1b57cd97 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx @@ -4,6 +4,20 @@ sidebar_position: 5 title: MessageListView --- +A Widget For Displaying A List Of Messages + +Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageListView-class.html) + +### Background + +Every channel can contain a list of messages sent by users inside it. The `MessageListView` widget +displays the list of messages inside a particular channel along with possible attachments and +other message attributes (if the message is pinned for example). This sets it apart from the `MessageSearchListView` +which may not contain messages only from a single channel and is used to search for messages across +many. + +### Basic Example + The `MessageListView` shows the list of messages of the current channel. It has inbuilt support for common messaging functionality: displaying and editing messages, adding / modifying reactions, support for quoting messages, pinning messages, and more. @@ -39,9 +53,55 @@ class ChannelPage extends StatelessWidget { } ``` -Make sure to have a [StreamChannel] ancestor in order to -provide the information about the channels. -The widget uses a [ListView.custom] to render the list of channels. +### Enable Threads -The widget components render the ui based on the first -ancestor of type [StreamChatTheme]. \ No newline at end of file +Threads are made of a parent message and replies linked to it. To enable threading, the SDK requires you +to supply a `threadBuilder` which will supply the page when the thread is clicked. + +```dart +MessageListView( + threadBuilder: (_, parentMessage) { + return ThreadPage( + parent: parentMessage, + ); + }, +), +``` + +The `MessageListView` itself can render the thread by supplying the `parentMessage` parameter. + +```dart +MessageListView( + parentMessage: parent, +), +``` + +### Building Custom Messages + +You can also supply your own implementation for displaying messages using the `messageBuilder` parameter. + +Note: To customize the existing implementation, look at the `MessageWidget` documentation instead. + +```dart +MessageListView( + messageBuilder: (context, details, messageList, defaultImpl) { + // Your implementation of the message here + // E.g: return Text(details.message.text); + }, +), +``` + +### Enabling Message Pinning + +Message pins save and highlight the message in the `MessageListView`. To enable users to pin the message, +make sure the pin permissions are granted for different types of users on the dashboard. After confirming +the appropriate users have permissions, add the user types in the `pinPermissions` parameter. + +```dart +MessageListView( + //... + pinPermissions: ['admin', 'userType1', 'userType2'], +), +``` + +This will allow these user types to pin messages through the message actions modal. \ No newline at end of file From e2d4df8e2a85b334d94886660fe93929d1318756 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 6 Jul 2021 12:41:02 +0530 Subject: [PATCH 506/599] fix: added new docs --- .../stream_chat_flutter/introduction.mdx | 4 +- .../stream_chat_flutter/message_input.mdx | 84 ++++++++++++++++--- 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx index 952c9460..6f15aa2e 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx @@ -6,7 +6,9 @@ title: Introduction Understanding The UI Package Of The Flutter SDK -The official Flutter components for Stream Chat, a service for building chat applications. +### What function does `stream_chat_flutter` serve? + +The UI SDK (`stream_chat_flutter`) contains official Flutter components for Stream Chat, a service for building chat applications. While the Stream Chat service provides the backend for messaging and the LLC provides an easy way to use it in your Flutter apps, we wanted to make sure that adding Chat functionality to your app was as quick as possible. diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx index aeb246d9..2d875eb9 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx @@ -4,14 +4,22 @@ sidebar_position: 6 title: MessageInput --- -The `MessageInput` widget deals with common elements related to sending a message such as a text field for typing text, -adding media to a message, quoting a message and more. The message input uses a stream channel ancestor to send a message. -The message input can be used along with a message list view to display messages. +A Widget Dealing With Everything Related To Sending A Message -The message input also has commands such as Giphy and more which can be added. It can also be used to add custom attachments to your messages. -The message input is customisable and the orientation of the icons the text field and the send button can be changed. +### Background -Widget used to enter the message and add attachments +In Stream Chat, we can send messages in a channel. However, sending a message isn't as simple as adding +a `TextField` and logic for sending a message. It involves additional processes like addition of media, +quoting a messages, adding a custom command like a GIF board, and much more. Moreover, most apps also +need to customize the input to match their theme, overall color and structure pattern, etc. + +To do this, we created a `MessageInput` widget which abstracts all expected functionality a modern input +needs - and allows you to use it out of the box. + +### Basic Example + +A `StreamChannel` is required above the widget tree in which the `MessageInput` is rendered since the channel is +where the messages sent actually go. Let's look at a common example of how we could use the `MessageInput`: ```dart class ChannelPage extends StatelessWidget { @@ -42,9 +50,63 @@ class ChannelPage extends StatelessWidget { } ``` -You usually put this widget in the same page of a [MessageListView] -as the bottom widget. +It is common to put this widget in the same page of a [MessageListView] as the bottom widget. -The widget renders the ui based on the first ancestor of -type [StreamChatTheme]. -Modify it to change the widget appearance. \ No newline at end of file +### Quoting A Message + +The quoting functionality allows us to 'reply' to a specific message without creating a thread out of it. +It adds the other message as context when sending a message and also displays it above the sent message. + +To quote a message, we provide a `quotedMessage` to the `MessageInput`. + +```dart +Message? message; + +// ... +MessageInput( + quotedMessage: message, +), +``` + +This will add the message given above the message about to be sent. + +While you can implement your own functionality to select which message to reply to, the `MessageListView` +widget helps in this case since it has an inbuilt `onMessageSwiped` callback which we can use. + +```dart + +class ChannelPage extends StatefulWidget { + @override + _ChannelPageState createState() => _ChannelPageState(); +} + +class _ChannelPageState extends State { + Message? quotedMessage; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + Expanded( + child: MessageListView( + // ... + onMessageSwiped: (message) { + setState(() { + quotedMessage = message; + }); + }, + ), + ), + MessageInput( + quotedMessage: _quotedMessage, + onQuotedMessageCleared: () { + setState(() => _quotedMessage = null); + }, + ), + ], + ), + ); + } +} +``` \ No newline at end of file From ecf8605310315aa57ce3b877efc1f748961fbe5e Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 6 Jul 2021 13:39:49 +0530 Subject: [PATCH 507/599] fix: added new docs --- .../guides/adding_custom_attachments.mdx | 5 ++ .../stream_chat_flutter/message_input.mdx | 50 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx diff --git a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx new file mode 100644 index 00000000..677a2b85 --- /dev/null +++ b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx @@ -0,0 +1,5 @@ +--- +id: adding_custom_attachments +sidebar_position: 3 +title: Adding Custom Attachments +--- \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx index 2d875eb9..d5f1bfbd 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx @@ -109,4 +109,54 @@ class _ChannelPageState extends State { ); } } +``` + +### Adding Custom Actions + +By default, the `MessageInput` has two actions: one for attachments and one for commands like giphy. +To add your own action, we use the `actions` parameter like this: + +```dart +MessageInput( + actions: [ + InkWell( + child: Icon( + Icons.location_on, + size: 20.0, + color: StreamChatTheme.of(context).colorTheme.grey, + ), + onTap: () { + // Do something here + }, + ), + ], +), +``` + +This will add on your action to the existing ones. + +### Disable Attachments + +To disable attachments being added to the message, set the `disableAttachments` parameter to true. + +```dart +MessageInput( + disableAttachments: true, +), +``` + +### Changing Position Of MessageInput Components + +You can also change the position of the TextField, actions and 'send' button relative to each other. + +To do this, use the `actionsLocation` or `sendButtonLocation` parameters which help you decide the location +of the buttons in the input. + +For example, if we want the actions on the right and the send button inside the TextField, we can do: + +```dart +MessageInput( + sendButtonLocation: SendButtonLocation.inside, + actionsLocation: ActionsLocation.right, +), ``` \ No newline at end of file From c785881f2c3f5cd8aee1faf1da295e371eafecd9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 6 Jul 2021 14:05:39 +0530 Subject: [PATCH 508/599] fix: added new docs --- .../stream_chat_flutter/channel_header.mdx | 75 ++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx index 71bb2306..d179a9d8 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx @@ -2,4 +2,77 @@ id: channel_header sidebar_position: 10 title: ChannelHeader ---- \ No newline at end of file +--- + +A Widget To Display Common Channel Details + +### Background + +When a user opens a channel, it is helpful to provide context of which channel they are in. This may +be in the form of a channel name or the users in the channel. Along with that, there also needs to be +a way for the user to look at more details of the channel (media, pinned messages, actions, etc.) and +preferably also a way to navigate back to where they came from. + +To encapsulate all of this functionality into one widget, the Flutter SDK contains a `ChannelHeader` +widget which provides these out of the box. + +### Basic Example + +Let's just add a `ChannelHeader` to a page with a `MessageListView` and a `MessageInput` to display +and send messages. + +```dart +class ChannelPage extends StatelessWidget { + const ChannelPage({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ChannelHeader(), + body: Column( + children: [ + Expanded( + child: MessageListView( + threadBuilder: (_, parentMessage) { + return ThreadPage( + parent: parentMessage, + ); + }, + ), + ), + MessageInput(), + ], + ), + ); + } +} +``` + +### Customizing Parts Of The Header + +The header works like a `ListTile` widget. + +Use the `title`, `subtitle`, `leading`, or `actions` parameters to substitute the widgets for your own. + +```dart +//... +ChannelHeader( + title: Text('My Custom Name'), +), +``` + +### Showing Connection State + +The `ChannelHeader` can also display connection state below the tile which shows the user if they +are connected or offline, etc. on connection events. + +To enable this, use the `showConnectionStateTile` property. + +```dart +//... +ChannelHeader( + showConnectionStateTile: true, +), +``` From 2363a6aca16d768afcdc1a299d1a00c579e75121 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 6 Jul 2021 14:22:24 +0530 Subject: [PATCH 509/599] fix: added new docs --- .../channel_list_header.mdx | 79 ++++++++++++++++++- .../stream_chat_flutter/channel_list_view.mdx | 14 ++-- 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx index af581c42..a3e3bd1d 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx @@ -2,4 +2,81 @@ id: channel_list_header sidebar_position: 9 title: ChannelListHeader ---- \ No newline at end of file +--- + +A Header Widget For A List Of Channels + +### Background + +A common pattern for most messaging apps is to show a list of Channels (chats) on the first screen +and navigate to an individual one on being clicked. On this first page where the list of channels are +displayed, it is usual to have functionality such as adding a new chat, display the user logged in, etc. + +To encapsulate all of this functionality into one widget, the Flutter SDK contains a `ChannelListHeader` +widget which provides these out of the box. + +### Basic Example + +This is a basic example of a page which has a `ChannelListView` and a `ChannelListHeader` to recreate a +common Channels Page. + +```dart +class DemoPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ChannelListHeader(), + body: ChannelsBloc( + child: ChannelListView( + filter: Filter.in_('members', [StreamChat.of(context).user.id]), + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), + ), + ), + ); + } +} +``` + +### Customizing Parts Of The Header + +The header works like a `ListTile` widget. + +Use the `titleBuilder`, `subtitle`, `leading`, or `actions` parameters to substitute the widgets for your own. + +```dart +//... +ChannelListHeader( + subtitle: Text('My Custom Subtitle'), +), +``` + +The `titleBuilder` param helps you build different titles depending on the connection state: + +```dart +//... +ChannelListHeader( + titleBuilder: (context, status, client) { + switch(status) { + /// Return your title widget + } + }, +), +``` + +### Showing Connection State + +The `ChannelListHeader` can also display connection state below the tile which shows the user if they +are connected or offline, etc. on connection events. + +To enable this, use the `showConnectionStateTile` property. + +```dart +//... +ChannelListHeader( + showConnectionStateTile: true, +), +``` \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx index f513d00a..2faeb4c4 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx @@ -30,13 +30,15 @@ class ChannelListPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - body: ChannelListView( - filter: Filter.in_('members', [StreamChat.of(context).user.id]), - sort: [SortOption('last_message_at')], - pagination: PaginationParams( - limit: 20, + body: ChannelsBloc( + child: ChannelListView( + filter: Filter.in_('members', [StreamChat.of(context).user.id]), + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), ), - channelWidget: ChannelPage(), ), ); } From a7ec39f3baaaf039b6b0f99cfa1041c6598ada3d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 6 Jul 2021 14:51:05 +0530 Subject: [PATCH 510/599] fix: added new docs --- .../message_search_list_view.mdx | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx index 4ea9f9f9..822d17f0 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx @@ -1,5 +1,58 @@ --- -id: message_Search_list_view +id: message_search_list_view sidebar_position: 8 title: MessageSearchListView ---- \ No newline at end of file +--- + +A Widget To Search For Messages Across Channels + +### Background + +Users in Stream Chat can have several channels and it can get hard to remember which channel has the +message they are searching for. As such, there needs to be a way to search for a message across multiple +channels. This is where `MessageSearchListView` comes in. + +### Basic Example + +While the MessageListView is tied to a certain `StreamChannel`, a `MessageSearchListView` is not. + +```dart +class MessageSearchPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: MessageSearchBloc( + child: MessageSearchListView( + filters: Filter.in_('members', [StreamChat.of(context).user!.id],), + messageQuery: 'your query here', + paginationParams: PaginationParams(limit: 20), + ), + ), + ); + } +} +``` + +### Customize The Result Tiles + +You can use your own widget for the result items using the `itemBuilder` parameter. + +```dart +MessageSearchListView( + // ... + itemBuilder: (context, response) { + return Text(response.message.text); + }, +), +``` + +### Show Result Count + +You show the number of results via the `showResultCount` parameter. + +```dart +MessageSearchListView( + // ... + showResultCount: true, +), +``` From 643a62eefd95a31ba4934262536fe33826f9e239 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 6 Jul 2021 15:33:30 +0530 Subject: [PATCH 511/599] feat: added new docs --- .../stream_chat_flutter/user_list_view.mdx | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx index 489cc141..e9891018 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx @@ -4,3 +4,85 @@ sidebar_position: 7 title: UserListView --- +A Widget For Displaying And Selecting Users + +### Background + +A list of users is required for many different purposes: showing a list of users in a Channel, +selecting users to add in a channel, etc. The `UserListView` displays and allows selection of a list +of users along with multiple display configurations like a list and grid. + +### Basic Example + +Let's take a look at an example where we use the widget to autocomplete user names: + +```dart +class UsersListPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: UsersBloc( + child: UsersListView( + filter: Filter.and([ + Filter.autoComplete('name', 'search_here'), + Filter.notEqual( + 'id', StreamChat.of(context).user!.id), + ]), + sort: [ + SortOption( + 'name', + direction: 1, + ), + ], + pagination: PaginationParams( + limit: 25, + ), + ), + ), + ); + } +} +``` + +### Customize The User Items + +You can use your own widget for the user items using the `userItemBuilder` parameter. + +```dart +UsersListView( + // ... + userItemBuilder: (context, user, isSelected) { + return Text(user.name); + }, +), +``` + +### Group Alphabetically + +You can group alphabetically using the `groupAlphabetically` parameter: + +```dart +UsersListView( + //... + groupAlphabetically: true, +), +``` + +### Selecting Users + +The `UserListView` widget allows selecting users in a list by supplying a selected users and callbacks +for when user items are tapped. + +```dart +Set? selectedUsers = {}; + +UsersListView( + //... + selectedUsers: selectedUsers, + onUserTap: (user, _) { + setState(() { + selectedUsers.add(user); + }); + }, +), +``` From c4a884bb25c4579f6249abe12093904fecc8830c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 6 Jul 2021 16:19:45 +0530 Subject: [PATCH 512/599] feat: added new docs --- .../stream_chat_flutter/message_widget.mdx | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx index eaafbb7e..3f8e90ad 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx @@ -2,4 +2,47 @@ id: message_widget sidebar_position: 11 title: MessageWidget ---- \ No newline at end of file +--- + +A Widget For Displaying Messages And Attachments + +### Background + +There are several things that need to be displayed with text in a message in a modern messaging app: +attachments, highlights if the message is pinned, user avatars of the sender, etc. + +To encapsulate all of this functionality into one widget, the Flutter SDK contains a `MessageWidget` +widget which provides these out of the box. + +### Basic Example (Modifying `MessageWidget` in `MessageListView`) + +Primarily, the `MessageWidget` is used in the `MessageListView`. To customize only a few properties +of the `MessageWidget` without supplying all other properties, the `messageBuilder` builder supplies +a default implementation of the widget for us to modify. + +```dart +class ChannelPage extends StatelessWidget { + const ChannelPage({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: MessageListView( + messageBuilder: (context, details, messageList, defaultMessageWidget) { + return defaultMessageWidget.copyWith( + showThreadReplyIndicator: false, + ); + }, + ), + + ); + } +} +``` + +### Building A Custom Attachment + +When a custom attachment type (location, audio, etc.) is sent, the MessageWidget also needs to know +how to build it. \ No newline at end of file From bee61cb4fa3d44a6a88aeb3ddf56a4955546a914 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 6 Jul 2021 16:43:54 +0530 Subject: [PATCH 513/599] feat: added new docs --- .../stream_chat_flutter/message_widget.mdx | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx index 3f8e90ad..89a7cf0f 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx @@ -45,4 +45,52 @@ class ChannelPage extends StatelessWidget { ### Building A Custom Attachment When a custom attachment type (location, audio, etc.) is sent, the MessageWidget also needs to know -how to build it. \ No newline at end of file +how to build it. For this purpose, we can use the `customAttachmentBuilders` parameter. + +As an example, if a message has a attachment type 'location', we do: + +```dart +MessageWidget( + //... + customAttachmentBuilders: { + 'location': (context, message, attachments) { + var attachmentWidget = Image.network( + _buildMapAttachment( + attachments[0].extraData['latitude'], + attachments[0].extraData['longitude'], + ), + ); + + return wrapAttachmentWidget(context, attachmentWidget, null, true, BorderRadius.circular(8.0)); + } + }, +) +``` + +You can also override the builder for existing attachment types like `image` and `video`. + +### Show User Avatar For Messages + +You can decide to show, hide, or remove user avatars of the sender of the message. To do this, set +the `showUserAvatar` property like this: + +```dart +MessageWidget( + //... + showUserAvatar = DisplayWidget.show, +) +``` + +### Reverse the message + +In most cases, `MessageWidget` needs to be a different orientation depending upon if the sender is the +user or someone else. + +For this, we use the `reverse` parameter to change the orientation of the message: + +```dart +MessageWidget( + //... + reverse = true, +) +``` \ No newline at end of file From 0379a1776727aea6cd90e3675720f2eda758a0e7 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 6 Jul 2021 13:20:34 +0200 Subject: [PATCH 514/599] add video compress options (frame and quality) to message input --- .../lib/src/message_input.dart | 24 +++++++++++++++---- .../lib/src/video_service.dart | 14 +++++++---- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index a0ff93cf..c4a665e6 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -25,6 +25,8 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:substring_highlight/substring_highlight.dart'; import 'package:video_compress/video_compress.dart'; +export 'package:video_compress/video_compress.dart' show VideoQuality; + /// Builder for attachment thumbnails typedef AttachmentThumbnailBuilder = Widget Function( BuildContext, @@ -149,11 +151,19 @@ class MessageInput extends StatefulWidget { this.showCommandsButton = true, this.mentionsTileBuilder, this.maxAttachmentSize = _kDefaultMaxAttachmentSize, + this.compressedVideoQuality = VideoQuality.DefaultQuality, + this.compressedVideoFrameRate = 30, }) : super(key: key); /// Message to edit final Message? editMessage; + /// Video quality to use when compressing the videos + final VideoQuality compressedVideoQuality; + + /// Frame rate to use when compressing the videos + final int compressedVideoFrameRate; + /// Max attachment size in bytes /// Defaults to 20 MB /// do not set it if you're using our default CDN @@ -1116,8 +1126,11 @@ class MessageInputState extends State { if (file.size! > widget.maxAttachmentSize) { if (medium.type == AssetType.video && file.path != null) { - final mediaInfo = await (VideoService.compressVideo(file.path!) - as FutureOr); + final mediaInfo = await (VideoService.compressVideo( + file.path!, + frameRate: widget.compressedVideoFrameRate, + quality: widget.compressedVideoQuality, + ) as FutureOr); if (mediaInfo.filesize! > widget.maxAttachmentSize) { _showErrorAlert( @@ -1892,8 +1905,11 @@ class MessageInputState extends State { if (file.size! > widget.maxAttachmentSize) { if (attachmentType == 'video' && file.path != null) { - final mediaInfo = await (VideoService.compressVideo(file.path!) - as FutureOr); + final mediaInfo = await (VideoService.compressVideo( + file.path!, + frameRate: widget.compressedVideoFrameRate, + quality: widget.compressedVideoQuality, + ) as FutureOr); if (mediaInfo.filesize! > widget.maxAttachmentSize) { _showErrorAlert( diff --git a/packages/stream_chat_flutter/lib/src/video_service.dart b/packages/stream_chat_flutter/lib/src/video_service.dart index 290dd54b..e914f466 100644 --- a/packages/stream_chat_flutter/lib/src/video_service.dart +++ b/packages/stream_chat_flutter/lib/src/video_service.dart @@ -16,21 +16,25 @@ class IVideoService { /// compress video from [path] /// compress video from [path] return [Future] /// - /// you can choose its quality by [quality], - /// determine whether to delete his source file by [deleteOrigin] - /// optional parameters [startTime] [duration] [includeAudio] [frameRate] + /// you can choose its [quality] and [frameRate] /// /// ## example /// ```dart /// final info = await _flutterVideoCompress.compressVideo( /// file.path, - /// deleteOrigin: true, /// ); /// debugPrint(info.toJson()); /// ``` - Future compressVideo(String path) async => _lock.synchronized( + Future compressVideo( + String path, { + int frameRate = 30, + VideoQuality quality = VideoQuality.DefaultQuality, + }) async => + _lock.synchronized( () => VideoCompress.compressVideo( path, + frameRate: frameRate, + quality: quality, ), ); From 38eb8f527d96e37e28ca2839e80a503f3308219f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 6 Jul 2021 17:25:25 +0530 Subject: [PATCH 515/599] feat: added new docs --- .../Flutter/stream_chat_flutter/setup.mdx | 19 +++++++++++++++++++ .../stream_chat_flutter_core/setup.mdx | 19 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx index b4141769..4ad387d5 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx @@ -4,3 +4,22 @@ sidebar_position: 2 title: Setup --- +Understanding Setup For stream_chat_flutter + +### Add pub.dev dependency + +First, you need to add the `stream_chat_flutter` dependency to your pubspec.yaml + +You can either run this command: + +``` +flutter pub add stream_chat_flutter +``` + +OR + +Add this line in the dependencies section of your pubspec.yaml after substituting latest version: + +dependencies: + stream_chat_flutter_core: ^latest_version + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx index 68ca94c9..e295ba8f 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx @@ -4,5 +4,22 @@ sidebar_position: 2 title: Setup --- -Understanding Setup +Understanding Setup For stream_chat_flutter_core + +### Add pub.dev dependency + +First, you need to add the `stream_chat_flutter_core` dependency to your pubspec.yaml + +You can either run this command: + +``` +flutter pub add stream_chat_flutter_core +``` + +OR + +Add this line in the dependencies section of your pubspec.yaml after substituting latest version: + +dependencies: + stream_chat_flutter_core: ^latest_version From 5d735f1571376ac6273e710f945accf2e4b1597b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 6 Jul 2021 17:29:50 +0200 Subject: [PATCH 516/599] regenerate files --- .../lib/src/db/moor_chat_database.g.dart | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index fe921e70..8a9f3d7d 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -624,8 +624,8 @@ class $ChannelsTable extends Channels Set get $primaryKey => {cid}; @override ChannelEntity map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return ChannelEntity.fromData(data, _db, prefix: effectivePrefix); + return ChannelEntity.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -1794,8 +1794,8 @@ class $MessagesTable extends Messages Set get $primaryKey => {id}; @override MessageEntity map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return MessageEntity.fromData(data, _db, prefix: effectivePrefix); + return MessageEntity.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -2977,8 +2977,8 @@ class $PinnedMessagesTable extends PinnedMessages Set get $primaryKey => {id}; @override PinnedMessageEntity map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return PinnedMessageEntity.fromData(data, _db, prefix: effectivePrefix); + return PinnedMessageEntity.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -3343,8 +3343,8 @@ class $ReactionsTable extends Reactions Set get $primaryKey => {messageId, type, userId}; @override ReactionEntity map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return ReactionEntity.fromData(data, _db, prefix: effectivePrefix); + return ReactionEntity.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -3776,8 +3776,8 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { Set get $primaryKey => {id}; @override UserEntity map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return UserEntity.fromData(data, _db, prefix: effectivePrefix); + return UserEntity.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -4360,8 +4360,8 @@ class $MembersTable extends Members Set get $primaryKey => {userId, channelCid}; @override MemberEntity map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return MemberEntity.fromData(data, _db, prefix: effectivePrefix); + return MemberEntity.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -4635,8 +4635,8 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { Set get $primaryKey => {userId, channelCid}; @override ReadEntity map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return ReadEntity.fromData(data, _db, prefix: effectivePrefix); + return ReadEntity.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -4826,8 +4826,8 @@ class $ChannelQueriesTable extends ChannelQueries Set get $primaryKey => {queryHash, channelCid}; @override ChannelQueryEntity map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return ChannelQueryEntity.fromData(data, _db, prefix: effectivePrefix); + return ChannelQueryEntity.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -5252,8 +5252,8 @@ class $ConnectionEventsTable extends ConnectionEvents Set get $primaryKey => {id}; @override ConnectionEventEntity map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return ConnectionEventEntity.fromData(data, _db, prefix: effectivePrefix); + return ConnectionEventEntity.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override From 657380f4e6e384e8e3c6dc7c74f41cb503838584 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 6 Jul 2021 17:35:48 +0200 Subject: [PATCH 517/599] regenerate files --- .../lib/src/db/moor_chat_database.g.dart | 1341 ++++++----------- packages/stream_chat_persistence/pubspec.yaml | 2 +- 2 files changed, 424 insertions(+), 919 deletions(-) diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index 8a9f3d7d..6a5bc38a 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -416,128 +416,67 @@ class $ChannelsTable extends Channels final String? _alias; $ChannelsTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedTextColumn id = _constructId(); - GeneratedTextColumn _constructId() { - return GeneratedTextColumn( - 'id', - $tableName, - false, - ); - } - + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _typeMeta = const VerificationMeta('type'); - @override - late final GeneratedTextColumn type = _constructType(); - GeneratedTextColumn _constructType() { - return GeneratedTextColumn( - 'type', - $tableName, - false, - ); - } - + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _cidMeta = const VerificationMeta('cid'); - @override - late final GeneratedTextColumn cid = _constructCid(); - GeneratedTextColumn _constructCid() { - return GeneratedTextColumn( - 'cid', - $tableName, - false, - ); - } - + late final GeneratedColumn cid = GeneratedColumn( + 'cid', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _configMeta = const VerificationMeta('config'); - @override - late final GeneratedTextColumn config = _constructConfig(); - GeneratedTextColumn _constructConfig() { - return GeneratedTextColumn( - 'config', - $tableName, - false, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + config = GeneratedColumn('config', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true) + .withConverter>($ChannelsTable.$converter0); final VerificationMeta _frozenMeta = const VerificationMeta('frozen'); - @override - late final GeneratedBoolColumn frozen = _constructFrozen(); - GeneratedBoolColumn _constructFrozen() { - return GeneratedBoolColumn('frozen', $tableName, false, - defaultValue: const Constant(false)); - } - + late final GeneratedColumn frozen = GeneratedColumn( + 'frozen', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (frozen IN (0, 1))', + defaultValue: const Constant(false)); final VerificationMeta _lastMessageAtMeta = const VerificationMeta('lastMessageAt'); - @override - late final GeneratedDateTimeColumn lastMessageAt = _constructLastMessageAt(); - GeneratedDateTimeColumn _constructLastMessageAt() { - return GeneratedDateTimeColumn( - 'last_message_at', - $tableName, - true, - ); - } - + late final GeneratedColumn lastMessageAt = + GeneratedColumn('last_message_at', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - @override - late final GeneratedDateTimeColumn createdAt = _constructCreatedAt(); - GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn('created_at', $tableName, false, - defaultValue: currentDateAndTime); - } - + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: currentDateAndTime); final VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); - @override - late final GeneratedDateTimeColumn updatedAt = _constructUpdatedAt(); - GeneratedDateTimeColumn _constructUpdatedAt() { - return GeneratedDateTimeColumn('updated_at', $tableName, false, - defaultValue: currentDateAndTime); - } - + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: currentDateAndTime); final VerificationMeta _deletedAtMeta = const VerificationMeta('deletedAt'); - @override - late final GeneratedDateTimeColumn deletedAt = _constructDeletedAt(); - GeneratedDateTimeColumn _constructDeletedAt() { - return GeneratedDateTimeColumn( - 'deleted_at', - $tableName, - true, - ); - } - + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _memberCountMeta = const VerificationMeta('memberCount'); - @override - late final GeneratedIntColumn memberCount = _constructMemberCount(); - GeneratedIntColumn _constructMemberCount() { - return GeneratedIntColumn('member_count', $tableName, false, - defaultValue: const Constant(0)); - } - + late final GeneratedColumn memberCount = GeneratedColumn( + 'member_count', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: const Constant(0)); final VerificationMeta _createdByIdMeta = const VerificationMeta('createdById'); - @override - late final GeneratedTextColumn createdById = _constructCreatedById(); - GeneratedTextColumn _constructCreatedById() { - return GeneratedTextColumn( - 'created_by_id', - $tableName, - true, - ); - } - + late final GeneratedColumn createdById = GeneratedColumn( + 'created_by_id', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); - @override - late final GeneratedTextColumn extraData = _constructExtraData(); - GeneratedTextColumn _constructExtraData() { - return GeneratedTextColumn( - 'extra_data', - $tableName, - true, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + extraData = GeneratedColumn('extra_data', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false) + .withConverter>($ChannelsTable.$converter1); @override List get $columns => [ id, @@ -554,11 +493,9 @@ class $ChannelsTable extends Channels extraData ]; @override - $ChannelsTable get asDslTable => this; + String get aliasedName => _alias ?? 'channels'; @override - String get $tableName => _alias ?? 'channels'; - @override - final String actualTableName = 'channels'; + String get actualTableName => 'channels'; @override VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { @@ -1405,257 +1342,137 @@ class $MessagesTable extends Messages final String? _alias; $MessagesTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedTextColumn id = _constructId(); - GeneratedTextColumn _constructId() { - return GeneratedTextColumn( - 'id', - $tableName, - false, - ); - } - + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _messageTextMeta = const VerificationMeta('messageText'); - @override - late final GeneratedTextColumn messageText = _constructMessageText(); - GeneratedTextColumn _constructMessageText() { - return GeneratedTextColumn( - 'message_text', - $tableName, - true, - ); - } - + late final GeneratedColumn messageText = GeneratedColumn( + 'message_text', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _attachmentsMeta = const VerificationMeta('attachments'); - @override - late final GeneratedTextColumn attachments = _constructAttachments(); - GeneratedTextColumn _constructAttachments() { - return GeneratedTextColumn( - 'attachments', - $tableName, - false, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + attachments = GeneratedColumn('attachments', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true) + .withConverter>($MessagesTable.$converter0); final VerificationMeta _statusMeta = const VerificationMeta('status'); - @override - late final GeneratedIntColumn status = _constructStatus(); - GeneratedIntColumn _constructStatus() { - return GeneratedIntColumn('status', $tableName, false, - defaultValue: const Constant(1)); - } - + late final GeneratedColumnWithTypeConverter + status = GeneratedColumn('status', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: const Constant(1)) + .withConverter($MessagesTable.$converter1); final VerificationMeta _typeMeta = const VerificationMeta('type'); - @override - late final GeneratedTextColumn type = _constructType(); - GeneratedTextColumn _constructType() { - return GeneratedTextColumn('type', $tableName, false, - defaultValue: const Constant('regular')); - } - + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + typeName: 'TEXT', + requiredDuringInsert: false, + defaultValue: const Constant('regular')); final VerificationMeta _mentionedUsersMeta = const VerificationMeta('mentionedUsers'); - @override - late final GeneratedTextColumn mentionedUsers = _constructMentionedUsers(); - GeneratedTextColumn _constructMentionedUsers() { - return GeneratedTextColumn( - 'mentioned_users', - $tableName, - false, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + mentionedUsers = GeneratedColumn( + 'mentioned_users', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true) + .withConverter>($MessagesTable.$converter2); final VerificationMeta _reactionCountsMeta = const VerificationMeta('reactionCounts'); - @override - late final GeneratedTextColumn reactionCounts = _constructReactionCounts(); - GeneratedTextColumn _constructReactionCounts() { - return GeneratedTextColumn( - 'reaction_counts', - $tableName, - true, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + reactionCounts = GeneratedColumn( + 'reaction_counts', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false) + .withConverter>($MessagesTable.$converter3); final VerificationMeta _reactionScoresMeta = const VerificationMeta('reactionScores'); - @override - late final GeneratedTextColumn reactionScores = _constructReactionScores(); - GeneratedTextColumn _constructReactionScores() { - return GeneratedTextColumn( - 'reaction_scores', - $tableName, - true, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + reactionScores = GeneratedColumn( + 'reaction_scores', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false) + .withConverter>($MessagesTable.$converter4); final VerificationMeta _parentIdMeta = const VerificationMeta('parentId'); - @override - late final GeneratedTextColumn parentId = _constructParentId(); - GeneratedTextColumn _constructParentId() { - return GeneratedTextColumn( - 'parent_id', - $tableName, - true, - ); - } - + late final GeneratedColumn parentId = GeneratedColumn( + 'parent_id', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _quotedMessageIdMeta = const VerificationMeta('quotedMessageId'); - @override - late final GeneratedTextColumn quotedMessageId = _constructQuotedMessageId(); - GeneratedTextColumn _constructQuotedMessageId() { - return GeneratedTextColumn( - 'quoted_message_id', - $tableName, - true, - ); - } - + late final GeneratedColumn quotedMessageId = + GeneratedColumn('quoted_message_id', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _replyCountMeta = const VerificationMeta('replyCount'); - @override - late final GeneratedIntColumn replyCount = _constructReplyCount(); - GeneratedIntColumn _constructReplyCount() { - return GeneratedIntColumn( - 'reply_count', - $tableName, - true, - ); - } - + late final GeneratedColumn replyCount = GeneratedColumn( + 'reply_count', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _showInChannelMeta = const VerificationMeta('showInChannel'); - @override - late final GeneratedBoolColumn showInChannel = _constructShowInChannel(); - GeneratedBoolColumn _constructShowInChannel() { - return GeneratedBoolColumn( - 'show_in_channel', - $tableName, - true, - ); - } - + late final GeneratedColumn showInChannel = GeneratedColumn( + 'show_in_channel', aliasedName, true, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (show_in_channel IN (0, 1))'); final VerificationMeta _shadowedMeta = const VerificationMeta('shadowed'); - @override - late final GeneratedBoolColumn shadowed = _constructShadowed(); - GeneratedBoolColumn _constructShadowed() { - return GeneratedBoolColumn('shadowed', $tableName, false, - defaultValue: const Constant(false)); - } - + late final GeneratedColumn shadowed = GeneratedColumn( + 'shadowed', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (shadowed IN (0, 1))', + defaultValue: const Constant(false)); final VerificationMeta _commandMeta = const VerificationMeta('command'); - @override - late final GeneratedTextColumn command = _constructCommand(); - GeneratedTextColumn _constructCommand() { - return GeneratedTextColumn( - 'command', - $tableName, - true, - ); - } - + late final GeneratedColumn command = GeneratedColumn( + 'command', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - @override - late final GeneratedDateTimeColumn createdAt = _constructCreatedAt(); - GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn('created_at', $tableName, false, - defaultValue: currentDateAndTime); - } - + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: currentDateAndTime); final VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); - @override - late final GeneratedDateTimeColumn updatedAt = _constructUpdatedAt(); - GeneratedDateTimeColumn _constructUpdatedAt() { - return GeneratedDateTimeColumn('updated_at', $tableName, false, - defaultValue: currentDateAndTime); - } - + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: currentDateAndTime); final VerificationMeta _deletedAtMeta = const VerificationMeta('deletedAt'); - @override - late final GeneratedDateTimeColumn deletedAt = _constructDeletedAt(); - GeneratedDateTimeColumn _constructDeletedAt() { - return GeneratedDateTimeColumn( - 'deleted_at', - $tableName, - true, - ); - } - + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _userIdMeta = const VerificationMeta('userId'); - @override - late final GeneratedTextColumn userId = _constructUserId(); - GeneratedTextColumn _constructUserId() { - return GeneratedTextColumn( - 'user_id', - $tableName, - true, - ); - } - + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); - @override - late final GeneratedBoolColumn pinned = _constructPinned(); - GeneratedBoolColumn _constructPinned() { - return GeneratedBoolColumn('pinned', $tableName, false, - defaultValue: const Constant(false)); - } - + late final GeneratedColumn pinned = GeneratedColumn( + 'pinned', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (pinned IN (0, 1))', + defaultValue: const Constant(false)); final VerificationMeta _pinnedAtMeta = const VerificationMeta('pinnedAt'); - @override - late final GeneratedDateTimeColumn pinnedAt = _constructPinnedAt(); - GeneratedDateTimeColumn _constructPinnedAt() { - return GeneratedDateTimeColumn( - 'pinned_at', - $tableName, - true, - ); - } - + late final GeneratedColumn pinnedAt = GeneratedColumn( + 'pinned_at', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _pinExpiresMeta = const VerificationMeta('pinExpires'); - @override - late final GeneratedDateTimeColumn pinExpires = _constructPinExpires(); - GeneratedDateTimeColumn _constructPinExpires() { - return GeneratedDateTimeColumn( - 'pin_expires', - $tableName, - true, - ); - } - + late final GeneratedColumn pinExpires = GeneratedColumn( + 'pin_expires', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _pinnedByUserIdMeta = const VerificationMeta('pinnedByUserId'); - @override - late final GeneratedTextColumn pinnedByUserId = _constructPinnedByUserId(); - GeneratedTextColumn _constructPinnedByUserId() { - return GeneratedTextColumn( - 'pinned_by_user_id', - $tableName, - true, - ); - } - + late final GeneratedColumn pinnedByUserId = GeneratedColumn( + 'pinned_by_user_id', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); - @override - late final GeneratedTextColumn channelCid = _constructChannelCid(); - GeneratedTextColumn _constructChannelCid() { - return GeneratedTextColumn('channel_cid', $tableName, true, - $customConstraints: - 'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE'); - } - + late final GeneratedColumn channelCid = GeneratedColumn( + 'channel_cid', aliasedName, true, + typeName: 'TEXT', + requiredDuringInsert: false, + $customConstraints: + 'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE'); final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); - @override - late final GeneratedTextColumn extraData = _constructExtraData(); - GeneratedTextColumn _constructExtraData() { - return GeneratedTextColumn( - 'extra_data', - $tableName, - true, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + extraData = GeneratedColumn('extra_data', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false) + .withConverter>($MessagesTable.$converter5); @override List get $columns => [ id, @@ -1684,11 +1501,9 @@ class $MessagesTable extends Messages extraData ]; @override - $MessagesTable get asDslTable => this; + String get aliasedName => _alias ?? 'messages'; @override - String get $tableName => _alias ?? 'messages'; - @override - final String actualTableName = 'messages'; + String get actualTableName => 'messages'; @override VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { @@ -2587,257 +2402,139 @@ class $PinnedMessagesTable extends PinnedMessages final String? _alias; $PinnedMessagesTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedTextColumn id = _constructId(); - GeneratedTextColumn _constructId() { - return GeneratedTextColumn( - 'id', - $tableName, - false, - ); - } - + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _messageTextMeta = const VerificationMeta('messageText'); - @override - late final GeneratedTextColumn messageText = _constructMessageText(); - GeneratedTextColumn _constructMessageText() { - return GeneratedTextColumn( - 'message_text', - $tableName, - true, - ); - } - + late final GeneratedColumn messageText = GeneratedColumn( + 'message_text', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _attachmentsMeta = const VerificationMeta('attachments'); - @override - late final GeneratedTextColumn attachments = _constructAttachments(); - GeneratedTextColumn _constructAttachments() { - return GeneratedTextColumn( - 'attachments', - $tableName, - false, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + attachments = GeneratedColumn('attachments', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true) + .withConverter>($PinnedMessagesTable.$converter0); final VerificationMeta _statusMeta = const VerificationMeta('status'); - @override - late final GeneratedIntColumn status = _constructStatus(); - GeneratedIntColumn _constructStatus() { - return GeneratedIntColumn('status', $tableName, false, - defaultValue: const Constant(1)); - } - + late final GeneratedColumnWithTypeConverter + status = GeneratedColumn('status', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: const Constant(1)) + .withConverter( + $PinnedMessagesTable.$converter1); final VerificationMeta _typeMeta = const VerificationMeta('type'); - @override - late final GeneratedTextColumn type = _constructType(); - GeneratedTextColumn _constructType() { - return GeneratedTextColumn('type', $tableName, false, - defaultValue: const Constant('regular')); - } - + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + typeName: 'TEXT', + requiredDuringInsert: false, + defaultValue: const Constant('regular')); final VerificationMeta _mentionedUsersMeta = const VerificationMeta('mentionedUsers'); - @override - late final GeneratedTextColumn mentionedUsers = _constructMentionedUsers(); - GeneratedTextColumn _constructMentionedUsers() { - return GeneratedTextColumn( - 'mentioned_users', - $tableName, - false, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + mentionedUsers = GeneratedColumn( + 'mentioned_users', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true) + .withConverter>($PinnedMessagesTable.$converter2); final VerificationMeta _reactionCountsMeta = const VerificationMeta('reactionCounts'); - @override - late final GeneratedTextColumn reactionCounts = _constructReactionCounts(); - GeneratedTextColumn _constructReactionCounts() { - return GeneratedTextColumn( - 'reaction_counts', - $tableName, - true, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + reactionCounts = GeneratedColumn( + 'reaction_counts', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false) + .withConverter>($PinnedMessagesTable.$converter3); final VerificationMeta _reactionScoresMeta = const VerificationMeta('reactionScores'); - @override - late final GeneratedTextColumn reactionScores = _constructReactionScores(); - GeneratedTextColumn _constructReactionScores() { - return GeneratedTextColumn( - 'reaction_scores', - $tableName, - true, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + reactionScores = GeneratedColumn( + 'reaction_scores', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false) + .withConverter>($PinnedMessagesTable.$converter4); final VerificationMeta _parentIdMeta = const VerificationMeta('parentId'); - @override - late final GeneratedTextColumn parentId = _constructParentId(); - GeneratedTextColumn _constructParentId() { - return GeneratedTextColumn( - 'parent_id', - $tableName, - true, - ); - } - + late final GeneratedColumn parentId = GeneratedColumn( + 'parent_id', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _quotedMessageIdMeta = const VerificationMeta('quotedMessageId'); - @override - late final GeneratedTextColumn quotedMessageId = _constructQuotedMessageId(); - GeneratedTextColumn _constructQuotedMessageId() { - return GeneratedTextColumn( - 'quoted_message_id', - $tableName, - true, - ); - } - + late final GeneratedColumn quotedMessageId = + GeneratedColumn('quoted_message_id', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _replyCountMeta = const VerificationMeta('replyCount'); - @override - late final GeneratedIntColumn replyCount = _constructReplyCount(); - GeneratedIntColumn _constructReplyCount() { - return GeneratedIntColumn( - 'reply_count', - $tableName, - true, - ); - } - + late final GeneratedColumn replyCount = GeneratedColumn( + 'reply_count', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _showInChannelMeta = const VerificationMeta('showInChannel'); - @override - late final GeneratedBoolColumn showInChannel = _constructShowInChannel(); - GeneratedBoolColumn _constructShowInChannel() { - return GeneratedBoolColumn( - 'show_in_channel', - $tableName, - true, - ); - } - + late final GeneratedColumn showInChannel = GeneratedColumn( + 'show_in_channel', aliasedName, true, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (show_in_channel IN (0, 1))'); final VerificationMeta _shadowedMeta = const VerificationMeta('shadowed'); - @override - late final GeneratedBoolColumn shadowed = _constructShadowed(); - GeneratedBoolColumn _constructShadowed() { - return GeneratedBoolColumn('shadowed', $tableName, false, - defaultValue: const Constant(false)); - } - + late final GeneratedColumn shadowed = GeneratedColumn( + 'shadowed', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (shadowed IN (0, 1))', + defaultValue: const Constant(false)); final VerificationMeta _commandMeta = const VerificationMeta('command'); - @override - late final GeneratedTextColumn command = _constructCommand(); - GeneratedTextColumn _constructCommand() { - return GeneratedTextColumn( - 'command', - $tableName, - true, - ); - } - + late final GeneratedColumn command = GeneratedColumn( + 'command', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - @override - late final GeneratedDateTimeColumn createdAt = _constructCreatedAt(); - GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn('created_at', $tableName, false, - defaultValue: currentDateAndTime); - } - + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: currentDateAndTime); final VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); - @override - late final GeneratedDateTimeColumn updatedAt = _constructUpdatedAt(); - GeneratedDateTimeColumn _constructUpdatedAt() { - return GeneratedDateTimeColumn('updated_at', $tableName, false, - defaultValue: currentDateAndTime); - } - + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: currentDateAndTime); final VerificationMeta _deletedAtMeta = const VerificationMeta('deletedAt'); - @override - late final GeneratedDateTimeColumn deletedAt = _constructDeletedAt(); - GeneratedDateTimeColumn _constructDeletedAt() { - return GeneratedDateTimeColumn( - 'deleted_at', - $tableName, - true, - ); - } - + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _userIdMeta = const VerificationMeta('userId'); - @override - late final GeneratedTextColumn userId = _constructUserId(); - GeneratedTextColumn _constructUserId() { - return GeneratedTextColumn( - 'user_id', - $tableName, - true, - ); - } - + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); - @override - late final GeneratedBoolColumn pinned = _constructPinned(); - GeneratedBoolColumn _constructPinned() { - return GeneratedBoolColumn('pinned', $tableName, false, - defaultValue: const Constant(false)); - } - + late final GeneratedColumn pinned = GeneratedColumn( + 'pinned', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (pinned IN (0, 1))', + defaultValue: const Constant(false)); final VerificationMeta _pinnedAtMeta = const VerificationMeta('pinnedAt'); - @override - late final GeneratedDateTimeColumn pinnedAt = _constructPinnedAt(); - GeneratedDateTimeColumn _constructPinnedAt() { - return GeneratedDateTimeColumn( - 'pinned_at', - $tableName, - true, - ); - } - + late final GeneratedColumn pinnedAt = GeneratedColumn( + 'pinned_at', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _pinExpiresMeta = const VerificationMeta('pinExpires'); - @override - late final GeneratedDateTimeColumn pinExpires = _constructPinExpires(); - GeneratedDateTimeColumn _constructPinExpires() { - return GeneratedDateTimeColumn( - 'pin_expires', - $tableName, - true, - ); - } - + late final GeneratedColumn pinExpires = GeneratedColumn( + 'pin_expires', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _pinnedByUserIdMeta = const VerificationMeta('pinnedByUserId'); - @override - late final GeneratedTextColumn pinnedByUserId = _constructPinnedByUserId(); - GeneratedTextColumn _constructPinnedByUserId() { - return GeneratedTextColumn( - 'pinned_by_user_id', - $tableName, - true, - ); - } - + late final GeneratedColumn pinnedByUserId = GeneratedColumn( + 'pinned_by_user_id', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); - @override - late final GeneratedTextColumn channelCid = _constructChannelCid(); - GeneratedTextColumn _constructChannelCid() { - return GeneratedTextColumn('channel_cid', $tableName, true, - $customConstraints: - 'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE'); - } - + late final GeneratedColumn channelCid = GeneratedColumn( + 'channel_cid', aliasedName, true, + typeName: 'TEXT', + requiredDuringInsert: false, + $customConstraints: + 'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE'); final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); - @override - late final GeneratedTextColumn extraData = _constructExtraData(); - GeneratedTextColumn _constructExtraData() { - return GeneratedTextColumn( - 'extra_data', - $tableName, - true, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + extraData = GeneratedColumn('extra_data', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false) + .withConverter>( + $PinnedMessagesTable.$converter5); @override List get $columns => [ id, @@ -2866,11 +2563,9 @@ class $PinnedMessagesTable extends PinnedMessages extraData ]; @override - $PinnedMessagesTable get asDslTable => this; + String get aliasedName => _alias ?? 'pinned_messages'; @override - String get $tableName => _alias ?? 'pinned_messages'; - @override - final String actualTableName = 'pinned_messages'; + String get actualTableName => 'pinned_messages'; @override VerificationContext validateIntegrity( Insertable instance, @@ -3239,71 +2934,43 @@ class $ReactionsTable extends Reactions final String? _alias; $ReactionsTable(this._db, [this._alias]); final VerificationMeta _userIdMeta = const VerificationMeta('userId'); - @override - late final GeneratedTextColumn userId = _constructUserId(); - GeneratedTextColumn _constructUserId() { - return GeneratedTextColumn( - 'user_id', - $tableName, - false, - ); - } - + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _messageIdMeta = const VerificationMeta('messageId'); - @override - late final GeneratedTextColumn messageId = _constructMessageId(); - GeneratedTextColumn _constructMessageId() { - return GeneratedTextColumn('message_id', $tableName, false, - $customConstraints: 'REFERENCES messages(id) ON DELETE CASCADE'); - } - + late final GeneratedColumn messageId = GeneratedColumn( + 'message_id', aliasedName, false, + typeName: 'TEXT', + requiredDuringInsert: true, + $customConstraints: 'REFERENCES messages(id) ON DELETE CASCADE'); final VerificationMeta _typeMeta = const VerificationMeta('type'); - @override - late final GeneratedTextColumn type = _constructType(); - GeneratedTextColumn _constructType() { - return GeneratedTextColumn( - 'type', - $tableName, - false, - ); - } - + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - @override - late final GeneratedDateTimeColumn createdAt = _constructCreatedAt(); - GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn('created_at', $tableName, false, - defaultValue: currentDateAndTime); - } - + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: currentDateAndTime); final VerificationMeta _scoreMeta = const VerificationMeta('score'); - @override - late final GeneratedIntColumn score = _constructScore(); - GeneratedIntColumn _constructScore() { - return GeneratedIntColumn('score', $tableName, false, - defaultValue: const Constant(0)); - } - + late final GeneratedColumn score = GeneratedColumn( + 'score', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: const Constant(0)); final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); - @override - late final GeneratedTextColumn extraData = _constructExtraData(); - GeneratedTextColumn _constructExtraData() { - return GeneratedTextColumn( - 'extra_data', - $tableName, - true, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + extraData = GeneratedColumn('extra_data', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false) + .withConverter>($ReactionsTable.$converter0); @override List get $columns => [userId, messageId, type, createdAt, score, extraData]; @override - $ReactionsTable get asDslTable => this; + String get aliasedName => _alias ?? 'reactions'; @override - String get $tableName => _alias ?? 'reactions'; - @override - final String actualTableName = 'reactions'; + String get actualTableName => 'reactions'; @override VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { @@ -3648,90 +3315,55 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { final String? _alias; $UsersTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedTextColumn id = _constructId(); - GeneratedTextColumn _constructId() { - return GeneratedTextColumn( - 'id', - $tableName, - false, - ); - } - + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _roleMeta = const VerificationMeta('role'); - @override - late final GeneratedTextColumn role = _constructRole(); - GeneratedTextColumn _constructRole() { - return GeneratedTextColumn( - 'role', - $tableName, - true, - ); - } - + late final GeneratedColumn role = GeneratedColumn( + 'role', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - @override - late final GeneratedDateTimeColumn createdAt = _constructCreatedAt(); - GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn('created_at', $tableName, false, - defaultValue: currentDateAndTime); - } - + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: currentDateAndTime); final VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); - @override - late final GeneratedDateTimeColumn updatedAt = _constructUpdatedAt(); - GeneratedDateTimeColumn _constructUpdatedAt() { - return GeneratedDateTimeColumn('updated_at', $tableName, false, - defaultValue: currentDateAndTime); - } - + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: currentDateAndTime); final VerificationMeta _lastActiveMeta = const VerificationMeta('lastActive'); - @override - late final GeneratedDateTimeColumn lastActive = _constructLastActive(); - GeneratedDateTimeColumn _constructLastActive() { - return GeneratedDateTimeColumn( - 'last_active', - $tableName, - true, - ); - } - + late final GeneratedColumn lastActive = GeneratedColumn( + 'last_active', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _onlineMeta = const VerificationMeta('online'); - @override - late final GeneratedBoolColumn online = _constructOnline(); - GeneratedBoolColumn _constructOnline() { - return GeneratedBoolColumn('online', $tableName, false, - defaultValue: const Constant(false)); - } - + late final GeneratedColumn online = GeneratedColumn( + 'online', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (online IN (0, 1))', + defaultValue: const Constant(false)); final VerificationMeta _bannedMeta = const VerificationMeta('banned'); - @override - late final GeneratedBoolColumn banned = _constructBanned(); - GeneratedBoolColumn _constructBanned() { - return GeneratedBoolColumn('banned', $tableName, false, - defaultValue: const Constant(false)); - } - + late final GeneratedColumn banned = GeneratedColumn( + 'banned', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (banned IN (0, 1))', + defaultValue: const Constant(false)); final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); - @override - late final GeneratedTextColumn extraData = _constructExtraData(); - GeneratedTextColumn _constructExtraData() { - return GeneratedTextColumn( - 'extra_data', - $tableName, - false, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + extraData = GeneratedColumn('extra_data', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true) + .withConverter>($UsersTable.$converter0); @override List get $columns => [id, role, createdAt, updatedAt, lastActive, online, banned, extraData]; @override - $UsersTable get asDslTable => this; + String get aliasedName => _alias ?? 'users'; @override - String get $tableName => _alias ?? 'users'; - @override - final String actualTableName = 'users'; + String get actualTableName => 'users'; @override VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { @@ -4165,111 +3797,71 @@ class $MembersTable extends Members final String? _alias; $MembersTable(this._db, [this._alias]); final VerificationMeta _userIdMeta = const VerificationMeta('userId'); - @override - late final GeneratedTextColumn userId = _constructUserId(); - GeneratedTextColumn _constructUserId() { - return GeneratedTextColumn( - 'user_id', - $tableName, - false, - ); - } - + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); - @override - late final GeneratedTextColumn channelCid = _constructChannelCid(); - GeneratedTextColumn _constructChannelCid() { - return GeneratedTextColumn('channel_cid', $tableName, false, - $customConstraints: 'REFERENCES channels(cid) ON DELETE CASCADE'); - } - + late final GeneratedColumn channelCid = GeneratedColumn( + 'channel_cid', aliasedName, false, + typeName: 'TEXT', + requiredDuringInsert: true, + $customConstraints: 'REFERENCES channels(cid) ON DELETE CASCADE'); final VerificationMeta _roleMeta = const VerificationMeta('role'); - @override - late final GeneratedTextColumn role = _constructRole(); - GeneratedTextColumn _constructRole() { - return GeneratedTextColumn( - 'role', - $tableName, - true, - ); - } - + late final GeneratedColumn role = GeneratedColumn( + 'role', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _inviteAcceptedAtMeta = const VerificationMeta('inviteAcceptedAt'); - @override - late final GeneratedDateTimeColumn inviteAcceptedAt = - _constructInviteAcceptedAt(); - GeneratedDateTimeColumn _constructInviteAcceptedAt() { - return GeneratedDateTimeColumn( - 'invite_accepted_at', - $tableName, - true, - ); - } - + late final GeneratedColumn inviteAcceptedAt = + GeneratedColumn('invite_accepted_at', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _inviteRejectedAtMeta = const VerificationMeta('inviteRejectedAt'); - @override - late final GeneratedDateTimeColumn inviteRejectedAt = - _constructInviteRejectedAt(); - GeneratedDateTimeColumn _constructInviteRejectedAt() { - return GeneratedDateTimeColumn( - 'invite_rejected_at', - $tableName, - true, - ); - } - + late final GeneratedColumn inviteRejectedAt = + GeneratedColumn('invite_rejected_at', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _invitedMeta = const VerificationMeta('invited'); - @override - late final GeneratedBoolColumn invited = _constructInvited(); - GeneratedBoolColumn _constructInvited() { - return GeneratedBoolColumn('invited', $tableName, false, - defaultValue: const Constant(false)); - } - + late final GeneratedColumn invited = GeneratedColumn( + 'invited', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (invited IN (0, 1))', + defaultValue: const Constant(false)); final VerificationMeta _bannedMeta = const VerificationMeta('banned'); - @override - late final GeneratedBoolColumn banned = _constructBanned(); - GeneratedBoolColumn _constructBanned() { - return GeneratedBoolColumn('banned', $tableName, false, - defaultValue: const Constant(false)); - } - + late final GeneratedColumn banned = GeneratedColumn( + 'banned', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (banned IN (0, 1))', + defaultValue: const Constant(false)); final VerificationMeta _shadowBannedMeta = const VerificationMeta('shadowBanned'); - @override - late final GeneratedBoolColumn shadowBanned = _constructShadowBanned(); - GeneratedBoolColumn _constructShadowBanned() { - return GeneratedBoolColumn('shadow_banned', $tableName, false, - defaultValue: const Constant(false)); - } - + late final GeneratedColumn shadowBanned = GeneratedColumn( + 'shadow_banned', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (shadow_banned IN (0, 1))', + defaultValue: const Constant(false)); final VerificationMeta _isModeratorMeta = const VerificationMeta('isModerator'); - @override - late final GeneratedBoolColumn isModerator = _constructIsModerator(); - GeneratedBoolColumn _constructIsModerator() { - return GeneratedBoolColumn('is_moderator', $tableName, false, - defaultValue: const Constant(false)); - } - + late final GeneratedColumn isModerator = GeneratedColumn( + 'is_moderator', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'CHECK (is_moderator IN (0, 1))', + defaultValue: const Constant(false)); final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - @override - late final GeneratedDateTimeColumn createdAt = _constructCreatedAt(); - GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn('created_at', $tableName, false, - defaultValue: currentDateAndTime); - } - + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: currentDateAndTime); final VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); - @override - late final GeneratedDateTimeColumn updatedAt = _constructUpdatedAt(); - GeneratedDateTimeColumn _constructUpdatedAt() { - return GeneratedDateTimeColumn('updated_at', $tableName, false, - defaultValue: currentDateAndTime); - } - + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: currentDateAndTime); @override List get $columns => [ userId, @@ -4285,11 +3877,9 @@ class $MembersTable extends Members updatedAt ]; @override - $MembersTable get asDslTable => this; + String get aliasedName => _alias ?? 'members'; @override - String get $tableName => _alias ?? 'members'; - @override - final String actualTableName = 'members'; + String get actualTableName => 'members'; @override VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { @@ -4550,53 +4140,33 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { final String? _alias; $ReadsTable(this._db, [this._alias]); final VerificationMeta _lastReadMeta = const VerificationMeta('lastRead'); - @override - late final GeneratedDateTimeColumn lastRead = _constructLastRead(); - GeneratedDateTimeColumn _constructLastRead() { - return GeneratedDateTimeColumn( - 'last_read', - $tableName, - false, - ); - } - + late final GeneratedColumn lastRead = GeneratedColumn( + 'last_read', aliasedName, false, + typeName: 'INTEGER', requiredDuringInsert: true); final VerificationMeta _userIdMeta = const VerificationMeta('userId'); - @override - late final GeneratedTextColumn userId = _constructUserId(); - GeneratedTextColumn _constructUserId() { - return GeneratedTextColumn( - 'user_id', - $tableName, - false, - ); - } - + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); - @override - late final GeneratedTextColumn channelCid = _constructChannelCid(); - GeneratedTextColumn _constructChannelCid() { - return GeneratedTextColumn('channel_cid', $tableName, false, - $customConstraints: 'REFERENCES channels(cid) ON DELETE CASCADE'); - } - + late final GeneratedColumn channelCid = GeneratedColumn( + 'channel_cid', aliasedName, false, + typeName: 'TEXT', + requiredDuringInsert: true, + $customConstraints: 'REFERENCES channels(cid) ON DELETE CASCADE'); final VerificationMeta _unreadMessagesMeta = const VerificationMeta('unreadMessages'); - @override - late final GeneratedIntColumn unreadMessages = _constructUnreadMessages(); - GeneratedIntColumn _constructUnreadMessages() { - return GeneratedIntColumn('unread_messages', $tableName, false, - defaultValue: const Constant(0)); - } - + late final GeneratedColumn unreadMessages = GeneratedColumn( + 'unread_messages', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultValue: const Constant(0)); @override List get $columns => [lastRead, userId, channelCid, unreadMessages]; @override - $ReadsTable get asDslTable => this; + String get aliasedName => _alias ?? 'reads'; @override - String get $tableName => _alias ?? 'reads'; - @override - final String actualTableName = 'reads'; + String get actualTableName => 'reads'; @override VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { @@ -4771,35 +4341,19 @@ class $ChannelQueriesTable extends ChannelQueries final String? _alias; $ChannelQueriesTable(this._db, [this._alias]); final VerificationMeta _queryHashMeta = const VerificationMeta('queryHash'); - @override - late final GeneratedTextColumn queryHash = _constructQueryHash(); - GeneratedTextColumn _constructQueryHash() { - return GeneratedTextColumn( - 'query_hash', - $tableName, - false, - ); - } - + late final GeneratedColumn queryHash = GeneratedColumn( + 'query_hash', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); - @override - late final GeneratedTextColumn channelCid = _constructChannelCid(); - GeneratedTextColumn _constructChannelCid() { - return GeneratedTextColumn( - 'channel_cid', - $tableName, - false, - ); - } - + late final GeneratedColumn channelCid = GeneratedColumn( + 'channel_cid', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); @override List get $columns => [queryHash, channelCid]; @override - $ChannelQueriesTable get asDslTable => this; + String get aliasedName => _alias ?? 'channel_queries'; @override - String get $tableName => _alias ?? 'channel_queries'; - @override - final String actualTableName = 'channel_queries'; + String get actualTableName => 'channel_queries'; @override VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { @@ -5110,85 +4664,38 @@ class $ConnectionEventsTable extends ConnectionEvents final String? _alias; $ConnectionEventsTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedIntColumn id = _constructId(); - GeneratedIntColumn _constructId() { - return GeneratedIntColumn( - 'id', - $tableName, - false, - ); - } - + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _typeMeta = const VerificationMeta('type'); - @override - late final GeneratedTextColumn type = _constructType(); - GeneratedTextColumn _constructType() { - return GeneratedTextColumn( - 'type', - $tableName, - false, - ); - } - + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _ownUserMeta = const VerificationMeta('ownUser'); - @override - late final GeneratedTextColumn ownUser = _constructOwnUser(); - GeneratedTextColumn _constructOwnUser() { - return GeneratedTextColumn( - 'own_user', - $tableName, - true, - ); - } - + late final GeneratedColumnWithTypeConverter, String?> + ownUser = GeneratedColumn('own_user', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false) + .withConverter>( + $ConnectionEventsTable.$converter0); final VerificationMeta _totalUnreadCountMeta = const VerificationMeta('totalUnreadCount'); - @override - late final GeneratedIntColumn totalUnreadCount = _constructTotalUnreadCount(); - GeneratedIntColumn _constructTotalUnreadCount() { - return GeneratedIntColumn( - 'total_unread_count', - $tableName, - true, - ); - } - + late final GeneratedColumn totalUnreadCount = GeneratedColumn( + 'total_unread_count', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _unreadChannelsMeta = const VerificationMeta('unreadChannels'); - @override - late final GeneratedIntColumn unreadChannels = _constructUnreadChannels(); - GeneratedIntColumn _constructUnreadChannels() { - return GeneratedIntColumn( - 'unread_channels', - $tableName, - true, - ); - } - + late final GeneratedColumn unreadChannels = GeneratedColumn( + 'unread_channels', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _lastEventAtMeta = const VerificationMeta('lastEventAt'); - @override - late final GeneratedDateTimeColumn lastEventAt = _constructLastEventAt(); - GeneratedDateTimeColumn _constructLastEventAt() { - return GeneratedDateTimeColumn( - 'last_event_at', - $tableName, - true, - ); - } - + late final GeneratedColumn lastEventAt = + GeneratedColumn('last_event_at', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); final VerificationMeta _lastSyncAtMeta = const VerificationMeta('lastSyncAt'); - @override - late final GeneratedDateTimeColumn lastSyncAt = _constructLastSyncAt(); - GeneratedDateTimeColumn _constructLastSyncAt() { - return GeneratedDateTimeColumn( - 'last_sync_at', - $tableName, - true, - ); - } - + late final GeneratedColumn lastSyncAt = GeneratedColumn( + 'last_sync_at', aliasedName, true, + typeName: 'INTEGER', requiredDuringInsert: false); @override List get $columns => [ id, @@ -5200,11 +4707,9 @@ class $ConnectionEventsTable extends ConnectionEvents lastSyncAt ]; @override - $ConnectionEventsTable get asDslTable => this; + String get aliasedName => _alias ?? 'connection_events'; @override - String get $tableName => _alias ?? 'connection_events'; - @override - final String actualTableName = 'connection_events'; + String get actualTableName => 'connection_events'; @override VerificationContext validateIntegrity( Insertable instance, diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index e5fb8fe6..1aaa2c0d 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: sdk: flutter logging: ^1.0.1 meta: ^1.3.0 - moor: ^4.2.1 + moor: ^4.4.0 mutex: ^3.0.0 path: ^1.8.0 path_provider: ^2.0.1 From 9687d25c9dc7af5e3bfc7c41252cc68d532f7bac Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 7 Jul 2021 14:23:24 +0530 Subject: [PATCH 518/599] feat: Added new docs --- docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx | 4 ++++ docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx | 3 ++- docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx index 6f15aa2e..710cf3b5 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx @@ -13,3 +13,7 @@ The UI SDK (`stream_chat_flutter`) contains official Flutter components for Stre While the Stream Chat service provides the backend for messaging and the LLC provides an easy way to use it in your Flutter apps, we wanted to make sure that adding Chat functionality to your app was as quick as possible. +The UI package is built on top of the low-level client and the core package and allows you to build a +full fledged app with either the inbuilt components, modify existing components, or easily add widgets +of your own to match your app's style better. + diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx index 4ad387d5..6d93c215 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx @@ -20,6 +20,7 @@ OR Add this line in the dependencies section of your pubspec.yaml after substituting latest version: +``` dependencies: stream_chat_flutter_core: ^latest_version - +``` diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx index e295ba8f..f39f243e 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx @@ -20,6 +20,8 @@ OR Add this line in the dependencies section of your pubspec.yaml after substituting latest version: +``` dependencies: stream_chat_flutter_core: ^latest_version +``` From d1d33cbd6759b7807c9377bcc53e386c3938c814 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 7 Jul 2021 16:06:10 +0530 Subject: [PATCH 519/599] feat: Added new docs and images --- .../docs/Flutter/assets/channel_header.png | Bin 0 -> 31547 bytes .../assets/channel_header_custom_title.png | Bin 0 -> 35151 bytes .../Flutter/assets/channel_list_header.png | Bin 0 -> 27067 bytes .../channel_list_header_custom_subtitle.png | Bin 0 -> 36495 bytes .../docs/Flutter/assets/channel_list_view.png | Bin 0 -> 1140947 bytes .../docs/Flutter/assets/message_list_view.png | Bin 0 -> 1503899 bytes .../Flutter/assets/message_list_view_pin.png | Bin 0 -> 470429 bytes .../assets/message_list_view_threads.png | Bin 0 -> 25365 bytes .../stream_chat_flutter/channel_header.mdx | 4 ++++ .../channel_list_header.mdx | 4 ++++ .../stream_chat_flutter/channel_list_view.mdx | 2 ++ .../stream_chat_flutter/message_list_view.mdx | 10 ++++++++-- 12 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 docusaurus/docs/Flutter/assets/channel_header.png create mode 100644 docusaurus/docs/Flutter/assets/channel_header_custom_title.png create mode 100644 docusaurus/docs/Flutter/assets/channel_list_header.png create mode 100644 docusaurus/docs/Flutter/assets/channel_list_header_custom_subtitle.png create mode 100644 docusaurus/docs/Flutter/assets/channel_list_view.png create mode 100644 docusaurus/docs/Flutter/assets/message_list_view.png create mode 100644 docusaurus/docs/Flutter/assets/message_list_view_pin.png create mode 100644 docusaurus/docs/Flutter/assets/message_list_view_threads.png diff --git a/docusaurus/docs/Flutter/assets/channel_header.png b/docusaurus/docs/Flutter/assets/channel_header.png new file mode 100644 index 0000000000000000000000000000000000000000..98f41c614bd358028cd690063601649d4fd40733 GIT binary patch literal 31547 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%0Dqma8JZt?XJfSg`LB^=%I?il(+M) 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 N=d7Mqp{Dhne*@bQ-(dg% literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/assets/channel_list_view.png b/docusaurus/docs/Flutter/assets/channel_list_view.png new file mode 100644 index 0000000000000000000000000000000000000000..a4390c2eacc30331f50a4c25cc26df040c49f891 GIT binary patch literal 1140947 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

Epz7av z%mrD-yCdGi+An21LD(s$LnmXyofLO5S7o~E?X^s|gs9+Q6I0OU)|v* zT*a&G9U6=6D%kT$72T_U94zwkKHMn!&ZMW?chm8<)!`2BrWX^@?vY(fN>L9hBb~tf zRo#B^9o_P_mh^&RF&0k?>|3w0{|)x}F5^CXCBs_1JbXE}TkcG|iaO&XBlBbI_b&Ki z&1$-Dtrp0y+*hPh%hq=x;1u`hO=!qRR%VE4?Bbyzg5~7Ie4(9yo6R6iW_V;A_c_$g zleXq#SX47$4N&87zsN(RYEsEVsrziNTR<@ru#`+2pInx=Id#S;GF-aPL5UcbaNe{@ zUZWYh9^zTzWLLXS?q;=_liB%#L;6PDYCK>9;w8@n{j*O0o;*I-8NBbiNUY#*q8|liPB6}=QiB;Pu9k#?$>d*UJ ziloY&8Aj;u%Wd8&on+U;?zlR^EFiHf__D|=rW9W1UP(M*e0B@GOYfInJ?I<(RO%-w zLB2r*Lk42;n8oTkX_PfZiOHF?IOq*#p!ebsW(6lO$STcNtn}P#rG>tii9B@Z2Xv8K~s^?$LjblOo6@HE9fL}{y4;g zYC;~+;FPPcU@0WM5_|~Vy-f6TbeF^(wdPE*>}3fx*bQ736 ls_7_hociroV&67j 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

I%HE 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ð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 z

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⁢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

KHM zS!@P$aclhkTJ-$rYy`UnBr8)e6c~r}=_d#%;PAXtEUe_q?+nA}La}Y6V+tK>b)C_J zwNYTH;P%o5)(^z;oBGc7LHX+x{>0p7VF(;Bp`Nt{ZkY+euXW*L;19T>?&t+Kd6=x2 z672HD9&7TS2V*2YR-oaDpT_Pt-o_5<4&C%;MS{B&U7AtB#e<{$K0c_FlKEUGJSyNX zlNRH6WW6_Cim)L`3;jIS+g!}9`=ZLJ1frcqIW!#4J=FM>8kr_z02iy0K9cc`6ihb; z@|7jns3wpc_u&7a2Z7$mcpwXt)}L;Toh~gKiarx@yT9J(kBprq<)^?~GRU=csk< zXvCnCig2Y+w!?pbR*bwW`bxW(&#Odnu;Xp@QZ8B;pG8F8VjiFHR1fw|#FFi5n&sL` z$5k1wbpX9K1;6HNw4Mkg;o4WL1^+dL**VJRcIs5%#CYkgeAidbO~UwC-WRHe~CK3W2sTr zCxu>#id6`$+u%2Qb!#6hI$V4y3j=e3rH_*2z-B;5=A)_&z*LC-BSqICoUUs;@eQZ* za@?;7k6A3ou-o1h2CPZ$VsKo~q3;Y*PEvq|IGLJyaXS)1>|+ZYYGM>2655knu|RrG zM2f=ITMR#}UmWJTo4!#Y)-}@>?@`L{C{`3^t_s^n`6_N==z^HBRy01`JSswQCes3Y z>?!=T@&kAj#NI37A&br8HY0t3L*Q;}%8X3Qf;B+tRMf{`E_L5QGy?>5Ec{@B;sWSR zQBTMMf10l~N)?KXj99@y$JkS18nyR%o0V=|MB9Te-==K`eizo z<9WAChd%$MM}-1^or))xeKXcf%xtky1{v{(a!l#^JR!d11UxDgS*o>M^yaVCS8=UQ zJ+|)pw2>1`$635?H>No{I_Wv{9*jcL*{iz~zP2m#h%J=>cDAe!q|u-8E^&s)7B$Am zq4Vo0GHJsVKiu{7TY@z7y)WINq!ZbLKOLI2G#09)dimUj80Egz`vwk_Ok?Q8+@sLl zl8u|FR+jxFS9dRUo**S|u>E(_`A-*r_^{C1Od}I>hV{4&8+6Wr?m)8+um8q{``x^4 zw^Zi^L1XpVKE&{cjXBp%zx;TECB|D5`TmZ}0Dm(%>mBD(>jDB+35!ZVoxn@YR+?_r zR7joew(wh^^%8q8i#>^cGzL*DQ5Lq~KaMM=+Q-@yby*A&P^`+0%%?@ThO}u&&LwT} zHz`{5G#gzTj-vr1tv+Jmpd^ScFXkaoH~W^Hu1J<=5BE*6=>!(w+eai#A}meZ&QsB% ziJ_T&NY0WXnA6B=#bUqA(p&}O-NG0gp!%A)Qau@AkgVOT8alv)8WNe%$T&}&9pr-0 zo5{%JsHPb}q}^iebo7M@iT|vV-`U-lA?Fmk4wEE`1OdJ8iXz1Vwp6GvJnsy>4|nAd zeRx7sEGCeDq@5yCBlzEC!1_WFk?1QJX*3aZyr^V-r556X1|xV)^wj3fyoXhQ_H$J= z)OgVfDuqw8hb}U(Sj%pC^;}=`CJ=XWN-p)tJM8^9A!X$}`DGd5L7tw$3PWp(@+tL7 z5c7RSVO@obppq|=P}+X9x{{j!2;iM#pSEHaB=gxE%2A6RxD-E{9=P5pY~8q|U{hLd zI7qv#b~dG@5aY_RBhn1NevAW>HsWf}f>$zZ|Jc!O8EIzTrPF4PWTPKPASPB$a zpVlfhi1DpTnb&UMOjFg1i>C!p2jpgP6#0AtS=T&I19L9ZGAQ`Y9+U0TU*0s=Z)N%L z%`Q}IyJ6RvK+`iky((U~werdtTe#cX88yo98*}KSaoG)8xwHcbUwl zR1@E~v?6>uVb_mcvdN7746*!ERqXR5*tqIYvn}d#HjKH^aqlqyCT=}^Yheg^QPWuN z@vA+0o4G<# zi@hP|^Cw#8M(25!jeL9uoaz$@G7K$|hDLkjL-~7mI96{^NPE>#z zD8GgcILa5Lhz?B7R5EbM{vI<|1t62qjoRGac~I!Xw1I)>L$?4`+Ga27>58eQ@qtMK z@JR`W{GeI zc&Sq1{;H;q&VrVPhC3fV7n`GFK8kHXCS%Vp62(>LLOhnsMPq*XFP;2=cH8+ zhc*6q)A|i7e#RfNkVvvNl@Osxn*~~*qeZbi+~Z(@Ikv2kNu&Lbyx99_s+)v@qCiI3 zac^jH{))zFa_M3F8#uc<0$eqDotA)ptmq*xmy-Jy6RN6{E0n>ZB%5$mnd*nl$D*Q` z3gBrAxIURJag$KU>Iqy~t2S27^Afbs=E&r+UW3i9cha_IoE*U*DV270wR1u`kHTz` z*e29iX|(OuW=5?Jb}0|M=Jf~ZJVHda*QIqURPrAJ&juP14#`1pZKqxR+^7K`6?hwv zJ-Mdk2PCv->*hsKd5fOHD82eZO+vGqr#G3PE3?)iR_fO?u(ntE^3bnNlRZ#mh0ss$@}4W)()xR`fCK^Q z^y&T;pDD?zi!R69{zSC1TeV1xt14gdFh_giigFy)<@xZ8&OYF(*H1^QN&K_C>M$O> zL~k+29gRsr-BEXT-DfOsdjS{lVyazz!HUXb@;E_rJUnlY`HnJg89=#>o(1NSD~nEp+bI& zm|+Hxxdl-kHV{az(lbFz{_01-T<2_wi{cIb`!ILw(WVyA+=cah?d1L&LJO?_Ym4}w zh3P4{D%1g_{**6jQYgB`aQ`GG;?_xo&lU5#^^u@eX32|b3E-ohb{Z~vF`A_PZXDm& z=;A7|v$A-QhT42EV4>bTwcT;H!S8$_Eq0Yh&8Npg=OM z`Na=!7emn|_g{%I+`QQDsMg;D$zmh*q)s5vAF`QxxiHBvxH60WGltJv6J8MHI;9{L zS0X&NqN5-WOf1sOr;~sCQt({`BnvwbjUW~yk2hhP8qRFflq5(bXfCgi3S_iS?XY~t zC(^?GYl-=L3R(3<0F8N)|9!VGBwM5VBF1JP`OL?&32^cN+>n7DBfQF$pBGEKo&XGa zC84slC}kG&D5*BS<;fgd;-yJXp)8Y?E(Uk{P!q?co~QU1ui2AdGINzKmj zLT5RzpcfK*FT-__m|=H*91cz=IMS_IfG`2Lun@cb^-3+vj)&997BPd_>L;1eQRadl zX%qLBTH8hQiPq-VWFeq6?6S}AuW^#OEK`5_K$kt1w5Fm~1=z+Uzi<6kfF8b3NL!Hk z!OjK&{x=c+-?fP|7Tuav2_k%n&Ei7jS87eir*2jL7@FSLPKZw+@tNO1{R=Y5#&A*C zY}^)DyNFAqnNlUVr`|ebcls>+Glr*p9=X#1aS@*BSF0ZhiU&a4{m9{5n_Zve=TzGu zjYysKXbJLflE0b)ozkXV#L;&4&7RTcC$>a}` zlFlsm=&QXJFxRz4iqz3Rxe;YBw4Q%qmU~0VFu=6S7|rqv8#R3^&WwNP*KG4ZO3DH* z_}t=F@LGZYo;%Pb=jnCBn3XK)X-X1lll<2&{>%Wb6)#Y=+F#uKkNh2HKhNGP5<>by z-T)G@DHXieE?Rh(y*Y943Ztw|9>p0tAxy?R3gg-9m~= z@_*o!)xee!2T$v~LW_lVUnK47@>m7D$yzQwV|});mzwJtj65&j>NC$$uKdl{PBHEA zi3-YY80<i{<>o zIBH+zvPX=TuUeCI)WLpl>3FJ)id+zyqprMeegy@+b8w0t3++l({r-P69INqov6JqS+@K zt!!+0CPFIFjIsl1)?GIH35er{M;oCo*%c#&7cKftJT+D#m2a~lkS+lL zS$nFR2ciPZzLs{E2#_ORxGr0}$E~;<8bWmcXk#K)bYJ$Y5@Raknv)~K7XU62+k)+J z^91wgl#62du3k(9W_X#Zf95<~_`#!8P>>YlksnT;F1a^xdaJtZy6FNlG)&cQ<*i_s z-eR5S5Aw&m9=qih{LOpv7=4;={9&+(wAm}x2($@%k4dQVdUrNeT_Gqyla zHFv&FzX10VuksOtkc~D{X+y5H-3h6T*l$qyd0Wddm8Z#g_7n4)qLN9ctFucyd)YJV zkrel%Q4|G&rHNQ#?33DO{}1)-4YtC$F>)Tl1*&Xgo>NSCvz^9p4;Bt(KWxTc@ojhW zf9KR>`XHa~mRrwo_2f+1Q7k`iFROTE;j#3TWV&AMYm<<9SjB=-5;U?CIU$~DZF3*l zc!q0Qv|fH2no4(vCyGF7Oe;JdTh_(mtB!Ph6|WDA)(QMQH{;ZdLtL0mOWod~->1E? zK|AWx_&h~b@xo^KxhIs|h9RNTyz4ecr=ShQIDsKlSYf69%tcbzXms{@XtaG<@`9>id=dE<{JQ6v@B;z#(wv@c{ zdy7wy8ds^f$CEIeR4BweHE#2VUrBRF$k!wb_z)6R5GoS|;RZMHSGW(RsVx=VAmUba zmiU?C=v;A)3jJHVmelf$oP_9#6prf-fu6;c1tcWMnXvfE2gbXe=t< z-ns$f3#J9wWMS!Ou(J@i?E|m826qbWruBKcSP0CD%{9zPvnLk~X(HFU7701~Qeuf% zXe{~8(7k=SkzNouC!^`vw^ddWGL%-kH!9xUiam^Zu8GzW+~fbJ4xi5JxMiW@SBy}n zDrXgA>uJEga7e@2QWYv4~ zuq`V}Fsc6Ag?j3@Yy2=_p>Czm_~WJVg$Fn?d^; zDfg73ZkFQ4`PC;U1yAQtf>KE|G=e)~gb&B9U>++-UCyLNVeh6M($}ZKN0>`I#g&R_{hpfL4xNj0 zLrxx?vkDv&AN_=yUEiD`Wan9YY6GkcA$*_G&Ys7C!C=3=11Lc@jjI(G;}|hlgXv4v zS25o&1~63nXCk>tB|$I9RdRcPDv2Kw_&s`}(jU*vgU%`w(Kr)=63<^$A`MC47QJD} z6pSI)plJx6&-&~{W)KWzl5hxZ6ID2X1gjT};dn-QS}wN}rMI%Y+^LSYS?<<3UI=j# zy7`Po8*16zbP6R)xs=C}O}rYCB-$b(R8EIDRGi1iCDTN7aV*iWT8!G<$hmN2(pt^1 zz|#h`6;gMa85^1LMi{{p!`jR*SHH4~a1MY4i$;>*F92)8*VmxyGK%U}STkjldd71_ zrffDhWtK4;MLE~gry(7Pq(K`Db+v&aLIxUhr7is{PP%D*uR{9H(~>!lmeH3>n3)L! z{7ia;bv8kMIDI^mgWpR>sDv>+ALCB_bVL_sXOve%SPm)LReZS%PUA4l*596Np#s1!c;1xwllFBfsw>BMTCV+Zm{1iNA3KY++&M*O~kj zecVw&x`KO>h$IWX<^6YbI@{1e3*b0jY@}H$rFt3ld}v9v?vo0VRBdPCIj6;w$C94& zJ2E;kV25V8tBAb^N`zGAZDKD~*zRFa`n&^hb@7}%N&U6TG|F&E5+Ey0VG#SunSsWD zYr!>k8)}uhPQ{K)Q?S)ZaQENn2|O&NdbEX|oFoV?WtZi>cP+?6nm^g*u_%+JbQvR) z0G7Kw){d0lcAs7{5PUQ6R9qdD!>B zJN?bKqxM{W2wRMIg}y0pn>$aLJLwz9P15{KaY1Q*%5zZW0DAvUcm(!Du~7RcXVim# zT;zB3>2!+*S=PtNlGe!68-{!`mw<)*o3U0-ei|NpaXOB$pkBJJ>ivC`?%i5_Q5)FV zmwzl%Rra>d5w{V)ZGxtplPRa|5wAzRljtvz*-`QX_HWGrD%PUN=&x9AYAyG%g` z`F6!s-jDU#ClCVm=f8YjZhkCQdECd^uMYWmT+n>n7r8^IhDh>>3^+W$ITK&5HA=6! zEL!)vuLEs7p;ccme(Nz+f$7Bj0@bw!iLwsA^3`-PZ06-i2`e?0bIXMov%RfJ4GVw% zL%yzFs>`u)8!{k#b?}Nq(ff3p^swq9u$ZDOV}9J`z8JR_{mFJrwsRNlSI|MDhlVJVh6{tPK&OaZoJXRdretE0O%E}55x|9G!19VR-!$9!8iRjbg z@6y?JkvWJ%m0)Ca!MFF_JCVTx>)}yp-M4$WQ0+&7h;6Igv2-pjVXlM>dgg z8mhYfuQGJ^dkukXdBlII{GFdw?5nNaYS*?nuc^1~C^fY#3WUV#jay^CZ2ZC}zQ~p2 zp9)@8EEpfpSET3pkiDBdI&L{)NssoyeZM2yzfTXx^4)6(uQ1e=*PQ4E#?y9sO?5^v zC%qq?_fNr<>wmAcg70XbR~cL=QrsWc2zh4Iz=QlYYTyMm)OOE}^CSc;Z3!HIJozqE zk{i283P=c@(pfZ9J#1neB4rWkNWXI9^a5n4FUDw69;XRm!qq>KVuvXT?3j`0+v&rE zIPnQ2g@%vrToo+4Z$^>ohX6Vs(gjwNRzr)A6mQ;(0%*IYOwMC;^2}*UB9IT%>vH#_ z^cq6eBnoEMbumbw@`WO5^H(wygi+$@vhEV>Wl#E8HH0V7@Q>qcfrLTpCBp=dSAN`i zaGMum^mecymX5P^!r*G?h)(AhKtdrk>ghc-ed3)(=hp3%z|48vXqZVbuL;>SKWac` zfaRZ3*We60JlQK9O~ScFBsLs@^t-7k+-O}}Zg4Nn<4Qbu0@614ntdc?iZ=_A zmCxS~E%Q<@BGVrmXDbe)q6|sH5>m$-kB`JdQ7NcRJN0FPJ73hdDHcnJUCiK7G%*Ud z7^_>7!;w~g-`2Hd8v?93Bc;#^w5P}o-5xe;X zFvJygBk5u1b_T^Tlq7atFK>+xydqKGv)9HE2u&X4HImtEDK|!NbK8#2NFDPdbP`#tE7_| z$;5%@BN5GJYu)-6--Sh*tn6dTxrOi#<&N=qR3KEy}2Z12V7lvvW`UOk*kShaq%3WJ-xa#!C5*U2Do^Us5=6{?Ce(LtPEl1;DDywM5_~Dk`dwLBmeDaX`({$mc0ksILxwkI9?1qe?;^9&A?g88p{ zslI$aM2q>iIB$nO^x!ANE&P1C+@ggYi8;!OI{Ga9SbrNRae@q9B7JOxetAiRpqwMw zd-D7P`A6xmD+EV|HEBh1vVx++ubq}qgE9!Ag}-8nhkxX~B)_@oy?C>hNc_hw4ezQO zxhg<1ts-M*gYzWJFfMo7^H|A0IAJs3c>FxW`kAC%Z%8V{w2eX|ff@NGft6aMT}R*- z{Q!RH){Y?6Uz*pA4{Dju?sj!^m+mI?K^2F2!MzgAf>dNbCW0l3=*ev{MaFPoJzKL& z&AzciqopxI`h@=DXXlz;fZU&^Pp#el*Wki7i&}sm`CFV;t!tf1i!TId=>j~T0(~hJ zU|`R2Qdd!wvP~Q-Qc?8d9yfO$5zqX95l$qSp0*dh9w8~V+eE=EBjOXb5{rE?Z4lv_ z)**je5!#bx*0r+hr5mvuRaZXz$c0OW@~H7j8(1z+ZbgFqjmt3&k~EY)gitD(Z1xKB zDBY)5Kc&KGO4_m#8EXwPJY@_FPT&NAbURzQr%yuH8kMvF^q&|Go|a0)3iZ`fMsgvFt8uw9ZX8 zN~6DqV|?`C;~t{G?Rwfww&_q?mmAXM)BR%M6ANPht3aGgdw~u<=P3cU^FLNcHL-)i zJI$pNSq9-5{J4`vDJiLbOL;6L5cOhDygHq?EA)-upp-o;0>j} zVE=WJ$!vEkc%jTBvME52#!!H;WRv?AlT=BEb{&PWY`DhFKd+O$;r^&rGwK}-ch&PNIr$FF-qNn_%0`2Y~5SxtJK98f?Ii7f4?bt zUU__X`1QYkeBdRkIPGZW&xc)?q0OknKQ(-$W+;DyS48jkf2GepRdN1oI$MU?Y#GhdgAYloO$}tdqa_x>8 zE>#lXjRjcPmtr|FJ0BePs|$E2ETHnsu`OA@ZnSCFCPG&SW81e5PY zMbCQzhlST?IvJ#1G!l{yL&?|AA~dK&8`u8vt#xTPjpXAnAxIX8Q;DKC7bH+FCGOgM zff+gF*tV8YP*GLteiGFe)TE^|t;$pJPAz*lzoK-|_OHkG-z=6di~LlPn&a6@9=>}C z)o3OpEfddTmX*`LT4j{oZ2B;0G-R8z|CcyA@FUj+a4CDIR8 z6glZ`X3Iz}OG*Y)!kQFc?;JH?A9Be*++s*HhOH?>eNE$?T$I;{;kC@mzncy}B`f#? zf70WLgI|R3P&Cc_dV%YbP5AA}FsJ0F(-C2{eKnJ?0ojCa9JVSud?=mia^!omkY8yoMF@dLET-P&JM7;{(Y^W`I|k_#dqN9h`@EhBn5q5e*wZ+c+Kyf$Kgo z2P9p01w{yH!e#g7twJiIsY@{-3klHuFn*ShWh@Q+svl1ygRybCMi4Vo_Ll7!$JyUtyNYLJ4-PnFNsim*r-QHv|%Cr2iK;@Hy?{L1@Y zE>{I)iX2RAJ^D1@-v&c3_=NXxhH+?b($1Ifb73k}l{7a1Blq7sE8mi1Q!=O7?(mDk z!)2X^2lC>Kx%M~FDr*SQ=csi*uSA-K?cDI@mw8b0D;YGK)|Y5pl?NTen>-i3EsIsq zRPHm9$>iRT;q&O=QYb6ulpc`uyknbxXPc16kF5;Ybv+oB1cmV*bU*(11Ho%MPCrWB z+U(d`(y0{wiR3zxisTsIL-xz}4Lz%SY7pghiKo$-!vkW=SwnmwOC8tZ%jHUbAApTw zR}ovk230bMmHzBbIhXq0BF21OA8{I#|6bDkHNOAtVNx3RSn+Cd2!8I6kJe`YLC$X$y|-rr zc*8{zeeZzSd%C3)#P#39A^7lrg8X*spu~LhZaj>e3M+gIhVEm2?l2OmCsmV0 z#G7wuXeQ{UKL}&SL*^YVl>+w3Jmz!ZDaplT!3ey@1B*r3xZW3VJ`0iU>cz0^+0F;l@;Y^V`-w)C5GKa z(|}P({^T|HX`V{JZ-U}0k>%WYJdUnPWSc%�#&=SGK`_?86M>L6;q4S?J+jSJ~7X z8{}qKKSZXIiEUg;VxCH`?>C3@FPZ)Bv8YW%I;}&WkuFBLo_hDd^KZlv=4>x=R?b7C zf}SS!MKgU8w+}Ygj(Xncg%Ld8$G!lZn+GC@BVV0*0odahP?kS6zh(j_`cP$x2RA~I z0f+d=AwKIPKfaJRtb6iDq1UvmWw7#bE}?8($smKpgUMC;d2Q#)^4B(_;g>r29Ce3= z1wLF9Gt9ks6^eP>aMmciFq_9hV=s)%-f2O= zK5;L4Ccb=j3dlao=w4CWK z76L~-XmKKMW3>?9Kxs?Nb+QSISILie6r>aJL}}XEw!JVX7i{)w8Izl%5MnX&JZZ$nr~zl%~1jH1q^*ur3n>j z7#6<>Gcyc+%b-U+3-KPp8zcJM2^CLh!93xYrr}K4o9Zyk`BL&oGYpmd^Pw!vpT9Ek zVM`V=bcr_%Db?#N{ca8O0d)^v9qtq_4(g_A02ttx{rSb8G6c!GHs1~l@)$932veKk zj?#D67<9SRU#$pc;8M`NGPC|aT)kyjlwtSwODGtmsDKQOgwoB>IWz()-7Vb%3@tDq z3?MMn(5--^ba$t8I&?_P(6#6J$NTQRkL$zD*EzWFwXU_+xz3-%%%Z5p=SHW^9=pQm zDHax@^xIRdEH?3Pc&`gue0j_1q=()E!sDb<&rY|9o>ZXPWGGRg#_rfG16>oi5w{@7$fgUD zln~tfgx???Lg8_&@a-=+ZMKg8?qA#8#cqbP9@T{ebQGCIyA3rX=^+=yGbb>0v0eXa zwk#A1oG~H09t!HhrElz-WtI7U^`f^uoSYr^)g@HRw|qvIF#-G2wslLyM#W_#o+v?; zi&y)QDZGQyr0g>4Vo;}*w@kP4Zz;PCh`?k)cX0%Z*tESnSs){>(CUkOmq@47w8;Sl z*x!If^yKl0yc?ofJ?9VJg1n+&&e~jX3~@Y&swB^mVA|!g&st5$K)med4ein)CJkk4dRtv?g_t@DvwQb9 zR1whICY+85W=CPNxT(>bM6@DHZp~{u>lodTsS@!}Fm!EQe4mxJmt<$le`%h1PvULS zOIqImO-(L`Cj?TlWOxSamj5wHbAQHV0XynnmwBlo4CjZt%Qud1+jHs=f=&FP&q`m5GrPNkm>SZ`N|h}yh?-Cb z?zJcUg;X}gJ%tVNW;C|^i1G!if~bRNw%26^-Su~{T(H<~r3R2*x=w{Dio~P0aQlqJ z0e5K&|3P)?dLHef%CZ5W#qe&d)`{{wf5Ctv`VpJ1G$LcHW;@n&+SKXw>9uef4QgRg z(Q{)Ff}W4p?L(hJY*9-Wz6Ir!s(c?n;)i(2#!_R7Vp2;)jkl_X z?^SiVs(%#JtZWa$;k~zSz#0+ZR52NrHm;GJ>|dU14pj%1PNjJr%_aT2>uB`oTfL`q z6v-wlv&I;3&GAj~WqHZ_nj+6rF2}TOw|d;V4S%~R=ojbJxc0i}PbL66eRCFkce-_b zO{>qlCnlDc{EX#F6Ji*ftUlo33orSi-y+YP19Yy(kM4g9o`~PQ$aeQ99%VXGg;6ZT zFotoN5qzWoyZDlX5(4lgKPjg1N|Wq2>0l~gv+40$;R;5)d12sZJ3V@nW0#;~=V&N3 zf=@W*NoZj#)?E&2?(te2$(y^{Wcl(tA2@T02e0DU^?$I(6!6$qhCe*@)5IbIn zt`Du!$p5W)N<+UUS#<1%=Q2mu_mY%X?ymXn&^fktYewh)hKX66W-h3{S=B#93I+g1 zpXS&5w#{aW5rHKv1EwX_>)KV6Gp{0kdPLVtEi69Q7PgZTK=HwW(WL11w`(p`N-2(zP)=@r^SrnlG+QLo-lVsOsAU~lid_pUeR|5UP? z&fIyzrERv|O1^tlQS;0cEUpZbN5%W|6cRV)8h)@MrS?>`5HvT1^wcw>Hg~Dd*1=|@ zIrE>!g;2z~x|jW_W`A#4m_>7kOp4u&*wwz*sOb}hTZD5w%6*)R{HnD)oA8F#5z}|D zZvpTS@7LApow{~o72A~WB`V;orHpSVZ1WQ4Irwg?A$i?p5uCdC0JnCL+UmX;zBZN* zYZGSTGb+lWIrxf3Uesd4CQ2N%4ZRaYvzo+oxD|%S8%Dnaz}UxdLayYqggV#@8_FM< z`YtR*pX~(|?N|x0=IRC2d$-!gLxl*5@E>fW-2)AK=QjMw*kz0BHD~tI@mFd#RD627 zLII*~mnVsbo>XD5^h2PBZup{QOFx#4zmt&FP$+rx=3G4PFV}Imh67-ZR-5q)ot@w* zcC|On4q!{$lGemN8Xgi$7X0)xq8_H3L16(U81`X1joNnP9L}b|L{X2gP@l^8cY=Aq zLS(vo7>)T4Ko&EOyjgcw#GCFQEngWE(eS_1`D;&b!(PQvS`t>CV!wM4Hg#$*^oPur z4QnKnOgu-`u=(Yf0N{J|U&(;W2bG6S;y|vEjB+&%PyD&8Fn=?zf7Fwl`+P58M6EKz znu)^z5#852n=QITsSUm)Tv{7L#_}To5eO^C#EX2!(>cVn#9IUJgDEXKwbEC{)ecG~ z3!b*@)WRP8Ws@{~zgq4oOLW4Sw5-=VADTe4qfpIORUmvgE7KZAtq}@b-%Wu_D?u&Cl$ye0 z^P6aW#exUb$>n=?tiBTADV|WpnI)TCm7>=rZ|~5#+jfq1M_mnqml~{?ppe2+8!={b zchMFlJ(4D1-$|(%{u$}9woeucqcXnzTl3ijei_x#9@jx)^3#}Ij0ztf%8lnia6m!8 z@%hqu7i!i((k$%IoXSECBmG}5fb6!<69NlESjt?RPn5R4eWr|@D!cLOCh_lVPmyLN zqQq^4`kIjp(4DKfWsZ+ciJ0%u9`*%?x<~!WuzIjX+DP8a>%w{p1B7$1M9poR+Pc@j z@>C2*TF6tXzVK~z+f>&1Q=^~#U&l0xqg^!g;^mk(;!*ReI(`D_QwyW$k-72LAaTrv zzpU8?`g$`ICq5T}CpV}?wKAJAO3};MoJ`}h5kE(@sS10v-;*OF$4+Yrm`CzpHQu8& z9{5)-c91o6D>3Z5W~ZZmNrE}aiqljkZWi`R^_ygB%IOs-QS?bpn}tZ+?%+$BmDzL; z6XQH@5!z2;cv7P*l2-$B^(l8Ia1MjdRliS{-k(P{?BsqkM;bYRnX^s2caY;3R`_kG zF|i{b7O0w^g#e$sO9XDpT|n`)gszpHH=Z=VjbHk zRyhi9Yl8Jndw1ANI<9pN&@(WAd{{;0%`4pOBKdKw@nGD3kbROqaaB&i!4Qme@R+-= zTMw7Rba@&VZm`vXJx)h#d^+>%;E~v-7LAo{S(d~&5mI%Z_jIWF?D_H+TCsnjC;8gX z_)j6h8ILK7n&|}rY{||kL*+uT&d*Bh)}$r)mr74ZaA3Xwwxvg$b@KCTuIb7Fr~NFR zEma+{kcTI6R#!9fEsNFx05&X$6D_~fx5Ze_JvQ$d?eMHtEUOluwW3TY#Sv+>IZyf- z4M0CPA;GtIgWJ^I2n$H3FnU(yF+>GAW z>bDDv6{_!?HjxbEMe4uGPVFup`1wtSgaIj^xZTL}%%n{3vsaasTt&>J#+uIXzzsqU9;ga_)hprkB5Z?o%|)$0t* zkJ%QVg6=kq_>`%b&W1E&&JgM(kd*+pI{@`y1D54i#ADH%8d#= znl_f19B-m<7ne13Q0&P30Teq{3{Mddd=+rG9fDi-$o5GBvw1j_48dH$mzVFk0;6bs z_jk>OqIzE`(uICH6`L}hVloT!iC+qhVI^X3`K{b#dhA*y!*k8;UW8h=8uQvRJ_6W$ zr{>9rgfS*IJ<2to!#Lcuvc6DqcLez>8KC~){P#()#|C-*g_tJj=PEYecBlDhs6xL^ zJo;{P@qVUM2^A2&sR-AIa?i9Y0$vXB&NaD*Vi=KmLJGMC~%A!!N>ivZ@Rs z+%XZ08?5FNXvoiYMJMervkS3lNq5n1JlC`6WtVOQ*FS;XM z_GT>*F5v;*CFR9T33P+sHp~6V9B9B6=hY)pMY9sItdwouWO=i~C=deCdU1<{6lC0^ zrjh^&xCgLInFgWB?wLzF7c6e4ESgv|s_~n#P@vF;b)Y*3m$$LRVWNiD@k>rhAQ%?U zhC}K8y4QveElrj^O6xdJj0aNtNj?44oH7ByySycN5INAd9Yj(nZa!D4^WcbKjGj#A zLZJhlHt5mz5}_a3YITRhFbZ|GMvYm)0Xl|o%{pio&hRGIRt;;8DvjE(NQWyWXhoB& zoa~)TW>F`;WK}jc`}1Z@b|@ z5x%>7I6GXBv^JI-LA*R9kpFX>o7rY+rn=|n$xh=X;rj8W$@Yqjh1q}C;3EHqW(6TA z*(yp+bCiT@#&5)1&n*ZDFu#zPchv9iXs7}g!X{>ZS#O(#slQ8$yp6+{BG)=ZVdLRG zZS2j@YVCP$w}3_$);(xlV=Q!N^S>n{9teGhngWjTTy;#aMT8*JHfEFTY(Hy_o;Y_0{VAmH9yO zw2jeCDY{pvg{QT6fwar?MU!y%B81?x$g5CAs+l@(CnAT&w9o(3AXIIAEG!+d1L`$9 z;0fmoe-b@u#+@3^{f{2Qn(F^cLYJ1HouDhTuShaAVJyLVTplpiv`S5N(+JxX6TbF2 zsu=$5F`3fKMoV{O;vLwCgo);^%EkZv%DUQz@cPOa9<%3aTcPuu>LQsxI@D#W!ti;E zusFcK^X66Gq@(Y4*+k}hxv|Wr71%1!hR>zCq&MB3K)Jb#BQiC)VbRr-QIGkmp~k50 z*N`WF#qXB8p~AsRp!O-ix0jzN^YthqpN!S>%XbM6h9dX-sn;PD1S)v7 zpy=M&bk}aEO*}xdd~=`!R`?(6)Z4R>urXgHY^}Zcp~v<9{bJPAVz*LD);sm3 zSE2NfrNoSBW z?-je<#md$+)1xSbZ<=VaJf-=e=ul?evj~5sxzgmu;yvz2ces4qKT_lUbl+?p5zsY*Xw{3Ne*dvj+b6Sb`!l6oeVdUnw z?IH`e?B>hs{Ym_lv%;9)xKmGNkXhnU6#YNmc_}vo}5wD*1 z)Hn?x%`6a_eX&{N5(216nM1v5mBzU^at%pXN`p9PD%YoaGB|-RX;hWZV`^6Ncbk=< zx;)M}>G|jDPAZe6d*so>zQ^WZWIP#1Au2^AgE4&dZTj)WQG;+R2aa zN;!(CO`7h`=PW;4OkXMnTNIN)pR9}bwsJ!Hk2Bu~WK9tZrdgR(PhqR4LqaYAoYs)*ZMW{@fX$W%l{T~rro%3`<08Xw#wAqzhp3-y7 zIVm~~ zcDL2cAE}-2A(pI*gT)H=Bu?u*KYS~MkH)y&ZkHe!-^mTgHmNOj|a6v8+RHMy#JKjiimw`?cU9`*mzT!xJH62VP`s&R@PZ%jr zP|T5FP;8dBti{6Uv``;}LGm$>&_Taw=OlJa60dHEKcnPKhM40lBl$ycD4ws=k42m^ z)btIgqjaVbAyoE5U$AmT{mqoVpp~sEapsUpLS{ko(iiFyuU$+^2G|Pj$1sm`sH=VX z52~t5E=${cXRscg-ra?ltNMlmM9(En0|d|d_)rc4!N!MU*C=)K6{CVKVn-^H!_N%?*> zYC5*^8f2bVPMmn`wm0*8FW4@49$Ny7M_l{VX*49gl}sAV?b=|Unt57IKLPbMokQY& z_NVc*9EfzjVHxHZF{?l+trNRO|3+}%yqrMyWnK9;I-|%sx?C58p<9ZphKELc2BZQ9 zeAOC7OoaAhiSkpRsmRO~k4rfXeTjNH@hbN=LBQx3Xh9ucmys&;wa5x*)89RG&gw%8 zIw~2_==Wf`aP44~HA;#rX;K9hGh@_m^-7{|hmX-=+=}mP<-%O}`9m4;$`6p8TFeZR zjUvk9Dslu|KA2DN5`v`i&Ub$IZVLkr{ib#a#1GuDsy1%5CbdJgrmuLznHZjT1Nl)&cwt$HDyB^WH4l30z%IYf=9CL1VcYo7krp7J*&Kp~6CrTTy z`>bb$Kf^IM)?29WLv^$L;rp4_RKwOj;@z6Io(-z~k*9h?(`AWP?$hL+Kf;L$@?8_I*E=%5~GlH{@zt8CY#48)DS3U(SR- zVeR>A63;c@midf;=F#K6<$E=y5kzzXtvu>O!4_|JvU64(8J&$$(#YSeRA-43&evMZ z{8$hy{z4XkxVivK>~fHCiR8PYy;v%q)N2P}kHp^UJS;?vlyZ8p#TJON2!{=i$*Kev zNHVT3;fd9}WC=%J_Oel}rJGW3<-8Cn2Nl^I?1YYzyXD}nNt-;~63E!+$l|@u!R^O= zn{o4mMr2%9(#i4kpDbIS+iu-%>91(0ncd`RC_YAxBW@0!f5flF$e=)+cH1LHYjTLE zO7~yrD{ITw6T;@;{_V_;`F}wuinuojLAh=Ze0#^{MP}XwtMsCdEG-Tol6jG|E5pa7 z>o;2T;qPj`Gi1}bQ>pWifoVd+4v$}0#-$gnK1TFPY_l1waGp*aLG~M~0OeD)XpSam%sIq4C${|~! z8xfQAPOVl7bK1kKuzMA(ED7S{*p;lr`sFf+@g0;UDb*?1JDU1$QcfyEy)oVv6!7Q% zS~u;-!H?3C-s^a6+2eO;xsKyKcs4kVpJlZQD0T|8yV}ghvk9@m8jGEFiioly2MZU0d5tLe_MpI!pRMQm!v>o&XW7_EOO~Hm}f<=7F zns}h)I#h20vp`-6c5#{S$99|%)e<(BdvrOS|L3Pp1jdLYeq!1nFiH7K02X~KcW4zD z;`21S3iQ?Y-`7*sKmBX;f6!@Ua3H4s3cLl__cIZ@DA`#qy1DsZ3GLSbW>p&*3pG7f zWeuhzZ;~H4m;L)3*I7GMRXeNGE~S_1HH8FTJY{En(ASvZsV&S z6^ra+)Z@!-%+v`lRW6_+8pDqjXb^UPU{hlTcyQsXVucuo z>q^_(Zex+C;*31++r?Ulef)Xa`(SUOBHs0AneD-(_bkzV$_rDQEUTnb1SkVS)2}~+ zSs9o6?>CTv3Ijd_D=!wjy7^hRH~`;+nFADPqon{`4pu=jS}m+}F~` zWKOPm_q)+t0wn{|S;r-xaXjr8)FnRK@)Y0H!t)v8u+kTVYrCPWom5)Lh%*XYgJvepZz9Vj0=}TEs#YjeCJ_ur2 zght?1Xc~DvqcNV+hb``6m_v|Z)%gDuq}$VuVWVz|F=AGi(JZm*wgZes8+?vziz|{Y zSm>akegz_rnFzohn|rEPFn|YOGGg8G6AvHH19FB@i^QN2092cf=5x$2XeAR|a=bRz zWK*ZV#4j@Qdj8{8uzb=Ai*yJh9j!@*=s*Z!NfFFz`ES_ew*MsmtycM$qGH?yhX%;t zYN65EH-{$SlL^sJFbd;}i={S^yT#R;Q1qcQL_)@}t0U(ryQks2?H9Wgna`b9IS($~ zF1P%eyezdvBT6moAdp7Bk8cSlr7pthitB-yqX`@dr(dVAEBz)WrzD}C!kDIbl#J_m z0|!kB9bG#$T67U*`11dF`$2z?X~9L1)vE1{aW8>Bbo8k=?0~{=afc(M#iiHO0FRVl z(B-e;Z_Lf}XWRMyuDG*bjJp5n7;ls}-}%{J*jE8f|#ex`FpbCf8ajj+QYlp;E``m5)`0a@21GoB31 zk~A@GpJmkm4X`O=W7^JA6vK0@>dgwKwZFR3>baJ`GA0`~*_n&M4XZZwW{d>@m2tx`Ul%2O#3cZ{&9< zknrZIwtn~pX=9`mrY7`D(oUerI;KG~;t>%`U}2PmF;pI3zGqO$rM-CE33?)-K;e8P zSG06ryNJZaIqKU-2W*J1K4lkOXHrJxC3`ce_(RQma{d<7H#^T)WC~7rAw=2DI_;3L zwCIT9#Gk)feM8=@R62mi@Lxzgsg(PI*?;K}4gT;Lo{9YE+WqVTdR~!55WnA?71^0X z*c|FJi=vN+mE-cA_@?h~Hr$~7yQ@|1tkb(p-N(&DrR7R))Q2O(iuy{)v%6oKG%li6 zWcAFMXNRjQZtHLN$-^U3hBf|zFn`Q<2Tj5IKsiM{m?O!p=J>*Kr8HR?Ah>zUva?JC z0DkGwa-11r%M)R+W)#t3KP|h5s=AV6I$AZj%j)QBgoc7Dwh^*zv zN`OH>f`njVXeS{ss@~)+t*|ZDP>$+~e@Ld7+p@YATxcYTvA2lXC`a5JPDp4Ui=kD; z{Cw&wWixX!x0J+%)+x8<)1bB}6<4@5{X6lZb&XC)xkaxaAZ2 z3Xoc##x}gNf1$SiDLBI=;FuwU50qdeVg}xIYn&*?!;vc+&fpU@a+~;$E_&Flo7x=W zRB6#S4@v*#*K^8r9`zKI0^o^wNmzYz!ZI0pAs*LyTy|7|F-}*qcvd8bf79gB%zrZp z5Dnl55V7l|<|NY&r5A%t`n3)0__)4sp64`}pG{3k-h$dvSC03nICm5&R=4>?%(K_` zDMIP-ROhhqpHR*62s3C|7rOk?Z4!i?vA0!q^d+-*Hn=);L}-UYUjyC=%Um+_j-1o* zIFV5i@>2ZWr@>W6aP)7fkb8a|;>|{9k3x!64x!SLVg$-pT2O~p-qW!7VA^;N`PT zn=_30MMp{;#h2hn1RbmY4SjTEkt?p7+g+kAS92_Z{8*ISa$r?fpb}l;^l9Vi(Ucpm zNnPswlGnbY-7@aJd2~;SYSF%Sph=)yD||w7XAAjMB?K(w7u|kCv`u2cD2U(Nf%K+V zDUc}Yr#?4i`Vv+!@|G^4p9aBmTn+eELshC<$#88yKW>WO%Ms0EOB~M+`)CDlJrtNN zO#bELGo*05Cd}|6oLIj3#!h?pYfvwV-=9+dO}mSt^pBV>t4vX7menmSufP(lL0Nle zAA^dZP1ko%qKNgL9p$$eJ<#L%M?_D^0k%BF-&TRa&7#L9^RZ(lMOuBU|BVT&#D@qn zhwG;$YN(|(`EQRJz4`I4owSRF$KWj?NOku~aM%??91F3}7VhZib7aQ^!(}St_fur< zN&+1-&D+*QLN0V+!SG{3$OiT){HZwD1uh>Iuu;Fl%f1TU(9Nvsun|d&Cyd$6*>r0f zx445pmXu^hH?4T~=VZR*rlG_vXPN)V=BEKJ>uK$Ay3BoAWm5UyG&2eOntzYvIhXM_kgHz zS9%Pb{*XIk|N0v=<<&QGd%H;}oVlk&=~h9$oCN@?A|;)Yl^cGSk(Phj+LG3-8AB#d z_mMd^q{|PJapj7Q%;c@#q zJiY>0gF?+{$#c!YW}I#4?`Rtg+V}kzSruu^#Y>qGY?qXNJ9jOro2u=hxoDV8$U*gb#L3bh;_yUbBqr!KkQT-Tl_ai+D!gS3uSa z%$(lpy;u%3Ip*ayr|U^(gIV=9$WZWCL1os9_h0X<@Jh+jhJp~lC1;P*$8o{TCabrP zOMAT22uz-c+(mXUlD;!ItNoUcvGcrL>NP&0KbwZM3`=NL)4#^|t>m`<31m(~@{e`l zs7&u4?-sftNnY*tbi^5pPgi{Qr82kwtPML$Xw^0F{rX|ca<=15Yg>&?GxTU%ti&_% zIwzHvmCBbhi6MC?qg(19yKg`4bb2_I#Tx<~nw?TEyIUj=bj3w$L>BmMY2F&*0(1IK`vJJRrSrxCa;3&RJ z%~=-Yb_N~zF!7K?v)Cvx6T9WLG2^QLozWcJfB`@-gke9r2x>91-py%&%opsAN!&3eXHiM0>SM&u+bDQZ}r^&Ezs)# zYP<%TWY2)f_P+%q1T2?m@Pw*u##Da!pwxD=?zcF@K1R1W0e88t)VLk;oFmMz=uGYAe!$wZcSG;CZw zvY*p%J)=yEWeH_I{tW>p#+z8Oxc!)1lc>pX)&8*k^{q z7!NQ@2zvyqRg6|Ul2!R+dEu_q3WpPws6~rF(d+(!K;O2&rh#{^(Qnl+U=c8h$$+V( zW=Z>2hnoh#LoykzLWNsxepnPVRXhRtGi>5E!bdgwXS(_^wyz;7-8zcwqFD!N0*>!9 z>_XA%u304niw%xq_B|9g15BghSFEnXOw*@7!saRDlhcz8 zYf1#6`g}B)1*LN4jwJ^0$f=cL(vk4z^PM?^_anr4;73pUHjE)3ag^hGXx=uItT83vr#n#w z`hZa%aEKQ*bA`}WK{EZL=DA(cL9f(tk2Q)5>HCSZKgUs(s?K=JaKTWI`!*k4Z!KRG zNZtoNF!|r4n|$6F%&{dGG*cq&)1CTq9MnSJy8O&Qzd1bp3UDu#ci2Uzkq^TpCXPJe89Jcp< z03qVe_>2W1a7$D#H#X7&`BS@Fc+n!%vhV0T&D=rOR)OF$_}ucp>Y4vM9}*0tERy_Q z-3@4&0uj3IKq@Aq#@Dd_L;AboT7t56$&fE9g+a-D$l(iT=?rUG+)q>5Uk6AsJ zlHbT!P&iUb2uv@NakJz1hC**jp?XhhZV%~{<9%KhL*7Z;i@>)QERI|K*O&;*-z7;Of~dL1`KCLUuX^XGEPo#*Q^kcikg4@ za-vNvWuZTm<3K%~t}wq=6wSFWnAyPG7$+;vW~#TbzMe}XzENf}kF~cZO>;I=@E_L( zQ38jmaeF;~eBqGfrA2SCX`f;pW~>UEXEK6@UxkL^Cfhyzeh#Iav~F?JI(`(aA9)U& zU<%Y{@55^G{w28YD8Mo$7ynvje45_kBe}`5T=E~=IWIX$`i~iY(gujNS)>%rI>JmODBo6tng1pwhA`MeskZsLNIMyl$zhB!q;1q5F z9E4*NVc`>lw^}wzs@1Iw{9XK_E&e(9&wSc@jISHWskUw+v}X9T`@<4=!vLQepVgvR z9XEZnw;fzqJHh6DWla|$R&u4}Ds~(y^LYR%hK3W53Snf9m5@EG!ExOFuUQf}8x2de zTIeb3eXIxqEzX@B>DHnA#PwjfFLLm_p4zLEZU_SJJ7q%4h7xc(@^D|4vnOSm1c zeb2#_6=#>0#ueESPuVU7l{d2U|RqSyFn#mxWE2!p~(+IzlUYXE74t%F-RK5_r0yE2qDbE z=(hD0z{7|kI*XsSJ1ecWcpEj^pvdf2=c1vX879NEW9v04C8Gg6eH+c|i9s&iiVuhe z|J~GCajq7Os$b=o-yfC9_S|#*gVBI#xBu5mg?X*6DzG$L5HGro-#Z7O(k*1KP!ljM z7OJ~yF6x^Rmv;;*i1&z=#|yBO$gV3l+ttB3-k94_H=cW&g$ccp61ZFg_@3;Myf)*h zeDpA+zdDPdD9J1Rft%L6F_fl|&vG7}0#HfleZ|%jjGy^1u5qU@2u+D``|J8;(!!j< zGSdf*7UJP!)?KJa4=C&VZOH{IcDFsoWfA7 z-mp)BYl3t}9Fduj{zz0MfylPnSiZY-u!uQ~k@-vZ7tF*yRaUM4K}eI+Cq$!*H$@f* zR-WKJyiND#%T3BNv$d5|bRQ?&WZeC(<9Lkcqn#C5^3>SklWlg4$SNt1Q@r*$tU-1t zN#aJRt^i|fyUAZ;)71IO6;9H@`({F3u4~P5T`uqO^Xr;YTi@%9wu@kNCT1WPW7m+N zp~}z*y{DdOrMvMuR7#(5R`&Kkir8&3@G4|%ac9tv^tZywb%FjCc}JsOo|Xs2=w@FR z{QTuF?K_mdnzC}7Mpa8tVsdz_F~Cu7_y2o|>Vhs5=?9aon794(Uc5E~^tN(9mdwZC z&vVqaAJjAapPyQ{jMR&NKc~32p?vT&jxRVd6S0#;1{~gWzI>Lg;GXnhawj@}z}_iD zn`2d@w>J&C@eul!X~&x){YLAUC}lGNRD{N(P(Yka^o=o)w_QXLTg(b5Gx(UxY99-qj!KH=cE=-KtWSW0SJ@Zi zvb`i&H-D`-qLJYlgl`C1WFG^jNTz3R8=ZzPijnH>G+;f)ey9qBpto5QB zxFC$nH!aSEjxDC(W6~C1V_`<<*>-fDOAn00L{e0Y$utD^vx&SNzAfoTT>7* zU5Rr7cT{`wYP&YZQeiG@|Fetuf1gS0QoZ9?Zb5!(|WW>x?bh_BT9satg-NH{p+zIPy_8o?Sn;`$>Ag6=#!6C zv%|H?ZBr{|#psN2W+#Z<1lF>neD0O~=bg~n5n^KxNrXN-5j1c1dAPurtlCEb!p?eI z)%RNPxk93)WB0On*!+&5LTc_>jGXa=txx^Fh!4})r z#ax%B{`)AfAr-Fl>;q^q-pt4=JSDtlxXF0uHO&S4b%|`DwiRQngUeQ}Ix_C)Je?eK z1H#B=LD8^ZPU>n=$e;8hM6Vx_eI9u68qT~9p638X|Ar;CXDa3lt5X#dHZ(ypO#L0a z_BX6m!R-_2;W0LUG({-GBi!XwHLY@#`DQ%TW4N9)SyJsOy4}locGsVKM^rZaah6U3MUrjYpzj zQ^nEhyIs&ejk-= z#=jRcoA(};{D0%ge+}zD>SkkrKge@yrqy9Q+h87>FKV)6N~vRF@bMyX1Bf}Rc7j+! z<&sNRcQ-rp$F!OXkZP<%l-5*uq#@4(#v;$R~Fy5x8@OZbCZj5uHZ?`hsHm^R{edw$xMI0PxK$eJGjCT zj?F!!)OqcqBDm1M?d}NbIj;daNE(odT%BE0CMDNqu2cxX16SU25~wG6^pKi8VsR>1 z$Z;a$-JK{7IX$jKcf-W(ZY*L8eg&ZLcY{4Iie&L=W~~P`3K>DLf?BRd!Mmxp8z=Go zGY#3Bp$fUk0%Jz}@Tq^xH&e1v6<3R%R8z0TN6Fa{S=ac<1w%TL;>(vI_LF>>zZp2R zn1;UxhgIT2O6sI*mm#I?rWNrQK%1IYxCcE&E&D5+s@!Oc0NaS?{8%W< zsSjg4OYv6>QZ3t)I zfOFiyQ3&EZRgu`KZ#QVW2*+3y>|voq!<@~?NT(cTMC%W-&!HmO;^q*2;w3R4k?q)P zn2o_JQVd-$Uu_&r5pHgaCShbKEGR!a*0k9S7Ab52Rk1J&v0Zk@=h@mCkD}OtZZhd- zZV*p}y8PFZ?$m))8@qc8W1qE!9lwF!iI^{x&1fqQ!Rj^eiK*47Xy!A1OSZlrIiObI zYPPmYn{a1PI}%|-xm-``0wEjVHxgj>L)v)ltvTJa#WvwGV^_^au!|YMKy6y(;M(|I z@zwv$761De$~KtN?E|-S3f8vit;yhX*Xx92=8>pff@%cd>X&(DJv1zOi&vGKKRsRC zuMohK8KucVuePXYk(uW+UJqxacaAGiO4C?}n*EHp4{&73euIe;e6HO!g8R#uf-`ml z>Z1!w0Wmd*sC#uH$T2xYS?kUERYlc$Vvedk0_Yp=ds)9jLhchZjj|LKW0l3F*cJot zO%QWk*JoD)j#aTRPWT42x10%$9ODL@Y*H;&-|NIIeOd{!cH0*@)k}~v#N=zdY#&ao zhF{)$GLF_N6IkRWcyBAQ;R;6W8`(4yhVcyC;B`E&mq@M>vy_gF){^9hcZ~3Hd5lv< z7ae$MYS8!`4Msv)Oqhhmz7*@;gju{eBCjJU>@RL+=v_3IFJTdcvg zWfK6;(8h?rF(Qp$7(CyxPLG)=jQGCN?8xMH;`i%zft)4jl*28o#10b}!-2LW%6Z_+ z=>3jF6y2s_haO`D_D$=tLlmCW_A$kJRkfTz?%S~vrG=l&8&W1XRUIN6`#4mcZjQH( zPYis(6SP`Y+0Qqp^CSIB3jX(>_J5xb4IEPWihKy?0y2)=g)H;itZiDo@0%BA9IDl| zOZ_O?W2sCL?){04p}oYcGAPBl{ej?v*$>hV&Rm;+A@8kSOzDUu{~xyAIxNbz`x>T4 zB?hEn7$hX5q-*FBNkyf*rC~^kp}Pbrsi9Fox&#D47lwT6?c;nN0VJq!7H3gXj1j#|87b!VP}Yac-8=SE+uwImf|z;w&#y zEr1*P-^ zPQ%dtuEb>Sd50)`O(!Y$hkR0nO|a8}>&TxX*wM{^p~j|k@M1b5F|g)cn-|I(RNc!Y zs%7W~X~YVad6dviif0JP z_}cL-|9iE|o4&2Z&&JgEOOKPb`3dV9Qmh5v6N>wmt##W#8&p!I0r%C>9qcLDtB z*Q)axuy&Z^vI`3!u!lxonOPDmUS(nm(W$?CQ%n|kha?!!e(Qn_z#>=| zzXQxi=i#%Z&!%NSfyp*54q?b;ZjAK7!Kl49S|2)LYhcWqSI9doLFDnG6f#}bNP~L} zYwD3;qk^0xXO%vaIzK^;w5UuHQt+{RSkmHy$L_fzfX5;w1sm9P^Xzq#Y`$92;CQ7T zIYof)3kpFZDF|4i>A8ZKmNLocF!4JHS^qzk>h6;{uOP(V0tYHC1kvxNpOlKSt`5-E zzWP5_KL6te_|NBD5V58~4tD?2u(6n@GW96{=T!YN>S|+SV%1e&tUOd1F~y6BKiAf@ z^8~tb>6;zav&cmwM$G1>GD#H1_BL;4VSWg ziwb;nb(s7RFbk%o9`8woDfP`}U69N{kAfQXdI=b3J9M%&K0vP22X8h(h7cPjV>8@m z@qY&K7)K6V2$o$4o+qrmSHDLl)$P)l+HJ|jfA*y9?p{wX;n>xXHqNh#x6Er%!zkQB z9&ckY35PbchOzqSu2?FbYYVXEE-RVp+BQ4jOd}7Q7~YKKIY-v<+8PVdI5=7L@XnUIzI&R61PWC#8QI4#Br# zl0KC+HR9omv?>>lAMSB)yNnM&Y+ltjF)<2f205}y1Nx_q$yw)z200OIcAXgytEZYw2_&pw zKaQN^gvdbr5h+EZ+H|(~YJb~AI>vgHejb`UUri{5z*|rEj9ThbqX&-AMZdPW=auvP z2l1!V);#ki1+QE|uxWY|A~mR4%V$kT5Us@2BgQ2kI=AeSb-opwBcZp?e}g*in&>+N`sRWadTCM7s#Fxc zlO_EW#h^RVLCg*(F@~K1-i?7eK+_KY6Fpv~B5^F+Zvho1cfOlv+aN&h57o;i{U~Hx z=;_o@vG*Iolk|xUTCwqrhB42np6}-?JG{jRx*Xk%kHoB&o0BDvwXXH}bpLhS6%2v< z?RDaj*SwL^Imgr^C10QA5FM_27zu4Dz~y9M9di}#A9rW}qcWEk_RXX-pLGsqvmz5F zy?-^pLsZoB`1MVLjqaO1F56_fX4j?Z67;9P8-N9soU~Q*?n0e)CPYwH)bXPY+!Yhw zfLB`_>d#JExCP_Z-{2DL^hh!Zr8WBDyULBPt2KEEL*3m(tb$lhyPh@Ahd_J6zpDjVKc@p`t8 ztjNBQqNr(9vzWw4K&w8u@^xY2VB46dg8t4|+eWU8tlNHd3rDViaK(w|FPxDp<&AtncvJ;{Q8>g;4jYodOMq@6rh%?*KU^)Qg z=W;g5&f_nnBcy$6-hJyZRoti|jo|l%_F6c&_X7rAq{npOQood+GtR+k^j=QSreMH7 zF96?|aq&67;fR6uieaa$OJL(j2|%q6Wf6_ z-yeOR$Zgz>GXe8MdPQ(E!OvpER*eCPv2Z9^bE7m~!Z+ znncvgIYwOya+H-vl;+s%;tjw(0v10tXtNrG1XN4cKBWh7Gs?>Vg46**&qk2qbpSaW zi(IoJ+lam2*#9m7Xn>O)LVMz7*ao{}Km}{J{d)F>BZj76zWVM|Bb``b=0;ci zzL4&az<$m63%#>URZR}GC1aAdQNdw}SpIw{F0ug^xv%xWZ4D$oL`Evj&-+6>6h(e( z@y=zDeJWH*?4q^%Ga1YukWlNZS5#3=UVSb_KesuOgD>2eY|VRcOKQLoH}d*_c~{ud z0*=zk6B~Pj3de%7N?{kj?US?KCB+!RTK#5GK-=ub*N5QbjPYqqRC^#j|V4;1_TD?$kF1@`^DU6PivpGNSssUPsr8__H=%Pmn@G z%ipTle*8eKF@s^|)#}qU%@LXCZ+h{sYfqQYoy7nVlQl?p>$}NVPB)q}_~HZU2>n4u zXMWPX)Gr(Z``@eRP{IkZhXo$L--t{MUa?LDI?BI4emy+e=%FVo8Y^Q}q4xC%m@7yg zdoS+x`6On4vIGRwOPb$vbFk;ahce?29jPwm9=j1FbGc9BOFZM!Gglog%ov96Ts{K#H=42^9Ej4KFe)@!Uu5A$rc8tZnhzZ{tc8~v@AkMsvNR_@T#V3>+%i1kJ@?6G2^(kTSIUEs_~nmkUy*-6))^S!X`SNezGQBz3nmxvfTYBAT@qH4yOsultGGr1sDH-M!H=~-}* z&Yw|N9^u%2@BgJ{fIWURAo~llpLUF7wQ=d$Xhc*S)OHz3*gZ@CXBxgIzb3>@>xadu z880>A_m+=08UHl;L)|UAkv1Fc9~`f}$r4GE>-a9u;?G-X=ek;kEthmC)D)e{$|%L} zjuF=GecP_8hR{MboqG=$YmwEI5dUA!hv7W71|abg z=kOK8<_PikNhg1l0#W9?y{z<8uFJ5fj-C#TTAm|dfd|K_w9U?7F52u#EjL{UaWw$` z2x&Fn?K*itL`3}W*no}Cs%nBS8G*Y*dsFMW;Qw^<3M`PIsf3dG{8~@vr{Hv--R)_k%Xi z{%T19C6Xra2OJh`bP7K=5BarzbzWyEkVGf8*=eqk27SkV64Nu$ZIWcDll_6t79xmz z#Z4*cfV0fa2!i?}R=&nNn@<~(d841H{k$;cD9vxLH~ZEtOxtAg7uO~Y*poVhCB;~0 z_rC|kgJGr2%e&0mRzIEEmRdEF+D}x-+AWFYeXbep%0IN; zf*)DGneL*}-s(2prnRK0aZlHN;p*<2K3=nqt)-rD4KfJbkVYDVVgw1`4si4N;L$dK z&OsD{S;FEOU)nzIi`z#IJ0#g#|1a-QvE7x>-tb3777UU-919a{K$You9|!Dbe^uzr zQXx%>HF&?|swaZS=6Ew*s_t?)ZSwz95&vgI&nonvCWMnK(iVDD0n)=>`o=!W(lOtB z37D$9Pg#n&QfhcyE&F<7+(S^%N&S499U@vZSg}w~lv;dUN%aMow`mfI)v^++QyXa& z+k8o&o${fpMZcVb&j=v7*3QrV9((q|c-2PO-gZy2uZDLyNS#j(7FjKAJw`(JN1ovx zJe2cu?=qqJ@G#o{!~QDgH)T{&jcq?(xw8g89F2onIk9)XaWX6`W*#x_$wa`$3pAj= znq7~k>4#3jz2+CRPeJY6An{cbo_>p+LN`3ADsZk_GpS=PsjKz;*>tatj0F?MT5?It z9@2qh`@^H5IzHfFX~;+34Qe}o`|6z<7`R#C(y0owo+bh>(5*=;${2Mfe|Pj}rgaXT zd+C+O^t-{!eW7C)69eBjVhqRZI^$F~=)Xe+bt!v<%5I~g8(%y@`sR z0WY%AGOjl_f?mLIK7@zyvBwSNAyb!BbTr40ZIymo5SX&_J@@a}#G zmTBogWe;rWVI3t)xCbclo&Au)Jj{BwuWqKiw(WbYfjQ@H91FaMutq}LvXF( z?#jVxz1m1zXlRc{v|3aK8JP6j|Le0iI8hXnB_Zkv+g@x?YkD0peEzob_(d$uAL}~n zsKk(DCVM3}@6_n5M+4=t@`6FLl2b9b58qNsSvqoQYu!tFtb4?ejr?AwST6sqLryci zAccz1VImnLzt;XYrswhh)j|1x9vMF`F;*{4B;wSjdvxmYKT>>jmR-(LTc08@;WD}Z zee}F$G!XDe>QlpJVF^5&A%Zevc^Nb25|9va-Tc%fl*iVwjNfV(mt8rh_T@z?@QjZBfp`qy(>`pXyPj`S&B012(w0V(>jk56P zwR`9B`-0_<`XT@^QRlb6bdVaZqyhJQK0d?c@wnDL6qtAOEc&irO$K(&&7o>CRJS=s z435!?hJs$gnBGiQ9LRP7dRyv|joXu9_k9GlPHqmnT0)+PA?c!`owku5ElS*Jq9@<_CEm((ZFV>*Pu2cHfWKY zH97m$gv>%a_Wo*}i34jX6n30=QgbDto5h)Nwc2Fz5|locQD)tv_7H}$;D7pBUQS}p zT`G87FBO-mvd4c96^7BBoP;_o(mGW5z8`{l6CGw1G`G8z22SYz$3{37@T$;Deq z#_!yb;69=o2o!t3-H~D4p<9h2KLGez#@WUajQNn7fOpBllTGo&;hJ#FHaj?<%AKil zdLSuU8T96z z%l=)SKaNF8)P>PWNuQvDkz-#_qR4~&g$N>8>|Yb6#3fvV1DpE0As^Z7!X#h+XVtqD zR@+UWM#3khwj45UY;;HzyR&IbMpr4Y|Q>YjUX`A`>a-3KLC#?eh}l~ zlLIc6<5Fj=jD8f2?DN$$%^{p2lhuZ)uZLbh_Z!Z|V5Z-}QmVpF@1cH^yk^{t(BL_% z-qvmOgI=j88fFK3ld5oCpW8yd;}3Ke{!|N`t3y6_43qrT7iqCxEY?qPOVbE5wEm7w zDGZs2WgYfgI~*X~5R^ZtH#{dUx~d1f(PdHC(}K6@RfSKUCj^0J5X zz*|-5c!H&b2MIe-*h~y7=rIXfa#HAEIIbMKPgv+G%bgSwAs;-4uqVy7dKu2MO3$gj z)o{#eiM)=HLY)0ea^{397H=p#bE~;K$hK0bN`X>`J z`K@Q0U(~v6PPI$0i5#VbCh;|MGvTw>e8T*G zp7|oi@^nJ$R}O)((3|@V4mH|EC5oXOFOI{F(1#Cf%TH3XA*m&@{L6LR zd=R{w`SHQ(1jE1=IrmGd%4>_-a1!0W^1JU^5>^~i6@D!fM7zGH(uBOP_p|CqKQ?)4 zS{YqE$uEWJC4(i?=5Br*7dKAJpMLk+K0|_S)z!(G1lyIq7a%_Xew87J}}8(O`G4@-_QUPfaPN zC=2JRDJuSTB#0>7SvbI@=QO`AZM*2#=2OBiPdfNHnK9Be=@A4fuT5V~Q7eR4_}mmj zo~nt9Y-Aa*{KU%fug(xW_d4`?niw_7`a^$Mo4?N{mcn#OrkGmrQQuhtow<3?%MVpC zmde;T*U09e17o?pL^k|%LdY57(Dks;@JDYm$q7!&nBhjTo@_>V6jQfPA^pgJoP127 zWy9UqOVD^!Pk9r`L8+GC!%-iY*%`uRGQb(ibAdSwobcybQh-DX-@BuU3GG*-hn&Q2w*`PxUOHyV8huRu<4E1H~d92cj20IyvFxYmnqh$y;Sdbh)*^C zqNif`z!2TpS+Ppgz6XkN z%$E{g^{kpTCu+$`7~vQu{|K7eDnD|XFC}#!o+A?5Hy;C;V$TSR3DHdn-YE^t^IVtf zvqVxSZpKtVq>C3&2+Q4`4Vf}wK0l|js(r9mtxhXomOxP_dRMvp^Ln{aqR{m0B>sUtM=MZ^^E*pGbiF#k70x+Zr^P_bvKs&WQ{rMwIt-nrN=cdeQa$HQ-$w z^^90>@y^St|Gd3gmAiSYt`g;vhdWPiuD=*R-xzD;>l0x=$hcmG(cR~@U(+_~SM~0F zd%{pBSSfyM`R$7W;~myp!z_Ikj`Y` zXU(HaW7wU$OWvN{mPK_D-=`|s#kpwH3e^74d=bYcRqz+CdpxN#QX}b}tDzAV@_aLf zN=!ql9$~u>9py4Ssn##MH+Z%+AXt}R0A5#YfHljJb8*g$<68V^cFe9|o?^;H z#y_d%g+7pI#8*gt8G^U?tQgwHiH9R8ja)5;e(2lZ9ZMTbQ{hy?V05^htKA;DqzZkU zw}P&JSh>NMd+(tjfOb7y`q)Ba1-)bOaQe=_Pv8-kXBJg zRUWId1Itbmb5mC>qfEV5nU8U3@8(ubta;S+3So;8mrLjrK7S0)`BDNDdVx(p1EHg% zNsTU0t!@l(glu48y7!LLn^LGmU^3|~KL-o=lk<1dTNLiua!*pvGQ!o=uk`jgm^q2k z6HGXSdpgAuy07fGAuP#pH)~D({mz3)ywW_)4WhKV%h2c`>_k)k_UA%8CZ^=buH!;s zemIQmXsGVP{2Zu7#vMDPcvr+(!B>G$HW;RwOxb=t8DM7i+lqk?0c|y&SB7@y2che= z92Fz8h#&O@0$&cL*jBeD>&Id&o!+`i4Qc4s(@$7&FP6spJ56dW<|wBUE`7h>v=pOz z<*?4s*nU~xRJY*^s&R(c05GiDgeV6Aq%qQvW$~BX4JE?Y2$3_@#81EnV2U?PXvSl!(4O&&! zWz>eo&gvfxGd}#hYIU!-s~3O2&E%9+KXJL~#gqg(O-8)xs}<(HCpi9EW#prfHcONu zfqZ+PQrA6%v{P6VM3O~<(p@l;ru}?tf;x0rB5Ae?yopt1itRAT)m(i2gqKZi`NS=+5M>}Te$c3hb$JXcbpl{9kTZ%2!d z`zYUIfa3%V_C~J}FardbZ0Pf!5}r`b8yPWpYUnR+{k~kO(rR^{xg4q%pec4Upx5Ie zBaao40fm-;%fCb^@_)IF|2p4*DGFH+J-RGI;%{^Wu08SX3x^T$!;qC$b2-@ z-}rqoXZlQy)x`FD%GQaCo=aiC_sUWsCbJ^PJzhi`&1;r2QWcfg1phusS_D=qBNTzR zyV__;MUNnFotwH(UerW|sXqw7+o4=@*lP8^uM=!aF zHt+|%m6&oQMJ<7HJmyic<{9w1-fxT*XE-ozqvXMA7wJ!8ivdT&b;o~DxCzO*nI47V zrr`sQIcqNSM;f=7H(b@A?{4-pEBows$wT+BJ-BBXq4xB&)G4@{17#jIS3UEzVkRR9 zq6f(#AbN&Y+}k5QrQ64hC~L}D&yk~T;&%PI8Bn#H%^8jm2ATZDb88Q`-|V61W79tEie%JCzX&6Z0VMjLF3$6oq_YPL_wF(iXziX$rVxcM9xttD8Z(sE#Bzp!maSer_tk0HfLgnOl zdA>M4hq!1lOnFaZ+3!EUuL%8f1!97G4?M^69Z9mOzRPO#d+4V1cLc5&UE)$9S=BO= z(|7zGg1P)KBg;l4Z0mswDO~D#GRWj#DE-UU0Nc2S{H#K@5V*G5ydvwFQJ0+cVLiig zGyG5ki%iR$D+p2;AmS6FW5X$k?i$#U^+a4Y;?ytYgKuyhU}N#f*~|fA=9~c%SNi#q zTXg6kgqr*@#|k(#V;~LWtGlR(zfK7QB|K1Q2zKlKZAt5Yd;bhyaiH1x*NOA5i~FR^ z=n0RB-kT~na}@qMKB_o6mzZ@AJy5n1q58`j9uG|p$#(9&NeS2a1-mFy>XifHIFwC? zC8{^T+Uaf03+d1G9f3yT>_C@-R9>y`4{u|E|5mSA> z;cX2KvD&7lwu#o4Y>_4`tBrJ>9R}LH-`zT9`hHqC-H$$wx`pYklSmNYArBukdfEx5 z7}Vh7@+Xq)g(YT}!VhUcVEums=YM{d(55gjFpzk=dn=izD=Tk?QxV%6Fzc%P;lcAU znuM&$y=kU*ypdVG2L(^u7?Wmj+D4ug4IV$p@G`FwGjHNOsSQtXp=*(? z$L3-v7?$OshR)sPgekoo2+|9bVuPHKAI{Mv2!_Q?4(-~WCzIU=cdKEVx5h8fF1^)H zaG}wcYCnatiV>6iX$R4(gPA=zErHyi#r8e_JFsM8Y69}wV8>zjygXPkQ2r1^x&t0$ zzQYH{@!zz>ZzqN(;3IYg{WP2Jet~@2wj_oWs^mA9SW}w%HS>sE36uGuvfLJ`z3FyK z?g{FAAn&bKUjshI9EjhJ#TZUnc0P*RBT_I*97~!wb`C$UH9L0ug|Y11KC0K0Ugip1 z(!T%I_qeZGJwY4m!$%zS0riKC>815(deP{?y-4%B-SRs)RLWbi%rtSdmEVZf)1Z&r zn)^RjEB*^c{{ySLJ>t6i`jmxp{}=}`cip~8TKVN%-lS-oD{Pf>H6YaXO^%y>%1-sW7peK*zM$9Y-f}#&%Lc_HZXxs!7;i(Y|lN zuXrKn=!5c;1RTwSl!kV$KVkUc%Yx<(sEO#EBG3Wa-9=gvKiV$8g?iR?P-*0yMR`HXN;Jj4f^9+(X%ILLFmL5L)7WXwW_c6wVv{7wXpeusk4 zZpmcn_%T6vcjT=PG#Lta3(>xh@8l1FzNDWkqm+87D(%giLyffO+5vC7E06_h&Bg-Tf-Zd;(8>=}R#n3FNd~Ma zn{bQ1(F@My5SZU4hpK#kD3zTB%4@5NF*F?Hu~I||z7)%6Z&ccUJHta2#egd+Js%uG zSbXze!-#=YvhDt*=0!C!hA zfLb>k`S4cF>EN(%nFe>=v{CupqPhcsz}4oAAv9cTia7#b4EeeR;KChxdVB4Bu)Q z@C3HaQh~5S|<6p`_1v1pJ1N+fpC^kdnVAKzZ8`=`(B zK}SMrdxo+k7BZ9SbKB0Rj17b95GVtjDFdiN=~F zI}cQ&n^E$i1ZFlbU-Ky`g0zR_O`lLOz)uG~QtM<}FEfx}3ozF<5-c&GQg+u!n&tw% zkDDZOgbv-=pPHks&j_`XL0W>KLMLk43AwhBM9CP>GoAsW&ZFp%d@ups%rjid<31UM zmYXGrg$*f`QbF-&y=%2nY7vReIg0`K^AZe7T|wpV3rlY& z0thu~kxbh6y#dRZlC7e(i`NAd%f@N{JR^`1$>%2un=oofA$SM!wu>0L1WSN2EQJyK z{GK@I7O<_eXEO5CFtWitOke{a&6R?|^Ed&(|86SPwhF!dX=^^paUB<3cbAC~hL|@q zJ~6)-W&m$+;>+CQ!r!(?=aZFjoLVd|UDpkkhnCweQE0B;q&RFySD;#xq3{+(;{~__ zQ5@X~ZuO5OIfs6W+h$VIJ%h#5R)@r1;Uq8=!Ak5C8y@uDtzfTvUysJIki|GD`bJYP z=66CCxK7m3+t${SyXI3rwQW`vz@;5ltIy>q|6VD(Cm8<6Yk{BdZJR?lu~I%ikCg-) zhlZf&cf>GlZeEO1wVl#@Rz~JwTvnKE@so{>q-vh2Z})yf8&hLBmYc|@D2x6(!M09g zb#QvJ`abY|v4nu%8dA(?BHa&DKT4lBD^#YKa4DHqXY0x2gNu!D)>S9A?;{~0QK7M^ z%$2lcQNqeyPEPV^dd=nZsg|iwTb;Zd>|vI_5$>ZG%+vdA&HG%sD*X$sR{{+4LjD=`E~=QGRJEf-FeTRaPkXF!iMKtvnzzUz_f4W@r)Lps0wc949Pc~L4}B-9*%A$*bL&K>#iI5E z!Rq|qwLiJ~L{y7=XbwmtkVS46q0Jwy_xT@!aEY79y&gC;>HVu@1#mboE1Iv*LE=X7 zHi>OBc)rCibMpG8Kr4ol=fum+EwE25&K}50|vN z=Zf>ybVMOml)S#+oX|z<{&u~q)|_52P!~Cp&p(}!U@M!N&CAU>s8$ycZQ(F|=fRXe z+kTuaV8A(Gjlf3m`^kr|QS1^Zf(fm8k zuh_dJ!G17Dq{}bs2NG2G1MnS<8&O|DwUMyuaFTJKg*mwk)Len_I%3-(nTGyyfHr8# z(Xh=*W_Uu4rci|}|9XZaXt@!w!d+qkqQCt*$l=n${YjesfN9?B*JEc|tA7|Fj}@J{?6AWAKh{v>e8wmW+$DbL~nkQP6V<@xLOnTL~^ZEOI*UxW(7)w<7_V0@J zH9?XFf$EFG2W}ELqd(3;U%qQR?ejgn{?bV6bHwJR#o%-R{&b};&dL8A_ZfdUzOCN! zvXPANI-FQ+j8U)&tBOAqa?->U+hLzytv*!F)#pB~R4z;@gq2e&rm~vr4FFP+B8h!p z2WvbapLkn8pprsu427u;3g=}!o50h=lYzRQMiM2$vLmXe)rj25NXeL`M8XgK>q+nRArnzRvcrYw zek_QSdl#Q9$K%xTjRz>HFig639dDKKGwQxU1Q0 z&Mzl06OLhjL`c7lJQU&9?qPI>)>wS&X2Qzi^yX*+Sm406TJ@nVgzr;Kc1WPNzj_OR z=YDoFNisXgJsykseeqW#lmA0)eZfuzd0P0OQ|a9VGug`k!QU{-mnKMqV!a-KUK31V zQXU(&Rvs46@L(qfR0*k%99s{}@tToseEtfg*4d15j6F1Jw6~TI89%geFeO-RfBz5S z4ibH*_Ib&<)URgHz{8z4XEz0h$P9E;8XQ&dUrrKOlV zi+H2yuX0a9X@!N;hZ!Od%?(r6o^L-uqd?X15J^@(5k$s-PaUXL9)B@;Wqm$l8vyPL z--XTujy1Xr_{ISs&S^v1{9D#qp(d9Z22TMz)WCjQt8C$2wyFLOh~;7&9kTiS!fU^x z4DRzX7UCT+E&T#1g#owET*vb(<7 z(R_yue<$p&M0;=qQIgB1;-R00ltjP`PPt07Rq}QK43Hf@lJ~~Zjm1c|6bW0wb$8?; zn2%wE0z`2%QsSzr-Ugq5pi&*#o)`a?!np0b)qcm)Ka;5a&ywV)7=U)2pv5i5BeKj+ zAdm!@*63q9#x{qr5>W@-DbH~^>k$@DiC>0v1=iziRIV2K|%)3!E1vg^{hm%bZ+!k#p+5_#ei6!`}VDa z)vipkDwCr2pnREHP`v1$A`qY=a%MD8cTnNDaV$74!nF>g<=#C65c&&(WNe}=A8bmq zMdtM3eNT)+*Af0N1Q~)?2wT#u6whyAjLsd%al3WZ2thkjO0VOf_PWa>UFS29DnAfa z+w^L*wS4Yy2q!j)3Wf-l?%@Y3Jk}nLcp5NE%fkV_-R@RUO_rB5IO2BR(-;7eq~CO> zHsudQLg1%-Yk<++$`IZ?31%7}?qJ0GU2~D-G@5l6aGFvXgt&F!54Ftt@Ax|WPSBMU z*$mz(reyaS%WB}yoW`o&fJ`DE9{>)(NXKFZjnnw+SyibQ0G@7C=%<=c#xFfExz)yE z6xpUId6r*~W4e*e6rhMbszV640O=cG|9Koge3Qz6d69GyqcYwQXuUAj?>p)%^Ktr5 z)=q(d%vtN;^wd|dMoC5M#e4leo3Mt24LcQ0tX$8Kaok+GQX|TjH5;h8p4dqgAvsxI z6AYTQI}UF|p0D$nJ!4UoCbE-i_`7yx4@}vOj`$2w3&v})tbT~t*qnGqGZ93*NQ7d*BOQA`<5Ohz&A4z8A3>%r}ra<6#6{ zrSrw%Tj|U4Aj$D)*;Yr?fx$SRY-%0frRu3o18Z5EsN>Dn(2!|0V2@5O2tmF#C$ZpZ z7me+$;OeVQPf{GgO*!u1)v;S9eaN4$Y|k^b+75s~wql%VRl%~8MUKjzO|_VyUoR0pFBLf^@U**uUS?t=$thwSxCh$D)_VGRVn@!Ou7s1w;em)-zub*ao53U>+NGUZD`CoaLaEZ;A- ztyY3FAL_=KQP*Wmwe(K@OM=GWn_qsP8ctnm2N0a2eKL*?nlTuWQuH^t^vV{<-*z4uy{Jnip2P##3qPw=}$} zG`~DL_+`|-2R_(Ze!Kj*z zncJ5X9-ZGpv(?|HG1P)&PhI}Yji*BpFdj2#{`*?x=Q(14=i>9#4Rh?>*QR>g)#`+m zf(v}RHfA@2aajsG#j@6~~FExa}w zeCg%-+I$b^40D>rhtC`WL#*K@mXIljZRUj|PsoX_ z;g#MPbfwS>8UeKk`0>}aM!FedaV`~!%p9j$!JF5YDdn>s2D>R$CQG$uqX(Gk_jS*sV&|WAgg>~`4zET$V~O%I zKj7g#JkM^?TP0sBFm&=#GERGy@l{n{>e6u}6U(2U*cjyd#+caV0`aQvEYD8+zdW4R z1hVUQAV6n9ZO>VJaIaTOOVfuhw`tV|9EU`WtScqW9EoeLX(M~lc5BLGZVUYw_J>cf zB8ZJyAIz5B(?|dGQ2;j|5J9IWKHjF@oK^-15D^EOPV|YFCzxalOa@DBOnN8z^%xU$!3BrIl_){T_PR5K+9n99#bAk zWI=q3MtF`aoe&lbh#c(`j#@wR=F@Sy9U)xsanQIei(lyPl|pxG+~(tVbm4|eJhk{r z21%=vTW-{xh9h0jkOZMSOZ-pvG;p;o9DeUx3^U{LD5dn$;3N-tqUo&F#lh_^VfaVf z!4Jj-p8Lz!wBtg>h4!wA@u$2Z*96=A{>aMi*%v6zpU-me`P7*w4f-H)tpgy;V=?C` zfCQK$=j~%_Xq*NQI?jL93GW@V?DSU|(W4dk_PjmN!0ph;le(1@X=ZTHpTY6u^F>_~ zQrL=sB&xt*&EjJUu@O`xknH5UM1(&o5s~96(~d;$6&gG?e)Tz{c2n*}PMklW4ffTG z{TEorg;y0L8Mk{It(9t=(66dq+AcqqlDw6;^-%c0JPn8x9}+Cdvc*kpe=e7Jhqt)-qRY@upWwz88dK7Wla)vxKy8oVokEL_(W0P`Eqv zQeoU*@MCE1?Hf&u?Fj0tJ@5L|0Eg~HwEm<^@sT4eJ^4>GK!(rH*zVnsZycrvh5bg{ zA(fTF>A?xu&WbWK3`8dLE`h)GG`J;4(->{cpYR$7(_sz;aDotB+l6kQq56_!ez2L+ zCO+hL>v?Gt=~QaC-DPPFu_6jD_QPaKK>iSa6a`*Ds}cj;*8s6 zFE6FYtamGadgY0jLMI#+H{L3hu!u7tF*k@G@-@BF)PhNB|0TGR^a#ixnE8R-TP#f~ z&AJ*EmIJr919}wB-iwltkrhRT93)V_T^Y#)bS2#v^`V)2Y|g=!3hE0$yqY-b_jUJ$ zn3&7VbGHD+0ZH_6*CS&y3ME1OA7k-(YEz2EGdKmH7BnucWg&hkBmmZ|;CeU9j>&<) zth*t|^C>*FmmluFCEt>Z#<>7+!;Iq3>g)LF$&?0Ybx*K^3%;zx@>h-%82|i!>-zjB zG|n(O6qL0ZTn6pYfiMyJ;m!EyI&6>QhVNQT;@Lhx&SrSm=?&dCiMx-6JeDQc>=AHL zR7(nrbeV(l!OzxlnyufL@&<1+8~UNxHV3+#r({i^c>HoPd=lEfq%YJ^hjQ^9>e@2if9Js zVTRJj^#+t{YnucuUywF)m;yD1A+?oRrj{FZHz~LcdS1qCqUZczO_-Fv`+#4yH4?0y z6hjh#zOtZX;1jv@VP*(i&-nJmWg{>_YYKX)=!o6%Jy}*u~B$L!< zoSLNeidmStA6=naLdNeJIfA8wK;Y(IQNJ98$b|TGwI}@s*`DCQ+>>V&9zN!W8Gy~s zaVr^d`CSZXOfQ=tFo*nTk3&8;xtXQ?vnn-Q0)`;DBR(FH;Mk>Eki?Q~t2ffWqY+Kd z^Cjj8*I^00?vA}G~#bvjNrk-pIcOR}&IG}4zsKyD;gVe#NB2=n_*{Cz1j;q%E0lrb;9_9r|y;I|?MuseuDiEI*p3 z^~?Zip2Px3*9`>&r2d4OjkWg&c7nZ*S(+(ThRrNa*T5G)C|hU@pu{h(?AQd+zGFvX zr&j8#g(bu}e)g~pI>sbO{k~y$qKFI0DNpr8$D(xT0dYDT?9D;=OEsd?`G?SF7`8Uh zK@wzJJ!LQ9c{j|oG<759*X9p$JNFOWJ6f`K`O1Wxnu8Lcvi&RUM7|;rE&a-1{gZd_ zkZSEEkAI?HZjE%*UdHBO1mD9667WugKEb+xN!Qos(kOv4Yr_E!3%tY)Jge)QkM4v& zPEcQ`S=P=wX$?M(%Xm{hR#Q{Mz0lE?3dB)9QWyYE-~G|V8z3CaL!jILP6|9#qwmQG zKaqP(pR+eBa>e}qBCOBfclEd^Mu#^(_cgJjrAau2qdEpK2=w#(OhY?zq_1sya!~Wi zk~eEpQL?I;Ixptmh(PI&f%yhGmWPU%4@e4*?CF?4LcOPeZQgzS=Pa$1I(H{WndSv) zdN^y%7&0uf75E}&R+O&n`yQX6_L4mMgaaj7U2Q$AP4{120LnB^-7^I&E&*9YcjeQU zYdJaKIN_xmd7*(set-Ze2+nKdhf_&6Q}E(2k`J?c?PUT;ED&uW@VqcvVNW&zfhg5v z_1mc1BV1h;5t8hj;QkJ`Z?ziufG&+dheo#lSrewEYvFdRSabDR0y+YEFuNDaxzHu= z(kTBTCBJGU@CsB-=Tu|l4pq^(8{ED$bMgt1E_UF zO^&?4nGOOA>;XmUCSdyRJ$8i0;BSZA`{&GrIEy!)qeJm?iedsMK;Gzs5&`eVJNGgD zfzeQrj{NC3%$__|$i^0u1fUIL(rGNzm7l;1kdDjNBBk9CMdpWnsTLx?CMJ)zw z%l2N@u5~N#JnbO)jl2&-$;)#Kvh0GrFGhTdiy*lxr%W}qPPlyLCcPjA(~QKoqjt>S zGHc%b;wj&>e;EZ{hm5}OU}V|C2d;?qXPA!4k((tC z+=3Jrv(LFyJN%=JpVB_;(gYWdwa;WdvtP)1WnMPwV&%Hjg- z;z6=ffcP8sBhxzHsz72^J1rWs<20>sWyOqcp5W2FOa40}x`j2FF^GuB>E#+2tJSWt zj0ty)Ctg}`GI7myS)MZKZy1lGKDjTn;kO(drlyzx66c;mWJS66^1!IM|Fh~+6xZYFU2+AbcxET{r_iuq z;bsv8b%d#-#UWTl#vbYBTE>T|%s3Co6KqcEIPn?!BUIVnCaTSZ6XZv3a5eGQs>0TO!r^)UvB1~)La|&JVc$R5RUY7r zI{_V1TbbCW*lvP_T89)}G zg9iwKky}bC)Q!H;pd*?z=45@oR4JDq!hlPlzYJkSa>t|wcqE?iR9_UD6VryEr9-g3 z!`}zK@2P(oOYj|zu`IYJXNh-Ru5aPHCnrD;>Rv;S6CiuPs5oUg$bz;kcy_RiG$lHd zJTUL5RK;x-wR@)-dExe*JLF|et+n{CfpTtzN5a70-^uIg7gOGrYesz2w_3E6N^QM? zfu0m{`iz*P^Y}d1Q5dlvK#GFY!now+!qfP7^P}yD<5L8daGKWdMtoFtewchQ zaZ$%x3uN{C-X+P#d06XQ^QHTajd?Y`u#2fy1_H~Zd_4A#Nh~Oc2JqhcbmwL#lZTz5 z9sdx(*XSR5-h)2>l6=9}D>I)d5)#t>l-Ke%qz}ML!0~efNKon?3{7rB^B}H} z9C+W1r0!%=s3|hS1~pU9$@Oidyz$7j2s2meTM!NTEjfqJRPnMfaKZ?6oa8r{bNjYJ zps(zW*6(yvX5M#+;5wKP9z8ep-6z!FUa{)IFhLU&;UB`Fv)}1EZ9GnDYL_;i@6lxg zCEx)Om<>sV_bUczQ-bqn6o6^yj|efnu_Lt@s~|kK-l*JtVqn!Gd74hk7kjp=@JPX#Tf-lFtnf$JeIx7~W z|H&MI3g11NQysrI{h(Jlyo0o{K-|=Kj0P_UH--90JwACNp+ta_KRsW^-tw>iHH!ZL zkQA?O1L)o!UTL>s9=Z{r9#GwSHf$qm<0Y=uZcpg2aSHacSKKkj%=wWtLHXbQZOouL;18J`fqP7#-s_Sbn}uZddS=xA+C^Aeh#*u3}Ox!7x-FhZ)qbdO5z zQW1e5z0|Jq7?GO^-HTFY%m>eqV~9S!mw!6a^xN)lnx;Z@DX#CFBv1j9iq< zp9^|Dc65ty5gi&-;jCbNQdV|8u{|pn5W3x8msPVZ{co75ZfSUQs${xk*C-{2Bc%c3 z{73f?!+k2S0kXEs@Icnlea2qeC80ZP@zQRcrSVu*rB|=l`Lx6K(tfN#T=HT@S-$Xb z`^=1l7EROgc*sRtVs;bw(bZZx;C8~X@nr@ddbo^WFlc~TPuOK~WTkO;6F z^ofWO`tko0X9BN;6qriYKj$ZIh%O^c!4*h6MW_V2uqf`4^2vhH?O*(HRL$5A(w|tN zMq)br$h_1mA}p>r?(KB9Vea^L?Kao$$!ucMo_x6M#})6?@p&);dtzui3Mkf{c5s$M zDeMm>!<%|C8~HChh}}l@QM|m)HFImTv{`D!ZyI)@8*nQGniDtaL}?#ot(tQ+ysG=x zbZ0k+B-A+Sr1|n85M?6<+pPcwIH5{L@Y7Gz*Te49(yhe~>e}Yv%!0rIW<6y}n-0Ny z=A=#)1#I_B3V`&c5O)T_;me(+1jg;jsS;rzcmSCEfZH6aT}CJ;rSx%7&Qw`yi%U^` zaO%`CjqAx!%M3cL!8tk-hU+AGwhGL<0Z{a|p%+>)E_2viH@pu&u56f*cCsdL9s3 z7Xlshaq2&aslmul{ICtXCt1?ZT!sy?X+dN(-7x8QhG@5RwYO!TVVr9x$Ep_8gC~(W z7dx0!_4w=fLm8UGmOyfZObiM8!IQrg7iakqubCy+83+k(|Gv9hOwyc6fixE~TQ@k{tWGP*=jy_-6-0 z9=!`*7w3cx08@q(%}VpuD46A`D|dh0>BePlsmJ{;#}3cKxxL*Q^2~1Suv)OQD@`NL zWg&gDdcyOsMgiRitair1rd2Ms`iFIA-i!>p=f&4%ZtCGL!-_-AAWVUA%_4@vdXXFJ&u)tzDSSMx5S zF2`wi1Whis0qlbL*KpesKbM;%)@pV2YA3j=omN&oMLF-E}zi0-SN*t zB<9JfPG??@@gsVC>$Y@lw=FQm-l433D1HQRj0*wDfC>cYaCp;*sJtBKJ%Es5)J?yr zqq<>6zQ6G!Lf{k1nOXM}@Sn}8XVt9*gvG5fHMN};L;|l`Vmjo%j|7_P*Zs}?l180z zdhy5Ld1n=anO80{G;8o9*QZT` z1tiZRgA?;mbC8lz;jYgLO>Ao3aOc(etq)( z6~}vp$>8G=hrEkW$u35k}Zh}ig%S$ zpKJfkOsXXxf*~)H--UuuI0p#RA^u?>h84o_zOPrh$y|d~n1KO zJwp3CETNf2=E7?mI-yba-ejG!4dOGSNN2J)D}~a@h92G2Zr}nc}$banDZ! z$xJ_b3dN-JaJKpAEWL86$?&KZ&MmmlXc>I@WZVlOOe?yNc8q>~Dg6tGu1C9XjpdpF z{6cZVJG#k>KVfYo7GFvPMAiHFz-cU>N`$8<6&{PUGgt2pt-fTQH5y5*_9L1T}U-# zFzQd2l5^)>Ko%`Tg?LS@&q@hW2#Y&#ajNDN<92<4POnZ=Lj`oinT zP*VO%%;%FE2XKo{pwLv2J5?*;XB@pkyTPA+vTuPmtB|O8$Ad81Of>;$p6c>h$8v7x z9+T!5wBPq2cIq$E|7t5DA?U#5Fmn1IWZJn-#vM~R4JLcs7sBGB@x}eZo0@+%)$Mqt)p5^MiRgx!u~`QNaF{2p7!Cdp&N>SblR>?db)JL6r)QNT zix>WwGllyku?=Gdfo@CUF!ZP=GvvayFdItu{kfeO48awcB;5-tFv2!*!4x`K3n1=< zKgkXF9rP)JlRs{Ft-)+NkNQ#qa$5|g3*lUGeF-Wbh=7OELN#seiCZS}JohWlfpN|B z@M5R@`NVVgBiq53-!wJ9;L$)RdeBCmQ$WLQYlyg&;1r$X~E)Ic2>Z(VLjGG^I`@aM$0&2#4s}RN*`WX82 z;$q$K&-a1Gb)?E17IS-*N$)alY^D-wf)JL}le*sa+G~(#m(MzK-{zaun?iSW%F}*& z_(YfWG@xx7ig1T#G+m6I{~V6pLB<9rFI`{emkslD7W{chz4IweGGTW^qAKQgY}0Gb z*hnu}L_L>c8i%49K*G8E<{||7CBFROWjcwAkMGwly_K8X`8j48BT^fTeu5RNLGaQ+ zQ@aZ0Ck71UFajTgKr}z%7gx<>=cuazgjKF zxA->{9g-##0QRt!QEh!539pUK7E^-v_xxP~%vfrl(9KoJK!99+MI~R_4S2c9@-to> z_9nob>g>}@wJif9y4lYw58px7X@esTje(0l&A`AOc_Fn-VgV3aGP&d0YA!)|hZ4k8 z1SY==w#}&kKPE%LNb2(C(@1=e{WrFi;8B>$LQ1#-T}<#Kklo}G+(%ovgmRxJ=MxVf zUa!hE(6pj)Sw7b+doqkpV3v{P@DSYuD z7uWl6iPp$y+Vdhg>i^+&c#&;x`aB%WPVj2w3_?MjckG=>hs7XKm60M9Adl%H`3#$A zj*IbQ$YDf`&~W8kmPdw+62r9LutmJn7eRij$u*X{nY%GiK;XsYn3LcAu7%%aW^iVj zslU*H*4+YNVI6&QfZi42QsNb;ix)sI^=pE zMrbqe<#nI5u~Biu&fLl@KM4^Vjt*M*O2V{Rl3yftPtGXa zLe3D!`mM*mCwC_EAJmXZU01@|TkBX&xj6so9R)&VS{VP)Oy{v=nKmmqMMoO{8}z&8 zaJi98|4+~_XJ)Y9`vef;FhXml(uUn0zKu}weeEw9-~dv2Hf6+p^mf!A2=;(l58&X^ z93va&uIpwpK@r!A=$^tHp_hBumB=OoFmbjxgdz4qz@ED*Z4-AvHIup6RqHF64iZ$2;97;;-W zIp9`uRBKBDCMt1?;c=HW`&_Abi z&_G_(_l203W@DIQrw`%iAQhwQt(V}=`>=GFN~?ag1gz@lBW~cjoeAP40S%Q1OfxPn zPU7d+jEag1C~$X{@7#0Uv~(LiJzWsY3)UnbJ{?K07hkOmOfGws%0HbKJ150Pd5V1= zMVIY;Ek$%TuE%5AGz$q+HGe2cdFqo88E^25pQ_rok}c<1}{a7F{LT zOB@bME?#sPHMK@{6w8Tanv^$hFJq6s6lTJ=Z6c}-*6qy^;Uh!&d?yqrvKTpxezP>GVEg&)@a1F0SiqcHFG!bW+bcHwUzu^N%=u zwNb#mVgVD%xGXo{4r$WKvUcKTwq!$!alaE{I{6>_I^g=*fyCrEHvr5XF(LQO4~Dv zUHu0^?0F|^bzCS)K|r>~x=~<6?oWW>N5XCUgyLOrDc7W9-@_fcDB`$23_qA27Kv{g zl(yg)DDeGd!7?vNvdq%(Khk(n zvp!S*Dd*ttw-b7Q za}$X{|7zIK^{jeWZ(?J*w)A!mqpA3_=t0wlI&*vxo+|6jexyjZUcTP;UKrI|^X=$z zsicRqd{pMS_e0pxpHnpyRix6td8&>(84)H#ZvraGkwIN3|JPQd)`lE!^-bsu-X>|j zH@Fk7uv`oFaF7TVPADuy>zzs68*+K=pop0_ZQc=91P4q#y78EdI(Evj!2SPVB`y9o z2|Km-{L`NTDAFpHH1-lCBtu3&_KaQLxkwzZ{>s`U^Tm0Fy^gS}Eetp^V3=K(;a7%y_2&pj&ci3XS)esBQ|MRQMEBKX{m!fG0^6L^c=GXNx zcqUJ?^YYjQ8NL9l=AE*s<%*AB6q!xG%VwMlSWQL!Ik7u0KcE0}aQMorvCioJvc6Fh zA>oORy0}y)yf=4Nx3Hhq;ZhEGi0#UD3cQ7S5)8Yw7P}i9eo&n%h~Y{he%{1xC0%Xb zh8RmD82zJLvig$LpwZhyKRn~*=LxpNy5#4z#;eE=q#w>PAUb@X&dkd+auAE_tvYsY z4Aq`ePglJt5N*w{q)y+B&*u}B(~YFcHodHfnG!-`j?~ALsE}iDG>&V4vE0|+XTC1H zyp=@}I#CQ&?qWk%`FA7I>e3HP(ivstgRXD>wf<=(P_e)$O7z;Z^X|j)U#^Mo2M6TK zs2h#>;lph)JKdJ;J!cX>*@BFhRRKwi%w)$}_p2#?)_~-HeOrZVq5zGFSw|hcJ!buj zXcG|aVpe~Cc_H8U>?}yPU!szfZ<@J4fQj*nIemcLmp+EuB9T075~K0>2a)LYB--es zG;PR2AmCukNQnq~WY*k_!FR&46S&Y&RU_sj1j0f+=pH%ne;zw1ZiR7ZUl$#4tZcIu+#%wwG)6q`!fxeCG!t)lZP7tc zG6Ns1dGr$2&`wNi-=tn_w;{Ra9BCz>76;k`vn%usXq13$f$>lwRtJ$}(nBpSf9q$< z^$%vc>zz?kz`pib;`S_Qd8)bHks*>Uvw`e&oU6-Gu_43RVedh>bb4yClhCgH#~3pd9$6YKq~5j6m)>+*g#!zk6cz_Zdu zH)qL3ANv>80Kr)HcR0kf*Y%iOp6~~Mt$D}F#mlI%AN!c>Y5V2=6Gn!n>h^Q6#tMtk z#27})wY3)dVmV6|&%~pV{#RK!;>F;Dwmyx#+s?7&mVDE<=$i()Go3Y+z0m>#{DWi+ z(_K;|{@+cZm2if^ugqynwlc30f(vJg1i$tY3BOR!k;n{MhfAkCoY(|QjOJ&_7X2Gd z@HR$9s;c>BiB3Iico!J*^52Bsb)=188BI>+>Ko*N;vj)wg6Hw$wp7Mqt;eq%km^}F z>(Vpttkh@@RTWR&&&n;CZ~q6DdR?>J07S_B0D8uX&E5WK3(IVRQK8m)x99-2Pm)dr zGf&w#<`}sjHjW1FJGeZK3*8>mxaEAAC0L9LsPHlA7UkwgbED%CN-UNK=#cGHei)D2 z9)$At6F6j|E)lW)V^@E| zM1InoBJ~`i$(Wu|nA!)nn}a`z%#-|_293^r{`%n*V?TXYc~BA^r>HwkyY;2LISavL zTt4r#i7XhyK}8*3!5@||_L8_S-+OfPe`%45e{xrSiXyb@i5W|pRd-(ONMCAiNnJ8D zd}A)pVR*S(Nk|O3KHMuSO%f~=u-qIAU>!BS-iyHvuer8ix!=I$DU_g%TJ*F>%iF@!l@^;AReiCZamN^}Qvv~?~&;qb4ie2+Yr zDE$wL%oXhyE2od(kEvdK zctJfk*w~(<*H6Mpt&?4`+47=;Py)-br1-~mA8S8hSeMUc=J{XLy>!`5b+(wk-h4#k zpX~>vU+dYFi3vEjwwfA2`a3|DnAlQ`*MUD5A;d&F^ult{uq0**=Ja~*cW9?~y6`#W zf~K(3qN?+DOBbv%%uDOfgu;C+VSYrJ_vR1PorayR>=R4o1 zu{0*BH#mSqRfu5u$bQ*^*7q}=kJRh&EzHT43K7~PPBmON_%mL8jh!%;;?GxFQY+o& z%L(c0=-||cl>Gb1ozy|IfAeDOPyk+xKbEb|#!rp9*pAiTu#ifNhQq5bp*ZMpPSM$l}__r-UqRmq; zu{-6S3nHCX(bKMG3R2){Qi-8Xv;pSRL8_3A@6RTX&G?3B!7F9+LK1iLAdkINOPLzy zcc4Il845REhx8{RE)Vax0F)2JiL1eD9Cr+|6x~bjLPJzpw#}e>zwCW@B7_DNL4dZ< zBdv`F3)jLr;j7?ODa{Hf4OsBb2p*X95gNDkWYn^W#*Z%6cjH>p)Mxv(5{3kei_cV{ zCSOG4flAe<)lgL9C8v}M-)8RsuBLnVX|;H>JlPQcfSH^^_6OHzsAreAb513)aYY!> zH0Ht$G0O&jic&y3^|#WWhmH@Wf0poF|s4=Rx)TSpsIW z-)fnEj!iJ0z7Y>TzHlx{H|ruYN_v|LWgHp1gz5fa%~LPXKPZwl9;#!sezjJ_+E^(| zFpxv!m@=1g&sRM|`T8V6M?B1Mv|;q}YhnHQZ2ry7`K%`D-owppzx%ni_`gaKM?x5j zvT=f0M-@j^^Y<|46Yg;5ek+ASI-Nw8Qbavo{U2%MW7xz{RQD-)pjkwCjMvk3RW3qO zW;1_$z2L72fA|uaYeN(7M}Q1a@=Q99y?yd26TzOk`a_?@Kee1cD%h5@`lkTHt(ApC zOhRBG*-|@z4*?o_QYcVmEp&iG{{D_0j)v~ywj;5!pg0{kAF-K3O(i0up$u4-Co|IR z+Hgw%b*PZ5iKgA_-5dDjKDPT=d28Vr3c%pHoD+Jw0kG!R zWmx9p6#6JEbZG)0z`n%HgA6p zawIo7tGc?_f(4bz_k8i)wDJ z(u;UUQdw-0;+Kv#RKUDhRci0dSw8K|S;5+ndRRT2cRk;V4m7H1nkwbr{nBKM7|EVF zEa3@`mmTpW)xOJ*$C0GD1cSN{Je@6lZPAB&?0;V^$+=GNTl}OWAOe>pzQ0-zP`2(()7ha`l3)V8r)mNha zT9S*escm?*qs$-q{U@p|mi$i0%xp9!V}A4>WY$-n0ML3^jUCX+>0c%ck^6Nz+{Shw z5*s)@6gDsjTDNJZfa+Imh!`5zb9Vy*afz}g_}vi)sg8XF%QNfd4l^$6iO;`u_?6O# zSfV4i&g{<$Ea6<2XtzH)?k3j?^nMyaTJHt)T;{{3g}aZn(88=OWNys)T#+HtglD4y zi=j-c1ZdxT0!A=4a2puY_5GP0#}Ay17K$NTm0v^A5t~BB3-P z!N>^hDSjaFuxl|4c9WQqox0qXy9!8(R5%%gg-5hIu5LlT5DHXYCIO^&5IM(lfYc5< zYkyLfSr@|tklKY5y5aCYj_mw^#AVTsmKa+U_xR52)lc4M4=W1*u45+KI8M*i2nZF;; zhtlB?*aaTmPPmj*(qI{jC)!~N>|q;%UY*ra&NyiHT>@OfLbr;s(v{}d_`ViW?OgKR8L|>fBJ|2_GqFp3FDPkW8G9t&HKqwvC80q+Hv{QVn0wm)+CdmYGA*E2@!Rn{uo>VQMo$dxEqz=EBK zHt<-gPvQ$rTMkXF!IozMbZ$MS21qnmy^YxlnnyuuL)AggFvB)7G9q{zwsV32dL1A% z{&^{$z>-7E~7+VAs}YgQCOa zvc&2>Shg$znFv zPYrVHRJa%wi3*hO|BQ23WMxk(O=YcD{TdRZ3$1d=Qv(9~^F#8)SXIEKg=zi|(Y_1q zv;cYz1qvNuy7>Wnnw=S5c_?Ymxs&^uDS?9fe7O{{6_`L?nMS-_ELdEgl&RWdwxis; zh+v!+@im1Z7t-hXyDD{7OX9G0L6xI*6S=e-pm{ zTs%J=_22nfjep{Cn@m6`?!9S@JcmA%$QFJ~kovmC)0OZs*JDsPM9y+5N8Gnm2}7I=6`m_7~Ki4+km$GfpbH6@r8?)uQ@%O6%16)-_Ykv&RECWSJ*Ulnr`aDSj9K1^{HM5Am0!&De&vlRr9uU8`0rj zCeq?2Zi)3O(DK>0_IsvzJyy_+X5Y*P8|J2Rng^(d`2FdzY8_r9|Im<_KH`VeV;Ts? zYh-+0s-Rq>BoL?Hoi6k@E(5UsKMRf+$2&bXXy520cKh-r`UCX6eB8&A-OmH(Q`23E zbhIYw#aZh$HJM9=8}H_oXUb%@FMNUcYOJGu(b4#PA+jnj%j;ma$Z}ICWpn_C`$1pDUZzn z`2j-Xf_+V#7s#0awOR z4fPT`NfHZ|rjFh7okz4H5@o%8UUR>3%J3Z}jXJu>^7krsD03+Va;BPLlfs>W%<6&H z%j&?88>}1rVnsS~?GXuSt#M0ojY*r=yLS!^$m2-q9J;{sUzq3LjYCevGaBfqWRpj=}IdMe)`d=4_PdYEY1KM!LJ=oQ1{EI z`<>g)quYtu1{-Jzs~k-*>MB_J{#+B)sM}Z#Pfs2XJapgD3%<~U_AhI}8|G^NM{-?+ zj!2~L#gFCXlvENQRz$MF14I(o&QqSv1|;_^yPQ}*}c z&+#dR3PBUi{^!gVjrb-6N2;FsL9n=}inQQwG;5rTN_T^CknUn`~(o3DZvT5z4w?QJH`@#R@-Q}KQ?PSpGKmw+9c3$ER0ZX;C zG5MPU20e^GXnOZa!B!eLMam7auV`Bda%!6@E{zK9yvM~iYJ6}(G;puUfqkLNmX{eT z=u^T3kgSV>wy3{kRLG&#lqvT!3JItE0>xMYH^8S5vONF_2PX~l_W|_9=Z%D)Ryal4 zgjSR&+PA#)jUUGCh5e4!{kmnNo+f7T&lS+W#9OE_r9^vpCP`KlK1eK;y&SCdZ5<^QhRD;7VHY{}TZU2=P zEsf}&}$mr8HG(aiJF^k_=x07~W(TVLpv^ID#VfQ?vj=_AazAqz#!n4tb2hv zP-v=)J=;7Prh&i%h!3e5kHY>_1j5}Y0Zda@ph-ytp`+g;LrH3v09Y(^Fhl5G4UpDp=Y|I|eZJv8g0gL1gR_0+< z&OSLR396t8tKnFjC+yrVbjpa|TistdDNDRyco|oJtbsPvq{B~W#S|DKKH`n9_xmhFy|NfJdnR&g^~xwkp)v5o>jA0{yqE#D-ym8# zNlu!N=wf(p*~uIo9ZG|5BH7=T?3~}AF4KWu>PEHuB!sK_?~M(rPg=F>VXupBe)Xr; z33%=3ZtUn4LupA^19Cmq?9KwYVfL_b{x_Cq@JM8Uz~rXJXS?9NQ$aFbcX}k`VHKj$ zbwb@mG(d5Ej}>sgbaZcuyxWq_Pj4BtDMUqG_~P#*ZkPlTR0Z*?Rc>F#c3sD61^h%^ zXP-UHb+H2hpxdj-t>6v43G&Y4@C@EZUr{&S!NT{WT^G$uW@uk8QSc@IZhJ4Ci{-sb z)AIC@1va*nr6e>WY>ZE2Y^QMYepY0m!Rwa}kWoloH%CD)wdt!nITF&uq7aITr%$ve z{P_hkmyQ;e=5KEg&+hX4H#3#_e=IQYxahj&El!#GA9?19UhOY8OwEO378L7FSJ^2IN zm>l_DL;){57@NP_oHrz0a+gh9QzbKYH2n~)EdvB828sa)7GR5^5IgQO?_Uv=y)s#Un?dsJ@Py|vocQS!Zlqt(3Ntg zE4MK6k-e^yb%Q7Io0U9&1Ueyf9KR3>f39it zV6DA%GO8+`?wtBfIbrY2eWW(?gaIly>#J=B6sadEMr?K88CwTTXG4EqtQ#=BlIAp$ z!1jy-gI`JdYQIkgwuspmc0VHGsmG_d1qgmJvhbCbt3^;gUlW)1OdZ>qvEsaNv0^;L z#ptMeFNq0d|7mV*OEMV{ml2mAboSOlTRyFlGyBa&=4c^Dg2tlyj;vAt0%|4U`^8{a ze1mR6|4-(NU&iIO+?*cTijt}aG)EFI1c)q&<*59%LC;6!FC654kHBmvC1}?K`X~vR zVl&uUSlE86sXTYl%F0L{2o*wBnYkKwHzJr%lobXCEi_U34&wZD^H&$}!@xiW%rc+R zVNj2dPwDr9`$=CoQ&;cuG-fBzIKV=x(YOX+=^ zTwGDPUL!X`v|vJII9H@A6Nf_R{HoECxe~Ca7}X-qPxT-1KKw5Ha9acV>x$delC6H`)ZA|<~jZ9d-SEjv+FngmYmRsde z3n|asqOs_I)bXwCFFe&D7dVRrlk)h_4nK8CW!F4iQ+yOfNkoVla{12-&2Au;MDPfR zIe8cZ{_yjP&?5iBP+83XS|l#)f{EyS?N-*}A3605E{DEK_gtZpMVwia(ekqdc>+zj zZ)uLK_>D4KJOVtwU20HMm5Fqreqh-&D9|xMD!(`_8fvOj7v=TqM@%*ILR< zYZeboiOKd8eY;2P+M$9>A>HZFebBsI8c_$9NJX4ousvv=og@)Ao!t zDSZ#+I?gogwiNiJJX6nJLFOb0_!*`*tdJ3t8j|fp>mMb^PivT(IL~^wd=m|8e06lN z(}#?7>5dEYf*K9LL#KBpisJYB5KpJ_Jlxof{p%08SP!LpX%6Ei8|HStb5^1OK>)wYd^HEgF)RsTq~aPxb{f`y3VxEwC|`!jCuRjKR56I%7^yONqulei?xWMr*!`WI;LXy2|Z zVGZsd#5%V5TRM&#oP14xFm6?xzdVd}UJh#R&iS$4I>8uHV6*d0zuCe{5I5YJaD|bE z#h||tqko7B8#j|mXNvVT54YkSUzHC1DspoQtN*cYw85g1QP{EKqx5o0gT9d`Q9aBD zd0g{c%re+CjifBzH-x==npXCuiyH5No2~r(7TJoR=7n0R^FlDQX*b2SDRLrx zaQSX@S)xdl;9EV6Vc_c^dT`75d2IEgQMjfeOU5s$IhRRI zzWmFQ6Z*_s+pRw8Cbzju(m?zdh<+1-I)SWFGRIN`L+yrBw!*m+-xPNTwDXn*gQ<>^ z9+L}k6W?t^#io>1Xv;=Fzuh#?{aZOb_n}|)>m%K7_M0LW?c>X`I7naRXZh z27dm=GYEWe?<{R7R{@6PQtxmgi&K+Q}$f|94B31LMz|ptrra48v!oc#u~(P`HHy_w=tZ z{XAlx&xdj!g05VpuL{+u?|n`Lr7sfS3@XyS%JYM6_{{-T`a>1ZSj;N01{-9R3~Ynw zbiaIlIjizoC=?&KM-P*#(I|STNNOcb?7zAIz*cCWYum`vwZzCsp+Wv%>EGaj;Da<} zGpA>5*w<&yTY1f2!-Sp2Q}`Y8!nR)h-0-s^hVWB@e-1Y{;rcP>=M(b-7uBEM+_a3k zA72l#Xi84wx~K!yJjEL7y{X)|Q`zXOnFg_j!*bh6MZYGjmDuXg(}-Tf=w zZkDhvX=y0la=nqt+Jt;4^~0hYPNN!Dw=(LzIeC-*)MCuqsc^gV7B<**zX&QSw|_P! zUM`;YILht|dK6J2Up4DKhVj?0yH-z_>a5OPM%3a8o_WhyBjb>~jxuwcE9 z`-x%ww)=+?+oRM>?Sf27!mCdKMD%LSD!7bf<3x{67o`i0tz-_yUhuB3e; zv;VAXFQ-2cVTeZHc*kJTheO!s{a&}nujL4Qv_LByw3RmF<-==wbJq2A`r+;c72K>T zeBv&8*gWj^7c@xlVFmm^k3=G$inw)HbS!WTQ&ce*C}LO)Dk!d9Vbuj3he6Z=hD*A- zfCh1>jpUwzvSNicq(9I1z~0OVmSxajt$sHteg8&cgH4lfSh?KtTL@$4sG`(q(z8kf zSAVJFP;iMWN9f}!_s4m$kMOa0KedsZ{Ul!LmIF(sZU*jEHxJ(be@y*lSexG$EDR^O zOYxTCPLSZxV8x0T4^GkIF2SWmio3fN_u|&#ZiV9R?hbE$=XuX{{@-)mUv}1>wPt3` zzFo~$;{2l%Dgu3hJnt(j=crM{z<8*F)tUtq_sa|)br6(D;U-k}(6XuyA#H-~{@rGT z&|aSafE*e?!RjqJxxz)qW#(zYwasdWu+J&{njqO_7PuhN(!*EQ$l20FPMlSgUEV`H zLjv6Xt@7oCzMCl(_{8CN&pPGF^jf9W!8JA|boqej(0cr9F;1x(v{8WQACLWxujf07 zL5f&L9rA`oAoJe0JMpF(AbBo_F%EuNNLODE{YD4pZe!av+vM^nEgvnYE>yhddMyZ- zL2Du_E6a#nEMK0Z{pzRTUP_MyI1p;etGGM*!hd2SXh9{guC#u1HT``)?v# zeyB+F;))%SRX-+OwmZ#xN$t;V!2wz0hhIu$+Un~_3B~Pmd1uQ(J6wXjzVMor(EWAb zBUl%jo4L|}9=e|~nM>MgI_X`F)N?mO$0rRhe@E)^VV-!J7sN;2#(34}uQx?o>-4zI zk!~K;w}m))BisAUM~Y<}oK?54i6*f2f{0DQEi(`AOAiBMLyrq2VjV6gZ8Qw(Q&vOs06m7=+#@NELoRz6#V@mG)*5mm-ue`q=jcAtj`CcjKJN%OERcS5kCGc1Wsy@Of$H zJhnUWREBKGZBRQ)`5;z8SdvVzKyP3ySy-gx@voKI#YbC(=@Qu{hV!*g-JbH?7dYhoLL9}at%nn6yagmICT!h;GW>-_54>nRJ(hpjLB*I-K;t$V$9GI8HMCQsO`856qAAX%F?{7&1gEFw3)m5ka3Z7ntEcd_7XdvALxJXUCkjKQ z9Aa3+00i-ZKR_UINW(tl=N%_1YJa}Zq^0BEI)gNXhZWCpM90@C;e)dLNaQP*0mYpN zjWUzOCyhhoyn$)i9Qg(@vSvnfe@hLoc)j?y0KKbzq%t(VzfDf-Me51rW=Nd-S`Rue zUkf)Mxhm?4$bew-_ICHvm%ATI)-7JU9nkidw!8e-XRpaR^Ah_%xWc_e`>s`9`rfBr ztMci){~=_=s@f9Xk_H7qDE{5~{kWfqUFY)+k3HXb6Z3We!rOmmo#)v0q2t~+bz#wp zSO0M@aPF-X>W>QuG`vqV3{!>^C$c)$*4DM2_nmMiMeY@C2Ng&NAg}d$Gs{$_S%J~@ zjvxs#@Y@!>D_A6PhRMC&LiADaJjCxV#Bts%IWZmHJFg>S@K}PGEljr4A-$u{|7psXsTe*{FrCut?)WJ6YS@dO7lo0m^WmgnK zSEp9{frh0I^b3(Xd5@Nh z4~8{M1^35_cvv|z8P1E`9W-iQ2TuKJ(GhwI^rL<_J1Oe?PHzPn|0<+3t_eNAgTA=9 z(a#ku)5Gt?c(mD8wan*_fW5AXBSh-ceAyDjyPV~PypeDFs4BSI!&lCJs;4CDuI;s9 zF{s*6_p&9I9$WJks^fPBu%YDjZDR_(2zxF{E~?}@)!Fe&S3i$f8DZCZ*iUE{uD9M< zm%Wp)*#gyEbH?ASKxGsmqAg^qMLFtg4^7hJEgzLJ)~O%05Y??F3In^iZrnL%_$9F@ zyB@NZubj0+LUA$R4{__@xIP1TQq8TQx}HtXIU9A<$I}G4-Pz3N*}y8d)(e;0lC}vc z&;*mhK?L4cyq&v)cjc+@$3MJ$l&WH-u*623o-x|O6z8a)ofJ({-;~Dw_2{;vrD8jg zMo)1poVuz;U^BOT;xIyHB|{j;8K4P9O{0t`Sp*ZozNA9>#%5x+_3}=b=$7^31ld^q zzvnzsED>bCOc+XD-gO8#FXsu^PfzXX&|S|WeB1{|XheVvtt?#h8jM>M6mjGk-|D)T z>#UN(I1AO{%+1abcz9VI%p}t{U%F!njocN$CJxFQZ#PxTNi$Unpb(59mU}e>hhUf5 zDS!WT@pBxOA_e)~Qjb0&t^Ady=Z=sK3hf>R=Z(1Xk4e88`imNbF@_eI)D2D;5wy|> zDN(43Wbro_b<^PY8TJstV5R^V!Tx8UZnS0w$i&(wha#$&Egg8t2Uj^anzvk{~ z&&8MPQDBCC0X@_V^3`%AJAO3G#NZ~;FUW{r z9M9{Eh1s+pI4RboR;;!s`_@)~zwxf-WMG#YLG)Ng*zB5ab;cDO9^II69au;ZVmw67 zYdP(7JS|iM0O;ym{e4;HORlxk9wF;{>q<}U!kn?q+S%(0! z0lHAeoUm@}*{EIuA8k~z5$CMSjCzy~#Cx?usG;XzJjB${d6akG7Y7TAu515463h5g z{~w7FbgFO7PO8MK$e{$a)dHSDUHtvPQY2#suirE~m8~Z~j%zo-+X1A2j@7%!)zDM# zUGvE9605)ag^@YGJ5UGA+C>?=j)hNQ%4yMNM3(<=UcPgj2At-|*rPeI&KOYJimMVm z#t`_uDz}CCbQrb{ORO?6IVNNxIe9Aj&>Dy0t?6z3?L z`$UsD-nf0a!ziT^LzJOK4m)U8VVZU-k;zh9Sj)vq9q0G=++_^o16 z6p9Xl47&>3CZ#5CLZ0+?+|wvg4MK%3B5*$=Rz0ToJ6W1UC5&zwwGVSqF__{zGogzU z)NQb!E;U%qC(z33p@&ZP{RkmVZ!$&BzmeNG@B3q^H%n;~7)6gXb}PS8BG~fq&u%8c zLshlA6R%R%ChcVt^A-Q~I<+(0Ola?MB&*l4Tf1!g5d9Mu%hShncjN9oe3cLMXOf*U zYW!;S{1le0I&1Q56lt|Csj=lQwF&T`y-lXmb^J`Hg6RZ8MssyW=E#VFkLIQ{!HL;Z z8@pBq#@;%%2*M6#zauRbHY4_R|1nVQ{3sStz7;c%`Ec9Uj(e*4dG$+gQEKS*f~kFs z6xQ9338LOml`aFKwCW%D15?zEk-zxh!~lPRCvt(5?b07Xy?msdnV%iG6>JAv2!Uo@ zWDuOkI**1tE_DqkM}J6M{-Jxp^Hn?nLt_Da z)d8==olnE2uH8Gt?)d~jV+!jrn}ye>@mI*p2FD8x(7>k4?|IHoxYv%l^D|P)4ae&R zhovwIRZP3v@q#JLd5}JtjwA=(hnm{PqLQ=hc6IWLedF+6$F!Q_;1g2BoMTb)-YB47 zGMT_-m)^su22KR}cP>4`=i)p5;6m&*G#CKeC7L(3J5MBl*FMe+z1x{ODzQ%$iJt*Y zi=wLnB%Eiqd!l-a3Pt zJ#%$CLgoy-Y!QM0zXgKHt=@FqbBkV1nNokdo=RfVMUbab8vM9w`_OpXaof(}tKx%g zUKrIY^FNggo&vCcDBgG$t)j6NoocNOx*lr;wi~&Q5wo81A=igf7sO~aJeLTFF@tl$fvlk?zw%G9GY$!Y_Tv74oXlAe*@pyWTS z*bqGiU+30ujgO%Y6b7(F((bxSKoMag87|wG~g!&ck+`Axyb@bkNVWxau}< z;(*#V(nf9YeXYHbuhJEF^%weSyVN9!_qC7}+C<^3dp&7BW#F*0F!Lo%;V6GL;YS?X zj-T9}hijv|!tSab)TSmCaoNfJMh08!;v@r3R=x|P%DQH$B9+{fCSH|Vha~g#EF6hs zFY^$A>d-GjE+fGsNogW=HdbHypvri+^yddbYwZAb$j>(zdU5eBC3brKthfWb%p8KQ z#6gq^mnP%tFXi}c4o9J6Y0wFM|mByzaxc*pQdZpg~7Nz=!NlEc8A%V0% z_AEQOwlEJ*=^dE_n6qHBR0gSMV95Mei^19tkg{~2v4XfSBXVd2`o!etny>Q zlnF8|-S+H3;eHX z@Cfq4cGQKC==D`!G(DcQ#y=%@K5e5P0mU{_CEDJ05r%?+I_%ANaU-hot^lJZQJ?d0 zLj{Pi4x%IMf?d4{Zh-=(h&6%VpU%)-VSJMGHw%rgfdgy{l4h>*&(2O!?GY+WhcLf) zO-#VwHAS01a0LQCoRnTo>+pgi*Wlc(?QXGdtlMyC54Ua6bA;WpcwDu(9!`1e;aR`! zD%Q1YrvLk8JE#|@`xS%b{|Gw&rjOLZeCGYfgpoV7{HME;I zZ1gis;JDhfz<`2sP11gU=4|nHu9UmB$;$Gs8oG-H2Fb{_)1&i(|zsz9xf~^NhbANSmxnlLP;?e?&imJ=9O@>6JD^YN*+ZYcg43T*p}?u;fv=s~OT>7-{t z70Smx4Cc7V)Z%MUD_SwazR)YSu`;ovST(|REkdzvF`(*ki)Bf&nJJNl`dwj!4if8l zF759R?11u*@DmfiQHT4f0xe3|%7Xi@Bn$mm3N%Hg&i|TG1#8YcIU(NtU#$fZXIBKJ3NYkuj$-P|l z#116iM4+-VO&b(4CkiT(*1w5OAP(_G)eia0=K~x7c;)tH%GTgp0NAl76<-o zGSLq#!=(yYHe=~Ge!@PS>$gVI1aJxX%F$;JdsE(xo$CWYcRT!@sY~IZ+<)LAYWuM* zdPY|ZG6lkfQy}3i4R3AvL*^>X>*<{G)p7n9H3qfpoj)g1qZ=};=fmPfEdM=T=n`b0f~ zG^%PP&z(g{ae*BO?V(DOtCjMcvd!f_1?6y?C}>NaG$z4U}6 z-3v<_1luE+-0j4MBy%xefTp=(WLFFeAWdIo$>HnU{XLmk?ZH7S^wre_RCqJ!-IY1V_ zm9y8P2pDDaoz&2eo zw|ILLesWW8SasLDYg=2OJ+neQdQn9`aMt~jD^Q@8F}CVr?sQJ7@{}FUpGhFE)Jd21 z9GD$-GInoNGmkCAo89CJ5OKi3?lb@-;lX)Mms&BbHJ~OlR08sJ_D7cCTw~>a%$#ZuJ4`VdY>UOk706!H3aga7c&AWa?3>3ICaI){kcFI z0`x!Q0Bu)+aT^^m2w1SF?8l|up@4+2MFhM$*yT=bC;_r@bt#ZAkdmeYyGy^Y1I-`n zu4n(#XJut&?;Y-JSv$2vF)WTaBteEs+|I0R^t10?#*K6ffEa)qOPIii%VLp+zJ8G# za5Lw#0==#Z`~M&-@!L(_e;nlJ!uk*pSS4Qx>CY3E`%wP8K6ckXqysqjEy^Ub4 zWOmz85ytklU%o%-N?CN{tE@LxoY3KE#{;`TDJl5jG`B)`MygWw+gie>zLGhwj~2Ts zVj~g=#wXJscGzVHb$dvm;zBS37BmO3A*AIK60sqMi;+-aj<&O=Oz$+?5(E`Yf;q95P}0Zt4AvHmuSxn|lVT5O9pt2iV+X2t;zFcFFGkMEL`3z!_N#tb*EYBC;Ec z%we{*3lm1I8uv+w2fp1oeYtl-J=AcHvz%v^j^HyKp0kH`q9e_*V{y_5#8PmQ-R0Rq zeojrRu(J+5-W}8i_o!-kwoGOROd)FeOqIym9PQF43(7^D!~~D}C2UXdvyR+W7@2za z324K?4|huWdD*Zp>(MUm`p1SL&)|mSyNa9CmK4&AgzE_hax(HT-)6?aSf+*V9vpGH z<8J~eGyGjXok+Lmt2vtLoro{QXpSomn2Glrz`5&P8faydS0%g+6kp)A_d)zzQ=R1U zd#WBot%W0KVT~fTx4+sy4UBxK->?(mv9gmLwt^V4>2NBx)VjG&I#G0bH9dg%-FBaG*?qUeS6IUPRoRF-cjrooRcXa5)}c`m4}! z{_l8n;c)k@5?jq+FY>hdy2 zdh0k8JDaVZvv^q9TIpRocJQ|>ejCA26YD1BRlUfcw1qM5Asyw8L_sWY-h>Qvaup4Rb9R$b%F>wZX zGJJz{yiNQZy@bdh?_vL+Hi#$4-7S0?z=GgCHmblGY3 zy81PtHD(we=P^GXSu)o(ZW`06E|e4t>+VcGTW)@2cpUKdd(aWNKBN9)6kS^qmEeZv zdjU@xF^fj{gf;s)T4BJ;Y3M|G#iRwNIjgKT3_^^2Yr#g5?e?H1lU4KYHtpj0s+jV4 zi+Y%`Vq_ZUHIM-Dne(S+(A{viN5tXx_&hY$9*ncmGZJY~ShOcvYLfW;GYB=6f`IC` zA==%=4;hr&82NUNdi0?OVDw!?ef`b`le@&%$%K-Ug#}1v$Mm4PT_wq z!l^MahyY%6BM;?xe)clH8{nhf20Dh~Ci)q7>=fO_s-#pl58LT#7hE~@y67QB+TeF^3fIYx2 z-IO{1vKqN>ph5N(UpZK;9g$YeNmq5k0La9uX&Y)`c%OII?QS1CXc8ZlVHe8wc+*of ztsE^CSLbtTr`-rrbcEqps|_+M<|C-|^`mS)VZYlDvo}1_Qb?mmU|{Hz*(lV~%j>Ed z-eg^UQL1nJ)%&LR^dGBxJ~cA5(%M>MB=Y1d{#W($Ay9^=)}A+rsr8};;iP@Np2))m z>39MOqW=7e`v zY+?`qM!H8{>Y75K`NuUYl$i6@pT}D}X7gwz{|{?r(}p7u3|-+wk@1u$-(7MZS3D0_ z?alYbOYZYu&#xv@{J(~)&tKPMR1sx>;Bv9kYs~YPaS>|m{oVSh$;ryj>sn)T?p#Nz zxGDR7%O!2?Ypr5RXQJq6wfHOFsEncK@wS+YF}kDyj7;~(3IO1qSqs=xG5s+bXB4;pi)m@-LhP!aLCs-dU9YI$1_<-=iy60DMgm^Zc9? zc+F*6D?S+%ZFoZPFXqB{1V3HZ_pe3Zz|VrAhjKmb53AN4X1?A4nuB`)mLS)rtBaECRvtuU3pqDz zlqmH65)RpT3Hsw`cv`U0svs>NnWgNsn?~$GtITi?P#=`~)It+ur==_J@{9j4^n-28 zwzir+cSU@Bk?Rt#Sj5IagvNeBVHjyb;b?8*p>dsm%IT6z9YU3x)NNkx`9z1l<3o$9 zlf;`)r!%o1>$$o0Kv5d&6ltG%^Q~YWLW@daMPlw-scipD&1Txa7?QAS=So7(8pw%{ zk$i16bZYVquFdK@?uC)l7-WS`zGsrm4wdM;b7-d(ECY6;?Ku6q^hVV69J$7N)c~0; z3QDU`<)>OKUC{~2dJW8$lex-@c%dviwQXEZS_*Pd_N0;2!$mmTkc(&J+eZrO6-LD* z1ib;+2^f=U;rj=u$wrJf{nCNQc=y*u0Gi-{_58O<{M|D%kQvX>EL0o;FX+SQyVbis zq~D)WJT~@ruYWL0RO6G52s2Gv7EM1%6uhz-b{qrn;k@_De$`ilci;qaU zt!8C;rs1IX@cHU%1Cc=5Om2R=#Od#*{W5f*VBWya*xw_^gX@(Di#H94kJ((bqcJO+ zB>Tb{F=wsqM|NoIcCE zBYCC>%jSFV)J{U}G>g4mncn=#61y!_7=4>MqixV9T=jbh98WC&G*akfFim~IYu-0{h%LZO^#xk;16yi z(NERs%O8DDu~1=jDP%_O*F2y$o^n!bQ#xEsLE<|e?R!7`bk;R~mxN9UL+ko>4TomJ zq1{;Ewpt%=^tRw=G$QUZ>8f+?GNfLY|A;EKTk7RIf^o|=>m8R~{2hJk?dPgev+aOZ zEN=gdQj+&TU5~wLYM>r?dvOnONI}0u%Smwv?wZcfZq&Q^`NQNO+G-SoC_7PKX_jVZ zr^=7l?qLdBXUM~4(9=jUeZuzry!YN2Q2P&B=)9|d+jV5`m**yfD#eqj*@dQ~2qMS7 zg~gb9sbLie*1px(4&Gje_`R;Fk#-GQ=xsC<5fOR??|MV1HFPr*mVdU+O)ijzx1^h4 zPvEiHHmInD%x{$kUI%aVsQ1SL10QgDYKer;ZJHUDA4FG4(CXA7yfwO)Lb_mqG@fK( z1R2hjef61};FgE4F;1oCbdtnut_Z{@TK=mB0YbE!`89dgH9OvweB;B$A8)TZJD!gP zPOq+OWW=TtK*ps=Z~j_N<9t?+-o{P(5H7SC&hIO1P<=1g%yL}_=+ejN@%+>s%-E{g z>um4@d43qzI-Qv;0T|zj5UhTqEv?z;`uHtA46S-7c07?CRBY^;!R~V0Fqgz8Ch#7O zKQWP2i-Max2Rn(WM_=}py;ppMK#6SQF}k6@b>({OY6Dmua7b)~b;t#j295!KKvcp> zP1jj^!(^L?fjIy){5ceVl;$e0<-<5B1%r^Dz~+O}vbbJMt^w|TBB$@%U;YM(cw0?{ z0l*o0NDhDYC14*Lkq(!eoh?dNs17+1z|PT^-`?N4oUX_m}y5)8O(}o#b;60Bh zub3_D4fn7vUV3k>2SA8j8KU=}8#NM7EhuiC<%C%ZkA#W`ir)vKHfL4AAjHS_=NbwZ zKSS2|?L``%dg@~+u`W#U0YJNUJ2rumJqNt)@;MR&Im}@Xw8;s+ zSL-yVn9Y0rn`%Ya%~wI$kcWVa=K%s*l<*XKj9IUn3<`2`Lq)_)ZWipSVx@~Pmj=~9 zcr?qXKiS*8RG$`6UQpBwF?fg~1Y(80%okFoe{&>v-Om#~?#hVR6Tymh!ryUs|Icl> zbC~zjiNL6k<am^`XnIz}CPv`25<&Ceo1P}16T82NdPWf- z6PJxl<8y`nfrRoe45i4POrK8HX=}WDg=}>Ui9a+#${+JkJNc5h5ysh@*FKKc$gwL( zFylFtEtQzNL|gF~<>z?zqRd*#IoCAW)8XLY^uN6Paa>QkcAV_{TLU-mUt_yEPP=zf zTG;L3n{(%PbxK&O{bM01V(9H$$A#8`{2!1fj)WaPoqQ_p#cwv^{kpka>faF}&0zeq zg`U0j;J(Jm(v95VewNK4c=T#@ATCdC0yXTixmcfVWr=@tqOydYhoL*KOkRJ>$_7X($uT9R98WYt+`62QSC zf_O9T;cSJxH3Qqv|EX#LD=I|er`ZmN)7QNr)e8RXv;;nZ<~|;)hi#azciN$&;?!fq z3lnACYmLGJ7aSITAHQX__5H^n9dRoP0B7pIZ(=x}dfWy!!654cVw$x&Svr63qH*p z-e=q*ws>Y}&E?@dtK&}tXQVs%*aoMs`{6e17fHyXhkV()Ri!hOzM-DCw7itZ1J&%1 zj=7wo?~KY;>-3_vk{nn#gaB=PO)R;9r7?(4E?#4L2%m?~Y2%K_$4KOSXAQdERLSVmCbOkVTl96wg6B z%$3674ebGGwkULB*&9nYF@Sl?d9U^c{Y`9*0C+pa8D`?%^GDRBa=2h&_qYk0FZ5)P z0#aFYMoCb`{He^4%98uBQ2KG?Ba5r zyCN~kJeRKl?cEEda}j3C{^^U%vYbl_*1e&aXwliMy*@PFJSeRFZ7Cb@?eCOQ7GuVFJC3HFJv~Q;!6S4LI#g89P#QFyCL{3gDy?uN@K2wJCTs~Dg*Z8+q*NjTuy@0$?PZMR<_cu6&6~Ni^ zRa|*u5ZW_A=^vz4R&BhFj0qd)D@Dajgp*O-nhfQV)sXeCUqc@QTJgjkf%a${!(pA!Kg_0VZ>3jiRiJ2GnKy_Qb{TDu&1s~@0i=F(V^R)n`*0If^;O`IBXW!%8X`CPo z;a3P1gb82Frh>VeYqr*NkKX0l(AZI2_s3h`b53P^1Mpz(t%8BxS?GC4+r(H;Eljr<|Fq|%XtD6}zKzN}uUK^N z?VzQdKkq^?y!)e8HXYt&2Kb`$ZaX+fp|~r8Pdl@om-kT1Bk&z{^jleyI8*@!QGaJS00e3Tj^od`2k$fpthK6s zNRJju7YWAy@IrjQcc|sRa^CdG(Rjz)tb&^nTUeqM|2nlim0wcsE^6b~AT`qYLe}XFLD}c)35%>*G z$nWHaQ2a=4KQeP%^|?)yDV^0FN?*C!*<#Q@|9Wmu7@qu}V!N+Z;8d)Nk}2}v`a-|r zV(Gqz7?v2JOiJg=b(M0ZN}`j1EJhjOEUHJuHkKGF@rP(hUQgiRzBb96zPmn#z!6Db zKPJ2jz+~;9^;ZG7Moaovi9Sz{E38oi48;G3QtH=01Q3Q1-#x!F`B-*#S(Y9%LpgAe z$O@m)z0jE=v#n}tMQY%-5&MO|KRXR5r*QiV!a6gGvMWqV0 z23}mB$P2?R!IbPn5$iCTIrxmZ$=X`IHOO8*Ba^(-yG5e--qbASBNIHnO(DCDYhUL5 z)7g0ye--CK3F=R=+>S3ZvF={`hCbTAcEyF;DEdvB6^4+(9K-JKnXe;G?_t;jHxD+E zznf;nuSTmve3K5FKQt%|JBlvETDr3=#I8H8T|R*piy7GJtf#sq;8r{&{OF`_$c)&9 z(W>os-+nmP&XV?zGGL1nytjnkbq}ABoIJ=pRQ-Q<#ej_QTm!dI4O82Bkhb-HEoF>n zVBt+ORbp4%kU=^hAz51@n}*LEKFc4^!tAf33^1y(*(%PEtpNN z{3Tb!xS48g77Sx+gFs&^V>Cb&S^1tk3q^Zs$klekKaUz|W^Cdngf zf3HVxMEIvnb_e|HaBeJ@xRG+L1DP!>sI{epo!2xy#f6(T)AF-X%c>!8_DLgiVMj~n z8|z#h%oIon(H}&}SH)cS#dpSfbNQ135Tl%S>gj#)*9&sAoK<69zif9FM8~1hb!CQ7 z)%57sxGHqWXNtA+I@Pu8q*-qy`|c+Pj(Tki_|pJ*02JiISYK5*9F8IH`IU_H{3GMB zXAcFxJO?1-T>e7FBxZiCLYV1*u4!#A?S*XPh~Y7+FFW=h*RBk1DCK@I?xTtZG`cYg zQQ(;f_6z~PB4TYqPjhp-uTHPCU-q*%r8qoQVBUKDXr@@4AIo!}@Ye=t{4dqAA7%)z zNU6}|-%rDBmV20_f5J4OuWOYF+63mRKv<|2BJ?V4u8m=gD(8Ijsl&rpO6Py9{)9Yh zlmb!wbMdfO<%=tvj2o81k0Irw?_8YLpH2ewd(Ojb<>GxwWkoq?>5c_zkL5K-x{^n9 zf<5)(i7m%_&j0FOzi@}Js={7_tX18tRO>*-H;cUcS#R*Ti+}(c($H`=f=B3pYrzp) ziN(g2Ec3MV&0xA zb?SWbsGr1zFW_b+AF}R#5iE%54~T{^e>-}Do~0+dF?F7ln*&AKW03K#nnFdJqqLJahe@?k+SuhU!{$D07oBqmy-G=- zkoZvbIlX5#UHeApB-~c%{MrWcunZZuHuCn%-P`25IFWxZDVNy~_}R^i?uV`yPe?1> z?YW&+&|XhLQ7tF_)!&JQiKv>3KN&k)FMgrZS+-OP6i`aEp@qCwako$wdO+<>Xi z+I*4RhQAaFnbVu+jO0@K5iHu)OLVV>jK@D}( zRz$Rgp1=5ZTz*W}=j|yCMwYbH@Ewpe3`W#QgSE}`+;7+a!Cu*$zee=`Q&5ax#TiM^ z%Xq8vHeaH~{NSy_Vhna{SGh}ETvw9izJ)qiQyOVOLBV0mwI@YH8mSA*Sh2ubeXhWz z!{vU#<6y_yg}lK-qtR=Q;=R3a-n7qH1ThfI)fBrJ5ArZwpQ5@gejCYKdSm?rTtWmE`VR?BpMY?yx$!g>o;Z~@8eSx}n+PfB z8tM)|txOqhr_X*gE@Ohbbd4lISg$XquUQCtZw|N1xyOGf`yRvt#|fxA{yN%u)8qHvp3GgBkB7VKAEUxkSfMJr%_ZM?@Rv0$LJN<%TlU* zqPD~uh_wnL0a4N_K`8W+0Uj)2=i0dEL7yCBtzzk?rh9Sy;~71|RQ{x7H~~29toDfb z3ozN6sG8&NJ6bp%kVx|>)$>J~BCoHm3NG?};Oc6XS(6A@^u(-HY;2n~sTj;}yWpaL zw<;5_RZsN)kBopj3@J6R>B3Xg&R#^Ta8L1FNTq8-siCKnkI{kw3{RP;>ZyvhSCw)( ziaTiqc{?equhrW|9L7|^6ipHYw82L0Jrg}N42x|e2Qp)xQFU3CmWusaCip?tQ|+FN z|Ckxl2j5~D8Ol=7xMn?5m{xyJ5aQXBpLcq1dyi>G6|P8bGSOd~sMxlXYiLH{8`^Qi zO$K)hK}2fzF`J{>wqw&6SMA6O&L&VOFKLy9C5P^g%w(w>o0YRfMMwp zMXoXBvi1Togn?f`IK~0@QaeN+h1KWnC{$Tjyw99yKc~zk?KZ|?%XN)QrM{CtsI8R^ z9xfQzaC+~9Wz0!&0pwn)JW_d@k+>4ak$mrSQ45-eB(P1xR39ehyoVU5LKB2`kSH%{ zzU%F}9e}3A6n^06W2Y)9rX^axDP~A4GWZw(oAAF;RmAj7%wc4PR&8`lq+uuesIpkc zw}pm9V2Y8F!UF=%N#BroZ%=alQeB-X?1@*7Nw;TBPD~XE+bqK+hv4~LSooC_gfbYX zBJ5_>)H1UlSw`4tw~g@B%b{X~4Zn}Q9vJCyP?t^Tt*A{%ElF}6u(>+j50}ur#e5dh~*Ge(|c|L3s!~!Sh8M9 zr-mr_{;;ak;6%(``CRNjmjk0b&OLJW%)|UsU06205jfwkxQ%D+*JV?bKvIOIb~aUb zNB;+$#{!0CKqjn+Q)V_KJd=x&KCwX)fw>r)@qBFO-+EHOCJQ&dTG$JiGqCeEvO4RufO*j`RI z<7!hgQZF*9@m2i8)pkVyOrj?g{|$j|BYwG7h&_`C?IQu75ih*26r3vvLF!q`Ml}AX zB37_6TYV}eP>EV_blUV&rW__~$gX=DuUv8!KJRh6GHMa%d82vmNeV*v@lWe z^7<}|#CyfU3#T5Yx4`UG!S1E7@N;-r1AP8x^O^m4;AeuOzOVNsv}m{>X88W5+zcq1 zdK51eVOuD4?alO9lm}oxX!?w0mSDLJ-CsiT+y5H!)!A>@-EY|0%6I7Ou80fnN0OOe zL+0}7-sHe)wF>>^?RT(5>ANetZiaP0MoBg!G2^IBu_>{`h}$_Q*SZbEdPx7;h&30V zq^zhKky1BLPA;^hOTptsS|}590AD1dOQzI$8<|rVzc6Akt(u2J_EDjpRJg6adp%cD zIR4Kz74o45^9c4C;bjz zyJ?MvSAZdzrB6L(LRMRbXV}~Pc#0hv-A)ZFgQ1yRW7ya{_T?+{Mk^o_=))*>sY?jb zoT>l6SpZDMn(S})qoY9<)y64rLV3z~dC>LVCWyFUDooAMjI}@23OsIvXXV`ZxwNih zNOi6LXS+eC>!uIQ&ka|+i9Qi#GdK8u?HC3&s14-}p7RLi%j72cv^T=(>->qB@H~l_ zoNBefQe;B$FA%?(o|=_fjX@g#+`sr*aI7dRcfd>*PJ&O%V7!=Av5#6eHdcvrbS5$; zq~TV*5(LkC@J~6m>f;T=#!N2mW%uV=mYqKZ6EZ`hFvTeh&_v=Nr*TyH>Wuzj*g?ie6{Y`YiWf&%i3Kz9;iV z+lvg>VHF!IN~Su-#!?2oB#>m_J(kb0PELEHvO8>ufJiZLaR<|ODOhpcDOR9F)6T+#Z=EE=Acl=)%(2@w><^CJ zhu}y zQHBos1W}LBN^f&<<&$!Pe#)|oR@DAjPuY9hjHNi-VBu}U6C5mKiq1_QB0wZN*HZv8 zb{X#beit#wYpGD8)J2R(n>l2hBTo!|$Kh*-Ak_zi;$Zi6uh7=mY}cA9Y3Bn>^qqoq zW(vC6f8cXd-R6okf5sk2v{GLi#BY{7I{rYzY0EXH+YHeh%q!GlJjeP?qa zn$TQSQR6w*7h?QW5&`|47BNi2MV^kyS$n9lK48dR&#X$pB&K zBDaj=E>rc1Y2&r(8VmhIaT^cL7e|9@niWmH`4vaOK-ArJ_V;7)K0ZjHOUy9I5ek>C;_xVyVs zaCdiU+}+)s+uuI-oW1Y4|JE4YqeuU!wN|}Vb3Svz45#*SxKer+Nj#&oTM}R|Nr!8M zqV~y@mJAhpQqEJ4q&AmMUk{0zBZM)|bZ0;SdLqSr(uYS6t-SRbg6)y$1dTtq!(O3fobOiesSUWwsLzx zOG}He&(J5h<5mA#*sAZb_M5|^_FW}sjRvObGee}i z8=KLb#D8}Ly!Gk&wf54Ss!VQU%ynjH)FCmR@o+;kn_UWj?-B3 z(1WJd*6`^;#+GxElD}&{fMoK>N(OfR%rG7&=rvKx%Gl$(0IS22zkFn&R?nx-*lXru z3pt){x~b(nT`{91{wa446rvh?gAFu!)4&Ud@v_s@q$4h<$>v5m_>fV~9pNTY$PiAu zG2$=Bj~h=8{tK0615JxnsY~{?L&^bvx>31G2HIo!t9F58Ayv6n6Dc^io$a*5Hrj%c zcsZ`jz|uq#T&Tg#L~Bq`z=wjAmLj(vSHdYSFxtfLTOrx|y0Me-4=}L`B1V3IP;U!d z=td&1Wf@EU&0YI1n@$sM5`s~LSNcT9QsVWljHG`2?#eTNYRvGS+!p@uZ~(6yjDo?S zVzq~&I5Fncty|(A=h615_fB8jS_h8!dtp^Ir=u$5E|x3U?1gaV0-cTmZM7kKieITd z*bwD^=^0t`!a!A`soDwni`AFk>{oeTs83H0tHGb+&=k#l$k#JlM=nT+gebP{U6HRY zgl4Kd9)?;nmHP*n5eS34E|NDK@SF%IZsVdi*}#c_o#s5Rcnjh7W64Fl8a)>(caN*R z>xVdcBEBPD)cbrH!rx|HL)}<97lJxKxarn9IS?qQmvwxMq>FY7mGsWP4 z1w`1=0dI0qnMsx)$2x1mBLKe=LxZc_;P26qMpw-XoClxC>SqOtb9cc{jK)zyqXX5v zwn|6leiN^};;Q*g&5VE@k|#~}R{)=*`0z!}BHpw;dBcNo=nBn-WV97W(%Hjw_ zPfRR+fh-$7R!8VRmN$riN257~p+<1h!|lbyJ(2NoL$uEqv06|Gf(85jaC!DjyNt7p z&)QFa`aq*jY}9;%iriNw$5MiWquv_YgdI;>KO}ot(c&zPpbTSll-trGi-|$N9WH7k zav-UdHme`Vq*)p^SRz6w{IvCPLxB#fEV#hQw(&<<@x1u{G+WifO`)0ohqs)A$iC=y z=)Sz@fRMNC?2epLwh~Hlw!3lcm=!;6CP_23<2VXmwh#g^P3Ej=$Wgmcm41OqGI(hz zt}|W{nsu!DC3$vghNapWCr%U@Iz>IRo;#sJ1DEY6mCk{6AINjxI~dn*Wf?Qy($3`{w(~E4%ZMGrZlM1wX1#nq-!uz!8)YfF`e z+mo2FT)j7$K>n z>o={4I~)*gagC>k*j=6kT#5Uf^a7WoV-t^CpSve;0qPIXMs~7V&4%0kkZsRji28>( zB!keIUTW1PWs0$|ZJ5@T4?DG(Omg}68l7>>s5~?IN~KWC z2-EcQ+mArVPV*$ueTt~nwR> zt4W@hx6k`9c3J-XK;;_3+Z=Me> ztOqK&xCCapsbv}(!(p9xU)*n^{<v-u=w`$Sxr`{1(kE@KI?P{<}^AgEteC3$Y)~9{KK-#zp7I!G5}yhDJn)DS%Tig%NPnj!N$+m`j7fsU`9A$^Ri|M z?(HTEiSCZ6&(%I}TpKYD3bsADqCw$db+26wof^w?BRicrZhy>jZ+UY9WBd8%KRbTm zJezBc$GBAFF4#)Q#DcVr?x{(4@k+4-$hM6==% zBU0|zC7Vzt?XX#;Lh|t8$PdP-ggnBoB@gkVLI%PNz_iB@O&9{L3^jy80-Q9Hqw z;v_DkR7n+$vWMi*yJB^%7t{f|jhDAJ{VoQX#O@b*RU)e)GI)F>}MKsE-lr2)rm;APl zZK{5!)?CA>nDD8LEmyMX$LZYE4EMEYR*276U#fi@oo}+As79HZ?s;1|?mmZ!#>#nG zR@#PD6f|qJ7x6B!u{)H_MlXeb>gS;yNuY*iOtWKSB<)=RE|F(e8;d_iQjk`cuXF1cHG{y!N5YU=V=c zVZS;dD8465Vt&1*b{)L6$whS+f@Osm?xV%ow}lCNJc>eE_u+`D(`ix%oxTFtjqRBD zn9H0iArjHzx^{9f&-t`%^U6qhDw!y^#lsz~}Z6-K^5 zJCjWfOu2$fnSj!ci|0=9$USsc*4hrAtDZ?q=6?6X;3WXSc(jp^T1|2UpiraQA?YvL zx#X+`!!uT|OgLnRSMj3GD;LnOBj99Eg3{FQs6uh?$vq7rjn3lJ=VK1)CmZ}DUS#hS zwtmI}I&Gm)@E%c=51A8OuQ{g5r2vc(bf=+d+=2;=K~1a@*o=aJzvXwG@&WuU*(Vn#&>V9qCQVfjt=Ggo#)OkCOz=BEC*H&2wQEH?|4V4>1D za{&B#%Rh(0mslgukvhoMUE85{i2mbooaGyiL=#{V`e(JVQ~c358>1FMT`6-YcB!q7 zy>`9LGEO=$9kGmSdLGLv!D%4bO1(dN4FY>5y(1tDv3NDKurLo%gBBzRQ3k8%6A<8H zm8QW%Gre>{DU7stfvCY~b2qkg7V@9N$3Wh=s`x2la9OA#y>Oddg4r>wq`y z*UHU!nx6V92H`G~cQjYCl|5~x(h1bC<8VY@TRp< zD@cX@?(_ZR_6o2Fv*)@h#>*Tfy=H2v>~zl}NSD z2+UWC{pG!;LkA>$I2i4{=Abncro+q#2$ys z#<$;-5DR-lwT0|iAl*~v{!B!8ZSn-@iW>N6KctM%6b{&Su#7=zPqb314pI!m?rYpI z3=+uDSA9zIrYo*~xtQ94`yxX{A@!%%1T6D9c+=8` z3c{;4Zd)bBuNJ2IB4L}iSa<<0xABz zvHfUqeV6n?ey0os*Pyoq1peC_CL*V>^LE1Z(;M$-IoTsMx};q^4jax?gQ_+dBWBZt zPHd*j3lcs=0`O>@ym$jOjmxLG(m$-w4>t`gjcMN@9E33az^B5QvOUv~O> zWA5l!*N0=3*qd)w5hSKrhabZOrq{k7m_J|1HqI%`DK93+{Ke51Lmp23N6JZ88o78q z+b@_dt!T!Dp*YUVqk+3ESE~Kx%fV8ktOzEt zie-ahO17ya*eT>)yZ!_^kLp6F?0>W2+9Uy zV}uC9YZ{U@L}Ap&9?{clq5@$Mt-PtASL-WkYLdwbDa4y4z!Zu_A3~E{2GKo7LiY=E zSN5~9eVSXIWdbivTh8Z|=VLRq^v3bG<3^Flic%|!0-hzWbl^X3Kq!-tm+HyumFLx| zCC!KkU*eFbuKbZ}lo`OJD$t*UpXh=3$RZGcFfm}P$xuBQ3)+1yNm?@h#lPd~G4f~P zYDhVgD6_p*J7PdZH5qdHzOEidMv69!q70?^)(OzrZ?(12GaOx%qCDr*3yqg0y4HFe z3@{&=@7f=EH8=Cv5svd@0T3+#Djv;YUwq83H4<#!0Fk>cau<_lbyTUt5Wd~paQg*r z*ku}Wf71LFFXae{)894I>YzmtyS1J`RLgHiG~NS`3x_!JsDRZH7PE*_)qi-qIUi*;{2QNahyi&~9Qq1XT zb`{k|_3svYUzjEnKf)#SBwGb1kKRoVZA{;1H@g3CmcJ*kDZQohXJ+nuw4T3T z^X@wgk&0cS#nPSg47(>zzX`kJpL+*UDu;_A%YJ(%T5A|liHW8&`tET&H%eNN`?U;< zkIs9zkm6LH$q35mEM9cfhHaaslWGkCR!o5+V6Y{f*`u~pYzcVs6RHWM>v2D%Lz%|VV(S+Fs}!5x z2SrYnszj}lBZj(qnA*>MId3*jtB%LDjhsDLm;9X_h804;+~N4rkd-X2^5CPM5k#!{ z9wuR{rzKJ}5jsiZfCN*LD`Hw8euO__Usrw!U8h3E?BH0F_63RyGH9l7$P=#={Zbps zsANYiPoY_{)Tcvr1v&laR~RD-b^QcbK`FN{LG|wMh6J>2+7)8u+-SvuMP8mE6FnOE zp5aZV8~OM!cc5>7Ju`imG?Myu|+S*6GzPwBId(J zC&;kV71{B7!z-PFaQRv>Pct_VLeTqVInChI%@)i{;0_RhuKwYAB-4nlII$K^R%mgb z5yg)2dJQW2h2vT)ocA~aoQa-C$)d}w!cKMTHv=dQN9_yp@iWf5%Uq{I#-G=%JJILP zd)*H+HCT5KweA3=AXa^OT~i#wYcPsy*|3MDPEq8rAePjb@z4-$K)}-;j!ZTR`F=28 zh45wj}$SfOn_+U8^6Hy{>AQK!|Qs%ccG#F#_*IR*Z(T^ITBhK{a%eTxJ)kEiA z&Rse2aMcpkRfrdx?mmHs@XH`Gja3vGlZSycvP~Ns{MoY}tNKK5i7IVQe!lQAFha8F zAuIxQAdAlMso&D*{Oxj~$?C@pO9>AvWi4asz*O`lHZFcl=(JrJa-^7_oCGO@bm~@h zoNgSaoE9c?Bkk-#DF+7&`GyFSvE^|Nru4U0#iAn(T#Ql%$>u%O58U#3FB_8@EB5bx z)R;2rcm^^xR~X#M#avoni^?=NM)L%!Hs8n;$<+(Lfkd^+j4Q0*npux`3dD5FQ!vy3 z9#el!a1}T7f5Q=}ge3(!pM>6#kK~r7R?1myC^74u6-5N{jfDh0qQ>TVL(%CaB|A?B~pX`hB zR5sxuq-vT>$aSGvXqrT{_QJg85vTX7M;h;S##e`<)dy(6mw2NMpYK{uq_=(T&e|b; zP!`H9kQ#bf&ND-+{R@tf^ow5>5!o!>T7S+2E5oWdBDQ|5CD3F^(HH-qtLo*prvdo_ z@PYrwkbhs`m^}aXg(#lh4eSg((Q7TWgV%^CypvR?3%DCWK?ruut+*u|Wk0uy zFS|sJsQ)ML4OncvQU$W$Xf&J1D8)V;l9($EaWlq4pP9+sV3akbpF%R1L_^;RLQGrw zUPeO&t7=Xq5`c;=GcublD5E5FP<7)q6S|ogBpzERnqr&&)KYF;UAh*5~IAm_dZG#ugs- z?DWp`7VAZ2_0xG&$*u(CyabZ&l;3cK7mUr+10&*Wz_T>sh}v;l+akFr>6SD8x^>@* zMGa|zps*;9#19=~uc_1B^?2K7k10i5MuUAdaS<7S*bKM3Mb_N?n(RXP0;R zgVWIM=ZH)|#r%Z_ZVasX{)GUpiU_iP(95=YY%ct{XJosG+D;mmlN&3+yrg`%?3g{W zbLJHf$8kHJs6MLfE6;TM{YnW{X2erPwXRlHt$>B^G)z966jB@}6}^HMF`a_7Y{~cy zQmIPeRlHQ)=hert_qMD-jcv}uZZzdbriJt}WtD+S^lVned3JZ8vE_^h&t!Z+$UOfV z@fX-XipQCoB)dV1k`dTS9CnsUMGC)arlD`JB9eLrECOg^QrD_BURITh>>B6!}N9KhYxh4&x*!O zHe1`V#}5bb0HQo1pXO~r7bl4h(}j(KDw~nJ9_VxK+8e)I)(q}#0g6O|A0_ZvM7U3* z@`1aJ5y%W+$gdNUk$Wz{E!vdg`oO{qpd?y!YEqIr>fF$-G1SpC9ckB^fh2Y}2G`ou zqic5jNsWjZA}nY<`Y&;>w8eKr9 z&!UmxAYG3f-XsS*3T^Y|V5A(JsHltDkpnZx>^6Xbg%+EYCo@ zl&7+9Gk?zC)gr1LyzhmiIQelfx*WA~t{)%8W2~+)n;M zeJxO}d&>5Sywr?z!WQTExY@R=B9t;e+-CM+LFIhfxb`a;TWExm@{T(2Tjx}Tr6c0L z&hcZv(eZf%urJ+EUt3N(9|g=;mMR0&eq887R_c6t2MHBziDzwEC_19OX_F}_qD~|V zqMItvg1T70i>u;;TN(Rk>7p(v*J3wusD$1_=q0#a#ixidFpNoZ#dHTtgC?fxxIeML za2!jse0_iy?R`k!%gAg?p{%9@<8jJ}71>RvyqiPjTl19?#C@NjE!&Ih3YR zFM;w-1-1(Zzze-;5%qdfg_M}n#fqX(hX=f=$v> z+bK0xHlr!| zVR4#F|6wP>vr^!(S1FG`;=0gKXFyGJ(xd>53R}2I?aYi^xqw~^zPU~+Yb73EXkxj| z5n%WB!QlC%-f>z3me}DrY{-77rNKd=#x#eU=smbx)CS*ez{2qZceE;pU z5Xaq$wncogUY&bgWQ0@)WC>ow;52=16*wYdMz*t2o88h0`I)mt8J?gMH#XV6?7|RK z9|)t?>eAEGdk&v7eW#SsH#V*rSJpmDtO;l%8aI<)T745iNIAD){EIQtHToa5qj(I_|jJQo57CF2K^aiq~KfZs1~v$T^^zlO2<7F z;M@KDrOKb}6;99l_T3GVm5MsvK;!}Nyd7se);RDhHrrEaF1fUB_HeLT;$hBOVJMHT zb+J*3{sC%DQ~JFQ`CEq%2Qu9GAR&|}3(b71Q$S{MuGcDq5D zWApPx9IiIgYfJ)%22#qwY5WMB^!S-+8p<$ILq{i?)+%j9x>e!Cyf@l4HK$Gk{$*1= z4`t;VG$0F`Cwn?h{I#^C$ApYr-o7IZ=7hQ8a?Btb7`&E^8XYk(H=DoHDYEs2nk5@( zf&{9!o}SBZSaXp*imfSVC{(AUt}7%9skax90R(Mr7Di;JAEuvo$WC#*1zknyQB!5O z&Jy#NE*&A%gIM@!y(RVkclFw>VG@(>35~wb<3bf;M<+5N%pr?H#!+owb^9uZqRSe5 zgOY|%xC?^gZzn=7@G}%ILPZ?z1<8+RZHq15mHUb%cY?Bs5XUxy4)u?Yj<`K)>)C>p zV>6UFkjUmLqpBmejXVdkg8pQmdD*phDw?$+q(7>Dp1pyc#KuMk-X{mLyG;U}oDM!;*g?0;?`WfX=vn1vHa{QMc-hrXO*~8qE;kp!#vKe@ zCK$dJTsCfK|GI#h)4h=XxI@t-i1z{1zcs>f%KpbG9i5Lb;t>eQ-rAh3C&_N8ir#K^ z)m_pgiHWlEOb|wF6zPD*NeIPXx^c(9vEK#m;1%_laX)mCz^3ggH|SMb(+{)`LIY5) zI}w19X+K`Cj%=n!Y+{GQS65eG-OvWPz)K|<_PCkuXLcNUiPo#l#-b6v5_8%((FpxP z-IQd(t(itWF<(Y#IWQe4!phI&NZmR!oV%O%5f))@-a zm6$Y&=8hWLr8kyLxrj_vlhNN-opkv_O^>X_VuVlao2M2dv*$%gdH_TG zCY2kR?`rjW3NVJEi&Qk`9!5egnTQzTf*exH-lWgh)pTx)*v5N z7Ev-I{Ahz6aT#LDNd~v4mg{Xqp)^C2h~gL6(!mGcC?-YHz)QPt4vngLjOGSJE;wXQ zn>_oR%r<9%9uY{>l3wC2L3f>mjBwGYN>5M+6J}|Qk&WmLR1yrEt_J)ab39C~D$OBFFcYYeVRj3?I3@slWAo78stwr6h|j;-N+MRWLz zizZC@wEb}8yO83Tw-v-y6oegf%SZc@)JP$i1$F~gV)cG)p0p^`LI!g zEY(n~-BAL)AtOx$$)>vrTM7>IoAXcDU6oBJUu03R$Qzsjsy{@a2& z8&<}aFcLL+^{VFPSlS}A;*^({cbUUxiL(emtV=)kxy`Ne?r3-A?FKAwwMr*{wqekk zC2tvRvl##%yhZ>0;BPnnKidWpnB$%3}C=V2d6pZxeA84tnUujk;G#mAYt=~Nr+#i+IW z0@=fA`qSbLRefubrUUzGIP;%BDd@+C6xrr83cfNqy4kRXA4}UwMbnt=VzhOI|5y)-E(sM zFJ^+~bH*M2#Ta(Iy9CxoGkweBMVjXedE?KMo}=l!DPoyTwCOHeXRIu4tiZNS4)EMD zpPNkvTW?&^e;y4tb~T|Wq2)rwiCn)E@Y|Eh%F7?K4p-P29DE68WoEw08UI5)2K>j% zi!g=&NiZ9P?Ut}p16+@?92WAwTD)`)ww4JmS1j^@ZfDgCPnR2xTQU}nJ|_|JKJLUy zUT@)o!g0srIodD7FNM`SIovv4Pi+f{Xc{&GA<2Zb?)PWEMXHna`JW&W!jRNL^_eS` z(PDOqx%{O&Pe@#7CRnhykyBzDlCdN?osf`ZY($37tFG&$ikkqN^`-}4ID5VvAc}Oy z1*b(`TJUBm^EVVgu$K(CmMxD>P;C=OlL*$odv+i==G1z-hJI!se2Wlss@=Jab~gE~BtN75+gHz8v*{1Y6cX#C#gcbQ8S zk!kdr3uhv&{Qda!?9k)x;Cet#C8F}KSn~U^>FXa{AMSDFx!M`UvEP5{NPrhQ@OEc~ z{)Cz{P0&XPaOI}I{pKJ|{n6pnh)Wb)xs!XVkSM=nf+SIGhQGFWL?9-iolaA5_JqaM z`EAFour1*TsJKG)soW3!FMT(o5m^}tQ&$h!7CBEoA; z!aw)0N^DPu*M4<6*Fwg6;G_&bZ@X*`0&>uSL&tNJuNPF>txuuNkIF|7S4AoouHEfb z=Wg?8pdY*s(SUp<{txZl+qEf<$p)+ayVD*ZkYM>nX)PutY6nCd$_XuKO=fi`GE~BhXRlhjW zM_x8Z-?DdY!*HXzq=&xjjF;Vd@0$yZL;aUf@1zWVM+NPm=4mz_jy(N%vb|fL7Vw>( zXvslsZopoBZINW(?;L;J#GBA@Bk`0u%q(yX#^KJ43^yNN;&>50n5|+vo}%Deeok+5 z%~jOsFfLX(lcxhN z%u@io=@Ln;`}9N-WDPH5>F{+=Sas@mHd4TU&v!QJY$18G&-dr31AWOXW{w5|zjQ*r zD%{+NhyXV(8ZZ0t$exVpL-Dyl-#yP9GNna&{vlm_=H6@If0~M%{wFUaDVG#$ANG0z zQP&UDzv6aWzNE*O18|o~M)EmNU4QxMcF~2PY-YC4m5#^BS33O@Co)6jxFGSge6 z*G;ayKa&G6jpx_8=P%9xyN`!i$Ya4~r4B}C*9ECIZh|7HAp1Wa$J*;sA$cG9(kNqx zUk6^TR~nhxAYp|v%MCyn%DEG#r}9Yg=S(5Jr9=ATP7+>&0p3{!k`Z&(3f=9<7O7> z3k+`|YAuhqWajpkN=)rSLG%2Af!AVXRdtd#v)iSa5OwO4HI{a4$gO;hSL?~~x%RZ# zly)pWnuEr0a8TFOMjcJ8TX@bAe55j*0Nbk$lt+J858NbbrnH@p1=S%7FG%N}bETk` z@ED!upsNNY0HOIuxRgmM`v8(|z90M#-Kmn>7TI9Jw<^%=wJU)&@1QZDN#{rh9z0o5 zNw)u6fX@dhX9Hho%4(Gz8E6wXf~qh};`4!Ldj`3__(3oDRRG|oO~ ztK=deMJdB;3YPK@_H1^C(zJGdkKsYyf%XvgGvmLV;4>Y%@ z{T!!RQ8zxoJCaD$;ZCb>JxiFW6YTI{yV{)O{~Z3y?L>n668t>9IglxxOUc&g*7a|pQcd{R-1c96uX-h%#tR04ud<(c{DRt*1M)p&zb&o*c9qye zZ8t*SAfob7?$aK_1fduED&+RFkl{L8yI#8&*+IC@#7~NR`3E}-fm?YeuD#tg1d{o^ z`5~Yd@SHW0^&=4OLnGPcC=hx*Y}!q3q&2-l!pL%@PC5;iC+6Gl>KrAs)4d)c3EpW_ zq}D)g^{=0%c>k5V9g+Qy^(-4Ja4@m`CO^o(>2WU=%r>usaZOu~OTg#Bx#~2pyQ$HY zEfgn3xcYmH|NU_SfuNOR#ZLGF#K`~Bg$nlLclmLKstdq^mCJ{udUJR}T-Nd}&Zkng z?YmOz96E|ND{u*zY`TeE)K)gpyxyj!c7 z#9&V=%TtSwhLFyG^To(J+80+ARif6kO^mEuV-&|9a?6bFrPeLG^`TKKM zs7(4^5!cqs9O{CT898BWhc6M9z>_^XopHkf!pkvtaJ%55XN8X>PxSM8@YVTQbwJe* z&ntlV`KCaX?Z}JCXaDNqkfSpYd4wtbHZ=nMt?q(rY^3D*ryl0@#L!M4aC-qN?#$Su ziOE5@5rx-)%UQ?6shfl^99OB}hRq z(H^37cd@>)&&J9^*?C&|g8SVSy-Ezt1>gN+n{+>`m4zp96dEPRbL|_y72?; zxw>1of1p-XqV!VwKH|e$Ek09UxY`PV8_!((*AVruAx-o23L zR?Eiwb(de-;9N+{?p%OE@D3<#{XXKh$bOU<_;aybR4z)>^=JQn0(fX>(b3vbYuR_l zU$J{jk}H3FcoUsW<$w&0vdUW>CH9-Hn-6ze5&i8jBDmDQDgFC#e2>Ub!tF|mlEXQ< z#m*k;(R^mTjbZ9YE}7r{j8<0ybq z@6vRN>@lPn2lqrdXKl}}al^x~x(h41BTp-i?9V~U_kE?q zKx-&e0NLJq6xp5?h?A{AS_~dM*M_HbZYOVfxEh7K>>l@+;r$ziD=T4-0^Hqu$m$BB z~P#?PxTF!?)^V+_e3~WZ<+O}gw)Kz=&rf6NRJ&d zi=IE98Qr5edCyv}$8RQuQS%$xZ_5!Wg)?eC1VT+Q|MI#XuLVBW+l4J?8stY9xQZ;N zcx`idk)1Bp(RwMo46C=tc)e^BAxz+{UHnl61#cC+R}BzKKi{1q?S~TpthcixfdV2m ztqb)8k=gAg$7ti^XZ!TFuF|8KIO_zU-jlYQ>E65I7rq4=kr~ALg_d8oPi$O)c4E*A zJ_l(Xuge{u#b+Q8&WfO!D!KmWf8t5&^``TR~WTQZzGcK%JvlPo`7eHdWzzmneX^4udhofA8OtpBoR>W0m8W1 zw?)WhTFQ-o#srDtMi4aiS1fBf)DS_;+t$y7KI0Kzr7q(15H|-6=@0w9yv~Gg@2wPC z4%S$Yi2n19&&(jtnI`Pm+thJm<~G-nd|rPu5Lt zAo`#Ml8kTenf+r|HWp4?za|H{kIubj;yKCuvHRfT1_zMAkK3RyI@hE#dF_6?YHGgl zyWa9mb%*6Oc`D;_v+40){2x36<7pNd;4LDmH2lw>fcW4RO*AI5kf15)_qcYStvhx4 z5;hefIx0s>=h3JgCplM%5R|sYnI@qfU!$iJ6GW!0-j=K%qg9-2)STUemtUiBv_d;d zFtaGm8%y+CrrSai;0*?39WlQip9Yc4T=2gy>KDGhP`)#;F^m?-p3+)a?Mp4q$#&Nz zu+A6tTBL`NPF13hA$u5#p;0Zt@)w!XK~6O3oZQh7e7x~{<6(uT^KE3Oo=9j+)}}Ps|G9M~ z&hab4NW&WS8(jX~<5RSlh(ve7|GYTSuSNFk%}4!}hxY!%#LIB0ssd6}m)6qqB^ z{?NxL=06TeHmPa`(v|j>Eq1bVstoR4(RN(XqHX%+`T>)dATT1%u&3=69YN&iTGA~A#Nd^4B29|1^&h&U?;>d8(7jMnl065NNJK*-km zvWdfz{=kgsdfW`Qi0@Z(cJd-vRrE(fTt*={?w1YRVrvbIK)QUj_eU zsJ#j-uXhZiZ3iWpk&{OT8J26}NX%MnVMKSfWTM+0w&RQI)3MPw;ttU))-u7jGSJHx zMUCOc2IS7?-Uy>=n5~KPCi;5W1x%@;X=Dx<xkt7T>h|yT>Wy6(s%(VOb1#z= z1l^e-)EZ^Roq4PE0~`A3200m7YqcOVa7=w?ln$1_#=?RNUA;I1!B9+)-+!|Jf<>Lv zQogqJ$%?5mCUrlOqUfnyI?w#pb0X5$B}kXRrLpCx%_766-hzzccY>;73lx+UR6dR= zIGTEwg0S5K$V+FaW&^z(%Noew$pa(s<=^+bU)RBJf#;H^OX%R-*wrVzJtOytkh^9E z>MJA3-JBe)gq@&d)$y2Q3tJu~6l~G_(Zd-2ye)sl4is_s_ZV15y?9Yd{y#rqvpNobxBb?OCMoaPP1t{y|w6>{L{FP{XA@h z%g>T>_B@`;4R_3CL!;YX%A;G~MDJwX&Ia^+JwI%Jq*VEjUG&2<5Sr9bf^Q^&a1X{> zvKf+kKfNcL${K9Qd{+Yt3c~L~vpZ#*FMu?>o+LYf#DMy^Yg^)PvDGpiM z=bi!~&hiI*AJVQbLvFY0%AnqhN4y}&s@)}YFWy@A=-}vP?mMU#P`2+VQU4+T@Nh%>s%xqkNH*e zWa&fO-XBzM69+|(@s~;5&TOm;jl=tgG1Q1=V78^e8Y;g>{kIPZ@|yaE>&?ZLcv%NV z0$$tLe(W3vsrP>Q;exI|d@g6Yp1Y>*+B67({0jKryol!v^=_t7AwniT!uzho?QA#* zWB+LG+}_XZTi}~c`(svNru`%;Z~pSZy+(K-e5+|vAiCC5)-CU4!r|nI*9Y-1y$Uk7 zCR+j`ymd})_PM`hK|Wtpr@E!nc68lq*@8`QAf#NYm^BHvJ{2+2?qQ_BmG75rBSdXJ z3E#52iY4K)L!hn^X`98SvOf_s;UJFAeK;HUFS%9U8hnehm6q-d=J|vFx5L*g4%^v=v{?v5%7yo1TV0^k6X>w?)RZvN1h~ z(?ZUJ#Ey=29OYDenx1}$I%kD8*xkp$jXMB%L!HCZSv({N1loghtX8(E;A`}7&! zT4KsFk0S(+wM;ikJ*4Z-eRr}siBlgNo{|?DgniZ7iy(&a37=`xu4{^P_Nrs>dZH?V zo@voQ=E5Hg5A&v8eEuyyhVE?+`jxxx@T%4!Fh}nDbI$KGhoeEP>FFUJ6J>Hny+av( zFA%fmUT9?gam9K%-&0isZHYw3O0bPya2%v@I6qP^9hi2vS&p}9{z>fAFgpD5l!o#m zb+l;Ta)u@yuki2P<=Y!X8iM|FzC!m@-pK@iVe@=PT}WXrC00(Lp*iWG^|=pq~# zCNE!xoC(upcgje({e#dzO&bE6dHHC!)Dz-u3+5PYe%ta-@L z>q7wmbE5Q&I*ebl4!rpmUfu>AKYM)U|1DW?k)GW5_hO*@Fb&eGo-H!ByDtlEBi|7+P&NmJY~ zVTYhQyUG{Mk#$(IF1IwJIEeDFR1g93-YkTG8rB-l=e9!rB8Mx1hRRTOOc0uv?Z^sj;vx!g=L<;n~iWto4C zxsQqP=j-Q^dcG@p`_maNGe^W&L-_$^|&>g+!o0~3EJ@VC>ubN+exyUz+0 zEsC_>>Oa?y*wKkcz5O7>1CV3)|IWgbg=#w?DkN0s8Z$x z0qWo3(kD_jDTI(d=BF)$`eu?Kku~eZzDTM+-Z5)tcliV0LXfypb5%q?yk%7ZRMEMV z-d{tBRnAa2If#947xHg@GaCcg%A=n$Q-JE?Vwd?SE*RqV$=%KDAv4FBoYlWf3+KP& z$0&E**xjLeQ9k?rgfd^W$($o`zue$4(_U{r-|)NSe00f09#dO|ftg!^@_0EOpUzT= zOai#ItH7@+C#_X2(>ON^a5y{;n*6py%ddk%aL58%t<$);NDH!fllNyl>ho&Aqc#pR zRKgZNq9Li>c~wA4yX{*v2Ci~d<5b!)d{!L_N`pAZ9cgkMvvHISo z%>T@O)l2WtFI;`*`+v-L8&HRsE_4_Qj^jc?j%9y+K8-njfJ;`BViZ{Yr1<_|F_Gab zyl=nlLPNv8l86YjClr6xapGBxx&Nw!G`=D3`Nc0^J&1j6V^Gwoo_Wnvp*bG0$*$h9{|h(mUEf`TsUm?@YPcu(568zjiVV74^S zn_^mwx%FDq6PkyE{jtVF3)n&7}htPVE1o6jVnjpf}S z1?sq2nqF$J9f|EoN3kK~4aoqUg~@K%RLeSmfa++7f9FQB^K{x{&l+o}h!1d4c7O4^ zDo4QIM`n!5U(zurGn3?lSSkdf5ujr))5p9M^yyd#Rj7junVBOu-^4lVH@;oHVuHpw zo8)F{9?Ha4^w?wm1J+w&kI0E;%tMH|kQy~u>@ocLK6Yjs zDCRmD_QT^cdu?vDA#Q+pUnF5MuN))FNX z1{1@Tz0!X$L<7a!dyIRPh5|w8>Ei5x6qe>>KmFXfB_!nZHQ1mQoxD$?1f`vmS z>_%44VBcaRbmc^;u)=j1pmWRmdxGER%)#yRI+d~fNtKhi>LF2KU1ta@{q`f|Tpw-Y`OQKg|CT|q1# zg_4D1ea)ZbU?YN^cjI538sR%Fs`c%37v-PU+JrM%TeJ$npT;znzJ;)7iXeunNQl>4BcE<1lB=Mex1f_ zcL^uJ$47K}dpcjw+|aFxuavqx zHI-Sy$L+8ZlcXeU&zAiun1g0YvJWcLbfcxgWw_C zCZ*G%MZCyKCos<FQD;&ojCKC%@OrSv2G7rrY?g1<)^%jC7EF?P(cT}vE{XY@XCwin z=Hg(s!KN1?y2W#LmQ}@#?qyMd@sAAm>S870x%EmcRt}MkxQvw=In8}wmCdMk`cq&U zAC?3dqy`z@ZzYXJx*v|IJ@KhfEJP>(%Uc`T4f9H}4r`=sW}B#l(#1ShIFDzK6$jOb z)@?GvlKfP^lt05O&}Zhqe3{Ltrc@V#WyV}AC@3awmwkac+MQiJx2qPg#>BP>eb%w( zKzLC8rQvfx-g98@In^!1Ku6!$Fyra4xjFfr-t+L+aQR(del`X1FE19RF`!(}trP7x zX;oZRt}Q)lx1>r>d%W$tJ|;0l^%($d0$0d0N4|+Zrr8ZQSHf8d^i!_it!9<2sCtVV z6q0yIdFl<4Nv&)c4;Q8rwp#WuP^|#JszA^0T3Uduo`MBoh}<&g*aNZyh|}Hepg0u{ zP9`JQ9*Kp+8O%JrDrlUZU5_kH+keYmq@lS^LpA0|k%Ste+Ro~hd&LvUA}p4xWS zgvJpRu#<0&cA14=k4;Bfu1AC{%16JB#97W%11}8-eZyqa*5jcLlh0|hM4W`Phs5@d zgQHo-{eXliLQHld*V4M$#t&IP`v57D$U4(XiI4z<`oHFhH2E_6_yHZ~uMH~|JXjWn zz!iA?6BW|LxCuG;#jBY+ew#ZH8WJq2Zu24rf54Nm8OhPMnItcZVxuJvnTvs#5Am8I zXWnznJ|?wbEyl~{ShvEw*%SHMYwe^_QU@Ge780Qi%FwTq7hU?|5A_@=c)5I(j!<`T z$L&huR!QH=9ykl7gGXzui!7dAjHNPvu*}E7Js>x|U)>RpqXOr>ufl{DW zqEj5*$4NvtJA_eCrGSqeX^^}Bri9Theljwfc#vgRFalMyiRw!v303bro64~s6jL^W zH&3XeHTP`}R>+ts81-{Ew>WP#1*K&RJl_dZ@*!V6icY?k^*Fx;KK~_;lIOrU3A+Ql zh;j%GKs*ftn`QMjN+A1FBSnCi7rVa!F6p40jV4GJH#*W()SSj#SVVg822Yssm;?clR`kEco^ zI^Qx7Ggo(fQSZM<6g^2jZq{BJus0D?Bz^Pn%j06`X2wseX@KM2b0}?oC9TzCOIz1Q zsTJB`Y4@Cmx>QbM-1>{9{Wt|}HmWgdoOSx5Z^t8j&UNe3y{^p14tRxan!>qqQ|FXaAKU!HEnU;im8mqbdHZgIR+@V!X0|0%8;n(*L4sR?$ z1BRY%iLn(wN^Hg`OvfA9P7)mruL8BgUv>)xi#==t!rhCq(Z0CDgj5?jhrLT0i;kyU z7rA!7N=~4D?z8r@pAlnfPgcLXaQn;uh`_qd!1-78bIuG?T+V-wB=8%WTyl41CUxf9h$)bl5O1J z_mK0wqF7&1YHybz8DhEXN1Bk1Hf>(p_JIZ}Z%2H^?Qu+M&sm?b`sqzq+C|Ht0l1E_ z>6zK0?$MpXsB=2`%TJm4IwG&gQ7J@PcsNk#B*Qn6$j9V%O&jhv^Hg)N9S70e4$zqo zZ&GdDB@>~;gHFN&8a>Y8uOEkyK$OB!p}>0Z?}go0#0ODY{0DX+;jzHBDU>^*uI+)v zh{5A8yO0*unA9xk5b2e{0mOo>N?~~fcWw6@$>vr{TsvL=LLokof0&EWgkuv^jx`Uz zTZ_45xG_#Y)RKsVkjg;FMZ%#}#t=e8H|DVU{zqvDiRjKgaulkKH=*B+l-0cbXA>X9 za_)eRWf9?1z`A6aPDf>w$F1!bkz~D;%aYe-9QE zBk)5;QW0{N`BguZ5~f_;*v2FK!t@c3@4dgk)#&tWg=LK}4MzBzH`Bs?ob)A)JjmJJ}G2TJ~g94WgmO3&S4RBhnv;UD||jjJe1^XdG`r) z#cr|EC|R|R?(ktXwIb)$&+k56#t32;S8!2J?R)WLQDr4tx)|WTtFxahMw1<=_dHfz zY}*e|&1gC`WPdgs8$#4%{iLZJ15~9$QjMd*D4tYWG|-ly>4_W ztP(t&40mDk4IB1=OThqqCWXz)5&S!Yr^`ukY9B|)_3aQy>>_xG44g~n^H=csIwKul zKrVPIYya@8Pg~F}5LRHy5ipzyU(9S0jb{!#$hf(Ho z{dY?WQY*yxp6_M;3L$~SA1d+nlYF8TvNCo?&i5n%ZBxB~oz{&4$FpX3YQGxqQej0A zvD-g3cS(WgLntIor-Sf=Ix2DIQNCfoD_H^0m{PTj`fsuUVb}8ebds;S>$YG z{Pht_>JztKGY@$Td9`4eo$(%Q&e4dR(&i-q5IBgy$aiam55Aik?F2)0`Mdl$B{jow za$_jgL#}4`@?$Z9ZwG_W|AasJMNJuq6%Bp@j`CAJdaVVHRzHm_&?7v}?dBnkj4HMc znwja^ca>0V5Ezfvf(!y#Du9p>H7=(U{4#RMfA#g-`)|od*lv}TmAT)lyXB3x(gnt{Tw)n9)Rb!L0k?Ya&Dny(LD2UAnQ10bx4wG53ylQq-Iv|Cy#h2oj# zur0`;`ds7#H?0P2EJ5{67e;|87TK;!iVgjEIkb{$But4@<=*L@Qq<#9eyoxRv#{^0 z(0GnJRGA+XvIB3G7j$V&vs{h6ujn)4#^h*iEhxTCY4|I=OHW@(I{VPsYRjfoz(|5! zwOZzA=H6-rjiXhWcnl>~G}#n?Fz|x+T^{xUo~rpi?{&|Wa;fyFW_%wl);IN)fo){c zDjwV_Fcd9@+j#%vXf)*!-qfx_6-{9<#>Bj|^YNl-fElLieA~No!4gKYa+4@=86-au7CFp2M!4ZuF!kPwTl>jOvKYa zRiY$!$&(D4v z1f%cp<1ho$tCh5q=It2ygHZvte8^GI-Rs7u6aRIUFI=k`zZdEkyeczeeO{N4cY}7Y zR(@V^=)myx`*=14*ZtgkY7+P3%~jRb$W=@8CsIG3u2HT`xr=gr=-h}Qp|#-bdg%00 zGokybQ|ji|AhsW$qanl2^Z`PTUJ}F(qW-fyz*VAB!;c|lUV8;lih(4YZ^%lSycGk` zLw7G_WU$=yp8449jR*C)SZIfbeV5W(=&1{e``Hzr_peB*acHQDem4iC$ zoZ4k`EStR9z>qvE^T4QvTV)hee@b5@6QY+-3{=2alqIT?^7+WQ8~E+4c|__r$J8x!X)T7Z zd$_RM=G3wfN})MSRueN5Rqrp7v%R$0O~hPejf~S!8Xj%~o}SVv=JYzZf0CR4>r^p0v%bJgNgN(!~lRH@1Z zv+iXD3Yd?3J;G8aDTKlxT7_!7;42nI>s+-v6_1ymbtc!F5RDz>jOF7aQzLuj3w&mf zvR1lo{m6B^1E0oiZg!T~o8`#9I=Zh3m`qq+5n{q05$pAC@WtWKbiO?C(KP2L<1@U_ z0;*J)Y+w7BphsaxwBc-wBMJ{^O&K z9pZQ?zjs!}&HW_+wL|^Fde@%~E(RQ|=CyDE#iI3zcuD& z#_SSb(S$Aa4la8MC*H((m{iZkn|z8Jdw=pBANg^mD8+~`BqZeAKYBJWgx*Cej%gSB zo0U(ADoZVXuq|05@@D4S83wi1NYzHDcgFk`Fr75LA(OlRPvBCV1Tg3O_P%@0y^-Gy zFBL85@9!?LSUba+brulT^!rbQwU!64ve`r4oJVE$KjIH7osYo}+-G74C0x8|*sHgy zMy#rHEZX}4Rg*%_Y8DTfuzEy_WRF~Wf#{dprQeL94=c`{?a9(iPM{W70*;XFS0x0K z-+jbxi~zr#IYc~&|KKLd_EqjbbhZXgi4xR_JE~~PD8JWO^ETx(wW3VS2su{D#52&} z>az)Ji*Tq4_=(;2utDl>(=d!nr=|>aiL*k49F|p!hjlYmmL*gQuB6wSK0kreg)Q`c zz3>uIx54c_qg?7)j>jZB6OK)nRKuVG1|EugTJaDd=9Nyf=ql~Xqa}Q?){>*Xvytp) zQ~JeMUpPM9UHRJ0qI2UcQ=xmhv>5SMSju+Z$7^tr8~s@4yAoOG{Lm>=$>qXJj5+04 z&yv)zx@)JWn&zzeib``NXpA@(*a(v%ZtkNs$x;(??IC!~G@+Do= z{jKQd)8~YVOkV?q&?|rpjjWJ~{8tJlZP(c+i2T_>pA`I(i&9n4bT-GStyT?ReMwf> z1CHiZT-Ee2I@faUnP00j`Guar{NGT7+t6AhOh?rjb8@Wsx5TteV=Y)cesK>R_uoye zBk=}fqM~Giwa#SR-r$Dte*M~$8S|3cv_)$rnrbf9Rm{k>pRl5@4v7)>H8{=AWyHbkf_;#hhtRw$Uv1~Udn)sD6m?NKQ3=XXkpop2g8Ov4

_X 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

clx1QR-tEJXV4n^nsw2479aH6hsk}_QL2@q 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

||s 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 z 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%0A1GE}0mT6zv@(q9tGz5o13ek-9!o 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%0AAlM@*SE2fNA9mqio4 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�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%0A^La&}+exysqA5thW#jaOs{JgX4 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 z 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�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

    #P4ly^={vEPy*QH=}L? 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%0AFA&B)s-{N)XSL9kg-kZEl~pua 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%0AO|iYux(7Odst9 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�-v3N#|fS{T%_SZ$yUL-w^IO5?~bLzx;i3SpVGjdiWz50&Y6!z6@Pl`qi z5sB|N(6X#@W7Gz~px0Zw-As+39M(GkG6HUH%h;tZ_t5&;1AsZewr>i=oG@nGj^uOZ zGibxKoik&o#0Fz*dw;00vpB8-#F&2`-?6GJmcbT)&UUbwB*-ivA-?)sj)5Za?NcQL zLxY>HRR%guqLxp1$7$b?PZ$n7aVtPo;)Y9E%;aHD^o{IVo$13NGD>I(7!pt4mKy(H z7o_L-(M?xKe^A>Xf-U~`54Foo-Z+-euzbIX|WnGZ9W2@gb24d6z>@#9=_7Omg zH@ykIG%zD@m~&$6z|@;-NPhqne5t$&ybPcW{n2mY42YqE&8_L%e;f}1=-KZRAcp>L z|Elw;st={o$bK|o1c2eE=Ajxf-uxD=7%DI4qL3HcC)+dK@|8Ll(BxrG5QtHOO+Z6U zo9@HfRVOt^_>Cg+;72txmMgsayd&0f2(qgY!|qwezdttc@GcrnW=1ZRs`o)rZzhSJPDi%U^nF`@uQm z(P?$OF#wb0i^Q1b*LmrD=&xJehBcjhJ6)W!wDs#S<0*G)>n9J(p`UKk_g$VHr_(oF z)A9Pf(>Jb0-tE_Meb@LdU8nE6PxHk2eobfW{ib?R;I1eupp6yVmEV5aU71~3Hd;5H ze7ZrT?RP7CoJRAOrW-+bH=b^3#_#%#d9>RVXs6$KGR&v*KTcnFcL1EThL^tSbZa_& z`83at+kD1APSbbVO=BI6-OXD%`5CTT8q?D5Ta)f{Ytrxffq(wYv!x&(O^%7?DudX5 zlbzHx{`z&u;<*Btpa?woXQOKs>}EgVB| zS)&)}d*Y6Fg*jw|4F*HM@CaDbKT_ZbDSOK|=l-5O& zH?Ju4qKd#G^o*@2{Nl-sr54OQ+m5cW?a1q?rwiVcbjD3dXuo(v5|Zmpv?uOx`0OUz zf~G8XkJ;99w7VUfy+jD~98ZeStG)-v#IVriflf06VvrmaD*QDp=N7PlSM@kR3^S>R zr3xmvTvs#VkaJs}{`wm7!raZX(2GJ z-PlCShZ~nrE$5Wp$*C)Bzz5H$$T=0{sQ?$t!~&tU2XI?9^D77=pk#TJU+f|Qn|MFr zjGLTegYf4zD-ThAiMG?L7cVA{0o-zHDV3){v&GpkuhEuq8xYc0&Rux{Fy~e(w{{4| zOx1890d%COuy&mg0GeFwK{#W2cg~tuq zfr`v~_?0n6@vzDDF5oxSlRqS7H!TnB0%B$i5|()Ui5(vD@tuFh#qRtnsLHb*{b?Hj zmVh5Li?)D+LauEly=4jr!@Gb=jEuuaZ5SMwE}!o;zgss2-Z*>uc&3xrU>_d?$ef@Z zLz-%SJAy3c>3l}_F@Pto|vZh<|~gkiosrau=D1t+7MY={sLQG+w`e+-c(``v2{h|+ z8yW9{-Huzo839Sm6V*4zcdA;%Z`aT|`lprZJ^kH+<8K{$2GlZVnFWL)$whBaf*(c6 zcZs6KHXh!^D-}m(wNuEl3C0w3XqOTMOo#!*uW^D6BmM1VKGIdW?W6P;>L5T2^QwJU zTEQsiktOETpZ(Eye^{?stiEV!gTb?gM1@*sX67wgUo`Jn|HprbV5F}YA+pspB z$9{h=c4z9nBz7qgE3hlriqNg;iqpRAHhf&6#_^F?SDKRUiS5UI&%`{nG($ zSKQf{HV;kn774NYokXAtU6zFAFL`vzN-EG65K|g4+{}ZXwg(z|I4?RRXsobhg#&rl znc3F8rkWX$qhUBFW?^z05ayQKeh^yEv&H4jgj@(b7#p&S9hTavcbi1_C51Lwj#!yV5EPdxc*Q+~vnI7P;5hYO?4x%iNfA#$hD~+^Vz-cyojh ze~1>${@xMV6$g`NFJ80d#q&DOV%K>tb-RPVK-abb&p2`F2W+BFHm;XO#JvwbU|Z6I z$r^{*F0J2)#>lB>g#g4{jiA_ABsGxwKz?!Di*&0%Dq*R0Pbj6$Sf@ z2kDlkv{I=sAKKNvWgjp|cLZRVD%1qTN8ne;S2N}e5WsCoo(tm^CFB)uP?|zj;--t#-(-_w+NjrOme8MTF6VV}_FUA%bJ_aT7GA${@y ztr!K@F%WYO0D2A>>QV=hne-{Y0F2{zu~rvYmNS%+ce=tc?5PM+*Wg?f2B|IF7GrWaSa|AmhG$U?SB)(^!yZB--oBe z47aW;y!vy-1O8zZUaX6#9zm9z$AhgfTU(R#Q}nGg^{YG-Pl2Nt2S})@41+Ia)m0)S zG5Kd53B({J(RgaZoD5HHXyWXsBhZBtAMzy7=51Ly;8#A8LP@Cyaq@58hPt+5Qg=g{ z83UTsKXDMv8M^{8f(Zr0tRT30o>Meq0AUJ{0gR!)x;4og=iEZ%d~ZF-QgEuYg9?Nx zw;D19#su7xkOMX538ZE68v)QG_?FjF9mS~$!fWXQf8ZMn3D`Q(9T>dww$J!WanyxFhV)?M;W zc0G$q4VY;7@GXNi@A)ibeycg%%=uIrnKNg7#Wd&*0s^GzTMmWRx7!#0d}j^ku_rp_ zd<3E#V*$aa1aF$tZNFw9QSSmVv?rkdPyg_5{T1JuM4Fcj*-h=HWc7m2%yB`DSFiu| zoW;wBhlBh9Vg#i-EENIgR9nhQi1uKzjfwhum;7VB+6FNugzGZpP zBGuvZj$vKqG5BIyhDoC_PM1M{ zY0Y>08z!x`Mt}KA_p{B-O*S=7?tRYIBxl`cs8ydcY30+K)cG?^IDWXD)ANT3$OX5z zCKWrZ7zl=3XRkeUzG_@7zu_>;cN+ONA@0nZ66&u-SO<6d37dHq-S}@W+{S)a?yDO3 zZLNXJK+GNs>A&;yU$QmH>15RZ>G+PV>iUE2z#RPa@AW|qpc+IOyAGOROzCi>$6Q1p z@{C}V=rHz+*c6gp#~nw>U`@dACxMw!27bvQDVLCzMGl#p{Y!8IF=(ztBZkE`*L+@M zYWG>5Kjvm6uBv7y%?4YNHaOsL4NzqRFhdY#%A#>jA(Hb|jsRg!*_t$WdYHHZI-FxW zSw$yYO$0X_3HbEHinxmA3U$DIs1Ojr0Gcxv&U2`aUqc{XKuGo%S%vX^wi|6deKy%+ z`;a#>t?-N66m6Up@*()_xiD@k@(|Tcw;gS)M_u3bzH=>{5z-M(A}kkBu>WRfvi0mG z=gx2qHs`>c0?Ztu{jz)DRu>MsRlsBQxXl?P-p1oEm~OY($YH%|@c?EZOiwv4=LR6m zO*C*ex$b$BYpd4O zBN2iTMAClLUkT)H5vuK53N7A{;zB-slR8!K%pszHNb-?~8Xf^-GRad;uBuL(sgc3C zFh^)Kc}B|=Va@|{AAj@f$&+usp4?`8*Mqya+1gXLQK_Bj_JLJ2il*z=(e^=bTIRMY z0H=A%s9>d#Rf3u2KrkY0HAt!t(qAd`tXv2mN}bZxA);u80In!K+GiBD1mV0^TmWv5 zTdlS^j9#snH(Rfx38k>@tyVjLa69yc*Jw_?Lc?hbS_M!Css}|2<_N^B(J?((dR5Q` zNlZfq>Ls*C9m^kP$#R;Zb8H!;ikI?Y!X?-cta<>DmTc z(`bKqsR5ucdT3mh4=1$e5r9m#Cdmsv(S{L(5#YlXlrir~r-7K*(y9?d8^h1$S9rFK z1K{~iJ`_X^(*)3k#;o8B|MDe zpR`QLmwFW9h*Iy#fq?^GXyoSnAh~%pe-YmCz{$Hn41z3`nZtOsrGd7#dsI1OU|h>2 z&&v0ZPG`pSlYc@P8_5^Ws-o0H+^)+4w6H~~z!=g3eDS*Sqa%SB(>5P0OgT?R`g3i| zVZBlo`$kgdWd_8=hja}LkttW(34YO}X~VjDoqkC_Rcoz)7(tkRJTIoSV{*khebIgZ z=gL17SwPh=*HT#mRsr^~XZ?s6G&g<8IRgMlaDD(`1Y)8McD=UyZGY-_G)9<1_ z4YQw=9Waq{%s>~i-P$m)jep$$Wu051;%%8sBeiEI=1D8c7hnI`QUWX5X}+{C`Lii~ z_~+Ka84!~?urF6%4Io4Rv`-7(T7I8p!1|y5;eW*U#1jpuDjMYf_pM20%KVvqjjtAU zaL4NRyjTD{#&Pt&Y~UNPYUF8o&4n~+wvk(Z{rbTfdTEE82gK3e&fnXhn!jo5 z)^^5i`MA^U{1_(BHoB$J-?TN&Q@8v~-=}$){+K_nvpDkTG_*Tzm&3T@^yRP7f1GYS z`P2M-Z(8Ns=BIzxkM401?p38KE);|RR8zqXHu_1@=0@xU`;oYS8^A8t(|Fq0D5SLM=TIC(qC z%QzDy5&Tupu!J)4`O)F?hnH@GMkcpNuu$y^83o08*}vW|`(DJl2)Rh#MD_QN3!N)- zRRiCb8h95F^I!e^7tx5ZL))9HcjIP!xfCn^dXF2g(7D>u3|6@vCxaorE3(-Ld26ti z|BU*ru#R7Ifp1v^#;&lu!+(|*uDp?6`8l7|V>bNdI{!M3nPOpxNW+4Ntv3QO%vK8s zexCNRgb+AgI01M$&rM0!ITPjvfWt~vJKcWkMOgM zK`}fcdy*HvIat2W0)}8t3`eb3%^I}+_;+N`2cyTVby%aq4%vKYB?&rOL7;XCseGz}J zsXhTbffC^bfP*@i2S`wWQMJAJDxfN;_@*#Uc!cTtb^RRS|3vUiFy?TNHr&rPpqw8= z``DhZUp}8a`ubN0C+Cv~sDj@?^GLzRLwh{~W`i52JRjzC9svq~X?bIlI<6Z}{5s{A zU-BThWf>LV>R0;uRAws`ehMId#}ouT*X0PX#6yAIdS;y{G`U6TfCGE?(c1C0r$d0T zecD_;Tm_8rigiJlRe%?@ zVQPDVKoR|pZ=0ad8Re+MQPnn3aHP(uPem}bRgML-NXxz@GGRYEw_H5AqMUkK;_=*= zQ_7lO!l~C2I^Bst6LE7uuiK9pi*O|_ywr>V7!~L_rQHSLEK|1eNlRSo&4HxA>CGbw z&;l|fGXqMf#-VSKV+B9DSAi}y2k95h5!%VNY7-%r@g*4QmI;zbUkq>4qbbWzdq^{p z>8%5s@WY*VY}uO~J8Enk@sqdzn7`~HzUkk2bD3dLwH+P;Fabi`E<`^!{|u*W`KYo~ zT9v*j$(_GR$NV~?U`8SVK`qFTDd$$F_SpPQ+R4v6`~_l2$MgYU1Yv5+lE9GpA{?;B ztxS%C(fv`>8>)f~0G z!awztYD!KhzCObPP5x=?C-_u(bG+qH-h|uK_7U<4Ay($WDmU%OT=C<7_df)Pxd#{X z_#FW;@FKg(n|w9(e%ZIeF2c)Yy}Kr|l8xaX>-#N(-wi=}=F^0Q@Pfiuds%xsFLSmD~W!}q$bh1 zAJgk6$YC5G!^YvfhKF|3wVPbUG`%mafz!p7zv+)lW|*dFI$VDG$xr&uZ_~9uL7}l0 zJJ0%Q6x}|Towq_i+eBQ&~PW(M2^;Qx#fQ_Q* zJd7(|xBi!HE6KR}x9Rx4xBuk|Zko0Yw{bPy8H}rc8{M70e*NAw%}cx8(&?6`bmNBC zU%$4y!5eHLXckZRn6}@wrPp+L^YdM!JqBC4eEPRr+w_}O9zFsyKN)XL@}u?Q*$cdx ztocG@vf*u3)5J1j77$E2F0^uewKSMLE}0RTb~fA}cBn46qwvIGz#@S`@(95f4_<-!8nLto#Z zRkM$Fh1X3VavsdlKEMP5)z%imKH4w~{37Wux1J5-FYNwmy9a-70rA&nuZ-Q}JRE_T z8yhQ=k3M>Uy7Vo;iH(3TZb@0@Y#6m)JQqYDMzCUGWqmTwL5vC^)_#F5d^Rk*?M<5~ z6xmJ!7j~w!H~tZJ6l?^)coZDm4&}{0mM=hzQbpTlETcfoeGWz52e>hhD_&L3VtW~Fk0r{v!1f!<=e8ub8~AH^ zKup^gK}kT!GAU%mK^{F!be_k4Y1sn8*rxzu(jO>mY4OZWcG2+k5kP~OeuHoWz++t; zkk>t)*MMZt1lH&y`_TF0#STEtHa7rXL7~239n$ZtYp?HK_k1Awf*{NabvLD+)hbv) zxK;SgFX7~Mkv3NVI#Hm6m%_UFDQyD#B!+1KxUvfm3KMsL2S7o1(e(npL>ngO#?XHe$pXT-ZAl=8M`7DCC~(td zEs}{>Izye57k?#;UjZR+12LX+!aT|&@S=v{3ZRABF=}(=+?b&s>nCUua0#}iNzy|Z za65KpPxXarDDzg?b8Zp%s4J`K^UTHav@h7^b+%KLulkQ=XFNJ4d9p1jAPjvRf@t1Q zw$_B9gYsWM(=AD*4Wp)^K#cR=1rTFd0A=*E-=+HeCRFPP!uU9^+fV#At8e;A{#oDk zKUL2#&bX-l%FuP}@Ib<28`x0wP(s`^w%?iIsk(x*LBxo*OQn=rPQM#!ytHAQ8>-F) zr!|R(U3oQC>XbUU0Af}r|MuVb5B^Gkm}DTOB`+4bvQ*x~-@m{+p?`7MMMxdi%MT9J zYxrLT4X=1upY|n4)IPMob^;-{u4ubd86M3-s@r^v{Lul8m|R~C5aTtp|2>aWuEtWe zo0b|Hr2GuiPrCsLY1?m1-|kLc;6-EHexxzHw=FcSJk4(N?R4>qpLEh_k7;{_1FrX{1E1^4yVcm#L*Oep4Fs{T7Z(hFJ0R!h-dh5sZjnnz> zdT4s-#_^={{ikeAs#}vdtk((M>#Ggu49v34tm6W<6NZHOs(_dRX7s7haeUof(XA09 z<+n}8G7UddgK6)6@7xXk-wKAV{jPwRS*2Y$z6UjM8Hm~ESG>Rb^Zy$l#_b-pD5_uk zT!yJ(fFFy5{Q8IcEDJjUni{ng=c3>_)5O7%5rShj$n?7ya}nT1VX}Rf>4ivaA5%{Ys)1JVGTalu*z=X;I-NPN{IUD900^(}oEOU@8s`7v`bLVI$^o>d^ z*wv3Qk6vM|pz9&73JeP7YQ!uf=r15_-iWlqEjfZkUVR-9ibdZsTX42G59T%JzbNpV=o!Ck9|FGY>>NxUKjB6m zZr)L|;{eT@H=G%BgkZ6X28`#pSb3!pG0(P`Ed=y~$-@V?C!c-xgUS5|ccT%rvAN0B z*a8#)F914T?TiXZKp8<854nvXL;X9qo4=VFyp7CbT2+2pa1dk^W;`tS#4}TBN?>Rc@nNVQbR*&CT2lHAmSLu)MN4c6wL|MuJOahP;vC z{zW`mOXSI|Olo`BAJjr|oYt8Q^MGDExxV@k@Jzu=5YO|4yaj3(u*KVxUeiCe=@V)% z*$=k4?%JD`SfnEyqQyfWT?3rkSmU9OtlG~281z@$K#dpsq}##-Z4`>!l9K~S1sth6 z)AigKw=R{yXgSn+u>tH(j!y>{@5C)I#$UdHI`KP)r$E&?aZUrokQVXIj0d&bkOgDV zAHeNH+W^G4UFno9a0SBPF92hYR~j@Gn;sx0nlM)X&>tuk?#vKm4$9OsDY2SSaLv{` z>^jgEf853GLYTH0}AXZr}@#RN`aHrTq&uAaL4A@3S3bDDpW z3{SKM1|TK};xb+1=$KL? zCbr;=WuQEcGeMXDG3JeY<*_96eKo@ZN~TPvVc#GxL!Rsd`BoCoVrnd3m(g;j9}tQ& z<6pjl|LLqV5T?f37>seX!z z>;KNbKe>?;4X82(-FLDzsrwzaIp{i>d7HHuGfC*%))4j<#h7{)-nS+tfq{pCNi~D+ zB7K9G&`!+Z&(@>uJmWNS5LRYx;;l)3BftFeOEqGiyng-qDSg^AVw6*r)oLIyEO8r= zRu52Om~O)j^YH^|ypDr&#zQ}|Bgn#R7dqedm#^_Py4&Bd=GAn@m51-To5uW@e&-YC zEPoxh^R9d;U8f_hG#b;FueOi=O{d*?H_Y_L>9>4~cjA~{^UyY~>1ucV>91*9_c(q1 zyPi#dOegJMxiQh-gt5??F7UV_^zVFixbI`Xu6$`VhDqC%-)^ zxBaD)hmU^J$YX3eKiz2zYj>yL@%1;2j?;Ggn}+9O(S)$>@#t?+)sj`M=gIDXTbe)H+@D!L$)WElan3Kuh|0jR? z77*itTi~T17fNReQt=}!?qPw;;F^sA$52c;A{@6Du^qEDaUrSKp`G}tF={E>DIVZn1|&W{nEkN4RA z^Me<-$y&bo00ao5vhLFXA>5H%l6Jb zhx2YvzJC0C@{I69gb1I#0|a2IaczSGcFCjFsxYEZbi_RCeE8u9cPC$b@!90z2M<^{ z-<)i)EowP8=5R(0D&`*YJ2%sY@WSNkFA<_`&^#ofFO|_4^Qz=>cHOD}H!%o5bq+3b#Y8VQZCFkE4=e;q(=o$J=Urh7$gA|x?|u?6z2woHVpq)QI*i?$9O;D~-` z9^B&PLB3ONMsiD-0%Ej$XfNZ^vmIb5Of-}bD4i?FCfY9u@dOFTzM3~slvG-zOWI&Rv&sw4BG)xy*6bXR*Ugv5=M~rOepmE!%+@ugN==>DHtGF)0gU1v^4G@pSW_I;3q;8)kMJq>-O)?dGA~@49`O zNBhgKi{0UPCEjIP-+eUFGV*6-YIpP1*7UpXjyq0MKhq!cmezRsnNEk}j9>FK&909w zL&wwBT#l<>rz!8>i0RC|@5INI({3w6w-se&>K->{SJv2uWYz&L^YOhO>5SK%#rJMd z)6ZDpdY^InhM7hKA-+o^&o;gr-sv~`r@yA-8mD-FuaHy`)El;2h0RSo?1)WBsR z<^V9@zxgNsCx^58gM~{0H|q4FTL9)lcvPr59J}dEjjRXqU6hw*>okOH4ww}pG9Y-qX6O+&odx~5-$P5 zEKLppVmKRyg{oIlukdUjuwF+KW}P!)1YyoNjQ8k>!(G|Z;sPtT%pjDiE`OaZA2-)l z0>mt^fIT@taNgqvH-H#{gDH!9=dPUbvAYM*0yx8h+-s!$MReW;Kj4Ble^^@22JuV~ z!I(KDGSBi*D?!bOed4>og#qVtc<3owJZL+t-@MLs)@=WwD)P%F5}8}5Jec>`19$g1 zU>7Z@MKlv!sBgpPDd)sIdd@97Jg?ZEbEt|vfvoYVYPJJ#L(ma<-BTljMY=-0X>43y z<$M@5VtzPz@bF=Pn7S5w4G?Da0*E2s>`me8LElnPP}s+|#iC7MIe72GCtcL5WnugK zOS8W)pO7b8H@}cm?~An2JdfK}&ZsBjW*d^gk6JeZBT3V8k`98Jf|rNhdMK}2F-S3B zF@jxtIdjErN&D2%E`Y}_8YVjc9Q$nL`tXDMfHC)Ye@#BtLU4WY)j+ zumqn4%681*yxZh;58y@c$L&ID!@TAY-^Y(1Pk!~wUvV)0(+F%HLcFdf4{fLBfSNF+ z$>sNyuPJ><;Kz0_eT71WP=$1FP4dSO{g+AG>(ZA2a}*k_R|Qjrt`f!|vgHJ6y3HAz zbz{;THziRPfgPC5F=kNh!Qq^O;OIZVmJ>9~yhSORFs%JghpkCoKYm8IX*u??r6T+=S)qGUuRIV4wg!ZuKY9b5bFF6`<)CTimePb_ z>k<(?)K^e3UxP4Dhsb9_{ua%#mANr>f*U%=K!cCgh3!}%41l`xHW{+rBQO&I7Sy-( zTtJK`GLmeFj-wO^)FQ|~ytRV_%>M=FLOZ5UZVK^Vap<|jeY z05KG*#);v!VQ~r{DmU@5^Dn#ydVPqSDAn}&>-XO}2@u0K$?|$@(!c(bKm2Zi80s(P zat20uDW`M;(a337y;}c6nJdD@UkDt-o|Wcyqc(E0>SMw>K7)svoqYS9xa8{`0Ik)Z zI87Scbmu1bIIP$HFlfYhMhpVgWPC=9LyeKw1qqnZoLuN>4A+cpX&P)Xtoi6R%y*5p zk3dgV%Y_@SS-dWF=Y@_K{?c@Q43kcN`uQ%MW}IfHqkq$Cx4&-VHLniSughp9n`cd@ z-|>9cNGqL2zov@^e>%O(VNEv<*F8?ZdHj{zCj6T+<3e{sc4Jsc8Y{RPJ|6rrtm&JF ze%&A)-VLU|K!pt|KjY{pkFIn92u-K2Te?nPI^TVyX+FMpUgX()4DWn&`W-$_Q@8w# zCokg|SL3_+?sAONlE%3Gblm2n-MqA$kM=lC={kM=@ba(0SmVUycn5W8+n0G%eumgYmqTg82*fzO#DhO@K-jAZK~D{{0h%py zfG2Yt_~`^uIVIBZxD{SutmvFLHXg8;uwb1C9m)u@tZm0Yog%G9ZjVM+vP6 zj)E0(#_Mv&kX?GqQn>ZG^~RAA1{LPLo>D3#qGlXcE$S>g5~1qRPQQS;`Mxzj7H z&pGb}NsetzEUtZ6sPEF|&t7g#zIpm`^5ivJT+od1%IbZDc?Ao#8dlZ-9C-Etb371u z7huTkIrs11o_z4(gUMaaPx<82PbT;7KR^@bMz-uMa5l`fWv$|;Osm=%j2qHiJy6rgdEDC`Ze>Jxa_R)Uw{GS8b(05o;AM`v0Ei@8RpLj-4V_+Rn zE>zVgPoF-CM$F?!kD@W75HzKqdgF}GDrd#GRmrVpZuQ!w`~pzckD4yt61B?qHUT7u znn2Jo+n%h)Ddq4mXfO^eE#v@R3MaIBgAWG-B+R5%hT{oNk-;4>-ecBKvs+U<6|V$j}G^0rCmYSvn?y zv61+%pr11;l3C`M!L|?sW!g!%{37s^U^H~BKYx1>R0 zwu|jy8oo;x+b|kAeFk0)yf}U{wjc@!V*?Y%78Lv};G+N-`%pl=q3r^~n4%rTLBvyO zI9>o^1YZD*1vEV)rnOaF4roaw2b8lfQ`bJ(I>tNGL)uHN zGYeTiUR2-nZ>?RiiBo;S7Ur?T&;i{V%On{)APRmt6d3w|G15{O^$c;7Pr1~4z?<+g zj(p2MW5Sr^OYNm>bqo+gJ92pKzwwj5!4}(FP}nZt)z+l2`_>=pHreVBi)1!2BWHQM z>#t>ozM{-cZt!D>nu8ayN8>< zL;gSWNTjd?GbYKBQT5a9BXfZkP5aTvF}`c0*UyPoKDv$PyJ6aN=ow(set6CJclz=) z+()3k8%3rdTAcg7^l-5yQTM$hhRr{f{trg=dE9-ZdR&{HAZa=|eb#H=XG_ zqi8psVY=nre%d}A*1V1TV}O`@O#0cH0g{;s1zEKVrfBZWBdmC^sd3f*;DvG`oX{}y ze8tWY=#OaYJ+`{M#n$Bc&f#R6tw?)>EA(6c{xYeC%Nkpa_5lmL5_yM1 zdKF4P`0)PZ(;t3#w z2W10bK$<&Y%a=e*wxH13ZclP6K;~lNx&6TUo}*5W&`#NV#g*N!o=>)4zas7t+C*vu zZBEuWr)C|s_GPvV;Yk(P{)${|od6)&1-!9-<`FpLClIm&@Ztf+E^zHrJJ!zt#H<5I z+`qHQ)}ATn#5{+`en5>W0E<_Ty0yRqjn#Y+Nb}HI$Cbc>?YfM1&?;@bgv!13a_)^( zlttm@1fXYc=S{Xj<@^`v0YAL{SrAL0;DEkx2zcg|xVFt60NNq8gRKtv^eh~JQTQFw z=5AHmqOG3uoqhD^Fx3Vwuu5^3W#wVoFLB>I&ZBp9f2aXs160*oD?iC&k-GK-@a|x z=@%}nBFr*2JaqaPpd~jkp%o)2lS6qC1_eF?#NbB6r@RR1Oab!<>l8h*uC|&~*BG+` zC=JB4)?EP_3eAqedUOZiAioX7*e%-_*LSrdN=R35Y!F6U;o9`eKL8N)+Op~Ram$qQ zXU#x==6~MHGsA>9P*TT+DIiC%C$HwsZ7=56FtNYEow=OzNyo=@c+5jUn1C@%H@=g%9N3FH`A;0@Zy3sx z{*bccG`RpV`1=*f{7m|`EqDaLiZ&WC0O5i!UMHqeBj2h+ z9B^$wE!&Rl8$6~_cC}q;Itj)QpTODHLE6$ToQAeD@K|?LY4u$y-ab@7NY&8{gyGw% zb{Nd^G!Rp_C8=%G0E_@U01aa#;7|Hh#jUn61Ia}ClH+&-cKvVu z$}}Gi_wVbgVKkOMvK3B0GLmohkiMWzZB2p`eq;H^Jee2cw7KKfB`891z^Dg(;&!HrEqn~{Gk+(cG&0kx(PM3~9lbxkC+%Re6 z+j(kU?Ju3iaCw-18^e5z-*oLKjqc`WTzP8RHeRRGd1}Arn+W_dTshZzx@F84lBy{@V7Jd9+G-12@9^Vz+IVEw)yGzu8e-~@UAR*Sm9Q@MmlMHZ(5w`0KRu+ z>n9J5aeTV*ns@VT8vQ#T^3kndr{DS3Um&Jwan90h9@6S=KGK+8ccOmRmPXTYwWXE+ zIF59k2f-9g`$^aJqQ7+db+~>_V_eUO@%rlfUwrY!@48rgo?DYl62nJV7WHX^6|eEp z-;W6NE&`p_E2;YH+boB=F=pi803e1%ddM#mgi5+uYDG504bOpQY;{Gn-|XG&`bD}j zS2gfGsex|;F-Mbs_`m)$1hqZJ0*go=7H!EO5SnbD3w$%<-I&uiVbW_8GU(c)O57$uf)m1=`B5i`$cS(Smu& zp}j9&zL`7+fOxUJKY6js0k7Piv=4yb8oWS;RkSlauVx`a_&ypw3di17bpPSq$qzpH z7!c;e$%79bvQ6nG0z20@=Ufp1gkg4Yx^g7%lk{OjrOASmj#j6`qaTcP6Ww zxA0q!(3pNim+%IlZ9ocv7`1WM(dsbmHyn7o%h@@~3W5mxXa?;tCiXcyW{y5`gL883 z-`xbsHz25`tjdH43%UWTA0xKwAgxIGAdG@NJ19ME^v?%Ah| z&(S`8^!QQEjByK-2LZ2ePR!lgx6xv{JGsqO<8JqQ{qjY&40|??S~1qQhn}th#H^w1 zr7&n47FfZd%>YGpU@v(fzbk0iECZamRnK~n&b)YojAK<0#yRCkfAV=ke$RN0sXTw# z_k5cMVgkm{&vH`|W61-2)kYGCajS%Ysz3}?PJd!Nvx-4(6`!T2Ew-PY;Wq$zW&K#bZkOSE%sO#+0Wzo~6F)`m%Gs1L`pTy2Ls z_!k?uZRh)(sg7^m*%)PU$?~ zV2W<%G#|m(x;aU^o{W78pS11Rc525DX;O-m#TpBLe$C9*C^BOD-TrmOxOGl`pv^a( zn7}FgGmq8&DFbos0}0?yZP~)Zieui>u5E{cF}Nk=k7bWLbzv*kdmMR@yS#bm$i@vQ zk;KeVVy8+oj;Pyc0MRCH&-yoi|0igc+zh+#1cC6|5J<^U+aLaNod4OnI8k@ zFjv{vN6AwU_9^Q+K>^;agN%=%Uh|`sV+3PdYuC<_Wj<#{-A)RrxHM*Ox zfQz(ECr!ugIGye|P2HM~=X>*N9-W>v&C58=N836vJ&p7}Ez9a>Tr!Oz*`rUBfHHOKvd3CxC>U8@0OXGWo`QHAWC;95`Fw5q9 z({=v!)99};-t5+-IwQtglU~dct;r};5Pr3Am`z%wmS5KVWw(n)ghGg&%$dBL*z>_F z9`32nVZFo+Z(^EhGkD?f<=bXP*Fk0^9}YbX)q+zd$V$(T+LgJgf$vKVybFl=$N%_$ z$pN4CXrBgR`h1uGGAsy76J`Kr7~(GS1VHN7KgVKCM;Q^rAFVhCZIrw@h8Rv+Z0MH4 zF~xYRQRP@0-0>UuLY5_57N(?aoQy@n(TFK9#(WGs307cJBPQFFppa8(!!Suf=QbBa zL?%CPO5*$tXqWI?1d#E@BSDyJb8N}ryMB&P=tB(=?v^CZN#R*z;pka1UT?jDps>I; zCs34$+Awnn?I#HT-Z12%)|)Uq3&wL#TwE`yErE7|=fqf^GlbVu=uZ)(Pl@BgSRuvj zI!7mfE0kdY@J9e=0nLhY_z;nBwh92$oC3f^V}N?itxi_>j4=Sm%J%S;RA30LF1Hs1GfMiD8y6Ol-(^XwtpR3jDQXQbA<8w^%cRL zIOHCox`Y^s6{P&Nd)_l$*oyS}@vnKl23Xpm-JpQqf|?}M3#u8DCA4AIHt$TPY?tzU zl3Zs^J-LNx53u1FEf@tdfwW`389M?Tp1VVJ9Qn(%O(g3$E*M;*+Fj3qs$s0guzlaC7T%Qs(~0m zEZaF647dZt=%;i^nG$D+m&nc=2udxmyPYEM`st1!uiHFSUo|fc!jxY)r+w2{B;`D1 zG76}%fyaeCw&C zE^;;$I+s@Z(xf?On{im*Nl!dFDlkKy2|Jp&c&j0 z990L2^Id7gM8hROxIhdVF`f$(AclF9_grV~d_u(sgt0y7Q#HrKlrsAWFJEz!gPDug*0r3Q5GWQT*UzLq@LZb#y(T@UHpVo(UZP%eH@-BFiFAa8pnAnt= zd>CJ_M-XPF9mBgojA;t!svTjvf;Qom4q4!fr6B-bq%ou&FpNCp940dAI6HQ0nE8R; zbagwc62#wb|0+Yim+&%&{@1Q;NtRqcVF3RLsKf_%r~{_rHS;F`LuE+^v5)h?y^Z0G^tzhfj0>>DNaZFHG$$$RFv;1`u zoWpwEZX_vQGrn=O&k)Az612`E{<9yYxd*l;Uo}1ag~uJVPw4_^#JqEBlJcr4X1#{tqg!M8{oZ`q->JcP zrlVV0jp-ZTbjI=Z^S$$dGe&>oG>=Ztz%E0F>DPSqGoD5sT@Rgxbo#Y_r{6X~F^0ca zW5#|HrVHK`)A`WfN^gJjrn?_2)JhUS=*H=|c+D!fE3)}Cz3(=b;T>PMewt2STVt5+ zetb6%f*{)ZciwQublmngj_EhOZvC1!&ZIxi+n9%W>-447Gzepu@y6lp*Z$g_reV4@ zoiE*;hJ1h28Zn=9YmyVo^XHx|C7Zz*(^sENd==&b#SdO?O&VyNj{FEzX?Y_&QTgM1 z@4|SCMRf=aFy32cltu_qAIW7A=jg_r{@hQS#(P>O>HcYqkf zY7pw+zk%{$3M#^Ko44T+j4}PtYX^iOPOf2*PQzbV1&B$y$x%4_%0`JS{1$of$r&za zy)5vHyQN$JfLY+ZKo|fO1i^VVV@S@$e4SIU$aT_-oFl`{N&pm_UJZ<}Fn5aP%KkQo z^s+sP1*oelg>H-EIRL7jyJf>)Ox?;eW&4ld$|(Y=w;P>#Fe>hOq%E~E6hLzp0C`jT zTw+ViHTWEJ?#r2cki^uOu~wwdlNzF+Op50SMy`E5q<5d|so%VO&9%l`2;_KH$6;j`aeBIS62*#)8@%FwTuZAAbA*Etq?NF%PmWX_MXw~HyodLXYF zBxeY^Zui=uPWAz%{6*Psk5`o+@DO2gg9C5Xj`0Sf4Q%Uu53L!&7;mdu-?*MFO4gSP z0yUkcXu}i`19*Y3m8}AnpRGRL%(TaCR>!2TbgfY0DqF$n8X7Tm75IAUHfMh*Ww{Dx z+i+%B$)}%w#Enn@ zY1E$@Ne66UdczgoFP=W*My5AuC(nwx4#?!ztNfx5n56cP=V;`rZQ5`t2dL6F>6bb4 zfO06PO20^d6^Idp+4H6_giwpI&%5KmYuLT*sjmA~n?rX4F#wZnQ_4ZT0GzE6Loxwk zkYDLjJXDF*qPu_+Z2Qw->_-j62wdoPy-*q)YIe9a31X|?KQVR~jpZSvP~&{ zmcB{nG_1nIA{=u51)1o0at$~Xw)`Pauu8wzPB=Cl#3WOHGa!b%1=uq0kO(X)fJdvx zayZ8fb{!UrUwB-UHR5!Vbq!Vg$pYEv@{)@<`C}FaZ)rn^r#Wu$;R1-EE^{V~Jf;6z=O`0(THgv#w(@k0AIir z+}V<3pNF4Qhz}WcDycMRzy@rVXfHvYI^4H_8QZ81U3T06l)%$Quk(vshr-TIkVo*P z{H&LJkEr|lE?Gu9VM-NS(8YMtR^NobY4k&<4G5!6VZHgQwP_M>NH^bC`B-D{*A@g~ z2w@Ahs3F5o2Ko)}=E-k*+97RVJMb_0)~-jOzM7i=1MOwZVf8gl3}TgOp0;P|hlsYP z;o9NDOM!sIWnh&?3_PWhuiq(ajJ9^{3c%O~z6-?s_(y-89|LYA!S7Zh#@r1h!AWww z4GvQqf^?23eT{jSp8?J_sW#l#(a4-KS7vaJ_=Q2ZS`Vhlf5JTz$MRQ#M!@Y~+Xlw* zX+A`_!(qLCC%*c{FaDJFd;03tD+MYK>y=92Kn5nF5y;TcOY-D}KTWGYEieOWj5jpY zTXy?vOK&>K+CXnQrYpZrf6P~Z<5uA=X!UonbiQ$hue^OU#?$!T>32GvmhYz5X-MmP z)9K%SIA?j)Um3RJ8>W%gNB-@`8MpauzNTULINdH|r%&+vHJyR?n`)(Ip*7~AE6EDd z-%8T%O6^L+YZh+hc7Z>^j^FR{Xg|{*^D;fdJAIs?)i%C> zh8=Yrr^}?DZfP|AZaj^A4c9hI)9Fj2pXqk|E_>5zyT!=#n@5+!uyOq6H|FoHNq3kq z9&%Xk@A%8@bM~Uwq{S3COf=@e$jsvx20!U@YZC6xSj~En)hs$xYK5v1^M+sbW;K1m zZRYCC>+w$a*z+<`xC{hXS6h=(AyFv6qD&i#>rCetGw> z(=&wA0%BPBF+k4%Jfbawa9e;3iv>ZLEH+(UF)mz0q)zJbcz+cYy_)S`3{Yr<)mV6wQ&%#pbwORmf_u6y`Vl?6Nxt9F4H zf1`I>(L92n?%_9kva25;0(3J0fCaPX6s#c$-e6nCEv_^cjPZOKud6;{`^*VviTLZE z8Xzu4o$I_Y!-BgocKgoibp&?~mvw=Df(C+HRL%h?7N8d_@?h9w1Z1@YL?Z-jmJp1Q z0{~REQk4<9p;fq;Lzs7q(j4>(7H)Sr@jMY0=0|MX6o}d4Y?zm9OWFo#*+S#x#nx`N zC+z{$DENQy@B=geKfKKVirhag}Pq?966_cOohx&mH}Z_ZgAk^yj!Gb82bve9@2ZfzdJbq z0P%d5^nLQOME)0PpB=P=w$VOOp!L^W&s=%Ij|ba)S8i|KnB3wlpgLz|Bbp$`fSHE^ zF@Q8{w5QvJj@9xYEj401j9Ku^ty8|c?SKrS2}FAU;_Py`{2_peNPuTVxb-Zhc1ziC zuFN9!>((aAvBwQ(djL0TawtGKrjO_|US}Tle)}2Y$8@}D={aZJeDj3Ed(n*fHP53b zFDJY7wVO9qCm(Op__=rIcFJ^yMwOZ|f-!F4+EO!yZ|VvfTb^Cx)(OF-z8R#1 zEi_^Pa@?w?uHbBobf*oR>~EB>qL+E;cwxR zvhMh%K43G{g>%2NxIjdwXZnQah9uGiy;L)%&g0=7Li1uenX^Y}n27&U2c$uj;--i5 z)K(+rW`G#CCV5bA9o7qo!o;0%I4e0yLDh$s!pqHT`yIK1-3RI-c zeD;l1^OUJHW$;f$&?5v}^3*r5zQMThOW3&@hk&7iE&wpP1ICa)+obYaV3K99|I^E> zEeG52i-(O(?)<0QQrEZA7;Op^7|QOqp875zru+iNgdOj(9aBCgJUq^9{bK=k4o&8} z8m$n>o7<~8c-MEIWNuCkYhxJmqAyZb9oL&&)duLNW z-!;-}I^1{N9k2P(;kZ)r!pK`XZH@HJ-}er?9Bx>rk>cusF~0AH^@DT9-}D>kY5z{s zciqxx%!lb~Okcjuzw>B#r>~!B8ee+doo{W!m3upV-!;-{ny36UzIWa_{iY4U`}otD zd>?6DPNN%A|E}b&P%F6`qbt|SF~5e{Fk>1kUb?2iIdgY9(sy~gBXos#{7&EZrqQqS zr|n}{)A%kZB3;v(j&5zkO-Fj&`WfFionOP+NYlKf(dZ{X%PZe=cfT&!K0s~Y&e)WEla znElB={o}unz=jqX1Hq+i7LbB42vGttY7|7PfCZqxzLf?{fiV8Al|`vGiwT!-F78~U zDu_8HIW8T8B^>ES7?zS%iX@?%jU&^~a=P+i+VCVJ*m83bGo-`f(*uHT1FOr`pK~lYh>Zi!M*`s0>U8BvPjIsr(q61qze}FY*%98pBp|-Hj4skGH=FG4i;U2Fw1P`Sz(LFHGUDZUEH4H zb|kka%_G2AO0_oJrr}Jxz;+llVSJ9zEZE|l7q1y!MGHhV{hTes_9TUA0hxK`+B1aQ zXrAyhsfW?-0WxfH7RqzBCwW8Co1FuI7!Il>ToA>xUN+GrIp#cyUq4cKf5I=UTv-js z;aM=B{P5#|Fb}xW`8KyVte_dO${9CH9Mn6HHq2bwfSR?j_)}`>&eiq<&$wVKk^ln1 zvx6zqPwZH>E_IC%eo8qG)mkCmf?HkyGAz>}XTt2E*>S+N%%^NoIs>?(fGpk-Za6UZ z99j<0nJlsxUcPyIvc$F?Dh|E~EtJu1U-Pud2?r?eas!fw@A`e2vKU@lLG$G7S~P*) zpf%$8CT;_I$(Ek4zy2m#V-MNRqky0Qw+i^P0VtLq&&k`WX9NM#ctGzy=dtVp&YUCk z37jpEH_Nb?vsK&2-S&$>fRV8{;Lw2Y$nVt4V;K7a#`T)`TJXfC&Kzm_sr&?WO~b!lQk!z!>HW zffPF}CcuG$JLR^%^3I?BRbS~#QsCG%va`9GHPD) z40#s}iNKwAzyi<}{RCoqi_$C(j4WHqH}EMfm9DAGlA);iyXws8wet`l2ny%ifIXx~ z7X<8JPUZ?MstenPxCvvtlOe$vf=HVZr(CIpB%Hb+EZDcfxV=)a($V07zEc>lz+g(-sZrw=CpB)eU5U<=eoGz| zjJ<7Wn2`VutyAlkI<^vr?~+%$d)}9HHdua)NI8i=04CXnW2{XQBQywzsbx4{vdMdk0OID5aYMm*|mN!DZAcnH!|*j`+|L&d~27n zP}P_1o824YX?^Hpo$xp>m8~1T@h6-Y$5r}4*y-oHf2cqob2qrj9i$tu3j2# zro*+zKuYIHyW>jVadFPdrJw0*#e>%f@ z=yp+^Gh${GaxY%(HS@{j*uK!r0@`n6{{>;@|H@p|!1tvF-UY<`vp@cO2$^pfezh=+ z0L5Yti84=vFj)+;*m9BQqPiA`PL6^j7XXP1d<6j4K91Wg_Hu)f?@pyWuF%wyfhKfT zphF1{#21_dxRA}1!*VT)%t~vv7!=fC5jsFB(7UW7p7|pKF3J^XvM`r92}QtRRwV(QRHueM%A@V|`)#9ajMO*BQ^(sX>V!{edH z#D~~l%##lBoF~o{Ncd^h!&lWjner^K2uqD9K`B)s*?4}*IRK2MITz3YI8ar7N_z81 z4KDr<5nPYZRG0$dm9m=ZRrA@lA;Z#}!-vCL?!c)O88 z`4SpO8ywtdKiTKovGwPFKG}WrwO1QWu2EKj6$NpH{W)(j0vKE2u-@fcx6wAbLAO}W zcjSb!9|4eXm<~yB_4V!++Ae@O)R6~7E^+40;Q?1y1H$ZaaB+U|rN4XY&^O;arcRD0 zKlu1V_UPY2F!Zb#w1faA+(v;o#DbbLT>#c>E6=Yl?KM4>1^ z0uKE<&1|=E&f$rh0Q<20UOGv0a4hfo%UxcjeKUBNcJfvriy(k=Yv(`j&dFr6fEa2C zx1fPwjNnPiRcxyKmW#CHV*va>1HZlKuB|1yEjtf136m!EAOIonAs^T@BWY59*k^#^ z3gQAViH|Mt>0DL(NQ4qPSC8w`8Pc*_f6n%n-^HU_71@cD^Pi@GEu{@3c+w!u|I6N+ z{d$&V`CaEm+#7d{dCIY}Dl_e(tJ-di4OR;<$jC^vyQ-lZ84F}#8jBZR5E3suAR+mo zh1{~#k|iWdNFX7BthS9U9*~g#17jMx%2n7zZjATFormD_`L45m_r%Sp2H|v6 zr4&2j+`Z49_u9kR>-$@KZ2*)=!~nzqrbtu47N2}#vYgm<*MI!^RxFbF5`f4`z!>wQ zj65Ef6xdPs5^D%~1#}=UC1DOo@3{40S&U!4LA=+IHmPRwDxE#+*v|LMZ>k^+-?G{F z)o+9T)Ca4^2kliVCIAiY+_aSMZ|RgeBJThmP-phT2rNUSQv<|!pw?B$SzzCmgiPM4 zzhk0-7zaeJx%oEp?ns~r9%)D~;noBoFhF0w$JEKMV(F2~YK_~7gj=TN@HiVufFEUL zE~Qra9`I2jf#1~HK@<>USyc}eq5pf|``3mmmjK2w|I8#}Y*#wYQgGOg*}Hxd^M{2^ zqUnr|7}i9;ACA3bYQ8#S)wq<%kRacU&^vS{qG>`4A}76xPBKtguVd-Mk3ReXAu8*A zfc?8X61hPQ-Hg_>$izi-92otrjeZ(!Mq>O;XME#mI-jv2HN5ceINGK?omb~$xW;h( z%*VW)?p?;|cY+euWp=p!=Ha6;?=F9uxAo~T^U#<_$L;*I4L7aJPUqWo?6mrur|+FF z&bYO0OPAkq^*3(k@B2$LUGbMz;8;)-NW0wu$n!n(j2;HC;}> zH_@l>#?!w+md;DN>!58}*0J;Kjy0Zc&9pwgcR3wyTAzM5PWyHF4mM4f-|@}cF#WCX zG@fqb8OQJrvz$)f<>=St>xZ=GnO|dJdL8!-jwYca=GkzM&9sZX9|>(2Z6E)hm?Ebe zBG2hpEQ{x(H(lT?WlZK;waeyBacVs$K_}Ucz8%(fHDy}8~lP_Kum1}>({vcMNa10O<1-~Yz&;Y*@$Md=cc{<441KT&bsN#z9KW% zrGr#V-`Z^P?yq7@ST`V<{Z6MC9Y$o6Is!3n-rZ1FIRyaamqC}B5r`Qf6XKA3d|VH5 zM8kK>@u9%f3wP-wV3EQj3|)X2b5a_na-6r0yvywS>$bn5DhYE5@Z~znTd%^nE>n#| znC{&zfCKcxDCVxXvA^uul>A;w8E(34he~!;ZhH>hA#js0I5K^S$}hP{1OUWDDu!~s zp~v)R;XoFVxUe0&NGhlsM25q&=qX25EkWB9FC6YmZTR%g_qrVF7T)Z`Sr6eFSZmppk%^N}9vq(%H+o^$G@!vhD9p01}nt zD%Ijxh`to4+aQQkw&n5crh^xdmhAsNY+ly9_O7i%d5+oaZ`;=bsh}AO3 znv_P^Q@VsPA&jv_{_9A_2*f<3OqDioJl+IYS{(lPcYk5H!O=6Nk?ez;*8!ZU|3i)v zfBezM06Me>8hM@d5>i;XZkKSy@K*twa_u(neZ(Ta{73SOV`7Y}v(UiZ_zv}RotObJ zaP&_BVm3LZwaHPrr`Wpyno3W(fA+SdL%~HDI3EX9=59b3#u#(dHN=_bequJ(aA&R? zHjcZ*;*VoI0F1V&;U-I3#`Vl`>6{$h$-7F>Y5t`bhOS|*^FIJW02W>>)8dR5#ukrA z#^5I)qr1SFdUr!50MWW;bZ4C!>&+|kih8Eb*2%h8{k5YXgyl3h=rGP@x;?I<&1>2< zlV&k5+lAdvzY{fW4+uzM(qZ;ckjGohC;3|rw9;8mBV}r5 zCWU;17Se=13?1?0Rg-S~(^hqe^TEz%b<^6|)ar1-YbMXh9nVGwg@z zc+$Jz<-tvUXPCpz8}qfJ1s0S>Yw4Ga4Zs<%NYB`De6W__L41G@zr6&M-Zp<4aJ@y! zY);@CpjIG;76z0V>t5e#zLoiIdVHzIjYBGfpE?)#UE@5xVzv1TKOJC|%#H zKDFKpRFQ90nyed$Avem&ti)EbOkH8*6k1isIB4)AVoSsrZYpnH`}e>1%NzmyX92{d z7F8EYt2fI%aryRQGscNEbyw`p;UYR>l!)o~42@ZaQd&yY7_#l^hzU@K|A~1%m1!Ku zu6zS8fS&3iWg4dGGEApmr}4ds(s6v(Xm?o0?>g#l`IaHsI}Og~$}+6e z>hAJ8jqe)cbXjfd-|h~dmSgx!HCEzhWtw13fa$w#jh!=f>*pZVGy%Jl^)s#=V4Nm2 z&balPrs+6_>pv~O<91$#x7&Er_|rO?&UXir#&{a*qTjSFf-PM~yMn9kS>oWS?eERA7<@##;UwikxcfaG#V4cl$45{2;v?(V`j-D>= zRK<E(jt0`JGdst|((iU8%Y`R}iS1;li&+U8~a(IlhEd;33UpQP#NCq5@R zz5-&NBigH%pK}U)8Hm{){=wh-ok$1x3!Rc2+HM4MuANPw+lCT977*cKW9CLR535L> zMfY9Ef-fGMQ#W63Ih?p|`<+8ROxC1~pE>*P?%DAA!@C<&AC?fycOy7%DDz7nn_)MC zGT2!Is8isRk(tWOHcQgT_*kqabs#I5qqr&8&A$(O;M65>6Lt1jPqw_o(XCXBk`Icu zpN9b#fVqm~#d(N*HNbPcC&Z98e zNH_qz2*hlmgjW3^im@x{@)&0RG4WP8qOyc^#R7Gbfmw%9-&axu=Gl7Q1c-V2op*-o zue}Bcb7ffN=+6@1%>p0*gT|iDIMCf!PP=x?J<4c>*@I)7{o-qH2= zF!lgq3{xSjSG-2rf>I9DUvlg7;lU>W0|5F0U=W4PEJw61pe&c`m{+d>AY4LvgxjCy zI4&halV9qg>oIgYKtkk*G7gZ6IpAH|DcK?OAbq?ZTIqtbD6~~N#hatnRdNf+;Sr|u zfISxhS1!^29+^1iDB2!*+E*UY5rmV0-KSpaQxW9yMkj_RM{WQipo7%--1H#7D(0M~r{61Fsox`e##Xd8(cNQa~p z;1Bfk=!8;Lr}xt#`hWE4o!?>Uh5a*men;dOYs) z&pwN8A#c#K(lQzzQIe^Q{vg_;h`o|UDx@~d^+SoEGth8>v880t8N&T_KR3Kkd!;r! z0=5NkwKYn`Y@!Efi+)x2j<>f6#N=vob;M+aaXoP?)!O0hE16|9I&;XmpAnf!mHvgw zdg_sW4x{M4A?u36lpLr_Ns!32LO>!pgtO;Jyu75(WS#@UxNgiI<|+PJbI97hwMf^S ztTVhN+&E)AL5GC#M=(I?4!SIP24FHD-p#{0yH;oiV2NGQFm;0x{R{vRpv3kt$BkDC ze*r~o59yPK`Hb#bp=~8$#?N?R4d=zM9KFHa(l7Dsts6c}N zF_f!rm}Q<-ff(j(q-@Av={9MvM2sLZ)g`H8^aO<2Ay4q@fH7VV9lbF$SlZauw1+f) zuWG>g9j*Lg`wgR=KF&#RM$$_FIcps6D(uae;ZMRxASR#(els9O5XLRJl73Jt-@fRJ z>2G!Qz5D3d5v7a=>F<7-+6sPWzSzTllgH{|TL=5ZF{j=fZP#^=V^P_ZS9^dOU(Pk@)n3eRb_R zN0adT;N;e%yA<-j@CY_AQd2Tk+`gNG#BMOennsn{Buu}Kqua-HreUNeuj;UJ>t`JE zpO(>a@ScSmSN|y(qTBp^%tvD!^U~;Nx@j8yx=eXb({}m3cer-vt9x44j@RWIrs??I zmQLrpX{=kD4l_-+x63e0bNan}U(#PI_OmdS+l1^oNFWJ#nvjMYCZXDmGo5IBO^2Db z^U-D{N`4(K(YrkD(;d|1o31;k;~8c?(~dWu#;~rN={o(ieA8;$HjQyR?df#B8`r1f zb((HN11^?hm~GbRZ<@Bvqs!54`hIU4ujrcjzB*!X-*_()F&s@o)Z4|-48w!*=VV#J z)5XdhiQodhCv)MV?UxJBq>Jq*T~q1BVs`lTZ-ZY-NAJ#NW>>)YcPwMHo(nR;$=cd6 z+CGW;3WzzO#Vgm(KLuV0#QfdA4kP%O4G&BJ8^ZjOtsM@afET6E1Z1*_VII~mZEU=l zmbo6-4H%QgO<42-WanL*y-Z{#lN*VEE}6l_JXwUB3MU)EV;nZ8Z1~(1M=C}zhO&JW zf%li#=YSaeRaPj25Jn0+YXpDX-k7~6n@gME!@G5%nE^D^GvG$tZDx+suu&}_#&F`X zaZq+zbWjGspmU;!pjrv4DA27lFE^r(uQf{n3ZHqcEh_B=;-7C17ZsJa_h zZLd0Z)9sbRhX5&p3NmL=x(gViq)34eLyS6-C6p}VZ5!h24Iy^ zNYQTt-MwY#IzYwM%NNq`>Ur_@B-eH`_1c?)Pnn z{FP`?e~Z#8N+a#j7iVe58v1T-yn!^*o8RE(stbG*4~V;&qeUL$a11Ctq>|umfTLZG zp~(Py)WUD5ijwyMYG`5rw#R@tcK~4S+`XUYDN39#bJXlA(nJ@aRVFcC=xNwPsfc%lcqonZH#0BG@tmySEa*KDLwnE zjwL}FmWN1WAOfNeD!~O6gPY!bnO4cO7%}QXel{^{Ep|W{P=n~)fu4ehr8mTN(YUNp z)ZOvX8(Ldp^Wo2vpDmRRtk}?-JfNp(l8ylF9DOnlc}77jtV~^3O&(;#6b}fZL`g#;sgEgw~b6wXyc>KE{`Q44TGluj8ce^iV2Mz*TfUFf>zQfYX~`# zH0u}3HG{*{VL717sa{Tf`h>de?C2f8P zA#K)K_0uQ~!-Fi}*w+P+2+HKk3Y`lSaa|~Pjdyy%%F@66RIRPQwWn;ysCCeW{?f&^ z)IjWVtPA-Pp8(vfvA7+R88_Oqr@%b@1H2Q{apB0bE?qtq!Ve$@TKwfd|0e)qxCIaM zRRA%z#oV7W7L4a;Jr|^B#!`GcM%&Lv?YD&Tr;a9ZLtgS`E|3``D`O;ur6!IwB0z>E z*4mOH9c$7uZukb9Es8F;(q(IonZ>#Kjjm}`lcze5p@dNc89prL^j-zB z{S9YU&BAcTPk*c5`DwSAmS;Y=qscwt)O*qNHnOIy?R()O9g?K*XuuAgQ1 z;OMX}zvHkQis|>RkDkWWU(;^$?DCD<{@NPjx7)a#pXKY{?bEHF<(sC<>G-}ISBaP# zY;0b8|NZyA>n7&`Kui&{7RMxH@p;C=RBm%)(RT6VgBNAJ1f&AS^CLo-k!!Iegm>L4 zCw#)~7UpC8scFBqr(q|VRcQW>5oc}^h)}zwh#?@q`aYl0tG8Da__?LP3xSxw^VJ6ZY1 zc@85*sLYgEvX2WxQ^nNgFPl*|vymziOFP#&0T!9jX96Bn$xXS(l9cd}gAj7qgGFg!(cBYAid)VgO~-b5dXh3kipcEuU8#Ap6u^@{y1h5CRUm=9+ZX&5Cb?OJ-mHLoh0+tk2-ri z>;MLR;%JY6#wNg(dR7jQ22lr#ifk31zkcNsdJIsai=@&4 zW56E(4$0e_iyUNbz*Ww>jEeK8M^R=LY@pnKSx zNaxH$X92LAZ@fLc`OY_UwYU^hr^+sUWIBNs%T_OpjF&)--$IXl34mEYK@Ax|8MJN2 zh=AO!+v~$mKK%lzm_3O0%m@u!c-sPy0dSlE-i6kXro(gOs6(i_q zxdM`~bIj{N1&kZ0i;kFL1!d@v_YpjBoVg%b8J_;3gf32<$)pT4NPQEn+zheI+t@@j zYCrOg;T`dF_(DedOoo+r-99qdaWjLQ2PugQ zN@ut{WL*S=gZ8y9+W_i`WxZWbYOO6GW?vu%0MVm|f+M9nHPdQ~8XIkv*16pJ@0FyY`*oVMQ92n3n=E zO2n*4Z}M8kEzPsW(0MII9BEaa>Zvu^*@d^(Y{w+QH&M9jYF#ZH{R7=rjVi zWjj73#3E7*~HEjdoTG?9N~RHXYCR4)6SY*O`W@%YSZ2r3(>yethqmE%rfyBg?>MH> zJxyyo^KS6N@Gf6>KgMxa)NP;U)p5GK&fj-U`)eE4X*=9Fwc|Re+v&I+r_1VmtoOe& zD(ig>iJ0%W5Z~u2P`)hG-lmvjthN@bj!d4udY44Zcyk(Q^VL2{F-vN6jc4;qS#K}A zW8PgxY-4u0x=)PX6^r?4;U_W6)f>Ij{;%F&QQ+s10xtw&{`Oykp?l(fFU&>%((C3p z8yIIZrrd5YYwP4Z$>yACTpKXH%(&HW5gixo%sm_4bHusj{MGh20sFi#9AP$G4%~?N z5KzAsQkJ__mn6#^ff%J<*oxz?6a&Bv>xCOW_>gS)+~Z{m^T&-#Px%62*kGm(ZayiW zM~Q+3%8`lneU1!lVH8HnB=Ib}fd>>g4+FUd@UX-t*;|!7mLh{MGv)Qp0x{OrU)KdB zWIjD!lCLK^bM4XgH`yxyV#T=`Qq(4g?ql!6W?y0qTMARxvPX zEdPLY?+|Gi6%>12^%JCF?yo;U^23tPBFV6g^4yu9(&TO3RJ=5U>BtG8NKl%cMvP znK2nOi*((Wu|7Wbnr(p}e#iA&0I+n3q)I>qxjg#@I;nHTQ7}gyhbWSLeEaj^(;xqE zxcliR03%!*jlLFjw_Lw@bGW25Ou!sH7~JdG+pi+-nY6KjS$-0>#^nWQ?Q< zH3$eGaIl0@+$FAz7KnL@t{g$B1JdttWXN%$j+iUx2wDY{lPOdx=c&qt0TeRkXuF`` z8T#9!N-|^7r$D1TrXA;_ z!;e23KEHJb-81!_U7<~{-MBn_gDc8c00#uXZd^kr5E3npQ_IW^O*N6pOC2tZV-^GF zoyWOW09;n+hjY}$apo`j6pqqfXu#dS7^#~yD^As38f(lIkNW@TfN&1ZKf_5^4QnMbb zsoT7pajtjQQ|Isr5L4^^A&7)!u>_p}F}$mg+%7S*NEtcz$9HLwLZl0R($irS@L{B3 zNyB&yR3OH6_bh8JM-Qb@fEZ~~AXPpO(#5)yr+v*Z;3?aM*EJC+8%4b%5F@Z7%>%|1 z5aYaX{i^Gr*+)X0ZQ3CKqrJuQyaNamAO>1^Y*UGtw9mF^D93-4n6=Mumq3ibyFg67 zYYY_E8^M<-=f%zsAA&JFIi5y68;B{Ty{w~*6+sXOMBQYFB#ig;~#zGy&EEMqB|o3+xQyoj?-!MH=bcGS?1%rb~nWLe)MZM&bamK`sml? z8L#uye;TLr(ZB0rT;2U{oHku11f0b&%(%wW)^uGu|1Q7ZwfpHb+U9TG+M3R{-cItL z*3o=4+Q#qv^m{3$EAUbZn-nr7pzbDG`%j`iM-&Ov9cWyM*&XXn-+?m??@lnD@qOQi1>)y8}DG~D>KU}lxt4Wph$P94j*uwK3EZ8cS9Dx|) z@nnIcmVTGqi`6H^zx;~9ubj`{nnZ$x)RO31T4P54AB+w9Qq=au_Ji`uyrRI@g#s@E zV*dBv1&G=5Xc7~a*_*TX*fJOH4+6l907=A@op=~Gcrq=5Aa43RJMS-~f-w7RQgcL! zjhP$dad%^8nZJ>Qu&nXPubV30OgWTG8J3j|O3JH^pTGP)JN8_>APk#AIBk_xdb5R$ z0>!6JRVKx`o;XFZBBeq30c0pgW=Y^hiI{UVG(e0|sjsEUaJwmV126Eh2J`E2B&A}! z0#y*ftEvS^)M4Uvz5+3(c3F@KKf}-40!4CFwxw>8K^g+)S_zCjq!!eHB}J9G@wm~U z(hD%pZg15$?&D+5>^>xnX`Kw zL%EN_UN`gJK;+Sp$4FnealV9P#~Mda_9%0U<25p~m(T$d9VXQI5;y8x;s&CtH<38G z@ftwPDpEdt9$>hYj8S4k(8-OpQc#-o-DI2)lzzzC4Iq%?CeltR4ce7<2*mInNh%^9 z(?^0<-pUh37EtJrc5mGMV)*1oKNxO({9&Y8u3ot~+~iiLYd5Z=O9h|^=@Z2Yj-h9m zOxg(XziFpy5g}JB{u}aY^UQ)re3f^>67Tp0T?;3{)SUO=`Zug$cU*qW>?a8l=B#=lXWCw z9wN~sGh>=dSFR0jzWq*su@!&}L7PVaHhxc4^y}RA=-cXfbrdMp55tHQPxK5ih6KAD z`?gPI#2Z_?xiRb0TX%=g0Ae1X#CHd9#$V-cT)Q;9`Nj=^ChCYJmdCJe0Cuf$OO~M4 z6K;7Dbh6$u`byt;^|bAG{7b)O6!d!J&Hiv6oPlm<0M!H_&w1Mxk~0S+5cu-uq^E#g z9+~o3(l#KYpwA(+wcnzw*Rdr?Nv{gT$ol17KOTQ&R{|)%yc1OK#qb4;v2IRG|MSH5 zJ*HSfUzt9czbOfK#jvN`VT|hr{VvnoYoxZ1;7r9dc39O~PspTz82X=_0>}{2qe$Mq zr8LYkK+N(|NyjYAw=@j-vWv3c+`(jhqw?AU&-+wX3B-N4UymlufEdAQ#-4p$z!EdT zw(v9n<1g^ek7_5mQy-#dy>@Mq4p|9J17T{NFVH({tw0PFVc0Py$`))@Crxy;kfC>4yspQRJO-wIm`F8}kH?+V6Qho&a{}g+tCef^Jj=$8@YU)d#shjn~UGMds%y$_7{2)S` zq*0DZIf}`fp^@C|zye|%n|zNbv-Idpz!>_&`DJ|q#8@wLkcP2SQNBB8hQIPF-~CE~ z7(u40YgPB;#X9_S1Bt#IwLPVu)rW@l*D{{vIIj{hNtqyNZ^&ryv&p74sS+^`{ul8- z6|w4GrJ&=1b=ZVR#QYG)-A9ie3B>$e9{E*s^&?!wtatr<_c5&Bjqh-1KZ_8oX}(vt zew~;8Q)42di^>KXN>7J+Qv1#rt4_= zrkRGBkKv~2^4rh!J{{lmz8l_tx;x!855wEEyVDEi{Mt++hNDR?z-}C$Gn7YeIn+0WtsE-}-CA&eKOQ61DlCXLHMbkPV^lZpaw#>>AiB5T`b6 zHrGwBn`}4eJ|~O;41r7mOi_U`%tCfwH6xjox*TL1wee<7%x&ml{^Z@L?VVwlln83VJy{U;4kqmwTiY2!gv(LUop)=(R4SiIqXAx z6~ZbRk>g7E@3U4Z)u8^AMZ#ss;Aj9BIBo(HDvkV=R7nwankWgQw9Xz!HgdxbK#ISn zQ%(H1(ilcsJu%*x<65Q;5pMz7;1-~JTv08@RVP&snanmie$9N+W3U>u(lq zD#!n#KkG4_@(ksx$Ljq6h@r7O?Dqs<0MS*@Dr0zt`bmIA0Eb9;Ko=^8eix)z9z7Vo z`0&TWt&jcysg|wb3dfR^h`ETO-X)lSmBY>fu9!Z|CSyU6MX+j1Me(!`4w9n1&U<%Q z0zg|uoLor_mB!LZj7cPZU}kgWH&QUhJp?S+LR!T>Kfj7p5nzJ4Vw7M~azh4228x0M zQV8Nm4#%DIM2QbUN+n#PsG0bF$ND=JCWvw9C(vU@5BQEf0$_QHRF1I21&$}Z^G&W0 zf9G527bSGo@7*14ef~Mer#4fzV@{AppvO6LB*;Y>&NJt*^jC6a8{ouovB_2D4<0`q zZr?*!3|CaE14htd1!)!Os?-4NDf(j8hIih6Yk2Doj(#yV9^PLMh-J?@x4lux<4`hL zwowprVG)JIN=q>(ydGUCpmW@|L`4}p%r(l8sk3bYF;9?&dBpK9k0#kKxp9Xv8E}sA z(N~;1-+Y{-0x*%35r}bY@n~mlqB7Dw%sFe7-)h|iXGsV%D6z9VI5sQiVqzI0c-VjL zK?*3Vo}$~bc9rykYgL+*b<64MaChgSZ5{Cn(^WS9lta&_UkZp}eE}F)SrUlBy~tX` z@SpMF^qoMAD`<`*kzPp@L6|)N5TC5K+Hkz zz);9IrTGBg9t9PMsiR5f0jW-a7{N_yN4#fbU|S;fL%xv)q7FG8MY*o$z_?lCqo|iP z)_kJ)m%MWnjLG4=bu9X0!Y*}H5@ripZterbaBb2SZQW*>Qcp}k2J#l@qxvz93@fDV z7ewGf09FvDff)Nx$-{Zd7l>&|CnaMd6~n;to0IP!^%2}IrM)y1`Y08{#L9E*?L`7G zfHAQFS|VGR{;vMZv83_6fEcJjKFP*fT3^b{x-$Ytk=&86_6P0oJL&r6h;==+jgDx) zYy8Qa!88909xWiIBw`q+jDZ8_ly~TA*Skiff1Hbqfn>r!$+rR9Iq_G%_lv{zulQ(^ z)tq%$b=%2bn_zo7!YuCSn7vn$ygui567Le#e)fo1c$i#<%P??KZsY)N#7pwmZ#Bv!{)FX@!mjNsuN)cV-jlMa1g39j2{m z|9Y#x=3|}>F!*kK{RAJn6TAH#-sRgN8s9t4>2f9q}?+U6;+qMzo0%t<2@gM6d$iyX(^PXdu-(U`s$?N4@Fdsu_n&R%ji1^bP=5`%4USA z;7KI>@*zp+t3R(O@bgcBF99+9^8CO4&;QK{5W~iHo~O>?vyo+%voB(!!1fC}K#V&% z=GqH^7y%eI!ru6@2g9cnfScrOyqrO9?8rxjg-YJ!DpG#sl<{zLeJ0=!iy0eH!Hww8 zz%7I6k_0qFxrkx2VLk$YA$!Ugffy#b{%-!7`HGl)))&f6*|oWx0WtVR4-9220+P&A zIdzWML^p(s5Wt&~BGwN0pfrpMdzGMBgn3%T?Iv0y0F%u(@#eU3$A-FTRWie0NYfU| zkOnfV0v_s3u{@Vr) zbb*+)!N(6^z*}(CcQc3WQ6bQmDA3*CQZEP+DYSb*hMhWiBxjjpE&?ad08*X_)^OGO zqc1)g)<6G*o1vaWlHelJDa(<*fbplkivSTyUnpJUuh*8Z#LCvghmmB89v&Eh13)0J zmtF>l5xm&}0P{wx1#VmNCYU1#=TWZ%=&qvVM-Lw4DARA6iW3r2aQ zY2RaEi~)})$Qv8&Vw^kRyTzn@LVz2lqFEtAGLkU`o4pi^n@1As9lVio|xye-2c zVy|;VY!zt|B}O*5Me6?gy?pbW+x`mgmDbuG@v{96Kc8ZBWLA$Nb+b(!4*@o}xen&u z{RhKcz?e;>ssusyknmZieelAJ!^0fKdjDJB7{2}fdq~S%86E>@Z7~Lwwz1z$r&41j zBxF6#BAIhmKn!~LOa20Y%nG+mtwCpxC!LW|;W6x(x~d~)17Oi>xi_&b-y=y%-w4Eb zG^x0GXv+9dqRuaLSe<||jy=E_wqi2KVa%aIk3>;e=B#OyIv1xExU(kz&BZJEK7X5yw?{aBT= zH%d5lE`1+_5joUNgQ>h!CK25XZG2c`DL2xO*zq@vE)s}wt{*j1r$dlO| zabi8OZ;wn_zLjMq z9u^^9uC8W1S!GRd{VpIzeLVId^N&X`-=jwYF#<17q2~s3cysSKU`zus0eftlM0IoN zf65485!-Q40L(n&-(#E3ald(I>1)?nbyC^CIsSux!f1aaMYV~abzw|LKn)lN0(pCl zbR=Q?L4rA45;4vO#xEo;X{hSA+GG1=aGh~IvYc9z)lFhqu2H$&iNWSKD~%#WzL_>q zKM78{3TJHDaaqD7K#?M3r#}SubO$)Z@f;Atd&Y?K&%FVUYm@WaIT?wV@BOji+9kkb zOJsD<;MNPh&eY{-s)FW-p^-_HI1eIg6S26+DTg@0XK~-%NoRXAyrp3E5eV z%thv#Y3-XOqzCPPORK-y&RyqZqNV&Kr2np8Lq`lYK+NycE_KA*A%aJf>#ciQUgy(s2#)dHr%k_`PrEz+&Re_vx?UZ(^Lc5u75Q11 zCP0_pL~P<%rzW~_ba!0se!4^PnsuH;6?Eu4wHt&mtn<;o^J#aNuYY&WX*WSB%eY;> zaeOx)pXmzZyX6~4xA8Q-Pt)n|;Oqg~?a;sLqPrjCNf&MXrfn90(R3Mx8Aqet{+-r( z>9*d#&aFw;v2VQh{`=o$)n(Hs!$9S;%D4@vWpB3X;Q!2H3gjX~rQR1av=B|CXN-e~ zqNlUciFBePro2seV$7j@%60m^W12DXo=@C?Cm9pX563I>iUMC33Y-RF_I9_1zxO}> zR)82KQs$XZO2Vk4p!5{zW-)LRiS2&SO`N0N4TVgnOuo#48}ooLFq<+34aB$!YrPHG ziI@kQP^CT2MJK|^FThI11e}p6^`X#gX2XrtCQ$%J21y1$#l1@69Kv+Ckv;)p3}*vK zJ*;PMl%qhG`uk6qL=eO)pdevqdNjH z`+xx6%%r`A)W9yfLp(}#6)6w_g1fiBK#}iW7J_)W54vBVHF7ve-T%ath!me{bm)=WeEw3^BhlF;mC_ekrus? zNQo2Lb%sr(*NkT^x61T^|J+2nvS%|ZJz+TAP^)Ozqb74)MXW?ofEZ|TY}){vyq!mp z|9OCzXV7X9rMm(_M@UR~Ea^xvifh7;IZC(l`2Mi7@faHI!At?5(N9Vn&BNfIBb`TK z1Ytbt;~E`pB)D4J~Y}wA|=XnsaF7;&G3VG( zQc7S#1-8=Q!sV;O^&2+ zk9VnWbm;)@n5PmyK9LA>OhBh4ZyE!LSp@i6VD6j+%sZy8N+Rqt9tEGCa66U9;{<~Q zU`iTB2`btl+6O3Pogi19nkV=(W{wF{CzU{+`vw{2D6h5ucmxCV5A&BK#RSqyhesZw z1#IwF{9^7|yYh`^tWu^cALPs0H7^tII_BCq+HpIstsnUk6dSKPUWcFaqrw1Si4ZV` zbx=JqEfM22%?kk3bTj>xMBH%ZI#+9L02s#CZa;uA0b*Df3y7f<=uUJV{^}rozti%` z{3J|*@?TR@FgVg+{hiN67nXL$PCdFySuZrATYN%i!I;pQbgt9*&)!KB4xRa+onxRW z8k$fChX1G=hV>Pi1c(ukiA0R+vEK!%>m-IQ?ggWZg!}}z)8EjffEd;?>h9zE?K-|P z0x<$#0${H3uE}PoG)(J;Q3^&7W&@B?AZCkqff%o__Qt3gfS2Bc)}+teV*We70?6|n z$Ty6!@4WU|z#WO0lM?L)^fv*H>1n?{*6om1| z30EP37uQ|rP2cxzNqR7n$X2k3JhihP>t`8C-mq0CPv1+{%);mx^1(CdLvY*yVzBe> zfWicn!FJ8hx=%fns)|&Z^Uv;POytS8nZ67}b8r_WI*v9YhX&n8nocS2n*_N4$ZJ2{& znvZV7HSN~!c=}tud1}nF8ej3_$sgl%8pAEid@bMneDCsc#;t!poqyM59K=RVnf*R3 z-!Rkp=x-cNmv4D)@U*)e>!Z=HjbUBBZcUfh^(o(%@V6`S5{i1R2np51wGK^)wk1dt zO}G9Mvx(b8Zvq=`SQB!ZXQyvJ-%Z!$``*TUe7Yk|)8YC}%hBEC<4pO!8^6=&uiLoo zXB;2vYyO>wZjE-A-*NTVuTAIGex0`CPxI`0>(}MID1YnlHq0At&O730Th3{}=c1g7(66WM zPE-EW$p*bLuPE>}p}>oPnBV>H|0)s|55pK22%|&{QW0mo8W|vl*?lf&^WDhRd~@Qm zX9`0X5Qfc`SJ5g3u&WKjsZ@*`_54cd#+>+>gXY1?;Cc8(#PJoy_s1?74{r}~gIr?& zifZHL@fSuniI(n0xWFA2U4WQ+hxx?c&9q>Qo6s;~?3_*GMmZbNY-C+c*z6L5FN_fN zppi<%=w^=^T0{Xa74uhA!56PTJx9f(HwJ%|^DYAzoFm-Lx0`uw%gWNH9~;~`ff&{a z0W6ISc%(FNdt6CC#hY*Z)qjbNdRDq{jde`PqevdBd4^=n9!%;6dO`M3zIy>_k&7Hx z@fNx-KK&#h%q19I6`p?n`4=pbRTm`_)&OAO1lVf>QXnzmQILQ9qaP2Se)c&4!u;?H z-}xixiQ#HtK#WV=I-~@HV1!_dQYjw!@w)0o?0NL5$RQSxPFv|0KPQ3?UKMLU*RSfV zjgY`cX%BySjnZ7&0S8Z9SO>+`1fYi*~ z{?2fo@p2Yrzel@(GXOJ@BBCgnmlEfn57V04gUCNeyFZ&}T}s>=D=Rn;_VQOIOm*>V8q5jgmC(+tn?g zVqK3Y?NB%K64ddV0x{54sThyz_{~+~W|8(;*Dc0LNvstpq!3W z(;oskrLX9C_{Qt64ZrY5-y7b2=Pi1hInJ>#LA3z0bovewKVFfp{u!A$uScI#0*v-3 z73T3UrD2qIT4JoJC&qE)73u;XUSYjW|L;(De^5CDU=x6;r=()4pOsr7sq08K0_=0? zSM|dP#Dp28y#fUu)oCz>A_5|W`D4903L_}jp2bYt1}LKO;Ifo!+Hb zRmQpi<@v0S1kt!1F@gSdjD%q(FiK-7$2B>%Ca(K2 zh?_A}t!7S>r;;!}SzkwbmLFl%BVp9fBM_rhj8|BfBnkPA^>l?-&(KUqPVy&j^7j|SW%5#L8)K$T`| zsmmNo0?=q6W|Qy66V4$$2@u0kKIu$5>ic55q#>D8bLUd@!7wm#oQQXU7y*s|G0X$O z7eN@;+R_0-8fabsjOnE{x!DJfX^r&9pX^}F_2V&~Yg{BQorNV4Q^(L;ck7|))X(pr z$;_L&`L(mIy2*pek{6G8Db*;TPMAQ8w=4y4sh%NyDnWVnR8`b3s74=arvs|Q?m1Y% z3uD)MQMtUkmf}%I4{L1-ILhGAR!ZqV{{El%N`aWHQP%ONnOP$$LcgB2J9+s}|DJ?g z;=AIH2Qdo+UiQ)R|HeRREt_I@3w7-73HEo-YkNC`(f7iu0ZV)VAV|v5% z>pXGBZ9dk;aKn6W)Baulv~I?;{`z(P#_z|nI^4L^@^yC_<8_+$Kb>c%*Wb9>)BM{X z?}Tf=bYmrcR;CFcp}HOtstMWz)UVyvRS-dAM|DTE-Gp{f=yqM7#)8+ea+|J)P=BaHPS&8Vs>Gs{-?kBUvbRfAs=^tjbif}iI|p(!H?CTM7Y^3@yU!l8*L#^P6uYae=bi z3rNmz?4`94N!#aoADz}>LJ!5~4gma*j#o%K6+hkc}34tS1{in+j; zxxB*hAoXcbjtrdT$N+dH@-_fBLCC%|@Fw7Uxk z!x5(~xah}7#@s`n&qJ;l_eTbqwtOiej{Uh1Ns@EY8U8L^k zkuFh^O2#_J9|+HJ9>6XE6t6YkqYOb6d0)ruvJya)xd)AMEPynrBY=*&UX+aSsM0pK zN~yqEpvSge27vp<8#jmd7%MkWknE8qrLnw`X+cRO#=q^}Kw9f5(k)DVgr*n|&{XN9 zWBSKUhPtB`sKW~NS62)jM_hhzp{yPHe?O8i>YJ&jkLwA-1c-rQa~F^+tfh$DJv7KM zDf0H0c*l^;UMsN`AP@`)|IZ)$$hA;VOPV^4Wm@shn3JiGghkR+e)XdZ{Y-xchzVfK zJQj?hZCN8D8B^-fEa2yE|HansMO7Rx<;9080oC% zx&^$M`&3HW9-vF502%7ZyR^*FK55GI^KmW-#^hL%jUkd%a2<9%jC3M&h;A6#z6vdr zh*^Wat*|%W2Iib~&9Nk^sdCsTEvC=xw}3&^!CThO(Xq?4Lm(!4xEP0m-2ySr1%YtE z32(LoqJ_2ql+bnq{Z1R+J{KT{^#nTOMSBG)iXPHfWBTYaBQL=hK@R7l5_$qLO3GPQ zKp5T)&$wXBxYm2zx9Ch}0517Qz0-d#nK+;*fjqt#KWhfy33c|HXzTzl)X8e;Za+n7 z0>043*^d#_L9jWGAj}!1*O;R`-bz?EFqub2aoK6NwW_t=J}W7kcvXGr(5}DpMM=;) znk*yK&aoZjnH}mWKh?4Rw)ABoi%QUS(PS>Tw_1Bn(Zh6Zt-*?N>Hm`P@R-^wktm|yt z&R@5AYRp%+eqE0)!#Zp9*WG1lYdW5`c~AY#@1>irz{@V!#!CdBHrA!<)5K`Mem8EX z?E#^m;hnZSr8`gmCUy@h{Z5x}y3WHe-Tjz<=h1N-1YQ4W{Pyp97~iyApDsuLPKWb3 zK&1VR+vz$F{WO9vQ`7mjzwhSLd7OUlwsm_fBVvyLjBA|RBSmXnoINid(k16g;U2#vV%(mZkavQ-g=AynNh(UBO4o0uujEzO=-=Ln zJ-!(2OUAI#)H=+<%7^2Xc}0P*3kALm#5^7ThrjV(B5`n^4WU2`la`siFyBX%1Y!gY zU^rnErV=r(lIl_j!{a8H&GjCPn36HE0YS1cW-~^#+K7#FPG-gp*P_5n6jZWN4lq+d zj12CAo7gZZY-mZ~X4lPS6nrv4b3}<1_-F#exQSFUCX52I{Eba3C6hb zSD2wBVgO?Bw*()Gj&7I|Wk-=No9Y!dS8FhktNfzxrryO$VA*x6#LhjAp>zy6P`_-1 zDOa{}fz6in_vRfX6~Z*L8TN)E_j;ax7a)iZ9tM+=1|ESqI)G8P4BofU1G51Ge*_~x zPoZboSnqP9kGC#an(d;fFqnHeGQtrY0F?9Sb@A6v0ka*--{5AN_4^M2YaZu_lu{Je z5qEx_BPW-xUX3)2k_?KgFQ5lz1!rk0pm?suAgkV z$%A^yIMI*wD>4It4YAjhlc1$1qlo zpG83k0I?M$nJxjsT)K22V1R(n0#ZL`I0D2^*L0?Bw++;p>blljPXVzbzzZco1Y$rs z=mVw3ATN>pw)rcs%!ka6pIXPg(lJjqwuXmKo(_+>spugJa_=D3^bkO;u7Q@Za(>Ai zA}!)^H-1rFMjA#X$-6Rx03<5e6CcQAXBqSxf?ReFX{f8F0jHwyQO;_l^nq^0RtYv81+Vl zK{PJhVb4<;#Q@8MyKcFLDNWMZZX3+33fr-OA_0!MwOj>0fhqi*<($X^X08|8wk;br1#?xk4Wqr_g zO=j`Vaq_t$Hg2g8ounxRfx$-_CLoMp477+u3;}Ba>5(cKff&9j0o}ZL%RX`)@*AWr z7$gw0$OK*@Z$WiIZlxY$3&b!61xslB^FR#r-5if43C28S{wc|p9~BtLkv^e)A+oji zyFngG!$cB>vI4?TUP+)85L0?#h!;9L@1(D`>nQ!4g$5eao~)I0qO^f%S=W^I%3?%$ z0=79$#5bURXdtE?72}xE?WB|aw+{tj>dAM4HQF_fv!%>QZ)W-^Nnl?%ed<@pCfw0))oD+7~c}IQH*f{{Fx8 zl>;#$iPf}@eza5P@s&4C>TmVy_4&VPTY1@4MdSKwo>V<1Hx9b4#hjG$qVi3$ED!@N zt&-)NHxvo~Q(4p+Wlomr@l<>D1CAy!S3R1vPJl;~m}OZi?4>SPx64O*4%;G)r>&pH zaN~De!!^dy-*C+o=sEq~WtfML={nE$GmXal4Ksg@;Vy^T`gM6YA&w6wor{BA5qQrDOUrhf}jfMZLj0Dp(L9{j9 z!P@ONCDJs06TQKYCSb?cwlfUx!J(h=^qZQ_w>!9P<5(A;POG1$%NNAx^v0RS@A56v z_=f3j2-f&h(|H-!uxWm#ZKJ*KImYm-Fu;5hD=u`g`xa z_Z>el>zuRiNEMSTvoKcX(=ZojH?mon61o0lvGRjvb^K3ynM8m6Q(3PIS}%-M#%Ygf zW>mJa!PC-t8BNv2QcnB~N-PTT1M|wfqQKXM0;hqPo$aT?fA>58#jyFLfEf3sD(Ynq z2{R!e!4sy#U;b-uWV7OoV1nfrQ8(mldffy!10xe711a+;1L0hB2DQyT5@urrV%T6W zb3|zo>4xY@VAJhw7A2)3b5lT!n@>3iK>#haM+|Lbz44k~73ZoKW0j*PB^ARFC75EB<1Pb8*p4t~EF3Z` z3&iutj~npbY`QY!NR%61S1QYw9&jZx-mY!?xCLRteETaJ>xp_dR4nW9mpPcuGx+6L zj35NZSr%yfS)Of*MA((m5~#f=|nU8VfM>J4j?uJRrq3x8vL$9-u?U zrK

    gmqA=g|FbUX(Mhn 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�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 Y$27N+r3hKs6e+Q~Pe#vwHOYNb3q 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 z zoC|H&r@aAG+EALWGJeowNM|J9w4u6aD3s7%##aGVc`o29e#S@s%Ps$hR7_p#*(yGu zO!Ca0PSQ)sSEoD-_`b?oA`&rAvx)Hd=~8}?`iv^{zLWoH+K(W8!iNq&hK(3~wrEzV z<_`j;xF2%MuT(hn+>?E%`BCr|lz`tpoA;bEofKnw)gq^XqbS9?Hz|ci(xravjc9+Eah&eRS6a_}Ce%JRooXrz?Z4(blcyvbUKZd&t12!k9cb{iT=|V8{CtR!2zmCCVN0km$VD! zxiFc;HpkVjN<0e94@zF7tCUBK@KJ@x3u%J&-vq|~0+D&4h7)Q2)VFo*htVh<-n?67 zna6(b;@#YhdDsVZ+Lik4r|TyQ{B}~n!f9$F=2b+6|N4LZuS+#)y1P|tkBmP%NoD&W z3pH1Z#b;rvOI^_Hmw;;m)cBRIdcp%_NTy+WF@ePYoi1YPdGS0V%UO_a&XLer<|Ryy zluddq1!A$wsO!?Bs9l7~G9JecbC}62SV+(qpKMeB1*p5c3bSt49Rp%9Xdycmo<{XJ z-Kp#>WUM;fXy^7Yc$q++JXy-Zdo1+TXE9O|n97Zjj3A-0Ax!i28;i5Q{k!jTV$!wQ z{zFG*2M-^qyza;7=*!GYn*;3Z%kkAm4j;%dyoU-9xODzpfS8NLcpW)>Fd)r=IvEI4 z!1jkDh}Fv}QJ72%UlJAS{yb7cPr?*z4P$*E>6zCoMA=_p=+(lU#K-2XJHp6^i3+VT zkWO{kuyI|$fbGS^J&3x_QszNNOy5|DbjI0pWmI=Zn61Ny4u|QA6aa=K?P(E6!h$_6 zV8fm)^zH^^VeAzH^!ew00s%O-M^OW7!~R)R*>wIP>Xr^FpO~eMH#{+O_CFRzW&MAvol|wNn8ekV^}q+b5BBN3uxsm z8y87$ZTjZ3ND|!(kO7E!8vnI@^mn5fk9{dmvjYHDd|x`ax60=wmY`(tRt*3YZuf0+7^RJ zeTF(s$Q|Zl(`<9X?T+Nkx|EfK5d+Qo6NVfZbK=O6+4|7;LFjuoQXam6ei*%tDHnS_ zk0SYX_uid=C9ye@_Ml3n9vdU!;j}8pQ$M^P6@aMQtP41@HT?w`<7;&*Qds9MT*y4I zQ0<~`u|?xss;t_4*RCDaE(Kx)xOtH=wG?3Mel|HA0nS!ZFtq|-@RuZzfH5T6fO!lX zQ!iogcXRAAVZKRzF~AI5)hB?tz^=_1*ZZ@}`}yHRvjYL-*j(Bj^)O}9CigQ=uHU#` zhM_Th<{abO=%(tYjQS&$WMErK>!rT|l7KNluDhX$vaOBHstn-J?qSCJy#m9`PfmO@ zr;v(4M~tsA+CU83D?niV1S4!71p=TM^=elVVCv@xZgUk-#kGK#Ft0!rC!3kifHgoG z_GB2@2C@KONHSQM1qK-EN$C?LR{&Ce#{byemuD!Dc!P;^53B?NWh`diMJw%T^Q(X) zX{1c*R*Ze(st3t1j!K2LD!?UilT_#&sYG*Zt|JZkr?#ObzXFglcWGQhzz z)H&Cx3kvI>sUu*CpRyDHm3Y3t)elpT6eu|XCuN^7DT}r#P$>qhKZ`ydAm%N*Fu5i~ z$`chx_RS(?YI9-p!9NXPmDBp-w8@VF#MIzLgp{i)Qxus$$|z2mc?zucC;XYadQ{HNveEMEUQJz=;`*R$U=f6wAIKkHujcKkgHryQNe%ID5ILCSSF z57YF<+kfQ0GXG6E1ml_@u3R^;Yx_-!G!5TGZ}6iD*x_9@hWFh$_z5?@M7f!}PPZGl zuU;#QU&rOgb^bt%jxWqKeCIDu;k|R;5UlW1*J%ms-84OM+woV2PRp}02-Eqv=hyUc zZ9nW5xJ2k7;7bQyL>+{*F73qbzcH=hyy3GkpX(Q+)5zk zmw*1>gekf|7M58s*5cCw-Y?viMKln`LcDg&vQRQE=qb~eCp7_R0mNWfj7oN2o<;QE z2wEGaZC%umgrPP>l4k4X?KQ3+XII*ec2=mhI8Bb(MSoUmDHgj0bY>2)bMA;(KMOO` zD?kB85-lkC8N|XX8%9IkY-L~;Fhd)&!%AX>vCOO+YX@=5)=dTcFw*-t<=qm1f>eba z!lz-{Z{Av*UA~-SpYPm{gvx<>M~qmS^x?N8tpi~kot8vJiH(*EXMZTwq|IR>4#wt3 z86OREPg(~ML#hD-fypL;V>h1YJjVWE)RIUUINj#t>C;&to4!+%R4U|GrP;2i25s54 zbN29Y=DIM)PqKKYzHs#Dk&=#ho(1jw7({iX_5GxOZDC<{ec@_$<=2GKKadlKa@tZD zr`rJ#04|s&Mqq&pY>)sPF!Q7tsQWkt2l#_&SPB5}S;~vP?D{NRxl(*6`r3sB#Mr53 zZv_xTDg+1u+`{@3iX4ffIl}P#d9r^ zG|$2ivah1uMt^Y(c1}(T7?N*RBxKfwNn9#$hr|y0+z5b!F|+H5A-S5+BMc1@0h z-5iyuZ2^GRWc=K^y*T^T_di6+I3Y>1X6P{PXK?kfw}`YTheD!>R`|~IvZGq5q4}f z$)3CU0gte%TcZ|eegfL)YqbW*0z#jYXGsM{dJh9cod@6G4F z&`kJZ+tNn*HHp8HD9I#3)ukj8QXca;r)@Z@VEVlJ`X?Rps(PYv4X*g3O{Jf!F+h?; z9Q~7>D^exC8}b1l0FDYAPv0P217HWFl})RFDy90C4n^Hh-y?;=h6#W`TAu}2D*H1@ zAJ{eQ$>ckdxYl1*XQd`vwz1yUHv*tE>P?~}luh{ncIH_3fCq-q(MLaG#|a<~?35RJ zd8f7o;HKRA5&D}4Dn0ed_Xp6Ty!yTG2EdQh93aM=hg#AgiDmqm3xT7dQ2J41B+^c= zQzkSb*_e-N^?l#n0^X9oYm!0w;Se>XZ5=!J^LqZxSnMkXSOO zx{9bifFbRdM$0#VidyP8Pu`qbEv_xoHrfg3Vtw$c$~siHgs(A?GF97$hKgKm4=hdj z)Z=Zwn{30_K*^8`Ez*Z_Whdt8^L#%@+HU&~XKz2-me}Ll{_hRMr0hkjN^!-X6!QP? z`X6PAP~A95dRyjTl5b@LQ&tEQde!(bRudLo+L0W!mf{4RzAMl8=x-rkRe|cPF*N~V z80-BX^1~bYVM_X5a_+f)=f0Y2?$=>;DIMLl({SZFJP^bC21WRb&#%LDT+j0FG+g;{ z{5vjxKWRt{G1_ls_|AWtr+d68C;T*Sr`7pYp#14)Va4$iw$th~;#FZ?IsQ!_X>c94 z?>jxu9oGFvb6v2Hmf<|3X=RmRS`PP!$F00aFmbt#(}Z4`W;c*H!uGG9#dEJQJI&ly zxDM0#yLOmPr=O+MX>`0Ui_SLn>p0Tjyc33hrz@?FGrjLH{DkeR{H4)(3FjSuT){M5 z{@k=2oj*UnKb-?H`vb&$|c%zrS|Y9fd(#P0;1my3Fr8fC=)G`$U1?6$<=15c6b4HR<_N z;H8Cny1K<-7L69BwSWr~TYBU1FR7P=vCFy6Vl9j%wWK<&BdaO9X<3_Pp)GC}^A^`( z1Tn=1o1JjRcGqUd)^4_=hApfCJyeoNF9>6ovP7`W1^Lg&>t1z>S?a3?EgUEt?|w5Q!2JF|`97roDC=la`}-scWo?#D?8UzyPL=J&apX zVYwTI){f@kLr1Diz}}r<-hm$si|&s^4B$c9)RoTukQ0F-&A`qDCg1hw=ZA~QVbjAR zmO21Hn$!uVmq`257(RC7FExtYVf?@P`s=yz*BDC8t~F4AqzDjz(cl-aSIzE(=_Ek} zJUM>iXh~H(2)J?|z>@ku3}afS8s&;6)U>vSDbGw=QXx*T`7B%U^v-K1J&cdzl1T<( znlPnQp)k5W0GM&05eAKs+FOwbu_#1yi~jqO(m)sXROCnRrs{1_v)cgz zu3f)bYBTI~V2+&xrLP=1e0cWNw_ne``0C37*dB-0Z_+89=7i~bkn%1?a%lr5DU17? z0eY?^4=2)K;_Psf7}*~wBvUg$M4u{YhX6mA^ea)zVZ()*%=YN>lY&tn-;jM#2cw>} zGe8WL3N+B?095KpA|yA-V|$fX`qyI5g$mH#fM<;HVnUy0^MKtF-y&*W#*#2E*hvaQ zA8uxLI(0mL>SX%yCtSsre;@a`^@To0Q$0hsLBlkP*nJua)Xj%IO=SEY_v^J7B{6FjSI;Ehylx zxTOx2_Mn0ZY+03b`K`^bBm|Wp@61)CM@U&}S3t(t;20p1HkMxbr(88(r9SGW&Bp>f zb&VgXnAf2($(SUXclt{FGY6GKO)_H?-+eyc_5DPP5G0yhL`R;0TBMxx1!{_b8)bi!jTr!Sk}CH+^>kX2CR)G2w_ho#dZisRMwizA zV)lA95LNMh42c*N>eBim4Hwc+z309wF7q&T`3J&>9Mz6(toL0is8+2IFA2>5rZ_f{ z)~_9>CXIrOM5-)=h{!D@vePu z0^ryFxm|w8uhWoq#}kG>zfOnWG!5x`S4E!R=Tc$#`*G3~uhZh^nd|ht>-YUmJdN1- zcNqR$`wPQ$ylFZgy64CBwe#t){Q&bo48N}Zr|EW>zIND7tDn1!o|r7ZmA(eGWSX$dn;qvLcu?>JZKxc)~0 zVh(1pwEuKelYDG2AV@PZ@fVrKq@FqFUGBN}v9O4$u=#@#v>-N7l@!c8or)GeJKz?m zoH+dZrYFi}5AXgSX9~1PdC$!gtjqk5kIX0ci2}bX6j%ww)bZ8-^M9E|?891|161t9 z>DCr*CFVaj&RZ%pd1n`t&cDU8Q;(<`+0j`Ca0#+XHR;t$i@yLUVO}g$O$EX@;uZj7 zQD$6Q;94YNrYyMZlv=14z+%9Pnfz)Ij#0_t+`U~?5-}Feb|9&cP#H1e9UWVX^DM5j z9xPCRqybd`yU&0Ji)`ZVLuDmvjFfF-jPcqbr_y2v^Ff$Dzz9_gJDUghAE?DY=3#HZ z7dzTp1H|l(I*|J<*@b=bcxm?ici)8pzh8_oX&6AzvDm)A^gBrikb?=K+H?QjgDiXl zQbc6}C{gMXNq<`y@-vZ)IGR&@oc=+blhIPA3o+Un&wN-uo85`3j72XyGzSA9ya_0C zFOn_~sZNEy?2ItF3;3ZHMw$bI9K@S`ug+bK^ zWAnmdI_(4`!vvG+!L*Q`Fk_PH*d8X{2JW4dnGG4u=wu#hLqxXOFS#Bd=5Ff#;9(35 zhmqfsT||q0Co-K0K=IYL-()8|K!_dJ)V~bWraX_sU_Vd)vDf+{fWzf;=VoWW`&F3b z!E|p8jSs~}%)ZbAH~=UC9%)ZfDR=MOD=8iT2Ot0wOe)6dA(%kNSMLig*UECb-W?K)4e(hoB9&kZ4a=wBfya|FlLPD0l-i-V=G1*>aT3q zTn*SndTLEnZ!*NfWRU{c902X{?ELwQvrCsQW&BYc%GgNT03Lkvv=cB6ILNloozM)E z3WT|n@&MqBIfg$;yS)Qq0zPHTQSYIWxIt9$@AY98{!SObU5 zH|YN&#CVW=??pQ5allVN;A;ztv>E##4a79yurc!-RUh_F#wkQ$szY~R1=P7!k{XBs zX0ZQKCm&@XkgCCkvGo9Gp}GW!(f3FL0cNxtsWN^W%uOj%WDbU!NtkVb7U`#om-{4B zbDVMv20qedng2>cG4ZXDQm~pkjo|`E@~j<4UzQgk=Fz{+spdm{sQ`%NN!?}4y8(qV z_LY&5+?q?$Z@dGTm`jHANU!Na%KS5)2G|n-Cp6b)Bu`pR4G32!UB#bDB(Rm8CLo@& z06xSAzBCZQAMm3d#;<+^T+){q<)yl+AB?&UDHHzYT;H=HB{S-cZqn7C^dq2_{Eh9( zKSTz9=y%qk?8%g2+xP<>%Dzr$4wx`zNCQzt>Nr-9fQSP4AP#wFVsuiHZ{nJiwWLZz zt7K!bn>ji*Vp0yxSJFnxki2U{B!RSD(In}pyS}HL0WRr~@f+<1kSUNRah;%5CnAMr z1t6N!WS2aEL&`hPu)ez+vT0!)_y(6cTIwx4n%5ct9r82ex0JW$+c_#S- z7%!k{zLuz*ldpM4+1Er(iK-GiOSKEMDnL$d@2vgl?A59qc^{|F|K1WYAt;(6mEJih z;Qqr-|NVcn{+CXt*69Oc%+;8m!HYFef?<)ecU|-bU-)z0`FWm(&tv(uzvq6> z&rdwApQVjYlz;p6wMpb%mxZ4+SEeIQhndFj{JrBmw-Zh}^6&J#o5uI-oqV}2(=%x50Oy`;DY)#N9p*2juQ@$p66V{dE-;LVO-igb3HzmAx zo~7wsH+F|t7JmKCwexQ}b)KHZ6Q;}M9VZR%J3rTU;&d9mE}f_M(!v38T@K;>I)C?^ zXJMq>`FQ3A7d8B+aXXIu#gV^j$8*0S6U;x!#Kiphz8v8iAm(i?(7iMj)pVEH(MNr_ zF-481d?f1Qlu5gi7AtSOtQl4OdZ@Sl*rj*}#C%{fl|rIO$p>ON#7KtkaFgdrs9$@B zf&An?QQ&ul0v`fm{>}gNFLDx1PJYRv5mP-3D`pogDKw~@{$@meBo1;$|18T6%6!APZrYL0IXGwc{)fD2|}h>d44oP{)Jyj$?ug{zYU zD!;+_TYy^hTQFlnpNA=+*Uum;KY)ZVc6(oh`6HUWE2<{jNt|RsSd&*4rZvN4d<%~` zGY^&OMotF09wVNlOe}O>B+Yv%E3k+p$;RY8jtgZ6BLGYQ3joZfFh=sh#9*d>_~E+% zMNvV@I}$N=zS;h;Yic1(G5|9IOnMZ9s>I~&m|}VvgSBH%rGj+G$uOmiv;tzTU%ytT zF9BlggFX#Y$>1vq5CF~YNJ=~l6S@>>m?uels3*mS2I-8@$PvIKT`)~lZGb^b0gUX% z@6R()eRef0Bpp#~x7TFbClOIoV)KL4A?Afz2$c~`I|(7`Elw#ZFd)Fe&8Yv7$f3@L z8Qv8D0eC@bf$9v2jXQUp7!|{;0b=YtZ%N)KPe(oJXWz~?ga)grB`_)(Ce)l?Mkxc- zy$<~zM6%}M4?oNcYpvqL~WYz5sK8o-0?b=9@Es5}*ea7mPnZ z06+sI1YEGOLYiaSwq3C|bg+OnhCBb_|M-j9#q$@6S=<@PjxSH0j8w>(GTKWb1OP|M zBLk+&2#C>VF|}h%M7kn4{h~5s&&dDEhnSx zidq;32#^cxd-NzaX)@k1)fmp9q86q$w0n@YS&XFHgVgPfG;3*LUL$ovatjlWhZw3Z z8v-h@3jO4_C3GQuhqaAElX)(FnCX{M;m}9dXAH0h1PF2*uy(3{Nl!lJb>j@f&80ZC$WbjojP;c;9D4Wx zh|C4W1g6{~$YW#rjD7_eAi*M@_&^|a0it810R-BE#Eh|~ZVevDegHB09R_`XFvcJN zhMguSUzKf`1lSbjA6Pq1E*dtA)D_T^j+r)3y^=v2*9M}Lq;x9i8>4?X@u!XsPdds9 z`0_1biwGDxj1mt3ew?U>8p9~O6YR7d5RQ$Y0W_r5l17F>Kojaw1CY?a@9|wr3#PTQPtTO!=>;IHCDGu(W(>Le&hIj|&N{*FZ{S6&L()aQo#-XuM zT&+$j)e%Zz^Q#@|@vc;p65<1@Np_+*nVP&LYC>=wlUZ)!stZi6!gD4F;YXq0U4`fG z>RtbGyPQ_%g&^Kf0T@d!&)gu`i04_J(&g`7+y}hp5@W$iO+e@b-aG=a*E4IyZt(@XU?;5oxf+lPBXV3*LA@@Zm#bt z+=N1ab_mm5=8${TCM(k-GowhLD2=2S_+zT^(<~L2F?*Sg2f0xNS zzb^kYfA`%1a=YB{!go0LT<0&HPP5~7I)D^)K!*!`0>nIh za=(}f3sgJ5YgT7rm5$yjN<%d%<1vh6E$EF=qahs3o1Ah`yEbpLsL!-O73s6Yf1cfD zq3n)hPDz{GzI~?_;CA5sjOP+l&VnBZQ=->dh?O|Kg{#F~7T6EN*c)*edJK?-y`47_ zHjd6U7ufBroy<%Kq(N3E?fucGruKkI#VFVvwP1JZ40{k3)%5k*_W;IR$WhE!B1wR8 z+nl4EF%BenFiTVvwubQpxLAnp+ndvi!jMbXf>It-k1k%gP`mMVX4xFskua^IL@)ng zzz?bu)T=O!c1K@@hCnKeFB>s-UJoBR9D6BWhnWt*6DdF7j?*R;Q2kcr{Ov)}*4AI`QX&DCK*SBKHHXqP$WbXEFbGM+sSU`8V5 zTqI&1r@q?WvCBtu`p(hV7%2cN_GH*~xf^vRDm6~3^8I0VgWMfYP?e<2B=1?SJ zTHT@4TEd*5J43UWM4*Ko`)%8H%=Sl>=3&5`Yu9c>GA62EC_4vXo2_*qZuJY};mZIKq+<5%*^|C%%rFRitNQ$ofJKfq z#~fiYsR!zRKs4>=TS#r=_N~R7igYn!ij>jbNYd;M`GA`_H7Shnh4U9;F!}p1#P@0( zk^%x?V$3&WTyBec0tvG!ck05f3>B<90b>A57&q!_RDwv(Q29~kGQyiNW-RM3rP3BA zntBmqoETmG)t8tQgT6xYg4z#>p55ukpNB37(iWI$z#fUNhuR?R2PnZ5J__9dF;rYI zhSXXBF(uiPISD|s6iFIPDuCHn8fdFOhuy2bwwV4yMd@Y27Z8(lFpeaBiUAMt*nR;_ zJF(5Ur*a2q)Nk0~SQEw(ctXm>e1qV?89$tQ*^3EH2N=T;a-`8xFF!zx1(F|Q;v3|V zF+^%_W7^kx=ym9$4#1ZJVUiEv!)b3MSEwN|@;oF%Ql*Q$%M#ozo65Fl}4+zm;(yi0!krCBaj6@REAjdW zz-N6OP{@{_xt$6Qu+X>~YC-J7Fit*BG%I^N$sdz$ju|SBqyNZ9e+R@kU6Uk?KA;|e zChcVYCV2(;1E>rzXMnB7i#}*hB8dWkY1=WQj*c0KB_BYV`90r2;9W9MM<+Q+z_(S|)z5e+X&=(B)z6Y< z{FT=?G7X>qK&k=8SR3mLRIhx)@>Jg)7zO-T~!8)CyE&^{$+- zag|5fJumk}8kdaS*>h+6)TG<#J5-Zo*nok1zXl-qx1U7$&l9hEooXx&hQILMi^B=u zuJdR=;X1DHN;{1w?M_eFX}Wo=ccwvUxN^dC!g8Iid+(&#c?i>f?u%Nva>7i*@|))0 zWo`an{`@+B;rm%S9lq1_%ys#s?OnSrV?TeCYZvOHWVRv`1Z)B$gnOdmdFtfXCh$5nNvOCk zunA^>FNsz&QQl+*Guij?)yt_T2qsZ%@yA_x*5b~N%XlLPZiMK|@^!pi{=1xxROeA9 zH_PF?lfOJZ0Wt5$^6Bokp8_8OV*Xu#7{+?XsTf0|q!x&gT_zRdH-_X`fRU218%aur zWHd>b)!A(~UR@nay&*r-K<%Do+Is%t)sRLA5JOeLVr5&HT8qjS#kT8S3-d4rzy~|+ z+~Y8-c2I>SHjWVl6aWMO3#3boNjs956s!QX0E`^wbYB>H#!N9s)D22CC=xa70$6lX zz1bSZ+QO7XOrNmAR)MtbsAjoYU`*}Whgr0UWLpHtqWn%NV($Ut#s-PgU^a!}0coNJ30Eg81FEc1;m5_qf!BcVMm0-f+IzbpFAF^i7$!Pm}@FLcGB;JS-lZ89LyOemUuFrOTyaqyOItBM79z z#ACuR*rbZ~1Sr~nFzQ`li0U+-F1X7wKclrchug#-mg0*C|@B&l^J>Qhvxs3)hPN?jT0l%mT9Aan_y+7rI~C20{gllFZM?sXG?ckn91JlD zLI46VkS!MR0TpOuPG}V#s~JqbvNae06fY1<+MzRG$9tgE0B*Emz7Lu6w1M$g(l_xn z&dfJ#>!81R)_hAs%=mMBymgvyh)V;oT}Wwm{hHRAt17O5tw9L z>vO(Sz8}D9sy@b~IYBsKbSr@HI`d}%F+-vYAYd+#2L79rk@f&K@zWNr=&G&-+=K|~ zZ}7+`m#ULKPGwA=WQ^JO54}=|q@53Rt^nKWPw5xhvS^n63y4uhdFlHhNBQ}#DnEN* zz*ZnxsR5^5EAN!UTmr8lJt^!4Csk!GCE>}SJK#x}QgO~4SgKf_QyqrbG4~<<2k2D*+HzvC5tDD+6o`px(v7@ktQT+Tr$~50e3=~GyZVuu!S}iM zo)f2^r|~;Z`*{YId?-FY>B+0}&+T%$;&L5-kZt&h!-?-Ft@g`pl@CwJb^P%({wg1y zwCf=4uG`9cVWr>s_xpYpPhOq>^!@ZcLCUqCIGumfM}U4`fj`zCnf@l+wBm^289|k& zf89YoMC1f3;)~mL=xcWj&z%OKqB}&VFD~b*QwZA)J}r;@yKMYB&F)B@USFjr&NQt2 zb*T35u<2|=}n-uGjf86qOJ;)<(r*SIO`+q(GG4Ipk)6;J^1^zxD=H;`fh*$t-F^18pg+_re znMeRJW)EXC|1f$OBjXZygE3#bIt$UPL@}G|*2Jblj_J)ZelXrKP>M0L1Im!-tCw$T zk&S7zFdoJrX8~%WQWK`jV%l!Hg}en`iL+-%-6DT;fDH@8z##34w1b^&i__W-%z{^Z%rQ?p`eq{rPJtn$2G}JjarP|oB~4WT*s(KAB1r{+ z1(0P^7zQHmmoHx|P)3~=7A}Vlk*GT*z+K@VDWwA^UupR$6`($BE_>k(my187^wwdpu6%Z3l)=10Z2IhTVnL}cEAnb z0GlX#0x|_`F9yd+D5MfG_&1X;Rkl65Basw3u-SnTGp#b#{389}%EI*+M2-Yc0HQ~! z+vWgbhf=nqCr{44`1&USEh2FgLyiCuj3EP^FGHtwsn4BA-CR0*E`ZOKI$;R#wljI{ zO+9v{Ow@+}1SF>ZufP7=80fv8{U z17-mt4x~Mf#@-J)QTN%Heg=5C832z;8C5gtY``=O3J?ZUg9+82*N3)SBN6lY@#7^S za3|nGWDCk(4>%AJTq$?P zBl>SjA0lNU0bmed27r1Xs$T#F3@WJ%eFtMKJ~cb4Eyn)%NRtX0YIPWCjCFyo>5G6N z0HAS`sciN%?EzrApSkG%!<^cdw97_J`YOiJ+Jp-UYTPKNv7}taKXpWYugneU@4y#h z&l~~N1KQLADhU{mWve6csUwkyDG9I4F`m~YKOl@XjCk5xJ8p>@CxC?^U_j}bi~%%9 zM@z2)VlwVadNFi0|Gi=VCS@{LP(u->7~{;F04vNcU>un19amBwVSvp`)ut(*{(#vg zsRBGTs{$$1!yE{t0Oa_|H#rw=&E>!$-wthtaR+QrfzcK4Zzxrp&sjfx)lpg?7cGl1% z?N-t+d7FaQ8rCL40Z%E<8~NlXzuHVmzwll0ZIy2UNTHie32LY|H@2qF1Lv&`jA^Pz z%I3QTsK4hsmbppW0g7!R6liZfkuXLNwbL~*N-I5eHo|&d@Xa7e=|cyx!-}EARSsb&hJoyjb?EVDJA)Spg-}Al3%@a(Q;zl9}f(pEnuw ztK%VFotl*XAf1)`&$~q4<*+xK0U^}v+*~3iYY8d_j;}TuHqd}S`BA z{Q31Sw-sL4dC3dJ;g1K@U|gp!Z}+YSZS%*SXP3u2X|{7O41alZou+%~$=|QjkpDFO zJTAwPM(5$$al0PkPV<*O$A6k{dFD^2=efh0JUc!9O&|Bux^&)MFYiB!lksoDbt5+6 zRlLhGC4%={`w7Dj@!U5NyEFB>PEVLl*Ht(TIKA&U9ZrLFm}&m*{W@I7bCsW~cwCoD z*lBp-bP9gL2=6xyFHGm(VW;VL9)0a{2;1>;TjiOS$vgQ7*Kq(Z?eFTxSnvKU8V|4$ zlQ}#K@A;`od0CTEiti{`)i9;i$7lY|qQK6acl9$=aYiSu#6Kr}%nuj+QC z|9D>M`!3eIH}CRK0V;ie>{NVmpD6IVLV;fgVjj=__kaB_BHH|@7Jq;hefmw9{KjVxmF8N24kruU5sjng{@ui&B@npn&xnCO~3&RdD z092)-1vrs1p^9P=ZyvHxXY+(Tk3(Uc*Q8#UOSVn+1Q?*k@HSx_AN?qEAmg2+2G|!t zBa3UG0adXRCr=h5dv)P*8TKU&@M!4g*D?5lt{>m|@Hui3AJ~kj9Bi z>RP+V7?G5Lq|L1`?>-FFsTeYL^fbv5yT4Qs-b7XAO4ND)G0)TAFvx4NfZdvtq4q}V zFO`@~>uR&z?7RS0BTuQrn`Ygt;yCuF&yNKoIs;`Z!_$m%ha|3e_AS%+aIAis1-J zbKRc)v$|}=Jc(q_tw{JVZc73T__7orWT-NX)222>?Q46aX?E=16EGmhk4MS|LrGOi zdF*WO4?X|r_y1t_^;cg6K-?4nB+^IMuNG(lh}oHbff3Rk?0JwJ7^*LC3lO6oL<$Ia z#AxlN7@^+1cYjG7V36)bJ?qMqNVufWI@L-)IeP3^frZ8wu#L1CVDNU@hH4kJAnHM+ z*?fP2RV_icdDE847elSifKz?Dz|!=Gv4f5Y9?}SyEn_*C>MM+c+Y#1RF!I0!Du25| zCxa9H%{y)IRT-!rVX$A-d_#(0Fs}eXeUV{cV;3#Z76Xad#N?GR?u<9adZ{NlavKms z#e%ws`M?}e0NS&Un)IF>DxgX#pAl9|ejaWD*3HXiGrMknRXL zWRA!`ZKh)AR8E=G2KrA)nj{HxmV8p>nr|@P!i%a6OWi6D#>S9r(qaV%)O@ad1;nH! zwV713fqBP#VeaT3fY97-jt7pC@RO$RiTRtlU`fGACFQBQrq0^Y`~#FJhCby1==eSW zBN+Uprl!z9K2kR%-vA6H*_3u0X+;tx^Evto>pM{=QzdV8Kv$c9RCucHc{VR)S0 z!c{&2acxrMWl4krW7N5T014GEKukp$fK2t##L!;=H2O#Nlj(zH^(?}aOjZDs&NjE@ zXK;GorJsi92jmdP&%5dSJXRX69j@<92waDEFMs}BZRwB6Avbg99oJ#{eaDlYvUGVn zT>Fdb9u>RH{5WYzN8B!7rysB7bUXcy@7nRYd@I9uIfQjDeBZlr)40RuBmC{mKSEaT zWuXBy1lMJl65c)kl~+U(mw&sC@45X~61c?Y`KfiwNFrAib-wWqn zn!-&Ros60sHznRI-VfGh2$@5J%J<2R3$-RU;gTOmiZ$?ba{zvlnm z+wrjC@5(4u&J|%kJ$|CVZz}~p1jPKifBj#C!DVDMi^crI+!TY86{B6dvKNz~ZBh7g z4y23)VwkHeq8aUFCkE*9G$;SO&VANrVUz+iJZ21+)Xc{CS=eUrm@#b;YS3E<0~n}? zkeINmy*oQT7HQOW?A+Ob>&37IAW0UgNn;U27k})S7xWJkdhr>W-*E00k7by-B4R7*n_Z$$j6i~|*lfu|3^%8Y_9{TUvy?1u(`0)ZE zE}TDCMtvC*{wj?Bf#hc=)Xp^~miRoICIAhR9c(+4v_tMmlCYDqJ0Oi+_OXb}-?Z<& zNH+mu!eJC$?DDgXb0dBvs+48;Ald<00QNOu<<;v_~KoTlZc2!ACke+dh)UE&;PVVr{`MY0zH~Zx; ze^u&ncFgq=(kG;aFjpi807~exD*z5Pl2gm%MKMpMxlLwWAZ@&2^YEKLSM=U?% zVQ1`;*y(=oAWVJgLaORv+7Kh+Nc2T27>TD07&$w{)US4CF^&0r8o&Tx;m2OjvoJIV zQ}#dj`R~oX{mD-PuiWJx7Qcuz^0ZjT4)jx}U_B@=BRKQXsVhYd*v#XyQOY1OI z>64`BNEqzQ7-4V~=mmHhYHbYKVlHC~Ch8v;e`+znfs!(h5!TcaD2@K^hM^K}n0!Uqh{S;Tszj>qp&9vJ%747xxZrb(s zodLvT%1XU6C*g3cD6%#wDW3Y^2W-ie>Jb2j3L<+@K$MMnwm5p5asc->0-G`})QdFU zV0xV}6a(A=WHFoOJ5m-K16*u2PlXX%A7;4bUz1wikvu7*tLe|AV#>Bm(xuLkSH)PH zt1#H+HavwXP*Qh+L?U(jUG&YCKAd@?SLV>Ye|h)I0_7V6&8k;;m;@*0vC z?6stPNwWaCgcY~ct+ZL_C;tJwo7Xe0(+2kgc0bIIadoF#0@t*gc;*?jmnPa#=hUxk zzHxvN;L9k-klq+?#sLlHTwsPc=pyZ>0O=+6T%5W|%?dCA=-Zo_~wiJtLJ zJLy|MWo0t|pa(zn8z5xHbMhiB0)QB*M=5cd!#FdJyr-rzK$=X4q2 z?#(dPo3dkWRPf!a`C~kc>(o^cbkm>8tTNF=i1LE2}ujve$()M-`N9RIBE1x81Dft{5q^O6}QvveB)Jqov-UO zjQ5>(Uxo1-liGMMT*sZhlV|5OCu%(l&wm=H%gBGF>%94QI>L+J&b@Hnd&l+r_UEU5 zAJugsK5C}(%*M)o5T=RHoy5Ct#17ZQ@#UBjvEO$?c-8^OR>ZqTiB}Q08>!>*=lFO2 zorg5tbDe&ti*|m};k@UBS9VH=((C+$6{f>W$8)zqm&dj9_f9yj(+9D4y3!V2xK2lS z^pU?fUH&ddhZk-dPdLwi64j(}YEq2#LcbmP=|vY{x!9R?N+&Y!+F8{5kQU%kC8|H~ z#plz67M4WJhh69Dvphb86|TLPBh5YDzbwCX(^0Bi@4fl-@QDJyy%hKm5c3!R>Yqo< z{!td8S>)#*QE5MtFjPBOU9sK+&R7)c{T2d5#Hm}@%YI3b#PN_(g9hl^~ zK8xXX*)az4IKJ9~i?P#7VUjIqNxuk-;gJRg2cv^grm_G?Sy;GSAO)t-!uD-wTno=I z&X{8$hea>rkW`;YDmcLg@U(b)u@=6JwK{B**yuB73NT`i=AP4(vXI`E-95H?wA!P9 z1dQ}zs`P>Fv2}7VK#ZMG(m|Ld^))9tc6l+XEU%NlQ;9H@j?}&vAcxeHHYJ9ACu$1M zJO`))$SmZfpBG^=PM$t9J9g^i?D(k@vx7$t1-ytlNa{!OWmVeqWh5?^0$kkBw`3`K zQB7Ei`kMCL5uohY@ng}Y51^Mi*pWYX_I$vYUzL=|)-BuXbRNG>h~bkYxgMz$Kv)4c ziSPTiF=JtG?EAP{jAPD}``W@n(TqgQPXqXT`_0$0BasYx{^DsFfTq%h*7^m>fDk$W z+~?J2jVH{oK8Zmim2*9kS&U<2hIR$8VT}4|)Co!HI6C~og$uJw7cZn8-V~#=Ba%q^ zE#~@BfV-;!si-AA4%4h(vSY*OuHEvDNfT(w28nrtN&*!n%;Y%2I$&wWCBO*C#2`0G z8~p_1QBp2pzKj{RR!AvqOMLc)w#U{BFszu9ED}gNkgx%kk$R)1gvrOKVz@bTP^&cp zNzv3UKZi*-K1(_Q5Rq~{NuRhIX$i-_vk~J+Yr+Tm`RHioxZH5~kv zT3k_8GUt(@DWD|xz({?wjPw?;ka3rJsnSi{lHdq!%zMeI#$u_~WKJ9)1v^my?g7Lo z6FOsp-LqlS_9~5*-Pjeo!D^;w19{ zG*knmSH{mfW6C@aWC6YdEK_VvRDYyHVupVKwkfB+4iE)eC2DOB0L0O|(n>n$Jo;GT zB~NuX*NOlb)bD*`fq8992J>INN}tVpjxC=tUeGe>rku)FfLq$7B$YC*fJ(-(KJ03} zZ7wQ0q+Boc_4H%vS^#Pw3;-q*aqa`AC!frMbJ|*k8y^FRF@Nb}smbiYj-Sn5ulaG0 zuddEgwXy>Fan}E6r>UzQJ;e2?N%O3hUskS&pBSCLxa$JQ)`^p8vz7dh^vdY~~YprQz9qhfyB+OQ$Q?F=*ZJpmIUjMQ>*^|UUyW+^N({L46zOGF_{#;+(PxI&B^^pHGoUs4sPNu&Jr_x+kcuENVU9Nub zdFt217nbu**d~78YrxK{<97q(RmGXc?am}j`wJ%xKYqe^KXqN6?lj)@edpit`FDM% z*Y=l3=kMN6-2UbEz4z1SX&Qa++F_*8&s`o@CoIXCLq!L6frKhKCUKveik!6UfzyS^E6CSb5D!Jl|an=yClU9R`>6o61n2POJ^Qn zavAwA&xi9jpMaPNi9X%_)>Gg^K+M1Sv;REDTZS3UB59oBkikHH+ELVDmR|>0p>|Yi zMFD6qTST)}l*vJfuDlJ1iOJ8zZ7@wAvxGkhBzW8iw64 zquBz?53nPB>N_L}jB~(*MZTS65+Ims3lR>mV}~A7jHz=hbIm7NB-UalSIkW7gR+%dUo$I)Z;v0Q>rJF1HrcsK8o&lNUxWqVTn-Q;EXIMM zQA{NDAq#p8GW#fO5I9cydW_hbpBQ<>xY`kBuYwxM%`om3wWaPA-~rfj^2CWU^msoE zBO~YAlkV04JD8xX-Xf6^1G|v|=wq~bJ9NtrO|Y=bBt1{~0UA=?Weii(qk zHBf|l(vtu}#I&h(J(x>;)Z86u7%OCsjd!qsAwPo|8AjvfgRb8^O3 z&i2%^q&`f*VOCxR76Aphk^J>80(91Pr%BS#_?^i`=hgeOQns3hHr5#1Y+cDY+^`A5I_quP;V z?}8-QmH<^7H*LX(7wEANDIG^RzY5JR#10Fix%#YjE}KRvKLBQ5fH22Uvw37U-7fpZ z3m0Zr(wC^_e0lox?Ax!uj=I?A#UNk5el>mMM*5K>c8By4pznCpqyQrN0$`7gl>+wW zz#CP!4N(JgY7%3a+K_aV6RPxw>(>^R>rSEt!&wqO0oyJ`V&-m`=a(7J+MS9U`)imt z%&GArZ;Ww)GGVlg9qmhc${5mbUS#Z))Z3hC#GGQ>jCahx@vF{MtelVpNZGe{U;1?B zjI55>f&t9dj=O0o?Qb3f$XE}I-Q)m$=?Bs!rH2tMU?6h|dI*mh9(Jc96_ok|V$k7v z7*BR$w2$;K(<$6o6PTN%8NYS$3!ffIm@uXpe*l-Ap}~#-DL|UnY3H@6lM|1$jbw(} z2nh$^MbggXR1AKu%162e5JL(Ch(UG4*wxpiPeQ}k0TAe~<}_f5{whBDp`AGtKmdFd zZb-9`Oi9NyX5tDsS1$DD5&uE|@EM7-enS9?tYqt2Cg03QA=9V0(sfnx*U zG1r;ZB@6g=?E5f0uI;G9-Mg2Q;xaY>5$J4Q0X8U|`A}Nw1e7ARQig$ZkNzduk&xyR z_E~@rz&LR$KW&#M02LcNwYE$-*rO>RG~r5>D2+0JmZ3i7+n`{!2B*B;oJj1 zv?oa;_Q}j|rSg?{z8@qk6S3wRjcyE(49TBLPk;9v;2dK=Buc!_vOHl5NC{1B5U3AT zm0?hMP1e4Q(E~A4_mt6w24Kg!*k(zACZU7=txeDh5K}bKr!yY{VqQmDkW`uP6zb?7 z36%bpw$F6{SlEP&6(CIfGML6MzP{URhuI+T?X_MPhI*>GOTmGxb(&w^vnvCHVH1lK zAOprF6_j^19)0(dd^$k&DQkOek4~ZJY-#r&&fctvbY2|4A4?*pvaD47=_mEfW7XN! zyOr+4>wi6d^x${lmw}kMm%6c?Ct1A!!za7WpN&0zXoL`IycL0TRXbhExY-+^-g@W! z`E$R_yBl-Wr2j7W00YVE3}Slj`}TL`WYm71M+bU$^-ggcq>X2Qhz8B9sSFWGkcU<{*p8VWT)6U~^{?h9A{Dtc@<=J%U^t|gZ zx&5`c{5nr*|3+LB=VNC+B~ITru@JC3#-;;6=V=<^c?NFCtDW$E)BL@YM*|_Ag^^}E&)#?bjsEfHI<7dKwy?r=I{e*>GfltC zH!Z9CX*|z=8X#tOChY@^^=979&hD+`CVGl*rpZJ#vL6$cSN$EZ=mM^AVpxhO#B}l- zvGX?(Uqm(H{=@r%zwbom1pWt|fb&3;TT#a2`%`ayu>QgHKE3=zf!`hqdK<5rs&y-SG7h>9+{Bz`*EH)4{-EE=xisBqXWfR2DV>#J3}7 z=hGOpkQ{bCb`1v20y%Lkn&so3u0BSR{gawA5{D#0fjhb18}$vVa?IoN*sM{WtyxF{ zVAf^Q*+N>!a~Q@twI=yKco6lC*gc^Fgqd;-G9ZS!4(WwrRSk7a9sgHxx!s6{dV#oM<_-N-S#Cr_Q29gidl35RjYPd*T7UlIh= zKuFeL$X|vza1=EWY0Tfnix&$}`1&VbMM5S9Rs&#=vH`>_re5Rd@6Too8S( zjC*Lfc;{C7aSV$FtkXBX`tqyUwzP2rW|%2<5E$)!5a99FZSDJ@7|-v2I9uQv=1Y6) z|7;~-OfV{Ya_sm182@Eh*y%2d*`T<7bs+|pZ{!H)fI?qHDyZ6bN5)fB;3DBd0xMm= z+6CxxIHzBg%1JRw>6huBz=#I{rWO{i71Qf9nsqUrO%kb2=E~S~GMGM*MSPgh4FxRL zu6OP+O!rcUiveQTh4(H+WLg^g1H>GTy&hwmItv4-#)WZQk_<@)cxwHiy{Pv&j-3Pa zawRR3w2HYiAB1qq${6>(UVl_CAgM8>y@3wWO0nzP;QK>T5fi;Sai}J}+ayQ^ z`48|wYkjZi7`iDJV$~7PK$Y|x_Huxg)kpJ9Fctxfr1`3!=N<^NA>RzrgeFk^L7K{8 zp4aZ$P2GI=N=+@_H~k5yOxg^csPZXS(I9hzHcgZiDLo-+)HO7cE(x8ISk1fjQLUr0 zTL4S?ySAbtWo|49!K7l7gVfgM%uO3Yx6P4mb7B-A>18%voPuh}4|LKF^42B;01FMZ zTf83b`9&mRencQfWjS5@A9TXa15Iv48ISL;|KCe00npY7eCD@yQZqKnRnp}%_n?|I zHlO5ctt4znGvaj6y5@aKK^+y)H+J_c{6G$!Nm-aFyA4&Pu4 zzfQ-!_?|geKj{v#yt|Lra=fl}r=5{gV2&_d9`A&c9zV~*ann3IAO0QIwf)_T*J<@T ze$wRdreh*|mw(5T7bk4Aa_*{<_j`L4rF-@BjY-)a1#U#A8BN6&0U2GdG+Lv~{z z5{JOu@a2&|-gWp+gI_mDzwZX)52(KyoN!<& ziReg{&Uqg(^(-PS9QjaN5{EQQ0R>@f-Uhh&Eb$$ad?)r7s1#5^spD@+ zlyPdo&G7BAAChT?b16Y z*kb=qBwR@DSe#=H*?aJwtqUi}Nm!`CzibxM+v_0os;AQ!06O zg%1TdAti)iA|-%PVR(2``ZVSqjqLtky_yr90%Wk&f}z1Y+ASvilqg|l)|JH0kpL}w z1FqF~B<=bjK+7-x<^cRa1HW$y)YG=d^4q(2*x>Q*^5skE%dcw39^e7Y(@*qWW5L(R z72t6#z?(c*#gMiIzEhBZdQR%PvT!waXD-zaKf5XiB0WT^NgV-3K*6hNyBjwHECDRi z$4RbWA~AWGOZk&tF&>Ay(O?LTE1&|#74S)Nfx|TFQV6e?r%t zsn5~RkEWfr740#50147s#xRwPk}ODDlHw!jLINj`RTkwjN0_gQ2~J#`CqvblbV@Qv zy-37Rg?pK@Dc2i|fplOhW3@=hg@i!^ENrGFhdF`1hN2d zO63S*ljWS52JMWGmdYUMQYS893+08le`GI&nH4?txOHm{M= zKnq|A07oAIT$sDdu&g;gc{A1xKp~Z)uRe_JCMU%uSk{Q>pE)RHqb}zApuAMbe1jN> z-jw)g>$yQ4^eXT#eN~^5Z`rE~?N;SyDH>!W_D}M?Od0j_WL4jr(awWz)i!ydoEj7; zXbcETO301AN+NWCA?yhSP)Yw}NSYMlx_mPLK2@J&E4;LP-}H5Wo&2f$0ZolB{Vt)Z zzP^j{tG=czN!vSpK=XAO^&92%quS+a&O!^{PTxNgaoP+>=etaD2YrBKq;{NIXZ|Gt z=$i$68EV0!p0#L?-#E2OI{;LHh5=&s{qgMe+AS&BDBF)E5mO1S_@mxvvh1|y{CRS_ z{QB2!5~z5UdJ&b~eD{rq_~s`e*AF{yfqq#bjYQ^QE7ttaSZ#Tb8r7uT2_4m>bLZMd zOpLM3{`-18)Oa*0cF4plqI*9D0km-KhmYhubHeizS32&+>$F@=Q4QD#&yVZ$JAY|< z-(}$6VRO5jmw3W(?#1hT+_ztDm;L*F=P&I}-?j7Xv^^_#*EhGzVT9*|?eNp|y>B`w zPp8xIx{MuerGM8gQ9q7TiQj}%@vf|^=O&md*Y7)Qhw-lOg+XLaT@W9)(Va&)&bw(k z{5pO2(wOEyrAwzFj5wX=^j>&BX#hLA6Lehfx}1IOJo=sVl%4B$;&=GIcK8Z7f8XKc zZ$@$LaNfC>cKf;d^}9}6+cS^)Hzt4YvvJ>(i-h3y2SDb=Lq zGt?_rnuE+fGWT6*BlVRe@Wrp?f)cbd7@ZKk?hEnF*?y3FrKfJ(3b z^g^efKV3gj;J29q9|B_j{LlVbBwDi5p8k!gVTf~G4CR%P-nXPq0~c;x?0t}oxqpAD7US-V5yS|DS+P*Im}B^pXguS$y`Z;nyc;HP zNMsCgY&%S2ffgnsV;F-Bl)>NuCV&|hkjiDp&JJEJ3d5M$As!3k!6XWY2DMNO6I)Y4 zhBwA-UCQNj9LH!EgQ#p_*mq_@xit%PMl~@=jObcq-VD$%P61*}lZ}f=2xPHLT7c~i z46+40H66S47PO_OpT%!67GWL%49@}-+>Ck*i3p5EsfdJ8mCybFI_hLG{t&2@R$z03 zij_6^=bs;r)WnfuA{d`#xOP?YWbBek!=nHwcLN64`ClJtBt>jF45ZzCw0TpDb}B1QzG4)3sTS!@)VR5rla_8r5-I@AX2x36Ca1spD!(tQzm$Yh z+Iwf-VS3+Wu747BqcF?TzHoo;|w?Y{RfP z

    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 SJ9l$g9Alm^SQhrC9K3y28~!oH1@H?oj&1TqGa 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

    PJL~rhk0V*b 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%0D1Ep8yUm&^EhBS zoH1cx5H6~7U2u{P!{ve%I1V^MM3gxe-+1!phM#*}mmDg|FRsNW)|{eChBb)q_d48x zt9(E98K<3$OF5zZF1>iM2q)2})&KEN{!jD>-K7oumX-q!fgZshnj-e|T5BprBDRa3 zpwak#4Imzd?xDwzV!r?0KlpC}{Vzo=oI_tmG)Zt%1?&uc-j82iBllN-igzRMB+kjr zYZ0YClEyem=Y|9SUN~YxIdG$>L}FMvV^4CreCM`n=9}oBXe;|gXF<5MnsopEy-)dW zDVp>g?^M<+eW#NqHK9NK8kqzVoi9}nCrWI2jBB{*>9KGEG=9B|pJ0sv_3*MVmUNju z%QUZanO8m=C-d>@&$6=q=4GAq^Zs;tyiRE9%*%M0=jr=;UdGRGubJL=$vW4=>-R(n zmwu+n^x0NM$nc2#w*FY?pM|TxxQ*en9btX)XTy5`rbfp51W?F#$ZHM^pED1sjT{jA z`?Fk~S9d?g&B0TzPY&2jm-Wz`@pYhCJ=e^GI&DU_lyymm_=4ssY zH%{)@GC#w-&-d}!ck(LelJ}XvasBD6pXrP%5Hkc2GXfBEM_)N&JVAC-P-=pw3lR&B z@}#jy`zJmMWph#@l3Tb;hcEGD<+d=Lrz|O&RD5cGtzAasKGscx_4h(p21g}a0PAT= zwAcP>aU&M_*L>eSyla7<%oaEe#K1^h^_{&7Q38?2W;ygruu^yu>(5m~!Z1uza{_;c z4I*SIr~hmK*=$Srd*UrPpkh(;P;sEA<;|Y-1R(aO9vfQM0O#-- z(#Sz0OY|{dOMo$KCLwE!GcDFL#OSX}018zF!c*a?8pV46_=54e2$)lMF<56E{H=m`f{mg zHA$esy1FS7tO|vYa1hxMwwdhwvRbE38+C4Y+S|`YU9KRNAUD%TT6gL}H;a^qx@3$e zh^0;%bq;8;$UHPps)e9V2n$80Y+pqmJn0P)5#CJ}eEZKj1rY*a&YBMA0=j9~bV_J$ z(yj}DD$n7f@f2C0L*hk+xSEod8)jf=m`y6yW#EYM9amPCu^{szM2h4F879y7-@6gk zk~(+&&5rWYA5gF9I>PfmXy; z7?)Z=+F`881*2Hf0r|8+!1VSH#<~!P9}rb?#VEEYe6$Hcytw?LdIktKzc5cdHllCC zs*!z1dkF{#_6d?$7YT5=jB=bPwh+p+LqxE_fkiw@qTVb8^l@-d*`GlS!{r6wG>8IL z!5YO=*5Qitz2#UDoKTO4)g}O$+<21{So(si!+CuS(A#v}O5Nqw zSP!5_v6^-im=C}y(%r?WJ@?)GIspHg0CI|QO*vS!@`HJ20EhuV6}j6&_YnOk@hn*4 zSQ7vcXqBtT{K071E^^5Th6rLx^b5M#c44ii@&0RTj)@|Y6<`Z%nfs}8 z=(bT*D1aEmq@ryAZnpVt!kM9%R)}n}MmkR{s~eGuXw;Z90wVrw7rDvo8}%H?DWat(+qVg;T3wh4ms3hmEkqe9;>X={Z412)0VUu~j%eq|4 zTyPJu24H^k=B?s)e(PTsue|!l#R9r*6r*FB$9I*bhR-`(IE)SJ;du9;LV#>^bO3Ii zaX^?GaMipKYw-X2zx|V9Y-~Jg`hx;7a^@*6X%if8@NEw2#Q)OYOFoAm5qRIn(&&HE z@h-uoU*Rthrv=H$Nju7g~~=hNBY0-)CKh(KF%7%|%Hk>X$zQy_e2QCp)2z*%$eHZ<+p# z>ow`#EFZ6M)=Otv?~R*v;WqruLuZ&S^EI9>^N_AIfAgx0%VX3d%kXMif7vdk&3w2$ zen0&)Fa6V5C-c!|TSzNse)^k#ruAxk(`DKDUU)qjXTd)VmoJ_TpN(K$DjOjiH2tiv zel}h1={W8AK8E!hA&v~EmGoJo= z?*Q>R2Se5?K^DXG%kWH_^~kH?nHRU=GHvD6dS$$mX)@lAvuPiYBj)@$IAU0Z=N9Ip z66WgS=TcsZDwHU*9}P1r7sdo)N?$kEZn)gIyP&&PRyt3kR*@ghm#hr){-l5TeA4$( zsz)J@%C}Jc{iCdMEb@<%JiU9`0w3BIcs~%cUv$>qfFOYT0)oH|r}N8AwDZqt=Ba%s z89KxOwebF*Ak2SO6h6=5nh@ z1W2$;jGbZw%6=z^8I@V8`$JI`^$-X`2)@W2pqP@I#y!ebY$?DRPu_hmHjJ{)M|)V` zs5c5yc{d3w*8+)7^}%?0slJ5$Lr=uvgkV$M12@dXxiiJtu`}ffFN!#Cz`A|w)?|P& z0x>ETl^{3;2&KgzngqfJQ7bm75>B2X%W)+5pd}p@M9MuNCxc2IEC4ddxK`9okTW+X?V?H1NHAPPc< z4ehJ0kI-F%GDj6UYK=;c7{LzHxI!yBvrf68Bokspi%izHg|d8~=^40k+^|E038=I| zWL%sWFJ5@|5_Lyk!wh{3F)xSDLqw=NVef#OM&QY|@bp-a%nhuZIH7l!2t^YV)mox$7U1~VfpV&%4G-aB5c-kpr4{ZJ31Ycn zJb_;qw4oiB`1Y+rkl8*D001Rk73-4d)?(NL#;M~*5er4W1o5mBl?cuU30TK*DA8sv zmS(M)hZ69-Bi(A$AY4uB+9gpfLG1g>{h(510VauJe_H=xD8*^LSH*3*IJMu|VLYQi zTOwJVL8HTtBe`WD3L(h%cRbYxR6;CCa6x6JHCR|`Y{sN+Q#D*|t;IkeN>rO_sq+ST z*x!JSw4+>a{e4|wxu5ogOky z%5kJ}V*5u>Nnm3Yz;h2EVV|{4qFdcZ)+sCw!TF+zaQjq_m_^nAMKxJvOl~)JU3`%u7=~GQVuK`HS%&dlGs>{!g7bB;K0S^&DTwH*o4_!FS2iJ%O z=AX)x6_JwDN-mr4ym38%js$YMyE~~aZx--Z@f?UHEh{JoX}>I>0I4r+?p0 z5ZQx)jnzfI!?*YzUnySsqgRX9UVFW`eP=Sb3E5TfU6uQdF((ki90-voDi@$3ps0rk zAA9KqxFhF^x8Hm-fUf`LAN=FuPYa0Anv(|yI#(a(Qo8c_q;G~+#{b~`f2>iDqDO)b z#+ZE+VP;+7d~^nw!c5e?Qck9v9(Lq3Fa>-yWQ0jaE4t4Zc! zDrRq7y-)BYi}fmqVVKTq0t|-vTnVD+XMQ^Gbs1lOYiJtp<1M%vu3u$d-dD!u@%Y&= zo%tIl^Ue5Ko^f;;Pk-a+yic05GJe*}GO~V|zv;4!%4_DE?V0a=GJe*>wB8#(@4f0O z)0RF@;BOZG1d4jB2sbxfJDKV&64lf*pRDf-4x_$pP^b`aL1CgY-MI#(8Do?$`$+miB1m6VL z0YV@&1?~1~kyjw|zE>ewa^v>?6geKmHN8o^ErZy~LQK zpE!Me$mz4nM@J9~Qq)KAUP8d}qz(l+6D52F6eZ9-t)7M;+u&UU4CIDMAjX}qVoB8k z4gfb=Q&B0gV@1vc-&5jALP`;b5J@7e8-h$A=Iq3H@%3+fJpdU+F7|oXeMBNQ(Q~l5 zwi2Ry5>{ax4ihjFII@1W zsmfagPPLR2%6pklf)O4@c-k%?C1DwyZoG?gGrp(vdk}|;4f5M2u4ZOe;O5X`(gE-J zfWDVIM#4$G5^_q(StZD$Sd*Mi>*{r3EN)YtqBw#9f=Wz^04mhYAi*jJfIm(9I<_S> zm~zA~6$h)gL-DmZK_?(KNwx#^^7*s{Mj z5QkC+h~vhqidF?)x=|i$n<>gApd*kh7~|@{32?Kz3MU$RL+aqX5QJ!9O&0(W+-d@l zddM2L0pPd|x0>VEJ{BMlP`(ckbwD|Cn5ol?WhB-}O=ORb3{ZXpb07UstQXEdbr-p2 zI0rWn+fsDXy347jB5T)nGQgbmEVYA+0No-g4OXX5Z(m242p=6AWjt;cHv#Ey-?>M7 z3Gkw)M)8+{F8aKO`ZXgOzrq@Pi#ZQa3ixYByWTmX|CM6+^b4#Hay8jHPu4l^ zHsQ#-d-F>1=IgH%Z@%$barGL9B!~+x1B9=Jny|GxRVW$6E4njgT)`-r>WlBgczj=_GJG zF>9J@wy*Xgq|?ze<1tqR*36i5rOlIVjx3 z2MaQ+*VPanwGFVBYnmED?tqg)75(lrMz!1>vGa4HnGo1tV(+K z*>E1f8RK;uVq_H}@aD~15KT9W74%TZ4Wl_}xnbNuH?e6}FA6((2m#Ic0xR)yikGOz zVi};;4R+|L;FMp27G944VJd+b30hYc-Z0)z(GfS#A?ie}>jaHxgNN#6fuQN^Mn3|l z(Vo7qAy!k5sK79%o0p%xP(1&_Wr&v+fPwkq?RT%hj()3{n!*APItkRtBJpQEs<@Yc za!phzr9__M7R`V%otzvB#CRev*FZ~%`LsbqY!q{VEn3wXL|4q2k&)1wqS(WIxEAIq zQ%-=^R;)Qd%uDFqo0)-NkkErjmOumuE5%w|*&vc3j3qXFd+#M`B!pe)+u(p{1@sV9 zY2c@V-*x))_T)VPi}~n(^;wieyQWg<3)8A@5-m`-zXf zQ2gjuzEo_Y>~?8xrdUS@%{CkTHAIsM#KT2F!tn*ksuO? zQPg7|E`Nkz7LqLx`E7OJe-| zx#Fu||54hxlRkssrG2*P7j?tzA{wEBUOAQIP;h)Y&NXE%Aj1uy3tueekeo1@;jXC?=1nI-mnM+~Mr+8^ z&$mi7=WLq*Vu(ZDf-u|GjdAAbdIRI8hB@qf5@_(Z&39Q5K9%(D(oS-2Nm!}7#`z_0)0&S^(Sh@g=9Dy|}UBUq1Xqz9LI=b)j3UKz*_19$YI~NAmUh$!dLrM z{Y>h$QD4g*TwUvHf)9>)#wqXRI|?YvQ70IoML2*nSb1Gt74gOMQ%Ibq}Q(>_*;OAVzU2%Mo}}&yC~0pRx1Yv(E%r zBmlDlNNvB`_e?#9P^@z*B0ml1%cbYOR-7LH7@!sw^F9R5_jawmb?vRWH6uFag&+)Ry`+3N4eqt2Mc-@$Y$NV{f*?;g?{uW$VaG2qK zP$0%V`rzOoInjVI+Wz$R|JE;a^au*(`pfn(PoJNtv(O)g^QCq9!j%o7 zf7UC*eXjJYY}|}*+-&^3xAU^BOrITKI^*eYyzKBytH1X;^T>QMJkw^l&y{)PfU!KC z>CH2rvp#t>ZIP55n9GN%ELvoPNBNZDm4D0fGoK8Znwlzxhllg=>Gf#~e5hOCgMgUc`Vxq70tFWg*HSMG zx+$1@a=lbT;Hjurv%(UT5}zKvNi;bpoQa-xsciFrUE?A5HEinZw1UKGzRN^dUgCsQ zYd#Ml`V=<_3o_)NVhrZtDZIeQDP`bcyGDBo95EF=Jyx=n}bv4uf+LIt{R*FBMm= z-h{3AR&nRfH0;<|c;mf14Gs_nVx-i^0x-czwIVoL@MuFvfZ`7|5Eq`vyCZ0a6Hk_J zEh+g^uZW6tM}~$%N#-tufgC4t07%Tcfp^Vay}waRPEQ9BEFt6CTf+%n=u07nVf-4F{vrQoqKG0jb($o7HeWV^tX^!t2iYP(?J_n zhsC4^)XjK;5OOlLBern)!bI_vFMgg)d2_M4Fjq|7zF92HV0Ick1RVe-=g*xj&YnF3 z(MS8sp+#wOE=l}J^!XlIU9ldDJ*c=-0L&9+#nL4HjUyL_0E?&Kg6Xoa3&NBq_$5b- zI#1LyBPh^J+5Lk<#n{=g;?lF1ix)l)5#&id<0@G6sh_6V85V$ojN_Oe*1`z_Q7lIc zbLbRvM?gky7&*2CG8stG#@+`kM_&q>sFc;QMkmF1Ep^~H_SCoH_*3i!FoZhp&{x*q zIFuOgsC5#@mLGA=KC6SxZ>S)KpsB>KpyUEzlZ4*``cad!dl2=|k|EY5;pB<3{qC4r za=gLruj1TY+Fd|pk9V?cIj0;e)=7X!aJE_?2(To?1F5F~S%}^cFDGPs<*`X>wyl7Q0RLZL=o>0 zzxH-Hj1|S7F{8Xp%z4H+Tpf|`AAODg_Q;fo2vY zvi{YqZ^o#dnu zC{sM=EPzQP95s%kyMVWHzbGzc-^;P%y0ftf_)nd>Fd6Mg4O7SDvkAA1;&Fm7a!RR# z%Xd|ePA?XyZ1(_PsY@GxMo8J}np9x3>*BSHI{xEMxsLpSpps?dpuC%rLayS8|@G=5P zjhOXzytLA<{siHcm(aJu`}g;C7UN@QNqZ`|cAO*TBgdPHxKFX(4UbL~AAk9;6c?ZS zG5`!9uxrDI%(;dwEzA_JzxunyAN=08i)+_!@V%LfIq!EuPE)^0Ry__I7)P2p9~&Jm zp1pJtQ|}jxi!XcyP?0{s{h&aMdvrN!bad%aPgOqQ8v%6Q;}MK>JdSeg=u}z#jA{AEGvQIRuC?o^T_gh;h19l@87r)95L#M(R_6( z>y4$JXH&XO@Tb#1e-@HIK?YrhRmL|D!}YW3l|T%Skw=zee&*rTG{!fbe#WT`=keHO z+)QgcozIndTS6QbFH^6wAr!=nbpkL;f>AdPrrq!=9 zzRyqGW#yi9!Ih1Z_t~H}l#Qvs4Qt0|oNUCr5)_@6AcJ{iKBmc^@$}EZqJQ33=B2;S z8J6Yir^~qdXF0}AaKkt{^D&Mt(|T{3%DQ@Qxbbz~XWrhMuWgZe`D`9ub;il^Ghh9^ z&*!{aMy55Lt}ti7Ov4brk5# z&~2@SRW5#I>N0_H$;+$DMt|IySZCz#>x_65sp8fo<>TW^uObp>#>NbO>Yld1hrR{g z55yc4gH4mnDTqQgP;Le!D0l!QVIvK(b#Mg6Q~^wAvRWci9uh?jbnHu+`I@R0I6-Fx z8_5HR#A-IKr(j1eff$wsR$dkQY9VJ=f_NJshPkbt8D;EMIO=J>M6V_|8#(E#WizI- z-u)^R9YQRv1H{ZiT;HC&8z-tNu8q@hc6H9o6d4kbkt4)SoCJ`ZD{{oFaDuxEcq0+% zsijV_AX(!DVpQU*GFRhzBI;(*lUzAMB<4c51E7yYuz81A7IEbx=mm&T6iMqUiwm;= zLZ^yz=S~B}TtM!BfOe=2-4VC%P6ZHi`_?^xm^C(4t}JpJC^i8hOL-Dmo_M=?=N5pc zRQlTvA+tw6sQ*NLHnM(exlI7A15TCGBcte0;G~_feYl5oDh_O-A0+TriGOc;w$Kc- z+z!5={)!W+fKpu`aubD!3&fP0=HQNj0CViPG`1pQrLsxW`c6H77=Bb|7Z%XZuo(G; zUJTmOPf9c50JhV8+Fr0ljv9#-O^Zt$%Vi-D(}}+X>hNH1@kzv!UW8452O{tKyYFzy zdL;ls#j{S24i+E%2(0*kREoz5>UbirXn>p=ihnes+eDz@J_MrTXmY~{Hmrr{06K~Q zP~}gJIkcWk&s zINltyrc-gY?Qebj*2}qJ8k&kR#h7Ot3Q`E(Xp-4?bleA_4tQi4F}G=P)A*CKPWq?4 zy{YI!6lD()!9#!-#Y#?LflY$<0YGwJcO!shXK{86y)J{T0ELQORUz&;U%dXEn~2Ml z%2>OxCNwnK38>Qy=K@?nfOaZV?L|CEaMZE3g%VtqtG2-rBUg-^8xrS^vn8xA2`E`l zEp@rx_#QMXfMm&~5eju#ca{MI?nAV1W3fo5NoY-Bw=mxo<4K(~j&=2iNazW?mi-Sv zOn-%l5@S*1!#;7}6dW)1C&N!}mI16I4WY}%=XpfL=jX9X7V88acq+Q+dLDkPuZ1@g{05l4ix#rC((uA}O~60lzb^wv6) zV?YJPuIJ7M^Y&il&RWiMY_ziuDZZ+nwGdUM%>`raM*;5_KK9k(#gG3)(KB#9+Q@P8 zVRmxJ-MR5j@txOx4~t4~6pLB|V+~V`)pg3Tbx%jF(O2((!#A9qLC;S8@C3@$vnC#LGWUA=Yj=VjKhMVzn|a zU1S|T2-3Fo40 zp%&r-XopwC(LZW^6mE!V_0K#i?<>Q-uPnpz&0l94pDo{OmSdhi z>(Z}$dh|E_z2-Z6pXsb`hGiMXlRnM&HI9CnF5l7eji=KuXF8Lu+jd&@V^ zC+aNthvh0?Iy)pg!$#8Cz&UurgFl94{LDkY?2ru44%6RRW`|~nWmq=4{^phCa~scD zzVWiH%s=zdKkH&Sl>iBk$9YTTnm<1S`mmbzkf^T$27{%_(w(fSaF-7k6o``y;__nC z;KSMi?+0S`i{ZAr=zReYk%(f$D-h$xJBULF{(XqQLpJyaY)bbL!(rDL#Dpj9f)pMI zs)(;v?ia+6w0d*^F?GP^u?k>DAf_7qE>)hwvpGLxqbwM60P(7onH>NbxgAzfa_XtK zgqY$@n%g`CfZ0G70PHo0I<;V8_prD#H(N~JyBDYOmft`b*4KAaFF}AY5DO(|Oj$=y z71jNsb(M9MD)KJQd6GjsC`ti=$WKv=l7QrWSz<#bpNJBHwvU9AWl6BOZUhh`0g8T* zK7g1rr#V&Sw0wScniFGKt}mP^KJt-g&?D7JBUJ@~cK7Zy`fcuT0*`1?u*kb^VVMV! zgsn{oDe@JFQH(%67qt)*=6&(PSwvwFPvW2ARd?^rL|p|%n&DOucxr`EP|wV6h_sXh zZRk3I*p^t6@KyhfmP*u}A^|P|Z(mwJ^HK+lr|xpzNPJ1;OI&(&o$UbVF<$T^vf)%T zNdkKw0$p7!&=e3Vr3i&$0L^elnf^Z9NQ(PNq}cc>eGN_<5-0|z&J0a(%L()R<%`AV zU;ac<55aQ#+STI9+iy}fr||$W!&o=@7(k7LjAAAN1X}S>ak`vUa=V&}L5>*T-;J|+z64r4&9#04S@!^|mLb9h5Jfu28n04fPdm?Fx>Q_z<`N%E z>kSa0NDghKh>_edVI2su1v$q;hYj_XD@z57hj5SxaGFl=i5I|60EU4`*|v*q>~`Ne z+QtHEA?Bs167$BsQdB7WK(ImrxYRcS5aax0{HT0cojHmE3ET-VdfJY10_GX>0Wd}& zM%_V{GrzPNM5kumt7$_O`6^zdLfsYs9l6026Ol`3n-ltnaQMhEwTp!z+fvRZMXVGR ziZU5Ha@%Y%mef5X*V!(FrJ&8O?dE$Qy{llPoDh%Zm!O~`RlGm%Dyf1AshEmjE7EGeTqs-73A5t2mlK zm|SNotDDhRR4vvPMPYnLl{YuT5u!dRb+QD207$e22xA<97Og$C0bmHmFboi{!wj?Q zf&+|paIU8rWC=L|0?QGMTmo!e1soAXvma_1i!A^@u1l?ysq$iV%LrcHy{l4N*Du@F z^{k5ZLEvJC_Qm`(Vor`{vJQ?IzYTKoj4&tEjifT}8MqzhXVJ@dh^{6qODVGD7!+I( zl$0CBwXD0Rqc}S@Li@?Navwc9vn6*G@4mhc=!H!A@=BtG}bW;17R0lqjn(-*se+~~S z#yjbs?`GU5;;hUM!};R*vR-}82J@=R@Qj;r49k1t*vLBH#m06<+0i=V>9Wy{@4dgw zpWBF+d1WW*ugg3vC+l|dKI0iKsF4FL%krA_G(Vqpd9}`#pVuri)8*APx{R0pc{LBq zw;twE`7@2qdSo3;=P$#&expJ(={$>S!y=+dY{6J#j7mis~QoULpH*PY%rm$*fPKs^8io;Q@?8~ ziqQ#J2%!9W9;316HCNQ&S;Yv(ARjdbnwI6YJbhw)J{B zY9vG@$RP?mQQm}jT!+I#Hg7ouV%cI%aKkG>t`#N4COom1^<1$?buCEnc`7fVDNv*f zc4ER6_t>FrR0b+*`GxamkoE5lS2E;6z zfA#UGyjB8DB31DP3HJ`fZ{(CvsiXw9`cvc>2{9LlHubqEir^ZkQdY%@;v`yP*gk_` zZYMmHoS_y#%Q6N1>L5TB z-%0#P+oVw6H^pfUWQ#3;2O~A?il?WAl zxOV*}0N4gZonHvK6Xf84D5f73xzMDxR!roiQAdV)hCJn;ygM1=dt_v!xNzxWF+4IF zKujOXf_wY>04JFfjupnNmb=2z4n%JNE^uNvX6Ol~5kLA78kYBzkSu|ivNl%g5l4)3 zt`vXs(yZ6a5UTy5%?kuyxr4?#`quR>^_HP^la_$U~oju_umZiV3Z zpl-IkK%aST03bcI{;VlfGumFWyv{HG#ahE($EoT4&N{CHkYz298^U>}6R@&PNKP!s z6Tw}R*e2Ak0WLJn0QVq*^Y9@6FLgz83S*=bKuhio-g$QmK(oI1?91ni&wS=Q?~meW zbc)POYf7Clbqe4B@Ma%fGrIuP4S+k&pLIY}ExioD^=3J)Rqij$S2OdP!?sQWeVfc> ze~Mbl#j}oHCe24XuUh~&T3fNG1SdcpglKSP!Ifd3*w5yn`R#U;f6EOc2(aBOD~RSS26vO-sGveSdUC8=6KjW}d~&BK=IHt`IXMHj z2YO!CG*j*RhADGQDa*wXqE-Mwjhg@FJ=H}cK%{t2ufQ^GzaqED+)}Jx4Gk#rMyav! zW6aYZzMo)|9Cib6;+#7-#vE!zk?S;is%FU(rM!&m%?*J;xefu=nTzTLauHG??)md) zY13^uSf-^*d4E7ZxMJMY2|%mkWnp18KpV?zL?=uWI)c;_B#5NRcdCLOt!3T|pq6if zx~Am3bZ;rwiVA||Ub6LFt6Bkr&z&Cy1R99;zkPc$^vKAKW_#DMMrj2~(9Cuhv~mu* zuFlO-4s{|kKtK)@SmRC!gcDcpBImQ?Ni*cTfVFZyS|^{kQ0%K>Ui(&&t-gV?#n-<1 zw~FUK_BG;^93swxKWlCs1?Io??O!W??VtSD>P(}JKxoGtFS1!`!mB)CFRru&$Kl77_^O|YA+EA70E90d< zQNsBSeh3ph%0@OH^Y*Iqn)l|B}2OlP>em zJS?NK{D}0V{$!<|RC!sBjiR#=DjOm@E*mZ5WWyVtjiq09e8#ge?Fg^=ZkaZ(mS-GY zmT#VRV&GIjN`R3O}5?F0b)kkU=5AKAIQSdyad5jZn#WgHOj@wkD#>*pZMaMo$RWz z{+IFah(dgNbmK$$D4YEfF}SG{Py`tI5Cfj z0FftUg^m?PiK=Tk!D4eO5W{Y`IP?cWR&jlgmSZSba zPo^|KtrAi7z6i#6%I~Q>q#hd@t&t$s0T3e?lR%8b(IIP)$~FaKyqBv((Gc5ET_M5q~-sJRl8#egi;&Y$>BzhV8gMgG3|E;&*gWF;*h#f6| zcv`D&h_IFd(dOpfdPy`%)Qpb}hc%7yvm=;AUPJH60;kECiiT(wgi^dg&Xoo9&&bIE z%>%Gj#mQ|izrJY8IYewE(v9EM-4mP=f=Lp!ikY}c3=t&WQL&d!L{aDlPW3S@%L%*v zT#5x~twmxU;>Nz_M{kAs*+ov{A3A=hzw5BTfMX;Tt!@KW$yp)TVYyn>X=#>=z|(o! zk#NmE13z%$-dlX;lP?wL$4)Z_cVThARxGoD@8LB50w7EWC)XQrHyF3CryJs?)P>;& zIFxL{9U~wicfxyDuLXC2U_*>gt^zUYC()d(ihA7$>snDvDiNv4Q_X&>GeR9;=PsNt zo&j)Ip|56>9aqkWhxEJLFbui?a0FrGjM?Y88lqXf4sQAt74f6Qi~&CqjA1@;t)eUe z1OXj66a+#Q4YLmv&+(gNyQeZ|$EEEms1onv7$#IeMKLmeB?!iN2PhDXsRIZLOJ?M$ z;#E1u6s1^QMyw42R1O*E%N8J@CaL{N#ETj@S2lP*$BJLg9p?}nWjl;b-)RrP=^;v6 zy>}_Fzpa<^NTPHVf^-W`fgS1_95F z?KX;AH}29NTeKAls2fl|4RE3*sZBWcdizMn8YrQxXiy&_G3tr&UR^+LRqR9l!_`Nd z3S3)Fd=%Z=gi~XASpayKxhi0T-WPs?GAVww&Kh7J2FDNmt?BFFTmgs>6c&h)fDa|X zfGYx0O>mxQX-8mFAXP3F>v$iqVU7IM&m%{N96X9^X)=8sOGS!3X=+<8Jxw`l5_)zH zt69seV{oOYpG9z3;6P=^a_;at0ak<=XgEp)xc6AA0#qba&IY%<2XMvEBfK{+M}znt zf=ML-*$%LPl{bQB0AUsZjht(a^%|72KiE=Ku+-fpkk<+rF^&@6e(E|47vx=lQGp=2 zTI68b2Ivyp*QB&o@SGFYS0F}3%YLF1>C{S-7L(>?77!QX`vQlGK(btaf=G&I34m#l zO>V9=@)qpuq2I^GdZT<^DmY!_TJ$>*bDFDTPq14hz;dz}rpT9Ek1LcVs9pIj zZ6>&=Y45pN)|dMaX`hnYQAON$C$XZp{xCRmdb-<+k+ElspZrh$4hngnkN&j|f0mtG zbpP_#|IgxAe&v7Wdx;`z#)so`{LE@Z{Zq&zvITEC)1jrb4?Dg z2#kAYl%cM9)c`Twj`hu(H*efzUQO|>nkCw=^K%83hPAZhjPb+cbNK-(uRJ~mE}Tqb z6*4dBKEu<`yprq1JdLMchG%)kVFpIIUX7P=vOZpQnMbBcKl75#^r|zxX{=A?XoR?1S($&PE5EK~Io|8C46pu7pJkfAPJf;G z2*ix=!5W%4H!;Ry(x#F&KVT***PB>^$jH+>{IW(|-Do{vgMR#~Bq$MLxBo0?d@$q7 zj=#w~F*JW4B5%v!XIYPKe0aY*j+a4J#Qeh|x`?mBW4fnWqpTf-#{p1|m=~66c=(%1I0~!)7yn zY&bzs0AgUF#^mWK*s808QwBmnAcjwv1XZW8U9VebJa_$^(1MICwFXCr*&#*O0k)D$P}TVZ;+9gYT1gx!d{$%U*8BF#;Uo8Ztd z0CBV}=K@5H#HGqa-RK0z0re4#QFKK@Hn}Avq}`DFPfAMyLeUpl$|ayYG1eN6dM9?| zh=CL40vs@!N}ZdXg0S8UQKcXGkVaImbv*@LAI2x2G)%0~$?`cF1tk5_j>HH5Wo zY`e(aV#VbMk@%JnQ164BJ)LmJXew2X62Xgkz?%DTf+*fJHg+~nZ{?P;O;upK!d3AA zl?n?IXbnqiONza$!dbH>2njq- z0x_By9_Z~VUV4EO(n0#20jbrb)y27@9SVBQ)+?Iu?bQ;_$8fT0oJckUPtj9cH)d<@q);T%8u(OxS1 zrjn7)xuKb4zs|;&a8V~yR|iU4QSjP{UKg#>tN=`?gM?RsvkaXf^vBBbJbkraeCp%l z#iu_x4ghe!xP9{;?Xb&O5}+6g&Y(M!D3WEKb)&3T?uQ4olLej~8;J2IXcmfq=|@3D z#m83QGO}${nk$$r_#_y#DsTW`U_LFaiV|TuoN=MZn#yKv1GzN>WEEAClS5rJ#&gY* zW5cyu<=q{C4J4z_0QzVMb^f^43D)fbbo*O_qeU>uHXR^}Jl`ceu zd>=(=R1`dic+t#^dUQ&QHj3A1eq9B-HR#cCjns6t&w@hgfDw$AqoW(s-qoP@+lZNJ z)y=VJ=B8I9sfl^3z8giA9x&GD&?z%NzY=9D!V^FYmZ}7-)Rp5r8655@Ui`>q(s#m5 zw*t^SN4`6(Q>!6Tt1ZWfgVD_SYY ztSJ2g0MBxOKCTt4=YT)1%_2Ez}cBj)#i z|M!c}eeQEku^8{)t2N&j91D~yjLuLmJd2(%wjagkPJbO2#a!j+il}%U$BP@|c~e%! zI9lq`$NdyTgLxXk5sm4EZ<)rwj_K|$M3WBp*T7-!a3D4XN6aKa|LQ1}^r|^{bs_cV zvlG&*AcA4(@|hWToW?Ny^)p@O;Z>J;d9PoVna^HLZth}nmhsci zy!A8Ot4=?kO>excYvz$xg2H7vd|)1nZ=iXaM~3N_`RbQ#mE~ucSAVAW=e=>V-d@vX zz4Q6s`_;<)FkCi3HcZwdJ0$(BoA>#%v1}y2gxQ&y7O&$5&W6ld%$us0sYYyODPz7^M+j zZtJ-*8jH4@v`ESyH>Yl57aR%vE@DTP~JIH^!@oUlH>-hm(Ha{`R+vFMs*V z$Kg+Jp0>b;xdq+_#2oAtbql|V&KAWKAmpgLC-QY{^4%0Ufwh(-7_$c12N?zL;hHOyjas?<$PTSq(kJKCv76`S8x zOwDp4$_8D6Qh+Tj#ci$mQ2 z3$nAr{sSne6&yLA_~bAU@vH~x&8!sl&x~lv_#hxN7QO9o%^8DS8-ODxr6U zcUDnv6I>f|$J7YS09H6J762w@=L9FvwZi+TBv|npXOQvaK5;EGeLZQ_H6uVKcq50F z?X`<&(asKQ8K8vVk04M7B2FsItwGn6VvPd6rtRn~E%$`tY6jFQYthtQILR1SVd9$c zudWsWpK8Xjbx@y&Vq=OFna>VjveunOFzc;uo~r77)(JpW)?q!dsYMMydIFjE#MiC2wsq2UpZ%gtB6|XhsnWdj7@B!NIe=y-vQEu?Gm3lS?i_ zIb$|4v3);8sTh+2SoCi_qGZ;ol`<#J4#pa+9u7HGTw9$}>Y5R}R6I}7tOe@P+10{( zU>%ILMiBP^kWp}n=m9>-z2Um56{V@Er4Zp#0kvFMasVk#q<#=ZxYhvx1zQ^dbk*^s zX>tM2(CfpRZvE)G0AJh+yNeZsQfd6hKmFz6Z~mA613=USegEgrsaNgyfA2Sn-}v=k zfs3>g-DeY_Td9$CPEbqmrLO%2);#s$xO|@afq)qI=wJQnSC1O9TruK-+aGlQf0SvA z*>WEQ5W}}arT(#*^5gfIvwS+-mFuW{0Pa8$zx92^LS|58;*E7H=^wMOo;uf{u5P}| z`}_BA-nw;%wwi_`W|E-)Cn7uoMJz-oXrhx2^_R{ZeNIp#0SD6=<~8#$Uiy2_%N+w0 zRyOnD_Bc&cyCPoZSH7Gv%ea>7Hq&dg09ResLBA|pfA1~dG7Zb{tXE!5pYgbj{L;^7 z(;3(NEI-q68_u}?jFv%f4O;~A#Q#`kJIS-yE>+$;~T2sd7)&pMb+ z=e2U6_-sD;PClC^^Ur7F_%mPQ3icSL^Eu;Zxjt7q{Y`Hgfg0aezx2;K`|N$@?{mJN z>Awa-JPZMN`s~@WBLFd75)PgknaoAk#Quqjn<5>4Za&=vxb;uLa%*#K214vib^K zC~FYGY=YTo3cx^1@)J6Nbb{CwfZ1actO!LY8f63OPEtTs;vnWBn-4iIP#S_rQgd;t zy}M}a#@Y;EOjQG%87vX|a%-@Jcp6*<;rReyVQUGUQ5z7oY_vBJ30dbPU4mv80u2WF z;D`~3Y3uDTwmDgR@5asI>dhM{rCfoS=F}F#ycGqq0zv9Lp%&DQ(`Yx{{Bupv`h};o z0=EJy65eilwZP#!gdPn~o*^*8LJ7o%;v8CcQ6GlHb2A%pfta)ECkZF>_{gkdtjrYa#HuI3cb>&&w2`)qB^jhk{O2knqm1=0iNSGy!^X zzp^5cLw`bmE7GzGr`M<{7XSc207*naRD;B!K!YMxp7OSlPZz|)=U53?Ir3J zO;Qx0i8^+1vS%O3!MK7#Ot}SAzIzBb*b1?7dK87A5Y(*@f9KAO6g6^RAj($DxD@c} z0;Fk_m}RW0dqv{V6K{c-MZ~rQ)YLa2SCa~urTn#w)YjF>J5~ocqrVD;H6bjBC+Cg| zdHehN5itV*yL`DA8$XM+B*eL>6TK6hI4hja?*Mev1MIXa`onnM3K1`K-O#R@jXov! zi~te8D(>B_D`KP5Uk;B0$`goDVYR?gHSf5K7@eSw;$RYJ0t$Wy{iq6L2;d3A>0V7`dGUTm%C=nKxZyCf8?NA#sZmuOr1KF=o77m%`Yq! zGxH0<)gnQ<8(^br1HcRQ@?d0(wLlJ^0|-@WjP54Ju-tHp5~475iFv%Nm=4?(zJpvc zbOZHa-ZRu0*0^%4gFWHkZ_v{j26uU+x`6?j%f@h9n~$&yfXr)FY}hGTLAK z=+{42eBz~N(EkEx2cX9^D(>8!D*o^fzf-*P?w$B*%Qd5=wbqvE;^O)4;^Mh(#?N7K zZ<6<21(1>xuNFnA%uU4yYEZZ>IIC_LExa_tz3{^G&x8oY%rx9-fH50@R$Fj3JcP4E zaUj?G(_r_0c57{Eg`mrsGNWh^>a`t&e;Vh)B`7uVq|jg?6Vf! zxjn-@>m7N^6=Pcpnk+A^1+e0_vL10S-%)V974DkuZq`%Q@lM)bT{nhplIG4`#ktm5 zV-)|AJ5Ww1bi42^y3aQ#baK&lUA(&!cNK1&c|bOSqeeis4nTVW)BA|-sbJV|zzRUj zhXd1m7XUf`JAba;+>4H7@Qn&yiSbOh#u+6KSIgn z%I?aDf_%P*R><|t_4?T1=dx`T6Ek46CAFn#YRE~F4ljAgoncj59F}`sU{K;p_GhC;i&zV>HWqR{6 zPhF;$ru3vC^T;yILuVS}r1M#4Jkwa7;aO(Z$8h~D$8ev$_o~ZqY2)Mq$})|o%RIAg zK3jgJf5tWb6LDF|CsNpnLb43?g@3FbJ0AC^uFs+DkB`Z zM?Sy$tG`$+-+su!RVXpACsq8Vte)rF;CEAoSD$(F+kEtNXseeI*y@P{2n5Wi) z&qUDxfb!k3a(hfrwAQF%oSf=L4JB{(=7T)R(s2 zf;&JZvllO%3j+1-oypMM(B0h&L3A1dtsYa?(?KvO+A%VE2Arj)xJvu0v!MZ2d`(r$ zO`Nt<2l0RL{8({jWDpJ@xTXND9ty(1m2e+XD#ft0Xk)orIf*7^ zdZRh+GZ6NOZ`A|BR0DMEG8dK+ zqf=i^6W~famXy36KzyzPCfR|St`;Dy0~9fhKuk59zFnw5rk=O?S#9c4T2m!0}y_Wcfl6>M6fM5 zN-PtnUJRg{82_HqD_XHmTdgx^1bylNKjdCf9PRG(Jw$pC8Kh4ooJ$=%rEVJ!Mjo&h z?6Fq(4_-NblmQ1DFB+Dq!W1^*E)U?)@H1_@2KvHPt{@-nA32h!ZuxK&rgBxyZmXO$ zME2nLv0VivC8%Aa9JA_4G2a2^#b-bF(c&v#{tTiHDCNa$^+Wop5l~X_@tt?B6t`|o zg;HeO&iO`|cg^rb0))&^D`v4YN?b)HeGc#~T!DS(sHVAi31B5+Y7;{`s5%oPO z;ujX1!VZt)I8Rz0Ar~4 z`kEH=YKwCdXUM-4^XcvHDbPs@*s=h~(S)+!)3M&I00`e_e96rsCy9WFih99U1LWu* z>}I^fJ#WDa0L(oBBr7_k$d{JtHW*`aM+$_y4-{N!ZmEkhdl5NR zB+SDD2dW~0ReX~KP}D1?$e-g?@uYg@n4*E(tSy@W!UANqd`mVNBU5*$!&06H16spU z&)7cSfCqp`_u*=4X&Wkj`oH;~iiwLKwORk%JIBGy)EmsZTgBa5S3}=cC)}-Fe0z$< zkIE7b9ME(WSK1ec#OFDvI%ksHs~i^m+85UJj~lLo%iOcGhRMt9BXd5 z{!C+B^Y*Hr&U8BCR;Jf4X-wl7ZXPG&SJIxQ_c_bVd(-*zIpg@O%X(O+49oJeeB&JT z!g2Ze4qmfv#!G*0<37vw`H8!%^ph?)8{YD>QLKZFk#)+3&&JD!%WF2i;h9fmTFWqQ zb|kmQ_l9Lzmg94lllf)GWFA>w<#XndX$|w+&OEZ5d=C>^M&)zn>(ww_=9P7|Zl6B_VusJ0Jv#`ZJFp7+?TLholN-nKMJPH} zFRrn3vnn|5gDqO+6|s<&-uc&9*${a@&q?tkXXsI0N1n$%u{dT-;v5I0U&Zx%g#F`x z^pA@F^soQ*iil5dpSHk2*CpgVXub}EPGfU+2KS|Fh(L# z(GXA$;sD%0BuF5|E(W4Qib}#tVy?hSQY}DCV|#B=*V zS`{~<8-@C6F)27<*p!FJCY6*prwWlyLPE}uA&84-pE*}doS&c>>x*}9+%6_(X5t;2 zA)*!kdC2Mb4x$YLF>B}mQ6x#iK*FY;IO?O2^FZQh0TCk!?BStafQ^aZR#84*{S5*| zt%x2;!wVd}cWp8V_tS_;oj!dAq6bS!03VCXOK_l|9|pq9wiJ92)X)NzpCbV^#guG& zD=Jae1#l)ujCEU-BZK&|Tx&f-ZUs4kG~eBgcoGAxXoQeFPg^!ZSYb;Y@TC?Jl(Q)G z?F$fuigAi>JE9nt(`x%Vlr}RK-MkA1#kU5Q0EsBAQ_=W{4`FSKKA43AL?y}+xN>y$ z^z@>D{o}=#zw$N2BTm!rh;?upUWGWpF2I$#YBmw0>fm%;Vv12(8~~m?Kx9q*Nsb#~ z6Ln1pfXN{wCGSbPTsn#YN#s^JS7KZ;#uRUZJM9!4G2S1@8ARg?#Mpk;-#*tumHJjB zJgZP}+K2+{dcdVRfEZ17+s*-$0G0?gDuy93V)Kza_ZX*WVxS@4<}XGU{9ELHwkl-gE#DSc+S_kNA-QBJEd)m6pwo zJ?1C<3n-&#Vhg%dakidwZ$|2uc@kmoM`-K-t0 z0AO&?(MGPBYXBH408nz2$Ppvw%^>SpJC>Oi78WSCg<2e7UVAa}bWOJZZr;2Lr;|FN z02vuazPocx%T~U}7vYma4jltd~oqAFFw7` zeZR01k^B1k@DYG!-ScB$-WKR>TnN+Ok382#*1L!h?`2&4J>Akpdam)z8pyt(6Om## zOy_Rjxid{$XukS3F@DF`{3-ztC!v1+`~dr`^GYG%=d)qbZeCff!g;UrIph26y?*BD z)gJ>n{PoMY8ID)P&2*OQeWo$oJS<;-(`6iP!)1K^vrGew>(9LO&v!Df4C6MO;ThNb zvb@Ywn$GGFg?=9T4VT>TBt{C$36E-U`T3Op((8^~+kXCoOU8{Njt z#?jyV91xWa?R_?Erm^$P+h<*t<27A|SC;L)phtF4mYM0ZEU#HsYY6h`9*hGIVD2%wSzZ z-QWuHl(!U&{@?A9iC~jmH)^qZ}o3t#aL(hpk%AiXfiTmA)C4 z*GJu%@jmE!6gw{E{onulzg+z7zx`i*P~@k6Pg~$a*8(Sj80TE`!zI}BWmiWwo7pR% zpis~uMDHOAXLpg$-+#y{JwS{Kc0IWlgi((LJBqm90|eNS_>-{2Ta(pIZHOizo>bf1 z2@ul~`d$Kr0T4I^kz*d)SiV_TNh{!NERql(tB5Xok}da46$)YZ0A%V={9DHcmmO}g z#(MD1^()1VTesrWd3babu7v>z(S|rBRSBu)P1QZ2n2*H0;zo)$xS>%@!gi3*2@xoW zn*d@UGPUgBDYS&6mS7|v+%MNa7d5837N|D?T8oCXo{sV<3>!zakJd4>y4dfL(jl$Ko2V4hw-4 zE25cS#YQ~6A3rk)*AJpMH4xF5$kmcmAKU=-w9&!Q*xTi5UiE+<5{&*NKm{P=5K#=t zx~u}YG{TkBH#h{5*jjw|t#^xSm=rf$@q+R3am*TjuK3~?zZ`W{)JS6WfI0FIPN;`~ zF^3Qktq`KzglLfpd!sV}83Zcidk* zmHbAAUL^V@MDYT35#A6uuwB#%11ALIVVCxrL%jDUK#XI>_fb4WfJ2kba!07+LXMaX zIGMH(0|F&s($`^aN$#QtaMCcBAZi8j1Y+b2Db3O|oJnUrtMLy`5+@S%6GT$qMl;+J z65$fbictx;kUBU{qK!#UQhrs%3oo22zVfwC7a#fPGk~106yJU0EyRV^=vTyI7-tg1 z>TcO(uD8%%eLXEje?MYxBVDXl_c4h)Mc7U;2xwDZFY&$s@HiLkDj1}hZS{mSHNq*y zxKs477UEo;MCxTxOiLg}T_g6fpo8tJ;`{HcI-9g&m`-bZejj*g}kAfL@BtW=!;f3W(MK@5#Vrtq{z!EfW#bN%rqQ5 zTZk9g@4e{Y7yz)8n`VW+ymgbg%KE6tlcG1SHw>Qug)|i{U=TnI`T2fr^o40%H{5#( zOwwU1dENpp~YSL!=3?Q~guzh!F~8D3&g$AxDgxwZ%n6c2-$i z_`cA`-Q6fuW=z}nD%aM^+Zyvj3sUND*?zDQ7KuWfjybW8l_7tElY&}`GxhVnj=3vW zuEVj`z&vF=ppAMF>FnXBY3X<0y&XVHaGU`?3GfL13SKIX<=QRqE_aV6!#4m~HEA!< zt=Ng8fP&lm2dB_MbSsn{gE_^#P~_A0kxQ|M_h|rBl3P)|OoGL(u}%QjBqlccgLaCK zfA+5xKl+#c3(`xJ|HX0-AFLMNedSk*+t>cT;Dj6;9xWQWzd;+TbBMO)cTzOzPXfmL z@-P2#@iRa3Ge?cez^tGR$|r*!-5o{zfXh*wxK!SkQT1`)YpJlZ47)qR4ChXYr=(;c zmbj6d@%zy~rV1)4_6&#?vsS>BHhuT*o!KKG<{DA{O++)Q6SV|2^2*4JEd!STJG^F? z_kuS^FBD*m_X)%pZd~KU$US=I68`#SnO;pRt!$d~w|w(6On+U5d2f8fbl%%MUUk`2 zS)a_qt9j_W`pY~j^R_%`*3384;1zW-O@?LOS-#KSn?|9v<23qZ+|0{#+4&ik zWoG$48)m!&KlJlwp88e3W5&Td{0+-I&By1gr~cmSZ#tj7*O|9*{pshkG?Fg;)2}j( zam*)wneQjrWL&}@dTF#5Gnk_)iuA*1&as~)P-j9t11%18nA0*MNJPcS84+BNT(ksm ztWGd&n~6#i^x}^!!=8FSa^_s|Y~GKBKZ^P2?opbI{>Z1St2zGSFaBcj^FROdkCHvT zd)fjY;ubgw#3&lEboINTi=zt#sGcy_LJU?{@!bW8IZ%l&o8JS8El$4=AiSWInLo@; z-cy1|MZTKx-CzT*t^_yGH4x!#9X&;JM|V+=_ym+MUt(C?A+S!dIj@JHWG7l|D$;_v z`90D#dZf*U`aY+fT3itXI6&VFst-s*z1Y~ELZ12nBJiDSSBkgZeK+)coH;uIVKcxC zM_d3iuj>%nVnmQ`5VQg=5JeE10je;WJ%P6!!b*-DB%VBb>JPygrg?ooRyzG879~_v z+z45P?vO4{s2d=d?$HDSFN!TdYyrf0x+icVq1E5t86b>0VpQU&j+7OM<0)9=6_b$A z5U7yzBy@g2bZNaLbaDtFP(Q^l%wGkgS~(fQjO)eoXCZvriZuwp8+YyjMoq@aZaW}V zKN~=a&bxPJ(7Un$5!OZAR^FGBdWaTJaaF*oP8P)|B-jPhdgymq!X+B)(^WZ&?^-Cx%y->XV#vcPNT?s-(Yac2el*@(cv%zcv6kiTb z6ZN!&xm`||t-l1&?A&~S0E!DK(zFi1AyC$3XKjRNE zFxHV21CuycS+zi=93oURKpCn{+cEAKZ>w;bY$JjqcTHEPmW0rS!JL+}Or1&XC>z%d zwN}Y)p@T^gBsnJpa0O(wR-|Z;_v$QhjaJW0BSgP@9ou*hg}007OL^}K{l5sok8cy= zH7XD9guBe~xrF$W7JHoMa)!hjN1oQjpW{L<6Gf8*VH5|GgDfnrF%JY&gF}e^7hH31 zBw#TF5F@vsz@0#>;Aac|>P)deU0)nKD{v4g(keHNYqK0N3k&K5L%fo8VPIeoux7oO zo)!$ol(Y3_tPH>n*oTPF($YeJRn7^`YisV=IWA|LqK94R+6nLmK>EtnTa5oE(zOJ@ zCvd7xomJMc$;n0P1&0x9yc~(HF@i#1m-mOB?X;FA-F!$igfW0Cby zu+DXH>K@kq;8JVj`)PVjU_1WKo*o9LhwF^`*q@4-=pVYA012JPLt`%&U;FX@ns!9b z*Z6XOq@bA*c0c;)z#JaxW3BICC8tI>GIj4r1vTT7f&s88D^S{qrdlt znMS%V^VQ#b{d58(`CJ(%^UZwB-?*mpKI>vUpEJI3D*ZBV^U)bsKWR_nd7saQWxUM8 z=d5dGyeucfGQM&0-mpxQ`QjCIFwA)RTdvPu{pn6Vm%dNvuNC`YILpn(%R1OVcGk&; zG(0;e@9~NTG`=ny*|eFDSHT7SGCUg{uj7U{OjjA-bUs_2KhxzvN`J2>0T2EBncg&+ zk7Z;znP)zG%`(g{({mgChG$-RZ+xBcy|-@uOsgw*G>@*P%lc<|`JC5G_aYnb=RmcG zhKGiG8=ITXNB~G2SqaxKPaVo-NgtexRlWgNPxiD@!YXHAnJ%t!(L{nsA6N`gmN;p3 z17*aDY$9Dm&p$_j87IBtis#YV%45dJ>!X-I_j5m2{L(M|(j$+jcTZd3L)-##2uw~Y zX5x2{FC&E4TfhA;IT7wD&R>`)TG>bz=vV-8f%t)dL5c4sEW^7GQ1lA1>1q8TK!qY0 zwaiCP{(`1m-hLZ4eB}NwT!h=BH%{QE zr_pPH^^f2<0qAfJ%V7}~M_eV?1gX4K<+UnnRH3Py6G1#fAb4skU}Hj+PD*U@vV=oy z@)L;Z@9!c{*x^yIS}i9L1kx@9={_PFr#L-VS!6G)`eT4FXU?Ex7%mINMb_Z*QQ56v z%<@ucEhH=m)q3)ZWg`ei!Gb;NW8+9D^`Kip*7z3kR9vAC<(FE)k!XJR>P<{DD>`xj zvE2l*(@DJ~Zq!|IKM0Zrh`i8^0>L0Lui{a~13b~!RI}hyJ7NOoCdN4JZzFsQeG1b- zSgOpp8PS)f=8od6ckUL~Z%lI<4mZlgNb&V=d>PK1ehAo5h=NUx8@eEuMMi8I-<$qxje-J`D?a7wx3(45kDeL~Dr8?L62F#mp*gRUB;x zPO%;OUNhQqbSS!GTc}4z5Jue*0$~df*sp!(O#qrxaBqB)dDB>I(hpncMiSs`0L*Jq z1cvwB5%^>rN(3v~VZ8)e0l> zAQov#+Tf6R#mPSNR0}RU)K|buz*FmB0x)az$qLrJmLQl_hAlW&nrzo>wfdLf2;dv5 zV&zkE@9@!mNIR_4Z|mqKS>Hfk5I~HgIOXO6FqJu|h?JnFbx{A2B0RPAiWe!41c#c+ zarw2ub)u40fd$90fTe9vL!pWr`UMt_y@&z zUcbtibI$yr#`^M-Qw+cCIJ^`0FoHL zq)|L&Z~#uEI)E5Nd7gjv4D$p)7!W{?5UugJ3sJAm)V&!%7xcyOjt&~d^VA_CM~wIC z>v7lC0PxcSM~g~iofB<>6%bc)#jKIfty{N4PnLTg_eoj^(n?YrU_mEJUgbd1dXOAs z>Y7oM%dvKfv7ngFJMZ14J}MY5ff6kq2_Cn?p&|&kO@4MxT7r7Ox1f$X^&|SD$!~!T z;I7~-TSp9P2F1H7w6*U9&{Ry@06;9Kf&izPb)X>GG2z;&LSl7U2$Zz3CJYVs2RORM z0o(%PQZOQcm^s$3#l>|%8$=f2U^+m_`n&Jm0*F}&0(Ig%fEjD5+=vZKj)wrDuG?}r z*__U6^-VdS?gJhlzy;|+z$)Y1HE`@S`gaBqv1ESwU9rvOd|BiB;=VKW=BOCi{?uZW zAj=SAVq|0h5KG-WDl$fiHNdfYlk7*=IoC(~RpqkVjLj{$-E0#@on~ebxdhM}1R$)# zZ##6+pg@{1=YiZ%edwMVI{i%Xg>U?9F*x!J?f4hh33R{r=C2iR|M5Q!QJs-XKaIE? zy5(p)#Y;y^>ymt9IHk!$QyhpzIjH=jeBWRGx3)|Ljoz~L@mRNr^|RQ)rL#E8D=drOnH&))0IE9vYk z-!T2NUGi!k{_=geJ$}yeE7KYNL|sHk^&(uk!w6V-Xw;roRnk+{!vj8j>k zjB8lld$s(G>%Dn-)#bDI#x;MP@eRxTz1NQ|aiq_8$oM{+{&}o)Jja5GVUD40*tN$u z*yu?RxPdW)NXX)&CjIhWPc~-mfQhinX2g$sRKR%dDh=~Lo&nA99QQmgbHeTh&-j(j zy!Fd#W!R(4%Fy_G6!G}pw3eSa6Y!t^XMeN!-~Zu1d_1eCPfuImL)roYG1Jr20Ss70 z0hF76^gCFg=`T*78DWCJM!$-_7L?>SHNXh~fwQ_WTRdRSs;F1xv2ws1vH@4Hp@B_O z1H^eKs)TR|9UtUbf*TMDfCeoPv^89}*|_c@Zd3;-)6S`p`Z#tV3^pK0HvuUyI2->w z!47~#Acjq^zj`;@=;}CR6TQWz`s%Hl#oJe|aKgG?JahRubOUrT@7J*Wb2r43-1;;@ zxHdw#dJ^erZ0Iha{%+pnh;a>a)9$IfK+QJX4Zer$_AW1*h+?QP*JZw{O70j44wUA4 zGAozLLkMQAvxGGnh*3z+IJK^acvEMB8~H)3`!qwWY0>9C?6&Z5h}6~9cFDVTzmxfQ8-|2w*qa0!0Eu_Jev|HsO zvTB`5JuND|-GUpd3*9rr=yWmd3S2pYI#if^@L$gn6qJT3sva zfKV&6yXJvQJtww5!>gEtL*>TU1Y8I0#e3fOgIM}%2iyS!B+Wg>*)BlmAvw;_%e1`# zFiBVg^TX3ps!#*0IN4{M91b7`{T!O#X4a^1*fvy0%mT#XO$d28j?SGs4^g1W?Go@1 z>{FyeKv119iscEK+V=OL61M1*6@bxY2X`#| zjP+f>$W@4772Gbb-DMtbL!h%YVo>-+m$0{Nxj9TL_~Xg0%2fqERluuAjO&RYM+@WK zIC5_|79^ZcA`+-R9=SkNQv0r#&KK`_@AIKIqz-Yl4Y+ld;8ys?*WbiS(yPVT4Y+w$ z1b6ELNa=(NMXsg#BfCX!4`#``;k2OM)@Nv_k8z8BDa3JR;m(+z<(+p0n&3DA=k<_Yvd*%+2r^nb9jyVvG<{ER#`7BQ*+je&?w^NTIc*z*GxM)U{u5` zh@qIy1WLNMP(Vw^mp|4#%|Qnc!x|$%@PIzC{(>>LSr4q!aptG*D`!p%;6f8zj4GKe z%}lSvJGyTAHr7k-5!cQz-3%9?=CR#`**1!<37~2HXlSrM`WnI!v8y@y^GLk6<){N_ z#WjNYc?;BiW_AS-1K^+W)5d$9KHbMy<=X>L878>deY#G_p`!?=Yg-fF6BXW0OfJM6 zcf2{K?T5jEwrDrUu4}wX)HMmNNaQx_f*e^mg%>EcYJzV?; zaKb$IuKx(IDYxegl>eW)TRSVo|M{igE9RzNEiS(MpTptTPk)pgBLf2i(I4@lp?#_V zO!fY8P51GSf4unYXFtmv`tI-l8Sc!?(wV;ie0xV5Ww{%8=pxot@l|Dw|jBCH?3jD(|KPCnl97i)wl_+WSuf@KAV3P5Xx|` zmmoBr7l?uFv<=G`=QbfIJQ4MzxvW~P%e=7ac*+qkR`V|PPSABi%I^4awV=vPc*mcx z%EZUgm?daZ-aRbKXz3S8_WqcCAlGjZz8A4}qs62@{gXf8Bb-gNKgPlHK?}TdT0kI1 zGsG`m=Z|H`8K7LfCq#lViWYH}9->A^v6j;cfvAE}xgR_&T!Mo^Ju|LQvaE+5 z3znmMY|#DMHXA%-cJw6SSH zDXhd^0x^Oy0mMMW2*kK4b}1o5kYE%;M5wD1eD4%PjbSn zXp(Vmg%TToE2ZIhptwLG?1<6cgf zRluPw#>oQjBN$n38)yp%QK?jlg81fx&p2wJ4-NrLt@Axs9A2wLwu#J zc=p-z08PW>>pU(2Q^i|ef9+=Rm9M;vnfPn0DH}1DhWbwyJ%|D|0N6A(JYb$6hJ)Ur z`rs5f#`x}GTmq&5Cf%m4LPGU$SRA6=73pe4;p=hIH5>tqfiuH4ka&{YNv;&mZZ6O%C3D~3lpeELkAOZ2ZSl1LN>9dUhhos{FH4FFE?S`nmf z#(=$nxhFbCdM%kv&n&Vg zGFMsiFbIm6V;k%D5*%p&X^19Ke1N&GQT4Qw9FOYc3g8ED@x34TQ1OwU`7fEU7_`Z1_%lv9(=6bJf;Z+3_tdsosuZC# z4pARN|Lm1S9<7`unVNYo+t5 zzb?b`YM69h=Ie7dj&U;ojFWk1em;9|nk+BtV_Kb8!_!sM8rQrt-)dar`7=yf(`%NM z*G!l7vmE{QKkJ`A)9Uu;oqmt2tN9r}%Qdd?($D87=BgEZVueyrwE?})#!vrhU2K?i z-rIrMc-i5m7pQRHRKu(B%sZd6V|~s%xQ+X29ja-(&vG(d4krDj>>YIa>9V7}=5N1$ z_1XMQYh3U1%I)5>o@G!z# z-aR&IdUE{<*x3IBAm${e+6V5S1>Wf`AVIdch}Z^VAn6((+$x$22r~4X+=i$@GcOZO zT^$ff5MuWr3J*aD)HCt4mIO)?r`WQGd&=nSl8_L@@T6O@7j>L$qWeOkgWYR!m{UU- zw~Jji-u0g9z!9Ks5Q*ly5Xkc=snq1BC*iRXN9MntG>VWYo>a%F;x0tdCi*B=sOJ;} z@953((E0J)^DnWXX~YcbRPpL7ZwBEl7eIrY1Ef)8MBN=8I(ot;@nF3+IQ37E*LHEE z*8~9}tFUW~r>HwxjDk}^@I~uFT3wMFCKMa9GRh$VYcPZ(aUfz^A$A&7*b7kMy~=$( z9d^xAsiy=Dda?jzAUwwBLqy5Vzv2a+FuNei4zFb)t;9qf$y;`Q+f{|55{H5bYY;?o zd4%~?Hmial651s<5?nqIe(I=@17!_jY6${)7h*}S1Btf~BNK=L?D15+4}wVoW)?zF zjwDZb<+^x)IE{q0dOrk9y08kN`Edd7`FRwZLY2!2wFWVI8a^e4$O?vI*Twa8upeLRZofCU&RCiZ9->B6Ztg)EIMxlWDes`CFmc;eeN;7mWx+keg)1Al+g~KF1paSaSOn;oKJqn zRT_Msy53^UqSFNKpcRN(!2>6U#I+{jR{=V2!vV8|MXW8w@8ATh18f1&VP0s7s>bnv z-w|}>97a^-E@N$WeiP2AW!ga?25`vlGo8qgi#Z^8<Xglu#9f5q!8U zCkdQ8_FW_I_`>r~6+ifcF9FD4s`;L#qBZr4d2>W*v>Mc^t{}iq6+^%B%Jt%F|JN(U zH@|TubVaox0@4rXiQ-B1M+8t1i$Sz4ERn&jpaRz}L`2kUG!OWpsEOl3Ygaq0Z_XPn zG_{>XXYmWepB4Gjz&N;;b z1uSd}2{Ji(*VH!yK`*eYxoo$I?txB1(95ZzIOZ%Mle$y7dl~D1Ia;V%0NmXMJas)# zG)U#a&L=rUdI1woVae@2{j!9~=Y<8hHkJ@WoMp|0AQVv3Ot%Vvhlf#wjhK+Q+rk3i z3xKZcv}?0Eh7=)GCywJzjv4{{<8YY>(nwskGAD;l_0!IZUO87zQny8vDZf@+y*kNS zSBH3GZ-{NGQ)*$Ml;1+~fki|M?d?Y7Z;!Xv2v2o%LfjzOhi4eU;P`DerwSSt}4EPa*Xw$Oxt@w z7rzTGEO8ePKGtsbHR2lI=8ygS&lmsti(d@dA%cx6A@R3xkByq1T=&x-r4AQ#TtzKi z3@C)dq>?hJ6^LJ~69gXtkaOP0^S2X3wTPxJvyyYpJqjFnSFj4U!U59s#>U296MU7Q zbfZz3xqCFQ&-!PWi&6R+PdY9AtgT>;{?c{c>$g8GUg4_#dXx*@!~DuvWqFpF<>tNd zE#G`B$6wYx%QxKkI^(6wdU-XU>T}jhzx^)D*57oNqn~lJEljU}wnMdiO!`o@kWMrN8U0AkLm&B;b_^*x^Q<0%g+;}SOLr8dl_&3Gf)#*c7to;&-3j*r zV{UO$zlo{ld(0(8lAPRkxR?7N6skfrxB(J`31T8nA(?gf34Cmk-yH}DbuAo)$i73_ zTM##QnYhg-1u8Ia4MDk!2#Y!`WZhPws2hHjARgnivjK4Dfg3PD5@CQXI1*OLcN~SX zm#<$BQJ!aCc!`ku;`+6*;`P@rL&V);(}tiE^|%co;>KPuBrLM9iSfjA2kw_e+933# zuwgg8dI|(Y6t9u7_%?Cw-bFbp?cs@RUr$>xGTaAYheAj=WbP}*g^mw(QhBPca!SD$ zPpx}1;M2tL1Bfk8$OJS5ss(ox3(}&8Ac`Q2<;ZCxRXmN}0y&{n{3}r_$A$f* zE(*n9p1yRE4R06Lou<%ZGZUO5>Nk-Me_(JZK$&SoYy>p4M&qVa-8VXwo+`rNSm@=X zTQEefB$fMW0Y?HxHBS-D6C zhlYx$p1#DXeSgu>)m@xDcY(Sqe!}7o0qaL26iP3^=>kA;jB(UVxs3n{hal{MK+umu zo+uGrK`cpS$O2)0w)=VqAR;)?XMDW++MAeI??nvZ{l)1Emk2d~xU(304;W)A#9m)U z2gM{N$#0GVDBWU%uKo|fO}Jm+&QMHDC9*Au`W;79&v%1biE+LSH_OEM&Em=%Zz5_l z2sg|{(jE`-Gr3Y66LNaUp|OVk3_(Hl)+{l;1ZNbX+6EY!XHIB|Yk6%MeKCl?uny2n zv^nK)6{u6kiNK5iAwk~0dNU;A>mYXJ(wWEd%{2((nOV5RP>vh%8UMa(D5MoMgY!;~ z1O^>*OTc9lLRoGyt;`^9#rwoMz!(>V8XV{^&Yn3HfQuYRiUMx$Fs5m9b%vbA9Pz~q zXAuMR#Jqv=eFv*Lv&A>R{t5)$XmIaof*HOX#su?4ESqa4(yVI>=xmXTtgVAO zA>K3%H^>xvRqRXGiO?Oxd&r@uP8Zu<^VjQ$0nNibvb71&jwsVIrmnYZ^RE{%%I+TY zA~Ejds#^nGT0<;KP7cM4ob!S^a#5*B*Y!;hSy3PL!>EVFI;!L6>8H-qKPn-m|9S7} znHfYIuK=3N1;8?hzNho&PqSXO7S~ZuJa%)IKE1=b4*||vr3E?H0+j>1_tRAO>EZrZ zvnM9d(?WYH!X`)I%(S4Qrsv^;#6ptdYjR$>Z&SgsaRp{BT^bJ1NFe9NjoIK%5fl<2 zle4pc@!dh*)wTKn&xb6@;Nx?lm&$j8;eG65AA?KoD-Oj9y^yJDSHk`-`~Px6 zn@5>>Bm0#Wz8}&ZF_+`|u#AWCOF!qDTw&jd{t23>Mp8>(_}uz^*-Yp$E~8z*2Ame zWdl7-k5@SJGmSsvSJRuupMF^mw|jLkt!ehx#mR5ES&xi|S2&-|FP-VLT<@SU%JJ~<@yFE^5hg^NT&8a zF8=kAQ!5A%!4^;ZWHIzqScO$(%!pRjh9p*s;J9(4u+0gjF8t$9JeJSp?Oxiwn@8g9 z19391M{_8ieePL+n9qfh$ISNNde8#ziaUMwey-N+5I zvN&I?EX)TnDhMOTfubOu;z(e5s_2IMM4Z;jJs}r{oFwWPkzF5s8H6{n;dZmS1Q@Z! z#$E!o2Q$cRSXHXSa>OQ_3T|B8^eYylzKf%XuGG7kmlEW6kFst-xUK`_jXK5dMz(*~A=d#<-8|v- zr+C%j!+V3acvtMMi5A5Ch$q2#0wS^xLjS$* z{Q*FzXJS06H^#Jw$Vc%e38-~ALpE2I0SOS5Qg;NL3gAltk@nI2c?Vn%0x`2Q%giM?-HyRM)D!F2Hf3+hRYjxqV4D|Vw&VaG!^0EAp40*Q{%dB)n6Yd4DViFp8v2efZ%aq;3X*Pdc*jJ5poL~!+V z(B}dcwvp}NycdY+$7+)6)x^Y9F*Ui!*nSY}g38HPSJiz5Sj2d6%n3sIQwNdbHTREa zM$kFJl#*Lzd;-vzwmpuBn%sNp$ z{ktKr`JGc519-7C*}WEiVil%M4p&-?rtR~pr4?~R{+mT#CY)0=PJSM$#DykQt8S$;NRHjZ`5MmLW48P{w6svT?9g)R>EXse1&9Xe0zEd4(W@<4>vcgG!I;RZOj7&%(5JlH|D2KWFL@5r z7I=rYfPjDsS-t91TG`RS3H%{s<~fPi0*oif z54f&!(my$NqnH}U5(j3F-6WlW3!|Bz8)laP+0)&0pJXE|PO5Gg#a33aWFsi!DQ7c; z`401bfjrjPm-Ed2w+=T$%(i}1MlTZ-& za09Lm8O_6XV5!IxUWtq_M-9t5G*1Zxz!9T<3e60!K%~owQ7$$m;31gM4`HaTnL7}D zJFr`CqH9LNLcKNCZ5@sb6{o5LLmeK12X4~b$k~<>K8lX0E5g(0<0`eKapicC&=;U; z;*{GDhQwYFN>oC^Tku6)6Rxik*orC$VyGxn@db5-oI2GBq1y`4ilSA(k>iL!jEtOu zxHlT5`X zN=sUDnn++t@XpRIz!5Y7fu?01O(G+z(Z-mfj}?1T9DuHlzOr350fhvhdH`T9T^fYj zqyu8zj}UDBd}#tiq#xCZVflW3RKO}*xMfTLLQTU7u(}F(Kz^MlT-HiVS5G%U%zKL$ zUU-q`ep(P^oq!0Qj{23}1UTG)Akdt&qB9cDis$UYouS2`4McZV0aDf(^NPEfj0128 zR&|-6q?9_?mT$g!mGRJ0Tzc-M_-Q`dxg^*r0CNYCz*}&jY{7-5H7>at6jyOvDq3*| zAm%XodRhT4JG;@}L0igcAXmu_Tuu`=MvFIIc^N=#yLjs21={`;a}VO5Hwvp+l&94t z6*$YaBgm#m%@me?*625ZgMcLSj9JApf*42a4xlnXPY8Xc0sXQI2+~+Qd+7}MqRg81TZS8F z6)p@x3H8NjEzG&A9uM_(EMk(~Z@=|vgEK{<+tYJ_WdWb}zwgE3#TNk7;A{v{ImBmX zP$WGDs5l1)T`OEwy%5lHxQxSrVV(l618`@!hFNPl_%<15t(4sZXP1h!T?15HPH~Jg zxFi@rf?HloPA^4uv=qiWA&TREMs740L&u_f7lB1NWMZDvuX2~DFk4O_33lhyhAAFD?no1-Ma~7DhAg^ zKRWi*D`VgLeG>ehn_H(3&@qD;lVv%6B$@@46(^J{My0`vC~w|Eua6*48$gWT0_T4t zTtF%}J^^U1So*@^YH{`Y6mx0~(ae@m)ZX0*=NjA#0x=uREBm+uPOFjg?=Q~2@Sm{G zX)W%+J+1`=_;1{}5#NhGEWas|6P!zQpoc$nf6TuGUiM!z&#!&$YsJs~+|MB{cGCp> zN+O}W+y9)Y@TZS!QPcC`HIwDlklMY669UH-uedM&1S}KTmeJ)V2Gr-<>6|YK8e;Mh zsnS2DwbrI-;Q+)#o4MJ!*IE0P5EY!|0OEfqXpWywipUROCh@AD94}d8@Ac0xZsT5m zo$-xpK1sKhle`Qo7ng_Sn2+JQ>b-gB^h=H#pUqdNzdxOEt97)DEYppqb@FF?!*ta$ zGavnpr^|TW8`tN2FRz)#`}EH=K4+O(zVZFJ@aeJ~@AWs0F7wUuvYut=6Zo4Ic>+Z} zQiKg<=UTqMY>2$tAlYc%>u+P~=e=pX&j!}d=WNJoo|!)VyqeC&_CCY84QF1~F9(9* zI@1`IDmBdjvp2bf$bd8A1whju=&(%@AM=a=o_ODGt;4@SN#qG16mA`#l8VTAuO5z zJh&N_pi}pQV5o{&K@fs_;uZwSBAb1Im}W#PdLbx>hfcHk?#Aj3gf{-J4a(5F>DOY* z1^|W@U5?9=%TMZi6~Ai$Lt_(@aq@od+{KvhZ$Ru$j8C!gh7}#cQsBZfRmE;3=sc;F z3&K-v$O<;_ve<7$nXV%O7lJ`}Gs^vCeMa3ao~{cF9fydQfK>mCM3kaNmZ1eG-^Dr$3MiVPg%^osxmeT{ zBVn{onTiE@+AOEg$jAsply(Q`x$`F=-Bm4x}HULm| zAQbO1U*uAf=$3OqbGki@Z{+IWKmZH?z-j_KId}e<;>?Ao0cB7k4Y9g~V%*yRFQ9sW zpgXj&%ADomQVc>d8rxgqR<066S&pF(g}GBlU-=E%@djo4vH)xNo7+Zp=&IfhII4^fJjSXjlCV~|yQv~oNK;$NC zm3oI6i@+l6YG^aUliw&+bB8I z1SaZfAIlDI49anBkQ>N#Kn^`QD6FH(u8*OxT5+!Xiia{kR4O|#)C1@PSj@J^xiyA3 z*37J?;_m{GI2ROofS?mRQU43ir7~pno511b_D}Jo+klQ6)P0lrd>atT`ENhBvTnFO zEg(v$7?J=?SzmPgFsI#*?NCSSYI)9Effy~s(P7NFHAFePm}ecV=YT1!e^V&PokACw zZRcDSuyBpw5pJ1@sDBe2CyG$DqxVN{H%(WoXkGnVnz5gsTVhQ_zZ2tEoi2WdN*yqo zjAy=3pHK)+o5<~=&KK*Zl_iyIyY~4$>*}K6Jp`ya0Y~J(bWIjeRuo2Vm}}Rj7}LwK z0XA-M&(M~RlOwD*T7gq6tQVj_i+9&YCwLDn5@MMPeO9MYNZdwSI|dd2o@Ws2bZz>_ z|Kz_Yo__HsV_wh^2ktQ~;2aZ(aX%msBXDW|KEf4aI@Xr`AWS|R{G~5_srcES{aHnM z9-$u$d&IezRtSuI+<(n9rGNEr8FG>X3c>o4Dyij3G=Y}%t5GUZoCglR7fW2wH0F}w z-nj;roH>?N@C`zD9QzB_w%6biTtw7(j1Rc}*X7l8Ri&JkYo1xYG+}})rq{n(7LR*=hUKq(>6meQewi-w@v5Ki zq4U|Y^JiX`Z(TEP)zACP%Xrmv`sdGhhFMmo@!4?Gm7BowS$|z#%cm#!*Ghe_oMmT2 zWj%b(M#~1wIQr`nSg;|pBl2oo^Rg4YcV}0PS1rfyKg>4) zjjR)1kypmGeCuKvI?Ks?)6c7Z)jIf`;nvao^W7}daLYG;{j*Nh{Jl3k>z`p+R$k3J z>%eW%$U^fG#FBcD-S5J@=rf+;xY3IW#tLM#td0Z`l^0~;>gLwXgeNQ!pTAZ>VkzR8 z6!$PSr-YvDvGDG94;Qssh#8vjF>Ws?>QP3@^5dz7n7fGn@gM(j@zX#3(~rsJ!0Vs| z-kB{R(Y1sEMTr94Q7nRB__(+WQMifNpxhu5Ol%P_i91_N-WXdG6`! z4jc%xb92QEfYCjO+;&*q+c{lrpghHsCLsi85#4zJ!QI<8zzODRaryGqAS|7la-K9o zfJ#}*`Y-p6CrRo{@Pyf`Rz9?p!;@rz1Bq?lSpY|3M<=l$ zfvNa}M2A3(>zDcp@CdMKgSs^WS_tl_7e)O#0wfZF>P3)vkfTLlN4EhntXODCfV4%K zVNHrQQm@Sxgq1*wih%7qm5rK@FJfN{%E(<~odk$fsvG)zXjeIMJegMM?MVoz4lI_F^s-<C>lzqd?t4cNm)w=zqn| zJh_W$WWh0sZnw{J|8%x@ML*sIgpt#r1qIbTeW&2U8KC^$c-On+L1{Mupd3G@P8juP@M-4VEK4)S_s}gf4|pO7bMxxe;@rqcaqhx}0N}JvsF;nQ zOC3sY)wv|+Lmho7M~>rFF)@@q(teoq2AH`qdcAo0)t8IW8`tR*#tOwXhFFGHn-H~= z8wOD<=NmL7gtR~mIuQVz)-YX-)&js1#1k1ij%n8cL7r9GeH{)I->sW8?|six#gF{Z zyJ^>cfRSbNJ6#DKH?A#;KdK{WYeNfP>UIKPge^`hUh5kG6>L{rcU*5a0CohH<#u@H z=@HDZpDTt2IvKw^5bg89i8Ow5n!Y<4K#bOW5)@J?tIu*rsZ853sQ98|U#=~NG<|jg z@USZs-73~}7%r&Llj0l(+}vc&1kl1fDMgVO6V%;hwTm@NV9uX=9>Gk-msDcy_RP7i zr7yY19JlKL0V>y}$%uA}My}w?WQ2!4b_hv*)AEaPu&(R90+-Tn}2% zwxzS;}&(JK7hUTh*!Brg>ER;Psgxg zTq-YaL7}tCdykXO_1^S$jBBg?;P;?|wwFs%rOURpZD;$~Hg8@=R~8~nf_SZ1Us62D zx+EZGy3X!S^h{yhhlw%+*mCXqcmThGgnj4B(0dVtNz}Xi%)qqj)i*_Nb%k{RwUDN8z73$(sc=_`0Q)>%X_c2m)rBt z@(9|?Gt0?5&BJ`n-}19T4A<%By|iWKXPR_bu2<8R4O!ML-$#GLGfv(ot!??<>u){0 zT84R*>Tm*u`XF38&#Kn`K({2wUINu;h8o& zB|B38Z0zjhjGO7bn$CE-^v`sef97R)4hZl4Wm^4xuGYhJK3l$5pZyuntIl-h;V=C& zZswoirZIk|<#sP^*2}B)Hjn&OpYh(ShxdXx#*?!6NPByGH%m7}EvT|89PC_c+;6F=XkCQwzx3xaLEd zZ7qiDc|_0WS|$&(s(DyPH{AaHuRi_h;+H@9NjA#aRF9-OcsOW*cU%hyc&I1F4ZqG! zd-MG{HqZbOoHA>YSpreLFtan$!4;;qdnOARj9oUaqQ$h#tH9n z2plKVI-F^%D74k0h8t4LQ~ue_ic0DR`-h6N!)KUAZu;TGfEbr}Sc6kw46c_6L<{b* z+1CVW-ypg%AQC`8ie-q$bvE6e_BXTnQ3>p|YopY4K8S#$=r(C!b6j%7$SuNB!~)@o zwgkIiftEh*Ls+^G5+u>OhwS6(lu%iwIxEx@Be1K^g`t5CxE!zuM0%~R=qFeZ$I`r& z>raRX@!oO+$T6@D;V#F7_bRqj(WfV*iV6vw2#UChxOR9iQK`-jPsSxM(kw+Rqavd05FK$s}LBO#(FhnqX~P{<~&;#^PaRn)6~6zkj!K_-YKF>CL8 zsx2`pSBqeZX0|1AwaVnCwx*i1!V*_TnGJm;@iV%@Bkb)60rht z%K$b4F-OqJAmJ{nekj)M}2VQkU1T3?bAMy{B9 z5UP%|div9GyuGnfj9z`Sc>QnwrdS4ec+X2OqHy>e%C{}$Hv84UEHv<wV}3d==o;Q% zu7=U#DqJy+brtiD2NwZB}f?n!4>Ok4Hik7FQ=g{#r8EvBy=7IiJ zI3cWyO4L^Rm)kS@2FZINP?*e1;5g>-XNZ_S<6@2-Z|Lwml{^>var)3HGnTl&Z(4B<(_0o}5JmhnD<{!gq zjv+vrGIDtH$8}i|UiD!;Okc){WO*Cc@}X2t&As_?v3&Ec(m#4wmML6QaQClWyZ$C^ zF^wLbd7l4gegY}dgZ|2odgK~aag(Q?&Z_wV^_oA^IDIp1xhX8u=%3(+>2#*EI{B<$ zrnQ_b-#Gc4ex^09&U>9oB3v5=4(9TXBz#gdFroUd6Zhl=W)-O zmt|!8n4kBVr(yc(jGOP0ei`31-WxyTSzq(f&+@a5-0r=f{yzZ_)5izA|J>QL=h(S4 zE-o&xaG4Ajt6EuHwK8R3`Bx$#E0js@v#}6kj{km;%?BrY{_>Lf$7A`&748v_YFMob z;gzpgOIGu9DXhAPRa><`_0K<5{O<4mE+2e0x(Du{1-=hjz|C}3Jjr>~u)9*g!@vd| z!Vn@(i$HU5!HizN20=N?G`$be(*dEOND8NiY=nDJwkU|9;!X+4dz|d6yFl&!ZlGjI zmvGghjFxRYWj%%rz8o>TY*H0ZIyZ7QPKIP3RzaVNycbc9Ix#(s0z~xE(4IYgU9jW# z7wz30#Su2vf)IDuXlcq<6U%c8i|BorK&BsJ4x;@I#FQuNM*({z48r0N91Omf#7qaL z;2oSSOXI04K;lF05}i7x)&Yei{zE(oLK%7)(FDXH*w8ofpb|~j6G2~rk9x`xOi`pk z!hjd0O(4nur{n^HE@NYU0w9Z1V&cfTB3NL1s5{0JKl?=PkJ}K&x<*8K)Y)d~#_|Zs;7@4mGMR3vJFPBe?N+==P+|0#>!1Vn+ zIhT7tg}sWbNTjOlcVuJ)Vt*RbvtyB`?`pjzh$U{VvxJfy5h?l|yX}u+#Uoep;MTp=#4{fHcIC7;4l( zKtqlhbsimtP@gMa{nj^&%dftQnfY7A2hmkFG6JXsw}kV?kLDJ0Wm`~$zE%X|ga8yo zqPkc#kM2i1#wBg2sNOv9cJ=CIxJh0QrM-e3n(bBOOzyT->S$k{giE0xaI%-VDPg&| zaEo-9mA`>5B8dCDaMc~9pN4R9CJ=FoK7qmT{u4gsB;EU7}f?kl>}P>F9eqbn@4Yq zrML2 z@gyjxSc_aSa=a+MCo!%mcmas$3g+FKilgBWlk-Ef+lsu&C8v&(P3FkPIviWPuybk0 zdf?~$>_fS&d*YyQZrcS+H1rCcTVhEo8~9rpTz=E{Yf`mb5}zK?c_@=ZK<{ zf~2m0f{e~RfpI|=2~I%?0WL+k6y=d%?d$I!a?F+o6yY8FfmkS5d=gZ# zj*b@C8(ec9n0UycMY1l&{O;E1=;(FYVTS$EG?CoPoF(dR7NIOb8!E9<*I zzVB*S`g<=eYJT}MKku_$y!xEZv_5A%?=3&`(%+vh%kf^n%rpHnt>ND1&*zNm{S$Lp zu_spGV+yhk)p~eeZP4ny4Q4~>GEFvgwE+#w4l<3-@XXt5Hol#id3nt+ZudGR{nIb6 z`df~9__HqhWnCO>>GqeSzja7(B+ID2pZEFBS%0t9pJkeNmSsLZXWp5we*Sc(*BO@O zXP(CS`^Yv9v6u{wpb?UdXR8})KlU6zK2*3HMUVxHPPs0{vPuLtfGE&2^WcIZe7|#f zbCZ=pvNi4jJrOxl?Mbq~OpCYAA`pWr0QJL@omED)plWc5UNYOUoTtpG_FDas?KfIy5VzltS=nO!^t zX*|K!!igJ20Siq@mzyg@DFBZptQGl@uu}X-4i(#0{R;vP-X{p7GTL>@bk%c{FA(9$ zwBUuO?E*IvAe#2pB)CMN1hM@mYkuh0Vbd?iOv!zul@;C}o0y$}+kSj7xo4C;1JUyDRkaQi`M$;IS4XS+z$SZCWwLRNy!cN`e#3F}LOBCD7` z*J6~)OjXLcu~8CWr-uiM=bw8v(yL%ug~2NJb(3mc)Jr4SAaN^EK8=0}6+lb)De5BV zE-2;+b~D@xo_fo1<7QPbNAVIldlYHXw6bQnZy}(dE9brO9gavaF{Bv;k&$Z5nC zPNDN?6Rw(Z0G6fVIN)Q`i5AAgc8G!uo;n@64~`D!biQSESoX+fq*f*e3YZVLOn4vv_P z&f~?|GyTN~;GIOXqLG^@3YH@eWt2gj-zI;7n3*}1_Aqy98_3tqfMO?n{cBDmNdf+*Mg{3!%;<%%K0KN5aL%VUg2Zk0>{Jv zK#ZJSa$^aWg;gZ-Qshgn7>6}`t%~97H!9_XpoS#F3=$vAH2@)w zM7Z2r0x^O&?w=gPatjDzZX(|0I-&)+Cgzai?I`P?^EJjHA~pg>asfF{+gNiI5!BL_ z^HI>+jh0|cthY3lYm8vy3Y>Cs$!YCMFi|cvb*H%2$N{5BkXFp(3aY2i`VjRT8bUk` zj*$fveY=(kymZ59B?ropI>s4#T+{=#$~rPV%{K8KxGXc~8SUr0sStQ}Zjtq<)UhLwD(EHGod*x~M>~pBmwu5pAD{*Y?%&q} z46&GVa&iU`Bar-7K#U+vAQ}w!+SUEdCqD6s;tOB+0!7*D4>#^U>AoBL|1u{^V1RW2 zlzhAMtA-V%J#wzBjq#2$m%wal9@dYV@#6dGALpx_f$adCtXIopC>`THr}+krk?7y> z6Tomwl+IlJnW_GHZ#@0{rJq-wSAiIt$6qzB_xc$p<8vE%SIaPtF5_f5K4+d0VPBl| z%W|Y2t+(kjoqj&2AGdquWW9`IeB-3cy6B&6e`C0DY1Bf{-5OZpHxUZqUq1O{~%~w7w-ZOcgvXaq7s1SFw+Jh!anqyvw!%mvb`(etYZV-`Lz-WXJYv6>Fy*I!KVm%PlZtx|VN>kbZ9jqfR z99kfe_-_H!P;@}RKwu!On~=Yp47OPttnIEbTA-1`#}j4=Ep?knG^j|}<-@C+E7v8# z1(n)LkQ@R?P|>gX#Sa1xiaJ0O^DPN%z$iH#AUXt7%ugaF#Fy|Z#gru24?`q1vq5a5 zpVeo9GEUmF6V4(xtb!5(G@df5uvg;3HCB-_m5~a9Nl?gTXxS2%jtvPIPwOo=Kp!|o zY!eAc#W_+@ul>0QV6jBsETc46E(QU-!67)I&W_NYCjqKZ@(UM^eHowQ~|}<4ye?62n8M1~_#e zpxy0n^@j|gujnlGX@}@kPl*;{7$3eX^_!fU1elmBo_X#WzC-tlZ+`O|#g)sK$p>*5 z=70qMI-pxC1nJpxXF>;#7PY1)rT_&1Eg_cWlGsEl*@0hB!U2==7)DsfQ(gC+E~3H&MV9g zU%3KT$qnW)#IxfVeJjCLhHjV+eiGD*e7*AWc=7eGj~COk+W~O&^|#SJ-Mj;U2Xo2| z!99S0Wk47e=JG;}H^olSzck#7BIdKfU9ipivb;1$->@z*zy#H_479wuQ;d!wzO#KF zfTRZn%jf8Om0zRKd1(car#{BY4x(<8fMi;N0$?J4=erzncj#BSwnjts};m++O-Srv+mb;St~vAQnh* z{`>5>lv8UPeK&$(j(O){h$}MQor~5*4i4*Wna;EBE|t!z5F3spI6_(gk>w`ogu^J5 zX9GGaekT~P1vpxA5~)1cEm}#~3Hn+;$Gn_vmg~H6E%2(&BTcQ(&uRJWE_0!YG1?v= zZV9xnkPhxZ+CY6X!A(ay*hvD$t~m?yD}Z5li_?H=5~b>rdhPXVlv#?T%FX8-SY1Ix zlC`N1Q}lyF>WhL)koQ(!R}&yv8~v)t;ZbzND0+4Woj-zFvnV8{+5q{83DKT+0JIe0 z6aU8y98N(1oDRxSKaN1%+}sLd zr`%S|(0L2f5Cor7M%eMSsKnWmr}{!571HnUC@OWje#E&$Ty;@C>ueyfyUBM-{<}J`{S9ni=h5GbmY>gA zf1fif%dghW@F(iBGEcP7T48?Yvq7_SvQab4`elP>!+O0z9MyMq?^K4<~;q0BEV1OaSy@1g@@^6LLx+}>PZqY1Ij=KmUs zG$*Gf*r1lyTz1(ktIW6Ld})dkeW!%Px#CB1kVwe1vPo5WXXq@&TWXC!+<7146)%o%qHxXgb!hw$ChaDbXty>5JkjPwj3IQ z5<~s!bZEg44V!)l++|K$7vVHmgIJW%mpD_mMITmk-0&;+e`gm?0Dzdg5ctX6;yVd! zNF*uRK<2@D;pwVth*yck;0$5YFQFyj*4^D1#DN?Wa+j$1(@mGei$IJUD)rY$NT}d4 zl!Nl_a@PnRn7M$H3WnuS5eNw}6*i#aA=WWWR0H0)@ppwXf8Sjec*U~RuOZP!P`qOo z+ybt%yEM9B(U~)+VnZuO)XkgI@y@hm2vI+re+e$DA`t-rMQHnkwHUuF=*TnnDRbU&udHHI9 zYZ8y@gHQ}h@enz1{1ya(rH&8s68KSnhXkYJLlG^_Vy~cpbA3%cHssCec_;&AY@P=! zalJkQQB)7N$+1R2s3tgvPC$5L@#rw8;V_s-JvPX10w893cBXjG``!zXajba#)mMs_ zUw)asW(>jYp%P!gi!S>9^vH04fr|CX<)V4>dblJQKD0kT+OZRWH~nEcyOoW#`hVsb zpW`a7P?YQll!#ZNB+( zkF6o{Q3puU+M<3II6JUL)Z0s3#9-w5(Cqyh`eN#i+=D9xg|v(ex5}nCbVro_Q2=OQN0e zB6uN3mvLI)ZfX=bVvMBzC4mP45y!dXNU&B87`f-PnzRb2q$rc&f-zbtbA6K-S2swE zRr)~!QfoCL z^+>tq2sFrlBB09xqKa~>Tm)3>Wc;dJ_l-BM^KNn|siy`&nRi}Z zR=nsz05KzHhJuSwuAI%PY)RF12-S~Fur9YUiwCZpX1p*jIm6p^wWmjOuRz|!1x zm{+H+ev9P(xrI(4>vN(ezNikV-k#3*wh2OOS*+y7Sz*lbT>|{_{dNG0QZDb=!fXL=|_s9i^~3?U-x$Q_|-bCj9YoG#CmL) z3GHv^yPTHk%5)_;XLvv@mPz;fTyoA~!I5?3-$(zrp1a@CzyWK{*wn%6a~F|J|KhF)zs<5U5d ztOwo^ZkjA3>yhd5Im`8`(_d#g^Y>~Rong|QmT!E+yw}e#>*m$?nI_9OFI|?Gd1pSR z)n$6qW!h?9Sw^NaPUdO)C+f0dPqffVVV0i_klr?IHct9^&Bn~e%8s-_(=X$julX2O zz1J__Im^K-(tEAOwY-d*{+5$v36|vG(J$*~+Bsk)8H9ym4 zK7u9=HtV6kF5_k#^Qqn&$GHBguYshLee#7f?}dTJ_8l|y#{8x~KD zo#%=SwXu0o6hjb4&IgGkR9;aUi6~EpeLve-VnePJ6>~PC3rFHlz(x_K9S9BAMF}iV4DUne zNYvfM-*!;xuObQ|f&kHEKiNOB(?9#{Q=EdI&_4{j&gSX~u@F`kG^e7Qgx z=83PI7J?^&E7p01HlM`Wi6_r;RA{o*Q*=+`trT zQH%C=dQKZE+3ZHNvSPS5Mm#nRS^s0-puw@GhbXhwYk5xp67 z!@Tg)3(*eam|MPf^-7Ehst^YSDt8X0(g5=UY%`>phr>WFmP356{C=odwYjyOQ}t7T zF>r!G>`6qsp3{`t%wm)*J<vR;=5jCyw+nW?F#)cO4?m?{h;%M^+qlq$K5SN#pE7xT@o} zF~1CwbI#p@V@X0v0LK1Q-%lvVre7V)i*WULb>4?1FlK@r0fI!1F?TkCEsEy2?sWIG z2KSlZqTCvSvw~}Kt+`P7PKxMkGheN@<@6)3O)%Hr zii(!0TVG#iaptU`Fl#S!x8xk8uK-2rS^ow9<^G!F`!O~)9evr;(?+^(-t`#eZ7~PX zX9YJ^I>F@t06+jqL_t)JX>I7>2touZz!&#M8d*PRg z-XZpgjK{ajX$?_PoUeWDYsHWL=#PdqPv7Ia;e01_lZ2H}Icb09XMU#m;UE6t??%>d znTKWiPI8qA#3UHwSoY`K4z3uRlzX4!M<@QvU*0oCin=Zq>ua=qd%`@NCIC-Dt>!+*c zQ~ErCzgF^lMAfn#RsFfAe*)c(1=M!<|u@V zFn@WU>CHdGvO}}Z#x>llF2nWvPU&+-XL{3GhG}$O{rN8DmG6-4<~5z+I@4!(rpfln z{JdIbb+tEczMtubU}ygTAK-pOln}e`xa4M0_T+p?7bh3cNajKn=!lCL3mVU!>Ui22 zj%bH!tDEUf6^majjn8hLJy|bZ8KV|46V$>UUiR{_R<$IRn@1vAegZ+tdH>h{;$IXW z|M1M~iSzLYfuOJTQYT`=(4QzBZVJhpj3ScFG-JDAJ zx=|Hea1KiVfH1H@a!$j-&pcc-e>SWVN=MmzsEF1biYLz!Epo(IzWN-T=UPpY$g`ZT zo-RO=I&_Szpa%hkybw-W!05qWF#>@TMHAFbaEv;bSKCRfSwQFx4Gdy_dl6#oIwxZ@ za7Soz8Ig!)6b<*FG?dfT2ZuN@W`nIbkc5kR7$o975mwKEB9HA0YqJ)Si$#Qx~l!nqzh=Dtv7(*CHWC@CR z0w!mX8)-RXEMFpNi?|!q@s=Vqtc!vz4GwGf zJHxx9vjh<72FhezkIg@H(~!pg6ts~5vy4zc8H7g%gG{7YH1RiW+$B-2edV9GLMMEo0q49SXJp}9|}`d7%ccD zAt{%Y1hoC47@V9)p3+Oq$@L@0gJVW5oEDnZK91g(zCJ`Z08J#)6faw4Tx#(p#vN9YI=gx}ZSN*efEYPp;G)nP zi$K!}H~h5oox7V@1bVHQnYhWW7 zrONh|Q$qbXqv)^E0?;;GE%$g2k5B|)x>2?)w}T*wpsmE#p?#vV4cB;2#1V%VL~K~_qS~KYKjF3 zjtE@Jd8J5_Z7F!?_rNtzz}2zr9J+aP0x$+WWwg0KOn-kbV+b(~>Lr(sYmQ2eRoXr~ zyBr)X?d>JUl_sj?f@x=saD7n8u)3ivSD;%UeSCZ-+MJ-@E4j&xH!YeCku5D!%;XFUR-d10VQ6@kf93N5$E*Z|^%vCwOR^ zAdKHBe|tbo4Ulns=g*h@!$17P;#Ypt6P*+*Vx~T#J-(9~YUh>i$2| zxi6B#*JnBAblxWtBNi`-l35DE2>6C$%vtvcbd4Xm`;`g$luOWgCiLgVuFQlbgnQ%qY7_4q z-e-9F>2F-~H^2SiJ{#BYYMS)JJL+Y)&iLkIU2RCq^k=x&bl%q*caKvi%lDe$=C?n6 zHN1MCd8eO?MK!(my1bT8Pw20e`d+y#+ZWG{&@X>>ruQ~jcCydeVcC#)?WOTPJI1(~ zhn;Tc=YTLQo#kg4UJWx|4krDw%=9;od79SyOv7#XnWh@oFRA5M^XDZX6TMRLz%{u$SFI`hhQFsz$Xnje8s>4Qk>=m$#r90I>W*{7OIj9rVJ^)AFwHbm;*_F#92Ed=(rEu zrgk3}!ZJ825ep(Ad!EMH=cue3l285WuNJ@ed%w3Y@Zj;F1>X5B;KozV4b7*@y`U4U zxbgbGDPI2Te~m>6^jrY8xVcw(tNH}G0YQ2YB@isx0BDedMPkX*DT#@@o*c5d)-p-3 z*F(VFf!KGm?1{b`ZHsbqC4k}PTP_owto$Kbh$(GP6`KJ(|??H z4=G0-1tC0H_T*S`00}WSL2&}yj44|{n?{sj>LjE*+147AIzQC=um)ik1Tp$K1UYO6 z^;EdQ4}it@XEil%fdYx_T^b~~i)a%G!Ps~LE`(e>#Gqh@gogy$mURLs2u>E-pwalW zz4~la1ngohFc6$Kw4Iy@>Xh-+T%vSv5L4g~OLDIW7^sX>C()}55hsY7Aj$wQH1*mC z@zV~_U|ZQo!F|HJhaL>-B!`9KO^Q$iphVqWXKhQt9Ep23&yF`ir!cck-4F2-An{~h zcI{obgcdkKpO{=G`5^#-lW<084qNaKg|D=~fL-Vk0t9mdEAcCFBr#WlU6`N-jPT@E zLQN&N{yYgUEt()!g9wtAjJhH01MrY*hOcx3{iuRp`^UN{l5`y+L?GhK+0&s@V;8gM znzCM6LOCkt-hF3@{F87|gyK;8P!P_sVVSm8-S^n6x1Fpdw}o{7vepo|tX= z)q1Je_vFbo@@iv#07xL(U|r-8kV{2wj*X3_;_{oXAbx@6Awa$3)Y&mu0x^jH(En=3 z*P2s-gFWZlH#YeZUDt@wD<~dup*zf<6El=ANQX!J$eV zxnUF=k^ohttq)GL7W8T~L6E3pX>xKJ&Y-yfKo$46gN_zC7gWHi-V=49$X(~$cdSig z{YMTWE$4J}XpK$rVu%ptYA>8dJ#Y#gJC0r<@}8Stgxh8&h%~{RP{z%;j5)wHxVosL z=@nyh?kbw2hs3p@q(GkKy7CAdI)?5x7(|#=dvX3dS9V5>v-R8|~xzDu|&TPdo^`&$)$CNvTY65b@g7{W2{b1$*F=E zl|UPoo#ZaGO;*umW1F;5=FlnDAqZHv$8yjK?#msiIFEBih0Y!lsL*?EZi&9B1609M z7hIAOrYzO0=OxFH{V3&fUei^QQx}lJir}Grt$Te8W6MlVfpW zVkYLHW5spWvE&%>-ZiVvG0KnPVsF_*n+e1_7X`jIn7fX5zkiOe_|Cx*)XbO@h-o-6 zSe$+FR}c;CC5N}q{ra!}dhrK;@CWe?vy`r`uHv_T>$i$u`lVlrzWKY9Y(HGSe7X3| z-~7$uFaF{$V&0gKK>lC;4~bd&s$30+keCSx$=Dh3OK0mU|zr{zleo$5vE?ab`YmMgN$v z6%beO29ej-5vf~*+i#Y=#s5LB3;d)nEm6AAl9IbcKYu>!GA#X#@37Y2tG^8Mn)ziO zUj1SS$YdIB_tGVeoaqeH`K)t;k@r4(Z=U*l%^$bnGQX_1SIf`$D`P!OXZ;N~U-Q(N zCiBtH`*eA48q3ysHI2^vGM|i_^nb?jT1{(w(^dU>d~z-;_T)-@v?LqAI%Fdl>V3Lw z(DXB|e%bJOHC+D;%lwS*&v=H}FTgQy!b$?y6jJ#$&%q!zte%@EFmY?yAllR>2#Wzk13&@8dwtBgCoflzYaaM3p=_w`!$eQEe$RM{A4oW1#g65n}H{3ODY{@V&prC!MDhT3^}kYQcNn z<@0{uz2GR`YFLq|9c5C1vRdVBg#Oe|{Z#Qka5~@#=YczDf$z5#kSKA(>#1zIt+nam z8-Mjba_ati5TU{8!i1G&zpJ|=ShGD<_JmMEO;&T~uLO{0qGc&p|AYkkZHT!YHdJm} z-P{VWXnDkqw5Oz=?3SWT3ElEU5+DSk-?hNgNLkd~=(n;t zcVn$6$N;)x&YwFUCzNA1#==bUEap#Lp4ClJT1Zj6N6rVtpdf5gj7JuK2^DouXrfsK zuZq5?o1h01r|JlB&nt-qbWIY>-eop!&WCTFmE!@ox5T zk)W1fw5{y3k{B#GWYjfb{{AG~IA*j^oFAc_1LD<@>4| zqSO=O7*Hv!7Qf_-P)rv+l|OOU()L@ooYhjw$_H;pWk5 zSqUTndIa$f7Z&d{IXM}iO#|leL|8`7o`FLK>tD>7xp@={bJA`9SdUWl z2x8y&qRk!8iWv%2I(LG@1x_V_82d%w#C8|7Z9>e%IYhLWdrg2t0RU6aCP0GQtY`KQ z*aG=LXc^IcfNLG)2wb>s253TTVN!V&RN(*wGi2v0qZst19>j~IsbxVM|T+JIev+S}pg9;)1TBXU}5hzDtoq0V2$aGk-Lz-ORj{ zb5Njo3UEb{wKdi$!Om{JJt{?YT`SvGaoE!Oo7_=?TY}D7T2hhs>EXWO%(>I#r+%J0 z5iTIFGWizPK*#0i=s05!1<{N*=Rj~7(#|D#&bP-o!%wi>0|9|)xzYpxwgJohIhGV> zmE$TnGZ+hUO{sIJrRVwL%nP3c3{{c)+vooL&;Pvm(tx#Lgeg0hM;5sVvNZuM~r^{qP?k8UKL6F zga^xB}2G*SZ&PuNWzSNv& z>s|W#y)R25&c5{O>~nn>zj^aD=Hmj~Ftc30OjaBG1SXPpOK`&+YGMR?#afVY4byqm zuX;_8!)i)D`ZGL##@AU+I`56|Fa3?5d3kS`*Nn?;xXjOMruXXm=x6zv)@wDMadgJ< zKEw6XWxIHv?UVWGSIsx;X#I?v@w0yW<7Yjx%xd}i>8fcHyY4?X`ob)#z>rfqR#>w|H zJO@|CHE)5D%q#0sO_Onmw)c*iZ^rX#T7TJI`sL5~mXme!KGWvCN5>y&Z*T8nW7l`? z?D?l?o+ArvaQ9l?n&`;Q4Bf1=I&n=uK7f3bt6`>f!=PU+RoulN!sZv}%lsqJ!=Sym z-{xP&c?<6de5~x( z#RgO*x)Lp#Pn8u{Q6-5E30=8n+^`41&BoX*MgyB`H?`_w5Qy<4RL&Yt&jlhB%WxhG z#K_4YC?kj<$A!SBpn&ypgQ6Icgi#%YzJQEitEZq6JQBO=B+v@c3?^Lf!ltk8nckir z%C8G>W_A{Zix7wRsjJ*9in6G9n0*`@{G)KQU`YvLO??$^UL~w%IX#_22Z6dqN^TN# zoAjVebwD$x<(lN?ULe5@VFM?>0y35nZ2r8TN{W4!AQYsr?bJOWH;hVbCGrGaJdO7n zRv@5;UHT-11Ee9X@8)7>8>&y{HXI}pz;fhi@#XB{VRX~us zKFZ;s2!!KAqV5jFo5Z|*<;GKhMq*KYJ&LuIx^c?W^lnaYw( z63KqMXwIl#=pCY6&EIf2VHzMFX#6YPqv zQO8?b05S7`7E47B0LWnqx^ZnZ#|d|7GvqgEu{{ z`oO=V+h`<>ZZo5iMmI~c4O#*PY%sQA*LI3sl1(b`$7U;Ly+D#8RAO!+yHHs!Wr!0m zaTTdl2#^BIQVFE8sZ=UXZNeXrc(Z|JA#8|k@J-jzWuzI6G^5Lm?%MC?^mE?#dEfa- zG9k=J^4l}due(p5bGrLIaIc)dIHEX)B7k^M2&TsN@SbBP_#h&OV4t zvz0Hh+>_N0`aa{sjW=B%BiJpAqq!dWQ=hpzx3IY;m@zb_my}hXbEDaw@JXRddFumf zGmR*v`Gy;^oh9Rre(ms8@6qM9A4VZEz8G_KcE&UYKSqViGA`Msb24RrCVl3T^m}JJ zs3T3u*kjxrD@Ke{xzQ&yRt`QDLl47Pt%!;Y89x|cogrg2nr$)Y84ehfoN>jlvp4yU z+vZN!J7OD;gRgDpVzjUl!@KOg-Wx-LzVt-4)g6y)9?y+R`Vj+xv4(t|X`=5kqKqxw z>6bCW=$mC|h#}9JEb7Y;!)WFFo85WOFU&ioz7#AV`_uV)y;ayDRoqx>G}LpR)TO|Fx_q0aHE71d#4 z9C{+Fa`145m^|*dgQ0$Gg}m~L7~=B#a77F)u3g_71K1dUvLZ2k&HHK<52K#5(4I_x zeLVcYoq?op)b_8uGmQMPKrODj^<9hG-}YUR?Pcj9e|XO7k)QghpIUtALmx`NdUTo5 zf6B@jb8stRtf=|uM?aeWGxTe#sNV68cP>8s;SVpq`m4XXj33L4{tyD%YqYwpNamBx8}-@45U|B5#tAQunB2Ko!S$LYSSgnWTy-?Nt01mKzQB| zP3niJe>?`PsSJOnUXyEZ$D8{EKO;GKbC~c|v4%S&?cMKw_maytu$92;O#&uzCOwq# zb`Rh8@x{HL{f)&vcl=f+zB#NjZw`f>B4T3fa9TzLd*n@Oy;&41%juoh;On$iA(WEL z=4-`(YgZ|!z5-iSQA^sRI22tbTh6R7;iEj*`s7RH?tH1dJO+V_qlh!Wp3bD6!GJSC z^EJ}M-%TC9_FfhRptcv~MW+b5A*0HW8e9+s#pK2)La}88vV`9G9#)IkV&N@+Dqor( z&G|DFw|fuYpRck{#t3oo;-;IfEglr7F(y$|EuCiIv4tQbOl0KzkSFrRoALt2nBq&X zA=AYDL~ivtmF*fkO~(P*-F5d6evwo3pLO84}`Fi}=cZG-q=>mMg6pL@58{g1@ilR*zUk zVRch&*-2ZI2N|IEWt>6C>KT29qG+Xnl?=AdXd=V$@QOQtFa_3SH@pcn%U~ka|AK{vL-iMiF&} z&F-uKxish1T$p9-7o}X3S_VC5k8SuD0rwAts8cLsrSi&0`ct zE6S92SC$#M{?}@?Q!(f~mz6QL3%Qm%Vh{h!D)YPSp|EQU+Xwk>78~cpxC(u&k|Atz zzwOpHMHMogJf@t0qm-M`xkfK{lMbmpS&Y0lup)m8i%Gb!WcI$jbZ2V7)mZq*y>p;a-6;6 zy|a?dmCE}wR#DiG#!#f6qO(%UJfZr3%2z9BA`gccTZ!eCq!U@8^>o@vyIP511(te0 z;oXUm#;O>G7KWoT#6<3=GPaE|PhBF^r0WX|0E{uN5SQ+JD@GVwZx~UAp?e%AYz$;9 z2Hz9oz;)L#0$*ElV<54r?ZWhD@5r&No;jWsd<-C7a($MuBO^n-o8+A08A7TY+Oy8e zi2}BAtQ`F?@(iBdVTuSpN)*4NL{l+ zEL%Ul?|Ax6uwqZ&vNFyVFXPoqc^CBwtD`M8%$ZBNSxiZcq{|s&Q{tx^KF)!!K z=|9GgoUNI$^~IBS-+kA;sqX{%-MugLzmg}TiqWWy0Te26u60gnZK(011VvCY{b(Z9^c9-GU09wUH zicMUCen8uq=+{JcBo%&!wEUn45#EdxjV1RcrC~iLAb~ZS^i?d-N|^B(2(!Ni_zaZt zn7JO(4gP=k@BZDzw}1P$*N?>vWgFZ|;B_Mb?-Hea(}?Nd#LxWBM;D*@kN>j1^7R*B zD+6pvu*AC*hHQ&qcwv~?6T?ES46(I=Atvvl_lHvG%nt?yh7=QN?+;^&gM1yx$iNUC zq?9xU9$)w^^S&fsZFgrP?HmPP{23AI?3#RS^+n9~o5!Odc%dom`1qpiJO*Qp6(-(e zuRFh17(_wc?MZnZ7HN{5bF50qi8&~;7-!6U@{^y=c_ELKG2!~_$3do)YQ_c1fhGBj z3Cd$IqUfE~L&(YN}7^0hl(;TdF1_H8RM_Tf)4q*O3M zQ1&VG4(MdVwygu-U1&-{$^f zct7e4jwo&32l+Z1$6>3=Xp5BXQhRe~FGJCb*=EN;V+DjWK~OxKEpg6yvEs}vSI%L& z|8Nd8z5jvg2UdG{H*ED_m_ny)wGAnKiy_8}uxFpoJDK?8j36^ z)BS!pn}Y$@RzBrmh&UKU`ns&Pc`nAP&)#`BtCa4KaqOwId*0U^G2txa)mtGnURcTEfMVl|El7+g#>6tp zCVwk|)X_>E*FrN?U3ciJ#hbFiW4s5>){4O*eTUJMF~`kC`nq?;t!x)Ymd0f(*KEle z9nP#N2hgTJ)i%i(OpOv5K?L#bbHmm}F&15vx=?`C?dcdqox!tPJci6CtmHYG z19Gzp(ZRgN5C#=}fx&5ZFiyd2`}_rP`}w-xQnIZy|3gPr$=Ay(hN zAoNz6{O<35ItHB_u3OuIF0Vep;GvGj8SjkMl|CZWhX7-3>V0AQ^!|gn`76H}#!T}~ zhO2!U!@U2t6P?a@$ajG6@w)XXw+E&UdvpEzmAOLP>LlZsm0z{`EB(W`bkmL3FD}nJ z@nq!wz~TFg*7j6JJ-;bdL>cQ&N9GKyw|~{2S$x%d{+EpBJ+;nj{DZTdkgJidIjy0-E)0k3IGFtTj21dp<8)`?x6?7&*wHZf&70p&9%!X^c%I&Qc4ss4c3Bdv ze9+B}DV=}wg(uu`KG$`amsNP4E)0g}SH=+YN8(?5=+L2knHb(m;qz6yMxv<-Zq4X{ zlV7!(d|$`wW|0Cb_HaTq%!Ocnnt3l(Evzo>ifp3Z^g7dGI`W@TMv9;Cq1(S#lw~e#t;SuD^D0=7zUgLVv_9Ls`Cmm!MCl3l9*Y2T^r2+Ytqdy;e3*dGMV?q zF*C4IWK2?g<#YytCHoXm_>^o)k#brML+LR|8&9@WWOgPW6FX&f?g3??9uxL_fyeui zeC_Hl44^Jl4>S8sFGj~p88-=ukQI;5Ztd4Sv zjJ7*)AYcBoGKr(;-E;4QRTexM*aq)mh@lkwYEQl-KPxlaVOxL_L%pOKGoN8hb& zJXwumg#}l)g~cFhwVShp^b6;CFp$-%u=G#;9j$(0wFU!KUg^A(IjF%iTW|_95}GAWT*WZiO9Uh zkBmFU2U}omansk%%ULd0WIVk*h7a$L^I@*e*vlZvQ1Yoyf3A!-+E8D%vMmBC!#f!= z-df$m*qd`Rs;=r{3+p8@`Z3_2PFpzJ#e2qZ=^asa^Uvq9x=@)O%UE&r;jHe$Mh*`!w45;4 zxM+u1*7e#>hda*$U;H^Z(pn!CmrutNT-HUn^J#eFf^*GJym<+G@Z`&XUgpLH?>ct= z(!e~z?dI@#T8HQ99d6pQ=Fba!)-s(kk~e|2Ac4Da2#I4D=te0({zyZ|;n8(~2fx$t zYW|E18kEm9uJk#)8>fBvoqy+ruY5Y*Wr5fH!RF;?gNk%?&7Ziz^q6Z!nGXr)to`H@^3_?Ri3z6Z~%7tJ7xLS zFZ!azum0+<=9K>Hcf#7jwi0;VNWi4Wq}Iw96S{U*#yoQ56N|%l{M*Gt*|J2Lp#YmC zGq6zlhXUfp5JTbkBITWlYge%{hRE1qzvBSSaW2BXDDcj$P+mshC!QELC!NeSt_(f(1v2HO zYW?S3ipu|0_q#+ROp5fSdFM+`AmoG|2CRh7!5oG*qZ zeJO@Va%UuT-q4LVUSBKF@TAP^N8TInJnI63Q(aRZqph=0Y-O}E#f@Q)$AD|45<`%) zove7V62WS=E2C)NcliFrXLCN#iJZa2fLW_QV$`X#jgo&Iq8r8C$}!g(8*fsKS_x#k zk#miVN%}7%+OE83yK{EQp2$OAv+Yd(WkgPL+JsR!EA(QdQhs$TgPC_N??V|z^~u!9 z90fgNiT9r&hjFXU7)cq_2~Vr{YR(fwihNu%-e<%x5*hD&iC1ph`%cCXVv7s|s>6wzk4Qq%9g8VYzNA= zv+8$gTTMp(-b=201WV-*%SFq3aIi zEUtqwa4u>~sJ^VOC7bYq=P?vJ=Zc~#%>n6FLNmJW%1WEvR;E-NX595V7Gdefwe{8D zs$zK2Yv&qnEmxKHECq3gp+T6+~W3LIQ zK2YZ;Spk;!^XZJ$Ok7|3j{j-#i~NN8~rA`NsXveEQRi zAN;`|T>SHop1U()HsxcibYQdcZ5m?yPJ5T;#u!GMKk+C2M7FejdXv?T@pPklhn9(n z>(AUQH~>5S4boK^t1pQ0wca_(i2kso#%104z{8kCyZL5P*n@|$*l z#@PQ5u6N`yLF-HyTDsU{0#-e9QjgXRA?dNDuf?kxE3^k3Ek45Bn1Bp8J+bqI50`@v zo;RiVpyBX<2i~}y7o5gVaG57O`JkPb1Du}8Z@T6Iui*(U%PubtK3sSlIR4<=91c(Z zUGAoX58inuxGZDyZ+>vkiR+4-i_AOk7F-Lgg)wi8IgYXt9K41%jIg`W8^-5a*q!fy z4j$cs(02Zv-i_P7JR2X5FxO==O6pwl0pok-cwL6}o45SAhBsaNBX!iV<%6cv!Q`(F z4eK&Ao_NFHcsk$qrSH!V>AUlTd0oCdUw!TMhrYzb!1D1Pvk+I| zZcI>p*_qHPql(lY)Ll%)&X05$AJ3N;&SaumH(mvo@eahV;7}ap+4--C8#hU3xLx(d z=NEtR7Z-p0kN@#AQn!Gu1YVC4pcpa6SmiQzZd^J3&?gp0?)|OBBM<(^yebaMwN%&y zId5VV5MQS19SY0@zl^9!q}2PuZA*^BS}7nVCB7UR3`}Y%F5}DcICPdmVuEXx0p%MH zUmPhjj4KrB0|$IXy(|h~-&o`pnkb9GGif*J$A^KUmb7P*%rL+(LCH0#v%1IG3Y@Q} zzP>qR^k^wK<51PQYC4lcicu|JkMYIXCX6_g1z%<L6M-8+XAsSMy|b4 zILWRQ@YKz~b{^#}{&S&KcVnYTtt}4qr9Lo91tpC_Wo5wFRuIEecu=q?AdELx=ZueQ zqfl7^gpX|?58RiVO%6X8S)Gm{D~IN~;U)Mc$qXS5SGICRTZ~mpr>acO?YQmMTdLiT zhU^?N$~>X8@+^dT!f7-IB| z=dy+8@%(Nv9#Hsf6CzW6(rO`nfYfbAx*}JOx^~$%izE3hdMIZdIA>*iJ=Y&&h)KI2 z%$5OH8k03ck+yL|&-o_oF}~=hWX%v$N=M$Y=aToMF*;gZa55_rtT^)vbyZ#3~JEw0Q5G4Z|R-Z;|&iQA`;>DCf3G*#dY)mfADk>L16mYRt+43O8eH ze!a^W=sYH?Bp4_!-ji$NBY)dF9BRzis+?mxTF&%HzuX^#o;)7OHl2I!J)G4{k5#_L z3C5!R!MQ5s^v^- z^8VUFNtw4zO#h-_nYUPVXKYkY{kq1w)Q8gkQf@r*K6@v|3N+g%O@c~nRUSqXt5zJ+ zdu7hcVLW9Za{f={9T_tOdG{PfZUu^!HjFNe;f$&bh5DTBi)DyOKXTZt_eH%KdfhT* zyQeKZSH-Y(Rrh3^etDvsC_E5>^(e&+-bNJP&r7?J>+(UK@F;*@a z4{Q@-h_U*`El0Z?RvbSsLwGQLD@(?+wAZB!QQ9swdyZi#=g?UFXsn`j7+;PVpbvx28|MD-_yP*+imUh`GFVgp*i|FrqPgLG4j=05S$Cq^fdpZAVDYfMPr zQD&}Ck}i8=4~!I$2TG-0mO zgwZuEJ|5{N4vaeuXI?WZY?o2Zz4H;==uO z9?C8cuG8ct+G)29 zl1^CN8M@;&V{i>I?;zcP^ZfAWyqcyvk+}4^Yg+z1tkcM^oieu}1RmGr<#(OXwCuse zn^*G)Xo({o9M29njr{QH`efEo4lWqHt|J~`Xu5m}F3Sv;lYVIi&9Be)qyx$M;H|ga zdV5Bo^Gv=ugA;{BsfwvzwHhsx7O)AQZAT7$lp&l|K%}GL6xt#W{_z|vZW1teV<@cL z2A?JE9BUO`<~Mt+?<-nDeEQVr8Pt z5aZbsrJllIw!$bxIih%389{-0JjwtCv?lra;%Ze2MTH^`Uzxd@z%y`!j|mYaia~^e zM)~#_Xyi$0QWgpWrOp{PzF1QRo{a)Xf$}xn`3;OQ&XO4Cr(98A%jNsH<@$Lj8e^Dn zW%Dq?5aBRdWq3@UHQq%rsGCU=p%7=5HjE^ZqcRdIV%n*SQlg_ zczLenzC7}x{HdqRDQRYjM!Y)UGUdn1q}JDKs}v~f<6zNTbsWV*CRT`$%d=T|Bi}N{ zL~(X?IYr5;7CG>so&)9(Ra^JQ73L0iWxVy}-dRPId@FSrV{CI2 z_P$$f#PF~u1~Xe_yaW0$zxrlYFx;FokM_mzMcJ`x=kQ^-^WF@ljJnLD#d|1|EUlUANSfEm}UA~fC^8}+p|mfQZhC+}+C5M{*o z&epgWpMj_Nrp!;qIO<)Nx4LRiWpxWv9TuGNMc=4?9$%l@pKH{O*Du9*?-1UTCu8W( ztz=eb)frxy;@XyY@L>983{}ofQhe{kFn$>aZPm1jX&5c_3oFYQ^2+#~I-j4qJ(qS4 z*TqYz<5-2pAeHSv^oJk%;U8N3 z?H~W!X@fLM{kpD~`ak#l-*1ttrAfzc+Fbu|V_Ci9GttSPvA$dl__GqF$V%;8nHmHt zoU5_(k%?jV&b#jVy?`FdO8-aF$9_5C$D!o$ntC*zxJi|6-f{XM)_m#UmDLoEx3IFUcyb&D`Pq?t#R-G=Y*R_(||WGFAUr{-aHPk^=n~I*Bcye>&o)? zNQcLDxM}!YmsS3a+q8H#9o#uhF2z>Es`p&m`HJ>?r6JOlZodr$P=>+{E^$0%aCCoK_^Ju?$b!Xzk<=~)) z1IAdw2WxoKi3`IOh9};53053WP8!$vP5brPmUL|f%j<5v_0~5<@wnpA9NI?l7zG`Z zo8@>Ppda8#RjEvCKYo-DUsP)NDufgx0~a2mAe@}D82U*Lhpj~-V%_O z5D)?Z-rUbQ&pT)4e9O##CSUffz1F(+Z(S?3CG~-!;aj^nxYN%SW1go+uR#Cn1@M;K z(CbGLe;UJQdRuU6*(VQbjr&Sj`U>mHdQbe%)$_4Q{Y&+B9S`g}=*8H24NE>Gz_;3UF|+>WCotOX_y%xmVBd0jZVBH zuZfw|U-9MX{_Cho1pP|Ge&dPw1^6-veajXlhi?{CxX)0*yG!A~^aTHQIm~D-jmK($ zP4V+eB)@Y5F_-v0&qWhR6u2NRw=wjKWt7u?l}zb6xYIdRCe!TKAN7sd!De7$2#v(@ zP^N4}s-VgD#K3{_y3=13W5Gx}Lk;FBT0VZ60JI{Yhj3JV(!TXulB!JgF_(#;wdTxv z<=u~7Gj~)m&4c7R6(ujH=r-Asgx@bC_ctU4HRAy{&8o&Ow)_SR=QCWk_Aklb#MPGz z#8{UVUM;yopN;@`NvX~pQtCqoZi*;Gp00yY1q0bw9*@{*^8Lu?6z;;xHb-@~&gZIc z2;L4V5U`mw4{NQNGijj>)s9v5O7Ixi>D3sd{zd$oskG+Zlc`^@nl<=UY3R!QrC-~^ zM8W&JpoilS!aAof>{+_`g;LaQs!`nGkJu;cbd>cTp@jw&Yh36JFp6v`%S)Rjt16FN zsQa{=aMo}V=~qoEQeE038}Y!!5T0DT+U%tFJ{I5b+uIji?A#x|`H2n+@`qNoE+$_e z)j#JIO>oP2-&6PcQEc{LCHd^B<+z*^>4*JR9&dhI!?UMmwpj*>>-?khejUtx3ZzY= zj`(57mkvV<#599q6_Mw-r`8NM?+8!p+=V~z+Ezjnt7beBs@+q5mrx%LS5R!tyT-6z zx258fs_c)98$(+^nsJ0BW9YNI7**Xmp}(&2S#`gZ)nCj!gitakRjkL9r5DGIF7w-t z@6bC`|7j7`5os3bZB@K|@4E9v=q90a!;t;3$H<|Bb!ml>a1ikk^PDY$+hdFAhQY>V z_FS&0haaFa(;$+dm>ZayhnvD(^5)iguaoC=C9m@az;Mr8&bkrBW`@brVY6SXRQau6 zv$o5Ii%3OlTT9Qw3_GQFJF(sykxvGR89LSOM>9$lJcrL+tggA~tk6O&Tzk$9p1Z@x zaO{?ITDGfR!bxKWPvMo`llH^vq^j-2Svf4l=`J^8)KxV&>-qlZZ+bJAJM5wTmG+8A z@P3%0HN8U$x**5sAN(GZZv{TOzh2Dv60(uSIbwmp1i0bJ(;luin-s8SxX;! zV|p%j{_=HFSua&6O#t1{4|2&xhToeE3)|5^q`jN`T8mpx-Nmkj@0y>qx3}#~qBTZ? zbufBfPtPXRfcQqr;hK}ci5prYzck;>u$aK>N=HNT+esZN9u zUasl(0O^iXGKcL^2a9tOO$xsrJH|_%w*7Z^dqa1>&vsihu&_r0oyS)1s!72{QP2L| z>{i$Y$*la0PdO#-QPr8rB{8gWJ9m{F5 z$u|_sYNMVNRBWmAJ}2(wPWwBv2zuF+u!kX`w%)|}DTxp{$MYX4VvjPI^}kgT+h`St zzw71^?Y11vr+lqkm?ONOA)NV%smQrO#3XdVsv24P)I{0+!T_S%J!uD&Ol^401LBII ze&}~|Eohls-LFae!I~Gy;9Mxr!*3t^Y%s|`O57?pG!G)AVO;uXxqlQ_6`$Wkh+Hvz zz5jjgNdK!;Fs}(~lsHt^1@%a!v&nHzr1J9XAe|>cUL7CT*kRF8MNs~!r$zIM@sqI2 z1D?~NIM!I?Zh>fjg3O+#$)7pUaY3z>sRn*RK%R&gCPSRJ zqb_24ti;7Gdfq^X-S?vg1Rkow6)%`ed1Gg;_P-B(E{ScwP@Y-N#f;ROa!}cNi=BVr zDI@ovPis~s=KR4?4;OC674`1CC0bebZSSq+U$1Yha-u%IPmYE9VW7-adqo#pyo1XN~BLlE+rG`NPw zt)&Lpb2nMC!cP-=)R;_oA+>rHg3nbhl-1Uo{Q2u!M!$-@P#C38YLM?#X;yySKgX7@ zv&LXwSzRuu>u5wpdP)+O8x}uSp5p(-sENk@EUNOwXK1Km!0xb06W^fRfyH$CIqztP zZzXjr!d`xkkU<*poPEWdK9)zv5NiHO;LY7~dg4nf``%fjbC*321!*XUOl#NpmlWqH zwk1XYn5;}o)W5A6^PJEZ7VwNsRnj)mSJ|q6L4$`stg=v~T7>v#w3p~~&4^x49q0R{ z5ewt&Z^?VYs-iy*k~8~x&33U^tY7b}5(2C^NPPN_b&#zhq0w)YgRDv<^vRq?C%0&1 z1K(Jh_>SBTY*8_*N;gs4gTz@AytY-;V{YGkTVuL^itYR|zyp*7=tKh8%aOBxX?8SSg60&} zgOr1!9>yXoNkx7}HUQ}jE5FrIo`wFooaqcR`k15i*mfiDoY1AX>aZoje1>41E`sOu z$7R*}Ed4dbIwo#_E#UI9l&t2>Xq#t~DD3q9wq1Kfx;a^FvI~`aDi7zkX?KPp)L3QT z*~VO!ws7*6sj;2FZP{)Xx3hcDW@l!JEOLy>4i=tb)}i+@u>0oMOaR{*C7;nAMbVNT zpjy>0lhed3t{Y9-Jes3b0ALJ7qs}C?;r!V9I4gLv`Vc2)&D^N1DWOB}5l&2kELmlE z+NNkOP_F9W>Thv@7mu{X&}0$1<;lxyuAtT#cq7Pte{a@(Yf=ePXmhnsS9O6ozWXagjUg&NPsuIA&55F}DnFmjC z7tKE3yhx$peBrEqGK0_M+9zPaB~dU;@$vN_k7@WsLh5_IULJYt!8*)-cfxlR@`aJ> z2cuvXsOQtl2@o#V)yMjhJgo1Bk~vkiLG{YCA&wgaT(*0OT2B}9}SJ2BdQ{$6(Aih`PH1~xZv{# z%(s(p`XaLTh2-P2js(d)XEPq#wDu%lPd?Ug(a~xOt=b8neK5g4oghM+-(%eOrZ$nW zc}V5_YMsaDd}=@j!}E|n&8<}LybW_wGPYYU!;nH&^m`|5m*#CUUxXm5&2m8F*_@8( zyXurOl-~Sk(3!j5{NENT*3~|SUCnO%^t4Fpitf?34A)drQ3lcm+@O$WnH?`loHO;y zxC_=>p85t_BnsK0l;73oYMAXg`nynw1R;JK&H<>ZlZ@Jur0*#DiCRcj&dgI)EYk2= zhYYHV*82u*8;&$9HvvxappfyeVrQgOAi?BcY{pk9QfH3nP^jRfllZ%BSr=Av$Y%Tx zKixemDAR~Vb>jUn#g0A0?vscM*SJ3F6&<#Lyfa7-1KB?smutP+5V>WwHu+gO3F5f1 z_AuNerSNw_XNvX9y4B0?q#ZeH@VNPUf8qK~Kx2)xDwvA|Bu=~)`lgKMTx2eXE{=_R7Q2F)zhcv{{zh|4ASNCvJ(PFag9R<P=Egb|;nOp>3vjPr z)u$7*9AxG94ZgIJ$ehJ%950Kac)Wmy?jiKMg?2jSbJzXZS4)$X7#+Lj_#hq=DtPOU z_j^yp!P7fG!F|>&r{^*O@QqfmtTR(ezl^x~&eqiTo!`J(EXbCC1=(UrJM_K6J{RYH zt?8V>ehq#izwBC$r-_(B43Y#c75K#?bURMv0}yC+u9L!OQ-*t9|zam`{s{^~m(U3#FF z9O~#!U-DO&v@u+VC8aQxqM91Sqz*V$0$z>q9qAL_~71 zIyZX^pn%otL4d*W$Uakb-wLq{Cw73w5XX$jnNv9V#r^t3fDHj-e5x4fqn0(F6(`uv zst&)cbJ$H*4SIPXQt`Y%bWoVGlQt(+E`C=)y~0Y8Oaw(EqSnBN>N>q`h{ifZ1m%CI zf!)*O!44e0hM@^F@5Mf{4L29qyj?Q6=b5Vz{rNIRG2!aloc~CQp`Y)3Q1v*U5@8FQ zoX^oEYY=AFHq%of<6i?K3?lE;8>+3duw#`H;}LgT{@PY#g8 zfSx?kY6y<@Zsm{gJ#0sLJn}V#FI@Ans%TYsNL69N=O`@G+fX}~>>HD~l?Rjpvgp1o zO(zRRH+LOYbNDQJXF(L^IU{?DlXI+OvEPvE2A86vG$yfV4KI$_^fY%|yCM=|A|~gy zP;nt{MQa|`5~P(lpsR!$4KiZ9+a$JL=nVG2U}mm&lwbOAZkcING27q^9KGeU-G_( z$85fOd~S|87w2@#@LH`z>pQ9BWC5e>q;Z|}r(LB~XO(;R)ey04@{KQ7m!f5|wCv;1 zOsCt=XMO?{0Teb&BJ|8$s`l~;V$A-IOjK8%S#8g7x(Rh6OJ=vS=mJr-jj*-B7#-c2 zB&+mPr7~sBo+EjxN>P*y@M?6`&v)s{OG|o&NeVKWJ|*D>Mr6!>^SvVyLP6pW&!T+0 zH~Y*FyDSv_fcul*4@2{l$CH0zq{2%C1if&ccM6pW8%wc*NV;xI4|n=gE#O z#x`__#JYTteLqPaFkL?bS8V}3aXU#h&c5DBC7?p&zqiR)hK8i-!dLtxNiEZR{N6ZS z!-m{i8v`qA6V-GA%rJGUId(sXy<1lO4+Uku{hirqQ{P6*2GeQC+3Z5==;d-|Mk1#E zSXvGck*9pG^x>l!dEk@O_3z&qc}w2Bv{MY%xL6laN2ecXQU+(Zzav?t^Z$lABK&E{wZhn`g|;#`+xTpc zU-QsP>8k>tykVyiH1KRh7hEXR1kxmZEz?nF=-X*Hx*s+fMj>wuw-@QdPEDbl%tBxe z{pCZKLHCz{z%9DKXRp~jQH~MadZ`zUG?RRd%EHk)(tEMBVk6Ak6J0rXA`5oRJjV|} z+(|M&W85aX9F~j4_oT}IApfoor-*f9b%E-R)2OY3f`cFHK`i|_2`n=O*}rvV+--Ct z5A0pa9p#DNN5TiQC5y$oqKLzap1?h|H|#g16mueGiL%{h2^x229ku1AROs2H&4yAbsua_(7+a#3U zFRXb%$p`_<4e$tomI~Zm0$ZgIM5*4=DhA?=PXZ0I-Sgg`q6H^+$F=D2y&;t#erkS~#U}l4^b?K~-G*)#ut}13k z;1G?cHEyeaW6?M?(B|d;F8Tkt?cJUdrHF7{IO1yEkc!+Yhblvsp1PVXw@+E&kP2L` z`C5(C89~i6!irWN&+>l=g!=|MAnESA4Q+YVBn^`?9NE4+Rf7V-L^zp1_JFwBgv!B)fDcSBkmOld-k| z){f&K2#t-|-LQX(7u&=;Ov|e5Ee)X*!q9#$lVIzXDreG=HCM#ulI&)=x2W(1FY8#s zxHR`LK$TobD|jWnN1P%uvTO%4X{!^-+Bw#DSxzU-eLr|0j&R&9KTnJFsves`hfblr z+n6r@foZPl1;peVPkTdFCBF=q={gLIQpsF-zE7m{U@oOhZ(3B2^?tl@HM8lQY4du} z$2iqVz?qj6xJWvNvR!icI|Q82_VY5#cI02tux;##Xp4FOOUXU(X&{0y5Qyx7ZCV=f zyRQ8|Is5-6Z|n~y6`U~&(#(pi@a88GJ&xay&wf`~VybR190-tWgDm(8h((Nt>lJ>o zR($X%jm_>FMso*lbJMb64uiU?Ev-Csvd?3>Ztb&r@xX13M%nire$&cb`SEwkg1?Ra zmMR_Rm;GU~*HZOj0FO?b=Z{skj#Sxmvj&{&w}m6^yfO$qL3LsNr^70A#qrP1u{|dl zi)WCSc8+ocMf}P9wF|VDre|w=rqyrVdOJrL?B#h~df3QebT3%w~=Aa;)Oro{~^xo!L}(y6R+}kX5p5LG@cL;Hv8rlPS3E2g-Z|lJoD`wN@?wGLA*$(8Qbx)rR^fN z+x=}=wp*TX^ZNh)m=7&*YCe8*w2eW{&^tJ5*Vo$~T3X=jUQDY{%qe$`xu_>U!?xK- zHqd6sz|Hz)_W-xz9dfTTFsr@Bex~-{0_DV6P(JEjQ>e?UPHGBX6f_(y+aPB&5bvUX z9zH_Po@lA(A1%BKB!kQuS>*E1Pg$+{-rwerFMS#ER2;9%p+LEXVU3X4i37;+Em9I+ zUhMm9lhK_5jd~pAVmByOVfa0MVZNR2``!~wd1vDJcvFvx;0QO<^K1%MU^d%2bA~uM zDMdAknr<-Oshef6=J4sNYaC%jOKK1g)E&x2J?_3G5eFdZ=$q;_* z4Z<1V&%!y)>FxtJO;-=B3#iG0GpWaIIPbOL=x+XCw44o zE}j3sVO--&bbh*oX{mzV$}O%WUl}>zhdcatwlFg6w5PAd1B6EeJ?d}%Jv!eCGfi>( z)`GHjTh{Aa-hF)A9au++VyYa6RX-(n)+i%7TvNt5*>qNq(MILI_YZMaw1=Wrs=e%~ z$K!0O5$ScUdv}+c5Zs%I%)IWMIJuLUbuSJ$RGzvrC>j1!-b+umBrxvTJC6svFDej z4tmQ>Zw?S1%6^w}+tR^W5J>4PaF*|7DFycBt|LOc)9(2&Z9Z$yQi|~90JG*V`qqMh zShzfQ>&1{G?n@~x)DZ~IJ&F81g_ITOV)_N0V9ST$6PgcgdFIZ8z;P5oVnS0gC!M6Z z5tGBckW?dIX+)aJ4q@X*yQQzbvOOrS4RWb&&kwGX4?NejrPjQTPuA#IWHb3IU8Fv| zV5oT+HE)=BMI6M^Zv}p`@m;Ue$WyLR zQus~>J>}PZXmM(afd*Ys&|!x&pv;|Q|8-VL!6RiWN!K(EtQ0K z-QsdWaw#S?C@f$eh~A4fb8WNl;De#|@tQf;u=9%HXpe{tLN>6l6lcKD_CnzC##~Mi zy`^Mk%SU0zm%Hxo%_?BE_ozFJ9B}i+U0~ofAsMWX-llR>*}|W6tpqesmX;z~ha)|+ z8FilSkQV?8fkvLnF&53@#w+|=?6N@|`t5&Yt=#{K@iWiJhC=pJ$W+v1aGVHXdehzW z?Py7t9UoRiTb$eWNKHO$&YphMBr#q3xBu?OUf5U&mCVq_SKrRD-yn>R6I&(9(Rd6; z!J>6<`e5_yn9#g+3kB+hPxAh8f;jfmisr8fn2|K#t7ok7W?6z+2g@v^;=F9m`!JH9 z#9dwtbc{{=?I=z4_!XR+G_tt1ii+Q^ek+t`&eI-?TtRai@8yj*g-5ex!A8^; z#3w4yTt9*6X8w^DEVHB%^yS}Zpb0^m%7VG)M5@tlPKLg2#}bd~I%8sJNFhhg1NvW4 zE%!6qUJS03fXJsNgP&$sB1zM{GfuxEuc+byd5SEyQ#wMs+D0QNjD2Se#dcIRr1VWE zj6LYf%OK295E$vQ@z15H#k;JsrS^X?QV_4|1UxUu)&2VX;C0%#kkQYJ;`0mRGMCJj z|LvukDX+jBwU{bfuyTSD;NxMZOvPbEiP)eq8T7F&{*c+m_58x>v3kC)#S|BLn@Byg zf`4O-A>DG4_*&MVm89V_w)>qR^A=8p+r}}uLHPj1drPtL%83Q3&tlmvMx&Iz!~#2E zuDUTCUus&xCCMqFh|%kzfiFVjw5m)(1FS1ag}cS;;gpSC>BkeXT3=|nb#pM|b!u_B zZeY}$4Yt8Wqp}^~8{!irI_a9mGNwT%(&p!$Qt?G@ZAAP*L)nC2@UpkD ze|m{~R=4|9yHd@n$rN}r!Ma_WHy0gr7KBM| zX9PZWxp(D6T&D(K&WxsWd-neOS*DXJLg0M(e|zZew;aN;XgShbx#0EMmj@f&TVkE% zw+P7c<5{y5EYP?-5G%FzUNuvy^6gzPji=cR_KDB{6@&gvza`l(tpo$ymh?1U{b!Y# z7opo$J=OE&4&a_D8m~2NvRyH8@7efrEp(1F`h5e*?o{cWyJa)OardXoUk?m>@LJ+* zGWH|jHN~jUBrASxr8jpsClx3gom| zrx~^p588nifYqs7WdoV?D9uYrq+DjbO_tph5$%}$;xKm%OvU#|IBy@ayCKVEstYFy zVqG6sV`N77YjefY-+ zBJDII1V(yc{4*W)*U6`V-LA(+q9sclYcGzzBM78_KX$DTz_-jUcQ`XsP8$fo59B{e zu`8nVWCze@AABNIUkD-$Qk4SghY9nqKio7KI0O!GUsbNMXq{p8=73*#cR2^|&}nlz zdy2`{F9@_hWz(l4mG1f*ZtcnCJe^oA{%Kd*(~f=h>rReIbJUkIfhlN~Z$=sN%DHXv z`nl)%d-Z+Q|7m^Eb4z1c0zhlmZP)F}(`X6p6!i^z7#u2dnwZsW8dP743G=z+k>-Fx zRBZw3vdfrXY!g@U(&yIH@kCTQs1(z2hr5(#z%2+s@|aF7Xnyru9uae+Q@N&}_Kf); zlM`>vB*$5`;6vpE$*kIy)~By!CB_~(iJkL*)RcWRQIwGWrU-*_2dc8ZKM2u!$+W1F zH{g&&qi&D|!a+6Hy-ds&HgpV^nml?b|4Fse$R)U{ub~mq*cT=e5>kK?SPjLGGi<#j zy&c_r7Y@tZg>XxciR#)682X^!XUV>|)X$7s_yKiDNzErbNrLmOdH^>Mc*as9aDM+0 znj!WDX@VZAjfzUU)%Y(wO!Er6w^kw~KO0q((XYX(tmfdco4eKR<*)@IKQ~-{qE8EI ztTN4=fQZZt+oVQ>!{U5e2USoxJuZt;QJX3>>i%m z<5U;45rRxy6emQ=;%d#m`1|81=fk@!dK=n~8F-k<&dUMLBh#E98|>efOP$W?%do-n zre)a%A9mf#PVpN1?MG`o)$-km_LYW&hD>og^;yT}TZc0d=0JyLWs8LG3@}$`8{a9U z)%cDPszcuXv)H04ILzB(Kf&5!0ahfxnxnfD0(DwHI|9u@wj|wqH%F(6wq)Hfsgbx& zgzMn1Q$G(wb7o(1poG*&io<`I_X&>E3W2v=fd{Mz*Ukt?2tS#YY(|7DrqqRzY>9JNd1U`KCFtdpQ+kQW*jvENkaXHofp5BVP8JIEs7zZp;{6&y{9RW9#j|l?AHi zI&2yyq!j+$jKigA>tvrfa#|{PS^}qo_ne#OZ7rBFG-lb2C!&@!{9{^j^56PlFPYR@ zq(dkCVP57f!N>gr^*8PdyG@m^4OHCu0=8H7 zc<^x}?!~SnD1wR&JVYpsQh@3|^5gVNwQhT}PoHY?ll3u7WEz;dar=n_hEAIa7-dzWt&}_bV=%SOx{xX1r<-ngrD5(e#qam^ zFRR)KYTn*5;L{&lLLkvQ#;$wMO{FwvZJxJd2_ja<<0Ch zv#B%HoA*i0@9*hfU7IWRnj{?zanzu8A$Gr$rRL*#js~3XY8v_;erSDyCd$oUUBZWw z)G)q$nA8BWg4;6vXDgmb6@R=vu~66t>-e*T*BxnN-;#g#r+4BUc?B|vaP8n%UK6sN z*l}!0O$g`2fSB~6C{~H$PmAVe9C${Yn@ELX1OOn*M>=P^SK+;zJ&3-2fQvDMyd__! z{hfw!r<#e?i~K85i?%tEQ*t8<(L~FcpBv@2(`hzd-}mx+W~q!@Dee+{KgWtsf4bnP z!RjY1eV=sc&tst@(8` zmR48gk&4XppyYa-kl%H|ou(d~|B%&H+bUV6)>O@0aI&v)uGgRHlayLGEocpFi{pCc zrbjr?r$2(sbJ&S#O=$F6526Q?zZXX7NoM>E?$iVNSJ$IZB`E`J^D0p z9kaV4jDg8-NeEt@t7J_7>bBO#rR$Noe^qfZx9ecRNIh8d|eO+citR$NU(r* z#?fI=g)*IImLzf3(xIP~TOL5Lf+QIUBQXocFRXX&%|1PKdzcsdI1?VJpfMB)g|b_8 zlF<9S)g$pQ(Nw= z?#q$&VjvbC=y0{Z+5j)vr{AVCe4^Ov%;2Xu+6cO7Fj@21wyUnwag{)Rzkl~pwFBpE z@Goms0i|-TQtK&2)KP{sN9BuBIgH*ev4CxavV&=Vy*woWgxHbRl#9WchQ}3=%e7x) zEpnPPzBos5;v2N!3kW<{3$k-}bk$#O#|AxsvX<=o!-!{t#o&|4F9+ew=l84Ip>+8p zH^>j`deGtBL8sX|ZzVwTUdyfF+?WKE&FxZFeW0|?Ix>Yt`L^jxW`?}auEN-tPWV`o zpn7_U>ZjhAW@XPF>M({$`5mhj~MtWDkTWY90ur0oUeT3Firp%otoXG5>X z&&(7S=9!e-Q=TFJK&m0JPR8uihZ`S(1oIEXs~0~-JA#E6vKl3A;#VGTX=FecW0iiB zPnRDiO$Y>#&D)K|l@5|IXVh0Rcf92cvPbqZ>cqsbVs8`*5(E8j^-K9js}pAwWoPDWefLq6M_iUWw7DG z9gP<0PI-})z9)K{=qq(g&5XbA+e|DqdInorufIC!!6-zlEU@?w zfV-zDzYl+=k39Ex!#+bALWTcea(hs|v`Xk?C6il}7DMc%Jyo^$MQX_kLse3``f@d6 zbH_;afI-{E-%ye%fUtb^%F2qobp_aCMbE{yUSWh!WaqmFbsn7`Uf=5%8~Ltw?TEtH zlM(1YJKVa8S+Z;+m_y%Cpc-p6w<(uhpXkwuN#wvm>UIXn7p=~#nQ(M~Fu)QBk^i(6 zI=e-0F70Rzp9`G(NO0;AO7dkt5-k7q_x1`f5T0wjN1=8%8k!E0`!Ba|_sPva7orOyAF9~t%htKuWJT{t#FI$f<1LKxueV+S zQ66s&&kzdc3z6~U%+;7|0xfV5!IJL)eS;G2Nr;(S+r_^ab+}TN=ZDWTkEtVFQ~8U= zz`-QoqKpUMTHotE-J58{~;^Psft;EjGGmDWwDco^M|j4LR*C`72xT?Tm#3KE5_J>>am^ z^=>qZvj0#IJDaJ}PMN2q`kC%AB;`rvNt4fOp+Kq?V<=5yS_AQS4qrwHISg2pyipJ$ z6|yay!+~O*ohB1-OO!wGQqUNu7VRgI+s{63P?t)fXoWs%csE>1xixE_(f2ND>V?T2 zi^kr!(GIS(u|HzCo|uwnA1Ob??$hX$O;p{A?0(w1_mkC)}|BJRNC z{6CPMM+1}zhC$ptFAjrB(U4x&*5t;IpzOzsRIigvOZkI_dsetbZ;vbgt|qVUfb-6~>lY*TxewgS7k~xLB_^YDD{~PlBB`giyjiDnC_tzf8Z^ z_e;TNo7$%;s@W2~exKU$$q@XzB}eXC;T@ed=c((D!R!((h<4~tr8l`rDFwOR;)SN` zU(6JK{MElwZ=8U2-QP+Hc5mqh?D%x}lL&RkE28Fv4I#pVh#Th&tNRNpE6m*K?fO8N z+Ul-+mx^ZQ7w+_lq-lY9$eCPygvel+_}(uGXraZM6zIi{$+>9NH}`R6%Cnx1-YBt` zgT*M7bhjzWPtLKyAESs?#`U>x&nD4K1JSAfxvVn;MUs;yAO_%Bf(aYmf9NW1!(y( z+?53VrbmfZXPY^)7hc^bN_C@!SFtbSwx7}K*g*cOfrGi;APxy7Tf@mcRntFFjE7nC z&}|^dHr}RToII^p{zG_8Q-i;k&^?Cs>>GllpK&Wti+7443!slaHK$PJHO?EB9+sQd<|%(#xRi)0%Fr|EjGnF^~7C znne|y`;0!E!x9~9W=zFGfSXqb>;nWQKPQkXXQy~*{FQf=8@(gDIaybP zJn(`rCvyBVez>vqlFj=whV$!%ugM$c)cc2!CTnCG@Ia+b2%8Yl6RN>J#4(GMip55~ zd-)Q7M(sIVzBzpdOnE5NAJ6~pg&)6<*e2rT8PN42Mpf0b9KZWH!Bfi{$M)$a_0oTh zvFTg#`=3AmjI#AUI}GJ=8LqYAoEmvC)5jy5>rY7A$-R+9ntqPg#Ff=#6Q zJB1-mds>LRDgD8$y{L?l7r1~aVu+jWK*9@c+QP^VS_!l!UE1)TW#^yZ4{)WKDG9ufp(+{a| zde0wiw(h0ba2xt>1U8(=9({AtQ4^!@5_}-2jV1rR*R_xSeJjY(2^@zHnuK6j15?o4kdB zJbfzUiyd?V`E~2xD7Cv^uM4=_ODVtw?;%y`EKyOm60n1Lb3)UyZ-#Lld=O=Aae`Oj z9IFK9C1YhgxE0skqddV-3UE(Ku(F-x(9tin_F137rOfj_$!wMK_qJWl;>*$bO`$l- zeMbL;E|F51?)WT8;4PXj&h&OPe7_8ftd(z9?r)Qc1EG$Dd)$|Y2rc3GWe4pI5PXyu z%N4UM0wy(d0Gw^1SQod5oUEAbg}}tu&{&tYUYLExjyNkDM)1q!$?X|w?ZV(w&%N#d zqafsL-8ZGcZ99|5I_PwH`o1hJ=b41lid>b>QN(HF;$uZSkOe^gKdl7|jy0s&ADLBV~d*^EM~TZr92 zIm&`hmA`+(_mjag*7?GRi3s)Svj~(4@@uKS(ZcY(g+pyb%Z7Q8KVBl`yY=4eNcYTr z%@G=kv$u9!C2E$$xHSku-`Y?k>b_Bf=s9b;9ERA%(aa1VEl@F;DMH6BsQ2$FvE!hM zMhE8(!O{NI;`?@xN47>A-yu&K^`j#Bk_2rY_LiSH-}f~qoSqX2Q@KUTGr*Rx+xM}E1d(Jc}iao$d(#kX8W~X zzY0`|1oU7ui?8*=e~0LHBdFQHqUyvyK+trhqdq1?D2L?>%hgT2-dF9ns>`sAkhxZ; z=h<&dv=4~%5+cS4D_0($J-SS>pDQgowz1O@ZMsO24OU)Ejelh>u{3PG4pu)9-Irn! zO44{vckwT>v-P3b*1!4Z2yb?^C$ww53=ydlsUj|&JSv2?ghd)>rqOh(GQy-n4d?T<@k?PDPdW9O@ zGF0M8J|oKC`el#f!WN2oQ9f#(Ux3EQ| z(Ne5Nv}~0)CcP)maq8Ay@yg{=q`314%Okg7oy8M7F1w#(1JfJO@HFEJRmx?RTcJwi3L; zt0|e=?ZTlScLnn-W12hE;2LX(avYt1vy^Yrxs!WJN>*95qOsZZ>><@IW`99O!;?M6 z(;6!XzRx8@jsBV@zbH?C3`WJyT$bJS^gCV?`BJf-OO%xTcB*g2od6 zj-Ui1f&S5u(>GnXC$B{P25V*PI0>JrA0BM$6DvImZyJQgM>O)XOuopmX0PJGwc?Y_ zByHY)IpT;G&kxC`9dt6Oj*a0D=a}CLv+MOYuV~Kt+Wbzs3@OjdNHLa9qG{$UcNy%R z^CUa{?M)3&na9hDGw;mOzDYCLubTVRhVwL9OKO2ti$;w$z#Agdd3V~7-3b2vBzTOk z`|(x3+wS|)=Va1_XtM%|9QzsnFA)T;Eq*QDw!U(4=OO(0zc;wrfx+BO2wB#}eJ9%# zW0>Ad;V#|l-?USFUZ{OhnjImXJ&{MdWt%7sV!B3zQ9;yX@<$Nv82D*nEc*?SpBzte;qMVWHE)YA!Vy6LPa!$yl01H!VW<=mp#jl#73VVno%of?h*hzT%e=% zYrTh9U!@XVkZs2l@%mu8go@T+l!Pq_RKq$?)~GZ_2>J$vL6J&y`Ip85mYJB@B3SJ! z-x;x}DnZJYeR)gh3UlfE`&%*R)mRBns-#`&^`?kIcrcHv#lH`;)Z!5PgSb8xJh% z1qWxSjqblw%{ch=*=8jv@TMi`3ldiefg3I7`Sz5}?yl=|b~+vQMf7j1CI`O_^A9tE zbaAHxPKar<7lfd_c^)b>ECZNKF0f&AD?Q0bal}~a8QuR_IZ~=+OHM!Ox>?@{xEp}S zD_s21kC(qWYUrWOFiWl_Zz)N9xrlpWGrTVhsr?PU(_4_`@_gbS`8 z(uX)+lxU4{efAONLcTnP96C~!{=YRa#dKIdQG@KYGxXAK- zw#|1Qg-KL{$|eqavA#xqImzR`!zn)=mCA*lg}CGKu9Gda3J%kHFflRKFqJSB`gz&< z!e?5sm5O*Pmi9Hgvxu9lK{+aZ`YplQ?gNz7wp=?X-ocF^r*Oa0PPPuu1~A*i*Nd}) z(yMG98g&1MGg%5!5ztrrLFv*?++#1~>KS3z7-5Sqv)WQDt6evRPP#%Z+}f|j{?5M| znf^wKd{gmItY@&y#UX2&qyT75<{np!Ap_uUqeRyGx6Q5omC&pZYyGLu+;$)xWpaEB zFQ&ql5T3&6X%+2sSiCO?y8S=YRwTI#g_Dz?%dICM1RK?78{X{W*d1Z{L4~d=*SG1w zPKFEL3vTDmS3831iSV;1S&|ex1WBScelXGzlE0Ug$YG5&x(4$KnqhOK#?b zC95cifGiV#45I{FJ~y{{)Yq9SHoSWbn=2lsO3hy(v6njSxw(cLCXPg?C(2>GQo59= z5@bN#2qNvdyGH#YpURwUFEt-eT}#{Top{s6wK#DlzY@|Kr=+VSrbWA|(WYZZRKf96 zpTY3%2cuW2VgW%Bu!D3C;Fa;9cd+5tG@sRqzWZnmy?Gn0cKG~$FWgn3+D<>$PHteP zaKKGxVJq#SXqCm>RQ@)er)qQU)y&8#*|Xk5 z#rjjnQs6%r&zq90x5YM9K3bp)okEQ+M6#BjI-CB%d|oN8Zi?2jlahh>6U#4m@|2w! zn3*_={igZplecZK_3NBx=$q8#)x&fSu0R^>k}$JULViRby@E#d4ENRQRlWH9t4Zh& zN>dvGS_)i}q7iCV)szF$jAB7|(K1Fyk=9m;kFsqO>&ctT97S5luz4KOsZo#d2Cg`%eHHX#e#>+GQN@D;0ZmiQVG3JvYmlFN? zy>7w`Z<7RoMZ4!QOt5EVcBi*)8!NymXr@^BwQIO$(pK z9BFQHT^UQNYLb7ZJX%ukq>M>w#^#ZP=}rb8&OXX@xbbY~NXOmT z#WDrU3D1LS{jmDbDbV+^yN*e(wD%UQb^Gj49+|!l-i;?*j!=)>aCgkj|0X(^^}k*K z!dUhYd!Z=GPcg4!vIv&}lmCj9l0q)q%^d;;utTh_LDvV)x})>owi1R6$e*Fc9A=yf zrO+LdUwB)%74mOKr{K|D1$6bwuHw3HE2yDkR`Q{HTp13P#Sm9J;l0`Q7-4Grej?bW zR7r@h@ZBbUD~|8=3#K3!z3Xo5W`TiA_|kfz_-@nf7gx?rJ`fcvA*@O6^fkXto;l)6 z{j_5Z?6SWRDQlU`USr)wzaIHD!f5MGpQl2JO8WGIymfsucU81zn*K?et03#LWBs3l z^&M@gB1rhTR20KRRXH z$0i*C(z5?-t|*0ha!?r&VggE2K8=*#?-YswF7ti22lcp;6{QKB)S9vn(~g|k{^(KP5My7*%&%b0>= zi7!8)5w6Sz*9j_VN^kg-A83MI)AC2*8gc2bx8np3usGo{6H@mni7+Z2l3{dd+@|gR zRG6hW<5|jc{uF=NO?i}JU0lJWPIA1v*Qsa3d}&rvClNX*dwFDZhdV&Ef+K zC^#kK;g`_`EDz#}Cw(V>ZADBPrB=AKu|+)3Ft+etsIy=y4ku@X=?mkK-kkrEk6L5p z%rMG0H>M0G@@Aw7&GWT&X_QU9l+o-A&ruJinE@vWRghlSox}CuH{nQ!KMjJDtdg39 z0be3ah`4GKME*?DE@UvIXa=C>cmsUK--!}MrB!dJ;K;lC!AKul7sA0e_Im2 z=Ivjz_=|t;FD?%3KhUUO5I&hZ7v>Y6_(UDbo7M4aMi>Set7U%fBR{wJsh|Am%=2>| zqy`v&hKwqnVEp-b*x+jF$a=(=&?W7lnc!>QDv}Sxiz_a_D*V%S(KnotFxoPFCp1Nx z>mL_f;Go`ITRpi6ZFCELXACT>)VwEGTy^E*qO78M?D+9dJ(~Nf^Fho(y`K!yrzXS@ zE)5=x3*ugjHgwQKWd+u7aqz|k?|6bmOBd^#li*2*C(N}WV;+XX!Rh=PpYMTVAZ=cA zxN>oD?=flUh(}bHxg3EB{ z3wF+&LcUg9m!$>A-wZ1&7~!=~xEh1c5qNhLI64c1L%ZhLh6%j7BS`D?Zp7v#&hfjE z#f9pQR3mReG;T5U_RWZ@0G8{>&FBn&ZNoEgU01yf*;iK%i(6)%`T!7E@O zRm@Dyn;CdRRo;+fhA>GWVP*<41rstMB*54#Hu9#`lC`wdyVR{-B+d8pea`pXbMOE6 zZ?$9_H@Hvtz2`a4^L>`H-21!d{O`hDG4(T8KkE(GPTIU*#uF?QWaNw%Ve|;53YCi8 zge@xzcpQ>se9@_&h$ZrmvCrt`KeEzV*=)s^zx?IXFaF{$PT%uA-=lLf%!}z_3w(82 zfIDDVgyD{uB)*1p#hIhJxZtZu=14^dd~>bbPFURD2!8fm-!pV6`hreqYh zn&q?xrtW+x8X&f;i@q-I#xey@sKduBIA^7cy`&zeS;fbm>%XNDqBZ9^uZ^d-qSX_F zk&&LY90L-t@tLPjX!F`irR3-hw$N3X zg&2MWNr>T%xS~rh80TXF-8LYMO#its>!LEJw8_v8O|SQN?9jmjo!X7#v3O;(k1@rh zd8K~f#aE6Q(=|~2!|$<5?DZdH(e{Tu%I5aaGnP98!fM;qb%9(Jbd=+RBz5;89U+6x zTK~FV0B8*K{y|5ov^^T`Kk~K}rM=$$!KZ31kgOS2-uxO5U1jt~+L9LIeEiM2hND3{ zWue71$9ii!!F`-ZzRKx`EYRfh)s*MOmqv4^i#nCZj+}$hLQLJ|!orIdX?#aa!+7DP z9!;_j`2p+28OdC?7JHqC7&9&I7Q3>-Q3t&hni?^#sGApLFhXiLgTv@dGf*m`I=6L+ zkK!hh^#*ZUw?bSrD_b)6y*kc1gS0Z`pN!<{f3d%yd;zw5hUcx=f@ZTjW7{vNt5 zX4PjLjq041h1k1i?{vkLZHm?WgL5BS!%VRlTXo*!kFA&K1oONm9{f?b<{wDs)JKKJ z-DQ_vHtp9tXryrVz6b96jE-#{OFF2>l73$rPc$tSOUi`xNU@51I}C!Ihgs(q;# z2M_6mq|efo#qVBh9B}CvuxTfpSIIZY*1XY{Xim>>dh?t86{T3(&RXSS`Cew((W44Knc`-X*vb2M1r0W2)^=7UgfiAn$S&dK} z97-=KS+}$~#CP`=3Q+VSD+Etc$g|EK2sm#WZa`x~M)74;-=$jc&Yx zK<3G9!`kwoSCiz~7Div}LQJ{0$3-leppJD*sBT*cf=L;y+paMxV=)c+kVN5 zM0qw4TjsbxWvW7++e1od&a8&1o_Zgan)$@d#L?qR07OM&3t901*F{p}1#1jCOr*g` zE@02a!-9P{XRH=aMgGIJAAHy!wlS6zUn9@h0M->AbORZ4i_5cngDVr6z)IQ~qRJ(t zpDdJ``^|Z=a>gipU3stw!(9&DU{h~3x9!wErX6ClTWtbI4F9K1j3^#^%CwEPET}j) zuJCS8FSw{YEWFf$%dE#j4A)Kc<8})%XZ!nKScExid@aIcQHHx-ct=bvykL*ME_=hm z3_cusixHgbfq(H_L&jv)5gYJ~p$>ztNC~UWYft~IlG{VPCU9T6AclQ3N0_qH!bWXn ztrzo{WiblPIIpBUq!_Ljwn?~agx$6J({8)Ue+T?yLa#Q=b`yx5(_chDf9C`|MS9eQ^7w*S z>#JTyg#(igQg*o>N7}b<@6r%ZPt+Eh$%H^0v^W@;xp zJFn6^X!OpYQ>UJJSYM8MLSvP$C4E7vf2@t$lv#u!abn7C%UEDzKE~lej#n<+$RlUI z2n`P!ED2or@O+3hUhJyq6fASPEC|JZ^zcu7nJZgyC`X1K2^P4{hJa#UUbZ9y} z4U8;}NuI$p;#0RVA92uGbQn8t91z$W?L)((^Tr%~%Ge{NyvD;HeKJ5sJMk3^IlR~l zU##v69`Y=rAR}RSjA@NSheZ|m)F-yb7_o`}$b;eQNpI3^h&Nn!-L-peyy>RbpFMMS zI;=$!qovT3PFVb~@x#lHD(A|iXZ2V8(`R+A5YJAKK@2IC5BszpgF52Jpaw%Ld`UQM z>Vz+!IJJI}16m}4_+Ls~YT_=T{N3;SJJT=y;{P(~WyPI;k+|3be_1WSg6Dw)2NqvQ zy7cUc>GCtj=EH}9O4@vwlx(%H<8xNJ(X%@K7TX#r9pu}F0w&%==L9KR%GrpQBZ zA#Gang->e?J01V$ln>A~DZTNs5i`C}?>I?|=@3e0B_j|SK%Cl;j|(yAR@-7HGHVqd zo;Lr}#Xl1JC_=PO{_I*RS(VFfB(z%^D`4`bE19JOrtrv1FQV8<-mERMV52(uFw-u=!54Vl*WwDSV^j_-L)F-l6c1g-RE?Y1pgQTBpG;6NxK%gBVcyKvULIbiZ=gBTz^-e zZ0hA2Q@gY{w1ZDjH~);6yl|bk7OA>?(p~<(!=+Yx#B5zj#)PO9E#^n0tC|StLP|6wDGj&IX~8_nLBk- z&yc1$xu`-Ez-wgerACCTigOBnU{8D>IGD#1Z$I zxVkJHY1=kQBR53BLFVxPmUcG@*EH3*&cSBkrEv6NVLb5{7J1(FQg_PG zk8L5wceo&L3oPP8Bd>*)JeI_QOzbooKe{5!f=tih%P;;@2ag-Y$_t*|!fFRvr&UG# zR%>$WQVy@M=#l6f)3k9>+N<5&+=USXj_pHN%J^1khOICl9hoq5-^Z4R3gQ|-GE;`C zo~28jFq*o1yt_`cmp19V?3R~J|IH74czXA{-tD_W(ERI^xD)0#{`J3{{-59b9gUl_ z(_7y3jnkdC-!)yTk63UQ%+8%Vy^!(Czx>P7ul?GuE&f16i@GbfG{{;qln1iZ8eTs0tU@Q!$I5E{0dz1X*;Opuc+k~(V3w+fc}7XorOZFj=3O}Z_U_}jc>3P^ z?!8~1y?$KpuzXU-+kce!pf;+_B!!&078@z^PVNvvMjGXzqemW{EJ#ou<>;ggP57iN z>LAM&IWl;uYWT4uQI8oODRb7?3V$p^(_o~;4GkY(_(7hw2{!!LN?g%N4n7!>iv*tp zmM!tZBf$fMjWISAzq*OQF0@2UEC^(#4Nc?GQ6$P~V6bDyfMcP6LB`!4;L_0|Z6kFC z2cC@-JMoWh>>U`BH*FUjwva`ReKv5U$kHCzOWPqE-z5VcyXe1JFYCQa`OQjubZ5+6C$y;Yls={F zAO8pcVET!l_=)LS{WaiXy4V7L87-i{xOgFk3u7l;C|`Q| z*mUU`y(@;{s^lZA(ZxBBy$>HL`Qn-bQDkLMroE70NeR0+yiK`Gj*}Ixbtrv@X|G{_CwL2s)c!R6DYsTxc8=Hl#JT>;PO!blERn+2W#{l-#t-AR-Hwyd_mW z6Dm)JJ&Bb}DrnhWt3zy)_YHj0A%x^oMIiqtvNrsi82RPh_H zmAfA>Yzl{4@#_&5Y_ZKTx;$3!Tr|2(p4`RRTr0Ld9ooYEEvY4aTmUa+Jzfx=8lw4{$7~j2kbq0lBx+GcQa3j+UfiUCI`1`=joC_r&^AL$xK9v? z??n}zue?y<_T;=ptF?B37kP5=z-J+ZJPR$wFI_L3u(-nAFIkY$Z-enozw~P1g_rWy zdtg+?CjRJmVT*=`U+kBxj}h#-mOdl6Zen?SkYl6j|^IQI=?u_B`_^*DVZI9@WtN;Ahe{K4`|Mzz%uG3j~x#h-NrnkT4ow^HV zm&?4I_qTrQx2AvcPyfku^2AB^E^Robv^2bt9x+?#AUV#4{tde;G={RhyY=#1-QD7T zhiz|5pRhU4S*-l>E9$FHyvJrsYCn=l<~lv^x?in#x5*kbUp$b%j@Y>jdi?E_r+ zq{oA&+iw@gtEj{ZSXYU`Bb$@bHiA zF~v56CY_rB6#uai-PnhZfAEP1Ol*=&GDw84NSR(Pb?h;8h$#s9c46KpV) z!;Amh^pdW(>4jbU^w#j}Z@vAt*XtMQk|z!xY(IW7ENyzffEMM)0PzJKcyF%CTJTPm*RF~|qeNNFy<$;I3IDO*J{$l#%r$0A6 ztlOoJ9z8yt)MJZ@wM?{FRT%45GBZ2*LWK2GfwAu4Ees4yk-cP^OUkXO;zLpjrBgKM zDz7MYc5HQd4FG^!#)2_}+#qlv$P3hXi_6~;WqQ<`Te`toV2N*57H7qDv7K;jr^{99 z^lTvpO0W3c{{r8+O{IiOo3p}l?2P_GgB@#K{E@SaKYA@6f!)%Ixl(c9+w@r?9~Tv@ zZhXbstv4H0?%dwral5c(*o^JRkXUfHw8jwU!cGMh7a3x`9Ac|ZNw(u(roYHvrMpvi z?Yefl<>niwyIytsbmuE?n|ANMR%4%NE6So3vpOCQVUjz6%NCyUT8If&HPwIgsI%)m z8z?hPckKUhJVEE3E{s1v;sOtAmenFiJ^BKzr4F6vyx4-Ag%agV`n|Z)@&!+eD<$LI zFO5d0>1C0Ig_h^_VFlhJ!<{jFWr+nBFTe;pe#Y8y3+sg!$%vI}x@VOuqo_^iiIjD* zR&|&bty#!&RM>bsbA#L<2I0Y?Ay2RCjIE&eEY4p-*n|wSKe~`7!x8b2)lJS z}w4x9p!ftC#lblZr`JO@vl5Ct}+;!#ip8veNk&T#|q>f&Xn! z{Q`_{b4RDAh5ydoLfmrmEz{5a{Lf7v`p}0iF2-zWor{bwXAAH+67PoLBHKwXt-j=> z9!b)tsy*5n&lY6x7VLcYz8)4>*B?%G@d>%SpqlxG%id});JSQpOxkQz=Ujq*v>Q~% zZ_M)7x8M8y|1|xhfBMg-yFYu+bn5gOw+9zZzQCF-ofoyd06VwJChYm}M$8M*y?t8U zi6}bT3N)v@-5@H4{(oUyIA6z zSH@HiV7A>^8;Ff|HsD6q`Lz8SGnRccoi*X*3${D#*}-}RnvnQpuFW$n%cT>JM>dr4?JwB8OAcky7bqLX4T{bY#<+Gu%0uXwJiv{(=)Qx7sO{KO(P! z+rV`o7IE7K&0R%VBxB)h=gw=i5c9XE54`^a(@i(tgoVFyi9h&!>MuSy{p$bzzfPaK z=d&Igm#J+ozx*;EQ@jg@&sM+Vt$$rwmPEF$0i@@ND9#F{zg=sSL@8!8s$lu6jZ z-YAD=ix2F!i`(8Fv5~e)TdhCeaSiZa~ zCN4&uEwn&dEVm~IGZ(f%H!*xcMj$QSapxV=Km7TBIDOy)AMkCF;a|)zw!oLG1-QsN z@Wg?MN0WLwCHmx%nnWvK=JK+JNV{;X-s%lHYA49qsFxIa2cz zcj-0@u0xaP_@qebk3R8d)6e~nzchXJp8Gs$=!Rb+kqOxOsBgla4{yY5N8>-T^UX({ zk&gbzgC5g}34aqWvgF(LeFJQgZ{h)8^HWZ~ozGD|(;A^|=*&EO>HM|N5jWCnoa2!3 z7aH3e^{o8DyBhA^Zv?OV=3M1b7Wzxg2`V;5dLxgcb*RdSN##b~NJl>AFC-tae7)uE zZ}|qjv*W+gV+!@iFz1|_de_B|Lu)(uwl7eH-1e18R_7i%zsJ#J_#Ma|hQ|^D>D0Jl zY|a}4vKA)*@V{i?jd#W5aiN;*GwElsggaSE=UIK4x_GtN;zbq~P_!81yH<3el$^zw zT8NPyEz0;a)-1xXfmIqAt(3PgWzVt?9T$u;Xf0UBSC)s^y?_ zZD>9QjL;se%h0U{C8kF}rR}>$XTXUSlh%kWYt|1@;o`#32H~+7HH9h>rhdkz4c%e?0 z>uwmnE<~9z`t-5W(;xrgC#J_9f2=ol8}{d}u0Zo8_o8ray(XpWX#1X2=5eGwdMt^N zQAcb55;9u){6l5^WjOx*ZWvD}q~|e#Oqixdj;{zuV!!fyeJYe*CGUTDW>b z?e%vH>(1j6AMUj}&88jYr8aFg~Xf)BIu~}dsC&d;todJI2!H1U)3?_WA z>Ab`-;=#cW4!@jr<3BRwu^paRJ(kF_fC4@nGEy+3t9L`Ft*umOG@6A1s% zTV=U@n8dk|3k)uPIJrJYZV`eyX+EdVG9G$T@5M3;djdFXbQNrG>n@lEg;5ey6v`CPCxhG{p|F8-~as=?~ECm^dkA?X#qa<{^+BR&M(5#h0ycI9@UW7 zF~dK`F5}UI@+JE8ct)4Y4QHY1m}|T)r@T&%Vaco`iYO~pK9tRO2%TVb6y*Vz#4kAK44#o3JDATr{$z_(_?3 z6EE_4A`{M>Z<1}|p|Ob{Uh)^hE#(?DHOLmWM_b@MZX3?OA;nzi6fC2#0m%5x8H(GH zTYYYa+{!=K@w@I9A3b#-+0d498SDKS{g;~KB)TKLk;l>S|2d8(u?G1`Ky~ZO2)S)!UX1jHMiDZePDs>JjxfNZ%ZaNBf#W-m`88afQ z46Q<&utWP_0Ze-#MynRcSTG?EZ_M+uUdyvsLawd8cvK55rm@j5Jc5K>m%(b$pqHD^ zJf`G@7RO2)b<8?uFNn1GBzNmrE>b}KEIDptTRT{vRz!{pP&V{jkgWBKycN#;k)H9* zL{k<}?caImDXq&!#JR`ktV3KV(97sm>?X>NqYIF?J_OI@K_S>VxkQm z{=yfhKl_urr>BoU?RwRxz||=9qdSupb77mRZjj-RhE?h&L@!Kso8nr)<3rpHbG_qR zf(1G*kN${{ z-Qdt2ZNN`LKf3VZKk~0?P9-nIltv@AGH=Ax|CQ zgC)l3h+JSOk8*IMPiPYMk-b^Z!+xDs%=OBY>v!$lv*)!uK5$4c`SQh~LHv*bofGHV zYDoR~=~Hk-{LD$c?#m&K#o84^z=hN z`cu=Ne)4W#B#HGUC%4^sTG@m>AKr)=Y21$G(A(uBKA4d=(xZIjLyu{VL$-In#Nwen8(!ne_f~u1uk-En*SPJ(M3XdC>*B-u=RzGP38pUur>RT)>$ZZOU)% ze5trw&4`7V@J)MBPp&Yjwc}AvY;FgOy)y?aIPz@RASq*M!}SEDG`Cl1m(AKFOEDU&6NBV|MJo=B#k9bx2s;jP^-ujle zP4E4#_fBv4`ZxHIo@V)i$cX#NC!f&czW;9ex4-wh(?ehUg6l_L`0HUD8`@ZiVG)Lk zKY0H`)2BcAIsYDSssz2^CTqf8?nDWysQs6C{k>C540NOh*qL`s|a34js_~%#+8C9s9hHyrh@E zJ~J<3E{OtjI=p6Kf^v8qF>GKtz2wfA$jL_=;F6G$u$x-N7XG6P4|#Oq!^0o8q2W=+ z7J16Zz_4KhJ}H>kjjhDW{17bi_(~n&gF}Y|Jz^3&IWkh@<}821|?QD=rk}nDSY(k9Lmtqqa!E>hfT_KW@z#xWawlOBmTfcv&Fv1 zKCq$(FAEkKIPr%p`mvFm_!2wuU?cvZv6U1Vyzp5-$)H1yEo7s=v;lJblAuY!Cm!sP z&<{TPk-b8<1-@O+jI!i^;~jV0ao1&+Ub5rR;Ul`sMenc>njbAp_~b@GOjsBi6EELA zt6OU0pokf1N;{t~r3g;H`+7e0pe{^Q?b%tAk~IbUuZmdHS5cBB{ABfWPd_<5|IAYw@^zu) z$qHlGN5xBaCB-@*a?HA%l7%6wOhv$~@72Q#%S7HM(;x~x5u=vaUcfaRb zrw@Ps``na%N$)(I6%Q&EV3BnW?ObD#=$b`f&R$#_%zC9;he)GQc4F++rKCqzKX%}Y z!&oBYU!%=axgKv}GGDSR#CXxe*jfOoyIAUxB(8t31C~V)FSuxtr51C_mKO&kC%10H zdO@cx9`wZ@%Cg5okL{{l_FKG;Ti0F0h;z!^E>Kmg9BzwC(rOjT{Bw@H2%B(U)Nh2m z*}t`b2$v14{o0~k*BrT(y$~Z?>+x4&q_!%1^ch>?!DsUYH*`Ua)6V?TX?l*0zD}Q2 z8;=Vy)%MT(Q^()@u6Ix0_P4%mdd=6p=BxgPI+ehT6{mgs z_q9G(56ge`bDI*$KmE-chS+=DsV@C)n2NIY#9jw^m0dyUTT_!IE%5HwC#&^1;Hd}h zyZ;`c59klUhfbe9{Rt^Nqz#SS4FkxiPwINu#(n}bQI4nK}m3biv&7<0udB(n2KEw%=&$xRCQ zC9!WlU9P&MUWiYgcxL+P|NeiTe)G3~#}kDH`;xO4Gtt`)yXKGOktW!&JoZ=k>wMIS zY~*c6k619IO}umQt+d+tVs^!&xaP0Rn=otMtfR58Wsr@$nVxlQZK1c6Az#KBdGbQc z`|bEMVy0dUcGg-_<9HY)oBD0d8_S`;Tsg7C)<|#Uaj@1w?JA6zQFl!nykWQ2-vC*# zuGx9@^pXGiznH%5TfWh8_;Cj{EFpPIY>fxk?Ou)FFa?5Dmm0q6P@$iZnoj`!-%g6d zIZOjsQ~bhl&UCp>o}p`uGnTdKf`~3vMR#(19A1$SBYk%NX{{B zd|_=tWwQv>$M7Z7uHC!zl{S5{`snd{4jg#wu-fJcy`=X+0sc%IpRDF!M=gs7p)oQp znt)@$gakiMQbrF9c92nyE%408$VtI&mt%+dT=4OgGV=H!ANi5NClL=D^F(mr;~N1q?FAk?@hS4cNomLnC!#qL8jSd8 z$TTogc$8^e5_CE&c?L>s1cz?)!H_7YQ_)8!_Mzb+kH5&H50-=-67u**XY`3IHlrVV z3p$U`dIOd|W6>45?diu^)RQKmJEuXs{zLb$;cJ9XsB*~ zIUPH4RG-0a@1+@A#fBPVnT;;0mk4h|8qIB_M<)6zU!%9@f8-+{nST7oe|&o7D_^<6 z>P6PY7C65aIC0{H-w)#pTd6#M`owhh=%c~F1SX)O2gwd8It>78}c>tA!n^dmp?0llMRe+S`- zxa?88v7K!{^)`+jZBpu`JJUtA@}LK298;Vv*LTMt_s}L^(CF5J0~7k(6_dr3WsV$d z)9H&MZIPzwdl4Xh^vIHKXpjzf(x4BmT+9&%&tFYy{PI(=X2HU7+b1=o(!>EKbWM0+ z_ywp9WU+V^cj3{oSUp2dy&V^(kYR(({Glv-kEeQ-4%Yv4TFc=yJK@N*vA&{|3_HD^ zilXkAskn%VK%7Hc9v)oqx;kxbo}9a4svl`z=+puHq))8xncnaXZN%*G@_+ld^wklm<`viG`h1J7J=MyIlNe(X^6CMqpPD!28DZ$fmv6qep!aB;!Hjr(5+`_Q5_+*s89E8w*v2j?ag#?zj$Psb7oT88JaXa! z4-F2z#7sGUpyMMLv3mQU&lVYW!E=V$Cq-e_kVXb@r(ZpTKaviVS~KkXQ5mITf_#{*X-xOMDU)W?67*mDu*92x&@ zs4gTUGvEbxj?ySh*o=1L1S>p)%B_JtdhilHqpzg>vtR!|r~mC={vXp99@fi?z0f+%ZkAzcG)Ydl+Z<9(fP)mNjv3*&92*x3?5o9Nz7}HY z5vbTHUuCn7CAsQ#!Z!75ulsYHC9GDlURA%vg^jw9ESp>h5>u;(V_~r+{^3iMp>Iu?WB#GeWAyT zGsUM1uyv*Gu-LzM|MZ&Iyjlw~Z<+48^Hu&T&mP?=!+QqDaf$2mNh~TIf9kmJ5`XxS zFHU!V`tIq^KlSGm^l{y#{_L}7sh(~3#TOP}+F}fgF!<%YFh>p@oj&vF&rgp(`lQAW z(-t>PYY*C%S}gOFz*ZVKChJ(D^@!u8Tz?ba-hKNWBmQlx#W8r3dnEJxLnno)-VM_v zva^_fcsirbX>rvkxn#O-*RJVm^@rN^{>L7D?4bM~)ZH*&kOLNC_~fgUJTIg{plK{} z5(_Jtlflm-0XY~#4xTbt7Bb*d#wPMKb{09%hsVSMIU5OHWY~a?f9$0kpUB|H7xoe} zJW_J(Ap?UQ%Fxjl>6BQILxUkDwowNfetb|)jPS96jGPS_80=EcTsRo9x~1cL zlZ0%n(}=}J?19HWY1E-SE~J1-tnmXrw&0ChbfY(7(Mdf-+V7Yro-E#gg?Fd!i1`NH z8ME*5OE2Gh`<=JF?vm#()m<^|{ViBfBFGAGK6P3kiDl1Us;?)Vna-TjvEFLp(=4+< zokaNo1A0(kCNHIxZNNjI%m0_&XnTiM@K_#Tm*l zn|v-dK8!hy7ok2dCE^I>7$rqsrE~A&o){fwi?1%nY|RE7GNA+7kb>v&#sBY5KRNw} zKl;RU_vh}N9)0|Y>6pIWa7tfl<3jpNP22h3WOt37L$A8dI#g^d&4tlM=;Ua0+9o#Z zEpVd_0;O)FinXUd#_~&`*W+L;k916L;vw_Ma@Vynw(7=NUY15XBW+u40#(SZ_{OQ9 z4i(n6;hay(utJ2=4I2%Z@FdCqyB!peXs7IxmNESyjm~+<#X6PO2i)@d2IQ_ ziKq4ZcYHdmKSVxp@X6`X#~#(ErytgfcORY}dGwKqyUhLgvg*Vr=Xj!NlKXKaFTUvA zDau)#;dk`O0|%$i-2M6K=wbetYDL!@(ge-kVmE8tol7?2w$NJ@JBOf~zumj8n|A2& zqG~)4Eg`4KMMpbMBO<@z$}994?wU&FY*2D~6E$AmD@n6Mc7-0%+N;GfHO$%j?|a}g z`daHj-C=a(Mqb5!n|aMxW7* z|D4cb3qCYCvf!~nIdYMm1|Pp*D3j2o5y{aDhCFt_Q;tvgY^2yij{nRl;|~lpTd-g# zlgRO(^5`Eqe&T=h8~tgT~cX(QbwpQh35*bbqjvZz3oJ=@-nHh*KL=D@Lf`2))M9a z;Z$%l;+zq*$<4YQ&ZQGSV4xFgYp14qKF8XOk?x}Z%SYtoot8f)jQGt&T9oC+mBi*c zzu@*!Q8qk$684ox+O&LDH1R4f%!jgYd3Z*-_ywM~_( zY*URNxq87^Ps|0V#fF;P&lno9+O{4o(uBWF`a4GQ;z_1ETFl4Zfa99V=Sg+E7h)tU ze=SzqkPNvO4F=Z>;Q#%!;3GBa~_f5Ha7xKEGu5qujm|(mhqXo$``dxkCzK8T;-iQ4q z(?o_vdbL^Zl!$FoLpvMkg0F4O<4E0&6+(1|W3TC-*i^$wV-pZ-uR zK*J`}n43Ryj}%=zRjRvTcIX=H#C?xE@<`qdql++qDd}*7Wl@46Gz$z2Es}?ra`+_5 zp@{+-Uii$bSfC+@;35Z)?WD+)!v_lnEQvBHIsDM!Qw|^h=x5pm23hPy1|PrR$cdK? zTw+XFEb~(a15JwU2U0_!NV6i$ik1!=unPbk;cU?WoUF` z3p%kxhOJ`4f-`#Uw!p! zbxY-b-R8Le&O7dSt%R2yI(&F~PCpV}KoN>-DJ?uZw>lQYN6v2y1#W}n4i{ha@av%; z7DPbTiQgQ4Nc6MjD0xJQzZQ)6k+w4jv9^@*678Vc4_gph^I3;FI3|G-FVY>lwedT? z<2$Aw`>`LJ-u13`O_%$kajT_^oQo}x7T{u7KmPOLqHZxid*nf#4fH$5pwh06nK}ka z&w_p|#a8khV=4E%O)xxWHGpgEwtXA_8PB=U>$>M6q(nr%*aN2kDngSBO~+<=5DW2A zuR+%JF}s*c78Fz3rMAxxLne%TWiFc&$uM95AAY*6db#WZFi%7S!{aSXw1HX z9_tRBBzi1{-ExaOkbu$OGuR6(D2oTT#$L(CNhd0`*+R!vsr34!kt>1nWF^}ymbrG+ z5U?v%9m{ig<TyGK@7TGL2Om7j*c>9QSbM9DJd2z zmBkWQ#O=tcs~#G+~c!?A5{vFVWS+ zew@&w7oHbRuq%7Q)B-@+bw3WgEV3k~@=qMnH?HW>SHsrUPq(-ROR*{z z$J6v&U!A@Zqw7+qv$`9Gg^Tz|J*a~=HzRSC+;zgGe9{4uxKuHSY(=*R5`4B1fyj1L z#!lKQ7D`8@`fp?Ap`@|sc0z_t+Dvrn1nn~Y;SKx2LZicm`5HL!CoYk$(u!xj22s5; z_A0No;PP3w#CQQmXMbZjzPL*UnQ0Uh#PMTKP4|87!RhhG4me(0Zn9M%t0GP+w!mi& zy$~sCD!s((Z+|6e-@aOm!juwiM;#ay9J%(d0peU-+Le0m*rD%=sKR#r0`rgdqpC&T zE2h2rDwCX?eenMKKXdlE=ML!)y@z!-%%|o0bJ|#NpyEC&wldXaz7~EKE+|7I3kJRD zk|QI59q*QbCZRXt!v{}_uh@l;P3CJP=#)oH?81Xitjq-y3w-RvS1`dN!+++XB8|l+ zviQJ%FtLRk9Od{3PAuDvf6DO*CUk5>9~>z*!GXWv5`kT)DY21{9XU264Nf9YBZEyR zvuKcxLQaQ_cGE#(+eh#jeDKH4g^oS^WHAGNWZ1+G^5pnLPJ&K3{wc5Zf<+G*F@+8u z|InihEd1CbM-L2GHp*<#B_|Q@s2@FK=(8aYo;znq=pnmC+Z%LO%&qz{*?-e5x9q*{ znrmLg?UP539+4uaNm890mnTFEBsAF6)T*pr=SQoww3lJq6M06r zVjkgrG4_V;=0q%uNr|B5iJyF-*_hkO;(ylOisU)mSHJqz)8G5p$EFW{@PocP#=jT~ zM;8khTcBEii?@d!ddQ!GH;09o!w={JnNPzrzSy)^R%I9aZp4IXwG?`;BX4!6RJoX3 z9Flt8_0EHkE=Ruqk|%gYU2(VYu{_s70XAtjK9+gnoB#E0fYK|g1vhqcB@HN1Ssdx^ z%;%fAw4~-na+7EWhx1lXCW~*L%KR9>F;``DeZk#m7$S}0L`DmgTzty_8c?WCOP<+E zqG^dZQIfUfNLz5*02|BDLnw_syVSICU_EHm0o57hVYL;EaYaVx96Byl_2` zncrDLLzhH*|5v+^yWM7r_z_R4ci*< zqa4LrfWRicGY-l=uCrJZnPV^(SLjd!)z&!*)#5O$U%NKG&?#sZEUHe#kMEZESiqMZ zFTxnp%FqpmYkG=*b*^cCgdsL$wnM1IrE*cF2TV0~8_;$oD?jL!jx%eQS>$X!kXrr1 zQ{LLB;;#One$~HK&(asR96BGo!5tFHL(!2`+2<5jsp`exgzAqar7WFVgppha4|->G zt^Mdjk4+Ch_{Hg|r%n*y(hV*!Cf_ZCGbb@QMOJ7*B%fg;WhuL)!S+T?G57A?t2=}) zrv}lgT*ZZd`h&^|NlaH%%Mj-dSGi1mP|z_aPW~+ zL1^%?5k5HN=Uzl+j}j7d^@&4j-iO$uqyj4=J%FE^=_u#a?W}OY9=MfAGXX zP6{qQ!pBbLqoWLd{KpP9*hc>X*8rpE`lX4qN1*u>~%R zFd~hMeen2a@gz1x+COCy`fED=6DxMIp%Xgt*XZ`Y*Xms;+&g&vU3cDfm#TRcpO-#$ z=8VDqQENX6{FsejP{KPWL!T(M&*!Y4gAcX`7zW{j)(MmouM85Q@&D}ASFQ$tvux|!B?+mD_8k``j>!n3*LAT8${UOf$D=WQf>Ox8sq@|yVQ0P+993T2%aasfHo61OLg zZ*=pLMpP;l;r+ae(0O6I07R(VVvGcpG{x?#DmQds4dRRH$eNC&aj zTbGxn&Zlpp!5Axi*HX|$>o>wgmq>}<4cer5TjXMwL~U3^9KB#}c&{`>FIX3!_Pd?k zCA`HkxQ*Pii0WgcW@{I*dFpK3)PxTi_(Ft-f+wMtf#p)6?9^B(qg}7z1QbdWOdF6F z#bZn+;?0kZVNdl&qkhX zs;_k>5yqp&{DPVpQU)C{{bf5%P!;G2T?d`aVax9z++;$dKn}MsgL&W() zeV}98ZHsO3xK^4MMI@0NtpAaN=bT}FEwGF{%Y?&L76(kXazEenvICU;EFzSyUkEG= zA=}vw^3!M$zo5N7|!egjYJWy*~>seEOvN zK$hVFYqlI~^QTaIDpV^ZYl&{xS4bW-J{$dVKnV9z{Cv7%$`{ z*h=`te)7g{nYca%O6q+Bb{buu2P#So<*>m>gWKLnn=!~EyXKl}rk&a-TYKSOBXqd_ z&oI0cLbbSx&s9s4YX`S6ZDCqv*`eg77qgC7y?;vYli9sT-ywPaxo7UZ@4nB=0AETv zqAw-gE!BIpF(;&QG#VFZm}G-yYZdRf+~EQrT`+_M-A2iRo;zcCcT9Xnha4O656uP+ zUTFL=pNl?x{G%UVk%?tqY$jIZB=T`XA1wBmB~EB;jIo1HJ0u4ao%lqS#irB;-L?Jj!Ur20(8$Nw zBO^~8p}{6za^6P!#@rQi!wom=-m_=-Yfk$v6+KQVO`jW^lCrW!u9KsGwVYGdk=kBJ zR#$3-gA3e))n4BD(e!DRGb!ShSp4B`obyZV#2nj;91HQgwB}F=XB+tvy)5xV$V=Hi z^q~(;Klp<`s5j@o=HlZ@%O?7oFKxdNQ(r#t$Zunw@w86GTkbS84RaQB#AagIl3Ov1f2F`|ThZ~$d5+Y=-X0-)HEFkAaT>w~@oeGD{z3@?1aZ@ol-Jz?Aau?H6$&hRp(s`JP(K0!z^$19lLmNFv= zoW`@zH0KNv#Rt={UM$JoEnb}{8?jk;h}5|QUhbe-^GDgN+ZJz{-}vu~3fMy?zh#dQ ziIG|!xtXmk{z)sUa--|EQNlY)uC@yeS zR{`;ZPK~$>v^g(?_t~M9MNJi;Ubcw?gAV&k6HVQ;N~U1;{&gE^VLU z#(aKKuWzKc?jUIvGlj6F&{r_A**N=$7HKSU4|C;&m&cC4QN}hYIdpWOV-MT$2R02EEV5vrNytbkkN$$k zJ}DSzaFmk=i@o?mmV_KWbH~ug;UyPoI`u}D_KF|!Y{*IE!Hzs|N#Un_LF8kA7eqiq z5Hvh#Y`C$JiG3VQEF3WCvQU5=JbAY0XW)!w!E6sKDS2$bgT_7yIW&U>KK`M}V>i0Q znfjm?xk&TZI5643ga$V*vZSuzQ!o63#~*w!(1|-ayx>F{6I}R|N#vnpA3JRLAQ2b- z$#2)m?~Pi)-ldcD^{=|?&f9gG-F5t_r>0XUPHN>^2&INE6bm$6f|?t8J*khk-WC;g4K zO?2_24UA$47QUFX+-BpeY~#0l0L7!~%Z@g5j%~_H${}mK5d(B9>xyx_Q{-6*WX`;~ z4#J9=1q^hvcu_oJAztC^uy**f90kQi|3X!?_yR13e zm(8QRuvdLh3o+G(_%J59JrP&=@jwF5(oBhgj&UDf(kMF(MC59$WC^ zC-T%4KJ!BSGbcnRGIA1m^dcW~WMG01Hn~LhpE)PEv;}tHkMSXo{n*78ybCU+(idD{ z3TPc$8k1OQ%yb<2*a_(z@-d}D(~;N!1D`>Z!9*ULBAwzpHjxL9o#u2@d+ zN%29>;zjIHCV`7CId<8gN%0%~_#~Fdz(psvkS8S;{Ev3Pf*bu}2Ru30;6)ls867Y@ z{`9(CyLR2F6XCw=^osMFZoKLB&+2S==-@$}GYLh@?x{}@*R z9G7+#-zeu8nLQz)&RLHzvXl&3mtk(?4LZgY$Tl|b5@!p>nRcy+j$3OeBdZA>%(*T7 z;&Ozf5$}rDBtUz~q%+D(Lwf})J~FKKi|JOj*(k2`DN=%lLagwo8G`1?Z8ZISooRX;EvJSX$oRpn5xd+k48(7P% zSCy=(eq6z0Gg7&dDp~arAG)}r>*&FAF_EFyQq%L8iAkGK1G^k-^3gUx?65P@jLw-Q z?5rLevwTgZZQ++E>9jF7n-J9sG+F7ic2}9jLE6jBscWPe(^^a)GN^@^YI}ScYn@s= zz@`@s$lJz(2Ry+1^8$d%Ui5AF@>{$tgrHxG1^62l53CEe)j{JUtqV^soSb35ZDp(J z$%YptBy-;4$|6W&a9LP%OQvhZl;vPtM2hQ*n*|ziwCd@F7_coW*z^(!Qs$~{#6qSB z?W5YK$^e16)G+ulXUNS_Id$&#(tbm|jZxYEY*W@-Zo_^2xzzoa(~1jS*QA{+s#b;P z*nrKKNDn@7XnOSF$EK%_Jzei#6hGIW^c{A3!pKnUe278M{ElrzFPD~!3*L-7mw#f~ zw{P!snLjyQwutF`uK&^HD@o+k->*0D1cOR#KmWK);C*^QFMkv|@c0vVA3uKVsIJ2f z>x-=SN%6DV_=IH^J;p^Cv7O+NGMB__=(&!r=<>>P(29`YbpkoUeX_JlkAZOl)4sl=yeEf@a8q4Ug z#eej`Bo=b)B#m@<7fe1@?t%%b1ww4;kWD!aO$x3`wFl2e2a}JT29M6B^HTrF!IH*K z3?_aeL!T7+nvQ??(9y#`{>Z~a9(y7eG#GpY8yx!Z<3G6Yu$h(H)}Jgti%Hd(N@)&*(g5G_s(y6=k&eb`@PfOdEfh{m%aSu{(K?o z7t_~P3v9O#Gd<7mRwYkDOx6WQ4YEkL=3JC)O7jFq#|?)Sn|3D6Xe_v`3}Qvvc z&)mF2`xanszu67F4S!b4&%z>@QDML?HD^$$*=jh%o3T|q{Aknv1$t2^dAAWln1XRc ztPM1f*qc(u@=~?|jDwBf8Jj*Lm}#-qECb3+5kluWuQc@Wdt-f5XwOs?rFH& zyqj#s+Y~mAoHwhTH=Y|ib089PMb-qpZMobVl4tx?b|1J6RhDwK<=C#A3#yEU@xJ>* zHhY_wSL5LEkBNO>#A)`-gKtE~u!v!V_+IEh&RsICJk#RIM(&2mf(mhaBS5vf)LI!j zu{FQeZ?yUwc|BS*`%Vmfp{41Ji!t0eqw3kM#BEWg7KswK7oHM|E>oF^6OO}k4%N!l zoRSSZe5^Ms6pDSfg<))M$iqv$DEB(MolDQYuqjj2sVv&;mR)(q;q21`z9AXE=raa* z5$~ZV4^9UjePTL#OUj0k_rh)Xw;|srh7R@^PU8nu|3&k@G{Cg%Xj*Mh?sS`}<)^2C9E=-5w8DUWps4L-3zhff~8V8n8w z%$5{7F;b45U@oYf;4b7uL#6}9SfNKgbUKegk;Mj)?w~9pAd8)J4(0H%!A8oU8RhUP zvw>qk$94uCwsMDwNT)1pj6M;>7JTesgFHS{#wPs8Lc<3Go}7fOaRCMy{?Un__>CQj zEDtVNY_df!cyjphPkC%}{8N6l?uL1lZY|t*#T8fVz4f+RU#CTsOAjA8JimNYpfXQL zn^T1%wLyfbj?vQbmmM$&wEbqyuPwEA}daM&;B*!pkCJ z2aUy&Q^G<#P;N;q;x8Lu3fU@uUS;c8b+f*Wtajr7z;*sEgM{ zpEjxc656)V5kCIoD1L0$V0KFJQKfl5@(0@bL)nfLo3F6Z#xZgS07Ge@N7XGg9B`%R zc|jxRY)cFycx9so@vPga8RvBe_=8=CnYL>>)@Zzu?j{E!a&nJ7GXT_h@m{<>qDp~b zq%<~?5MtH_zQ!ylQk_YP&c19khxCgo*ut(M(tU7_D6Q_%1Y-?;aU=71OaW<>%S5l) z0GaKG>ATZfSRFIis3|SR8mQUrnUv;ZqH!_Q+GnljenkefTbpa9 z&LlgwZA}KdJUHi|^v_*X_V7I~)-+zz$pVY-GHHuGwE%-pMPwc{de|X4EYg^2EtsZ@ z>vC+jxV_LI*toa?%|Fcb#UAm9CwI-TC{#3lLF<##c-L;$;e9iT4S9uR99SZA%p%Gg zdE?n$B+6=G2aH+9mJCMtcmZGKEXKGES%hiz!v;v>j4vPPdMGJ(YR=$W zpbQ&M09(TT34Q(O$pZ(qJvp7!D*?Eca4=vNMoVccYvvTSE;nZw`wCqDUVH7e(=|JH zI^>Q1d*evDy8gdRk2CSLBa&an%eWNnV-I7C6hoS{NKfy#q@AAo!b1;z`plWrhxJ;7 zL&uLFzeg&3u9^u53T!nQ(NGYLRW8ahCuIJ}78!ZU&}^}TPJ#$8i#3!Zhej`n7hqP? z%>MC_xmo;WK1VtBV}~+)5;#(PVmtK;pPU3gF{X@7Y%%v6>leH610FdxNQnhpc+@#% z^xzX037Rb!_+aBRb5{6llu40S=>poP(ic!jTSB3M)0i|QTRJ5&l)$8eA|Lg_BWIw{ ziNT9>0#Bmj=zQ$Q$2hoRD;37auuzIoOVVQA9Yrn`$8hi@Du-F(TT79qTi$w+x7Zk*muh1R&)2e)0v#e^LquR!?8ExZBnCFC77 zCr+Mdm@Y9A-7%#?BX2>k04c8X=oEk4&k`a%mw(9 z*I$4A#k*wEgny0mO$#y4^@W(KxF;SOT75F0VrSh5vvWO4gExtm7h$W-haSGUy!gAhi6wE$Gjg9hVL0N1?Ws80U$*oXv z`eH8pEx9m5b<$?Q<`$dV(&`OA%SHKuo7{?q%|Tc67SRtwUjUqaHhnw`TvZU^|CJlR z47imvhNK|WoNSq zuT3;NGH=)Rz)pPrcP9Q*XgVD$k>iOx0^wK;=$2D_{S;($tG%H z!ZLIl#^PopGQ^P=Pem^_pr;Ew>@ta0o<#*F@Wy(9fICcbmx*%hqp#d|n>2rd*dFfO za1d1}Te#@hvR{ima&JGf=TKVRr0fNY_)_k7$58J0hLvru6CG2N@taxpiB-OJHQExnC^Nxs0Elq2M_Bdy!A*wqn0+9_hk;-v_#C#T$@aBYhI>0KgD$( z!00)cl5O_CbLZ95wbx!}2(d+noOG`1e?HB+!+%8A}t&J!b2Aw_@vkveZ~)R z{DZ|VGFw_wtR7t>$27TB~9^Zd~VbT(|ilR7p$ z&b4clkC#587>mJd(>NZM!gc-N#LFdG4e68wK*^4w%ZGDxIiO3S`}SmoIM5m5sS8`_ zI|xK&H2aio7FCEO9n&rk_7M4^b>Ly6`;YFXyo@bi&al%WTiUJx`USKUJ@HYy z^g3)~3n#N4hNR&p@~(#v-%D((|H{8z;k)@HvtIfsc=8@#VwK?X> zVDC%&Y=H#4ZH_M3?Sd*r`xyGX;L&To`qlgS`o^>T0$;17AOmytLynL>-i@eh?)iEEV9NI}#_ zQzADh6QXeO*hLs-#OyG@buj{!$6x~enM9WlJ!1hF@(^_3k!qEKaYTCu_N*< zmQc>Z!EC!NFgSjqR_r!j0f!9UwpiSe09oo%FX?500`)pqvT!l`gJw~vcwQii4Y}%e zq=e4A&>@*yk5R(4V9D{_YEGc0@n=5m;(wHDbh%9gNSj30@u6K^W;?crp(4z)-NYuo z7j+a(uOI#dG7cJy3uPyK3(BzfI6kfSCq8xjX+3^)csh9S$n=c9hIIC;{YP*5`rjJFr~k^VExJwz&+BodYo_b2yM`C^9=-3r`ySHy=Ab@TeOT|O z{Jd1T8-|N6F0>i#ry30pQ}CD*5g5G8yRzUxnFNjv96X0Hl-q@!I3=e{Rlvq3`YhxS zOXQ?2QKfI*I4+Bi9~3n~%Vg`9|wEuBNGX-pcKhR>F|jGaR#&`GhA zP8mB6eG+=H5ngm-2V8W=g$m+>PlBJpLyk>kY_SupSUuq%K6w0(da)Ot#P&m(1U^0z z*BA$~v0cWx#s&vXY>2e~#Dx9Oi4i<0`q70CKK}XU++BKC%$+R2TyxFUyKlMqvymWnyuZJ;-6oS{rmS% zZ+g=kr?W+~ZE(KfwC`xU}% z?U(7`m|JhWl9i>c2|*i=bjC)=$)@`(yWoSacw=dhvZLLkaw^@EqN$zBq*YF{y`^ox zPiT9NI_=V6mC!!p;*kKlKb*)I<5YJg__483Ubg+@cYR@9s`K$P>HQ$G%w=yKa%TOZ z+PmLR57*)hT_vjYv(aLtm-^`C)LAyu?_f;)=~^%N=(cj7e=6WypJ1g-+rh=Q9aFI3 z^h3Ll2`?{3u`K2^7BuSQZ^fAtm=uf$W5g$gLd`7@dz2siA#))iHsM!{5{)!1h-xbrdhl3iU_M0I^(fjx2a6rVXyX;Oi-nDGF`|6o zFShgz5GG|5MK+}HaGIZ#_^24*#38ovDZ4J_$pd{H%74*wckzWOurx-HsN{^ZiP zdbdpc^N*dvFTllot3r!iZ5DprZG(0cU*-GSC z!&`e}i@#QaG#8Xe!l#az|FY1+yB6IJ>L z@cs9{|N8%Z`0z0==;dyh-^S{nvhlfUys*&$NQmn>a25rW!H1?~hsY0FG$lN3Ezt)? zf6(ZT{-M#K8MLJYM!tEVFW^T&mu>Wi3>trw=45^JMOnKcrGA1Iz=wyed4ZdcKJnp( z{&-Egwv&!p`N0nT(H9?sOL)kO6Cb zb`CoMeE@rG0DZYr-NDA!m$t^x2Yy|AfH&%cBX>~(AMnt_Mm*>Q)`dI(bgrixPeO0r1cP8s!} zQ{w(*M(sNF=45s|w{ITj<-YXRVc8$e$bk z;NX02Jq#wM@LDRcpJQKE%2ijVp6HPfU8~H zCm?%wcVu0MKHK`+(06CF_2{B|Pwt&_Q2EhK>BZ%oBZm-)3`Yr$b5M=CE!AEh#NDO> zCm-rkyef;kI);#ASiemHvarEulI+(X#3#|pd+@o^Xf=#d@@;L`eoGCs+I8T;LUYNQ z$ce2#R6BFDTi|czq^QY&qwojZ@UnO+0Y+xAsV6l%!W&2%>mf=E%G4Pgj6HI*zRS8^` zc+^2*Ut)9LC~TQp`bJ||HV>nt$lL!`j`k-4Y8P({y>(tcBwy>#bZh3(xiQfRK8aTR zRjyO}1!))Mt$i_X?Hqe#{AMFGESF>hWRaU>MgTmyBGwC3r zjV3t3m?Z5u)J=8x+3?>J|6&$V-gx6?5{Va9Sm*?q6U>V$JqP9qeSftBuEpp5E_oMa zlv4jxW~xG5)rULsh(9H5`YO+>)gJHu-@J7*?}s_uJ%0S~U%&e5>wnAj%I&Z0I7#_ZT~5!9HE% zBLj$SeDkEV4(OaIFxfMeR8Pjn92=J%W6qW~G&^q8WoNpB^hG)D5d2}MJm_L)!M8Km zG1Rp*j&gjM3-JMU)v?4kI?!t!X!XRynsl*)Pkq4Y*tQQed>3BOGfvR*K?$Hk9CJVq z(8e~BuODr!g9p}VXMD=P%i~GE&SOd}cfa{r;KGH2YY!eg+&#==N?w2wxDCB=&zX_tV)7>Z8c)!fw|NY#N!H`t5D-p-&Bdire+a zec5WzvYPVw1rTHWpsTskKdg$zk4-go-P=6-qD+b_=cn|`6Lp*H$qyg%-inI`-8B~_ zr$Jc_;*nn9ldsWJV>b7L6ID9PBV)>|h^%<4k*N zzrTeIMAWz_JiR|ef$?jAmeR~CtnZK3zD#B^oTqR zf2M!>f!F!vv+Rl9{dSr;6)(GGj@0ftS(YqDFuyYXlsj(vRz=6B1r_|qt`;3AtKQAG zi#+(MMVi=_|I%r(nhuLBq4RHE6k#EUHf+3?A^a*))2wt`yyVdANR&J}G$;Lc zjL$rjFC?{bj=iybEUDthC-3{I9VMfpWG;}&z@^+xQ*0Jjvg2y0h0BPZOE4n`Yu5*` zj`D{}=EQ0Dhxtm<=TZ8%TwC1BV@dxK{2#M1*}}NGLoZVl?E(w5dg#I@TdohW1K>kb zqANe>q6;(%MCtIZgHZAVV#j6NIsXAZ2)nM>@HglJfR1~+#b+XgR%{6MRZuC#i} z^ratmly{H>o#J5UVgokl<9F>RtjR_lRUcZEDCp8vhIH@t&s}o}V_NiYC@XzgroItq!6fOMVf!{l~ zvlw&S-kUY?J2IarCP`WC`U`sk6DPR z$xFIO@AdtHgg&!^K?g}4FYQ2BDV~(IW9x+44>HWD6j$*V#{B|@J`Z@|NeQd5+piZY zZ6PMS_J9ARvSiJ!91lOn7+8g##$W46|B$}pQqx~GIJ3lJt@zz}6Y4`VLepyPg@Zj%9#*0ss^{9xs9(7I_>`Dy`=3nre(*zfRjnVHL(}~sk6r#F z2ny9NV?c=r7sEuyUOe zo{+|``pi76XA0w)YM#l2&5;GXNPmj_zMt|q|3o!D+W+q}D;f1+9?>8~P>;+L#=XCv z;7{>ryZ8uA!9qeyBs4luy}=vZc+I@o)L0mHS(bgX!A=`}Fq!gG)TNJ3_peW4vM^AK zA(Vx;1&6j^5F9_vLD@&8`6+*CkSeRdUFBAbAA?`wl5Y7AGA*KXe!FX?`9&}6Y%Ek@ z*Yd8CT9%*2$4}{0?oF287K^%(k3O&^!2l(3*uYyn3;kBZ}Ujf!#mkm_j5PQ z=h6IawmaGAHf&f^vd96*IIz^NZ{)mzBcG0 z<8dPE9JF{Yz)+eSKCl_pWaJEBiyY7n9Q#lEBTBb&f%#T_Md{kLUp;^Ee0Tf1 zd_^f))qvXva^C4bf6q*`~{#3yf`=Wd%UyzLIM@j_v6 zHum4Rt(syZdkdVt5OZ8z&L8>+{io3w)6Zr=zUDP46PHUIO2;PpN4H33)2Nr z(d;}6$=ElVz`oLKBA-)TSlZ84%)ZaV#qLC6b(D8OuUD(s;KqOvRHu<{4x3fyHDa*c zk2W>@A=fB1J*{#JH_4fBrlCwX)u{$y_4%$;h8hr$_MDw@vEMGZ(V6p6r+MwaJy&GS zMaqiZQfqvDSmM?@TNDixIL0(&B z7rgr$d{}Q5Pspce;)Q1Y0lz6`S@Bz2At_mSepN|-F7QNJ$#;)aw%s>+6)zs! zLQadJzq(^3j|jcYT`SM~-7Ca+ofGxSShXBo!)){W`TVn?kT7@8e1%D~+@K_@wW)f@TX!ajd=Bswpp@hJggk>iNl zne=C;F}{A}L%W$Ly5KCN*m>ar>Pq8>4SjtgW1}s|9k34Y(Ury@JW2o`(17t-crgYt zH+0||SNzwx)8cP40AI@q=*?gK>a&k899+2dD7RfccyKT3hH4neR7U*NU<(6?UQX;3 z+|qf>mz2u3@tQotZU*gF=VP0D8rx~RyMYzk^T^}v-n|F;)ZgR6%MIbeZ$8oJ5GG^% z#BWMVU3h>e&eG|M@I_NLUodC3@}{P8(?Pby-`aocfBm>v6a&HGvK@aJE;7DVv-Gy1 zT|`rE;}2s=1DnP6Y4pUMl(zAA8-KU)w;vaaVjwtLw()lxf4}GabNT3U-G$vwNGy@G z%d>XA@-&q`#VQ%2H1mJ{5A*NC!%JbeKkT&ux{v176>}+qZ5iZD*voTuJ#cP!jk~^+ zLl(nsfByLwU*(TBxAR5SJGn#Y^Z5F|*;ow0A&I$Y0?l<7*E#$Jm=Ydxb=3*0c<2L^ z>gW&OfP8h~v%zOm9~>R7r^t_*0;6x~Q3kGW{lO!KbhsOefI~aRG|M0-s+2g{*C^tU-!2#^>j}7=}AF_DlXMF6;58v3pqg?x1%cD|2 zENqbD3;*!I$6Q7^3n>D6$dsFt_TJ?}KJb*I4~`rd?Z^1qBj*vNkMbzYZ*phE_1ryi z^OH|L`5&=7xO?|rUO@IFV<%5{=5H#A#!IV3$rWI6Y!Wjk); z&(vfCGPhRRZ#(|Bnp;T9SkIoDax@Z|-q#Tw-5E9Y8jZI6r%UzC80J>bSy4h@bka|&8G zJix^geZxbS>p1PnPv1eyCnbFCtpj{@&DHBfWB@tA1BhiV#mEnGuUXCC`l5q>;<0HD z4p1tm#Ll)1T5@>WtBY)m0exn`%AJ{nR*8_`xRXpJNAv&yKmbWZK~&udsL}Xi!;TG) z44R!t$xe5N23_ndc9bzFM?c{3&;g*u?MUzdJDVK?nofqCg$UzAn>#rA_%aW3hXz-M zuDO822lS_%{-8Y|jE{|eutf)bMep-5D?M^)47>jjeFEd-V8GEy$D|!4Uz$>$7_0Kl{7Kg1eo6xAU(D z$~OO)&$s{1^Y8UM@_v-}4+1nVOSE<3>IM8d^!o9~dH!YYVmY6AJt`!^7nUyOQTK8F zU*~^)HBGb=q(_K9=KmM+X#A}_l5{?I(){_~{`|kc`|i8@`J?XLyrSTbS&aES7T;v! zdd-Z$+eJxRnD82iYZ`F$*udclETX{YT8K}yQX};#)rU_E0UP6UO*O_Bhp!Lx0ZQWI z4ajCr7M4EWj-2V7au0{8%Yd>I=apg(YZ5Z?tI<7-PDyjCE@wsB`dBd28NAd?O{>AOS6o#euR`uJdn zsWZk$#s-fVV@XFL_NXszi7j!^6DPiY^sg`IQ5QY8pe3V(r(gU5I^ZtSRfsrxx3E5|ybN1P5 z%{kXx@R}=%v^zOyr}Ti-(ik8PH|>g^ypN z;ukpKzEakkk76vmgnS-6GriV-%6tO?H6_&+UEaJZTRmgYgp&&#$aHn+87G^bJ7b4@ zNv>ERwoR7owwk?7Qp&!^uqo6}Y^Y5vAyR(CG2OT<6FxLeOg^cbtqGCZ^=5ZN?kfHM z2szI>ak!e=hp1iSGV7jzfIS1A%JhTnf2bV04h|88OmXKTy$yI=(OkF)Mmc~)@UtZE z_#OS+X!v$wF~52M_B?tHCbL zXe4e~d20l8YXP`CuD1=+1A}azrlm`uspaN>Ag%=VUsnnKmHec(2cI(Ey-{1-^M+uy|D6+z;_s^08`(gdJKgVHI;ewc= zBfp*bSMDWX#KpTPt6{X^q=1TfRnnEi(5h>hp6{AJ9lIoT3JX3p(V-8#&w5>Nk9F97 zQ7_YmY28E;MOzo?@$V~eiw>}%BK59M1e9|d*T zdw$eP9FKp?gHnIbObYjAuQ4o0d06*vCNaK@MesKjOqqP=ZEfeC`@l86woBv(bysG8 z8tU?y|955{PvZ<5rtg(@IRNkLOBdk0=JY$G!h)ZR|KH57;%Y=FGY|jw)U^4p5_-r80vnl`0vT}wPY%G-X8w%%-h$K|9@SU zw7rXHPNL*6kpXZsz@21G-X=9Y?!pS}hS`zf5+MY(_ir+uUwI*ardHUn+(M_>I9ttP z?OVSYAhEXsQ$6g&BW9YJJ=OM7Y8O{QM9bus2V z%zkqi4rzG@kY6|K)tvC((=a8hacVAX{D@<`54*48;r)pj)Ks@WQTJ3>Mcd~NwfEc7 z3nwNh*q1`YY}Hd%Zm;VTdTBLlaimYo-I@SPq>*avVXANuAyvyspHGac<{*M^(wxUl{*0wH4fUVaPA{l!llxH13F7#Z zd8e}eCMm1|j5A`t=58hrU&0Ax2k)mhP@fHl0f2rK73@c&2c;*fUE-1ckeuBry!%nct_)5XxhqGlT=LE|A5KK2`XhdD*B%#IZ)_M5_{gAhW zczoSZ%Qjm%-y_7*t2$0-E<#QTRMELHzP9u-Mj6u*wfLO&HEcsS6X;D zTEM5>lY4ov-+B14F|aJ5O%If~hV3#4cxlM9=y=&BQR>TRmRVhh`FzPzm(4IS1$^T?yMDJY|`Oy9nNZe7!1#hqN8!S-evVoyB;||{B|EhTH zkgh1^`|)YZ3o6jcq*Fv}jb|=3qk#<#CQs}XiB3A{gvC?-K842>3cEL)%qyKAJb&?W zAM^W^!(c{u)?%^=_Kab*lFHWW=p_|lSxdo3Xv5)*Y zk9lofpZRX1ddNH}od}I%GOzPaQBd&fF4#PaBtk7~a0$y);NwOja$qtm=xDoofxH`3 zy8?;3J4aED4T30Uj#cjEp47WGRMxF{aW^ryTWS$k6$EH+Bd%e@Ugj`#Pm~A*n!SGV zm3GaLgGXMr1N}ll01{mS^&V?oebgQi0Y1!~LcQH(@y28dYO;a|4DoJ)q+*U30%8Ovk0kNnY(o(Xl zWgJt*8%{5E$HQ>3H(tyW0BK6OGd)94AUF;ZlX86AJ;W znVam==dnr#JF`uO;3e0>b%_#3gvZEisTC%i)?PiO*QnmHYse3Lkb>Sji7sXWUBOi@ zzH4Uht!GbK8{3f&@pq2~Gi6m?Wf=_RbGNe*`8t$zXA5D;!A9FR%HfVzy1@P98At8E zEOzTmZB+{}b-Ezj7#0SYjBOrgEFd$64TgT5?{+0Ae2;ie>)N#-TvW~t-;SI?W*cf| z39`{=Qma?RKr@=i&d7zZ13-Oxz?HRI;4xR$OJ17v*dOPm2ktD^Vu3>a`Wfej=Q_$P zUrySt?GA3woS*2!Pe2*`Zuf5lsLb<7ixRkXcBjEQH^>G~66^$<{Jh*Z(p>FzeIWy7 zFGVTPx1znD=dOp^umZ#upRQd8+>Zro6BCg}^p9a%<4w6swsOdJ7A0GL+C@%~LTD@u z+t)rw&}=7+r9mck{GXHtKY#-F)N6C%t}g^&mm+PaZ;3&rn@D!ai4jOVf$6Y<9CJd2X*%qiGfI z{C=Xw5~TY3#z}&h^5bFNwNO0_b2s1eJNewYxYADj=GSwDGDD~}+Pac;!L!psy{D#s ztFgO>I1`;Ja`jR3Hi0wtqcw^hrD`26_926=)L>TMt!sf2<#sK2xLg6ug)&~g=k3`o z8u`Un`Cn0)Z&YDe@S-9;xcV9g6pUYfvBYCkpHyDL>K zYlhH93BhdCWDf|7HLcINiI68e2XRwDs@B5z<$we;=TwmF9?Ci_j;l#_(S?w}>OK56 zjw^_2R(`*Swk2y*8Qa~?0?o^;#nOs5c`oKsQj!nkZp~t__J-Uz|MJwk(sz#c$`v>{ z=aApgo#-;gL}^8(%uI$mv|aR{RtG;6K$UJQuk54VcG1VZ#HjWGl+rsROY@lH1_(*k zH!VSl%X(2w0;N<3Y@UaUo7ov=%AXw-PT$J8og$NJaF4X~T2j)R_NZfdIIEO9w3NEb z9}bxYItAh(?DV9GJOk(H#vBXKGQ9z24mP3Vf>#?)TfD%l8cx@K+v z$B&o(W++&A$&vo|D>3b1y;O9Tz71^#*kYwgLr;(pXe@bq)%Qqp32?$JR5!)S=*$3f%)yI z6GM^nnmWW_-fOC)II$KdLc%|FHZm6-&HY*qc4|Um1*GoC_rVXu?)(W^qj#3 z8|U~bz-(^85Q+Wemvtjy=Y4db^?#Q3*d^yN)j&!&AIj=?mYOVAA}<5;>o+Hgw`*v> zvj{rpYs1M)_}vbY5hIDMwr0$w)F_`sUoIX7#tY$bv(AsS zsd&VP_agJ$vFi*9kk6YGjr8+M>|Vv&IEbP&hX55&#b%9`2T$#qga0ln4LM?PwIHyc z#WpQs)f85o7-IBYHO`lR6xqK_Xo8g96e>Wok}CxCZKrswGauiYnxN|*Lsq_>;thrQ zzKdau<&pGblMm%qLS=3Ys?o1$g7P4cTyy1?0{qg{dMRC`D$EI+-b@z8{{+7x<@V9L zZ+Z9r`3xMYdq;aZ9MXT2r|Bphw2WHDex-;%!kQ>$Fx!hUc)UFf_RCTCEC#<2#bar3 zDo(q4@(mZ)Hfc0TxV1D*)Q^BX3MM2pfMWwh$n!aA+yGh4fay?ISJ*N=< z+6_iHy;K)j5(Bfbi2!+cabjEBW7%9#i4-=`r3MmKLb>c$_1TrrTKFMU#dBQh;hD@*5n-hqPBz?5Y2f+bG zHg|^&Iz~@Fcue?=pYBowsw0Re#4xV_;a%%dzTfiQ__@`E6?y2K9QkD6yu`A(RH+V@ zqMl}?PrgeQVVMfMFNc#cvnqym9WNN9bE!&I$XA|pccD) zZ7C_cOSa}Q#CU$sn?3bEpp?Lnj)-2XKoB$_9?Ul99>r`N$vSTKdW#;|97PP@$cV?Q zf5+A(eD~gO@2D6zO8abq<-fOEy7G;txS$~||E#pI9{#yRY7S1g6$f4M6i5_YyWD3J z&oXSUd(piE;Ib4Nc59$We<}n4yJR459(VP{V z^}DP!sdE1t+nt(%4$dYTtJ&9fE2l6sG`2yMA>4e`*5$qjCq-s|*}mmn%;=1Fo+#NC z!%2KQP#iqzc63sUO~BR@M0Ai zIQ}e~ID`P&v_NaQUM4VBt*-`(sQb@VR@gvh&?Gap>)H8qe?|b(ffjzMoIr>yE<)Gz z=nFG#upD99%XKx+Vy{b#TLa4E+H;xRmv zR|p`vcP+@0#T~@UrrndiKLf!4Dri@dt7}C}^MB&XCb888on*0bbMfT;wQF9hBe>>1 zyt~4=n~@s(`PW3_z=7{TMzI|++~%Y0k{sQ0DI?9So;n?whhF_ZO7f9`A3--hs;A`T z^#u%I3$Y8qX9MQ}`^)&oKiRX*@N@kDpO)&@=6>YYkF4vuq^XPAJwBH>D;U^OH6Sq4 zo~j~273mYxlv9cZ1^%V9mW;}(Db?Uy4i%^&M*{sH=MSco1TfLwqW48Dx?maoz1w>bMBx^Xp{BdrazYsjiKDlEuS%3)eN+*a@eA)(Zby1%i+YYF9 z>(vA;!Ut@0ccKH7!p@h_=r5Et5j1vUhAzw7rxCW07NymOU3 zr>6@0;umkf{2_<=YZo-?IIh(mRo>}-_Lal=U-a>vUtDi1Y&~a6hy1wRn#mWijb#j+bQ6`ps#*aBQtbUa?bbxFT*ayWZOc24uzOuC}1Obw1yr9hCIi1*8f zEg==$u8BRIM{BnzF!9)W&R8zj_HrHr$O_#)c%>*t9v_ugB$3-ny?NL5C)g(Dbn-r} zNZGY8U#dUqmYlG5TUt%`> zqQ>-wXc!QZE9KHr^jlGuV*#~6@n9KBvz^H5>Y*iDYyPMKZ-p&rOkux4`qP8k^Oc2c z`25;qnUTB#bFqO(_TYw0*;M{Mtj*_RJS$4u)qY(A5~Ja#@t z{Ym9%6#3tSrZ5vLx7H5>hm#+5rhZG=<{dNbDn5_)A4yY@cFETS|L;KpOEyc2=^tw; zl`4#Z>}LPukH(IAy_@{=6%N3w>Feme|2^2$SwM$lahcSOE2>ExP?VB=4aOH6c1Fq- zO{ujz1Rj?C@4;psywvKvc^^%GXM2OPobdT95xnb~L2j8!f18ZAsPmWD-5wC3G^IJ+ zc;()ZR&TWHbj4vDtxfWJE_uSUx)-yuSff_IKT%FB{xu*v^<7;OTha81< z(Yxt7c^ob_%UaP{roT_PbM@+{zjdv1ygOGmyY%qyKwL^r+OqeXQJ87oYTH}wyKI&m z?15ss-Ce*2G?MNzrd0?;71;k=3470U^#{;)iGe4O&=Ds)+)b^0fChdKv~7$84t$7p zy%3|zY3H)HRGLF0_7d#miUuUEBf@jz-q;ZCAS9|^MqipGQ2LLbE7JN~ET)$T&ow7g ztEl-DWoV{aVrgK;kSuBsp7lhbf?XR^8Tl!@uNPS9wkU*8K81Fv*S2IO2^ zt4?_xxgcCgTFx%mU0HIL;&&4s(eunf=qSmCMW)z7p;yUN(*B%owORLoD7D%d#!$}? z&>!`3?9vpFclzBG<=bX2E@TX`&Ua&0BLK;6^UH|G$=IW@krGPTL-v7QO8s?}ZG?C; zkgQ%MG}XXp{brYsR{rhe#k<@Ty`ul8+_<~EVGixF>g?+|D>IjGX*wu-*!=lP%LtDy zp=>4)$wO*V9&HiO^%M18)KXssvFn>&Uw0&$ML4e2V3bwGo z(uqXDn;E&pLADtE`}9R%!YKe+ZWTV{@~*IBQh4XuLs@wNA61R^dbOIIh=_h}){6V4 zGojo+nlc|M`H9IQg&0pm@#v8_fzw{J43BaP8$n7eBzHp6Wsd=*fZ(L^?Ois+0D|q- zs7@0#koisD>(+VJWY6X7wGIU{c@i&xxJw1Apel9j3%2Gn%(29BXMB|rW_^I)^N-4-EdG=iLlC$ z(HD#HYJ6JA_qJuxsnS6GXu9_qtG>J&JbJ^Le(c#N!^(jaY5X!CyBt}e_M}1f?8V(* zWej7+eJptW6Uo#sppf_fq&({krswH#YQUJg)z}inE?M#xD7_j#;aeKEQ;w;yo~SsOf6LUdi+TKipO~@5u9CwQ;`D9(j{bBVD0@j zueB(j*|S&@Y};n9v$1`uxUsR5N~jAsaIoNlNf>S$>F?XjFHmyit_;fC-1tu7E}jW-Ij1W-B@&K;A| zc)2nGYs9^|A&?&;vP8WWUAnXc5@$WAve+vfVXa%-$OFW3hei)H{02axK^6DYm~P2! zbsPQ7Ntk1!g-{I<2oiDxLXc~VhQB$JuVV^WNtLPqvh&dQLWR=3wTkjqYClNT&J)iW z`XCSAGKMRvhzPMzGs}*Mf2kK05OkWP;RN%z&rO;ttx(T5;|lUdQ#-0T3@&zHcbHt! zo)SV%Q-*xi^GHor%Y9+$S03I+5fDj#f~{0mm9^+Jt+^F92vQ5O|69@AQq5!7i)b%~ zQ}(p%aKCLrCt@yB$*-*7<@-`qsn8RE>Ep;%UG~X)xj9F=uYa88d8J$-X57hI-P9by zGBh5OQ)-{y>r_lgOfCyZO`z5LF?$0ViT4~8+!TdmqeDpY43hdhyB5lDix}pSOKGbg zy&w2Wh9IJBrtr**jJZ`eE>O>HJSii+7ZHCU#M%&CMrjc}?rxN^=RbvU-)=r?4oO^T zp|B%owt65%&0wC*?M|#Gt_Gi^{4x3^pN2@C8?%%^=$?ue){9ahNrZIM zEWeYr4NlA?#$Rr7yFa)?0sXC+R585J-52_t0KfGZQ8&XM$W5e=`@VR;@ z_1L{Rl+=6^;V8wxGCDx2PIGlIvzff^m>uH3wAhvowrrZ5YYdW$V_iwvr48|RkmfPI zO&y4q6w#0mM(39?8iV%-j6PW%G^IMIo2=pFjH@{vbF3GCw6j5fGx-=k7s+1jd#r!3 zztm-%_*2PKH%GhT57PfqhSDXkG}zCx;AfU8CLP?iv4ky0MDst{mpsog2z2myUv1Kt zRLSBU%GEbAL(z9qwhT_V9uyMdG@80XA-EUpNebEe>Ru;(D-E+rbn)+WKRS!}kmUx< z|7Ij;9So(M?=!OTcq(S=n`1#IP8HMK&|QG7sy)@RO1SyQZy) z&rN->v-HV7*V_56F#8rSQQf84){xEP;=12#RL25c&K-I{WA!sj;?C5e%ISG7ArI_< zeFbpkqFjBe9VFI7JBzreX_>f@6XEV38rU5}JEWMKceMrfn5^=&>MeIzb8o$gX2is+0a>B$o|A>9&R5L%2 z0qAVuOSm!=X*vK##g@SaH2m;y1J`TG!qj#cg68VTtX|JVqFerQ>G$+(*nW>Cw?*9{ zk6*)VBOJPG6F}((qBCJc;AbrS8_GCy1};9=6wgdVy?%Tk5_h+8QEEb>jj_1w!-oW^ z0g-ziv*)F%$G z2jx4Bpa<{&1kd)D!Nw2UR7EpJV=%RnR=l{?!R9D$;_%XrB0?O0aJXI$g%MYh;g{2u zta(=AO8lgD9jQOW)N6)%@=~X5GMjm3K2#W9q#{-7awz?JBz!F_#+iu)Ui0Fq8|jZ8 z;e7$mcl=U%vZpAzy}54r!4*QsNiqA%Y21%}9^z!Iv?3J@Q%b#&LW}=mY=rSJ$8kfu zU40^_r2h%a@Q&J;?^~eG?MTX?;bK! zYUfX6J~uxoI2awZ)qko1{1bC$4pn+U5ptHQJxQ}gU0UXsYC^CL-wC5dcF1u`S0MpH z@O_WZZ5eH8Tde~?GbL?|j;+oWj3Ec}G#BbQ)TuLE!?B+(^?01N){47}K%hpAAZu{% z8O!wdkB_R0Et|<9z3sDfURbO?BmldCMi>09J6K<%z@*RfGcIf#_x|F>)#4=zrIa~M zWwbt@8wy~SzPP}T(!FfapzwDw=nfMmbcZrZ)}$B z9Uf8jmv3Yz4339j3+(Rs<(OEq$G(R$mynYA0LSma!`B5H*Rg?dmA@>({W$r zZ{`Ll9HXcBvOn;23Ik38Amr-UKM zgIq*^qf^Up@RnR>=IQus3FX;xD|!9pXUyWzww==dWnaaYOt`=YrO#g4@yHFkHl)TC z&hlpYm6`SW)+jBT-(`SbOT7XgB8{kd->enPUywUj0cEoSy;!f@6ThE*P7cB?7!{lS z)OR`vNr?c0fGmeG8 z6olt*Ie`XX*<5d?1jx<`(pwZYF;5X+6#nZ`UxHnnO@QUDK`ftPh`hIS|GW{4FU$N^ zb}>F7Qb^>*Zozn1i)>pm@fQ;qP^`|(V$SzN%g)24Pj2?B*7Z!ri|K_XC9is2X^`&3 zb;a+j4dMP@j20S}!PND(vMleNkUOUxw`HxBn#KObf)h;HWcp!3qa>mrz3BuA+( zM4O(^#5TK7%;zh)YM?Gk)jm`fw7qz5fX0P<$>A#@XXnhNcJ)m?m!euh`5p z1oKaA7s7pLBB14_B{of+VFaDzGA)mi9If!fo46&{$3G?h{B8`W)}_Hr1EQ| zYs>g^T8o(Rxx#Os7K=)Hv14CF@W7}tuCnQPH{wPZvzH$R3saQqwiFdr^Tzw<%C8QR z^@UR+4pYId{ZiR$pKdACmxdL!*YIzL?p$*UfQwacR9TZe80a4y;j@3WIpBYWf_xvIPi1EWKzhf<%eX&S%&~C$l zTmZcP`zm-}{d@h%K4y-Uy)Q57gl@}Tk3(=kD{}SOruUbGD}F!r-Yq7XS3KDg5B!}> z-Bp!dO&EU?mMCO=a9Gvja8(=w?R(en$zvaQtQ;q?qL0R$f@&)&0o!;FCbNbK*Quoy0Jjfti_=8#HPR8{(%{E3DG$48 zo>*k)km}kqQaT6ptv-S>+!O^q{ILRiC3hwTg8EhG84_3}AZP8>aKd&Y;Dl#o=Om(5 zhxBU4_nZ&6V2`~n5V}b%Tg;qtB`j!LeE7mc`fj;P+OoSI)T@g6kke2_jI>y7}pDl78U zcpf@a?6s-((FvL`iiBNWFWjzhX=uA!?C2pS3Olh)aHfE1no5T#7q97$!ni>vQ{E=uOWoq`i?#T<= z%TG7pqt9bA{fd6vi(^CPdbdU>=i(7D3US2ti2RP&V;M_=3)M=Bl)RM!GNNGaXc6SV zi^Dchliy^qHpV+zm%vC%5g>~Ok0lnrj@clOW}buAoUt-qh4Fr#R#_!N?#38I;y=lTT`Cm>gLi@%$!RT`7yAO)-0kd-;;^7q^h?r8#u8 zDfdQgAb@A6`D|>r=`$9GP{e)p@AkWVi&_{c{Js zQ%`Cy_6nnU)k&!`Fqg%cx;1t{la+{FsO<1$X(Y`LX9eT~w=3Fq$fv+;TqtACRoFJ= zMUf*WhPj{4Dxm12@b(rJW45!5O>A8^%25jB^{E6**t@e-+q_(;|B6x+tKXA9zy{r- zv2(#q)i{!p-&<;WXqNKNYVCA;3PlI{(rnz@(W~ap!ADpImz)LR^)=}y00LGpCswXK zx?8qtf8L3Q4e(Krp_-J&^HM+V4)p?857vnKAgZW_^W4A)eJ#O3pX1H__I+pcvSiuRV z+B}6AohOSW@y`cM>*lN*aR+rZ6FyM&n~S!%ITw ze{~g<&1^lz>c%`WTbcsSR59Cuw39of1H~r)LmY`DtsVO%Y)B=nIcC9AK|ISk_W{JO zXyUB#gC1@{*pky}8Jd!4Ns!Nm5A-*B=kCH~{p#FP{TI8B;+eemm zL1_bYRN5s$HL|p;b!~KjWh$8LaIW`+YF;}%<3c#AhdrUg4JHBJN0<1TRp%6NH6tq@ z(mT0l?L})DzN6Gyy5JcYLAA%K_uQQ@2?%@`%PrbHTbL7p5LR%t!V`63675?lDsrT$ z>rp(0;)mL+-eV*$@iIpU0@h0PFxE!8y`}RVUE^%yz{UxhAA6gZZL#IF7nJCfhV)hj zlX(1;eJ5=lgC^N?re@bw3-oLwxRj{skOiZRO~e$Pmp7A{?pvnfHBB-opSJ)jID4MG zqZmu4;7`e++<|+C1;rId?G9!z)nd^QDyQve=@wjdB%@lyhvaL`-P~%h)ASPQ#{RDZeHW-S^)hSnBGrH1@SM^6*o+Mp;uJ;ks$x<&`J7(6LOaTp zlxk|jd0u^>ADg~S^1_h%+Ai$ME$Lm~+4un$Hm;F}@7!V#`P>Q&Q@`r17p=kL)J|v* zASY*>Ump>N-pCGEAchz*GTi-(YhK|On(@_VyumK?jyds_+qBYS1!|~~A8GH^>!zwi zh1G-Irus85MGTvr<-oh)M)oJ%Mu=BS8LdcsD=CxET0QNJzvv}E;yfh~;UZ1IY50|N-!7l+7;4^LwZ z;cHrkuN{Ni?%v1wu-D+PFLU7C~ z0;+zBF++OByZ8IIZY1rSiMKF%6il}sMn(Jbv4nzLXV*ov*%k(+HVQQ_pDu9>iIwT{Uo5ubi_=fdPMbmxs0 z1gs5Fo~e}|fBBC8PPv&k1|H2dD3G-&&r<^gw*jCKm*mc{FjKJ99u!8n<=3tJ>l+dA zFw;0^WBC)Glc=JHJU zHg=AuE+8+vo;~`rvCzkcWy-AUbNr$699hLdWS+b%Q!g%6ym2|OVDH}bG!1LE&^)pqI0lM5rX(tpNuq!GXPmQvbQsFlb*EE z?g_BbrXE#^o!+-h_dI6hji=p3Ty>%67Dw@!5-5nzw47((M3zOH98>kM)xvyt6-HCB` ze5N7h>+3T6yIcZ=7OKk2mf^uKYr;EJhw6H}pvthc1hPmBposApcaiZ6T+AC2XScZ_#d(YXYA{(sW01;sqqRnXvXg>&~+OB~D8 zFH`ZO*JrlraMH>^vt)3;jz4(Z)UGn25>Emq@vxA2-(8*<2^2 z+3Qfai>$%K#XQIx?-=Mtn~;zTwfEuUpJIyqcftyqY62cnG%6u2)V8u4GKBA6OYWuA zE6rqzk85%VY&^Kf>33V{6JrO>J5ml^_ad#YBy7!VmbjN|%tfWa0jc2h+@rpFzy3$A z-X%p458k{%961e^-B>dU}P|VJEkiY@gLuaoZ$fq0LU||p4`B|*Fs9NrMwUwS?y%HtGDWFW1`I+92 zS`DJOrp62k)*KM3gttXDW!rPjJp7}?_)Ee?}ZpMKLNO4Pls?&{VX_#s=Di2hg zjvAr3wBV^{F3z&$L12fUFAXP#O77Ex&M3?Gs5QoYZaG(KoPq32CyR9c;;lcPxf1(E zvkfq7+&#wbcbj$3n?_0Qbngy6SC8dxH8Mq>#L z7um9!qU281{GtJnVO~~*%p`MijJ{d(7}PUZn3gmqGH6CzJOdVUo2#7O04r;I%k9SI zcD#BFE%R?Rd?5P7a;KX;y#6uA6$a4UFO_MO2yP`ESE{`1n;Q7m4b!kZp2|3&B>nj> z7$NkNPCDfC9+@Qz`az53W}X`>w@spYUiz>|JI$|<-x}SbA(d{G51orhZvPmbZNetD zQNzOXoPZ_D4rNin(9z2+bpW!y$cZ8Pn zI&g?bUA4@(K9nD2adRfv6vt8ZrZxF;)jdvIbrw59(3v~5*m1e*4t-6y}>2Is^?h~{K#G&(fP_B$u} zepm9H_5|=IIylD2D-9@?=Y4KB1Uva$Afh%#R9ik%x=biX=N2m_Vtgf?s6d#+$qj(H z7dLUOl-CyB5vG|{H%ws1mllun-W{>IcueI>C4HPY8B2aCoqJUwr{Bn2o2p6j@bGQ3 z1Vqv{Mp4zpGw}a<0qlpkz>c~U<7)Swe?K{RQ*lzmFDR6q7vk6d-ESdyw>>P(ORjql zozPVu0pt;!v2LYltspRgznR8~eVMkgrhnyJ`o0)S+aW7dVRSY%?X{{DV*#ruVf(JI zUuJeCk0ZqIspkVWXxW<)nnZ&+pLTO_Cjzw9C&sEbnv22zpbOz!Cr5cTZCngPi1_yW z!#}*kiq1VRRN8*Dt>L)MMUHUkGI9P!x$|)fFA%O+eHmIP`y)yXgwnaoswI8DE7@}# zXW4Lz@Sv&p8JGcA5}bjZUZl9>czoO@0_#HYMKMP*otZeKJvt`&z>=k;*sL+F-YOQu zTRxBs-Z{eq7eai_$?dDDh=n0D3fvJHkU|uX;J4P7}a>5hD5dwm-W^*}0 zhby`5xCekHYS)?D4O4xbN3H0kIwV-A#}N9}5X-MNq<#xiAfew85tlVnNA9R5s}@jv zJ;rGlFiC5X%w4iqOkiuJ=F3y*e7DIPNwa<9D&0JKBDqjuN{Vy{)Zg@x4n?dCrauik%PKV9U;&tgPe5 zleq{k@pFv<0A1jyPLM|cF8b>6I301#GRW(#Lwi;@7gf+cfMVN`$sII9#ofX*OUP4E zcG|qG=T>zSpF03uWo!}2fzR`;p}=I>MlBkzZLa1n6kl#pY1T_;G`mkYNzX>p^r5Y zH1N)j+YGnOMk@tAW#rqQ?5ocU9ViQAC+Qt`7reN9xdl_*=vLL0&Ehw`-2CiHT6}W7 zwN(8*hnEi>g=9(=_q6@~5qz`OYwUEU=Xvb^vFbR*$YIZ9$>RZGbiKz94!QVzBwwxg zlGj&9=S3<%ncl0T>;OqqIMBJm0hrZta21_r-ZU#c2rcJ)(=7?Kw;SU5Nz4XF7z_q- zOXAKFiMwC=0@!ye3+Ud+L+a3Ba$E2A-F(32>RDT1Jktk~~Z4f?5dE%nVs?Y`-c^Z-UHA!dhl{#b?q- zQlL$Tab}zz7{k~%{-Fqg?#Vw=kc#7~I9IKn{9x22l)6TbgDJge69mm}8}p_ZSm|de zJOQncZ=%8)mD#2Z zVs521J4e_XaY5E|+lVUc5-`|yab)(07C76`QF{H;D^0yqULy+26#7K&_r6vXnDp8) zB(b+;s=tr0w-+TUfdAYo1c6}?n?k>bRY3|Nm1+QpfU<|}OSlWwX4p;>VXup@Vaael zN0JGpOD(K@yl?Yjxn#AoDPorr;}!KqMC^J3`VO$GwMw|woppSKZMX<1;zxKL7Wca4U%^iC&=_-ON)NOk~v3LCZ4Moff;-8Qu%ah%)yD-cT zDJ`E^UkWx^kqNIlaK|ZUsD`?dTl-7&8v<49Nd^aC79D@yomv+_?Oy@7?eBnl5>#YD z7wY--2!ep~?6g*YIDC+F@_(565~w8CuI)N?vW^v%rIsem)^Q-S98#g)2J2YbtT~Zd zT26@b06di$s@=bHHse^+3k}G*9C*yq925YT2OS6mm73Y@Fdze5zduN+=nS% zdEgdS68zqTVdkDV;HF+j{P?8&-YcT%W3VT@{4k1JYMYSYjIZ5O*x*T*yDMB2yEO2G zxgEdX<{R&41{t6GGdp;{)*}CoRbjxZGaqIX&v*8z_Z>F2A0fx6FH$}PvH8E3Cz zRB*zXb2lzvo-&devd{)+TbCWAPL(@8Yf@Z9haZfbQe^1RTwSM|!aI|h@j<_L6tDSOZ`KnoQ z>ynXnBp-^t2)noC{)@e{g(8hqdwD(3Yxf7IjtDJvI2u>&y?qp}%zOFl;)eCsu72Il#5#gspD&*T+e^oLk&;jI>g9JBUgXV-I_*#R)i@=SHAR8L`Ae)-8>k@^R4&8^4Zj; ztb@sHQJV{@4$JS=A;UW*!5Ie~h}Hq>N20m{Pe!huylrk2`dyv)S1(;5`)i$pBIrT# zdrkC1O^BvXm9Y`=tv0h0O}P765T9cB_sS8whQ#m=#zw{MMuy&A6qhD|w|*1w=(4K&FVA>nUX{yz8-{}qaK z@y7($fuXmh=u@`PmlBUMux@?UB+o%jwCu&k!{ZsT3uj1W0JtB!?n1~1aa;&4i#Jtp!!5M$7?X+K^pZDAA(elAtZIRvTY$Bb<-x{Ntj_`+LaSN0PeqhSs<~ z@|(W+V8IopU7Qt@Ja$?AK(iY8?C$7if3IEt6SwucMV|}CCGYinh5sBvw0nn&`xxYt zRgMXlZt-zD^!_zXi2S)r)j=kugdG{_q=cBt<2n#uMqW@g=R;d>KDUHM)QzpTWPp#O zR)5q$c)QPmM<4#Uh?|z`C!BCBcJJT0(>+-{Mm0JN(Zld*2<5azBzD6g;3#GYdP-*Y z4|%!8nl-)4;#>J2>iZWmNLTlGxihSX{Y>$v$7b#(?2NvqY*`%Krb)OsYRfpr1;9rPXlJq$IXr-96yV-DZ6SD8?Mcp-@*{sro zHS{!%a8-lYgVArA)+8B4PpJsia!4iL)+%^6;cxDu<6@A zdo<$3&J!77aj;vf5_h^I7G8N3?5t+bOvNO^0&Rb`Y;kEwf64Y5={rI1#-rbsa(?ec zoXC>ghrciH(U(c?pY?g^Wx%lTxOiOlOYI2y&Onpe0&h)`QX451Q*G5Ozco;bDf|{0 zS4$^|d-{A1?{>{AE3c7`oku1^*j8;1+k0YT)o(tN7Bm$&7GqyLQFBtEXZuvJ;(!g1 zgWkJXncCQD%H6XnC?UG$`q#zaFJtecw;X2Q)>M3ViF2yl&U^QX{%h*ku%Of&cVK~k zRsCpWbnD1V#|&$SW>M-L)ZZ`ik6HG|T)v;Hhjf!)0t3$^Iqz&M zo{pe7Ih6d5ec*q?Vo*ZA=l$PyV{&WvJ`T)Zc{#zru;ey(hD zXwhE+Is*Z7ZU3Xat7mw0eCTkewJ8%B^$4UJezR6xh0>H?lO7X9hsKx;E04-mj~%Y4 zbRSv+x&!Nl?j=``ABrPc-<|$s7ja_Zu`5)v6Mb9u-gS_zq2oN9)q8yNwp#?$*h{bE z?~7<>%%Zsev%|gQTwaO#@3&sKAO7F8p*dBT_Go;KOC>lI#~ds@qxOSxwAO@jED18O z$x7j2CDRo?fc)0Lwq16#gtxEUi8*)`dHfv{QaHp-lOFMk*lN|Z(`)$PIN?ad)Il|J z?)|~iZ!7PFua<;+Mm4X12B@HgSeS5SV zi<8t=W8?$&C+V72TIP$UEl|Z1)wERc+)I*Ia>3`RXPNK?^3q-i!hhhKTA>hbHP+_@ zq%NsQ_l{b`M3L%ym;Nid2p&o7?s+WmIUgdiTXpFF*B?St=&8}upyAS`mn5XK@$PhVT_pQTOBO$4*k+M9&rtsu!e|v9FXGO>IA2C}iE+*1q-^GHvJ7>#-BOVnGsvQpA81>zK zQ^Atr+hmnP^OBBwfAL<<;oH`K7~__K4#J`83D6l}@*}c#Fh_r{M`r`YLSC4ns+a-@ zmm!mx*u51fzuxi4oe|vCV@cak#j`10@s__hIRTWOFSt1jC~C*w4~)R|E>oaruu7Rn zRhs_dk|h!2+<;jhRV9)JGAN)J0O@2#J+^v+&EP8eT+xIi!cVyKaQzdexM4AmDtG04 zzQaAsz(=uvY;%Yw7DblQcH?e6+KXpt);p!e38q$iNBOgxo1tgnMOi`aEtE#H3W;x; zxoAd1N6iLTogUd=K;{dx(F@OJXqJAjuDX}XJGy^)&9TLuFuY&oO%{e$!dNbqf zSXck_KD}r&TsS3ftAm-zOvaLBg(-0C$vs{Fe*SH|4Gke3jo;?ggvy#7Hw+VxO0Rze zO0lx(e2Uv(Me2IdPFRLEhJM1FHod1DUd#WJLZWt~?|gV2Zx&_b|e9Wt`f(@ZRvO zU3%W*cqT~X%4)=|P30p^+dj;>nBsXdhD+OEh0J9!EqEwd@gFe~v5Hc)FhNJbLI1k) zGUb-;fiPAP@4l2nR7yZ41LPx!#T3k9M?8N`d$(v|JhNPJ@71_bI-+n6!R$@C3@z4b z#Qk`8EN-6q^wcS2ty6TSHs5oL8Jb;&A3vGAj0v#ze2N1))NtY26)|$p#^FmlZ6X@e7#Tkv7fTzhR1bT?8oz!1q0$s|~r9FlMdVp%u56 zOudAQDmQgsZ}q@{e8%yjv<;SH(~75@Nhc(24RN4R2^73B^*XKDV{bYCa~*#iMoO)V zl!gX~s3a8K5yz*OD+VxPN&h($@nYo;ikW+nQI_w+Ci`1D5%T_WzD$`8IAxN(lpb~a z%Ze>|%U4(ordA$SMgSKX4mgmi1Zlg<5?}J!&S|<&cumdvR5iP74M4V|zk_b>X&EF^)^%F=fQEgt7#dgW@XranNlN z<8SVn?M9;*mAv1o7(xG63` z7EvCKF?L5Sus5WG00H3v6$9Ie#>tD9IC>QMOqS|Cz?wZ+N0PM}c=ujY{ekb@f_tA_(hvc`PtDwk zVGAj84D~K=nmx)9ZT}}m(;@}orb8QJAl*$&^=MJ9{*>b;Psk?+Zic_ezE}D+R$^Ej zK6ZtigY;K_qpi3-A89$75HR|5n_2<+oXW|Ju3^O={~B2?8oih}bKe%f89?*3YW@=_ zvd3AEKd}+cqOgWk7q&pKGnL*qQo!)GJE`@^3F)P zz2UJLjnen79Px<>vEMRNblBS_#ZoyYs$Q#lk}>1mfP@W&OY#9F4F;D_<$@vmc_2lx zlqptzQ4aicL;_*U?wVR>3N4e20`7Q@os%&N-Qicc889IQ_GBuK(r)0}jfiuG_v2AUTo< zDQ`hZX`y)k9sm!FN=+0Sv%z8!Py})%tOzS6pE4gcG+HIEE_H53P!+5c(V1gJ zjI2L~yJ0~LcLw5R=^`G}W|j$7=zWuqB9sew(S?asz@O6vVZ7x~8%$J%UZX^*g>2+H zKCgGpTn;q<0MEZWOezy?e>~!23W4=;{kk5%CH@#~?b0+4NsIELA`!L0zaE#v(S>f?_$(o$<$`M|#o45Ue170<%dgI(E#Z5HlgqoP!t#)`>N70+ zz{}gZG1F;u&H1r>4Y-qxT3*@saa>=Pg2YJWA|2Z(n@c~m9Cl>FvtKe&;iY{^rD|KK z?Q*@y-=0i+rC#1b2L>&o=EK6g9tpJpX-^)~&7#9aTRMQG7Co~;kW6u+>t0eB#Vu3q z2v}SHOHAzjldFIr9InELNrXu08DNdMyBIs&)Jvh!lDyJR5IY7Y!6>~CL35to_ zEOLBS?~aOI-nI|fIH+?Nr}$3%5dJBgG+Up!xa(nOug&qtp+(lXCB5|}gD(W53C~Zp zaRat$`=54gykWz?rUtcENN;_VsE*G^HHz6rBNsq(*;+Y1u*Y66InobJBbX+$)9x+n z1AIQ+E2Ut+M^}Fv&45%Pnc+*%`S;lkOGV}HG5pGNa*t^Z-SM&yejFd#;N+!n3$yB? zp$|$K_1lPMsn>wVz@w3eM@HBaf85&Zto~+a&P7(Q=k$Y9QJ8(oT(R6o&ZbY_de*e& zRKnic4}mVYQZqUG_ZI$myBW6E#a_D5*X-NRaha)@9%S`6MRNMK+(p0+rAaP{9MhK>|WLMk!9nLtwwQ*2`qmvS1czWLcJp8d+BMKXfzi zgAnYN3e(f$%5d*~!M}$aW`EM(Tx}<|F&&&!vxHaKOx?0+c7l#N&hxdBl65@*o}8w0 zreMVK+XtT7g$}AO5%ld{<|d_!cA6|F?cpqk5x~Bu+(|vqBq!n?h~xn25Bxx$m(QQVK16<||%SHTwhgEqW7r2o{%PtSKmSloq5#cd+E)A4- zN3U$bPBHxkk>6_czDY#6sNRT1iD)mx!yifNNc#Iz`IYJU?B)B#zGm2HBDrrdp_{P= z|D?Q%H@`u%KC(F_xXfWl&Tod&z9L#!kvr&xE8*?;3~~tHYzRoXN?q{%;)KL!xW^dZ z?52Lox}hN%QIlGvyb=M`%hbJcaRRCbeQx)4)WL=C$^IL$5ndJI+TR!8Ir>;FVa^e@ zMr;-jSu|Q~>D$;%Hg!2YP$lFJ_J@`lz5XzwxdXx4u2m12vALY2zaD34^Xv(%SaUm2 zPQSw>kB&13&XoqSQR^`lQuaC|f^`K2*(;H*1>^$~>C{3(PxK`(C5NbMip;^u%TVh* z*qh9we%ZR?kvSHTW-@vU6j|QMXUP97EB)%Ztd{}$)$JDEuG>~NvLOGqYCFUa?#6-& zyLc3dVwti$$E$bJssr4#D#GThm0`l^Xl+iwcI3Gl)re!Qn&o3M6mIzWRq!Tbp>gx~ z7k7m19*CN^u;tM}2_$uAgtg#74aaeI3I1sH_BPfkkclM0PAr{m*Y4pjOleORqc?aH zqQb0S$}oyRVJvy`{O+BoSGuTQa_1J(LPV4*4*q*Pu!efw{y?`PbhC zjHO^ZKF4&TfeXIF>M^Ecm5QMV*Jaad5otj+1os(b;hR@5eMBY$E}Ipc$MNN6TM)CY z3}gJaO|ZO3c(_7RkR1SDygBSB(4j>AwJKb0SI1}YOB`^b&1O|eNGt9n`T4?vqcFS> z>M@l7K~khCQ26@B7M4>U5r$h5aA=p^b!EYg5L8AuZu!HIMPr6kI;Q;mp+WIk%_QDD zr{xzL<4^NW3%uO-$?P3!w3GS=q{2EOp7iIysRy3T*;1-wrnRMK&`*y0%shvYoPxxa zzhX?h{qxXcBR6Y3Bi(n<3b@Vl0%X$c_`T6F^$0;i=|$Y}3*nL!ME#wSdYMJzE8DM0 zYI?e65Zu}vi$#wP2_^+O#7#uZ3MJ@kJ|;w1Celqt(|5>`(`9-Yyk0w1%20QP+<|w2 z>;zVwxJGnsLgg`|K-`$-GQ3EMqJ882)k+&|^+;-zY%Yd{5tgI!C>IX|fIaUAFKLbQ z!MO+Vz;PuM+%XZVV{{cQ}|V`I+S1r3y&DIbT=|d{)4pdzN;w zp3NWyWH!&rGuhVYGb?uC>b9B*w`t>hXN+hn3$$vC_bd~k^vdGRt11eRt_LvXNFl4| z^l}IhB#9!5D1M4MsR&eviM3)r4O)yhCeD)G;jRGxF6($vOnn$((O>Z(7gUa`dI=gR z=Kwg$NI1W$iAY=;uc>AglaalMjUyvCeu1sx&wZuMHl-R5W5Qn)hxNsWl${%jVQP`U zpzbP>y`kdRUfdS5I$$iBBKI8o;qhFusE>$|_XHD&M*f^n6_Vu(moD&~w;-?r+D!$9 zZ{Bjav4w#T5zwmIUA${8_@`a5Cj5JY%`FXQe?4|7lCl@1=Uq*v>0de2))YoQA(wBD z%^;I>`GUsBDTKMAO{*}O@A-XEAvA8vKsd(AiTJg9dh$$gCx5giZpCDlAKDu^lj_;% zdpR4r`dZpP6T#SB5x)gip&gyYhjnX)zZ;L;+?#ku-RqN;CMjJ~F5%LU^TBhG*gs&n z3e(;(^G->5lFRz1^d>F2C%Y@vz&)_h{;LMv`hxQ5Zk)i}y(p`d=wOdC(fi(~q|2Ci zRxuDK!Pz{LM!HSGUA3x3F{V81SWyNHC$160!8sIMx|qUHj>qcr0G#}6*WW#-UimOa zge{4FW2$=rF zq;4Mu-4lP+DnlN`>9V)kXvy~HRy74pDPTcMPx?naTle+4C(_0UyD-kgU;fk9K0rZQ24i#!a8Iy`t;FqlC-%tTVc#=VgTQBmMEJ*QmTJ%B`1H~WA^Y z=;jSMNs&{(y}KvMF3nvb&mXv$7%BcvyErIJwQ;G&Rj+J*Zo>*5){Fk3Pth*_{+v{V z5KarWpk>sL()`~<(eK7nY|ChTHf&iJq0?%MYM-77u1N~)VMyxre9xkxVJB@`i_8OJ$WrgWc^+dow-KB-PxdMGfJ#Vneq?G*XmBr!5%M7 zH~cGV0F2Qj!9=qT1!Xr`CMKWq&7as6xn z7eyOIW0P>uoy+?-l(BtQ&)hgH6si8>Cn3>RiBJE=8n=+AmRwF9uxSWE4F6T z$1IJ@dX^A{Y6gH*K!*{rRk-rIit5VcYpAc0!f|dGN;2HklqPoMc4$G|6aFcNuYhV0%$#T}%8o{Xd+|pb3tmD-_npd-iWT90SoCHecc=QA0{20wd zDm^D3z0etoPJ$wq#~^8S{4y#u9Gw&qHza9_Q;qs2RaK3ljBjx@0^~Bj*z(cE^{(l1 z-%mtTMuh|GKyrIEzocn*sD)=dbB64lTD{-JQV{>N%!Et3oPA>IwrnHaWua^xM*Xp% zano#Jc(^*%X>RO2F65gJ`s>{RzTBfrx-JvH+9(S%y0E+nA$1fVFsiL8iD$Oq?i?qEloXIbm)Pcg}y{$iI!HUzpC^t!_~7#%}G?_?xa{`=a?f* z2Weq9EWC=rc=R&AywX>e@@c!>hoa>ErqEQc!?|C;P=$$-vMVjeN}OF1eZNM zDDtzxb+l%FWaJouUl1;+^g_z);hTX7AIbBl&Z?~( z*o$Lr{N~2`23W^i1`!4yZ7chOkp7`)yN%x3QYf0h=TTmNoq<0IFWD-FlB1(0CD@z1 z%e|}gLBbfzB9oi89i#$<+fXRQ7B25K=%)l)g{*RAv8vKRq>0H~IoME5b_Q37N7T5u z)>Ubc-c_0u`{t$Fb<=E^&K&TMv)FS9tOak1!$GOY5eZIbqA1dtX-5Z&Lcl=cD0+>R zsKo;bjK5S#uxwt3$*i?r?u>vGs~v@3i5=U&MzU*Mat-)FU8&5-R;r$*$qkY)+PPdXGkiC zrCI|wh~MhU-Ct}scv;rRs5+`mcQbqMYbLzGt68q8oEcW^G!7e`^-T@*PR5(lTny`y z6TF9mW{sqEt0}KtZIZ}A&xR@MiV+B=h}lECgG0vY&AWt(Kd=ziMah23=+9q5A&#!)X@JeO zQfb0^uHJl|n5YYJZ~k677h_Og{sub##FvPzsb`M&o3oVCOjNzX7fYd=a|v2laFSGy z0hdLvd+H2)1mxfl67GV9xMP@fG1oB(?gT;h&JBvOEV`pX3ke#vLUop26os=|i+cBm z&$Q`V48;gogSVc>k-3piy7Brxo+YnN5rl;_md@@J7o4J`*+JQ(SPAH|KSv14G%C{g z_}s^mrf#1!STYVJ?_i7($l)LApQ$H2vLmqI?dP_w2MnD~}Ht{!VV*$zboL>Vlm(wRf5 zTAZ)dq^W6xi#R~W_ zP!g&gTIhch}HEvNa0q%^n7q-e$28czO58^1B!pj-ZgNBLthp*O7zjfQs zV|qBo44qggdNvL}{LvFNflWaR1G4av+?(qHRp#6451)WXTIz0WywlgP+%u$D(~&!|0|0JEjTr5@Q*kv;{!j=<1F z5aEX^mP+8{1dgBoVUb5yz^Imn8PP%!su~xhxokvQmV3%(zaec7pOZ-XzX+ep7QE+1 z9ju0LBx}NP(OB2>OTNGGCl91%wX!fB>Dh~B^1FYGJT7fF(N61lcaB-nUB*xb^H!Z3 z+G?_{?#zK=YUBK=1&r@^D4|j^`OtX+`m2@rGIu{;82TC;rdrhTp^qsC#}$?#-be@y5VBAkzGA;RWM=F27MtvbZiUnqG`Lemh} z9B1|iH<7-vK;AECeQJkLhPtXkYGcO8>nIm2o9WpT;eXcC0!y4V?4r-S{u^(xt zGW32Fn<$1@=RmPEDDo|&h z+z2+YjagkhkQ}pr_|FlN&rZH%LC@Uze^|#D+>l`a{rG!pBf{J%r&s(E)!tEv z=gLGKl`M(PZWc$Zj2`=PLL5U~h6fMl-W{g#0z~C-$NPB{ScHxLP1d*NUx<2X`5uU? zwLgD>dP?Q$8{X;rwEOsji!6)$dYxi+awaKE20Zx$V_@AZ*!+?jViu~XaK6G%BkHtu zhQi_t*8wLhbQv0Rbd-;N4)~|GoZZ;7Wo{_GtIbbE0PP2?Jwsvjh1K45*;jd9j)L=zMeK9L5Eg zrKYGzq+3ip{Ds1Mqb}by@=Q4Ehc0=V=~?{cX$D{OEu3P6%ra^mk6wAIP+NMqK!p|* zD{`~t!9gz_)k!8l&Q}Fb$@js-A)6PQZq67LVP!bjJC7`PWbovj{GT?b`ER_d%)=9qYke~e zzKlEOJ{YIqM?YKK+Mpcpyl%*h_8l9!2P(T$cy-su%vwXTm~8Vl-Wo94P}J~nc{gQ1 zid+CVuXrgUV}06tQcD=Cxzf|ROc$(1iJxdBTez$Q`>>4v04HF3chu8b#$aRr7q zRvJk=QR>50N(v=Vt@i_Pr}EEd>GNyeTS0=0IG;!5Vne}sV^~X~?u)1x| z5Le9e;{4-!)tHh{IAnWt88udm;yV_r>LYx&=7tq?s|iAOs_qvmlzfJPIL(|y4cXNt zS!NEkZq}zHImGChDrb8N!bsb5!Ug6&&hN72rZ8N{HV0|&RKKV-1390$Kt~WjJ;a40 z(rF)@q7{~3+Wl0UV}|1Ujb2YM>zp`|nN2WeV8LcOx!vWpxg-=9B}phsKFqnf+huI? z(&0uw-Ey`hZ_1}hbmRQ0n1VIO)^yvWcHeW^<;+1{-fCcjop@S4rCObrTK%E2->2+b z`FQr=1chpd#ON;icBLXONa^KgSS={wN~@iDa`>X3$BQ7sNCVORLfE(61Cqt=^3^1B z^R;y*R8mEWFii(PwAc!p*Q2B#zBRA5zTg8H#wKzIKwQAntNA-r_}jmT+V6|X3G(sd z(zsSV#uZfb(!f(wsq}pL52_i>GbW{F)sAp1#8)nz741YXZ%9{c+NY@W=%)-e)&EDU zA^KtIYd6Nv;S+`4tVch=yxx+u!BL#|_tkX~(hcM|f=YEoa;{IjeiI}mZpQdwIT zqG-o(NxH_dd(^nifmwup$F_^{9tR1X#V9KR_&>Q$rK|Z@P($LLW^Q7}7wgzze|4t4 zEC74mf!jLfQeL8a153N{8do4mPHR1jwqSRZv_^!{Fk+FZ-mUv305J?&8Ks|3P^ zjTb6>Glphod{q`6RG66wl>g|hjj(AgO2BZY7ZkJg0myyadf?baoUCd-hNH!lpu5j= zh!!CiW*2s-7h94|QB{G$-s-#dx8kX`ivgmhR;H7PI;8Yp`sp!1Fl!{iEvF^Zp5>Km zvwy)LK%>4xjVLU4-0@=tFdY~!35ZOcj!d#`ztYpiCtsi?b@@_1_j3;wMDZxbgOs8h zvI(tPS2K5-RZ3kZPDYzZm^g4bAHnFA750bJ0->@<)A(dlJ1l}XHOjt@b`Uq z{xiYdeTiM3qy`KP_>QF^ET?C$_60TWntj0lF|J> zIExBX(2GMr349WeKr$k5Nz%XAv&N4F6!%*6b#gs20)_uc+e=Yo7vXS`rwfaA^f$A= zT*O&Q)@wQq6)@?dZS-#TipwVX97a0pZ}jzW zzr-ISTR7ld>VMjbUnb0!jt`u3jcgp!%9^tYP9UcH2l*JFj6*k>`pEb%Di3J7G#W3t zo(e0|D5`CFjur4AKOv0)ta!gJ)(l!4$r_BYm08Rh!V_jT7}cf{V~e=XBNssG2%)}iK1Q?KU_eC{^h zsU4*UW#2kkdmfJO>Jp!LFyhHB<5RZ-ORX?tTy(mMXe;}4%I^2~-F!!+tLOA1m>U9V zIqGmhw*`U#uaLia?j0$adh`|w{^FqEV_&kR`V%%v0s0y@13HoDGOQZ8yA| zly-7erhH->?9=$H zIlMyBv)$1PnP&s(d2WNlT9ViZC?FW`A>6~1HxD5y!E`1d4PwZ}EdYU1IgeP_ zjP)ZgL#v&{$fA2$s*pE_hhNEAPLWXe^V8_Y<~Z!Jg{JBV*hG?Ed6t6ym3PsD&w==T z%FK2C>@_gD*>Zlkkr~f(--@VPwyF=J$0Q<>O%XAw%@9dMJ&GA_$r4deE4p?Juurv;&Mi>ba6T1L2B|IhxLL z5&hLEa=P>h{JAN*Z&AweNg${MO<`U78?&2FrJ2v6%i>g8PooEWlM%|O7r!$tLifO* z5;x>jSXv5cSLZW`iZBVCIl@6OlgI;9jUgYUtjEYU5F5QG^_v&W-byIA_mux!Qr6Va z#H!w4i?A6HWOBEC5tdUQU)>PgoK}!TtV7$wl}}AW()>7ayNS(??iHxLsVeYsxhd~WfM)7rLE2p)qJ1Krg^d_3JLWgd1qkDE# z7bCnUovL^I zuk=|WzGv5@PwAI3xc(sYV&{!=ug68vzMN)>r0W9$$8KWpnVV=Z3c*JCJlo(Ddagg7$bn z(V;z&fYhN>N(wwODlXt2m(25Mns-uFec(u{se=BjWBnGFIKBB7bf}`9NHN@D`i%0;|mvz<-oj^Z+~-!&lUHucCTYP%G1S3 zD1pOrqIkDnUPZ`Ep{3F^ug~9ZIAqt2z;Kh%GXB%2L_Jcf&H7_q;~hmNPm_E;Syc?N z8FGu}eT$M9Q~g9!fD@ev^{yiD&6O%*cyr5`PiFdJi|TpmdVW5p^3bzprXc6#?VBi? zn!0_mb=G^x63?bK{fbB@YTR~4aDbtrviWF*e@Wkn^Sv)411pOk96K9e^|iOMth;rw z{NNWCZTb6J&AG%6Pk6h;^Q`CgX2(WtlICpgPMx58aPWTK3`EK?K$ZSQxYRUb6)ws1U?v&12{*N|CfMc zab&^<(lVjgvDcpo1TedMgV7jqD4*I}@2hYqWRB9TLh_OkmO^}ogJYUX)G|vo)5Bgt zcG{@Y{UgyT0>V}$pjx8={Rg2$+GNxu)g2EN&j=!N&mw73e3 z5Qy)O-;s|SO(nTmNvB8cJ3ryCX7x(YltiEk^#|dR6%B~YVLG`k%}@W$u;$w zbaq;Frcgq?a42mqal3tyw#8xpB%?$jpYoqTdB+DQmo53qUqZ^Oxvcx-;QNr+BlZdT zajiprE<@CNud+C^ZnZ&_-(Qaj24^Ca%%zG`_j9b&o`KJ&>AE?zfJC zi$Y1-be#0KSjQ0~EA#G40w#XyILP~NwTq5SUcc~AUpaAmsp+1gH4~k*%~6Cu6cLXz z>A6~18B}0JREb7PSG%h;(Z)R0bI~dQCP*z%I!gEIgruMAIH{^vZ!D_{7_TH(E|0bo zWb(}!a0bKtCP~OZcT{##Xz`IQ#RxZZmEezuQQw;tGl}$T3Fj$uYg>^R@j-(y7O`U) zV)j!+Foqyh+|CoTT0Put1Cm9NywwqDpe(J9xql<8uVUc<0v4U~?ti@ibW3N913!_` zcQmdV>U6c)H8&&p&3sisIqNZ!0LznmiiEH7!2|LVcPg^Z2j}`|C&F+GWtnx;QQAwj z%s<|&fjcs3N33&FoYrmiQBEo%A_sa^ieh)KM^_E4vSD4StY$yfC4A-R<~b65@5qSk z0epQ4f1qFU6?O1Guhn-EC7MX}Pk-(~8P^AO@&yBeR=?6T^MIXCQh*P$)W4Il6Fpk7 zr;u6N4~fkrUQ<2aCs(}U%>bME!h+-HN`3mgfT5+TR<60oeG?IxW;p5iiYJH~r$O$5 z*42QP*BD!JrRFpO!^O_1yplczlvUeiOvz8A(i9|bZF}5kT zkmOy=)^ObTfM$OHoMo7_Kv)OxKYA<)h~qjKZJA*DbY})~wkn6+59LhyDa?~;5}yvl zevq3CvXNGPtqye{Nea|){!3N<7lsw8a9B6|qzKGE4pvU!&j9^Wq^lS@-p=ptAMHD* z5GvFtlHji?l$e(-9ZX4DHJYwvkQ@rzq8uZfQXx{st()!T<#2zEN{=a;qtRq^Ji{0_ z@$I!PpP_d6FdI3eE)UjG$1d@YLT2~j0ywHm8oglCf=t#vBAqIOtqJsURlOx7kWF%z zhg@vjFF7xFmI#{Lm1FtlWVUF0Qxy6b-0WD~X23F})3Glghnf;}>zQP{{Fo*dZSi|D z-vP(>@A>kJeD!rYIV2R8{P|38`s9D^mA2kOp&#wamxQWU#ghLFO{U#cDKN+jMvW4m z+2(hSY1)>2zcQmRZjT0_f--*d%eHAz;9cbB!8*3k+f;9ZdgWqQlZ9}hw4=7CjxT)W zazQ@e4;TT~GrLFc(7a3|_?h6p!vg4+>LK$@p_T&9>*f8K+8icPo?Y8Ne($_VhhIAN z33s1UZR$~m^A_(a53-VduwIw%nLyeA!$ozFhXe=Q!>i0pBvy~V7RRS6{@T zCEl~#g$Q|?TG}s)1eulKrzA*A&Z@}2EFE2+{|hPb5+>VJ6BdS2*Xr=l02>ytVD3Y$ zofkRE3wnvd^2!pGERbGz=E9_y?PKu0?$l(R^HNK{lW+g|KFKouSieNaR_^a%w#H-{ zlL8Ek$CpzX2U=`gyuRP?7u^-!>DJOuD;j9amiK?1Juhz|h5tc{R0QSeJ+|PF<+JfB z5wdOyMJD+40lnjDWWVUV&|(rXxEU>9AJK`{-ovK#>;9yE)muynZt_z^@N$qClc3K< zCes@H#a5yXPH{6<>2l~|n~P;-_nRFBy?aXTcd9%5gA0gOpP7ZR-udrtEB!u75BWxR zN~;c#2agRpM~W@R6UO&?qF?Q$lp2cul;5=LbC&AuYevG6mD4?K_5vop%Sq7Wqj41{ z_vgdHm_Ovrt;T^9-9mF8_5lQJ-ZgCmcCIG(P=2M7N{$Odqok^cW%pwx6CR9&95{5=;RlINAnb!1ZH zsBV!D-2X0ykObag0#&VCSAYr+SE_VKN`4tbN>bjbZyn2z058|8YTO#F41OXzW4(&GqMW@p6@%zAJE(rPX|G?scv2c=y)>f~J|@8S3ddBx}hdo7VjC$z1a_P+7r;zB|=MvW@>>0)m<+a)oiN z_Kloeb0XE3LoznIg3*;@Dq5bylsB1w)zG2#Au=y%51Ihwsd_>kK;980U)X`KY=PDD zCrC?}Su^x%KwH)gvqj)ar&Y25gS2ZXOpk@wH2VTh*JGh|?~M9hdwCIH64bVh0R=cu zpo}pGlw5T@^elyClz2q)Osep2rBDLyP(n?W=A&AJ^-eSDp7qLJRbTgCC7Cc}%z6ch z!#fVje@-HqHq092j7)qr8Axujq=iYZGl|UYl;v0R>M9biznWl)1gXtDnKCpW@`)48 zW?I6NYuk&VJ!aDBmaS{D-t)Ol`#gt%xQ-ihs@Wo?=jWXW8)fLy;jgEyr06Y@7D$a# zxoXiyYE%L|LoQ1}Bpz<^TknIDQd3>(RHIk6ZXS;;r9D9tsQ<&%zsDt&Z*SoEnd!_p z9ZRPyvAi&iCDTbobMlhNJ~NfpF?AZ7yvwL1m700KAxvY1ib~~7YF?PKvhsrFcvqN2 zBvWC^6!AuYNM2F_QNYdSyE~uX@B8}xr!N&Zy!T$~SXIuF{ zNtyNPrP#Hgr18-wJo>OrytsaOnf%kVWvC{GLJ@BLHJlJCA3N6u>yo!`3>mM4Ta;Ju zMYss`&}5&4l`u!2QJdouoD8gh^7;<@jVinDiL6ZkW4E=&?e{2fX|(HY5A?QgCJ^uQ z@oV#j@&3ehKAoFnrxxUzta}il*44|kXebZS(AvH>_dOVt~A|+#ZK)}zR-coN2b1!%kJsM zwTqB=CZ*C2z#NuvL6A-9;t193R|v1mA;^Ur7s?Zha&{Pp5yhhKlxF-jiXplxk9`g- zp!j>gy3b1ovU5t)fH0#C*i0dS0G)K*g&4 zJ)Y(RMM^6r76KKX>_K=E>wkZl;SBJRF9l#Jltze|qt!sSlH;DVPqAu;i-~F+l z1Y#b#H3kN!+f`)6QQ}a5qWZJ3PpQXXte5cXJ#9f{N*~}THKp&4TKq@Q!g@(Def*Li zeYf}8sOa4RExRJcQDJe`38-Xt%%5*O-Da|-XIwBN;n+owCClZ1f%C=^uj;+;zG%-tJpQk~;^;7|{n4mEUu_RcI(G z?g)<)>!B~x@jIag9cgKBY_p?h3^t5dT3f=Kfnjp|=6Z}9R2XHY^r!Ksi*j%*ZT#s4 z()1#;LUC7{vq(!|b-55TPIZnu0mu+ckKOCcR}FT~;n_QxchA#@twqUABK1~H!CLB@fiHfQbZ~>2v$H91HS8Pkue%8Ny)KgIXS4n zndmkGq%g{=jrA8C#c`FS3H@Ml`J%eqJpEy}gZOoSZCuaYYMRHHqTD-i4qyAZ z)_tE(ue;;;$XD6#FOg&HicBqVhApmGvE8p7xxuA&-^+~>nMGe00b6lM?rcd3&AtR@ zA!^RC!NOgvyxh;GsCaF|FGO7;=M{ zcbW1q5U%1DS6r>!udIBm3I5mYSV^d8PwkH3BPF_n8>Ee4dM9)g(LwG?gU;}JHjP{I z)52FEWPi8bRas)q%g!Ti03P4GKl%bC%-$aQ^_UG{OGvDBA~_DG)HYY7x4^NIL!JEG z1$s2IVC3C^JN9oQQoABAxUZ=#Z%}BaUX9^_aI;;6>4!~jE+Utr06EKD*+zR7f1~>^ zVf*X=EusJCB8`}S6Se?*E5E2t7}n8n4h0W&d-VkpmT|m-@_~{0G-Q@tZ`bzmC#0RE z4tY7*AwPRDB!`G#`=ac&#Tj1f$rzvmaWXa=HTLP{h!IOQyFZB3aph~g{b|RDe?2I{ z)lWqJd0yfE@|S$rY+TMkoat&uESnS!kDi+xVIN?vB)0xGii*K1A%YSmyKsqBh6>9o^b-*=NILCv_>1>&d&Nqc}L7bzSIneDvc zQ=1Poj5(6et2=W!GkdS-;Db8~!DNXt<^+eFe& zdKNq_kU4gQZrrHU8nh8)`t(5>nC$q_HB&R~FU|SxPlcN0?@h3@Zb^udo{a~W5^Phv zoy`alSMTz-T%UUo=qO$+sf{~58eW+2@&Mbn;4(&O-WHs1$HdmHA|-Usj@cnzDit?1 z%SQ<$qiw?)emtmA6<@CGX1D33k7S0`*zUa75W0kaj_IEME4-oA;t1wubEwO0r>J?+7tj2t zG%Fx%+syLtTAUI}7u)jJV^E|Paxre3hR5PiW?S5&yYAQ7f1Z@8hM~sj6<<-QnB3HJw z+--bu@pZ~qEl(nDl#q$InjSw1+- zcpEZ&nT#}=_S_rLI)JWfWE(SK#7*@nAfgge-6jUm&JA$0blxE!kv1obazWQO!!Iu7 z@T`E!zbGX0F>3!KT-ZXkCA45dfzL2~mb2PM+B*m<>_|h4|KE<^3Qpg!$V;e%t0%t2 zbQi?rs`wt9#R^hi`#I66PJKHE4Gjsi{Wjb{rB;>$b))|}&_4zj;5Xo0^}>n>EffJ( zO`5CnfVR-D`}?^-%uavO(-1L&2GlL|BK<0^J`M{z4ztX)k1zB6`r!Uopa5lTzRXce zE`#aMU&L7~{S&{gZh?#PjKc4joqf^h5}MtaV`@RcHJ=LA_a%tWyEfx7vJ{&Jt&MYK zAJw`H9KNqs&ySv+JbKfs_Y2YlH8*WZR`D(^_I*~9S@VskeSsEsvyZNH-Hq@6n=)w6 z4Q+idyTErz~FSAW9@@i4NyKiwN<6&ttRyWu9 zKlgiq#9DsVoOmRf&+VRjsFuzU6NXR0l_=gqcThAfA$#Yaav57|U(ydC)4*XSWPwjI z=d5vX1sGU2$1%}o=;Y*MH27Z@;zBcmE1asZx+EQ%Z@77;0>1w9Vm2)-WTsXv!uA6R zaAMbWf^2fF?toeb;y^$T!74S49M*Tl=yX#u;i7tT^C&cx)=qwNr=ex7K_Xake}heq zmp|qU6GcNxs&VChd+nf_-BUoL#}F%~4cYR;P~xBBKb`N`5jkHTw3(T>yaN?oRI7;x zubxauEKZJ-P>&HHLJONCOn>J~G&`O(XUsJH9J2|8VnDg6lV*gy19T_=CV#~J^%O}o zOE1KEE8wQysn8Dh2wg5NiwcytZujgigd3ctq^40viD2b}UM;Pf9b~5hT z5#!pur&VRe8DKL?G;~kh`-=+)H{<@=`QS?5&nw&BsAb=mW~U~uw|!-I&!}5$Yh&2N z4!&ZLK?&F8{qs@6u1-EmM}Yn z#6^vuJ;UJBLe?+>9fM;$jdmbYJ0=)v&lJ_mO-)EdJKxkdu764!$)ot( zSSO>K+kx9ANZWNS;kaQ=j~wC8O`LA*{W9cA)Nfwzc0iGeI8N6(>wM4Pn+KQfA3 z-?h78chRWzj5<#;lqk+v5zinaOHpsfUf6w$k&*4)Sesz>+BA!d5On6pS=9PthU>%Uv-Lkauu(ZOv=tQJd z@JTz{R?}N!zdjGN{hkuy{^+yR@E_Q$i`RNDe{y-Cz%YltcYId0uNy<`79?CZ!MSSL zd0VNLR?7ptsmGi5m3_QIO( z3lqKiw%K?Pp;xMu4k|uK-_t%O{67u3>-+9^rRt|#JxqKJ(@AqnvMN%=z~T~#vn^vu z%R|l0KxA4nXsBjV z@#2}49a6ONOR0u%`RVe6nw4w4%`NhaNef(dNAj;YTH{YO*vhkqfLMcMvtlObL<)uH zNW`i{Lxd?gqE_`sq=LTOKU=O54a+%nM&ydz1qmCDC2e|h{g3gWc(#HAC?zaEu9AmF z8dd&AFJ#IU6iM?yEzyzQ1)Y0eRlMM?Tx11=NMPyb`v9Peh}Dc%V;QdZC<={|9b(S3 zkncBYUBHtpi5r3@f`mnEBT{hS6r%2`!RA3syNWOT1RLX zhlF&%9f7!<4K6T^tB~QC!7a-`RYSd_t!n)f7pSAEkeUoF&a?y)kG@{0HI6s$A{^Ej z55DlDX$lRHW!dU~5@B#Zifp@kQ8ajjc=R?3br68X*8F(lxD8OCP+r=KNu&Zr{l-i+ zp}9&eP)nsWuwBCE%E?Vb=EuyLd*Skq6%R@1;hOXX%61gAOkz)*j4IVEX&oMW5S_<0 zC7G03VnyWRcQgV)e6n=d2PAJ;>MrfH&tN|! z^^1`AiLrK6@Yiu&@ExO7FgLPbb$VlJGo27d~{eQ%Pt9|b`-}$|p=g@Bq zV>OE|&;l;PJwOPH5O&qZVMM-Aj7~)%?*@vpz*PzT<{Xff zJ~0q)5nD{7J-Jb#Nf?4zT+XDKMtB`(+o7grw8Nk!v)Q>pwL!pISo7r@EF~;7>fkc@ zDw!rBVMRNcaw9%mkCSO9rx;7%2(TZ1~?Su<}pIek1^^e-hS8TVy}Bjq0bOha!H>_TWGe0phUZc z$QVE)tr+SS`XIV>uh_-j`P1^*k?2DW<4)%!KY6FTs+oLMf**-5(nycA!g*V$MMrmB zVJy7mcH{w!%XbY!hJ#aov{WeAE_|fX*HwM6B=GslA89(T;`Ihz&WZ1AJLewWcq@(* zy=^;5DZ{!hf$W=;59~{r<1h?wrjY9^-L!VZCbo-FDY&$Mrg{St(4BKC_ON_hOpRY> z4L?3GiU7hXLGaL;U4+wvl=s3LaUmx``A7D*v?obph0;mcZ6F6OW>xTXvccqp7^A_ z%)TbcuFMne}!F9(shaSC{!f5 zuVi7!-Se9Gxo{ax#s?eRTjB@hvhjWET`?kbkAC#~nOUun{NIdqp2acU>fY)x9j8YQ z>i{w?G5MW-|B6xILzWBeh%?pl;?+g`Bv&ON_#SF-KCNQorel-Mg5ke`sh!f@ok?n0 z&(GZxZjWIugZZ``{zNX%aVEh*>(R%4X$3pX(%m5;p%Omb)9w;zLb0^pT8l|y^V8Yy=zcVrZ8kV@-HM5^ki)ihr!i4B zndF?Xvbt-^vh&89Pu@iCDxG=YL`tUE<2ngHGF#o?aS80^wwL&#_QbhsH%a!fovx0F zKi^U;;we!E9$+cR{;9^9>mVWTCME5{)mJo*zbVNa$$lAMu;k=R0GImrqL7t+IG`%; zNQxU9(@*vG1;x02=2Tl|@0hQp)isluS*jDwl-V6L_~*cDRMM3-i7y?En%|F$bBb2@ z^=PGkhvQqREF@v{As(*o`m*v3hj0 zkM6vWqCUSY=>J+gCEZYas=i(=OF?qFXGWB1%>8pSDsm5)0iB|4Ad^$y=nQ%YnR4|! z;kp&6Wz^IJFOPUHDF-#thN!xw#N06tb0dZrc5)#It>ViplVb}b5sR$+_@^Pp0OSh$ zX*FE1QS48h(h^`18eFd9%F)vmcnYk4b$q#R^m`fAD@JcF33ac{8gxryKW%vdzXacE zOqRqdN*Wv2#@OZUi+taxc|4Q;tV5oAke*r=-Z;-3dGk-^Nb#zJ+VmFtdz9(OTfsux zUvs}onon-79<0iZkks%wqhfedS^VumYoj5DH44q(KsjT}R?}-aRJ%&_)BtnH9gRyl zX9yCK-+XsUMmaR)Di26xO7-LNc~#Vn4Apxk+BhN7^<;ic82$*pIwb zW<@&k@)5HDce;g5g=Kl|3C|ApWQs!?3PXS*x4ZTQn(z{2DgmK)L=1Z=vp*6j76J9- zqNb0EA^RRKz;S#t@lm}XS3Fx3PA2N+1Ac<;>4c%(3)-l-+gUOiL8g*TJ!(xnS^{q= z?O~j=*y;xu1s3w0!!nsp({qwS6eP|34QFKgX-&fWWIqCF?ObFaMym4Yw0%cO`g`1|%ZCG$5=lJl=mhTg$JO^y?)Vg3e4Iq|3NhQ?ep6cSs9U>twPNF{S{$^{ zaG&8kwJ_Sd52VT|*78Z04YfJPC>&Uedw5h_syt^wa}8O1+I}JPMOyz3w6Q6!Tjq?B zC+R7YsA{E2>l{FUQw7;4oZvnE09ee6YWUB|IUbl1WRTc{!?yiaODH6lo4)n$aFfxzuBQ4yjVA-7gBD+A3(I-ZnBT1g7 zrYE3PpILf!OF1N~>QHBSWSBeqyOWzto`8}MN+45I)+An{bG^tqmH0sd3h_%)0 zbPMe)-#CEb(=gE-BdH;e4(uc9j&Fk!*+ZJrmVRsf5U+&v)CJR2X;EuOlov8X9brUN zerWx&2ti-}s1Z8n6)2u}J}fuai!zFyBJmlcPm0`#O`7))wSB{KEkLNAK*>&sJ!oK+3{rj(2J0%!Grp z{|6|6m5+5C&+pp*O1XAleFa~b#i1qr(Q?sR%l-&H5F;L(x0(SBlw((v+yqa?OpvqM znrLvP$Ya74HFl?G=lC4b&vn$AoAc=gS?X-lBrRz>-KKr-@y9nVB-LNNNE%jHG2(DH zBZ_hmTXA`ZKQ`=2(j#6#{k)u2ND}kpgS(T$=@EAPm_l4dMgLA=8O$=HQN(POi;X;X z^<>NQ1ChnBH54@*L|6x&qN}cyZ(dG;DQtN&3I^wDB^&K^T9W5fm)3dYMM<~omf^h5 zqU0^ED!T@)_jIN%*szXm=jluFqtSL%qf2rJ0gV#YCdGm9ghp{q@v6L7jWTs78MK+{ z8eg4>Y99?>N{pjfbg-L>)$@s`>H{+gnx8qK1sGDtuw9T+xC6DH03+1N?yqQ^#wl}n+Rru1o2_<6^=3&YP@1BdBxuhT z@^v2$83<9*ah7Xf3vBP{Z+knb7XC&m($~zo0GCmP*N9enww}O*#glcr7atw*-+T-Y z-Xihk_z$}^WKTSrv5k4;YhU}VlA!>g?xU7BvSq*UwYzpcVAEmChhJj853l&d;JY`! zzM)ZurcxRkL&hzWy|YFC*fIGvpPTcD=vG`FXMa8;`!j&`iPzRuk~DD9@W2c=W}{mI{Z$ zJw{oJ@#6fLDC4BotEUpYR>%PR2(hXEIrxb8?&hYeKG_J2q3YMOK47f&>Mh(o zu5@;1AV+oNtXoQ*pM3bK^GsG;ORjrT@7L3~yHueEm9hD?w$2ZO*$ISU^1J}(6281C zSse`XUc!yi>lE7AV}TnJiA4p~Rchr63NBOHiF z-jYs^(Dk4BEbnJoV_3IW#ZfVk^v>1yU^@>i^zz<&D7y%UyCPr6Wy+O5E&iCsu-Hjb z@xZIkKD=}#IvZpr!fa&!B?-U3<83)sLn(BzkQ4mxZGnrLk-=go6)gMxsc^!H*^L}T z!vYdD|2*boQo^u4!4}p}YC6QQ#2n{T;SRUo{^zp~b1PaW=r-klzw-Cz8;)fF;6NX#z}W76Rae{nrs>!!745igB?C?9XnFoKn`1_PwV@N+RlZ#UEK*C zRMow0x^PMb5W*4%ML5!)ke01sl^$5+AU0nA zk-6h1r}FextHZm9&sS@OQ%-Wh*NNw?aG?qZxl_FIA{&n~B~*J6)FQJiqjeh&YvXSg zdL?lmb^HYyZ{6LZm$DF48%Bm=^=03V5r3UQ)_n(jk%$71NFCD`Sd+!4!Sk`wsBrsH zKW8atE%K02`3x!LCap^M`1Q4W*yH74NB_2|Di0CbK2Ns$p~3D=#}{^g5egz(I)tQEup8@q$XHFAKJ)+X|y9Tb@mJ4 zci(@GxZ%e`5sn@gte>$po>|5$_&# zCjjW^NEoAmU*gu_Utt*HbZ`YhEr=M_7RP}r8Tu}LmhOXYLsK^;<6%kq-&SCfCyl7j ze}oloddWx|`Gnaw8zlwd1xxR>_PkAlJ=5K!S##(TMq65yoiUfo!)S`;~fR!b{E9*32=3Ff&i>r9pd(L}$X69Vq720w<#47(scT zPQ$AS`Ibhc<0V-0jssE6m*#(&i_M(5X6ock*)^Q=$My)({H5|opG`+L2n8)a%-_zS zIG;Y8@KO%#JXlm%fI_1(JB?`N@#^`4i)=d4EP1|`m%E^^V}kEBH_!l~h znj8)y$%37v2;euYU@@`+cuuB>%UGf$|~ zX+Yg5l};pmaO72Qa_$q`_3AYJZU2pGai{*Bv~z~^vRUHJW6rcqVTRoe)i1u+r(wj1 z93!4Fc1CxsN)zA|a@NX!X1dTXK^y=s*b@^*3rd82z+=Q6^M3*GbPN|XmKg+8F)+`N zP#sfiB>$`#Bgid$$z5%B%(P)>ZHNg2<6Nl5y$=!QwnrjZJ=Q#zu3ZfwiAFN|p6Qj) zL3_(r+(6wxv*AcLws|Y^it~9B=3YfD7Ap5W`ZvYxOx$bz(Z3^W_t`z`P}}zAPJKU% zZ+gx#FGp0fv!2)+R#>#$8upl*F90n*)zs9KtNpRcgXc2LHBH}9LB$2?t7{m-;imi? zVQ9JXeB$*8!ad^=$AM zM+Il~`7V-OhQ8Evf8uF$(sS+I0%MEo#=_8wg{sADwsG4TjI+RUDK5O?yhvtl)#Ey> zJhWUPn}o+Cfv*=zmbTICCe$AvrR<805BnrDBqaX7GG7Un)XfWQ(?$S6 zgsG&u-MlU#4*Q`;8&oic(_t4$8x9N;+En*ufa9FT%QQ9PPd$b6xiqaZOh)L$*R6|W z0Y09j@8YX))}<+7^|9%hiG~p zuclP+AtCucInEv{e|4U;!ycb`TtNxOEyk5UxQ(2_1)eeNC{5ajGBGq2ms8E0^KKW| zFXf?a+^r*J{2zDq7o378`Ua6wXQjb*CIIRAQoi-)NVl7$Mnn|`dAN_BEL8^mDJZN$;p3cmMAP5e3b1Q17VN>V^ayM{XLME4?Cu?_(xPF*M zog+A(G|I(xEh9C5Z%mh}HT!F;Vh6u#fSwDrGK4$=XB*`vmxX}h13V7lK&RqKL*yev zym+uU`*~v3xCyQxORdQb(4Aim_*Y3o)pZt3;mnSlV~nyGdz&lO?4S>e{t5!Sw#3so zMBAUEyN>j9ob^Y`zwixID2ea;juVty+iu6H2co-I*#|i@fMxIp`Wu~W+Gy?h^4?f> z{g%uN5o67s6l387%=jR|KKgKGs=%D>O>9IQr5xKabM&T@Zq+&5{JNjtk=0`&UVH49 zL6|NA<-#_UZeM5L{`h7*?=gW@5~YQo8Q;^XYiyV3c-!z6s|OT2w>_^IW^*RhrLoO! zl7alubYrGg^M}L2{ntt7NJr=y&k+VUfK5QxYmDBUi$y^Nr}!1avnZ@4pB9&xw2SCg zCBnPNQ8Z%F)!EsyRuK=B{`L5F5zXzMn={Q(;1}X7%p=|Gkf%r|Hm3DG&^oj^Zrq*? z5ri5#ZH$jeLNR%?6%W!{Dv`wO(nn_DFA^uSILRWvE)7@0Zr^~|Ca4f4^l9kSvE+~t z5l`FfjW8`A81Z|qA9yFZouQWghRKRb5RiFSB2Aq#S_`i(i2h{rs#d+EPICG^szoo4 zF*T>&MjFFavYmXv$O|ru-yodTvE~9XUQeAr!05_W-Pyebkld+Wt+}F@H`n|{*GJ@) zRM>rO2zZ1sVt;U!t9~Vj?^(llMgzt1LafX5{C|eKt7pWIwwRh;mWu{#|91W)=-bV~ zXejVjL*N`+zNLRJwaB7nVATnwJT-sk_d|87?8>C3%})+66U)`QmuGy$e~87R`lQ6O zS41x46pK-0Dd6KK(9@(pE`bBtT04ZTFd~{#|3kV@344fkz=G93{ z!)HF!(QNfzIy!EtI&qNcrt9O349J&sNHOJ#7*Si*i#uqp(8g{n@9rLS1jV~%kLJz_&21q7NeYMH6?i;E;Wlj`K_w*hrE)b;4s zp=JbzB-V^Ah8ujD9#JyRq$yT#}T|sX;XCKCdwnjemCgs|zM^A85xq>?N#}xLn+z#7K z{N*I4HQgYqwu1rhc33KiFnfJDDqN@W=tWnB)|Mc{>lAL|-i*2C)>RFhyVjQQdKCA5 z=eyGc0&z{@NQc1rmm_5(H_%24x)QRcj^fiH2>|CV|3^fQJMX_O)j^SUlLrWMdg?!O zec~FUJA|DKiXy`Cr3Fe`W!+lo*T26OUOlZRRlyl3EGsf@N!P}sWkW;=T3+FCHgITy zcg1e2w`o&?YcE%_N$xR)ngffV9FYiRH;+MSh%&Xt>WT~D`SL&5|2jl)_8-%re6g{; z6)NSirQRA&b>pz$p<%VvF!HPS&uCV=d1lz9Id+*D*juh1{a1B23LBK-L%?YIXsHgf zR&y_A;Oj*j375DF#<;1(^Em7w?B-f*w4I8n6{VqkIanH4o2IplZc7_PG)V^OWaE2* z=hvh&fjZ4~KWZ<^E8+%jI^YAOj4;R?FfMFW_#4dq+Wd#z$>*luNap*E!XTG^52 zkJ}?EMI-m~08JjSXnVy~#qKVZUMO1m`&%3LaYJEfs2{_GR1e@XdsvZ2>-*~%;snsK;wyV2~euvdv|B%XUtE9 zdmJ25dM9}{uR707vzvBh22k@|65p1_%PaH{Er!bg!2DvJ#r$+-7yjEjWRok_ZvQr# zTGr3b3!3MN#YUg+u`C}b-lxbzuK$VW*nsQp=>oj}RHW}vdglVbLfd4WjZ=}tBY0%= zy65vJ5Z;S;6OgLKDjmQw9(2{@(XANgJ$EtO7{TuqcHt^!C)B&s@P@H9L7{bQ8YpLR zSazOnmJbZ;m9tshBcE)aTnjg_{i`D|lr!~86b|)=YsV4G={r!jf}%d@OA^| zzzqF)|LjN~y@0ta6!FcW^Mt_zRaaiA)1Sd>wNA9opECG1WrUjYm$=-pA-uJ5IPQz{ zuP470eEX(p<*OaV_Bd;yct3EwEytAl{s;LGYpt5R#fR{15+3<^hTC7E^7%W1K|0Z! z`+2lDHQ~!i0t(xcE;1V!|4OMgx|2u6$u)0D1bF})x&+^n4D?r-Z-27H)#Dm^%)4t! z9c%{7>s1JNb=}JcOSLW*Ntg%1)jdSBtKyRGX+#4v7A}*n^cZu#AaDO~kuj2* zzlWjmAh;%a(cc9q(?pbJIr@W82Tc5J)zt=VXuBGI@6kgUP`JBtM)SY81`P<2F%xC@ z%2ek&*%$PBp=gD0f;vXhW~rBV!I>xY5r6ZzEWK3;ja0%;#HambI6SdajKUxCOn5es z{rO6nx*0l_qWr)(+XMdKx=Jawn9KJgtA8T9)6ygRlm~Js&vEjI=O&bfqZ0NmYt4)B zC+Rj3zF_Sre-UA_o)2sy85=PAS)}O#4p}}!UR?mD>qwC!We8rkRh39KXw|YcL2Rc;x4yhZ%s$4Bk{qnM z?NfeHEGyUjX>F1u#>Xqgg8k*{^P;;39-2R?v~?%H?kx|%0xPxRdua*I&4(ao;r#-_ zYkOQ{)l)27Q{NLJaqeQ#8vD^9Bp3|OaH3s8VsWjxjNpQ1q7oF090A5DxOH{|l=u_} zCnCZ9RX85<+DEKtx*-!D3KTa3Ec{?i4Z$3R6$I%b){WrBBxw@rJJzxOBfP|p08)FQ zxn45OBQ|7@(r5I`%+WT!Fce*BlBM^+lHAkwZopM3GV5_oQd?8D>JS%)4{k495mR2s z1RDW8?_+hvG*MU9&}Mc<~0~gPf^BRS!`&MKAfTY zKHvTSp9LUWN|6%4G6D_I_b54C`r8}vqI)-v`>4!967lpI32kKKjw!Vo6RzwpQ{Mlb){Mj$KBXoSZXm!HIBDz z-b()PFiUZ%SyOD3?pRLj-lE0t3;D$Z+8N7oehfb;=GG5*oQg2>!iK^HTw!Cila{VY z{>ukEMtH4EdD$4^;;THC!s1ZO83|%a(oJJ`FsEnSjHM?T;E-zTDB{u&oq6=R1u`{S zm)^(8$@gC#jkj8^V=}@kr~qtpCUh>;NN{1zMU90770?y1s%bjJyzF|sB;B*q2@KNr z(V-v&DWiHp<|LmNWu>v56i4#i`8d_h!)+M<@c~7XQ{W**$FHe8O?>Wh}m$&Y!u-$rdDNhPerkA(C@vqaaJE- z(Q%g4QO-wVaKw68-*GJEejaXWnJFqJSNnb|pMNHFh2vf5Ghyy{W`6uo+L&Ka-_O)> zsaLqAh%Z@!6GI+zEWvSt)Xl#Z{wgbcGV{ZK$|LOGbTrzjGpBoZS8tD;jcJky{#YYq zO?*3FAmSZ+9$Fxz8E zS%9gIT@fLyy8`49$D5%UkrPNLjMytFrhGW44+6~3ZjZ>{K@$$$w%Rj5xpZ7}9$y8N zuf9BC3Rr)%#JC9be z&A5#mzn>rZsD`C1S8W{2E!A94%EgKXOQtkw-&MUalf&o#0(;fVmm_XX@;$qV1jV ziXwEEI#Om(trfBn)vi(}34A1J`VhmU?05ga!{93kH0DaS9*PknRooSQm&!e!R>_1? zjydBuj5msxktX^tao-3NcW-gi2M`n)kfck$)>CSorD`^z7#U&j!G)y_uZCa~joB1O zmiL7h!`^C!H_l?#XRU~FCoxQimz(?@vrj^YHgDZh-lmvc z>~|#p?lbPW3(L^f#*3SA^7+?wwCO%$a$);im7+5Hl&2r@COTCkYOZ8xZr_g`w@y5K zmd1ye`Djm-_y(E-!wKa=F`P7K@o7=}2lr(! z&#yZ#9xET-QNHjEx)csuYrIu_I9)twd4sC2R5QDvLZ(n3uIDGsr&R!1dbm~KzCl}= zTj|5`nJbCH#EcH%&ks`^6>mQ zFo>ik{{4Pt7uFq<(PFuD!-b}vdtyOvj=6GP%C;vs&$7>;JocC?6<^b@bfbXQxr^+5 z)wM>BecPkzX>pFvn2?Os?*!klC(aQvp{fNuZ6>#G4XSn81GGy$q>isY5*nN}R@P@?_&Uve)I44sEQ;DetTg%rp7o0u_R=*>>Ws1oG6?23Kk;+Q)CzD{o zDyG<&c3QZ(;;H!HHa@xUC(QFF>5)IBb)NU^xW9zg(ZwYu@bHdc_d`U@Uf${e+jCL z?(>Vb?S~@+!rmb#V_}#z%qmm@Ga`X5icOPYwkcsAk`qQH#avNH zy=-b$rJgrmHQ#$_LzMU8OYAJM<%&AU!Km0DJYwX&3o1aRC7jVB*-Xz4=pgy>A*&$c zupUFw!fK38acBdsf`kxMZ2TFKQ4{GGriP~>5~aw;P3_{BbTfEO4ZJJ3@8EiujCbS> zq#U$-|A^rBx6#$b|4@SBC%3O+&1V3>- zt$q4QAYuG&vsQFs7OyJl8kfGT(e&XD*@U_Ig6Wbz^jI*^6$H4avm5SSI)-dQEQ z*ifTVd%xS!&`DOO26I}Mcm}xIl;WmzCc_dvu zEV!@O2ZzpPXiv--Ek5I37I(E~xbD80=RXl>>P(P&)`T=hLz>HX^tk;$JbigsQ`fq8 zdps?+BGf8^Wk@Y8=s}H0nIW;oLak!-I7CE<3^gh!GDv`g>{`@_Oe(dY3{h#t3K$?F zGG!|vATmUY3}K21Ll`1S2$_?;zeVr8-@iP-11oE-_kDlU3ak0sc1;njXQJHF+t>dL z$Sx)=8gJU)&*FGD&j#ckNsrGKpVMElIAwewoBw6?fcVM&t&#rcZq&cd=l?L)?%N$# z^kBus+_T8^?8}?^g4ZkaW`?f{>KdGIA-j6ZgY9wNO>*TL>&tPumd?h0xZyze1_r^k zb&t@`D1J#ai7?X3Wd2DNpXEwbX0E}T=2tU~G7+(Bi8#`B@do|Xyl044EMLx4!CE7^ zS#F{D#!Q)Ta)dfb!nG6yBq=t6^G1)RrJ;S8j|~Z~fi83pH^;f;6T$ zlcGoyr2YRT_OEhp4SnsA zK`*b5KQ`3D@YuP?os5iI5KSlwMm+{!geL_a?JF=Z5h>%!Ui{F0o7h00)n*brHybB; zo3`z(wPKpCVYl68glEeYotElA(dX~jED~*hA~;)J`KwE4GS?gs#72r9`GpaybTm8&fU)%D91!w|HaSv59_kx#-Ku z&Bmd7ksHdS6L03)?kk8DPxu?ZsDQuFcH8JMV%u2$c9$H(NAij=G4s+cUh8#i!( zyR6d2))EG3YDRanSNl>D@Oc@zcq*e0J>Sx1WvP%Hq&)DWab=lJ}v*UsIq79!D+YZ3+JOJ?1xiyntdZ% zq`b(!_8k)b72y*8LH*EKwK01wT&yjd)}DzVY&+6$nbe!K38Q#>G~9)bh;$+b%iHfz zWo91#@o={@Zi{leh59C?hWDd2Qi)mt!?Ct3p`jNCAnPw)RcW9rq&%R}=|4(g}A zWY(cTRl!@s*0ep)D^3kF2xeRxPE9eFg=N!eDsVNSjx@SPu48GV#Ty_GaTPScXXelzv5(E3H0d_X2YW<|vdFou zWJ}?X#JKMA)w(u5Bb!vi_TE**saVnSM0Txyg0}EW&kpO9tY^VP|Ek}-Kg&VrbXr-7 z^AJ8cUA8Viog-Qgwclav(q{^<^pvM+x3?DiQJ+SxAG|tq^M&=Q8$IFQdOp^g@@f}d z%4w?a_v-FeXFnVj063a54H|J?#@fJ^6Ih@cq_S_0P{72l_-?oGHv7KYrV0Eu`N|qr2en>PhW+TXJP4fz(R^yEe`^ zh7N>HghQC+Sse9)@<3+bm1EcvII+ji7hM)tppa6-^3A`HJ0L?V=9#hz3Bjd++Lwbry8pga*=T)${H8@kjv z@^zBOwVYEK(UfZINTx*`_en_4$f4NR*#|1YlFeq@C-5ROYCp-h!+R!!?l{hCme=Y` z&Uk4Mp6K+T>9?0-Iirgg`5t3-mI_{J<&d!qSIc{K1tGs9NQraAqSAscUz23wFOq^W z^Zg{ayV+&gBq5Buk$Hp|ci33nA z+Ym%sHhDgdXL+zvehlI#p4jPMOx$A#%(9A1oPr zV^bYf+tyN(-p3koOv;2rACxz&zWN-^gTdUD6w?^Y6vg3HFZpHvT#3sd|`B@hoO5oVi5`1ji zOz-~V6C02=-@V&7QwMXlH+5ZRoqKz%RF!`jd*qUJqW)^=Iq6Kdf6CVb-Itr2*#$(u zfBv)rJ-3{N>F;{_Z0e^YTeathemJ<#DUa6nU2Lmc2f;jzpiPP2U-yo$=Ra*8x*8E4 z*SX(26VRPb#stSMOURNLth;t2&+CBdvT$_fJ5jk~QA2l1QW2+SMa%Oz^M-(uq@7Iv zt!Yj@RI~MstDDb#t@qev1mcD>+>u>Lw5+Hl$M0=#(RpCp3NFS_>C>C`+MG>F&?EIB zPB`;aEB>Y{>L0B){Guu`-@!Fe1z)C$Kerhc(woc8+%vX=*WF-`9ypPQ+>ZcXm*CSW=qLrMPpu zTsYqMLB2_Tz6$ADeIy=CfjV?04x@POqOEeKi^7UKitT!6BlyXsBzE8;QO?tq{Bt?A zAdjgjGBi&q)E&mFb>+5DhYri9a3I)V)bto{C>czdOQXhQrFmn@Kq`?`v(|dc0U4BT zA)MLG5n~=@J#K2v4zliTBM0Q3~NN^LLp|vR~H-`)L=cI>bih2>x!qM^UVgFce+oa;<`;gDQ>2f zWl7c=yU60K0;bm{<9sZtJLvOzk9PA5%4>rCDG}wA;713gHuizzZSuK5eZNOKW2fz3 zJ+C^P5>vh`jP=>uo#m~(Q*?Zb#~R;nm8-DExu31+USzHG)khdyXw3ody-iCl^hCO7 zj4KPfv=N8SQ7`d-hf=EdgKIG5P_H(mYmz@lCaqRPmWc9yP{FBM`71m}YxK8&t$RGt z))CPY%jZ7Zs)aGcrBV3D3tE|F9DJTo3|n-^8GSzX3M_j(d6|ddWA4zNmc1mD>65 zD!{llqU3{|h?@kC-f(eOai%S6{Zp}s2=rKaapR`K^oDj@jEE6m7#0aGu(B%v8vcCx zRaRyiF~#Fl?$j~7z!4EtxoV61`2d>k2M6)c@qD>p!vB<*?HQzZXVHz3_n?OQ2|iX5pq=%Hh?rQB^-;>GI@R-Vqc_oq4*o$(#Y%C?y5+EzsC zvwd?7Ko}>3d3y=v$fi(bY{Wp>WKNQ_Ep(9N<0@D>l;KMAyQ3v`);Wu;^Fit-ApFnU zI@39PxhQpliU4U1>4qt#=ttM6Y61ORf-fmc)mQhM69yFpV4S>^yT)e{2 zXTLb6B;f}QgbN98 zHD>QolKe-zXmhMU3Z=xpT0<(%Xqoyq6DtO9Grm;x*pj7e+RYM?b%0bZ7iHzVy6u0z z!@3CTP*akaK+p>8?}wVxv{TUz=N!YH<&aa74x~&)mvU@kx%{|1Jh*UvVXmwRz}i=T zIP&A!erl{07+KGsSSFsOuYlOo9h* z8=6Uo%dKtceN68s`6+Isa)5xe*6p#Dx0xB+c>t%7E{F`Q)BWgaQgCF|;&@->jj*T0 zygYn`8@{1i1FP{(k$3o)2 zf*NSg#uu-2Xv~>myFg!zoH?ABdI2f-ysvM3l~oeFKFvOj)^4SJ zS@r-W3RK_I2ZJG7c8}oVg{#}zEb4CXPJqjPpO*KaC>o~#Bqlbw&?wF-%BevM{s4l+ zh%czx=LMdnB!!m8?Z%fn$U2k7%HRUo#>OFJi!p@JxL-BFJ^R~43o^)^>m~(nN_D^1 zZ;0DViT6TQ4qkN-Wnt^j4-tOa_Ay;tK-G#ASTkc=hs3#5Vh&ZIYmhbK&pT{PKN;sN zN1iEI{;#u`CS-ZwrR44%HpI#BF(15FN+uA6BM^USe zy$$c4C24PJl5Ec5;5o+J_fn!!SGEGSVx||iYttOAH2vLX%{U9zE(fRs{ia5KB$-5v zPz&DsT&Fx^#e}aSPj3Bd#(`z6QB8OH}lAd3A4cXr7xuhvR`#X@s?V?RLZOL7I1D`-1Vh4vl9RxeP_!q(gHBN zhe%VeCBBq1eiCyTF9D^~n%bTYQa6ws=>0Da3kXRAwHzHp!vVen*4Joo zgFjN8@<%ngk-_OR41CUc6%t;TA0QFww@C zrrhG&RwlMPR^-NN9TD&Y@(JgK4G3F54>cUdk6MxF(J~H1?|-@|;{Le`-xQGKxZ}4f zU3ciYq|Q4`o=>-jNotX@rie~5n?pyvI(?G56`en4k*3c26g^r|oinv_XHx#P-MMjz zfM0s-l_XHPY~NsxE6s%VVtt~#v=(NH{H*!!`r~l(WMTelYI5Wi!V%NrccOQ*tzUuS z7ub1{J{e)_3{=~gUryZB3yY=p#!PO#A4Y{Y_tMBK{^YDHkX1rzW-ewVGp@JeARIxU zHC{b#w{D<~HLWk+N^O>`p%1k$re4C1^IKUUMCH^XdMX`V7CQqnAl`{CSr%o?-vdhY zeZqN);C9y16afdNOS%qoQuq}@G-zH(nJLj5&iS`8Iv$h7`S--{LJQ_~#1bWPi=+)~ zBiixX;^+;Y?v{Oerpoc3#|{R!3eaOic-wwG@lX@z-m?1#DdxiW8hU!3$8 z>&974^7H8!$8T?5IQ2(;!|=?w?MRZ&{{BeB_rI@(fv2Mucvp{vk z<+#1;>5Q)5tX;M=%6&)4TSb_Y1>?mc{!wZ83&vr3#J4yu)4M|AfDEJhYJKy6q4X%1 zeKi*9zv0_%w%)2Wolji>1l9YUjjjL05C6L=D8FhRw;g+OYlADQQWQQfhS4gqpsXW(n}ga!1*_!M2?D9KYL zg04lUtVF2VPFr3^zbSScf8xpSE&q$ww*@}=ti+Q2l+9ko%+;?*$MffH(p}_7D7WJ(u92P(=2n zeud+G4VarGvevCVqIQ;;{CtmK$>4h;>dT^gJ;NX0Das6fYHyTw4y869+%ai(8EIwa z`i;M=zi@&hF48!z^mov?55vZ14F2ypBhWpsec&^pOX((?5-uI=&hY0sOGbhKHI^)X zp}1=$q*bx>{f9}}x@#W^?dY;jn)s00hViuyoI9i!G85PZdcK_7{kZsnU1>wJTqAEm z0n6Vu;X5xgl}9m#sF*)DJ0ddC{SX?etYh=;-fgC>@~EkJM#T8rNILtgg2^xVNKS83}W?LptAXAcSYnc`{WDOUEL4R+rfgwhQ8K zl7!!o zHwYvn6y&q8$!R6hA$g;ZLO(?!lDXD^UWPuwa~ZcG)gfE^J9J{F8Hbh?(e0V4Qie`M zud25(-k0|X)Z;^vrUCx?OfF(&tgc|m?N)u-#GFa3jAbMbu|if7FGejTT(ydtzu_yvh9cFX)fJ zd~|QyM7Eoc(_j6paiRoS`gZ&;E)*w&PFJbxc8WIK~1A^m!^du~{xxf~P76hQ# zfFUI^Uvw5FW(*uvGc)+th1U|c)&^aJ8Ap9rb2T%Sw}F8tkl%?N(WBB zH#E97lL;wmt@5y`zx5V38`-xV=>zz){evH-E<>1AW~E+k=ls7aN{__9mjm3-{}GfE z+U+>{4U)YUSCom3dm_%iMN}1O){S`0gLWjiTiWT&@lN}QIYyK#h1x;ffaSyjdcl$< zX&;4D6?J3UIXxeYhb@SY<_o~-8TbM=z{@pPQjz3y$qOEwG-O&hlp1?nL-JUQr#_q1 z$dzF-Jh+CloNx}8ClL22pc+Ww|1s+p)taX*#JAQv5t$2##+jjY^C#jOI@sib9y`I+ z0FOavae=90H!=9mO+l(G*VBNm^H0@I8-bC6SS;;4pc8eKr2#~?2g&~?ZXLM~^ZG+< zbRTo@V#8l2lP4ecFyh#Csh#)7S#O+rf5(N@)VgX`$-c;gGd{ZT@?Dnq`>ewp>7wWT zO#U`ZjR7q8ADK*k8`g;_UjzaG0beiRTqA80S$q09+CO?4+tcN4Ok#=I){d~AbX@m3 z7%0?+MgFu_EGr;sW8;~V2V-|M$}QF31K>Px-SW&%?3!olie6v~&ZK>W4|N5<&BPnk zHtN44%i!>uVH2#L3tH~e29df4YNSbBhSwklZQ$%~)Asx6$Z>-_&KF99TVye}@M@r`@( z90zwtZ9m~0eE=v;{9@5i``}RtETTtoHF_R z^$8&Bq%nRdy0xV!@{PAtaxrk=!`E7K(sBC3UOCJb(!q)bad^qHIizIE z$n;puwGi{crmnwA<%;Ld0aK)2W5f@$T+5jLuG8Y8GGo_m2=E(opON1y~C1$QY~BnTenO}k)eYe{ChrykG7z68It%E=~ba+T29{>GyBKN z3#Jg#0X=w`V_w#ehhjg=fR2)d7!TGv--C+_a(fO$YM1<2Z;U~$&vzis^^ zL~njmC@OXNRS09|X@|TBxofeMfZqKr`9IF~GyZj--0tRf`4w5LS6Vn1jbFSTJh8FS z{*PE+`J#JoKrW|n(%!+0nl<*~x#wQ3{VBn;i_oGLbg^Pqs#U<=r>Clzd`FsV>t|(l zZ$nIx_o>=RhjkpF+erPAeL1Fbxd}g_O<+lq6q9Va&#oh@fzp_3=?I+9Ew)cP6_c`e zR8;I3znFdmk~zV1FoT3fbn8hkU?Nnwq<~kTw-B^j?}^aCZQz@^uq=BhfgsL=aZJJ& zQjj5+F}O4xWu^lR+0>NLZRqgT#x`*u9ym7>i#ECV9YZ6%iF|pV{TxBVqH5iGn^B_> z6UN@+;{v&C~S4UVh)x9=#V{e0vzx_)yDi;gK4rtdhN$M2bORx7w zXu7gYEWYh`yls>=uy%u{5XZ9)$Ys4HjklL(n0^b=iaTdROgwWqt*t~gm@mG}Opnr5 zw4~6EG+Dt~Y1U<|L&%WZ_!kBKf^5(CS*Dc$Uy!vIlozU?X@iUW|I0`;h)^O_HL1%> z&|dOVoC$fvr>I@#2~c^QU@;;Y=|!hYb^UJKSk{snK+ciuU4v%w1Agx3gft?hTHD7NcvW|7>`*eMG2Aev`z8%2dsb>ccldCG)Cu1v%0I`w& z5dZ!glfWPwzLh$?mlNKj5GEiGXDa|5H88xxDO5vROiO)=%$<#|b^qHPxR9O({?@E{ zwt&VSq0vi?*P@D3uX-xjz;V7qQ!TQlC%X*{qilUe5u~!7_-yQWL^d|9F#tx!e%V#1G0k;{O=u$WkP2 zElP+91C^N@3=fPE2bnfw@U#y{V~SErk%{ol(*k{Y69xSe%gAIg$P&wuI{LFI;7v$F zJxDT`tQ7hna*KVIv8e>x@k$Gv6+3Ug2<=+AO2sx9tm~4C9eeZ1XE`zE4U-)o zS8<}(;%z6~Xdb7;+)3iLwR!K0ts&6EyHUTU1FzKV5JTAEY@Fj?N!O+%e5>f;643u} z?vPFRUo8_SsH-qCvIebFfvIzu-O*|Padc4!HA(?+K0x#y4UySITD}BL*gy@FEP-mc zXN$FCtX>W*?UTe@B4w+Lo9i!_6fS|Bb}>d8oX5wvthpTbK4?6gN zxGI^6?>$6*fgmM9xG{#NGL&`9Q|8A^68emPReblG-wer4GG8B6igwH!jPx{%K8OZ! zS2~9-Zr26V$43Boev2do=aWZL2to(NW#V`6ucYp|b*nH)Hr`bssM5ZN1*<%!dE0}Q z1;~Ar=~PR=3={-u$?@AV-{X-|i*BA?6W!ugwZbFXHvQb9g}G0HZR3iG6WAR-$>c>3 z3O%vplA}cfqs5E<>^Z?WX+Ol!Cl}U)POY6chqdD7Ycl5MeZ^-M?z-JSlX20Bp89hD z77f8uIf2PZY`Wh@|$Rby&RycMgB!n-d^M(Ds@Jrd%Q7D){Ve?^!X~*w493P zeI+xiS`1ap@4R56|eh2Tn#)=uM6tqdC&l4v&lI34Hf8r zh_r?WF8sOO9MIy--m&%P`3(g_QD$lPpr{33NuI@%jb|q<@+{xqVhbcr=h(Khi|+TA zl;_Ke)C%d;>w8hV64QZI)_TI6iGvfm$8Df$tcXmW%*sAPGs7D)vG5$_wWDq@W9}5) z)eG4asCC17=-l@iw9;Pr?0%W6G1Jk5hS}oy6R02&Cxdd^(l3#UGxL~=xr!9lJ4qMV zaNNJ10;^3l>MQ5IMN_;@kM!J85TsFc8B~mA1+77dH$3p4^h=*IxV@i1+f2@$F<Y6qeqJcI)fl(W%csHt#;ls2#f?-KaM9UBW`(l&JQXEp^ z&0_(YRsdXbepy|~eWc-w4f~P5KTjS;=XcuHaw{8x=g*fES?AgPOZjCXPI6i$OpJRF3iqPoY*LDD$-he;Cp zD-BDJHW7qm4J)-^7WV-qM{&xeXwsOJXDhC#41+C+CSSlZ@OnL5?C~gg^hLzd^>3I(jTsC3NghY(N#qRBh|#ZGH11_ zM8!Po1?m~ISURSkuX6R_Iy`ipiI_Uzt00%SOz2f-8C%#0Zz;l&FgVU zB7_AnrC0b*yHY-_ca0Oe4^v-`-Z`D4$|*%=hn#^94}GjMn;$o^d<6m%P}h(V)&N%G zhO&{05B=xi6ph?4xM>M^Qe?Fn0o78rKkQ+u4O!QBqfBMeY@>T{d|Fo~a-g(^k||dE z+_$wkz#sL#)yIT^i{wC|J(D>-k=8o;DpzLD-VT|wYbjjMd$@y_Wf7oLCo{)7D+ z#;+vXqzY$!?S>~AbIIH5M4fVf+K^$*UGDo6afh}`04urfXP%>b`{orhYE{^DQ7Q-8f&fJBnHjg}I#piB>@;Yt(SA8VugQ?4)N~g8uGYM+uVo_v-onuo4YiLiDe=Xo84%x3oU4pQDVw!(Q0~0jq>@!DpZI@ z-Yz@yZ3kd98W#zqy~>4OqqpnW0E^VK^<1yFo)AdVai0fPRF2NkxL6SC|8w5t6{mg5 zgb7P#mFviy&#nSmb`kP}{iz$qte=Me++zQ%z9!$T!}s5QFTeX+)F=8o;<-oI4l_*C zv>DYmrPeypVav2sC(3b%Uii}OD>`pIHq5LL+NfN~1hxgGnMbr<|sDY!hmZN4F0uiTK(zG2?iQ zsNTV{0D6mcA}NMmn4=-6Wnb1-^mb^<6xPA^Qjk>O-^zy(0!Y>PYZWYm5}&Qb;1xv# zTQ5;pd9gFHaeOIGx-&~HJUa4tcKAcHowm(20j~|R{U^pAbWnb*-e}FJOm($*<6PqH ztB>WL?I!Lu%k@5{aDL3)MvF?M$JlG+g8On^FbIA7y1h+ukPZ|ei$9Z0(N|X=Q}q8j zU^CqU&nmakpYfPACrz_cJLa94IN*|HQ=}E}v%qhoT7JcgrW|D`Qa&iN|0`nJxS;EBXgLUT zs%7KpKwjrPJ_7OO5qBd+x=TXeuU1u+UDq;cUOj z7_q^b+DI`cfb}o^+jQkx4CdTK8!Iq=hP*nO_dHHtkbJiQ!ETK!aQAtAWuc^gHlOn2 z`S|@_ckR9h`B^!#*P@Z{ZieCPZ}xD#m+Nc*;=oCnb)tK}DI)_6vVf?vHN5N|6%fmY zly}J*3wohOElwSNK>(|ef109B%kfW zlZm-OvCf2n<`sXe0D@s?gL6Qq_FWOz0*_H{w@a9O$n`9_v9)NxH3XN`$@S&pySE(z z=NxF5L$q$?nNSX`?qCPd*W+5PgN}Pdfb5m~6-VOBqFr$5$$a^so(&*GvbLHpr%F`+ zEw(~I77d{JTl6yE*Nr@DT-KY1`$EHRRMgx3@e{z_Q)s5;(a311hw+2yeIb$R5=mS< z)UMAbQv@+qEFr#lZ9>|VDXZtmRwd#Y(={#7tvzWvs_4Y1Fc#u^alSM43EITJ)wRUs zk`JB}uvG8})bgEpyn=}<1}SWkxP9dCEjtbc_Y3{}Jam6J7S;54n)diEkKJ(>?o!6T z8yJ0{`Ed5N^s~FN^Dp{>>>b*ZEIyAv>E~k^_IC7YEz{B4v;lXU^!QC16X|yhYzVi@ z#rMh$-!vNq`)IC@@J5S$$w{oYe6=}Do7#xGqm8%@Bp*4-Fc70LZ*4o!=Jmk(w%?#$ zFimQno3i$QgomEy%|X`aEtO_YPn5JnGH{CO^%xmg9TJd#1F}a}A_E>SweFx4Pa-cW0!3f+6?xZ{6hD&*FJz?5$< z6n_geXKd&Y9y(OI7RKEa5la9D9gh)pQTw1jR-FA6{js5``^`m*UoG-2E?eB*D@e85 z66+j1Vl?deH?p@y<+J`feO;{KV zFkFTw$eXdijmUXBSpa*M3uggJi~NH&pyQ~)I3ht+v31tH3m8rT)VHjSOmmVoGleKf ztzm2|V7*fw?kY0~o1w#$2}fgOZR!#-b^U$!4}?yg*cn(D8=?N93+Nk1PV54J2c77g zq#v^rKFfMf5e;U6abTUt24IViy>>Cq0W*DXc!@Oki#0q8k6iSi&);px_1bR7>Fkz7 zr0>NxF6AiCNA%g{`6LajD0~c_F3sVsgm0wda$>X6tLrVlb_h03(+;@GW#WSJ(FKt; zG<}r>KmmS7bE9qN-=1_cWQ}?qe4NYJaBc!L7-V!pcR$eRqGDUtD=M@J-`k=Hg5Lh8 zCIcD3KkbBqy`gSrv;nTv4^>OFI zUdGJ`ejkF+1yS>rmoS$XYY*N*Y;lpG6;-4jDu!>-)sEL_bnqk zolf61d)+Yd~7*JI2V+Mx&KcFaH$hr)Iqaa%OlK zjcn;dY1w=|cA-oUO`0HQ&e1faX-)_x&}DVgvaX4Oj>&{X81U-Au_jEFD0>@G<28*8 z|Cza&N18PWh`#d2e9>8vk8Su1GWLk3cj3*vsmo|ZD+KBw{&bT9OiU`{i8xyN#VnY? zhyFeW`Ts=}SAT*pj{I%U;w-br0WGsXYCQryS=}T>FFa41IpNyN{VJ?%#W#_jf9VAG z0;XRl%ve_hxOThYpZ4Xh-tei+r>R$)X#6Wzl#>(KHf=>y zk5O&2(pmJy*aZ>B$#a7A|I)867W9XUi*!t1@`^`aZlG2Qmy>)?Dz)PzI=lqJTKgDxq(QEXOTiPX(i8ev1!Z*!XM~Q_Mn0@OW=v-74-j9SbF*lQQ z4a)$xuB;z*kw?TD=kKVu`Sq@NVu0Rf;GqHiKa$twz^8JyMwo{91n@a1Y%nGg!9&`E z{>pKaA>1Szg;h1$F_(OI0>NRd392%ALJ|Xs23nN)J*v`kZb2h7!!3o%A zUYnWc3?8xB;3jZ0I(A>arZR^EC}8%&bFJ(3*~S6A<(sPYm_<}#yMDQ5 z0896wEt%t+{1+@neNMcI9kx#yxR|=#`}*~)?-Ob*a50ZEE;u|~s;W;ZvpHD$cqsD1 z%D;6t{-wMAH3+VmCq1ZMTk*_{I??Hb3&Z4)wWu&+1X`>S&-Xaeyv~FxdSoCx7L;{; ze)y;rY&^*n@)&DzLW-R99@c8+m|BLQOIvlY2Ce`hc)}0}UxwtMdPHbK5ff3t?a+dzvSPS)&z_R=&QKQED8vt7Va>a}9$6UJd!nA$AmlV%4m<2~EuL9y#Fcu>-hKL&ZK7F-ayE$EOA(U^u$c0?*NeA_0kRjx zw+Z-0noMNazX;XQhkk6%B&f}qtI+!~xW^ zjV)?Y5oI9wCrz_}R4}Nf^rhEn-wRWbQC}2%l~bN}35-nXL%@k{22U8!`USj;i1=3M zZ#AB`fF}#`XwbVBgXu;b*%|Exw+CaE=OJtd%*AKLbKRgs72HL$LRZJ2MF7Aq2zoNW zq~$fOp!mLSZHlS3YFe?!%;)M=`*w@Xj6Ckc6DeSEH>FkGr9tiRUpb++^Rar(c()f^aq-47$;R?*d}573`XZbHlpMva(1 zN@le|f1%2?~=EgdMdYv_3ULH5m-ImfJKC z_&aZ3_Za=d(4j{R1-8Nlv@yeY%9N0rgf`bBV}{mt_?m1%Tc+aP7Ai3>u>6JdQ}jz5 zQ`oQOovLsXh{6TnI^O=*gUukjqh|TEZtV{82(9<6PCnsp)8qTw&>f*~_sc$Vj6+pp z>a&{8r!^}8ZX)}on>8fx*`naRIv2SQXOL#g-Z4TQc+`)WSVOF>@P*`{`GuAYe7fae zsDnn%g9o7+x(8CG`5!;Dp}_zxXU)A=qKzH;aP`<}<87f3u;9Ija@!5c)rB#LGmNGZ zGFD(i5&;t;^lpQJS}qp2)DhV`&5CuHB@51-{i*G628rY#vbNs$BobG_EG1B=@vUv( z@<{=B?)R)bQcFFlNDOWMs4I{HLtutj9IoMq~k$7kqC!6(Af{HFc3zCJ>EmG1JrwQ6vAW za*7GZ_s$h`wHU17APJwSnEE8Z;(YJ8#L;*f9!R<5+XCe)fis^o(3wryi`|vG_@e$n zuXGkv5+^yAv!uvmZH=q!d8p;{Y5M^e z92)sXR}&$2cH>;*4`f_3m1z@h^htZzKod%vItXX`PrZz*!qXaw^VXDsIHX@Md_itf zfq8XMOusJ zAc@I1u;Q^dWHeq+`X$_v*51s(TSg#jXf@oTgqmv-MmD1Rxi+>aYb_~vdS(r1;en$c z>^xGj6r{IRfOCBrc<-OMBReR* z`@T5*>F&?|{gUwI2fl(BSZ@>vt-otQTgoLU6&@=4+k{1`8oFu8dk`+`8f>5*F^&b@ znYE@aBu-f3&Y2_*=UUKl+heCnm7^NIk7_gl^%`+k*QCG18UBUYy`k6L5P7vYU7gUY zOUKNV$s~Dy+FpKscICT62Ud=*{cwfV*1Qi1wu{EvjMRUL%#$7;I1)Bf=rXf6hsgfQVImx3tZlFBWyT0ql?OXUmEWkt__y~7tl=oN zAWvIDMfOO`n34@@0iJxZ`jZFzCzb;O($1rsj7oTcq$t-m>7TnT3M-VBGCgY&pD^mJ z8mvP=r=oIBobeyTEov5F9i3k%3$OY?VClEG;?h2c=_yysydxpUc9*(i<~d6AcGhcA z8oz9O__JqUT%5g2bU7Wd(r)K3JA0nI;2?=Z}ph$8woxd z6YBT+8wn9PVwmZ~ZMp;@_3aJhame`CVD{-4_;NY!-adA!t^PAU7o{B zb)l(h{Gi&~dB=k8wQhIma9vA*DN3TT%bE>a9C-Y`>^)zDUQ?1GRp&Sasx*~u+={G& z9)1EMQtLR{(-KX|Gkm+>z@DcAhu`jzPp6kIzIx%CdOz>ySH{n*298V!R^scYYl);dN)r;rUv zm((E)(}~UZEI?q*-8js{l14;L2L!28*m;=7gjf8I?S&hVn&CYWbbE|!^cEB95Uc*) z{z<@)FIv7jLd} zxEb@-pu_`S*^9wm zA?mdA#bHsLYAhZ*QqBI1Z;okAb(7CcoCfhey>_%0PP|8yf)I-uZe!FfxwjaDn08l^ zKN}cFLlUg00$jWcVnMkR1QGL)uR+TO(8pLx8SsQmtMc-5mzRF|WqEr{{f9Z926W`J zJ=+N-9Gi+S)n0{WR=_jpuiCvR!?YDg(8(>OS_AOR1_cgIV9;k`6q;m4nB6UYsiYrN zNRVGPfBF2-XmfW7u6&+RS>d$qityvOUURqeY!FxUqC%r`ph!l{`0DnVP4DWb{Gn|m zKk*q@mQ>M@Q`vxvtt2k|RF(@*1EcmMu`|eVF*0yS9!rsRedWdbH0qbuakHa?%Ikx% z%-A997hjcAl$RBmZbel2V#0^&IyEFWSTm=c&*ia(@gyBZF9axYcU#qxTNF^iFSaHe zqqmw=@)sHwl}? z=Kf>UX8gjrrA0m8QqOZun*ITOlTuJkfFg9#-#t@ynksMx`nr_b_v45eK;U!!mCv&Y1;oOIn{^b2826p= z-?mV6!{vJr&K|9{lhHBnA>;gs_WFA#J`=G_O&H`};&;XF4eaL-5feiwj`d)UN=(Io z#}?Kuv&z{-M{|+ReN8(z2h3k^{~e5M)-D1b_o(|wBT%X+7a6Ye`n)#tKV;pA5(Bjc zQUQ9(@ZlfaZ1g*FNXEj4{?HGr2OPF(2dNMD>L>WB*8f&WP$y*X&ti5NUfpcHH2~*l zztA@F{Hy~`&qVpDYvK3xd6J=DyT1lt{bp^ukQ&#w@0-P7K)GY2ozqXcaP-<$g;YAA zA({ORcp=bTR3Q^(fZpH^$j9cb*uy~B4|R>E77w2I=w5~VsjmCLBIrGHdy)E*;wMC% zrIPtb`nGtX>E+_-UgKE0ll4&*@G$CC*-@3Im5SnLJvb%2(1L2@>5{1OvD*rtec}kD zHrIVvDLSb^Kxhu}H36W#S8JZz^z0y;50qO}hic z%FbAH=1#zy`hj#f5)z5vQ9wi!*9~fPz%|D!`|s4}AjwYYWTTjIl3)G35@!$*B2UU< zordY3>Ase&)VoXm;wsH^!?|7d)li3_b#ITTdBkFi{qSpGnk}vW&50WqLajlBKZgn% zGewQ6E3C9=jk>N7tAv&IPIAi<@!?@_-$+eYsBw6+vKPT~_BgB~nR8>e=eD`xWbXb8 zt@%RcO04)*{I~dQeDSTB_;+poWA%n4Mm44Ur)}LuDu>Mw|B+7XU{MW#@;zKX`0F~D zpXcj!KV%(Y@VTJGfiTRD>wWJ+KkMq`Aax1|a3s;4!Vy)fEBYtYF~c^gC)#N7EWOpn zc3IiBnb_&I>odkQ(Y_~f`X9*W^kvB@F?lz#nD~X$6o<)jQs*o_tJ%6p_H~%LI(rY2 z1KT^MfBQQ*lfWs3{}N)6bvA7feuDwA`ag`w5{~tg^GCQ;_5+ItodFlh551jfa_D4~ z!N>%B{Y3qk#94K}#_8erhqOn5%a-9q=Zo;M@ewB*|DIeY8ayg?W3Lu=;s8#NgclSa8npW-JF0edo87{|OMg3w>incf;dvHd zGo4Lhd!8xbnADl?>QI$0ov)~YsX&Nbl@+cCe)m&euDn4+JAEZt0Iye`oHm(6c6`d* zzVC!@0@xg&j5^AMKg-JCvPQh1S9iA>sakOhjiL{1)6^yhMw$lR56CDw#c8x$zIQ6NKL?yKEFn#Q zEoIjhHh|ZIj*6y1Mm5*}ym3FzQ}(&tU+BE$-|!6oHgHD!%Z?!khQ`LFbn(xmQTe;>nD*EaDBhN+K&E!CADg;E~PM7Gsp!m#RbknqLL_9&tx2 z<(Vr8&nt~^<*A;A?&|gbYMGK$M}Zz%*R#-fJP!~&(bhf8lE{A4UTqZN`BBS|>aLA51|N13 z(@9*b{fFNdcsF|x@!CZ~W|DQU!OIGZhylD~xE5)!mz*o-4dIu}Ko_77KEA7cWk&l` zSzUmFVW%ExHQKu0_5?yR6K2v?PRF1fu%1musW_Q0+{Z>wZo}`%4ZGZJ$Kc+%tsI_7 z;C;kVFK+^Ja_$f^WAeP&dxW3-HYw6{6)b+!Q3Td2fGqn1isOB&wd+@RPB~vMW;6u} z?h>{YKJ~JG8?fo^u<_>Q>r*dpRaM(P!UEpPK8Mp;q@Vi zv;NN77?QECaqI^LS~!JyK;{U%iVA^ie%${6R~WxY$cqR$v$c4x162DcWsSs@Di_it z503G9ttpn9w;j!J*NxnY4vIFSl@(Jm_Rrea!fRx@+8k$D6^0h%e0g2?b#WXqKImiX z$*v{D0W%<}zv;R5i{HMh6g=c%xyK=*0~E#L>Bn_h$)nl9G}(~c#8L-HqfJw%x2SD7 z@_XG3cJLJN^oFh>MLO>O;kA{l5hT;SN->Te|EPezs8?f0Rj#(2Kpj38pC76?vd!bh zL-%YX{O_)htj_v&L+h5v@O?v;gc`&`F$_D#2PAo}gM29PD*^t_?!Yj z?5`)E59TVqmeqA`v)YMJS0CnJ+9%CdnghgTH;L#aSPCT0w-5Pnn(ROwXzsx1o?5!^ zsrzIr!u60_d5d!ggpHyKjVZstzJi8#Hp}*pGhK{Oka5d&D$*Y@Idk8tXZK#;oC~HL z$wW{;+RSGv|Dm4i?3YpZpsiAG*#0w~QWR3OVRSR$x9M4rMR06N*)+%z69?Jsf;CmV zEB4n3YbSBhLip?o-DBePb~eqZ=ik7Vy&jR3<_KR`j()W`xM39N`~;BaS*z}vqFl!8 zQKs7-XsIO5Yw_9~R_6so#M|1OEzJv3@7b5f*m}|0?PBac{l2!r(^R=U6lx8KH?CD9 zR(GJ)I^F^vZeR}X;(J}Vel)VICtl13WVURBIjH=w7P=*%lQO~BXp`}vgC$_tNpcIy zD#ECG)O^5~9V73*oF{!!GwV76Vuk{3WU&s!gk=*Hq6nlFr6#1_J-)@+zP6_2-Yh=~y-9HC7x=tZ23eqvS z&n06qI&I|bc8raZ-u)%J7C!bwHKs^7NpyT!yUy!7%nde@-V`{^*w+1w%_U~Qw%gra zzE;WHjk4!C6`bm%UB53MOu?fs>l45x*dlS5BDiuM;!LpY5y z>xE2L9YtfUAC0zA0SdrDc^0X;l|4+9no95cT<*y!WF3t@rn$heAvf*!OILHiObIo zH2v5yy0l&TA(F`g#ChVY@SKN)1-*`d2VbGKbm~lABf7=&-3|(U7VOL@m-uz!K0oH@ z0|6t=U4qZt*!d~)BLe|Hn%uJY8vIJVwitPMpz`N6J`3j^!DDfyq4&Znnn!pyZ;;E9 z*N~`>WX63P#RJEwam&FNke>>M68&C(*}!!dg?1=ocR-xV6_kHG&7tpBJK(r}_9^TR z_%^^_y~y3@E7eyt=IN^V=)LX;9tlDy>5$6ioipx(Ut(_vcZDB)U6>!fH8wD%TnfIh ziwuJ2$*cKyU!_gAfBzE$E@DU>$ALMBly!dW7{bKjN2^51U9w}~FKuOa^gK3uT?nRu zi&C&E_B`T6wZx7p^c7(2kx%Xj;4GY5w%l?_$Hza(j~R2-ty+@52y9jIy30S*T%Apr z3n)Oml9z_W8>(yE|CP2DlZFYQ+eTnBDx)$*&%}+R#+5r@4lQE`{GHoVEL-XZC!0?< zS5f~(#*cd#N+Hb$+B_VfYbE0qJ_yUH$OsI&w^w4BiUVu7p3|hThi;1ze}@+&M%I zn1^q7x%>?&sp>D&og4y-ND(+dVH};BAJs?P{B!AyV9*=}Z-XIOrRs^u-2bor*EK+; cw7@GbL<_aQnfZ;ZKrZ(~M-D!BIdkRz0KcHTt^fc4 literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/assets/message_list_view_pin.png b/docusaurus/docs/Flutter/assets/message_list_view_pin.png new file mode 100644 index 0000000000000000000000000000000000000000..0b6f1c391f037fd52c69c25ecf4d8b43704e7dd2 GIT binary patch literal 470429 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 z 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

    RyKq_xVxjCI@OZ~E}Vk_Pif=2 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 z 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

    Lu;;Rr$NqRIzAWNVgK@>3YgbmP8 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

    mJMb}! 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 literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/assets/message_list_view_threads.png b/docusaurus/docs/Flutter/assets/message_list_view_threads.png new file mode 100644 index 0000000000000000000000000000000000000000..2c9387663f599cff810f176434a1e79e3cc667f4 GIT binary patch literal 25365 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 fvPA!`7u;jtSj|A0^QzZgQ-G3!y8JI$^U(hT!O
    Date: Wed, 7 Jul 2021 16:29:59 +0530 Subject: [PATCH 520/599] feat: Added new docs and images --- .../docs/Flutter/assets/message_input.png | Bin 0 -> 18904 bytes .../assets/message_input_change_position.png | Bin 0 -> 18303 bytes .../assets/message_input_quoted_message.png | Bin 0 -> 45822 bytes .../stream_chat_flutter/message_input.mdx | 8 +++++++- 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 docusaurus/docs/Flutter/assets/message_input.png create mode 100644 docusaurus/docs/Flutter/assets/message_input_change_position.png create mode 100644 docusaurus/docs/Flutter/assets/message_input_quoted_message.png diff --git a/docusaurus/docs/Flutter/assets/message_input.png b/docusaurus/docs/Flutter/assets/message_input.png new file mode 100644 index 0000000000000000000000000000000000000000..d63cfbb3e2ac7c21a3ea2349c37e21b26996e0b6 GIT binary patch literal 18904 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 L>gTe~DWM4fP!Vj! literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/assets/message_input_change_position.png b/docusaurus/docs/Flutter/assets/message_input_change_position.png new file mode 100644 index 0000000000000000000000000000000000000000..859107d25bb8e20d9997e7c3b5233a3e8e84dcbb GIT binary patch literal 18303 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 literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/assets/message_input_quoted_message.png b/docusaurus/docs/Flutter/assets/message_input_quoted_message.png new file mode 100644 index 0000000000000000000000000000000000000000..c1fda2237bf73ee92b8679ae60e1ee2c581035a4 GIT binary patch literal 45822 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

  1. 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

    *qO;;JU*TBt~5% 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νQdSixjgan2%VL(}rCc1+n3L#PPkR1eJ@)Tio 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ʌk}l{b=wc}LRa_7liJ!3 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ÐXP^q)i$%B0e@)4nhDJX< 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 M_U|tL@%Ib=2c}UP#sB~S literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx index 364f69f4..5ebfcede 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx @@ -8,6 +8,8 @@ A Widget For Displaying And Selecting Users Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/UserListView-class.html) +![](../assets/user_list_view.png) + ### Background A list of users is required for many different purposes: showing a list of users in a Channel, From 184a00b1958da5013106779233fe2317d24602e6 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 7 Jul 2021 17:39:55 +0200 Subject: [PATCH 523/599] update changelogs and pubspecs --- packages/stream_chat/CHANGELOG.md | 5 +++++ packages/stream_chat/lib/version.dart | 2 +- packages/stream_chat/pubspec.yaml | 2 +- packages/stream_chat_flutter/CHANGELOG.md | 10 ++++++++++ packages/stream_chat_flutter/pubspec.yaml | 4 ++-- packages/stream_chat_flutter_core/CHANGELOG.md | 3 +++ packages/stream_chat_flutter_core/pubspec.yaml | 4 ++-- packages/stream_chat_persistence/CHANGELOG.md | 4 ++++ packages/stream_chat_persistence/pubspec.yaml | 4 ++-- 9 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index b82d3025..5e31e81e 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0-nullsafety.8 + +🐞 Fixed +- Export `PushProvider` enum + ## 2.0.0-nullsafety.7 đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.6` diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index cb3d587f..5f0f71c1 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header // ignore: constant_identifier_names -const PACKAGE_VERSION = '2.0.0-nullsafety.7'; +const PACKAGE_VERSION = '2.0.0-nullsafety.8'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 4901adf0..2e1dbc88 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat homepage: https://getstream.io/ description: The official Dart client for Stream Chat, a service for building chat applications. -version: 2.0.0-nullsafety.7 +version: 2.0.0-nullsafety.8 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 3407178c..a6a6c535 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.0.0-nullsafety.9 + +đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.8` + +- Renamed `ColorTheme` properties to reflect the purpose of the colors + +✅ Added + +- Added video compress options (frame and quality) to `MessageInput` + ## 2.0.0-nullsafety.8 đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.7` diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 342914d4..70a02f56 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.8 +version: 2.0.0-nullsafety.9 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -36,7 +36,7 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 shimmer: ^2.0.0 - stream_chat_flutter_core: ^2.0.0-nullsafety.8 + stream_chat_flutter_core: ^2.0.0-nullsafety.9 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 4453659e..8b4b7fae 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2.0.0-nullsafety.9 +- Update llc dependency + ## 2.0.0-nullsafety.8 đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.7` diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index a231caab..01b42243 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.8 +version: 2.0.0-nullsafety.9 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -16,7 +16,7 @@ dependencies: sdk: flutter meta: ^1.3.0 rxdart: ^0.27.0 - stream_chat: ^2.0.0-nullsafety.7 + stream_chat: ^2.0.0-nullsafety.8 dev_dependencies: fake_async: ^1.2.0 diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index 38742b1e..64923c2c 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.0-nullsafety.8 +* Updated llc dependency +* Upgraded moor dependencies and generated files with the latest dependency + ## 2.0.0-nullsafety.7 * Update llc dependency diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 1aaa2c0d..64612e8b 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_persistence homepage: https://github.com/GetStream/stream-chat-flutter description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.7 +version: 2.0.0-nullsafety.8 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -18,7 +18,7 @@ dependencies: path: ^1.8.0 path_provider: ^2.0.1 sqlite3_flutter_libs: ^0.5.0 - stream_chat: ^2.0.0-nullsafety.7 + stream_chat: ^2.0.0-nullsafety.8 dev_dependencies: build_runner: ^2.0.1 From 404b9ff89e3e5a90967e5ab4801359e1d7435ce5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 8 Jul 2021 09:36:10 +0200 Subject: [PATCH 524/599] add color name details --- packages/stream_chat_flutter/CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index a6a6c535..ad8beb25 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -3,6 +3,17 @@ đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.8` - Renamed `ColorTheme` properties to reflect the purpose of the colors + - `ColorTheme.black` -> `ColorTheme.textHighEmphasis` + - `ColorTheme.grey` -> `ColorTheme.textLowEmphasis` + - `ColorTheme.greyGainsboro` -> `ColorTheme.disabled` + - `ColorTheme.greyWhisper` -> `ColorTheme.borders` + - `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg` + - `ColorTheme.whiteSnow` -> `ColorTheme.appBg` + - `ColorTheme.white` -> `ColorTheme.barsBg` + - `ColorTheme.blueAlice` -> `ColorTheme.linkBg` + - `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary` + - `ColorTheme.accentRed` -> `ColorTheme.accentError` + - `ColorTheme.accentGreen` -> `ColorTheme.accentInfo` ✅ Added From 29707637822a9d91bebf95e5b850ec7a71884f59 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 8 Jul 2021 10:14:37 +0200 Subject: [PATCH 525/599] minor fix --- .../stream_chat/lib/src/client/client.dart | 34 +++++++++---------- .../stream_chat/test/src/api/client_test.dart | 10 +++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 2f536916..d9877a4b 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -220,52 +220,52 @@ class StreamChatClient { /// Connects the current user, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. - /// Pass [connectWebsocket]: false, if you want to connect to websocket + /// Pass [connectWebSocket]: false, if you want to connect to websocket /// at a later stage or use the client in connection-less mode - Future connectUser( + Future connectUser( User user, String token, { - bool connectWebsocket = true, + bool connectWebSocket = true, }) => _connectUser( user, token: Token.fromRawValue(token), - connectWebsocket: connectWebsocket, + connectWebSocket: connectWebSocket, ); /// Connects the current user using the [tokenProvider] to fetch the token. /// It returns a [Future] that resolves when the connection is setup. - Future connectUserWithProvider( + Future connectUserWithProvider( User user, TokenProvider tokenProvider, { - bool connectWebsocket = true, + bool connectWebSocket = true, }) => _connectUser( user, provider: tokenProvider, - connectWebsocket: connectWebsocket, + connectWebSocket: connectWebSocket, ); /// Connects the current user with an anonymous id, this triggers a connection /// to the API. It returns a [Future] that resolves when the connection is /// setup. - Future connectAnonymousUser({ - bool connectWebsocket = true, + Future connectAnonymousUser({ + bool connectWebSocket = true, }) async { final token = Token.anonymous(); final user = OwnUser(id: token.userId); return _connectUser( user, token: token, - connectWebsocket: connectWebsocket, + connectWebSocket: connectWebSocket, ); } /// Connects the current user as guest, this triggers a connection to the API. /// It returns a [Future] that resolves when the connection is setup. - Future connectGuestUser( + Future connectGuestUser( User user, { - bool connectWebsocket = true, + bool connectWebSocket = true, }) async { final userId = user.id; final anonymousToken = Token.anonymous(userId: userId); @@ -282,15 +282,15 @@ class StreamChatClient { return _connectUser( guestUser.user, token: guestUserToken, - connectWebsocket: connectWebsocket, + connectWebSocket: connectWebSocket, ); } - Future _connectUser( + Future _connectUser( User user, { Token? token, TokenProvider? provider, - bool connectWebsocket = true, + bool connectWebSocket = true, }) async { if (_ws.connectionCompleter?.isCompleted == false) { throw const StreamChatError( @@ -310,7 +310,7 @@ class StreamChatClient { final ownUser = OwnUser.fromUser(user); state.user = ownUser; - if (!connectWebsocket) { + if (!connectWebSocket) { return ownUser; } @@ -363,7 +363,7 @@ class StreamChatClient { try { final event = await _ws.connect(user); - return event.me!.merge(user); + return event.me?.merge(user) ?? user; } catch (e, stk) { logger.severe('error connecting ws', e, stk); rethrow; diff --git a/packages/stream_chat/test/src/api/client_test.dart b/packages/stream_chat/test/src/api/client_test.dart index 36a35d56..72dec24e 100644 --- a/packages/stream_chat/test/src/api/client_test.dart +++ b/packages/stream_chat/test/src/api/client_test.dart @@ -321,7 +321,7 @@ void main() { ); }); - group('Connect user calls with `connectWebsocket`: false', () { + group('Connect user calls with `connectWebSocket`: false', () { const apiKey = 'test-api-key'; late final api = FakeChatApi(); @@ -347,7 +347,7 @@ void main() { final res = await client.connectUser( user, token, - connectWebsocket: false, + connectWebSocket: false, ); expect(res, isSameUserAs(user)); expect(client.wsConnectionStatus, ConnectionStatus.disconnected); @@ -365,7 +365,7 @@ void main() { final res = await client.connectUserWithProvider( user, tokenProvider, - connectWebsocket: false, + connectWebSocket: false, ); expect(res, isSameUserAs(user)); expect(client.wsConnectionStatus, ConnectionStatus.disconnected); @@ -385,7 +385,7 @@ void main() { final res = await client.connectGuestUser( user, - connectWebsocket: false, + connectWebSocket: false, ); expect(res, isSameUserAs(user)); @@ -399,7 +399,7 @@ void main() { '`.connectAnonymousUser` should succeed without connecting', () async { final res = await client.connectAnonymousUser( - connectWebsocket: false, + connectWebSocket: false, ); expect(res, isNotNull); From 1a6b2c4d2a3f812403aae0bb692379694189ac06 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 8 Jul 2021 15:15:30 +0530 Subject: [PATCH 526/599] feat: added new docs --- .../assets/location_sharing_example.jpg | Bin 0 -> 25629 bytes .../location_sharing_example_message.jpg | Bin 0 -> 34932 bytes ...tion_sharing_example_message_thumbnail.jpg | Bin 0 -> 12822 bytes .../guides/adding_custom_attachments.mdx | 255 +++++++++++++++++- 4 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 docusaurus/docs/Flutter/assets/location_sharing_example.jpg create mode 100644 docusaurus/docs/Flutter/assets/location_sharing_example_message.jpg create mode 100644 docusaurus/docs/Flutter/assets/location_sharing_example_message_thumbnail.jpg diff --git a/docusaurus/docs/Flutter/assets/location_sharing_example.jpg b/docusaurus/docs/Flutter/assets/location_sharing_example.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7f220379c90fe3552dfbde84702b3a40820a65f7 GIT binary patch literal 25629 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

    0d<~D!mQ-AogaNeY z57-m}=+jwWBYrm}YAla~Al9TeJM|hG7Ma<)j|1zPW_sKRijPAZmXYx#nH>nCA-hq` z7;MgEf_1$_MlMKr{D2Y>K-f1Y9Zjk%W}Y6775D%Vz%`2) zNq&E87f+n>y|cWmPcu@Q9R{Dg^-C$Aq1WbAiY^8tpQsvx{j5l5gMg7?9)S)$Rk})I z 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%0AK`xDR0yF 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 literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/assets/location_sharing_example_message_thumbnail.jpg b/docusaurus/docs/Flutter/assets/location_sharing_example_message_thumbnail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e2b7d624f54a69fe6197dc3f6b014e103c018a2c GIT binary patch literal 12822 zcmdsd1zc6#()T`eHz*|y0@8;P6p%cmAl=;{APoWn(jeVZA|WB&qEgZw(v6bR9q$43 z(dYRd-~0Q%@80{~-@EqVFneb9tTq2NYi6DG+ZUfNW&kWnF^Cud0)YSt=mlJi1HJ$} z0sKZ14|AIq6A>DL9p1Miza{+0096ZbfiBU2nG%w0TBrn`C<$} zx|S?{vf7RRsR4k$^azdJWB!GM&h!Dc;#m3@4$+e@Y3ub-e{euZUI@uYU-=$qegFWh z@}uEnuPd39_`DnNQ`z8P6e@={7y`Zp!Jqsxtyd^eEHQaky`aJ(oeWU+eOHX}Wmc%M zGHnVI0Dy}~+`@v&o-?Wb>`1sF!!H~F%@?q_O?6atX{bUKP_=qtW!-m4EBkc;fI4m% zssJNv+c=NfBdy7BX&z)*?Pb=J;&bH?T;y?YKLF^uA5^fOg}ypqp3EzR8iB7^_&EX` zsa0VK0Q3f2uA{yai(hi^PRjI@OYHZ*jd41B zfa+9CcwM47{*)H#vV`kHXjj5w7Q6=WyDH_sGXlp(qW>*L_IKlO`D};mwY_)kid8l6 z7gvZF<*m)+|te{tbz`ZU_AUMoDfV&B0-qxu6G;?w``M@ zMxb(-aXVlj^&a9?gdo%GK@Hq2RK(=X!vBqbFhOc{#s@j^e|>r|`Xq=VSSHGkGiYu5 z3JI8?po&<0hx^{&2NKm2%NeUSc*J-;d&by#Pb`N|DcbK5N1)u7>9i)^qrP|B3>wd= zX+N-8l;MoiSG(aTe*K6Ezcy2C;{Khw+PaE|X$uo89=;Hk0(5c&4!UIa&psW4KL%x^ zC17Z`k36Naem#N3_c)x&?MY3|yRtAGMo109ui^%lw9?JZa0WscR5JsGLc?12?r@U9 zqY1wAyVubVhJVj9Hj|l$m{LC4bQMTc0OE^c6-}r%s#Tlbyu0$^P!i-il-ZEHfa^#C zZar!KuG6Z{JpVIT{a>S#Lj$B^^oR7XfehTze)D}SKNk<^8Wxya9)BmRF9Ul4pfCJN zF=^0I{V5(HPtEqav_6s5EO>!tX+&Axp)v+Qb^a39N`(;?HOgM_70nJ6BM;7=KsEjv z6j0V>)3E+^Q1xk7OeheozqsN5o%>f4Hy!m`wxyp?0YFwRfc0q`>o1YXk}LeWqMKCu zVC-%%S8kIvmjkth=_}`^5|qc9{iU#p+4{@(o)vCOGQMO|{se&jvMv(OOG)?RT59!| z-N&{9Ch|91Uj;5k8I;9It=DX6J*z-(RCZNo&s)M>x9j-;s28UFk(ofKT7%bqfP}Pv zrUX=)wQDX+n`^+;0aBOzYFFQgyqjOY%C7v%p|X3}Q~ztOhW&CLf%{aV?H47u zRQiJSp)-$PSiGP7KkDqR()_|waR1oiH99Sx1?6#85JA6TwyHh=0Fn)J2r!;1c$oz; z6ULw%0DgRB-4p;|eRh#R(T;xmP}cWG6dY8<3K(nbSQwAuyMOq>#^X6B%qwHAhXR6x zCE_dS`!EnGJP@gYH4rI46i!0Ko0{?gH6_(eFWf7*kVLK@griJ_)+`ceWrfxy*k7s^ z3<&nBXkC>o7_1-N{N+DfE2!dY8sRR^?+(3zXFK0$2xv9&^vhQ8-jp9`cr|l(X<1IemN3#5P{&;Eu7lrEqr>^P3lX6z`xjKIN&>}+bejjjKm-a z5UqtXgr}GAIP>P-6%HEIbkn6o+ddKrokafrE>PhfgkugoS;Rm4lOu0?ejM z$^MXl`uhe4gamA?ec5z;KeE5P3(Y?@`SMSKl|S}p2;(#?foZ*d^)Qq z_Q%wP+TEUr79+%6aXW>*Bc>K*oMS}m=#GB1!dmJ7G6r_U1#qiy@_ry=TxsgZu{~o$ z5o0?hbn%Exq4kX7)*myPpuY5&GC~wbViz5Z`h{*pNna>r)Hcy#fH+g7C zx7kLEthvb4rQ#jC{ejpJy+|YncZ#b)^&=u;>Fo28-WcqNcHadfb8ZBE?Z*S-*?ly2 zmoRI;>3@%;g&Tx->J7)En&%e}NQ?n@ev|Jt~fNQ!XIk%@j7td{t)ODtr+9*5~Af>+hT(U@1^+ zk3*(Hp6DA=xkus1&0*Zd%gi0LvQDr0!kEibJu9P@=E0>qWPjrhA)ca>)Im%ip>v4J zb5gybwh;W&n=CVW6C9My3%Szl<4hD~NFG}bo1V7cq<+p{!ZKx0v1Z2YoQ=3|jIY?Y z2vo(g^h3qmc!TE;3qS0!4b7ENAVFyE^`6bG&YaIr#r|AP9Q~sB`qd)x@{=r6Dmn83 z4`ifdX|lQP+ZZGRLaKWzy=@d_4Q~j;ZD;jtYy3fKT~~NoEj3vsuJdsA#|TCCC!B^{4EsTWov4(>Jac4W4H7 zpYdUhq=eWt{`XU)od*^R{$%o`dOkuM`5ZO&AI!ySdt#G&VpvbeNFY-}epP<1c{fKO zCk>ZPw9(99WHrw18SpQ#~hsX$~)w(|jPnwyc1^Dwsed$2M!Cv8uS% zNmI(bvZ}qL*m!K->cKl}Y(5N27ipSdx9nd%t;>;?XnRxRylNktH4FAfHcX7qg+!{z zVOvGw6A;^uf+qI;tcL)$AyE5M@UsGaerp$^7Sav0VjP=FReQHCLv${`Rd|L|1_TdF@I*=_o1g}O$txulgyLnZzm~y zit^J7wT8)vQfE}xNx6exvy66htTv|v!g?Mx1f#9QC^NTZy3{3;!hJ;mmTJ-e+YKGW zgInL4P}Mi+=pDqh6GGn@-)iQJWIYYEEq?kEKcrv@>0_R)vt89N*B>~(zm@dbe__SFikiAArzNZtBT>d zeqTKX@USuk;^eP+$n_CTyHwsIHaayE_2I#e8(5`w)E#tE&F-r#cnNT+fp*`skkdM4 zj13!X`$nU43%Unk(OVl8L08Kb15O=mHocMtu|JmiL0ib6AHhK6%6idBccj4_igWv5 zpm)(}!E6V8U6+uMoQM35`mlH^dh0hmX|x;;k_g87U-Rp3owRfO7TcUhkS8j5`$z9{ z`D_FmprZAC{E8eGiEzeMI$7t$P_IoQ`hmK(ji0Gnn36T#R-taO@7IaYVuzM>#J2DG zp>^I=U4}s$uW?n|Nc1@YJA3IgG~KlSnr_&y(_0oh{9F{}{jdKZ3ud3I{5JQ5T<4zU z@vTjce`JV^&R?W-6XMMR^UY72dGEK0x6GH=t`Qk=lv>?aD^q2RG?dX0>~YZ_*3 z@!R@v0a*X0a+2-;T}>%#&3JV_@pnZITC;g|=Wq9DF@T)(+zu?`pIW2vjToz`im3=& znBA(OOm*aIb&2LlmG1cv(sHBDwJn!HN9cEmNDJx5bJ~${>qbippx9Zn1l>c)DpwJ* zS;Lc4MvS-3wajD=${HTyI^TffJ}j>Cn_0>`J-oV&{V4kTkpd4D0hX5W0>Wo{bf(mC4K?0H!IZXXGam0(^_om za#I2F#EjHKDqS!AXjJ%=0@lX71L%C-EHKBm@a?K7bFx*reWv6eUR%FGI~g}w7(UK< zSNh|mQt9V!91k=3f|n;ta&5B`n}_MWV#bTEbU#+nZLI3}KNX`7{g+Z;J7gSh3+X_=9Nz zqL4Sd%XT0x+Q0067-bC;Q2gzfT4FAshX|SDs1LI(K9=7(BmKGW$garQR#;JYU?z{& z*}p?g{Ea3PJW7EKas9CwMz$E@@7EN1EnT-p7s_DOR6uQZ0Tgh#V4GEHAWE0tShgDZ zS_0i{=2wa4Iy^2K&)rm*YY(dHDz<*npeg-%g{9?R`t#}Mk2JxBBGDt)vAY68ZS0F= z-jpioZ?{#lD;=D5ojT%5lTuF|TURwh+Ao07_MGM;l-w{8{6{1K3|YKbO44@ZZ{in( zl-OFm-xx%|D}JKSf@ywj>@X+&IHTwA6N(#jJUIT40X_dc+Q(|uxZ zYYdOGFVL3!<#Pt9~wLF zBiz#4WZl7h<{$5l#{aJoGE{td_5~tLFBzE`5vO>v&fY)q#Qa1|oBU<1=&qwvS;C>Y zvgpG(qK*KG7|b_g_u-nti}4OfW?~z?H87AZd1#9MspOlFRY47sgfINl-l3%6nbaf^ z-?pKwrj#AvdP}({>5n?l7@sv2rY3-hXjK#28Q!^{Vl{}t>t~|g~ zOR6 zjM53<3JhX?LMsP}p`t`c{13ZC&~k6Vukh$ByKhzfgJ)5-LCJp*{*E!S`8#Kr2HGM7oi7h zryrBP_%qe0h0CPuo<{JRz%omqI-v){9#`PpV&^AB5t)_eEjUy|vM zjs^@ATg~bZu%Lb$J@Vo1!O*aO`CK1zFR6^%uROyr8IKC#gM(x1;62{-KUX zPbXo$v)y>+Jk@yZ19rA#!ef0mit^Yv95uW-N2vjQKkYB6jpUj$iJymaI?1{9`0jD- z6Z@u2j=eDMf_Aoa=lg72b#~{z-udLS7J8f3wifd{xE6P+R#O(F0tNhLcNbP*OF?km z?~=f0^*5$VcMu+@kW?)5wC->8`c7lk_btEBWvS@&t57eOSRqyv-l5}aUrW%1Aqz!+ z@3%>->406dk9;#rW|#DD41Cr?Zm~9P&dxHB(uJL}9jT)A8V&@wFuD>wrcpc9zGIkd z=;mY^ey-8YY}7u4qy3DtPZRS?Ljd&n+HnmEg|_~$P&C%cs;NLh;F_DV??mKyXZdd8y_S{A;K9T~V`M#Ef6w z3K%1X0I4mEcvvP!a!=ZdS=q$%33dB@4RfZIVGp0m?=etivE{(M zTN)U6LxVVPFb^zYMYjUSw_h!SY4E(s@iKxwSwFN~xc6ud2d0V{51bmV&L8H`gbyxlDPGpBg^C8&ksTI8jo75sIeY z`f|ooOR5_G{l){@t>xOvAB*7$>0#eeUm3cQmUTJS0iksH`&oy4M9oX5DFKbc?Lyf)Y0X^nu^Xtci-NI_-t<=#RL6mnCCL z{qm#dtmUiQu!TBOPa!J8eh|yeR6*>Ruy*#a&e)lIlD=Q93`z{0j2rAFiV+0c1~F&^ z-S51bq+Jfr5Md5}0-G$I$T?lfy<*Q1wZbC&9{n>BRvMC(eM#pRUUMWT-Jf!K*W_#?YAFKNIUc z8bb0z;&`GKIFY~MZ&Koq8E3!HvZ$FR;(d_Xqz6m=@|CB_pyk?2zCV&$g=ehYC>!T) zl5oBI`y|C-d3&*EILoB-gE8*Tg%;~@o@^iMpP|7`I7S6_9}A<-SzCU4AY%%kH}bys zA&GKCcPx6wYzu($V_4!hc7^L!)?36YoqWk6hb6oR$meXO?WbLgAyo+an1!#=2+G5# zs`+2>!g?%BgrmawNTfP@oAPTVdQA0tXnHp34RHjl`C~PgxpzjwH`1qPJoSoeayC74 z$fqdcdYSqod5zJ2D)Z*XM?4b^qWA_5EDJqB8mq@;6grkl)+BQDN)%D)A?G9Bq7ztg zwg7z|z5q^AM#~%K^GQW0M+kTVgq?Pb4WRfo-hMT+T4xN+i~9%KEPiKJ_wa5Q%GF{a zmp6`-z|Per6VV3inA^mR_@VoFKK_xzmOlECmVxC&pk6wL<}5*zn4T4iJ*i<6i?;5Bx9U+1+s2Qi#i%uVRKc`QqO9={0f>}%WDcAhv z=nE0PjS99IOFYpM7$0eIe~B0VvFsTqX$g4*Um=1>PC+YKqSNg)TuSId$S5nWigk*izMAcYEfb|+ ztKrP!5m#>_t&k{5Ga35V;H>EW#7Ea;c6FHHw!{r@hnyB&tX>V9{5QQ@wp8ySMVByhPC=oj9lH@IO2 zwNx&O-N6|~@cGm1kqo%08m-vK>6JCU*h!_Nqa(o!v?o_vS=_q)e&yD5{4iO@$p!aAY9aMq)xim|GD@G3tL8UM93GqJ5st5eEq9mXE5I7NH6v%gj=bTI-D66VrP95( zh2+PIMw1#T{!j-ih{_Nt0cHsl;vKLg5}?LDz@l_1zQb&?5HSdI=qrDdvzQ&e6Ien1 zSiDa0>dv`ZaQoqS#uzc(eM0-j3NPGA^uCM=JN6;}jKdHWF^j}6mD^J`AW=6Hbl*lY z2s0I#sA(YXQlnl}XQTB8iYWl$IA-hiT>p~$2hc{Soh;J}6ssrCTMX1IMc9D~tYbs2 zeRo-=SPshAMn4P*Kv&UG9gQ{$eO`5lbE^@fe@1f{5mN9ONsbasuC6^ENIq9jMHm4` zUtGYg_4EWH&H$!df-MVv#yBT0n!etFewyM-K}ReUKb`hVNrRj#l;dzv)Rj;Rel3KV zLFiBC;iGO{BzZ*gxkyt3tx8gtFwi4!wlDN`$dMKM#-t5(7H?QE_MD*<7K;vM$YJ<| z2zwvv^2;}b7Ac!1jDabcM%cZrW>fX(*!v$yNDeA=UuH&sVw>h1!KOK%mvBSq=VbAR z9xtg9-K3pb+f0lqu+gM63_<>Uqw!U+wAjL3tBuesvoN=@{Tm2@(|iLYUlxQno^jYm zk?5R9qb2C@>ZbG85y+%z3y6H;o%WxEF=A`J0G2Ze)Y)!rS#3B(=87U0 zPN4OvFvoN$9@G4+uUF}5_=&d()hnTyHQpPJBkFA}<%7~D->+yEvZ~RYE+O!d97 zn0fm&<7OKM))Ltrw4sP_eG$W^+xn;0DZBk7cNPdD+!x}PMC?^!{gHL};u>EOOA&_; zFfUI@8YIq>BsFE%nexar{P>|TNvEn}k-b$A$7J#|<1xa7emd+1&Q~9P-vqC^7)X9d zpA+voe!@2ckRDapy?XF>&6H47d6W?9E00{c+GCHu)9C~amCFC&7h#ycf ztFfsSyuJBixzexEw_kQA2Yr!vj<05V(ewi*jCDyfxwt>SSYO@kfQ_@NZL5jxS2y z2H5l#Dootl$-QZj`loKTz-Z1H0z~b$1UH#CE`Svt4})|5J2@3}wObT&m`6!PIqQMw zjq>lZ^@zga3`2=XOg92r^UZ81S`5&G8qM}!_NiLkAE;qO&mMCU)hj!Z{hH5}YBq`Q zH?Yo^Q8e~6+g_Piu59CfTs3%h-}?&>axB5Ky2#Wx96SWJ?lHmsSL}GS$kpBKNHzG+ zvvzzkzVTH$M|5B=88g{@eh?GZYe*|MTeWdFYHTUO7D4v{fP@20F<9yaqipDZNm--Mq5U6l*?_1)8|UM#_Cp5i`d zF&WhQoWg#O&ty7g`#&FcUjQ)X6I*B(K%@V}<8_xlK^eUNyi3rzR&MWB?|bM$6e~6W zf`x%aygr1wd@BrkE`<%S!Qqg@<5I8+K9on$!xDO6OQ~QJ9+AFz{SF`qBmkVz&}I%M zMk$!fKyo>+NjJ9l%=eKijkp&=e|`@tM%W>gjh7^?G3S^jt2)d(!s0z0DWR3M&zuS^Zz>DlrrB!zZ_Sq>I zJc!W24N~m-gxvk%B>VeGfr9kiw?#_w5*y-SSdZNa+I}ef-{JkMxyN+@zjZDz%;Ci}&ZPPj8sq|{zSY4!T z{g=d*F!*+K{$w5^L_V7YYq{zJyow@IXb;WmmX#HkAmO?;;Fai=o&5H?Vf79*3nrud za(sL|#5FhQTrgeE1qcBS9{$IhhF5a|2am%h^pKoFP#ysnU(c43O5esV{QAYitBGiJ zqN*k>K&vW}eE3?-hAw2?i2zg9psn%fbXDa7h**(xk;kEBf9V`HVn3Bv!fh&{sBbxY zo)Zb7-r*Ow`l5M=DQsDHvlJB49tI&C@SxOvgEtn**Et}Y-ULtX9Th>!Ke(0RH@K2g z&LqkEhW9JOq;6?hqlNHCkwB{VRc|rsavk20-?rb9Z*e6a+px`!o>P!A=b(c0_D?zV zj#1hmNx&3Zz^tv)OJS8Se|GqiAtjU9Dk-#23>wvRLhgVF;H@VN_XP@qa8u2VP#s=Z tMeL?|K^9YH6r$o0qE~~k6f-C}`jbvf=^+$!Y`mv~-`fS?e*s*K{uk;JVx|B9 literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx index 677a2b85..f0514f64 100644 --- a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx +++ b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx @@ -2,4 +2,257 @@ id: adding_custom_attachments sidebar_position: 3 title: Adding Custom Attachments ---- \ No newline at end of file +--- + +Adding Your Own Types Of Attachments To A Message + +### Introduction + +Stream Chat supports attachment types like images, video and files by default. You can also add your +own types of attachments through the SDK such as location, audio, etc. + +This involves doing three things: + +1) Rendering the attachment thumbnail in the `MessageInput` +2) Sending a message with the custom attachment +3) Rendering the custom message attachment + +To do this, let's check out an example to add location sharing to Stream Chat. + +### Location Sharing + +Let's build an example of location sharing option in the app: + +![](../assets/location_sharing_example.jpg) + +* Show a "Share Location" button next to MessageInput Textfield. + +* When the user presses this button, it should fetch the current location coordinates of the user, and send a message on the channel as follows: + +```dart +Message( + text: 'This is my location', + attachments: [ + Attachment( + uploadState: UploadState.success(), + type: 'location', + extraData: { + 'latitude': 'fetched_latitude', + 'longitude': 'fetched_longitude', + }, + ), + ], +) +``` + +For our example, we are going to use [geolocator](https://pub.dev/packages/geolocator) library. +Please check their [setup instructions](https://pub.dev/packages/geolocator) on their docs. + +NOTE: If you are testing on iOS simulator, you will need to set some dummy coordinates, as mentioned [here](https://stackoverflow.com/a/31238119/7489541). +Also don't forget to enable "location update" capability in background mode, from XCode. + +On the receiver end, `location` type attachment should be rendered in map view, in the `MessageListView`. +We are going to use [Google Static Maps API](https://developers.google.com/maps/documentation/maps-static/overview) to render the map in the message. +You can use other libraries as well such as [google_maps_flutter](https://pub.dev/packages/google_maps_flutter). + +First, we add a button which when clicked fetches and shares location into the `MessageInput`: + +```dart +MessageInput( + actions: [ + InkWell( + child: Icon( + Icons.location_on, + size: 20.0, + color: StreamChatTheme.of(context).colorTheme.grey, + ), + onTap: () { + var channel = StreamChannel.of(context).channel; + var user = StreamChat.of(context).user; + + _determinePosition().then((value) { + channel.sendMessage( + Message( + text: 'This is my location', + attachments: [ + Attachment( + uploadState: UploadState.success(), + type: 'location', + extraData: { + 'latitude': value.latitude.toString(), + 'longitude': value.longitude.toString(), + }, + ), + ], + ), + ); + }).catchError((err) { + print('Error getting location!'); + }); + }, + ), + ], +), + +Future _determinePosition() async { + bool serviceEnabled; + LocationPermission permission; + + serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + return Future.error('Location services are disabled.'); + } + + permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.deniedForever) { + return Future.error( + 'Location permissions are permanently denied, we cannot request permissions.'); + } + + if (permission == LocationPermission.denied) { + return Future.error( + 'Location permissions are denied'); + } + } + + return await Geolocator.getCurrentPosition(); +} +``` + +Next, we build the Static Maps URL (Add your API key before using the code snippet): + +```dart + String _buildMapAttachment(String lat, String long) { + var baseURL = 'https://maps.googleapis.com/maps/api/staticmap?'; + var url = new Uri( + scheme: 'https', + host: 'maps.googleapis.com', + port: 443, + path: '/maps/api/staticmap', + queryParameters: { + 'center': '${lat},${long}', + 'zoom': '15', + 'size': '600x300', + 'maptype': 'roadmap', + 'key': 'YOUR_API_KEY', + 'markers': 'color:red|${lat},${long}' + }); + + return url.toString(); + } +``` + +And then modify the MessageListView and tell it how to build a location attachment: + +```dart +MessageListView( + customAttachmentBuilders: { + 'location': (context, message, attachments) { + var attachmentWidget = Image.network( + _buildMapAttachment( + attachments[0].extraData['latitude'], + attachments[0].extraData['longitude'], + ), + ); + + return wrapAttachmentWidget(context, attachmentWidget, null, true, BorderRadius.circular(8.0)); + } + }, +), +``` + +This gives us the final location attachment: + +![](../assets/location_sharing_example_message.jpg) + +Additionally, you can also add a thumbnail if a message has a location attachment (unlike in this case, where we sent the message directly). + +To do this, we will: + +1) Add an attachment instead of sending a message + +2) Customize the `MessageInput` + +First, we add the attachment when the location button is clicked: + +```dart + GlobalKey _messageInputKey = GlobalKey(); + + MessageInput( + key: _messageInputKey, + actions: [ + InkWell( + child: Icon( + Icons.location_on, + size: 20.0, + color: StreamChatTheme.of(context).colorTheme.grey, + ), + onTap: () { + _determinePosition().then((value) { + _messageInputKey.currentState.addAttachment( + Attachment( + uploadState: UploadState.success(), + type: 'location', + extraData: { + 'latitude': value.latitude.toString(), + 'longitude': value.longitude.toString(), + }, + ), + ); + }).catchError((err) { + print('Error getting location!'); + }); + }, + ), + ], + ), +``` + +After this, we can build the thumbnail: + +```dart +MessageInput( + key: _messageInputKey, + actions: [ + InkWell( + child: Icon( + Icons.location_on, + size: 20.0, + color: StreamChatTheme.of(context).colorTheme.grey, + ), + onTap: () { + _determinePosition().then((value) { + _messageInputKey.currentState.addAttachment( + Attachment( + uploadState: UploadState.success(), + type: 'location', + extraData: { + 'latitude': value.latitude.toString(), + 'longitude': value.longitude.toString(), + }, + ), + ); + }).catchError((err) { + print('Error getting location!'); + }); + }, + ), + ], + attachmentThumbnailBuilders: { + 'location': (context, attachment) { + return Image.network( + _buildMapAttachment( + attachment.extraData['latitude'], + attachment.extraData['longitude'], + ), + ); + }, + }, +), +``` + +And we can see the thumbnails in the MessageInput: + +![](../assets/location_sharing_example_message_thumbnail.jpg) From dc005ed86d5e6f0c76a94d1660fafab09691bbd9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 9 Jul 2021 10:37:16 +0200 Subject: [PATCH 527/599] fix nextMessage index --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 69f10d68..822a2f5a 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -863,7 +863,7 @@ class _MessageListViewState extends State { final userId = StreamChat.of(context).user!.id; final isMyMessage = message.user!.id == userId; - final nextMessage = index - 3 >= 0 ? messages[index - 3] : null; + final nextMessage = index - 1 >= 0 ? messages[index - 1] : null; final isNextUserSame = nextMessage != null && message.user!.id == nextMessage.user!.id; From 4ef365d97db753738623961d404652cf8b9363e1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 9 Jul 2021 11:50:50 +0200 Subject: [PATCH 528/599] add first version of theme docs --- .../docs/Flutter/assets/using_theme.jpg | Bin 0 -> 14067 bytes .../stream_chat_and_theming.mdx | 65 ++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 docusaurus/docs/Flutter/assets/using_theme.jpg diff --git a/docusaurus/docs/Flutter/assets/using_theme.jpg b/docusaurus/docs/Flutter/assets/using_theme.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b835d9316d992774eb9cef23fa50182ddc601699 GIT binary patch literal 14067 zcmc(F1zc6nw(vf*lprD9Dcvn4-67rGNF&`K9ZH9ENOz}{bax99hm?~14tnqZ-FsjB zzIea)?U^&NV%D0qCid(zkMob~0GhP8lsEtafdENJ03MfsAOIE)4*nTDA`%iJ3MwRE zyg);HfkB9c4GDyF_$2s{NJqs;MGpDB;bdm!e4`*DD59YM_N~6VqkDS#BW#0Z`~5XmrrySAYNj0U%h2rvLUpgJ59c;Gqy67XgGni0&mVNa#Y=<9Gl0 zV}yU|TaF({>xltz@^kKt^}$?)UfUr3)UwWs8N*@OVcA9q&DQBwFFk!3X*Bnz&wrYx zdlNs%uyQ2Qet&yKUp?mF;FJ`wx{5#&g0mHaU$J5QhYA2d$841*+gJ283Ie@-YB(eT z2!H zpf{D7xvAZ_8L~PTYZXYl;0OQ&Khyo5NSApBuyW#z$#3xV2*b9oK~sSYOSX;Id^&~_!RIcdg ziQ3y5cb3T}xtt?3J@or#A0`u&&%?FkY-~Pp9@91-mS~N)T6zY>TE(@R+Pp5V+-%i{ zl3A-(=Zalg+`m(GmJf#zDnh6c0e}{j3;H64Y28Bz%ojX!-&R*Buwz{J<4KF(4^-@y zLt|sp_-+Q(*~9AW*rz>8pTLiQ#{!t4_yKj}400{ydM0IM^i6Q(dRL2oVL_4(jjw$p zIEea z_d26c;jRM}o79q805Ge$n@rLiLTGhdVII6(x~nTo6pYxEi>UDf0QFt`5j@=r=nq3( zaW#WZ12>ejHMKwWK8-fW6P}B?xI4bQ8SN>z^THknFkE~@mus@fTo%s92wEhVT7n;p zCWi?79H{yiLZh{(+x!5!H6CK8uK5kOWgG0*$5l6+7b7=jb0cn^{A(jw!lzlET`IKA z3b$U?-aP^)0McC$Ze%^{+G>aK(7DlV!`co5ebut{3mflbi-ubb0H6~S!}|RBLRbrN zvKD}*tX{Bs@8j8gbfa6yG!b#M6g#}AvDG+BGk?qM)E)>1fQ#>CfWS2%@E!n=?qd`- z7A{phTWhy(SiF}Uantw+-0*wxiOnFUl-ZH~6>c?|`MI&7@l?zG>bUEzfUv{XT(S^w z(c;){+Ody6O6U4p%0k?*=KNOU&XW`XfL_pNKhIWhxGBS|sCYG5a($`N2|RbVg=-Xu z$dmppiIpdU-;H<51|_EEFQ>uQBK!SOgU6F!fueIM?WGyec=-n}f%WyRTCM-Sl^U)9 z|4m4Ghd5J(BbpX-5r98 z-3KK)p2RM%hb2!la;AZ1)Xq6qB3%FNMOsL=i6P4!K4d|Ig8t{_4h94TS?_+Yc2H;# zJ#I|hNn_-!XCibU6*$+#>Dyz9spSwHqaW`|p45bG{~@huUW2LlYwWcV!#lH101z(D z$eTa*(9BQeZoT>>JX8e#IT-+^M&W+7IrdC@&^Ck@`bAe@JWB~h9T^9pDv)amY!>o; zRv#XAPJWFs#R%YAI~-!t@?Nq6P^kQxy+sTJD!#k`W}Kf^nyqUe`{=KV z*Gm>Z3|^eu*!M3rWVs30qZblI-*yAg+{o)7^CtOeozYeA-tbjAc>y#KMOyDGekp?H z`fx85&@ScJOTyOM_t!+;{W+`(f4n*hHNcQs{Z|f0zp6REx&CI{bcJE5%hdo9C1GYQSB3;)tI~wim*`;Y z2SLIPr(Jw37iaJO%4n`vOeh2Zwsx3R!8Su9rG=6k>zpgAH$rEG1_0<2cr&?byQxi- zk5f6S?Ty=Nwj;jqpX zY%*1w%s&vk>PV)OvxY`6_s$zZksqg80wC!*kO!& zD1CQ_C`vdRV;?$U<29G_b@HO&?xCdu;uKCa00aYqhJuEJgMxyBhA=|`zny}Pi9zxL zivObiy>Z_(P!!{#ul?L_OUOT2tE=FaB?vp7r`OVEHDD!WzRhx9_7(Y~^nb;&{J9 zAmy_ER!H~<$mnm>-ypw<|3H3oJ+VVjPmsS*zajtB;NL{B{UxHH#xTqEiL0Q+v<;uq zA$Hrgp=f*C?5-;@_`%{>g`K>`j{dJ+8j2u1^%5MW^t+ebf9vJ-7cOhj6P_A2!EPSP z*fpnz*cUjjw@}R-7V^qGYS%!9*IPSyP? zSg0~IhT8p>LD;}8D&@(tL5kt%BD4z-K_cl&hz@UPQcPJVX72*o&%W82-;254=!MknaF-HcN|d@ zL_p`^Yq=cnT&=5#lB1Ls$kd=!3$Bm3aCrkwB)jZ8ZM1<~p$>Kb!jxaTrZ~sJ`LLP< z>Kc7xZ*b2V+K$Q*-rW59lC9b$qsC!A{Y#Luy z_foehMWMj5oJ?TLca}uj!HtA!+-O>u9tiiMwbkAiX+?^Gs12iL=(#V@9X0#r@Sy zCb-G^CRAaqOW)&XvM9d-K}sb5$GnvmW?UkU`WNskvDqRSyv~vt=1dO7DK$^dt1q*( zkwuq?>KBu5>?3SK@>kBQ`NaFa^^kD+G6p?nV2+KBut|%-Eg`lc>as`Rl zz8;1sY7YmxIXx|e38Zc7WPh3`sax)S6jB3rqbalG;^M|p%`V1w-L#&dsV z)jXrGTg>Z{Q8BU&`XxfZv-3(SeY%Y&2QBXGJY-v9sYYhmz5IOn8B(^h)C4cJ%0M^j zv7=k+aJ66quH2zcI0YFQJ;Mq=%Lsa(!`|?SICt>|v*Ol4@~i8`3xjyxnc76X)_!{T zR9s04u39a7a<#A3#+AuXh3xgz$Abp6%WjnB(%+5UPKe0Km}-96Zf`dvC9djezkmPV zSwy8K(ff4XC$E0J<=m@v)K6WR=IW}b{_d29uGQoyyL8=$lF%I2OK-V6zSwnF{`3uc z9|@wnhktdF>=YXrlS@&Bi$&q&bP5VK$a|UB@tMFivqzDIP6AWRfK_uB3i4(8pg|cj zXU!)q{Akg`5ann^&M$OQI0+D=?Z)v^Yu*abdyOsSmiU|>-(}1mxHPXol9saHCA$Vl z)viJN?=|h;wMPJb0qyrh9B;7;h~1~hMt#*0#zV7Stb$NOI$hMeR8Z=|a#{^9vNu8Q zY***Jq#lW1qcFE;Ok%$`I?(UJj|>jzZ7$b+;pXP{UZNj2qQ&Oc)$&;f{lsTy#t#@d z5FVSfF$7nH>klcS_%K2Y_dNeL^IaFTpQC)huaO?yXg2a6=cSO3!Y`Afjv#F?L;k=y{TmAKW{u%Fp$@Mt*Q(@Xa@_YgYI-AMoNHQ0tJ6;}dG&UejloJ*1($H)9B( zNADP8d{t`OB!N3b8)=RU4VE7WbfHH+Q0$p%9$5(Vu2$rYn2044kCJ`_;5EC~CpCVqBSt6`s)SVh)I>${fxA2CKb?DvYQ-QPG=<`kZTfe=OYz?6eXpM(gBIYpt+l9O6@DF9+0!#n9J zn7S%kqKM*|Xrr>s=2Ocwv~9ypD+U&=&t|5D!IKq{wo*nfCb(m9>4hHwLS$=bL|#vsX?pPZ z;*2{TuI7B27y*-C4T=mWf^)k#o#2M~1y(q(J+|r@&vK$rPf~oMro}YBUVELMW@QCY zc|#O~W_Y}{;|g*s27QQQK7%91r$cn{J~iuVOXN{v!i*+la}^et1R1OIduu1DoAa&8 zo$Z&&t8tyslsbp~O8G5$>(;093Dg0TvyK}q2Lx2FJ-HK+G!bYP91;oA)pE(A4}7=R z7E0U8JIr`GuB?mN&>*u!?ks+|;}M`VuaWa!MO`tMKR*{+pS*JTY1~QYi(z&N9x}V+ zYw76CvNs#9HyJ(LTc}||C`)nIWL;=W<7zO|=BhDmIc%u$T~~@ejYF|G#l_h=&G7`SL=sK;{z3hYbCBJ4Q5j|D3N;CKlJAGBcO>1cX;Lj;~_Gu@VQwmPs%jrNAUNqGh+Td)ds!N(NN4z)$Iv=YF1bpeU@w zxW37-T+s=nS|6SGGCcxiaJMf!R4@3tY#+fjl0>grP%HIPF2EHA?4BZD)2l{f#PhYDW6V+dQ z+tk$g9|0AUe#CdecJvP3pEp~%QD*!Ykx#b!t??&Wh??!1t9CU66&k~8Vd^U%G`O&E z=@71LA4+7}pWe;S`(_L<$9={AKu0%|f?4*hKyTy?;#$#a#f|v`+RZE9EB;W2yW!BOg_8Ev3MY>a z^G(4;F^jR*?rL8NvY745o}UunG4E!3yN|cYw68@pd<&GlqN}r%51ySL9#&m(CtYhE zH_Cu#ebx1Ah%f4|5>Zq(Fv{-QP<0GAflJ8w+p+nXAaF~rK$dzW#<3i3-Gn*kNL&70 zg)pz6B~H%mU%|Qq{{*i6_=oymz<+P#57;&SU)ugJ!T+VmMd?2Z7RKGdgTAJ^7uyq| zU_GeH3*=2*BXTdDRD{2?zZJtAeZVYN7EDnVR8nruy27Hnz#_kRO8*r8lp*P#_>b5> zH2+!ppRxR7ga0VvU)uRY_J`mfQU3=2^@;A|=mSPi$MUP(s5L})nsh$lhw%8l`<`3G zXaX(k%cSWCwM`XKc!DwZ&87@**yjA5AW}3n|Bf zxi{c-C&FId+F^qJ`Jz2;J9`fYr|6$IV!h?~-MJYhF5W>K44r;dAp-92^^UnjE^7vU1 zskj!FzN$RMbrhZDig4Pl_le6@{CSxIO2TL)SLdEH4IbqW z^~j5N5L9)0UU?km3xAR|q9?ipCB+VyNq-b$D2eOt-p3iL7AQXvZ*VZ~leZkmcDcwGBLsdYvA~9-8*GpBGj`XBhIgPbQ#M2{i=ffe8bzq{*$;M8|uw>m)YX| z$ythX;eijH>iLBD2G&wl1MAQ_ES=^<>_0TUiRRnhrd}zH!fcR#i7glPc$o|~`sl6R z5cV)7n(74mc7pptx2F=EnU_q|(|Aih%gxnVdI_tuB~&vqy6JvsgfnUR^$2MaHQ-*> z#twi*?;x~mR9u5@@$d&AhLslk`OF<)?P1q5H_*$cGZug6gl4@ z0WZEmu8{07IVaHuZpCnNkt^j^I8#lK1y%4QZ()0Q-`IV$nz*%Nl?0u}i44J&Qm37Q z?U#v3kv=KI#LT(^*+BWTsXMwirG^&aLm24_Mb=~Lgh$8AVej&HS6Mw##SrVdgN@j% z8>13t?cc5;$5e4Mt6pWq&BeNS;y<`v=~6eBytmSn?2*IkWX5q&*0)WW=g;Vj^}^^$ zom1S9#$;N|>v`UY`g1YELp^O$@WUD6(gC@1@SLFwtO7=_^d2dF`%qtgqq=RIhs)yY zD~m(T2joR&quH!h_sr3FM3NkItX=wO^H?1rpwaDA?~}1PPs4%YWqhGYJ?ro*dw6v} z9ufe`57|#Y{Ti#^@N-k#oAu|06&!nJVREq?v$UdegZNQGJgGmG5lGQ=12~a_pc9HS z(#p@Pit@Oq^fI}iiCW7Tg@%1ARCx>D!1R(nn!Uf?4b_Sc&Bw;99ZKy0`MWZOF zadQZ0l1yKSNJ4{zfB_g#u7CEL*q)l0aRBd#2_p(upG*J7avDm)1gD!aEzzRK+k8)LBxbLA8aR9597L`P6hMKS`4Ej>qYU8_;oR% z*HxC-N|j$wqqfQ*e70`nw%4qjTX$OEt}hcrt{%H_R}6rmN*v*B(NL`wW>H~CmWU$u2tb;f(y$l10Nt|qD1y45$jmD>b70wvWhmT zSQwXdy45UYbJ>Ayx8fRTQQ6L8HQG|_zRXh7f`j6vc)vwr4n_$bSa4|Shw+fCyvTGK zl;>aR*wS@oU&3K%5^Tg34|(|Y%wpN2$sv#vh@4yBlhTjC&dJ#YM(bdhe)#H5&t&;h z!BoBNA>6iLd%ip|9#_j)cV(AjlBZJ=?lK-LzTW<=hvcH1jsyO;bD5kF(%D3biAFn2 zzE8nt3+cz74cMFE`#sJL{euf<>W;Y5CA<9y_j$}e66g9yQJ{aF(4eT2L6PfM)q_fl z@$p7UZska7QzDr>x*y5Ku;dm+vq^yE3L7W3yZI$^QnY+ z^K~&#K}fYD!BP{qx}#GCsI|~Q&EVWK;irw~>~%RGKjKxP@zq_SX;+y=F*ONB24OUp z%lB_r_si&1eu(KOx0KmY8QGr!;diiojdUujT~W(vdh2DEo|JttI=VvQICJ$S(+kU# zwm+uduIQHvDYKj#e{t!Y0%g3+k4l_njhx)-+n?jGsFAaDCnF_%R=#zzS;o?9S+la= zI@$245ezlW|x%D>omAUoQkA`ajUdmbv zk>xQYv`GSkU1cver9R_eSwo@YBeb~8$3?$V7ua!qDbQc1MQ%-uh8llD0!VS6sx47w z4tLGLZdM4?^3N2!6f?_qKm}D}#KIhHwow{KBcz=>=no-}a20QatQ`=lw|^?wir#0E zlCY|UqwU@sJVod5zjZncQX6A44g1|v%>U-MteT+_{?^UIg1A=BoeP;I0@)xZ{c z(FtMnx}!2ry{4auGXJsNnWr1mT`XjW(b&OGLT`b&di`^-^|PEuSL`^dsq)iX8j^d_QRa|h$BGPxDp0+I1Y|xhaRi14lCaWQHn^Y4Cuj}1lTz+ zwfjRQngluf+svcqpxQR(?;io$1&XSm6dbm9TPJeJjWpL+fnQH6h^wPxLk2`GVBjgB zpp%3Mq4R7^d%y}&d6Nwx-h<S8F$yq-_@6Z<#Mzw=DQQZZNFSsx_ z&M}~fV6|2J;M-o4?MzFdL!FS{2>Yqy!K@2Y9de<@IQke@9cypZb$Rww-9D()(o%Yy znxOa|ks|U2A;Xu({`70xAG8O|nA@=QC zt#PfzGWqB=bIj0lB{{7Yf+`W$bwI8i1> z7>0~>_v?)5xP+SojAx||_Rn!)tPuikB?nQ*)Dk7%S@O(rFVUcC2=oO)i_mdRf54S5nOY;#`Q8$(!_V6D3RS%J@ zPM8V%r+?RaGOnwKxneE8^VO>u9);KP1ssL8ioV6h%jC%HPFad#+m;5oD4!M(g5r@ zsYSu!KcmNfyVpK??E7mZzY`r!LEmpm2tgvGXkl;2&=I*B9Z)EI5pmgxDUnm3$A`$l z4~kL+e$P%2K2se0{ENnP-0LH&#cS#Rl)&VFn4tMTQ1U<0fn|h1M)Fi66Z%C?f|RTq zS@Mw>%W`BG;v`z5~6cSrMgcDQYT5VMI+xB!NgB$(#85<579vTMv_iX{l855Q#}hAC|1kd47+ z2uni7%&P1tG3hnD@ijB{rlqpVh3S*fAskj|Ib z+@<5pP5Tj`{vXY+Z~3zA%okN`Yvmm=UQM~mBfvnR2p5;hAq1^QC|QweF*xsSq}3vp zm4CVJ9QQ5Aw=NlFWq&nhQ>cQRkpqJJJPkzy8I0OxCo;5|`53j0$O8y-d4j_XJ2A`H zKP8U9YJVk)wMx|c#04%nvCNPe`cJ4QA_Vn3-vN?!N0Qq_d>_(J&qW`B>2W_HvT(Y^ z#G$|*%j;;LKwB+v0n3<}@{G~~4pCUV)736+-e^`{kzw@f&{dpl8r3pcDA|~|(k=QS z5_0WB^?F43(d6Vs!Z1p&+LYwe(j7r>i+;2=?#K^``Jyy>OHfErN%b4RBtM(}OdR%t z4MCaJ9&KLl8Ej`P8Owe;z>T|P?9RRwc28NnQ5#+O{GH~ompsb(W{Hy1n*nmgVkOTr zkr8#-w^PnZ{>2w>X%n~5xUH}<+fLzG)=asYVo-4dN-u(?ZQpI8(R|!&tJ1=mMWs?W ztc(Rw2Na!MB@yvy9F)V0lnCUPCqYwh;Jq^Xv?~NA_m)!bn+0tEXHW#3o%1qtGg&WL5|s?J&S^q440 z!Qyqx&-{yUN@5sU-=?-`AfIY)3m|^6u=b1Wk}a}=LU_R~4?ly=6T~fxN?VRdsWb=6!)IKDGKR2#ca$0{^yH#73l%Mv_T^7>Z;Pk& zvUm6x`ms?v_Juzg3*r)`(I15m^LMcqQe&x(NVxDZtQvcG%~p+aL#d?hB$LFvuU*|B z&HEA^U`!6b%$^C8n7mgt!azarpiP@ZYGzezT5~Nn!ibvQNH44=dapkZHVkbjlBMnC zFTA(gch&alx<2&g`z+NTHnpv}wUt#_z*E8P8{xhw?QZNP0_|vHkUDN@0tb(S7NPxa zol&Rr^rPLKT*xn{BrK)e?qq0Cp;2*SIGohYwX;~)p{E~)yGYTT3}Efi`9a=h2I@cv z=!3?x1h6}YjY_7Cb(|>b1u*6(WY?L)Na#LKzwf*g4SEE=HNzf6S#q6bU~yI-Poh#? z>R(1m#rQ-CzR1-kks8i^Q=JVHh5_%t>re9L`8In)cM*zAVQ588Nsp_cCjZ7b*v`1b zu#=j)@Lv9@OB2^%;~^cwoDzI4|5$eiVj3t{K%p5=l3KwX%JG-0M97>W3r7Epda57D z8PvxjRz(iIUo>u=`V1HA5YN_@`Q0d>roipYho8Kk5-U%;_XsHcY4&c>1j;-tXs(hy z(&3{urxOoaaVuP`dj)Ph^CK|)GLrfVV;?H#vVmv#l?${mJS`kAxrThB;SA4VJHh~| zreFFssZs~(J8~GSu+EgoN5I*t9$9h>wIp|Ocf>u48p@G7_wDC&->c^dhGkMC&9Z`X zF>f}cVn{nTDL^HRBvM>>f}gBv-7C<~BR?gK5~B9)@D`Y0B-QS>H9+gU8P&FJH@Nej z=cV~S4>ap}w0Ok}$rYl=Xt-u zPr`BA{OyKQVj7=-hAWi&r24x)A3jv`l1sP5qmqg59ocec`oCa}Z!#v{-mJ`MJ{0=E zYCP0Tc=FwboI~s?`}76&4LSe?f`WpBgF%LYh5h{n7IJF|9R>pyvOgF?KEMhqDLcTC zs8nHMF|)F# zrC^G$$92pfK;ihJm}&yFh8vpq+LS~N%NdQSf2o^Z&ze5^9+QDD>%+w{tfZ+6#G=9wH~Q`pX;pcsWMJFA5;7A#p|a7u#=|dqd*$3kc}e_s9*|?9NSMez*X$i~ zZH!XTxH`ciun5Cag~bO5QTIzU(f~#UkfQN$WJ8FU+DkRE^=Qr&g&5_kO>>54wCcfhRZb4V? zouP!=$gOGEc~OkC4G8bi`$qS}88!U)M+_>9LwoY9=Q%TfE_yhy;2Oj;+m;6D534;# zVjTgY)tX{{&`1kiu)A-u#=bs9wkUSB&$BqJ^li{ME@nRX1b;0~liH)arJ-LxSJh@Q zpBD{9fYnm{im0p&L3mrpq247@atSV%1rgp7 z`>>P>izM>Z+9_89sJnJHG7WP;(f^D0o$boD{`o4?*Q|;|ExtE$(63FytLOc_*P@J= zBZ5p#%nEt8AzEzjV{zlkl7=^a5{=D>iLxg4ESEsu1IHc#$l)0Yv4HEZ=rEK# zOkNa8yTcz7yCp8>cCQ2{5!zTkap8ZNd;^b!AKl-;G9EI_zpIoh_cJC-B4_K4BJ>e3 zza*yPB*XcLOQ?!ronRGik*{mwVz`QOZ&{UWk^GEOj6w&>RumoGS!j+iKXFMII;JQY zOxK<>9NZF!G(mzits+5hwQE=C@JoQYAOUs30(x+1nSWo7El*=v#ttM_!X(WeLO8d9 zP%SPOT{w0JEGQ~qlVQc7%mnwF^PY$B89aLgJTm=T6?4U-e@^W7PiGw~G&;WkN*U?p zN0sC?IMu<%OLoMR1)^C2`+&U1;%$TL^odlcvI!@fj8lWc1^{3?&p5E(T~7aMNBq0=z$1yDfTr4z1EaR0HKCzMW=&dwrF_0m(e6BT~p*4 z=L~goWr8HTpYqZ7metH`Mt~I#X8k>Io5o_xFfM=Re`iyg_O1SA5i>N;5z`n90t0Ly> z9q}{zcC3XTjVjM@o^e*09k@kYrkcQ9o|T$V7WyHu$yW&^3D?*yo;%?dfAC@>+|oeZ zq?=^g9=l9h8|NDTCr21n9`HZMrGUe3EQKD%>?PC8twe_X6lzG^FvwQ2xHHE| za$T_spZ%|!cwD9nuG)lYUnV%H2_%tig5q6I=B@u@ZM@1Ed2o%Vflu#dsg_D?IH{zz z;s@wej~D4(qo$jAb(ImbK@)yd-{%jT>WnHU`6rtFXX=0U-Ij?;D14#TwmfV+aH(i8 zcO6~ik7rN!E%5s1qu)rbg?IJOy7PWGm}`;4z`J93@& ze6<~(mzKUny!u7JcYM98%B{ZK&e4fNRlOxfNg9_Ajd#xn&=9K1*s@P?)EUsIP;U9S zIVAYQdGj0=q+n{kVW|%Mc!!nktk@;w62|&^?)5tVmr3HkJgbptXpQfD!*W4=J9UKR zp#BugGf78lFuwB48o7t^4lAJj(EPATaKOSZnqfvA=2ZIi6xAj9zm;U%bj?BQV3khO zv)8)9{j>1lm+(2gQ>|~FVy~koz*5Y;P8|kVm>L#p6D1_;K$+<=)*fVyXb@b~_Z3=` z5wFsoE1VcB?pV!ww|gkS4Y_2Ya20j@@I~C0D(S&ze? reactionIcons, + }); +``` + +### Stream Chat Theme in use + +Let's take a look at customizing widgets using `StreamChatTheme`. In the example below, we can change the default color theme to yellow and override the channel header's typography and colors. + +```dart +builder: (context, child) => StreamChat( + client: client, + child: child, + streamChatThemeData: StreamChatThemeData( + colorTheme: ColorTheme.light( + primaryAccent: const Color(0xffffe072), + ), + channelTheme: ChannelTheme( + channelHeaderTheme: ChannelHeaderTheme( + color: const Color(0xffd34646), + title: TextStyle( + color: Colors.white, + ), + ), + ), + ), + ), +``` + +We are creating this class at the very top of our widget tree using the `streamChatThemeData` parameter found in the `StreamChat` widget. + +![](../assets/using_theme.jpg) From fa3dc1196c8e2b790d898b083f5f35e0685a95bf Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 9 Jul 2021 15:25:27 +0530 Subject: [PATCH 529/599] feat: added new docs --- .../Flutter/stream_chat_flutter/setup.mdx | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx index 6d93c215..af429620 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx @@ -8,7 +8,7 @@ Understanding Setup For stream_chat_flutter ### Add pub.dev dependency -First, you need to add the `stream_chat_flutter` dependency to your pubspec.yaml +First, you need to add the `stream_chat_flutter` dependency to your `pubspec.yaml`. You can either run this command: @@ -24,3 +24,42 @@ Add this line in the dependencies section of your pubspec.yaml after substitutin dependencies: stream_chat_flutter_core: ^latest_version ``` + +### Details On Platform Support + +`stream_chat_flutter` was originally created for Android and iOS mobile platforms. As Flutter matured, +additional platforms were added and the package now has experimental support for web and desktop as +[detailed here](https://getstream.io/blog/announcing-experimental-multi-platform-support-for-the-stream-flutter-sdk/). + +However, platforms other than mobile may have additional constraints due to not supporting all plugins, +which will be addressed by the respective plugin creators over time. + +### Setup: iOS + +The library uses [flutter file picker plugin](https://github.com/miguelpruivo/flutter_file_picker) to pick files from the os. +Follow [this wiki](https://github.com/miguelpruivo/flutter_file_picker/wiki/Setup#ios) to fulfill iOS requirements. + +We also use [video_player](https://pub.dev/packages/video_player) to reproduce videos. +Follow [this guide](https://pub.dev/packages/video_player#installation) to fulfill the requirements. + +To pick images from the camera, we use the [image_picker](https://pub.dev/packages/image_picker) plugin. +Follow [these instructions](https://pub.dev/packages/image_picker#ios) to check the requirements. + +### Setup: Web + +Due to Moor web (for offline storage) you need to include the sql.js library: + +``` + + + + + + + + + +``` + +You can grab the latest version of sql-wasm.js and sql-wasm.wasm [here](https://github.com/sql-js/sql.js/releases) +and copy them into your `/web` folder. From 68f1328a54fe52b33238b605c6244b01a3b0bac0 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 9 Jul 2021 15:27:56 +0530 Subject: [PATCH 530/599] fix: typo fixes --- .../Flutter/stream_chat_flutter/stream_chat_and_theming.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/stream_chat_and_theming.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/stream_chat_and_theming.mdx index e069b023..3fb4c847 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/stream_chat_and_theming.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/stream_chat_and_theming.mdx @@ -4,7 +4,7 @@ sidebar_position: 3 title: StreamChat And Theming --- -Understading how to customize widgets using `StreamChatTheme` +Understanding how to customize widgets using `StreamChatTheme` Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChatTheme-class.html) and [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChatThemeData-class.html) From 7b1a85c40cfe96be1d6b3eae9702398fdde1e723 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 9 Jul 2021 15:13:48 +0200 Subject: [PATCH 531/599] minor fixes --- .../docs/Flutter/basics/introduction.mdx | 12 +++---- .../guides/adding_custom_attachments.mdx | 6 ++-- .../stream_chat_flutter/channel_header.mdx | 2 +- .../channel_list_header.mdx | 2 +- .../stream_chat_flutter/channel_list_view.mdx | 9 +++--- .../stream_chat_flutter/introduction.mdx | 2 +- .../stream_chat_flutter/message_input.mdx | 8 ++--- .../stream_chat_flutter/message_list_view.mdx | 2 +- .../message_search_list_view.mdx | 2 +- .../stream_chat_flutter/message_widget.mdx | 2 +- .../Flutter/stream_chat_flutter/setup.mdx | 31 +++++-------------- .../stream_chat_flutter/user_list_view.mdx | 4 +-- .../channel_list_core.mdx | 13 ++++---- .../channels_bloc.mdx | 4 +-- .../stream_chat_flutter_core/introduction.mdx | 9 +++--- .../message_search_bloc.mdx | 2 +- .../message_search_list_core.mdx | 8 ++--- .../stream_chat_flutter_core/setup.mdx | 7 +++-- .../stream_chat_core.mdx | 7 ++--- .../stream_chat_flutter_core/users_bloc.mdx | 5 ++- .../lib/src/channel_list_core.dart | 13 ++++---- .../lib/src/message_search_list_core.dart | 8 ++--- 22 files changed, 67 insertions(+), 91 deletions(-) diff --git a/docusaurus/docs/Flutter/basics/introduction.mdx b/docusaurus/docs/Flutter/basics/introduction.mdx index 457fd5e6..4c45855c 100644 --- a/docusaurus/docs/Flutter/basics/introduction.mdx +++ b/docusaurus/docs/Flutter/basics/introduction.mdx @@ -19,14 +19,14 @@ giving you complete control to ones that give you a rich out-of-the-box chat exp The packages that make up the Stream Chat SDK are: -1. Low Level Client (stream_chat) : A pure Dart package that can be used on any Dart project. +1. Low Level Client (stream_chat): a pure Dart package that can be used on any Dart project. It provides a low-level client to access the Stream Chat service. -2. Core (stream_chat_flutter_core) : Provides business logic to fetch common things required +2. Core (stream_chat_flutter_core): provides business logic to fetch common things required for integrating Stream Chat into your application. The core package allows more customisation and hence provides business logic but no UI components. -3. UI (stream_chat_flutter) : This library includes both a low-level chat SDK and a set of +3. UI (stream_chat_flutter): this library includes both a low-level chat SDK and a set of reusable and customisable UI components. -4. Persistence (stream_chat_persistence) : Provides a persistence client for fetching and +4. Persistence (stream_chat_persistence): provides a persistence client for fetching and saving chat data locally. We recommend building prototypes using the full UI package since it contains UI widgets already @@ -37,7 +37,7 @@ The Flutter SDK enables you to build any type of chat or messaging experience fo and Desktop. If you're building a very custom UI and would prefer a more lean package, -our core package will be suited to this use case. Core allows you to build custom, +our [core package](https://pub.dev/packages/stream_chat_flutter) will be suited to this use case. Core allows you to build custom, expressive UIs while retaining the benefits of our full Flutter SDK. APIs for accessing and controlling users, sending messages, etc are seamlessly integrated into this package and accessible via providers and builders. @@ -55,7 +55,7 @@ but there is a way to create a unique chat between a certain number of people by In essence, a normal two-person chat would be a distinct channel created with two members (you cannot add or delete members in this channel), whereas a group created with two people would simply be a non distinct channel (possible to add or remove members). -Note: It is also possible to add more than two people in a distinct channel which retains the same add/removal properties and resembles the Slack DMs where you can DM two or more people as well. +Note: It is also possible to add more than two people in a distinct channel which retains the same add/removal properties and resembles the Slack DMs where you can DM one or more people as well. In summary, if you were creating a Whatsapp-like app, the first screen would be a list of channels - which on opening would show a list of messages that were sent by the users in the Channel. diff --git a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx index f0514f64..9dbb2ff2 100644 --- a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx +++ b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx @@ -4,7 +4,7 @@ sidebar_position: 3 title: Adding Custom Attachments --- -Adding Your Own Types Of Attachments To A Message +Adding your own types of attachments to a message ### Introduction @@ -14,7 +14,9 @@ own types of attachments through the SDK such as location, audio, etc. This involves doing three things: 1) Rendering the attachment thumbnail in the `MessageInput` + 2) Sending a message with the custom attachment + 3) Rendering the custom message attachment To do this, let's check out an example to add location sharing to Stream Chat. @@ -126,7 +128,7 @@ Next, we build the Static Maps URL (Add your API key before using the code snipp ```dart String _buildMapAttachment(String lat, String long) { var baseURL = 'https://maps.googleapis.com/maps/api/staticmap?'; - var url = new Uri( + var url = Uri( scheme: 'https', host: 'maps.googleapis.com', port: 443, diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx index bf65a863..d8707676 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx @@ -4,7 +4,7 @@ sidebar_position: 10 title: ChannelHeader --- -A Widget To Display Common Channel Details +A Widget to display common channel details Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/ChannelHeader-class.html) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx index dc160952..099b6a53 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx @@ -4,7 +4,7 @@ sidebar_position: 9 title: ChannelListHeader --- -A Header Widget For A List Of Channels +A header Widget for a list of channels Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/ChannelListHeader-class.html) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx index 7eb16189..c95cf098 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx @@ -4,7 +4,7 @@ sidebar_position: 4 title: ChannelListView --- -A Widget For Displaying A List Of Channels +A Widget for displaying a list of channels Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/ChannelListView-class.html) @@ -13,9 +13,10 @@ Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_ ### Background Channels are fundamental elements of Stream Chat and constitute shared spaces which allow users to -message each other. 1:1 conversations and groups are both examples of channels, albeit with some (distinct/non-distinct) -differences. Displaying the list of channels that a user is a part of is a pattern that is part of -most messaging apps. +message each other. + +1:1 conversations and groups are both examples of channels, albeit with some (distinct/non-distinct) +differences. Displaying the list of channels that a user is a part of is a pattern present in most messaging apps. The `ChannelListView` widget allows displaying a list of channels to a user. By default, this is NOT ONLY the channels that the user is a part of. This section goes into setting up and using a `ChannelListView` diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx index 710cf3b5..ac7cd490 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx @@ -4,7 +4,7 @@ sidebar_position: 1 title: Introduction --- -Understanding The UI Package Of The Flutter SDK +Understanding the UI package of the Flutter SDK ### What function does `stream_chat_flutter` serve? diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx index 3f725bd5..3f84f19b 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx @@ -4,7 +4,7 @@ sidebar_position: 6 title: MessageInput --- -A Widget Dealing With Everything Related To Sending A Message +A Widget dealing with everything related to sending a message Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageInput-class.html) @@ -14,7 +14,7 @@ Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_ In Stream Chat, we can send messages in a channel. However, sending a message isn't as simple as adding a `TextField` and logic for sending a message. It involves additional processes like addition of media, -quoting a messages, adding a custom command like a GIF board, and much more. Moreover, most apps also +quoting a message, adding a custom command like a GIF board, and much more. Moreover, most apps also need to customize the input to match their theme, overall color and structure pattern, etc. To do this, we created a `MessageInput` widget which abstracts all expected functionality a modern input @@ -54,7 +54,7 @@ class ChannelPage extends StatelessWidget { } ``` -It is common to put this widget in the same page of a [MessageListView] as the bottom widget. +It is common to put this widget in the same page of a `MessageListView` as the bottom widget. ### Quoting A Message @@ -119,7 +119,7 @@ class _ChannelPageState extends State { ### Adding Custom Actions -By default, the `MessageInput` has two actions: one for attachments and one for commands like giphy. +By default, the `MessageInput` has two actions: one for attachments and one for commands like Giphy. To add your own action, we use the `actions` parameter like this: ```dart diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx index 74a3ae5f..2ce1624b 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx @@ -4,7 +4,7 @@ sidebar_position: 5 title: MessageListView --- -A Widget For Displaying A List Of Messages +A Widget for displaying a list of messages Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageListView-class.html) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx index 864b5e49..1b3e1001 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx @@ -4,7 +4,7 @@ sidebar_position: 8 title: MessageSearchListView --- -A Widget To Search For Messages Across Channels +A Widget to search for messages across channels Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageSearchListView-class.html) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx index b1974f05..23c1326b 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx @@ -4,7 +4,7 @@ sidebar_position: 11 title: MessageWidget --- -A Widget For Displaying Messages And Attachments +A Widget for displaying messages and attachments Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageWidget-class.html) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx index af429620..2aad6606 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx @@ -4,7 +4,7 @@ sidebar_position: 2 title: Setup --- -Understanding Setup For stream_chat_flutter +Understanding setup for `stream_chat_flutter` ### Add pub.dev dependency @@ -12,7 +12,7 @@ First, you need to add the `stream_chat_flutter` dependency to your `pubspec.yam You can either run this command: -``` +```shell flutter pub add stream_chat_flutter ``` @@ -20,15 +20,17 @@ OR Add this line in the dependencies section of your pubspec.yaml after substituting latest version: -``` +```yaml dependencies: - stream_chat_flutter_core: ^latest_version + stream_chat_flutter: ^latest_version ``` +You can find the package details on [pub.dev](https://pub.dev/packages/stream_chat_flutter). + ### Details On Platform Support `stream_chat_flutter` was originally created for Android and iOS mobile platforms. As Flutter matured, -additional platforms were added and the package now has experimental support for web and desktop as +support for additional platforms was added and the package now has experimental support for web and desktop as [detailed here](https://getstream.io/blog/announcing-experimental-multi-platform-support-for-the-stream-flutter-sdk/). However, platforms other than mobile may have additional constraints due to not supporting all plugins, @@ -44,22 +46,3 @@ Follow [this guide](https://pub.dev/packages/video_player#installation) to fulfi To pick images from the camera, we use the [image_picker](https://pub.dev/packages/image_picker) plugin. Follow [these instructions](https://pub.dev/packages/image_picker#ios) to check the requirements. - -### Setup: Web - -Due to Moor web (for offline storage) you need to include the sql.js library: - -``` - - - - - - - - - -``` - -You can grab the latest version of sql-wasm.js and sql-wasm.wasm [here](https://github.com/sql-js/sql.js/releases) -and copy them into your `/web` folder. diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx index 5ebfcede..2562f071 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx @@ -4,7 +4,7 @@ sidebar_position: 7 title: UserListView --- -A Widget For Displaying And Selecting Users +A Widget for displaying and selecting users Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/UserListView-class.html) @@ -74,7 +74,7 @@ UsersListView( ### Selecting Users -The `UserListView` widget allows selecting users in a list by supplying a selected users and callbacks +The `UserListView` widget allows selecting users in a list by supplying a selected users list and callbacks for when user items are tapped. ```dart diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx index 7200def2..90ba02d8 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx @@ -18,16 +18,15 @@ class ChannelListPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: ChannelListCore( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user!.id], + ), sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, ), - errorBuilder: (err) { + errorBuilder: (context, err) { return Center( child: Text('An error has occured'), ); @@ -37,7 +36,7 @@ class ChannelListPage extends StatelessWidget { child: Text('Nothing here...'), ); }, - emptyBuilder: (context) { + loadingBuilder: (context) { return Center( child: CircularProgressIndicator(), ); diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx index b44a7df3..c89668ef 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx @@ -9,7 +9,7 @@ title: ChannelsBloc Channels with pagination, re-ordering, querying and other operations associated with Channels. -`ChannelsBloc` can be accessed at anytime by using the static of method +`ChannelsBloc` can be accessed at anytime by using the static `.of` method using Flutter's `BuildContext`. ```dart @@ -63,7 +63,7 @@ ChannelsBloc( #### Decide if channel should be added on new message event -When a new message arrives, a `message.new` event is created. We can decide if we want to add the channel +When a new message arrives, a `message.new` event is received. We can decide if we want to add the channel to the list using the `shouldAddChannel` parameter which is a callback supplying the event data: ```dart diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx index 146bfa62..4a31ff8b 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx @@ -4,7 +4,7 @@ sidebar_position: 1 title: Introduction --- -Understanding The Core Package Of The Flutter SDK +Understanding the core package of the Flutter SDK This package provides business logic to fetch common things required for integrating Stream Chat into your application. The core package allows more customisation and hence provides business logic but no UI components. @@ -15,10 +15,11 @@ Please use the `stream_chat_flutter` package for the full fledged suite of UI co In the early days of the Flutter SDK, the SDK was only split into the LLC (`stream_chat`) and the UI package (`stream_chat_flutter`). With this you could use a fully built interface with the UI package or a fully custom interface with the LLC. However, we soon recognised the need for a third intermediary -package which made tasks like building and modifying a list or channels or messages easy but without +package which made tasks like building and modifying a list of channels or messages easy but without the complexity of using low level components. The Core package (`stream_chat_flutter_core`) is a manifestation of the same idea and allows you to build an interface with Stream Chat without having to deal with -low level code and architecture as well as implementing your own theme and UI effortlessly. +low level code and architecture as well as implementing your own theme and UI effortlessly. +Also, it has very few dependencies. We will now explore the components of this intermediary package and understand how it helps you build the experience you want your users to have. @@ -46,7 +47,7 @@ The BLoCs we provide are: ### Core Components Core components usually are an easy way to fetch data associated with Stream Chat. -Core components use functions exposed by the respective BLoCs (the ChannelListCore uses the ChannelsBloc) +Core components use functions exposed by the respective BLoCs (for example the ChannelListCore uses the ChannelsBloc) and use the respective controllers for various operations. Unlike heavier components from the UI package, core components are decoupled from UI and they expose builders instead to help you build a fully custom interface. diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx index b80dec61..ad6c7bbe 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx @@ -7,7 +7,7 @@ title: MessageSearchListBloc `MessageSearchBloc` is used to manage a list of messages with pagination. This class can be used to load messages, perform queries, etc. -`MessageSearchBloc` can be accessed at anytime by using the static of method +`MessageSearchBloc` can be accessed at anytime by using the static `.of` method using Flutter's BuildContext. ```dart diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx index b9c42bd0..872c53a2 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx @@ -15,12 +15,8 @@ class MessageSearchPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: MessageSearchListCore( - messageQuery: _channelQuery, - filters: { - 'members': { - r'$in': [user.id] - } - }, + messageQuery: _messageFilter, + filters: _channelsFilter, paginationParams: PaginationParams(limit: 20), ), ); diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx index f39f243e..771633aa 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx @@ -4,7 +4,7 @@ sidebar_position: 2 title: Setup --- -Understanding Setup For stream_chat_flutter_core +Understanding setup for `stream_chat_flutter_core` ### Add pub.dev dependency @@ -12,7 +12,7 @@ First, you need to add the `stream_chat_flutter_core` dependency to your pubspec You can either run this command: -``` +```shell flutter pub add stream_chat_flutter_core ``` @@ -20,8 +20,9 @@ OR Add this line in the dependencies section of your pubspec.yaml after substituting latest version: -``` +```yaml dependencies: stream_chat_flutter_core: ^latest_version ``` +You can find the package details on [pub.dev](https://pub.dev/packages/stream_chat_flutter_core). diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx index 91d7cf65..2a43ae52 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/stream_chat_core.mdx @@ -4,15 +4,14 @@ sidebar_position: 3 title: StreamChatCore --- -`StreamChatCore` is a version of `StreamChat` found in stream_chat_flutter that is decoupled from +`StreamChatCore` is a version of `StreamChat` found in `stream_chat_flutter` that is decoupled from theme and initialisations. `StreamChatCore` is used to provide information about the chat client to the widget tree. This Widget is used to react to life cycle changes and system updates. -When the app goes into the background, the websocket connection is kept -alive for two minutes before being terminated. +When the app goes into the background, the websocket connection is automatically closed and when it goes back to foreground the connection is opened again. -Like the StreamChat widget in the higher level UI package, the StreamChatCore widget should +Like the `StreamChat` widget in the higher level UI package, the `StreamChatCore` widget should be on the top level before using any Stream functionality: ```dart diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx index 9bff8dba..b330b71e 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx @@ -6,15 +6,14 @@ title: UsersBloc Widget dedicated to the management of a users list with pagination. -`UsersBloc` can be accessed at anytime by using the static of method +`UsersBloc` can be accessed at anytime by using the static `.of` method using Flutter's `BuildContext`. ```dart var _userBloc_ = UsersBloc.of(context); ``` -The `UsersBloc` widget encapsulates common functionality related to searching for users -and also supplies them down the widget tree. +The `UsersBloc` widget encapsulates common functionality related to user lists and also supplies them down the widget tree. Here is a basic implementation of `UsersBloc`: diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 6dc52647..c59b2030 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -19,16 +19,15 @@ import 'package:stream_chat_flutter_core/src/typedef.dart'; /// Widget build(BuildContext context) { /// return Scaffold( /// body: ChannelListCore( -/// filter: { -/// 'members': { -/// '\$in': [StreamChat.of(context).user.id], -/// } -/// }, +/// filter: Filter.in_( +/// 'members', +/// [StreamChat.of(context).user!.id], +/// ), /// sort: [SortOption('last_message_at')], /// pagination: PaginationParams( /// limit: 20, /// ), -/// errorBuilder: (err) { +/// errorBuilder: (context, err) { /// return Center( /// child: Text('An error has occured'), /// ); @@ -38,7 +37,7 @@ import 'package:stream_chat_flutter_core/src/typedef.dart'; /// child: Text('Nothing here...'), /// ); /// }, -/// emptyBuilder: (context) { +/// loadingBuilder: (context) { /// return Center( /// child: CircularProgressIndicator(), /// ); diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index d354daec..4901c332 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -17,12 +17,8 @@ import 'package:stream_chat_flutter_core/src/typedef.dart'; /// Widget build(BuildContext context) { /// return Scaffold( /// body: MessageSearchListCore( -/// messageQuery: _channelQuery, -/// filters: { -/// 'members': { -/// r'$in': [user.id] -/// } -/// }, +/// messageQuery: _messageFilter, +/// filters: _channelsFilter, /// paginationParams: PaginationParams(limit: 20), /// ), /// ); From 015c4ae072cb49001134afa0e485ccfa0ced5374 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 9 Jul 2021 15:48:51 +0200 Subject: [PATCH 532/599] Update docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx --- docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx index 9dbb2ff2..fb1f7bbe 100644 --- a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx +++ b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx @@ -4,7 +4,7 @@ sidebar_position: 3 title: Adding Custom Attachments --- -Adding your own types of attachments to a message +Adding Your Own Types Of Attachments To A Message ### Introduction From a2fbc37d531e705eb3a548e4612598678352db1f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 9 Jul 2021 15:49:01 +0200 Subject: [PATCH 533/599] Update docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx --- .../docs/Flutter/stream_chat_flutter/channel_list_header.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx index 099b6a53..d6930cb4 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx @@ -4,7 +4,7 @@ sidebar_position: 9 title: ChannelListHeader --- -A header Widget for a list of channels +A Header Widget For A List Of Channels Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/ChannelListHeader-class.html) @@ -85,4 +85,4 @@ To enable this, use the `showConnectionStateTile` property. ChannelListHeader( showConnectionStateTile: true, ), -``` \ No newline at end of file +``` From 64b38a68c8a219e6fae95f6da744780bf1b16d92 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 9 Jul 2021 15:50:28 +0200 Subject: [PATCH 534/599] Apply suggestions from code review --- .../docs/Flutter/stream_chat_flutter/channel_header.mdx | 2 +- .../docs/Flutter/stream_chat_flutter/channel_list_view.mdx | 3 +-- docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx | 3 +-- docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx | 4 ++-- .../docs/Flutter/stream_chat_flutter/message_list_view.mdx | 2 +- .../Flutter/stream_chat_flutter/message_search_list_view.mdx | 2 +- .../docs/Flutter/stream_chat_flutter/message_widget.mdx | 4 ++-- docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx | 2 +- .../docs/Flutter/stream_chat_flutter/user_list_view.mdx | 2 +- .../docs/Flutter/stream_chat_flutter_core/introduction.mdx | 4 ++-- docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx | 2 +- 11 files changed, 14 insertions(+), 16 deletions(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx index d8707676..bf65a863 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx @@ -4,7 +4,7 @@ sidebar_position: 10 title: ChannelHeader --- -A Widget to display common channel details +A Widget To Display Common Channel Details Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/ChannelHeader-class.html) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx index c95cf098..eb7baf20 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_view.mdx @@ -4,7 +4,7 @@ sidebar_position: 4 title: ChannelListView --- -A Widget for displaying a list of channels +A Widget For Displaying A List Of Channels Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/ChannelListView-class.html) @@ -109,4 +109,3 @@ ChannelListView( ), ``` - diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx index ac7cd490..28640483 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/introduction.mdx @@ -4,7 +4,7 @@ sidebar_position: 1 title: Introduction --- -Understanding the UI package of the Flutter SDK +Understanding The UI Package Of The Flutter SDK ### What function does `stream_chat_flutter` serve? @@ -16,4 +16,3 @@ use it in your Flutter apps, we wanted to make sure that adding Chat functionali The UI package is built on top of the low-level client and the core package and allows you to build a full fledged app with either the inbuilt components, modify existing components, or easily add widgets of your own to match your app's style better. - diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx index 3f84f19b..f33f85e9 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx @@ -4,7 +4,7 @@ sidebar_position: 6 title: MessageInput --- -A Widget dealing with everything related to sending a message +A Widget Dealing With Everything Related To Sending A Message Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageInput-class.html) @@ -167,4 +167,4 @@ MessageInput( ), ``` -![](../assets/message_input_change_position.png) \ No newline at end of file +![](../assets/message_input_change_position.png) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx index 2ce1624b..74a3ae5f 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx @@ -4,7 +4,7 @@ sidebar_position: 5 title: MessageListView --- -A Widget for displaying a list of messages +A Widget For Displaying A List Of Messages Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageListView-class.html) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx index 1b3e1001..864b5e49 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx @@ -4,7 +4,7 @@ sidebar_position: 8 title: MessageSearchListView --- -A Widget to search for messages across channels +A Widget To Search For Messages Across Channels Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageSearchListView-class.html) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx index 23c1326b..5060bcac 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx @@ -4,7 +4,7 @@ sidebar_position: 11 title: MessageWidget --- -A Widget for displaying messages and attachments +A Widget For Displaying Messages And Attachments Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageWidget-class.html) @@ -95,4 +95,4 @@ MessageWidget( //... reverse = true, ) -``` \ No newline at end of file +``` diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx index 2aad6606..22e5c6c5 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/setup.mdx @@ -4,7 +4,7 @@ sidebar_position: 2 title: Setup --- -Understanding setup for `stream_chat_flutter` +Understanding Setup For `stream_chat_flutter` ### Add pub.dev dependency diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx index 2562f071..5cc1ae40 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx @@ -4,7 +4,7 @@ sidebar_position: 7 title: UserListView --- -A Widget for displaying and selecting users +A Widget For Displaying And Selecting Users Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/UserListView-class.html) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx index 4a31ff8b..3c6a70f2 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/introduction.mdx @@ -4,7 +4,7 @@ sidebar_position: 1 title: Introduction --- -Understanding the core package of the Flutter SDK +Understanding The Core Package Of The Flutter SDK This package provides business logic to fetch common things required for integrating Stream Chat into your application. The core package allows more customisation and hence provides business logic but no UI components. @@ -77,4 +77,4 @@ specific function (reload / paginate) whenever such an event is triggered throug * MessageSearchListController * ChannelListController -This section goes into the individual core package widgets and their functional use. \ No newline at end of file +This section goes into the individual core package widgets and their functional use. diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx index 771633aa..15b15a56 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/setup.mdx @@ -4,7 +4,7 @@ sidebar_position: 2 title: Setup --- -Understanding setup for `stream_chat_flutter_core` +Understanding Setup For `stream_chat_flutter_core` ### Add pub.dev dependency From 19b3d4986793fa8d698559b83c87e46961eb362c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 9 Jul 2021 20:46:03 +0530 Subject: [PATCH 535/599] fix: added new docs --- .../guides/adding_push_notifcations.mdx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx b/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx index 20621ca7..e0aea320 100644 --- a/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx +++ b/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx @@ -4,3 +4,33 @@ sidebar_position: 3 title: Adding Push Notifications --- +Adding Push Notifications To Your Application + +### Introduction + +Push notifications are a core part of the experience for a messaging app. Users often need to be notified +of new messages and old notifications sometimes need to be updated silently as well. + +This guide details how to add push notifications to your app. + +### Configure APN + +Stream Chat supports push for both Android and iOS. + +In this section you will find all information on how to configure push for APN and Firebase. + +Only new messages are pushed to mobile devices, all other chat events are only send to WebSocket clients and webhook endpoints if configured. + +#### Push Delivery Logic + +Push message delivery follows the following logic: + +* Only channel members can receive push messages +* Members that are currently online do not receive push messages +* Messages added within a thread are only sent to users that are part of that thread (they posted at least one message or were mentioned) +* Messages from muted users are not sent +* Messages are sent to all registered devices for a user (up to 25) +* Don't try to register devices for anonymous users (API will ignore but will eat from rate limit budget) +* Up to 100 members of a channel will receive push notifications +* skip_push is not marked as true, as described here. +* push_notifications is enabled (default) on the channel type for message is sent. \ No newline at end of file From 542f5a8018f8df129e1a2003b3076516b66bdf23 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 12 Jul 2021 09:21:59 +0200 Subject: [PATCH 536/599] minor fixes --- .../Flutter/stream_chat_flutter/stream_chat_and_theming.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/stream_chat_and_theming.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/stream_chat_and_theming.mdx index 3fb4c847..d527b738 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/stream_chat_and_theming.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/stream_chat_and_theming.mdx @@ -4,7 +4,7 @@ sidebar_position: 3 title: StreamChat And Theming --- -Understanding how to customize widgets using `StreamChatTheme` +Understanding How To Customize Widgets Using `StreamChatTheme` Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChatTheme-class.html) and [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChatThemeData-class.html) From 29779485b2ae162b5ba3a3d92762fa4388e82ef3 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 12 Jul 2021 10:38:03 +0200 Subject: [PATCH 537/599] port push notification guide --- .../assets/dashboard_firebase_enable.jpeg | Bin 0 -> 10753 bytes .../assets/dashboard_firebase_key.jpeg | Bin 0 -> 13315 bytes .../assets/dashboard_save_changes.jpeg | Bin 0 -> 3971 bytes .../assets/firebase_project_settings.jpeg | Bin 0 -> 9407 bytes docusaurus/docs/Flutter/assets/server_key.png | Bin 0 -> 13442 bytes .../guides/adding_push_notifcations.mdx | 229 ++++++++++++++++-- 6 files changed, 214 insertions(+), 15 deletions(-) create mode 100644 docusaurus/docs/Flutter/assets/dashboard_firebase_enable.jpeg create mode 100644 docusaurus/docs/Flutter/assets/dashboard_firebase_key.jpeg create mode 100644 docusaurus/docs/Flutter/assets/dashboard_save_changes.jpeg create mode 100644 docusaurus/docs/Flutter/assets/firebase_project_settings.jpeg create mode 100644 docusaurus/docs/Flutter/assets/server_key.png diff --git a/docusaurus/docs/Flutter/assets/dashboard_firebase_enable.jpeg b/docusaurus/docs/Flutter/assets/dashboard_firebase_enable.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..e4696ec8825abe2a04d88bc55e13568c7b75ae0b GIT binary patch literal 10753 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 S5ma6$s22lkF%P1sGyer3DUv1t literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/assets/firebase_project_settings.jpeg b/docusaurus/docs/Flutter/assets/firebase_project_settings.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..4fbc6521c3f6e5f25fb62dbb406aea0e1d5b34e1 GIT binary patch literal 9407 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 literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/assets/server_key.png b/docusaurus/docs/Flutter/assets/server_key.png new file mode 100644 index 0000000000000000000000000000000000000000..f11d6fb56b17ea7ac18eb1b99af0fbd605181e68 GIT binary patch literal 13442 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 P6#yAYMTr_Q!@&OnF?4ey literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx b/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx index e0aea320..af82d4f5 100644 --- a/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx +++ b/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx @@ -13,24 +13,223 @@ of new messages and old notifications sometimes need to be updated silently as w This guide details how to add push notifications to your app. -### Configure APN +Make sure to check [this section](https://getstream.io/chat/docs/flutter-dart/push_introduction/?language=dart) of the docs to read about the push delivery logic. -Stream Chat supports push for both Android and iOS. +### Setup FCM -In this section you will find all information on how to configure push for APN and Firebase. +To integrate push notifications in your Flutter app you need to use the package [firebase_messaging](https://pub.dev/packages/firebase_messaging). -Only new messages are pushed to mobile devices, all other chat events are only send to WebSocket clients and webhook endpoints if configured. -#### Push Delivery Logic +Follow the [Firebase documentation](https://firebase.flutter.dev/docs/messaging/overview/) to know how to set up the plugin for both Android and iOS. -Push message delivery follows the following logic: -* Only channel members can receive push messages -* Members that are currently online do not receive push messages -* Messages added within a thread are only sent to users that are part of that thread (they posted at least one message or were mentioned) -* Messages from muted users are not sent -* Messages are sent to all registered devices for a user (up to 25) -* Don't try to register devices for anonymous users (API will ignore but will eat from rate limit budget) -* Up to 100 members of a channel will receive push notifications -* skip_push is not marked as true, as described here. -* push_notifications is enabled (default) on the channel type for message is sent. \ No newline at end of file +Once that's done FCM should be able to send push notifications to your devices. + +### Integration with Stream + +#### Step 1 + +From the [Firebase Console](https://console.firebase.google.com/), select the project your app belongs to. + +#### Step 2 + +Click on the gear icon next to `Project Overview` and navigate to **Project settings** + +![](../assets/firebase_project_settings.jpeg) + +#### Step 3 + +Navigate to the `Cloud Messaging` tab + +#### Step 4 + +Under `Project Credentials`, locate the `Server key` and copy it + +![](../assets/server_key.png) + +#### Step 5 + +Upload the `Server Key` in your chat dashboard + +![](../assets/dashboard_firebase_enable.jpeg) + +![](../assets/dashboard_firebase_key.jpeg) + + +:::note +We are setting up the Android section, but this will work for both Android and iOS if you're using Firebase for both of them! +::: + +#### Step 6 + +Save your push notification settings changes + +![](../assets/dashboard_save_changes.jpeg) + +**OR** + +Upload the `Server Key` via API call using a backend SDK + +```js +await client.updateAppSettings({ + firebase_config: { + server_key: 'server_key', + notification_template: `{"message":{"notification":{"title":"New messages","body":"You have {{ unread_count }} new message(s) from {{ sender.name }}"},"android":{"ttl":"86400s","notification":{"click_action":"OPEN_ACTIVITY_1"}}}}`, + data_template: `{"sender":"{{ sender.id }}","channel":{"type": "{{ channel.type }}","id":"{{ channel.id }}"},"message":"{{ message.id }}"}` + }, +}); +``` + +### Registering a device at Stream Backend + +Once you configure Firebase server key and set it up on Stream dashboard a device that is supposed to receive push notifications needs to be registered at Stream backend. This is usually done by listening for Firebase device token updates and passing them to the backend as follows: + +```dart +firebaseMessaging.onTokenRefresh.listen((token) { + client.addDevice(token, PushProvider.firebase); +}); +``` + +### Possible issues + + +We only send push notifications, when the user doesn't have any active websocket connection (which is established when you call client.connectUser). If you set the [onBackgroundEventReceived](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat/onBackgroundEventReceived.html) property of the StreamChat widget, when your app goes to background, your device will keep the ws connection alive for 1 minute, and so within this period, you won't receive any push notification. + +Make sure to read the [general push docs](https://getstream.io/chat/docs/flutter-dart/push_introduction/?language=dart) in order to avoid known gotchas that may make your relationship with notifications go bad đŸ˜ĸ + +### Testing if Push Notifications are Setup Correctly + +If you're not sure if you've set up push notifications correctly (eg you don't always receive them, they work unreliably), you can follow these steps to make sure your config is correct and working: + +1. Clone our repo for push testing git clone git@github.com:GetStream/chat-push-test.git + +2. `cd flutter` + +3. In folder run `flutter pub get` + +4. Input your api key and secret in `lib/main.dart` + +5. Change the bundle identifier/application ID and development team/user so you can run the app in your device (**do not** run on iOS simulator, Android emulator is fine) + +6. Add your `google-services.json/GoogleService-Info.plist` + +7. Run the app + +8. Accept push notification permission (iOS only) + +9. Tap on `Device ID` and copy it + +10. Send the app to background + +11. After configuring [stream-cli](https://github.com/GetStream/stream-cli) paste the following command on command line using your user ID + +```shell +stream chat:push:test -u +``` + +You should get a test push notification + +### App in the background but still connected + +The [StreamChat](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat-class.html) widget lets you define a [onBackgroundEventReceived](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat/onBackgroundEventReceived.html) handler in order to handle events while the app is in the background, but the client is still connected. + +This is useful because it lets you keep the connection alive in cases in which the app goes in the background just for some seconds (eg: multitasking, picking pictures from the gallery...) + +You can even customize the [backgroundKeepAlive](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat/backgroundKeepAlive.html) duration. + +In order to show notifications in such a case we suggest using the package [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications); follow the package guide to successfully set up the plugin. + +Once that's done you should set the [onBackgroundEventReceived](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat/onBackgroundEventReceived.html); here is an example: + +```dart +... +StreamChat( + client: client, + onBackgroundEventReceived: (e) { + final currentUserId = client.state.user.id; + if (![ + EventType.messageNew, + EventType.notificationMessageNew, + ].contains(event.type) || + event.user.id == currentUserId) { + return; + } + if (event.message == null) return; + final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + final initializationSettingsAndroid = + AndroidInitializationSettings('launch_background'); + final initializationSettingsIOS = IOSInitializationSettings(); + final initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS, + ); + await flutterLocalNotificationsPlugin.initialize(initializationSettings); + await flutterLocalNotificationsPlugin.show( + event.message.id.hashCode, + event.message.user.name, + event.message.text, + NotificationDetails( + android: AndroidNotificationDetails( + 'message channel', + 'Message channel', + 'Channel used for showing messages', + priority: Priority.high, + importance: Importance.high, + ), + iOS: IOSNotificationDetails(), + ), + ); + }, + child: .... +); +... +``` + +As you can see we §generate a local notification whenever a message.new or notification.message_new event is received. + +:::note +Using `flutter_local_notifications` is a great way to implement notifications while the is in foreground too! You can generate a local notification listening to events using the method `streamChatClient.on()` and react to the events you want. +::: + +### Saving notification messages to the offline storage + +You may want to save received messages when you receive them via a notification so that later on when you open the app they're already there. + +To do this we need to update the push notification data payload at Stream Dashboard and clear the notification one: + +```json +{ + "message_id": "{{ message.id }}", + "channel_id": "{{ channel.id }}", + "channel_type": "{{ channel.type }}" +} +``` + +Then we need to integrate the package [stream_chat_persistence](https://pub.dev/packages/stream_chat_persistence) in our app that exports a persistence client, learn [here](https://pub.dev/packages/stream_chat_persistence#usage) how to set it up. + +Then during the call `firebaseMessaging.configure(...)` we need to set the `onBackgroundMessage` parameter using a TOP-LEVEL or STATIC function to handle background messages; here is an example: + +```dart +Future myBackgroundMessageHandler(message) async { + if (message.containsKey('data')) { + final data = message['data']; + final messageId = data['message_id']; + final channelId = data['channel_id']; + final channelType = data['channel_type']; + final cid = '$channelType:$channelId'; + + final client = StreamChatClient(apiKey); + final persistenceClient = StreamChatPersistenceClient(); + await persistenceClient.connect(userId); + + final message = await client.getMessage(messageId).then((res) => res.message); + + await persistenceClient.updateMessages(cid, [message]); + persistenceClient.disconnect(); + + /// This can be done using the package flutter_local_notifications as we did before 👆 + _showLocalNotification(); + } +} +``` + From 3ee48cbc1e029c574e9487f9e5f51ff4ddc8f0fc Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 12 Jul 2021 11:48:03 +0200 Subject: [PATCH 538/599] Update docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx Co-authored-by: Deven Joshi --- docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx b/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx index af82d4f5..c2164ebc 100644 --- a/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx +++ b/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx @@ -93,7 +93,7 @@ firebaseMessaging.onTokenRefresh.listen((token) { ### Possible issues -We only send push notifications, when the user doesn't have any active websocket connection (which is established when you call client.connectUser). If you set the [onBackgroundEventReceived](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat/onBackgroundEventReceived.html) property of the StreamChat widget, when your app goes to background, your device will keep the ws connection alive for 1 minute, and so within this period, you won't receive any push notification. +We only send push notifications when the user doesn't have any active websocket connection (which is established when you call `client.connectUser`). If you set the [onBackgroundEventReceived](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat/onBackgroundEventReceived.html) property of the StreamChat widget, when your app goes to background, your device will keep the ws connection alive for 1 minute, and so within this period, you won't receive any push notification. Make sure to read the [general push docs](https://getstream.io/chat/docs/flutter-dart/push_introduction/?language=dart) in order to avoid known gotchas that may make your relationship with notifications go bad đŸ˜ĸ @@ -232,4 +232,3 @@ Future myBackgroundMessageHandler(message) async { } } ``` - From bdf1d7d156941200aaeee65355779819a910630f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 12 Jul 2021 12:00:27 +0200 Subject: [PATCH 539/599] Update docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx Co-authored-by: Deven Joshi --- docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx b/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx index c2164ebc..38b91833 100644 --- a/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx +++ b/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx @@ -99,7 +99,7 @@ Make sure to read the [general push docs](https://getstream.io/chat/docs/flutter ### Testing if Push Notifications are Setup Correctly -If you're not sure if you've set up push notifications correctly (eg you don't always receive them, they work unreliably), you can follow these steps to make sure your config is correct and working: +If you're not sure if you've set up push notifications correctly (e.g. you don't always receive them, they work unreliably), you can follow these steps to make sure your config is correct and working: 1. Clone our repo for push testing git clone git@github.com:GetStream/chat-push-test.git From 68f17d0397dce775cc57c87ea24367dd16ae9f97 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 12 Jul 2021 15:30:44 +0530 Subject: [PATCH 540/599] feat: added background to lists --- .../channel_list_core.mdx | 14 ++++++++++++++ .../message_list_core.mdx | 14 ++++++++++++++ .../message_search_list_core.mdx | 13 +++++++++++++ .../stream_chat_flutter_core/user_list_core.mdx | 17 ++++++++++++++++- 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx index 90ba02d8..4196a14c 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/channel_list_core.mdx @@ -4,6 +4,20 @@ sidebar_position: 4 title: ChannelListCore --- +A Widget For Building A List Of Channels + +### Background + +The UI SDK of Stream Chat supplies a `ChannelListView` class that builds a list of channels fetching +according to the filters and sort order given. However, in some cases, implementing novel UI is necessary +that cannot be done using the customization approaches given in the widget. + +To do this, we extracted the logic required for fetching channels into a 'Core' widget - a widget that +fetches channels in the expected way via the usual params but does not supply any UI and instead +exposes builders to build the UI in situations such as loading, empty data, errors, and on data received. + +### Basic Example + `ChannelListCore` is a simplified class that allows fetching a list of channels while exposing UI builders. diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx index b77f2002..5fd5877a 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_list_core.mdx @@ -4,6 +4,20 @@ sidebar_position: 5 title: MessageListCore --- +A Widget For Building A List Of Messages + +### Background + +The UI SDK of Stream Chat supplies a `MessageListView` class that builds a list of channels fetching +according to the filters and sort order given. However, in some cases, implementing novel UI is necessary +that cannot be done using the customization approaches given in the widget. + +To do this, we extracted the logic required for fetching channels into a 'Core' widget - a widget that +fetches channels in the expected way via the usual params but does not supply any UI and instead +exposes builders to build the UI in situations such as loading, empty data, errors, and on data received. + +### Basic Example + `MessageListCore` is a simplified class that allows fetching a list of messages while exposing UI builders. diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx index 872c53a2..70cc026f 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx @@ -4,6 +4,19 @@ sidebar_position: 6 title: MessageSearchListCore --- +A Widget For Displaying Message Searches + +### Background + +The UI SDK of Stream Chat supplies a `MessageSearchListView` class that builds a list of channels fetching +according to the filters and sort order given. However, in some cases, implementing novel UI is necessary +that cannot be done using the customization approaches given in the widget. + +To do this, we extracted the logic required for fetching channels into a 'Core' widget - a widget that +fetches channels in the expected way via the usual params but does not supply any UI and instead +exposes builders to build the UI in situations such as loading, empty data, errors, and on data received. + +### Basic Example `MessageSearchListCore` is a simplified class that allows searching for messages across channels while exposing UI builders. diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx index 0f515a12..bf1055db 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx @@ -2,4 +2,19 @@ id: user_list_core sidebar_position: 10 title: UserListCore ---- \ No newline at end of file +--- + +A Widget For Building A List Of Users + +### Background + +The UI SDK of Stream Chat supplies a `UserListView` class that builds a list of channels fetching +according to the filters and sort order given. However, in some cases, implementing novel UI is necessary +that cannot be done using the customization approaches given in the widget. + +To do this, we extracted the logic required for fetching channels into a 'Core' widget - a widget that +fetches channels in the expected way via the usual params but does not supply any UI and instead +exposes builders to build the UI in situations such as loading, empty data, errors, and on data received. + +### Basic Example + From a8b3316508cd8b6986a84fa13add6e226ccc25e9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 12 Jul 2021 12:00:53 +0200 Subject: [PATCH 541/599] Update docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx --- docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx b/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx index 38b91833..529f03cb 100644 --- a/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx +++ b/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx @@ -185,7 +185,7 @@ StreamChat( ... ``` -As you can see we §generate a local notification whenever a message.new or notification.message_new event is received. +As you can see we generate a local notification whenever a message.new or notification.message_new event is received. :::note Using `flutter_local_notifications` is a great way to implement notifications while the is in foreground too! You can generate a local notification listening to events using the method `streamChatClient.on()` and react to the events you want. From c63e78805725fd6cd061ae1756067da14968d733 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 12 Jul 2021 16:52:08 +0530 Subject: [PATCH 542/599] feat: added background to blocs --- .../stream_chat_flutter_core/channels_bloc.mdx | 16 +++++++++++++++- .../message_search_bloc.mdx | 16 +++++++++++++++- .../stream_chat_flutter_core/users_bloc.mdx | 15 ++++++++++++++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx index c89668ef..3cb1728a 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/channels_bloc.mdx @@ -4,7 +4,21 @@ sidebar_position: 7 title: ChannelsBloc --- -`ChannelsBloc` is a widget dedicated to the management of a channel list with pagination. +A Widget Dedicated To The Management Of A Channel List With Pagination. + +### Background + +Most widgets in the Core SDK are focused on fetching a particular type of object from Stream Chat - channels, +messages, users, etc. The BLoC widgets bundle up the base functions used to fetch data as well as the current +data fetched by the respective functions. Furthermore, the Core widgets use this BLoC to fetch new or +existing data and build UI based on it. + +All Core and UI widgets which focus on fetching a list of objects need to have their respective functions +above them in the widget tree. The ChannelListCore and ChannelListView require the ChannelsBloc +above them in the widget hierarchy without which they will fail. + +### Understanding The Widget + `ChannelsBloc` is used together with `ChannelListCore` to manage a list of Channels with pagination, re-ordering, querying and other operations associated with Channels. diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx index ad6c7bbe..15f5a6d6 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_bloc.mdx @@ -4,7 +4,21 @@ sidebar_position: 8 title: MessageSearchListBloc --- -`MessageSearchBloc` is used to manage a list of messages with pagination. +A Widget Used To Manage A List Of Messages With Pagination. + +### Background + +Most widgets in the Core SDK are focused on fetching a particular type of object from Stream Chat - channels, +messages, users etc. The BLoC widgets bundle up the base functions used to fetch data as well as the current +data fetched by the respective functions. Furthermore, the Core widgets use this BLoC to fetch new or +existing data and build UI based on it. + +All Core and UI widgets which focus on fetching a list of objects need to have their respective functions +above them in the widget tree. The MessageSearchListCore and MessageSearchListView require the +MessageSearchListCore above them in the widget hierarchy without which they will fail. + +### Understanding The Widget + This class can be used to load messages, perform queries, etc. `MessageSearchBloc` can be accessed at anytime by using the static `.of` method diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx index b330b71e..61463599 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/users_bloc.mdx @@ -4,7 +4,20 @@ sidebar_position: 9 title: UsersBloc --- -Widget dedicated to the management of a users list with pagination. +A Widget Dedicated To The Management Of A Users List With Pagination. + +### Background + +Most widgets in the Core SDK are focused on fetching a particular type of object from Stream Chat - channels, +messages, users, etc. The BLoC widgets bundle up the base functions used to fetch data as well as the current +data fetched by the respective functions. Furthermore, the Core widgets use this BLoC to fetch new or +existing data and build UI based on it. + +All Core and UI widgets which focus on fetching a list of objects need to have their respective functions +above them in the widget tree. The UserListCore and UserListView require the UserListCore +above them in the widget hierarchy without which they will fail. + +### Understanding The Widget `UsersBloc` can be accessed at anytime by using the static `.of` method using Flutter's `BuildContext`. From 201a510bf05dc7f1fc15f6f84f05470552eec1a6 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 13 Jul 2021 16:51:22 +0530 Subject: [PATCH 543/599] fix `ChannelListView` not retrying error on connection recovered [#504](https://github.com/GetStream/stream-chat-flutter/issues/504) Signed-off-by: xsahil03x --- .../lib/src/channel_list_view.dart | 285 +++++++++--------- .../lib/src/channels_bloc.dart | 2 + .../lib/src/lazy_load_scroll_view.dart | 3 +- .../lib/src/message_search_bloc.dart | 2 + .../lib/src/users_bloc.dart | 2 + 5 files changed, 149 insertions(+), 145 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 316d7513..06f1aee9 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -202,6 +202,7 @@ class _ChannelListViewState extends State { final _slideController = SlidableController(); late final _defaultController = ChannelListController(); + ChannelListController get _channelListController => widget.channelListController ?? _defaultController; @@ -237,39 +238,32 @@ class _ChannelListViewState extends State { } Widget _buildListView(BuildContext context, List channels) { - late Widget child; - - if (channels.isNotEmpty) { - if (widget.crossAxisCount > 1) { - child = GridView.builder( - padding: widget.padding, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: widget.crossAxisCount, - ), - itemCount: channels.length, - physics: const AlwaysScrollableScrollPhysics(), - itemBuilder: (context, index) => - _gridItemBuilder(context, index, channels), - ); - } else { - child = ListView.separated( - padding: widget.padding, - physics: const AlwaysScrollableScrollPhysics(), - itemCount: - channels.isNotEmpty ? channels.length + 1 : channels.length, - separatorBuilder: (_, index) { - if (widget.separatorBuilder != null) { - return widget.separatorBuilder!(context, index); - } - return _separatorBuilder(context, index); - }, - itemBuilder: (context, index) => - _listItemBuilder(context, index, channels), - ); - } + if (widget.crossAxisCount > 1) { + return GridView.builder( + padding: widget.padding, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: widget.crossAxisCount, + ), + itemCount: channels.length, + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (context, index) => + _gridItemBuilder(context, index, channels), + ); } - - return child; + return ListView.separated( + padding: widget.padding, + physics: const AlwaysScrollableScrollPhysics(), + // all channels + progress loader + itemCount: channels.length + 1, + separatorBuilder: (_, index) { + if (widget.separatorBuilder != null) { + return widget.separatorBuilder!(context, index); + } + return _separatorBuilder(context, index); + }, + itemBuilder: (context, index) => + _listItemBuilder(context, index, channels), + ); } Widget _buildEmptyWidget(BuildContext context) => LayoutBuilder( @@ -486,111 +480,111 @@ class _ChannelListViewState extends State { Widget _listItemBuilder(BuildContext context, int i, List channels) { final channelsBloc = ChannelsBloc.of(context); + + if (i == channels.length) { + return _buildQueryProgressIndicator(context, channelsBloc); + } + final onTap = _getChannelTap(context); final chatThemeData = StreamChatTheme.of(context); final backgroundColor = chatThemeData.colorTheme.inputBg; + final channel = channels[i]; - if (i < channels.length) { - final channel = channels[i]; - - return StreamChannel( - key: ValueKey('CHANNEL-${channel.cid}'), - channel: channel, - child: Slidable( - controller: _slideController, - enabled: widget.swipeToAction, - actionPane: const SlidableBehindActionPane(), - actionExtentRatio: 0.12, - secondaryActions: widget.swipeActions - ?.map((e) => IconSlideAction( - color: e.color, - iconWidget: e.iconWidget, - onTap: () { - e.onTap?.call(channel); - }, - )) - .toList() ?? - [ + return StreamChannel( + key: ValueKey('CHANNEL-${channel.cid}'), + channel: channel, + child: Slidable( + controller: _slideController, + enabled: widget.swipeToAction, + actionPane: const SlidableBehindActionPane(), + actionExtentRatio: 0.12, + secondaryActions: widget.swipeActions + ?.map((e) => IconSlideAction( + color: e.color, + iconWidget: e.iconWidget, + onTap: () { + e.onTap?.call(channel); + }, + )) + .toList() ?? + [ + IconSlideAction( + color: backgroundColor, + icon: Icons.more_horiz, + onTap: widget.onMoreDetailsPressed != null + ? () { + widget.onMoreDetailsPressed!(channel); + } + : () { + showModalBottomSheet( + clipBehavior: Clip.hardEdge, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(32), + topRight: Radius.circular(32), + ), + ), + context: context, + builder: (context) => StreamChannel( + channel: channel, + child: ChannelBottomSheet( + onViewInfoTap: () { + widget.onViewInfoTap?.call(channel); + }, + ), + ), + ); + }, + ), + if ([ + 'admin', + 'owner', + ].contains(channel.state!.members + .firstWhereOrNull( + (m) => m.userId == channel.client.state.user?.id) + ?.role)) IconSlideAction( color: backgroundColor, - icon: Icons.more_horiz, - onTap: widget.onMoreDetailsPressed != null + iconWidget: StreamSvgIcon.delete( + color: chatThemeData.colorTheme.accentError, + ), + onTap: widget.onDeletePressed != null ? () { - widget.onMoreDetailsPressed!(channel); + widget.onDeletePressed!(channel); } - : () { - showModalBottomSheet( - clipBehavior: Clip.hardEdge, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(32), - topRight: Radius.circular(32), - ), - ), - context: context, - builder: (context) => StreamChannel( - channel: channel, - child: ChannelBottomSheet( - onViewInfoTap: () { - widget.onViewInfoTap?.call(channel); - }, - ), + : () async { + final res = await showConfirmationDialog( + context, + title: 'Delete Conversation', + okText: 'DELETE', + question: + // ignore: lines_longer_than_80_chars + 'Are you sure you want to delete this conversation?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.delete( + color: chatThemeData.colorTheme.accentError, ), ); + if (res == true) { + await channel.delete(); + } }, ), - if ([ - 'admin', - 'owner', - ].contains(channel.state!.members - .firstWhereOrNull( - (m) => m.userId == channel.client.state.user?.id) - ?.role)) - IconSlideAction( - color: backgroundColor, - iconWidget: StreamSvgIcon.delete( - color: chatThemeData.colorTheme.accentError, - ), - onTap: widget.onDeletePressed != null - ? () { - widget.onDeletePressed!(channel); - } - : () async { - final res = await showConfirmationDialog( - context, - title: 'Delete Conversation', - okText: 'DELETE', - question: - // ignore: lines_longer_than_80_chars - 'Are you sure you want to delete this conversation?', - cancelText: 'CANCEL', - icon: StreamSvgIcon.delete( - color: chatThemeData.colorTheme.accentError, - ), - ); - if (res == true) { - await channel.delete(); - } - }, - ), - ], - child: DecoratedBox( - decoration: BoxDecoration( - color: chatThemeData.colorTheme.appBg, - ), - child: widget.channelPreviewBuilder?.call(context, channel) ?? - ChannelPreview( - onLongPress: widget.onChannelLongPress, - channel: channel, - onImageTap: () => widget.onImageTap?.call(channel), - onTap: (channel) => onTap(channel, widget.channelWidget), - ), + ], + child: DecoratedBox( + decoration: BoxDecoration( + color: chatThemeData.colorTheme.appBg, ), + child: widget.channelPreviewBuilder?.call(context, channel) ?? + ChannelPreview( + onLongPress: widget.onChannelLongPress, + channel: channel, + onImageTap: () => widget.onImageTap?.call(channel), + onTap: (channel) => onTap(channel, widget.channelWidget), + ), ), - ); - } else { - return _buildQueryProgressIndicator(context, channelsBloc); - } + ), + ); } ChannelTapCallback _getChannelTap(BuildContext context) { @@ -659,28 +653,33 @@ class _ChannelListViewState extends State { ChannelsBlocState channelsProvider, ) => BetterStreamBuilder( - stream: channelsProvider.queryChannelsLoading, - initialData: false, - errorBuilder: (context, err) => Container( - color: StreamChatTheme.of(context) - .colorTheme - .accentError - .withOpacity(.2), - child: const Padding( - padding: EdgeInsets.symmetric(vertical: 16), - child: Center( - child: Text('Error loading channels'), - ), + stream: channelsProvider.queryChannelsLoading, + initialData: false, + errorBuilder: (context, err) { + final theme = StreamChatTheme.of(context); + return Container( + color: theme.colorTheme.textLowEmphasis.withOpacity(0.9), + child: Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'Error loading channels', + style: theme.textTheme.body.copyWith( + color: Colors.white, ), ), - builder: (context, data) => data - ? const Center( - child: Padding( - padding: EdgeInsets.all(16), - child: CircularProgressIndicator(), - ), - ) - : const Offstage()); + ), + ); + }, + builder: (context, showLoading) { + if (!showLoading) return const Offstage(); + return const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: CircularProgressIndicator(), + ), + ); + }, + ); Widget _separatorBuilder(context, i) { final effect = StreamChatTheme.of(context).colorTheme.borderBottom; 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 5de9b2ea..46aaccee 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -146,6 +146,8 @@ class ChannelsBlocState extends State _paginationEnded = true; } } catch (e, stk) { + // reset loading controller + _queryChannelsLoadingController.sink.add(false); if (_channelsController.hasValue) { _queryChannelsLoadingController.addError(e, stk); } else { diff --git a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart index 834d00b0..fb503bcd 100644 --- a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart @@ -85,6 +85,7 @@ class _LazyLoadScrollViewState extends State { final extentBefore = notification.metrics.extentBefore; final extentAfter = notification.metrics.extentAfter; final scrollingDown = _scrollPosition < pixels; + _scrollPosition = pixels; if (scrollingDown) { if (extentAfter <= scrollOffset) { @@ -97,8 +98,6 @@ class _LazyLoadScrollViewState extends State { return true; } } - - _scrollPosition = pixels; } if (notification is OverscrollNotification) { if (notification.overscroll > 0) { 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 daffadfe..44ac9b3f 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 @@ -98,6 +98,8 @@ class MessageSearchBlocState extends State _queryMessagesLoadingController.add(false); } } catch (e, stk) { + // reset loading controller + _queryMessagesLoadingController.add(false); if (_messageResponses.hasValue) { _queryMessagesLoadingController.addError(e, stk); } else { 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 af707762..e7916478 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -96,6 +96,8 @@ class UsersBlocState extends State _queryUsersLoadingController.add(false); } } catch (e, stk) { + // reset loading controller + _queryUsersLoadingController.add(false); if (_usersController.hasValue) { _queryUsersLoadingController.addError(e, stk); } else { From 8853d0a785c0f6259036052dc8dbc7ca16d7e305 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 13 Jul 2021 17:12:44 +0530 Subject: [PATCH 544/599] feat: Added new docs and images --- .../user_list_core.mdx | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx index bf1055db..cbc01e40 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx @@ -18,3 +18,48 @@ exposes builders to build the UI in situations such as loading, empty data, erro ### Basic Example +[UserListCore] is a simplified class that allows fetching users while +exposing UI builders. +A [UserListController] is used to load and paginate data. + +```dart +class UsersListPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: UsersListCore( + filter: Filter.autoComplete('name', 'search_here'), + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + errorBuilder: (err) { + return Center( + child: Text('An error has occured'), + ); + }, + emptyBuilder: (context) { + return Center( + child: Text('Nothing here...'), + ); + }, + emptyBuilder: (context) { + return Center( + child: CircularProgressIndicator(), + ); + }, + listBuilder: (context, list) { + return UsersPage(list); + } + ), + ); + } +} +``` + +[UsersBloc] must be the ancestor of this widget. This is necessary since +[UserListCore] depends on functionality contained within [UsersBloc]. + +The parameters [listBuilder], [loadingBuilder], [emptyBuilder] and +[errorBuilder] must all be supplied and not null. + From f8950f837937ff5456613cdf70a6c014df88fd7e Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 13 Jul 2021 19:19:50 +0530 Subject: [PATCH 545/599] feat: added petyo changes --- docusaurus/docs/Flutter/guides/_category_.json | 2 +- docusaurus/docs/Flutter/stream_chat_flutter/_category_.json | 2 +- .../docs/Flutter/stream_chat_flutter/message_list_view.mdx | 4 +++- .../docs/Flutter/stream_chat_flutter_core/_category_.json | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/_category_.json b/docusaurus/docs/Flutter/guides/_category_.json index c2af63bb..cb58ac0d 100644 --- a/docusaurus/docs/Flutter/guides/_category_.json +++ b/docusaurus/docs/Flutter/guides/_category_.json @@ -1,4 +1,4 @@ { "label": "Guides", - "position": 5 + "position": 2 } \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/_category_.json b/docusaurus/docs/Flutter/stream_chat_flutter/_category_.json index 18f33a2b..242afb7b 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/_category_.json +++ b/docusaurus/docs/Flutter/stream_chat_flutter/_category_.json @@ -1,4 +1,4 @@ { "label": "Stream Chat Flutter", - "position": 4 + "position": 3 } \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx index 74a3ae5f..0ac78f7d 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_list_view.mdx @@ -84,7 +84,9 @@ MessageListView( You can also supply your own implementation for displaying messages using the `messageBuilder` parameter. -Note: To customize the existing implementation, look at the `MessageWidget` documentation instead. +:::note +To customize the existing implementation, look at the `MessageWidget` documentation instead. +::: ```dart MessageListView( diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/_category_.json b/docusaurus/docs/Flutter/stream_chat_flutter_core/_category_.json index 748e3f4a..8d738e89 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/_category_.json +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/_category_.json @@ -1,4 +1,4 @@ { "label": "Stream Chat Flutter Core", - "position": 3 + "position": 4 } \ No newline at end of file From cddf466d00931e6610e36528915ff5f391182a6d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 13 Jul 2021 19:25:01 +0530 Subject: [PATCH 546/599] fix: Corrected example --- .../docs/Flutter/stream_chat_flutter_core/user_list_core.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx index cbc01e40..16e39339 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx @@ -29,7 +29,7 @@ class UsersListPage extends StatelessWidget { return Scaffold( body: UsersListCore( filter: Filter.autoComplete('name', 'search_here'), - sort: [SortOption('last_message_at')], + sort: [SortOption('last_active')], pagination: PaginationParams( limit: 20, ), From 80c3f9d5877b92aa379e6250abe1e7fac99e9f6f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 13 Jul 2021 19:30:21 +0530 Subject: [PATCH 547/599] [UI-KIT] Add `ErrorListener` in message_input.dart [https://github.com/GetStream/stream-chat-flutter/issues/489] Signed-off-by: xsahil03x --- .../lib/src/message_input.dart | 69 +++++++++++-------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index c4a665e6..9dca6c6b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -27,6 +27,16 @@ import 'package:video_compress/video_compress.dart'; export 'package:video_compress/video_compress.dart' show VideoQuality; +/// A callback that can be passed to [MessageInput.onError]. +/// +/// This callback should not throw. +/// +/// It exists merely for error reporting, and should not be used otherwise. +typedef ErrorListener = void Function( + Object error, + StackTrace? stackTrace, +); + /// Builder for attachment thumbnails typedef AttachmentThumbnailBuilder = Widget Function( BuildContext, @@ -153,6 +163,7 @@ class MessageInput extends StatefulWidget { this.maxAttachmentSize = _kDefaultMaxAttachmentSize, this.compressedVideoQuality = VideoQuality.DefaultQuality, this.compressedVideoFrameRate = 30, + this.onError, }) : super(key: key); /// Message to edit @@ -233,22 +244,21 @@ class MessageInput extends StatefulWidget { /// Customize the tile for the mentions overlay final MentionTileBuilder? mentionsTileBuilder; + /// A callback for error reporting + final ErrorListener? onError; + @override MessageInputState createState() => MessageInputState(); /// Use this method to get the current [StreamChatState] instance static MessageInputState of(BuildContext context) { MessageInputState? messageInputState; - messageInputState = context.findAncestorStateOfType(); - - if (messageInputState == null) { - throw Exception( - // ignore: lines_longer_than_80_chars - 'You must have a MessageInput widget as ancestor of your widget tree'); - } - - return messageInputState; + assert( + messageInputState != null, + 'You must have a MessageInput widget as ancestor of your widget tree', + ); + return messageInputState!; } } @@ -1989,7 +1999,7 @@ class MessageInputState extends State { } /// Sends the current message - void sendMessage() async { + Future sendMessage() async { var text = textEditingController.text.trim(); if (text.isEmpty && _attachments.isEmpty) { return; @@ -2005,9 +2015,7 @@ class MessageInputState extends State { textEditingController.clear(); _attachments.clear(); - if (widget.onQuotedMessageCleared != null) { - widget.onQuotedMessageCleared!(); - } + widget.onQuotedMessageCleared?.call(); setState(() { _messageIsPresent = false; @@ -2019,7 +2027,6 @@ class MessageInputState extends State { _mentionsOverlay?.remove(); _mentionsOverlay = null; - Future sendingFuture; Message message; if (widget.editMessage != null) { message = widget.editMessage!.copyWith( @@ -2057,26 +2064,32 @@ class MessageInputState extends State { _mentionedUsers.clear(); - if (widget.editMessage == null || - widget.editMessage!.status == MessageSendingStatus.failed || - widget.editMessage!.status == MessageSendingStatus.sending) { - sendingFuture = channel.sendMessage(message); - } else { - sendingFuture = channel.updateMessage(message); - } + try { + Future sendingFuture; + if (widget.editMessage == null || + widget.editMessage!.status == MessageSendingStatus.failed || + widget.editMessage!.status == MessageSendingStatus.sending) { + sendingFuture = channel.sendMessage(message); + } else { + sendingFuture = channel.updateMessage(message); + } - if (!shouldUnfocus) { - FocusScope.of(context).requestFocus(_focusNode); - } + if (!shouldUnfocus) { + FocusScope.of(context).requestFocus(_focusNode); + } - return sendingFuture.then((resp) { + final resp = await sendingFuture; if (resp.message?.type == 'error') { _parseExistingMessage(message); } - if (widget.onMessageSent != null) { - widget.onMessageSent!(resp.message); + widget.onMessageSent?.call(resp.message); + } catch (e, stk) { + if (widget.onError != null) { + widget.onError?.call(e, stk); + } else { + rethrow; } - }); + } } StreamSubscription? _keyboardListener; From b93ac23fa9d741100423b0d1016491d6aa8f8fb3 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 14 Jul 2021 17:09:26 +0530 Subject: [PATCH 548/599] fix: Giphy cancel error --- packages/stream_chat/lib/src/client/channel.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index eee18356..94a07f34 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -888,6 +888,7 @@ class Channel { final oldMessage = state!.messages[oldIndex]; state!.updateChannelState(state!._channelState.copyWith( messages: state?.messages?..remove(oldMessage), + channel: state?._channelState.channel, )); } else { // remove thread message if present From fb10824b14f7b7fcd13960fa3e564392e93dc985 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 15 Jul 2021 12:07:48 +0200 Subject: [PATCH 549/599] use inactive as a foreground state --- .../stream_chat_flutter_core/lib/src/stream_chat_core.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index e80472cd..6e39d5e3 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -159,7 +159,10 @@ class StreamChatCoreState extends State @override void didChangeAppLifecycleState(AppLifecycleState state) { - _isInForeground = state == AppLifecycleState.resumed; + _isInForeground = [ + AppLifecycleState.resumed, + AppLifecycleState.inactive, + ].contains(state); if (user != null) { if (_isInForeground) { _onForeground(); From e99c66be999254a05ae6992ed63dc96484d7d69a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 15 Jul 2021 13:15:22 +0200 Subject: [PATCH 550/599] add tests --- .../test/stream_chat_core_test.dart | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index 178510c2..99c42abf 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -273,6 +273,144 @@ void main() { }); }, ); + testWidgets( + 'didChangeAppLifecycleState should cancel the backgroundKeepAlive timer ' + 'if it is currently running in case the widget lifestyle changes to ' + 'AppLifecycleState.inactive', + (tester) async { + await tester.runAsync(() async { + final mockClient = MockClient(); + final mockOnBackgroundEventReceived = MockOnBackgroundEventReceived(); + const backgroundKeepAlive = const Duration(seconds: 3); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), + onBackgroundEventReceived: mockOnBackgroundEventReceived, + backgroundKeepAlive: backgroundKeepAlive, + ); + + await tester.pumpWidget(streamChatCore); + + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + + final event = Event(type: EventType.any); + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + + final streamChatCoreState = tester.state( + find.byKey(streamChatCoreKey), + ); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.paused); + + await untilCalled(() => mockOnBackgroundEventReceived.call(event)); + + verify(() => mockOnBackgroundEventReceived.call(event)).called(1); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.inactive); + + verifyNever(() => mockOnBackgroundEventReceived.call(event)); + }); + }, + ); + + testWidgets( + 'didChangeAppLifecycleState should call client.connect() ' + 'if the connectionStatus is ConnectionStatus.disconnected in case the ' + 'widget lifestyle changes to AppLifecycleState.inactive', + (tester) async { + await tester.runAsync(() async { + final mockClient = MockClient(); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), + connectivityStream: Stream.value(ConnectivityResult.mobile), + ); + + await tester.pumpWidget(streamChatCore); + + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + + final event = Event(type: EventType.any); + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(() => mockClient.openConnection()) + .thenAnswer((_) async => OwnUser(id: 'test')); + when(() => mockClient.closeConnection()).thenAnswer((_) async => null); + when(() => mockClient.wsConnectionStatus) + .thenReturn(ConnectionStatus.disconnected); + + final streamChatCoreState = tester.state( + find.byKey(streamChatCoreKey), + ); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.paused); + + await Future.delayed(const Duration(seconds: 1)); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.inactive); + + verify(() => mockClient.openConnection()).called(1); + }); + }, + ); + + testWidgets( + 'didChangeAppLifecycleState should not call client.openConnection() ' + 'if connection is not available in case the ' + 'widget lifestyle changes to AppLifecycleState.inactive', + (tester) async { + await tester.runAsync(() async { + final mockClient = MockClient(); + const streamChatCoreKey = Key('streamChatCore'); + const childKey = Key('child'); + + final event = Event(); + when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(() => mockClient.openConnection()) + .thenAnswer((_) async => OwnUser(id: 'test')); + when(() => mockClient.closeConnection()).thenAnswer((_) async => null); + when(() => mockClient.wsConnectionStatus) + .thenReturn(ConnectionStatus.disconnected); + + final streamChatCore = StreamChatCore( + key: streamChatCoreKey, + client: mockClient, + child: Offstage(key: childKey), + connectivityStream: Stream.value(ConnectivityResult.none), + ); + + await tester.pumpWidget(streamChatCore); + + expect(find.byKey(streamChatCoreKey), findsOneWidget); + expect(find.byKey(childKey), findsOneWidget); + + final streamChatCoreState = tester.state( + find.byKey(streamChatCoreKey), + ); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.paused); + + await Future.delayed(const Duration(seconds: 1)); + + streamChatCoreState + .didChangeAppLifecycleState(AppLifecycleState.inactive); + + verifyNever(() => mockClient.openConnection()); + }); + }, + ); testWidgets( 'streamChatCoreState.userStream should emit all the user events ' From 769258bb53453abf7e8566b7fd6c46f0cd882ec1 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 15 Jul 2021 19:00:03 +0530 Subject: [PATCH 551/599] [UI-KIT] Fix channel_image.dart, remove defaultChannelPreview from theme Signed-off-by: xsahil03x --- .../lib/src/channel_image.dart | 202 +++++++++--------- .../lib/src/group_image.dart | 118 +++++----- .../lib/src/stream_chat_theme.dart | 7 - 3 files changed, 164 insertions(+), 163 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 9ac07033..21737814 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -83,121 +83,117 @@ class ChannelImage extends StatelessWidget { Widget build(BuildContext context) { final streamChat = StreamChat.of(context); final channel = this.channel ?? StreamChannel.of(context).channel; + + assert(channel.state != null, 'Channel ${channel.id} is not initialized'); + + final chatThemeData = StreamChatTheme.of(context); + final colorTheme = chatThemeData.colorTheme; + final previewTheme = chatThemeData.channelPreviewTheme.avatarTheme; + return BetterStreamBuilder>( stream: channel.extraDataStream, initialData: channel.extraData, - builder: (context, data) { - String? image; - final chatThemeData = StreamChatTheme.of(context); - if (data.containsKey('image') == true) { - image = data['image']; - } else if (channel.state?.members.length == 2) { - final otherMember = channel.state?.members - .firstWhere((member) => member.user?.id != streamChat.user?.id); - return BetterStreamBuilder( - stream: streamChat.client.state.usersStream - .map((users) => - users[otherMember?.userId] ?? otherMember!.user!) - .distinct(), - initialData: otherMember!.user, - builder: (context, user) => UserAvatar( - borderRadius: borderRadius ?? - chatThemeData - .channelPreviewTheme.avatarTheme?.borderRadius, - user: user ?? otherMember.user!, - constraints: constraints ?? - chatThemeData - .channelPreviewTheme.avatarTheme?.constraints, - onTap: onTap != null ? (_) => onTap!() : null, - selected: selected, - selectionColor: selectionColor ?? - chatThemeData.colorTheme.accentPrimary, - selectionThickness: selectionThickness, - )); - } else { - final images = channel.state?.members - .where((member) => - member.user?.id != streamChat.user?.id && - member.user?.extraData['image'] != null) - .take(4) - // ignore: cast_nullable_to_non_nullable - .map((e) => e.user?.extraData['image'] as String) - .toList(); - return GroupImage( - images: images ?? [], - borderRadius: borderRadius ?? - chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius, - constraints: constraints ?? - chatThemeData.channelPreviewTheme.avatarTheme?.constraints, - onTap: onTap, - selected: selected, - selectionColor: - selectionColor ?? chatThemeData.colorTheme.accentPrimary, - selectionThickness: selectionThickness, - ); - } + builder: (context, extraData) { + final channelImage = extraData['image']; - Widget child = ClipRRect( - borderRadius: borderRadius ?? - chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius, - child: Container( - constraints: constraints ?? - chatThemeData.channelPreviewTheme.avatarTheme?.constraints, - decoration: BoxDecoration( - color: chatThemeData.colorTheme.accentPrimary, - ), - child: Stack( - alignment: Alignment.center, - fit: StackFit.expand, - children: [ - if (image != null) - CachedNetworkImage( - imageUrl: image, - errorWidget: (_, __, ___) => Center( - child: Text( - data.containsKey('name') ? data['name'][0] : '', - style: TextStyle( - color: chatThemeData.colorTheme.barsBg, - fontWeight: FontWeight.bold, - ), + if (channelImage != null) { + Widget child = ClipRRect( + borderRadius: borderRadius ?? previewTheme?.borderRadius, + child: Container( + constraints: constraints ?? previewTheme?.constraints, + decoration: BoxDecoration(color: colorTheme.accentPrimary), + child: InkWell( + onTap: onTap, + child: CachedNetworkImage( + imageUrl: channelImage, + errorWidget: (_, __, ___) => Center( + child: Text( + extraData['name']?[0] ?? '', + style: TextStyle( + color: colorTheme.barsBg, + fontWeight: FontWeight.bold, ), ), - fit: BoxFit.cover, - ) - else - chatThemeData.defaultChannelImage( - context, - channel, - ), - Material( - color: Colors.transparent, - child: InkWell( - onTap: onTap, ), + fit: BoxFit.cover, ), - ], - ), - ), - ); - if (selected) { - child = ClipRRect( - key: const Key('selectedImage'), - borderRadius: (borderRadius ?? - chatThemeData.ownMessageTheme.avatarTheme?.borderRadius ?? - BorderRadius.zero) + - BorderRadius.circular(selectionThickness), - child: Container( - constraints: constraints ?? - chatThemeData.ownMessageTheme.avatarTheme?.constraints, - color: selectionColor ?? chatThemeData.colorTheme.accentPrimary, - child: Padding( - padding: EdgeInsets.all(selectionThickness), - child: child, ), ), ); + + if (selected) { + child = ClipRRect( + key: const Key('selectedImage'), + borderRadius: BorderRadius.circular(selectionThickness) + + (borderRadius ?? + previewTheme?.borderRadius ?? + BorderRadius.zero), + child: Container( + constraints: constraints ?? previewTheme?.constraints, + color: selectionColor ?? colorTheme.accentPrimary, + child: Padding( + padding: EdgeInsets.all(selectionThickness), + child: child, + ), + ), + ); + } + return child; } - return child; + + final currentUser = streamChat.user!; + final otherMembers = channel.state!.members + .where((it) => it.userId != currentUser.id) + .toList(growable: false); + + // our own space, no other members + if (otherMembers.isEmpty) { + return BetterStreamBuilder( + stream: streamChat.client.state.userStream.map((it) => it!), + initialData: currentUser, + builder: (context, user) => UserAvatar( + borderRadius: borderRadius ?? previewTheme?.borderRadius, + user: user, + constraints: constraints ?? previewTheme?.constraints, + onTap: onTap != null ? (_) => onTap!() : null, + selected: selected, + selectionColor: selectionColor ?? colorTheme.accentPrimary, + selectionThickness: selectionThickness, + ), + ); + } + + // 1-1 Conversation + if (otherMembers.length == 1) { + final member = otherMembers.first; + final user = member.user!; + return BetterStreamBuilder( + stream: streamChat.client.state.usersStream + .map((users) => users[member.userId ?? user.id] ?? user) + .distinct(), + initialData: user, + builder: (context, user) => UserAvatar( + borderRadius: borderRadius ?? previewTheme?.borderRadius, + user: user, + constraints: constraints ?? previewTheme?.constraints, + onTap: onTap != null ? (_) => onTap!() : null, + selected: selected, + selectionColor: selectionColor ?? colorTheme.accentPrimary, + selectionThickness: selectionThickness, + ), + ); + } + + // Group conversation + return GroupImage( + members: otherMembers, + borderRadius: borderRadius ?? previewTheme?.borderRadius, + constraints: constraints ?? previewTheme?.constraints, + onTap: onTap, + selected: selected, + selectionColor: selectionColor ?? colorTheme.accentPrimary, + selectionThickness: selectionThickness, + ); }, ); } diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index 3513bda2..62b8b7b5 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -7,7 +6,7 @@ class GroupImage extends StatelessWidget { /// Constructor for creating a [GroupImage] const GroupImage({ Key? key, - required this.images, + required this.members, this.constraints, this.onTap, this.borderRadius, @@ -17,7 +16,7 @@ class GroupImage extends StatelessWidget { }) : super(key: key); /// List of images to display - final List images; + final List members; /// Constraints on the widget final BoxConstraints? constraints; @@ -39,20 +38,22 @@ class GroupImage extends StatelessWidget { @override Widget build(BuildContext context) { - Widget? avatar; - final streamChatTheme = StreamChatTheme.of(context); + final streamChat = StreamChat.of(context); + final channel = StreamChannel.of(context).channel; - avatar = GestureDetector( + assert(channel.state != null, 'Channel ${channel.id} is not initialized'); + + final streamChatTheme = StreamChatTheme.of(context); + final colorTheme = streamChatTheme.colorTheme; + final previewTheme = streamChatTheme.channelPreviewTheme.avatarTheme; + + Widget avatar = GestureDetector( onTap: onTap, child: ClipRRect( - borderRadius: borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius, + borderRadius: borderRadius ?? previewTheme?.borderRadius, child: Container( - constraints: constraints ?? - streamChatTheme.ownMessageTheme.avatarTheme?.constraints, - decoration: BoxDecoration( - color: streamChatTheme.colorTheme.accentPrimary, - ), + constraints: constraints ?? previewTheme?.constraints, + decoration: BoxDecoration(color: colorTheme.accentPrimary), child: Flex( direction: Axis.vertical, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -62,48 +63,62 @@ class GroupImage extends StatelessWidget { child: Flex( direction: Axis.horizontal, crossAxisAlignment: CrossAxisAlignment.stretch, - children: images - .take(2) - .map((url) => Flexible( - fit: FlexFit.tight, - child: FittedBox( - fit: BoxFit.cover, - clipBehavior: Clip.antiAlias, - child: Transform.scale( - scale: 1.2, - child: CachedNetworkImage( - imageUrl: url, - fit: BoxFit.cover, - ), - ), + children: members.take(2).map((member) { + final user = member.user!; + return Flexible( + fit: FlexFit.tight, + child: FittedBox( + fit: BoxFit.cover, + clipBehavior: Clip.antiAlias, + child: Transform.scale( + scale: 1.2, + child: BetterStreamBuilder( + stream: streamChat.client.state.usersStream + .map((users) => + users[member.userId ?? user.id] ?? user) + .distinct(), + initialData: user, + builder: (context, user) => UserAvatar( + user: user, + borderRadius: BorderRadius.zero, ), - )) - .toList(), + ), + ), + ), + ); + }).toList(), ), ), - if (images.length > 2) + if (members.length > 2) Flexible( fit: FlexFit.tight, child: Flex( direction: Axis.horizontal, crossAxisAlignment: CrossAxisAlignment.stretch, - children: images - .skip(2) - .map((url) => Flexible( - fit: FlexFit.tight, - child: FittedBox( - fit: BoxFit.cover, - clipBehavior: Clip.antiAlias, - child: Transform.scale( - scale: 1.2, - child: CachedNetworkImage( - imageUrl: url, - fit: BoxFit.cover, - ), - ), + children: members.skip(2).take(2).map((member) { + final user = member.user!; + return Flexible( + fit: FlexFit.tight, + child: FittedBox( + fit: BoxFit.cover, + clipBehavior: Clip.antiAlias, + child: Transform.scale( + scale: 1.2, + child: BetterStreamBuilder( + stream: streamChat.client.state.usersStream + .map((users) => + users[member.userId ?? user.id] ?? user) + .distinct(), + initialData: user, + builder: (context, user) => UserAvatar( + user: user, + borderRadius: BorderRadius.zero, ), - )) - .toList(), + ), + ), + ), + ); + }).toList(), ), ), ], @@ -114,14 +129,11 @@ class GroupImage extends StatelessWidget { if (selected) { avatar = ClipRRect( - borderRadius: (borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius ?? - BorderRadius.zero) + - BorderRadius.circular(selectionThickness), + borderRadius: BorderRadius.circular(selectionThickness) + + (borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero), child: Container( - color: selectionColor ?? streamChatTheme.colorTheme.accentPrimary, - height: 64, - width: 64, + constraints: constraints ?? previewTheme?.constraints, + color: selectionColor ?? colorTheme.accentPrimary, child: Padding( padding: EdgeInsets.all(selectionThickness), child: avatar, 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 72b04a80..6956ff3d 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -103,7 +103,6 @@ class StreamChatThemeData { required this.otherMessageTheme, required this.ownMessageTheme, required this.messageInputTheme, - required this.defaultChannelImage, required this.defaultUserImage, required this.primaryIconTheme, required this.reactionIcons, @@ -145,9 +144,6 @@ class StreamChatThemeData { /// Theme dedicated to the [MessageInput] widget final MessageInputTheme messageInputTheme; - /// The widget that will be built when the channel image is unavailable - final Widget Function(BuildContext, Channel) defaultChannelImage; - /// The widget that will be built when the user image is unavailable final Widget Function(BuildContext, User) defaultUserImage; @@ -179,7 +175,6 @@ class StreamChatThemeData { textTheme: this.textTheme.merge(textTheme), colorTheme: this.colorTheme.merge(colorTheme), primaryIconTheme: this.primaryIconTheme.merge(primaryIconTheme), - defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage, defaultUserImage: defaultUserImage ?? this.defaultUserImage, channelPreviewTheme: this.channelPreviewTheme.merge(channelPreviewTheme), @@ -199,7 +194,6 @@ class StreamChatThemeData { textTheme: textTheme.merge(other.textTheme), colorTheme: colorTheme.merge(other.colorTheme), primaryIconTheme: other.primaryIconTheme, - defaultChannelImage: other.defaultChannelImage, defaultUserImage: other.defaultUserImage, channelPreviewTheme: channelPreviewTheme.merge(other.channelPreviewTheme), channelTheme: channelTheme.merge(other.channelTheme), @@ -223,7 +217,6 @@ class StreamChatThemeData { textTheme: textTheme, colorTheme: colorTheme, primaryIconTheme: iconTheme, - defaultChannelImage: (context, channel) => const SizedBox(), defaultUserImage: (context, user) => Center( child: CachedNetworkImage( filterQuality: FilterQuality.high, From 6f8dbf7e00e9822e6aa67bddfbb19e5533823249 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 15 Jul 2021 19:02:15 +0530 Subject: [PATCH 552/599] ChannelPreview: Minor refactoring Signed-off-by: xsahil03x --- .../lib/src/channel_preview.dart | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 231b5f94..b6d89168 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -81,20 +81,9 @@ class ChannelPreview extends StatelessWidget { contentPadding: const EdgeInsets.symmetric( horizontal: 8, ), - onTap: () { - if (onTap != null) { - onTap!(channel); - } - }, - onLongPress: () { - if (onLongPress != null) { - onLongPress!(channel); - } - }, - leading: leading ?? - ChannelImage( - onTap: onImageTap, - ), + onTap: () => onTap?.call(channel), + onLongPress: () => onLongPress?.call(channel), + leading: leading ?? ChannelImage(onTap: onImageTap), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ From 485e077596dc7edddcd4cdf28c432bcc517497d5 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Thu, 15 Jul 2021 09:44:12 -0400 Subject: [PATCH 553/599] feat: add GalleryHeader/GalleryFooter theme classes (#546) * chore: Add ImageHeaderTheme and ImageHeaderThemeData to stream_chat_theme.dart This commit adds the following classes to `stream_chat_theme.dart`: 1. `ImageHeaderTheme`, an `InheritedTheme` widget that can wrap other widgets and provide that sub-tree with an `ImageHeaderThemeData` 2. `ImageHeaderThemeData`, a class that allows for the customization of `ImageHeader` widgets * Make ImageHeaderThemeData constructor const * test: initial ImageHeaderThemeData tests Tests the copyWith, ==, and hashcode functions * test: add tests for default ImageHeaderThemeData values Tests the default values of ImageHeaderThemeData that gets built in a StreamChatThemeData against control values. * Update test name and add comments describing control * fix: use ImageHeaderTheme.of(context) instead of StreamChatTheme.of(context) This ensures that ImageHeader will always look for the closest ImageHeaderThemeData in the widget tree. * test: add dark theme default values test * run dartfmt on test file * test: lerping between ImageHeaderThemeData Added tests that lerp from one ImageHeaderThemeData to another (light to dark and dark to light) * test: merging ImageHeaderThemeData Added tests that merge light and dark ImageHeaderThemeData together (light + dark = dark, dark + light = light) * test: add a half-lerp test Added a test that lerps halway between light and dark ImageHeaderThemeData * chore: Add ImageFooterTheme and ImageFooterThemeData to stream_chat_theme.dart This commit adds the following classes to `stream_chat_theme.dart`: 1. `ImageFooterTheme`, an `InheritedTheme` widget that can wrap other widgets and provide that sub-tree with an `ImageFooterThemeData` 2. `ImageFooterThemeData`, a class that allows for the customization of `ImageFooter` widget * chore: apply ImageFooterThemeData to image_footer.dart * fix: make ImageFooterThemeData constructor const * test: add tests for ImageFooterThemeData * add a missing trailing comma * chore: rename ImageHeader & ImageFooter to GalleryHeader and GalleryFooter The respective theme classes and tests have also been renamed * Run dartfmt on test * test: fix failing tests due to renaming --- .../lib/src/full_screen_media.dart | 8 +- ...{image_footer.dart => gallery_footer.dart} | 29 +- ...{image_header.dart => gallery_header.dart} | 20 +- .../lib/src/stream_chat_theme.dart | 499 ++++++++++++++++-- .../lib/stream_chat_flutter.dart | 4 +- .../test/src/gallery_footer_theme_test.dart | 185 +++++++ .../test/src/gallery_header_theme_test.dart | 190 +++++++ .../test/src/image_footer_test.dart | 2 +- 8 files changed, 874 insertions(+), 63 deletions(-) rename packages/stream_chat_flutter/lib/src/{image_footer.dart => gallery_footer.dart} (91%) rename packages/stream_chat_flutter/lib/src/{image_header.dart => gallery_header.dart} (83%) create mode 100644 packages/stream_chat_flutter/test/src/gallery_footer_theme_test.dart create mode 100644 packages/stream_chat_flutter/test/src/gallery_header_theme_test.dart diff --git a/packages/stream_chat_flutter/lib/src/full_screen_media.dart b/packages/stream_chat_flutter/lib/src/full_screen_media.dart index f86ac60b..0f6101b9 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -6,8 +6,8 @@ import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:photo_view/photo_view.dart'; -import 'package:stream_chat_flutter/src/image_footer.dart'; -import 'package:stream_chat_flutter/src/image_header.dart'; +import 'package:stream_chat_flutter/src/gallery_footer.dart'; +import 'package:stream_chat_flutter/src/gallery_header.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_player/video_player.dart'; @@ -180,7 +180,7 @@ class _FullScreenMediaState extends State child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ImageHeader( + GalleryHeader( userName: widget.userName, sentAt: // ignore: lines_longer_than_80_chars @@ -198,7 +198,7 @@ class _FullScreenMediaState extends State }, ), if (widget.message.type != 'ephemeral') - ImageFooter( + GalleryFooter( currentPage: _currentPage, totalPages: widget.mediaAttachments.length, mediaAttachments: widget.mediaAttachments, diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/gallery_footer.dart similarity index 91% rename from packages/stream_chat_flutter/lib/src/image_footer.dart rename to packages/stream_chat_flutter/lib/src/gallery_footer.dart index 4b32afd8..c26c8dbc 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/gallery_footer.dart @@ -13,9 +13,9 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Footer widget for media display -class ImageFooter extends StatefulWidget implements PreferredSizeWidget { +class GalleryFooter extends StatefulWidget implements PreferredSizeWidget { /// Creates a channel header - const ImageFooter({ + const GalleryFooter({ Key? key, required this.message, this.onBackPressed, @@ -54,13 +54,13 @@ class ImageFooter extends StatefulWidget implements PreferredSizeWidget { final ValueChanged? mediaSelectedCallBack; @override - _ImageFooterState createState() => _ImageFooterState(); + _GalleryFooterState createState() => _GalleryFooterState(); @override final Size preferredSize; } -class _ImageFooterState extends State { +class _GalleryFooterState extends State { final TextEditingController _messageController = TextEditingController(); final FocusNode _messageFocusNode = FocusNode(); @@ -78,7 +78,7 @@ class _ImageFooterState extends State { Widget build(BuildContext context) { const showShareButton = !kIsWeb; final mediaQueryData = MediaQuery.of(context); - final chatThemeData = StreamChatTheme.of(context); + final galleryFooterThemeData = GalleryFooterTheme.of(context); return SizedBox.fromSize( size: Size( mediaQueryData.size.width, @@ -88,7 +88,7 @@ class _ImageFooterState extends State { context: context, removeTop: true, child: BottomAppBar( - color: chatThemeData.colorTheme.barsBg, + color: galleryFooterThemeData.backgroundColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -100,7 +100,7 @@ class _ImageFooterState extends State { IconButton( icon: StreamSvgIcon.iconShare( size: 24, - color: chatThemeData.colorTheme.textHighEmphasis, + color: galleryFooterThemeData.shareIconColor, ), onPressed: () async { final attachment = @@ -136,7 +136,7 @@ class _ImageFooterState extends State { children: [ Text( '${widget.currentPage + 1} of ${widget.totalPages}', - style: chatThemeData.textTheme.headlineBold, + style: galleryFooterThemeData.titleTextStyle, ), ], ), @@ -144,7 +144,7 @@ class _ImageFooterState extends State { ), IconButton( icon: StreamSvgIcon.iconGrid( - color: chatThemeData.colorTheme.textHighEmphasis, + color: galleryFooterThemeData.gridIconButtonColor, ), onPressed: () => _showPhotosModal(context), ), @@ -157,10 +157,11 @@ class _ImageFooterState extends State { void _showPhotosModal(context) { final chatThemeData = StreamChatTheme.of(context); + final galleryFooterThemeData = GalleryFooterTheme.of(context); showModalBottomSheet( context: context, - barrierColor: chatThemeData.colorTheme.overlay, - backgroundColor: chatThemeData.colorTheme.barsBg, + barrierColor: galleryFooterThemeData.bottomSheetBarrierColor, + backgroundColor: galleryFooterThemeData.bottomSheetBackgroundColor, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -191,7 +192,8 @@ class _ImageFooterState extends State { padding: const EdgeInsets.all(16), child: Text( 'Photos', - style: chatThemeData.textTheme.headlineBold, + style: + galleryFooterThemeData.bottomSheetPhotosTextStyle, ), ), ), @@ -199,7 +201,8 @@ class _ImageFooterState extends State { alignment: Alignment.centerRight, child: IconButton( icon: StreamSvgIcon.close( - color: chatThemeData.colorTheme.textHighEmphasis, + color: + galleryFooterThemeData.bottomSheetCloseIconColor, ), onPressed: () => Navigator.maybePop(context), ), diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/gallery_header.dart similarity index 83% rename from packages/stream_chat_flutter/lib/src/image_header.dart rename to packages/stream_chat_flutter/lib/src/gallery_header.dart index 92f4fa10..6c4ecfee 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/gallery_header.dart @@ -5,9 +5,9 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Header/AppBar widget for media display screen -class ImageHeader extends StatelessWidget implements PreferredSizeWidget { +class GalleryHeader extends StatelessWidget implements PreferredSizeWidget { /// Creates a channel header - const ImageHeader({ + const GalleryHeader({ Key? key, required this.message, this.currentIndex = 0, @@ -51,7 +51,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { - final chatThemeData = StreamChatTheme.of(context); + final galleryHeaderThemeData = GalleryHeaderTheme.of(context); return AppBar( textTheme: Theme.of(context).textTheme, brightness: Theme.of(context).brightness, @@ -59,18 +59,18 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { leading: showBackButton ? IconButton( icon: StreamSvgIcon.close( - color: chatThemeData.colorTheme.textHighEmphasis, + color: galleryHeaderThemeData.closeButtonColor, size: 24, ), onPressed: onBackPressed, ) : const SizedBox(), - backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color, + backgroundColor: galleryHeaderThemeData.backgroundColor, actions: [ if (message.type != 'ephemeral') IconButton( icon: StreamSvgIcon.iconMenuPoint( - color: chatThemeData.colorTheme.textHighEmphasis, + color: galleryHeaderThemeData.iconMenuPointColor, ), onPressed: () { _showMessageActionModalBottomSheet(context); @@ -90,11 +90,11 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { children: [ Text( userName, - style: chatThemeData.textTheme.headlineBold, + style: galleryHeaderThemeData.titleTextStyle, ), Text( sentAt, - style: chatThemeData.channelPreviewTheme.subtitle, + style: galleryHeaderThemeData.subtitleTextStyle, ), ], ), @@ -109,11 +109,13 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { void _showMessageActionModalBottomSheet(BuildContext context) async { final channel = StreamChannel.of(context).channel; + final galleryHeaderThemeData = + StreamChatTheme.of(context).galleryHeaderTheme; final result = await showDialog( useRootNavigator: false, context: context, - barrierColor: StreamChatTheme.of(context).colorTheme.overlay, + barrierColor: galleryHeaderThemeData.bottomSheetBarrierColor, builder: (context) => StreamChannel( channel: channel, child: AttachmentActionsModal( 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 72b04a80..498b429e 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -1,4 +1,5 @@ import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/channel_header.dart'; import 'package:stream_chat_flutter/src/channel_preview.dart'; @@ -58,6 +59,8 @@ class StreamChatThemeData { Widget Function(BuildContext, User)? defaultUserImage, IconThemeData? primaryIconTheme, List? reactionIcons, + GalleryHeaderThemeData? imageHeaderTheme, + GalleryFooterThemeData? imageFooterTheme, }) { brightness ??= colorTheme?.brightness ?? Brightness.light; final isDark = brightness == Brightness.dark; @@ -80,6 +83,8 @@ class StreamChatThemeData { defaultUserImage: defaultUserImage, primaryIconTheme: primaryIconTheme, reactionIcons: reactionIcons, + galleryHeaderTheme: imageHeaderTheme, + galleryFooterTheme: imageFooterTheme, ); return defaultData.merge(customizedData); @@ -107,6 +112,8 @@ class StreamChatThemeData { required this.defaultUserImage, required this.primaryIconTheme, required this.reactionIcons, + required this.galleryHeaderTheme, + required this.galleryFooterTheme, }); /// Create a theme from a Material [Theme] @@ -136,6 +143,14 @@ class StreamChatThemeData { /// Theme of the chat widgets dedicated to a channel final ChannelTheme channelTheme; + /// The default style for [GalleryHeader]s below the overall + /// [StreamChatTheme]. + final GalleryHeaderThemeData galleryHeaderTheme; + + /// The default style for [GalleryFooter]s below the overall + /// [StreamChatTheme]. + final GalleryFooterThemeData galleryFooterTheme; + /// Theme of the current user messages final MessageTheme ownMessageTheme; @@ -172,6 +187,8 @@ class StreamChatThemeData { IconThemeData? primaryIconTheme, ChannelListHeaderTheme? channelListHeaderTheme, List? reactionIcons, + GalleryHeaderThemeData? galleryHeaderTheme, + GalleryFooterThemeData? galleryFooterTheme, }) => StreamChatThemeData.raw( channelListHeaderTheme: @@ -188,6 +205,8 @@ class StreamChatThemeData { otherMessageTheme: this.otherMessageTheme.merge(otherMessageTheme), messageInputTheme: this.messageInputTheme.merge(messageInputTheme), reactionIcons: reactionIcons ?? this.reactionIcons, + galleryHeaderTheme: galleryHeaderTheme ?? this.galleryHeaderTheme, + galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme, ); /// Merge themes @@ -207,6 +226,8 @@ class StreamChatThemeData { otherMessageTheme: otherMessageTheme.merge(other.otherMessageTheme), messageInputTheme: messageInputTheme.merge(other.messageInputTheme), reactionIcons: other.reactionIcons, + galleryHeaderTheme: galleryHeaderTheme.merge(other.galleryHeaderTheme), + galleryFooterTheme: galleryFooterTheme.merge(other.galleryFooterTheme), ); } @@ -219,6 +240,40 @@ class StreamChatThemeData { final accentColor = colorTheme.accentPrimary; final iconTheme = IconThemeData(color: colorTheme.textHighEmphasis.withOpacity(.5)); + final channelTheme = ChannelTheme( + channelHeaderTheme: ChannelHeaderTheme( + avatarTheme: AvatarTheme( + borderRadius: BorderRadius.circular(20), + constraints: const BoxConstraints.tightFor( + height: 40, + width: 40, + ), + ), + color: colorTheme.barsBg, + title: textTheme.headlineBold, + subtitle: textTheme.footnote.copyWith( + color: const Color(0xff7A7A7A), + ), + ), + ); + final channelPreviewTheme = ChannelPreviewTheme( + unreadCounterColor: colorTheme.accentError, + avatarTheme: AvatarTheme( + borderRadius: BorderRadius.circular(20), + constraints: const BoxConstraints.tightFor( + height: 40, + width: 40, + ), + ), + title: textTheme.bodyBold, + subtitle: textTheme.footnote.copyWith( + color: const Color(0xff7A7A7A), + ), + lastMessageAt: textTheme.footnote.copyWith( + color: colorTheme.textHighEmphasis.withOpacity(.5), + ), + indicatorIconSize: 16, + ); return StreamChatThemeData.raw( textTheme: textTheme, colorTheme: colorTheme, @@ -231,24 +286,7 @@ class StreamChatThemeData { fit: BoxFit.cover, ), ), - channelPreviewTheme: ChannelPreviewTheme( - unreadCounterColor: colorTheme.accentError, - avatarTheme: AvatarTheme( - borderRadius: BorderRadius.circular(20), - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), - ), - title: textTheme.bodyBold, - subtitle: textTheme.footnote.copyWith( - color: const Color(0xff7A7A7A), - ), - lastMessageAt: textTheme.footnote.copyWith( - color: colorTheme.textHighEmphasis.withOpacity(.5), - ), - indicatorIconSize: 16, - ), + channelPreviewTheme: channelPreviewTheme, channelListHeaderTheme: ChannelListHeaderTheme( avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), @@ -260,22 +298,7 @@ class StreamChatThemeData { color: colorTheme.barsBg, title: textTheme.headlineBold, ), - channelTheme: ChannelTheme( - channelHeaderTheme: ChannelHeaderTheme( - avatarTheme: AvatarTheme( - borderRadius: BorderRadius.circular(20), - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), - ), - color: colorTheme.barsBg, - title: textTheme.headlineBold, - subtitle: textTheme.footnote.copyWith( - color: const Color(0xff7A7A7A), - ), - ), - ), + channelTheme: channelTheme, ownMessageTheme: MessageTheme( messageAuthor: textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), @@ -407,6 +430,24 @@ class StreamChatThemeData { }, ), ], + galleryHeaderTheme: GalleryHeaderThemeData( + closeButtonColor: colorTheme.textHighEmphasis, + backgroundColor: channelTheme.channelHeaderTheme.color, + iconMenuPointColor: colorTheme.textHighEmphasis, + titleTextStyle: textTheme.headlineBold, + subtitleTextStyle: channelPreviewTheme.subtitle, + bottomSheetBarrierColor: colorTheme.overlay, + ), + galleryFooterTheme: GalleryFooterThemeData( + backgroundColor: colorTheme.barsBg, + shareIconColor: colorTheme.textHighEmphasis, + titleTextStyle: textTheme.headlineBold, + gridIconButtonColor: colorTheme.textHighEmphasis, + bottomSheetBarrierColor: colorTheme.overlay, + bottomSheetBackgroundColor: colorTheme.barsBg, + bottomSheetPhotosTextStyle: textTheme.headlineBold, + bottomSheetCloseIconColor: colorTheme.textHighEmphasis, + ), ); } } @@ -1287,3 +1328,393 @@ class Effect { blur: blur ?? this.blur, ); } + +/// Overrides the default style of [GalleryHeader] descendants. +/// +/// See also: +/// +/// * [GalleryHeaderThemeData], which is used to configure this theme. +class GalleryHeaderTheme extends InheritedTheme { + /// Creates an [GalleryHeaderTheme]. + /// + /// The [data] parameter must not be null. + const GalleryHeaderTheme({ + Key? key, + required this.data, + required Widget child, + }) : super(key: key, child: child); + + /// The configuration of this theme. + final GalleryHeaderThemeData data; + + /// The closest instance of this class that encloses the given context. + /// + /// If there is no enclosing [GalleryHeaderTheme] widget, then + /// [StreamChatThemeData.galleryHeaderTheme] is used. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// ImageHeaderTheme theme = ImageHeaderTheme.of(context); + /// ``` + static GalleryHeaderThemeData of(BuildContext context) { + final galleryHeaderTheme = + context.dependOnInheritedWidgetOfExactType(); + return galleryHeaderTheme?.data ?? + StreamChatTheme.of(context).galleryHeaderTheme; + } + + @override + Widget wrap(BuildContext context, Widget child) => + GalleryHeaderTheme(data: data, child: child); + + @override + bool updateShouldNotify(GalleryHeaderTheme oldWidget) => + data != oldWidget.data; +} + +/// A style that overrides the default appearance of [GalleryHeader]s when used +/// with [GalleryHeaderTheme] or with the overall [StreamChatTheme]'s +/// [StreamChatThemeData.galleryHeaderTheme]. +/// +/// See also: +/// +/// * [GalleryHeaderTheme], the theme which is configured with this class. +/// * [StreamChatThemeData.galleryHeaderTheme], which can be used to override +/// the default style for [GalleryHeader]s below the overall [StreamChatTheme]. +class GalleryHeaderThemeData with Diagnosticable { + /// Creates an [GalleryHeaderThemeData]. + const GalleryHeaderThemeData({ + this.closeButtonColor, + this.backgroundColor, + this.iconMenuPointColor, + this.titleTextStyle, + this.subtitleTextStyle, + this.bottomSheetBarrierColor, + }); + + /// The color of the "close" button. + /// + /// Defaults to [ColorTheme.textHighEmphasis]. + final Color? closeButtonColor; + + /// The background color of the [GalleryHeader] widget. + /// + /// Defaults to [ChannelHeaderTheme.color]. + final Color? backgroundColor; + + /// Defaults to [ColorTheme.textHighEmphasis]. + final Color? iconMenuPointColor; + + /// The [TextStyle] to use for the [GalleryHeader] title text. + /// + /// Defaults to [TextTheme.headlineBold]. + final TextStyle? titleTextStyle; + + /// The [TextStyle] to use for the [GalleryHeader] subtitle text. + /// + /// Defaults to [ChannelPreviewTheme.subtitle]. + final TextStyle? subtitleTextStyle; + + /// + final Color? bottomSheetBarrierColor; + + /// Copies this [GalleryHeaderThemeData] to another. + GalleryHeaderThemeData copyWith({ + Color? closeButtonColor, + Color? backgroundColor, + Color? iconMenuPointColor, + TextStyle? titleTextStyle, + TextStyle? subtitleTextStyle, + Color? bottomSheetBarrierColor, + }) => + GalleryHeaderThemeData( + closeButtonColor: closeButtonColor ?? this.closeButtonColor, + backgroundColor: backgroundColor ?? this.backgroundColor, + iconMenuPointColor: iconMenuPointColor ?? this.iconMenuPointColor, + titleTextStyle: titleTextStyle ?? this.titleTextStyle, + subtitleTextStyle: subtitleTextStyle ?? this.subtitleTextStyle, + bottomSheetBarrierColor: + bottomSheetBarrierColor ?? this.bottomSheetBarrierColor, + ); + + /// Linearly interpolate between two [GalleryHeader] themes. + /// + /// All the properties must be non-null. + GalleryHeaderThemeData lerp( + GalleryHeaderThemeData a, + GalleryHeaderThemeData b, + double t, + ) => + GalleryHeaderThemeData( + closeButtonColor: Color.lerp(a.closeButtonColor, b.closeButtonColor, t), + backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + iconMenuPointColor: + Color.lerp(a.iconMenuPointColor, b.iconMenuPointColor, t), + titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), + subtitleTextStyle: + TextStyle.lerp(a.subtitleTextStyle, b.subtitleTextStyle, t), + bottomSheetBarrierColor: + Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t), + ); + + /// Merges one [GalleryHeaderThemeData] with the another + GalleryHeaderThemeData merge(GalleryHeaderThemeData? other) { + if (other == null) return this; + return copyWith( + closeButtonColor: other.closeButtonColor, + backgroundColor: other.backgroundColor, + iconMenuPointColor: other.iconMenuPointColor, + titleTextStyle: other.titleTextStyle, + subtitleTextStyle: other.subtitleTextStyle, + bottomSheetBarrierColor: other.bottomSheetBarrierColor, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is GalleryHeaderThemeData && + runtimeType == other.runtimeType && + closeButtonColor == other.closeButtonColor && + backgroundColor == other.backgroundColor && + iconMenuPointColor == other.iconMenuPointColor && + titleTextStyle == other.titleTextStyle && + subtitleTextStyle == other.subtitleTextStyle && + bottomSheetBarrierColor == other.bottomSheetBarrierColor; + + @override + int get hashCode => + closeButtonColor.hashCode ^ + backgroundColor.hashCode ^ + iconMenuPointColor.hashCode ^ + titleTextStyle.hashCode ^ + subtitleTextStyle.hashCode ^ + bottomSheetBarrierColor.hashCode; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(ColorProperty('closeButtonColor', closeButtonColor)) + ..add(ColorProperty('backgroundColor', backgroundColor)) + ..add(ColorProperty('iconMenuPointColor', iconMenuPointColor)) + ..add(DiagnosticsProperty('titleTextStyle', titleTextStyle)) + ..add(DiagnosticsProperty('subtitleTextStyle', subtitleTextStyle)) + ..add(ColorProperty('bottomSheetBarrierColor', bottomSheetBarrierColor)); + } +} + +/// Overrides the default style of [GalleryFooter] descendants. +/// +/// See also: +/// +/// * [GalleryFooterThemeData], which is used to configure this theme. +class GalleryFooterTheme extends InheritedTheme { + /// Creates an [GalleryFooterTheme]. + /// + /// The [data] parameter must not be null. + const GalleryFooterTheme({ + Key? key, + required this.data, + required Widget child, + }) : super(key: key, child: child); + + /// The configuration of this theme. + final GalleryFooterThemeData data; + + /// The closest instance of this class that encloses the given context. + /// + /// If there is no enclosing [GalleryFooterTheme] widget, then + /// [StreamChatThemeData.galleryFooterTheme] is used. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// ImageFooterTheme theme = ImageFooterTheme.of(context); + /// ``` + static GalleryFooterThemeData of(BuildContext context) { + final imageFooterTheme = + context.dependOnInheritedWidgetOfExactType(); + return imageFooterTheme?.data ?? + StreamChatTheme.of(context).galleryFooterTheme; + } + + @override + Widget wrap(BuildContext context, Widget child) => + GalleryFooterTheme(data: data, child: child); + + @override + bool updateShouldNotify(GalleryFooterTheme oldWidget) => + data != oldWidget.data; +} + +/// A style that overrides the default appearance of [GalleryFooter]s when used +/// with [GalleryFooterTheme] or with the overall [StreamChatTheme]'s +/// [StreamChatThemeData.galleryFooterTheme]. +/// +/// See also: +/// +/// * [GalleryFooterTheme], the theme which is configured with this class. +/// * [StreamChatThemeData.galleryFooterTheme], which can be used to override +/// the default style for [GalleryFooter]s below the overall [StreamChatTheme]. +class GalleryFooterThemeData with Diagnosticable { + /// Creates an [GalleryFooterThemeData]. + const GalleryFooterThemeData({ + this.backgroundColor, + this.shareIconColor, + this.titleTextStyle, + this.gridIconButtonColor, + this.bottomSheetBarrierColor, + this.bottomSheetBackgroundColor, + this.bottomSheetPhotosTextStyle, + this.bottomSheetCloseIconColor, + }); + + /// The background color for the [GalleryFooter] widget. + /// + /// Defaults to [ColorTheme.barsBg]. + final Color? backgroundColor; + + /// The color for the "share" icon. + /// + /// Defaults to [ColorTheme.textHighEmphasis]. + final Color? shareIconColor; + + /// The [TextStyle] to use for the [GalleryFooter] title text. + /// + /// Defaults to [TextTheme.headlineBold]. + final TextStyle? titleTextStyle; + + /// The color to use for the "grid" icon. + /// + /// Defaults to [ColorTheme.textHighEmphasis]. + final Color? gridIconButtonColor; + + /// The color to use behind the bottom sheet. + /// + /// Defaults to [ColorTheme.overlay]. + final Color? bottomSheetBarrierColor; + + /// The background color to use for the bottom sheet. + /// + /// Defaults to [ColorTheme.barsBg]. + final Color? bottomSheetBackgroundColor; + + /// The [TextStyle] to use for the "photos" text in the bottom sheet. + /// + /// Defaults to [TextTheme.headlineBold]. + final TextStyle? bottomSheetPhotosTextStyle; + + /// The color to use for the "close" icon. + /// + /// Defaults to [ColorTheme.textHighEmphasis]. + final Color? bottomSheetCloseIconColor; + + /// Copies this [GalleryFooterThemeData] to another. + GalleryFooterThemeData copyWith({ + Color? backgroundColor, + Color? shareIconColor, + TextStyle? titleTextStyle, + Color? gridIconButtonColor, + Color? bottomSheetBarrierColor, + Color? bottomSheetBackgroundColor, + TextStyle? bottomSheetPhotosTextStyle, + Color? bottomSheetCloseIconColor, + }) => + GalleryFooterThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + shareIconColor: shareIconColor ?? this.shareIconColor, + titleTextStyle: titleTextStyle ?? this.titleTextStyle, + gridIconButtonColor: gridIconButtonColor ?? this.gridIconButtonColor, + bottomSheetBarrierColor: + bottomSheetBarrierColor ?? this.bottomSheetBarrierColor, + bottomSheetBackgroundColor: + bottomSheetBackgroundColor ?? this.bottomSheetBackgroundColor, + bottomSheetPhotosTextStyle: + bottomSheetPhotosTextStyle ?? this.bottomSheetPhotosTextStyle, + bottomSheetCloseIconColor: + bottomSheetCloseIconColor ?? this.bottomSheetCloseIconColor, + ); + + /// Linearly interpolate between two [GalleryFooter] themes. + /// + /// All the properties must be non-null. + GalleryFooterThemeData lerp( + GalleryFooterThemeData a, + GalleryFooterThemeData b, + double t, + ) => + GalleryFooterThemeData( + backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + shareIconColor: Color.lerp(a.shareIconColor, b.shareIconColor, t), + titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), + gridIconButtonColor: + Color.lerp(a.gridIconButtonColor, b.gridIconButtonColor, t), + bottomSheetBarrierColor: + Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t), + bottomSheetBackgroundColor: Color.lerp( + a.bottomSheetBackgroundColor, b.bottomSheetBackgroundColor, t), + bottomSheetPhotosTextStyle: TextStyle.lerp( + a.bottomSheetPhotosTextStyle, b.bottomSheetPhotosTextStyle, t), + bottomSheetCloseIconColor: Color.lerp( + a.bottomSheetCloseIconColor, b.bottomSheetCloseIconColor, t), + ); + + /// Merges one [GalleryFooterThemeData] with the another + GalleryFooterThemeData merge(GalleryFooterThemeData? other) { + if (other == null) return this; + return copyWith( + backgroundColor: other.backgroundColor, + bottomSheetBarrierColor: other.bottomSheetBarrierColor, + bottomSheetBackgroundColor: other.bottomSheetBackgroundColor, + bottomSheetCloseIconColor: other.bottomSheetCloseIconColor, + bottomSheetPhotosTextStyle: other.bottomSheetPhotosTextStyle, + gridIconButtonColor: other.gridIconButtonColor, + titleTextStyle: other.titleTextStyle, + shareIconColor: other.shareIconColor, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is GalleryFooterThemeData && + runtimeType == other.runtimeType && + backgroundColor == other.backgroundColor && + shareIconColor == other.shareIconColor && + titleTextStyle == other.titleTextStyle && + gridIconButtonColor == other.gridIconButtonColor && + bottomSheetBarrierColor == other.bottomSheetBarrierColor && + bottomSheetBackgroundColor == other.bottomSheetBackgroundColor && + bottomSheetPhotosTextStyle == other.bottomSheetPhotosTextStyle && + bottomSheetCloseIconColor == other.bottomSheetCloseIconColor; + + @override + int get hashCode => + backgroundColor.hashCode ^ + shareIconColor.hashCode ^ + titleTextStyle.hashCode ^ + gridIconButtonColor.hashCode ^ + bottomSheetBarrierColor.hashCode ^ + bottomSheetBackgroundColor.hashCode ^ + bottomSheetPhotosTextStyle.hashCode ^ + bottomSheetCloseIconColor.hashCode; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(ColorProperty('backgroundColor', backgroundColor)) + ..add(ColorProperty('shareIconColor', shareIconColor)) + ..add(DiagnosticsProperty('titleTextStyle', titleTextStyle)) + ..add(ColorProperty('gridIconButtonColor', gridIconButtonColor)) + ..add(ColorProperty('bottomSheetBarrierColor', bottomSheetBarrierColor)) + ..add(ColorProperty( + 'bottomSheetBackgroundColor', bottomSheetBackgroundColor)) + ..add(DiagnosticsProperty( + 'bottomSheetPhotosTextStyle', bottomSheetPhotosTextStyle)) + ..add(ColorProperty( + 'bottomSheetCloseIconColor', bottomSheetCloseIconColor)); + } +} diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 62bfd299..45c0f32e 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -12,8 +12,8 @@ export 'src/connection_status_builder.dart'; export 'src/date_divider.dart'; export 'src/deleted_message.dart'; export 'src/full_screen_media.dart'; -export 'src/image_footer.dart'; -export 'src/image_header.dart'; +export 'src/gallery_footer.dart'; +export 'src/gallery_header.dart'; export 'src/info_tile.dart'; export 'src/mention_tile.dart'; export 'src/message_action.dart'; diff --git a/packages/stream_chat_flutter/test/src/gallery_footer_theme_test.dart b/packages/stream_chat_flutter/test/src/gallery_footer_theme_test.dart new file mode 100644 index 00000000..8bb5146f --- /dev/null +++ b/packages/stream_chat_flutter/test/src/gallery_footer_theme_test.dart @@ -0,0 +1,185 @@ +import 'package:flutter/material.dart' hide TextTheme; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockStreamChatClient extends Mock implements StreamChatClient {} + +void main() { + test('GalleryFooterThemeData copyWith, ==, hashCode basics', () { + expect(const GalleryFooterThemeData(), + const GalleryFooterThemeData().copyWith()); + expect(const GalleryFooterThemeData().hashCode, + const GalleryFooterThemeData().copyWith().hashCode); + }); + + test( + 'Light GalleryFooterThemeData lerps completely to dark GalleryFooterThemeData', + () { + expect( + const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControl, + _galleryFooterThemeDataControlDark, 1), + _galleryFooterThemeDataControlDark); + }); + + test( + 'Light GalleryFooterThemeData lerps halfway to dark GalleryFooterThemeData', + () { + expect( + const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControl, + _galleryFooterThemeDataControlDark, 0.5), + _galleryFooterThemeDataControlMidLerp); + }); + + test( + 'Dark GalleryFooterThemeData lerps completely to light GalleryFooterThemeData', + () { + expect( + const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControlDark, + _galleryFooterThemeDataControl, 1), + _galleryFooterThemeDataControl); + }); + + test('Merging dark and light themes results in a dark theme', () { + expect( + _galleryFooterThemeDataControl + .merge(_galleryFooterThemeDataControlDark), + _galleryFooterThemeDataControlDark); + }); + + test('Merging dark and light themes results in a dark theme', () { + expect( + _galleryFooterThemeDataControlDark + .merge(_galleryFooterThemeDataControl), + _galleryFooterThemeDataControl); + }); + + testWidgets( + 'Passing no GalleryFooterThemeData returns default light theme values', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockStreamChatClient(), + child: child, + ), + home: Builder( + builder: (context) { + _context = context; + return Scaffold( + appBar: GalleryFooter( + message: Message(), + ), + ); + }, + ), + ), + ); + + final imageFooterTheme = GalleryFooterTheme.of(_context); + expect(imageFooterTheme.backgroundColor, + _galleryFooterThemeDataControl.backgroundColor); + expect(imageFooterTheme.shareIconColor, + _galleryFooterThemeDataControl.shareIconColor); + expect(imageFooterTheme.titleTextStyle, + _galleryFooterThemeDataControl.titleTextStyle); + expect(imageFooterTheme.gridIconButtonColor, + _galleryFooterThemeDataControl.gridIconButtonColor); + expect(imageFooterTheme.bottomSheetBarrierColor, + _galleryFooterThemeDataControl.bottomSheetBarrierColor); + expect(imageFooterTheme.bottomSheetBackgroundColor, + _galleryFooterThemeDataControl.bottomSheetBackgroundColor); + expect(imageFooterTheme.bottomSheetCloseIconColor, + _galleryFooterThemeDataControl.bottomSheetCloseIconColor); + expect(imageFooterTheme.bottomSheetPhotosTextStyle, + _galleryFooterThemeDataControl.bottomSheetPhotosTextStyle); + }); + + testWidgets( + 'Passing no GalleryFooterThemeData returns default dark theme values', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockStreamChatClient(), + streamChatThemeData: StreamChatThemeData.dark(), + child: child, + ), + home: Builder( + builder: (context) { + _context = context; + return Scaffold( + appBar: GalleryFooter( + message: Message(), + ), + ); + }, + ), + ), + ); + + final imageFooterTheme = GalleryFooterTheme.of(_context); + expect(imageFooterTheme.backgroundColor, + _galleryFooterThemeDataControlDark.backgroundColor); + expect(imageFooterTheme.shareIconColor, + _galleryFooterThemeDataControlDark.shareIconColor); + expect(imageFooterTheme.titleTextStyle, + _galleryFooterThemeDataControlDark.titleTextStyle); + expect(imageFooterTheme.gridIconButtonColor, + _galleryFooterThemeDataControlDark.gridIconButtonColor); + expect(imageFooterTheme.bottomSheetBarrierColor, + _galleryFooterThemeDataControlDark.bottomSheetBarrierColor); + expect(imageFooterTheme.bottomSheetBackgroundColor, + _galleryFooterThemeDataControlDark.bottomSheetBackgroundColor); + expect(imageFooterTheme.bottomSheetCloseIconColor, + _galleryFooterThemeDataControlDark.bottomSheetCloseIconColor); + expect(imageFooterTheme.bottomSheetPhotosTextStyle, + _galleryFooterThemeDataControlDark.bottomSheetPhotosTextStyle); + }); +} + +// Light theme control +final _galleryFooterThemeDataControl = GalleryFooterThemeData( + backgroundColor: ColorTheme.light().barsBg, + shareIconColor: ColorTheme.light().textHighEmphasis, + titleTextStyle: TextTheme.light().headlineBold, + gridIconButtonColor: ColorTheme.light().textHighEmphasis, + bottomSheetBackgroundColor: ColorTheme.light().barsBg, + bottomSheetBarrierColor: ColorTheme.light().overlay, + bottomSheetCloseIconColor: ColorTheme.light().textHighEmphasis, + bottomSheetPhotosTextStyle: TextTheme.light().headlineBold, +); + +// Mid-lerp theme control +const _galleryFooterThemeDataControlMidLerp = GalleryFooterThemeData( + backgroundColor: Color(0xff87898b), + shareIconColor: Color(0xff7f7f7f), + titleTextStyle: TextStyle( + color: Color(0xff7f7f7f), + fontSize: 16, + fontWeight: FontWeight.bold, + ), + gridIconButtonColor: Color(0xff7f7f7f), + bottomSheetBarrierColor: Color(0x4c000000), + bottomSheetBackgroundColor: Color(0xff87898b), + bottomSheetPhotosTextStyle: TextStyle( + color: Color(0xff7f7f7f), + fontSize: 16, + fontWeight: FontWeight.bold, + ), + bottomSheetCloseIconColor: Color(0xff7f7f7f), +); + +// Dark theme control +final _galleryFooterThemeDataControlDark = GalleryFooterThemeData( + backgroundColor: ColorTheme.dark().barsBg, + shareIconColor: ColorTheme.dark().textHighEmphasis, + titleTextStyle: TextTheme.dark().headlineBold, + gridIconButtonColor: ColorTheme.dark().textHighEmphasis, + bottomSheetBackgroundColor: ColorTheme.dark().barsBg, + bottomSheetBarrierColor: ColorTheme.dark().overlay, + bottomSheetCloseIconColor: ColorTheme.dark().textHighEmphasis, + bottomSheetPhotosTextStyle: TextTheme.dark().headlineBold, +); diff --git a/packages/stream_chat_flutter/test/src/gallery_header_theme_test.dart b/packages/stream_chat_flutter/test/src/gallery_header_theme_test.dart new file mode 100644 index 00000000..9dca4ce5 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/gallery_header_theme_test.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockStreamChatClient extends Mock implements StreamChatClient {} + +void main() { + test('GalleryHeaderThemeData copyWith, ==, hashCode basics', () { + expect(const GalleryHeaderThemeData(), + const GalleryHeaderThemeData().copyWith()); + expect(const GalleryHeaderThemeData().hashCode, + const GalleryHeaderThemeData().copyWith().hashCode); + }); + + test( + 'Light GalleryHeaderThemeData lerps completely to dark GalleryHeaderThemeData', + () { + expect( + const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataControl, + _galleryHeaderThemeDataDarkControl, 1), + _galleryHeaderThemeDataDarkControl); + }); + + test( + 'Light GalleryHeaderThemeData lerps halfway to dark GalleryHeaderThemeData', + () { + expect( + const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataControl, + _galleryHeaderThemeDataDarkControl, 0.5), + _galleryHeaderThemeDataHalfLerpControl); + }); + + test( + 'Dark GalleryHeaderThemeData lerps completely to light GalleryHeaderThemeData', + () { + expect( + const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataDarkControl, + _galleryHeaderThemeDataControl, 1), + _galleryHeaderThemeDataControl); + }); + + test('Merging dark and light themes results in a dark theme', () { + expect( + _galleryHeaderThemeDataControl + .merge(_galleryHeaderThemeDataDarkControl), + _galleryHeaderThemeDataDarkControl); + }); + + test('Merging dark and light themes results in a dark theme', () { + expect( + _galleryHeaderThemeDataDarkControl + .merge(_galleryHeaderThemeDataControl), + _galleryHeaderThemeDataControl); + }); + + testWidgets( + 'Passing no GalleryHeaderThemeData returns default light theme values', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockStreamChatClient(), + child: child, + ), + home: Builder( + builder: (context) { + _context = context; + return Scaffold( + appBar: GalleryHeader( + message: Message(), + ), + ); + }, + ), + ), + ); + + final imageHeaderTheme = GalleryHeaderTheme.of(_context); + expect(imageHeaderTheme.closeButtonColor, + _galleryHeaderThemeDataControl.closeButtonColor); + expect(imageHeaderTheme.backgroundColor, + _galleryHeaderThemeDataControl.backgroundColor); + expect(imageHeaderTheme.iconMenuPointColor, + _galleryHeaderThemeDataControl.iconMenuPointColor); + expect(imageHeaderTheme.titleTextStyle, + _galleryHeaderThemeDataControl.titleTextStyle); + expect(imageHeaderTheme.subtitleTextStyle, + _galleryHeaderThemeDataControl.subtitleTextStyle); + expect(imageHeaderTheme.bottomSheetBarrierColor, + _galleryHeaderThemeDataControl.bottomSheetBarrierColor); + }); + + testWidgets( + 'Passing no GalleryHeaderThemeData returns default dark theme values', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockStreamChatClient(), + streamChatThemeData: StreamChatThemeData.dark(), + child: child, + ), + home: Builder( + builder: (context) { + _context = context; + return Scaffold( + appBar: GalleryHeader( + message: Message(), + ), + ); + }, + ), + ), + ); + + final imageHeaderTheme = GalleryHeaderTheme.of(_context); + expect(imageHeaderTheme.closeButtonColor, + _galleryHeaderThemeDataDarkControl.closeButtonColor); + expect(imageHeaderTheme.backgroundColor, + _galleryHeaderThemeDataDarkControl.backgroundColor); + expect(imageHeaderTheme.iconMenuPointColor, + _galleryHeaderThemeDataDarkControl.iconMenuPointColor); + expect(imageHeaderTheme.titleTextStyle, + _galleryHeaderThemeDataDarkControl.titleTextStyle); + expect(imageHeaderTheme.subtitleTextStyle, + _galleryHeaderThemeDataDarkControl.subtitleTextStyle); + expect(imageHeaderTheme.bottomSheetBarrierColor, + _galleryHeaderThemeDataDarkControl.bottomSheetBarrierColor); + }); +} + +// Light theme test control. +final _galleryHeaderThemeDataControl = GalleryHeaderThemeData( + closeButtonColor: const Color(0xff000000), + backgroundColor: const Color(0xffffffff), + iconMenuPointColor: const Color(0xff000000), + titleTextStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + subtitleTextStyle: const TextStyle( + fontSize: 12, + color: Colors.black, + ).copyWith( + color: const Color(0xff7A7A7A), + ), + bottomSheetBarrierColor: const Color.fromRGBO(0, 0, 0, 0.2), +); + +// Light theme test control. +final _galleryHeaderThemeDataHalfLerpControl = GalleryHeaderThemeData( + closeButtonColor: const Color(0xff7f7f7f), + backgroundColor: const Color(0xff87898b), + iconMenuPointColor: const Color(0xff7f7f7f), + titleTextStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xff7f7f7f), + ), + subtitleTextStyle: const TextStyle( + fontSize: 12, + color: Color(0xff7a7a7a), + ).copyWith( + color: const Color(0xff7A7A7A), + ), + bottomSheetBarrierColor: const Color(0x4c000000), +); + +// Dark theme test control. +final _galleryHeaderThemeDataDarkControl = GalleryHeaderThemeData( + closeButtonColor: const Color(0xffffffff), + backgroundColor: const Color(0xff101418), + iconMenuPointColor: const Color(0xffffffff), + titleTextStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + subtitleTextStyle: const TextStyle( + fontSize: 12, + color: Colors.white, + ).copyWith( + color: const Color(0xff7A7A7A), + ), + bottomSheetBarrierColor: const Color.fromRGBO(0, 0, 0, 0.4), +); diff --git a/packages/stream_chat_flutter/test/src/image_footer_test.dart b/packages/stream_chat_flutter/test/src/image_footer_test.dart index e2809d11..0fe3349a 100644 --- a/packages/stream_chat_flutter/test/src/image_footer_test.dart +++ b/packages/stream_chat_flutter/test/src/image_footer_test.dart @@ -37,7 +37,7 @@ void main() { child: WillPopScope( onWillPop: () async => false, child: Scaffold( - body: ImageFooter( + body: GalleryFooter( message: Message(), ), ), From c2563c9d5f72a262572f605d0c43a203f7c95b9a Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 16 Jul 2021 12:19:50 +0530 Subject: [PATCH 554/599] chore: fix merge conflicts Signed-off-by: xsahil03x --- packages/stream_chat_flutter/lib/src/stream_chat_theme.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index 0c56fd0d..c62e92ec 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -55,7 +55,6 @@ class StreamChatThemeData { MessageTheme? otherMessageTheme, MessageTheme? ownMessageTheme, MessageInputTheme? messageInputTheme, - Widget Function(BuildContext, Channel)? defaultChannelImage, Widget Function(BuildContext, User)? defaultUserImage, IconThemeData? primaryIconTheme, List? reactionIcons, @@ -79,7 +78,6 @@ class StreamChatThemeData { otherMessageTheme: otherMessageTheme, ownMessageTheme: ownMessageTheme, messageInputTheme: messageInputTheme, - defaultChannelImage: defaultChannelImage, defaultUserImage: defaultUserImage, primaryIconTheme: primaryIconTheme, reactionIcons: reactionIcons, From d2402ba3d006ea7df6c63e01403d3485c9470176 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 16 Jul 2021 13:35:51 +0530 Subject: [PATCH 555/599] fix: tests Signed-off-by: xsahil03x --- .../lib/src/channel_image.dart | 18 ++-- .../lib/src/group_image.dart | 94 ++++++++++--------- .../test/src/channel_header_test.dart | 24 +++-- .../test/src/channel_image_test.dart | 48 +++------- .../test/src/channel_preview_test.dart | 4 +- 5 files changed, 94 insertions(+), 94 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 21737814..803feed8 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -166,15 +166,17 @@ class ChannelImage extends StatelessWidget { // 1-1 Conversation if (otherMembers.length == 1) { final member = otherMembers.first; - final user = member.user!; - return BetterStreamBuilder( - stream: streamChat.client.state.usersStream - .map((users) => users[member.userId ?? user.id] ?? user) - .distinct(), - initialData: user, - builder: (context, user) => UserAvatar( + return BetterStreamBuilder( + stream: channel.state!.membersStream.map( + (members) => members.firstWhere( + (it) => it.userId == member.userId, + orElse: () => member, + ), + ), + initialData: member, + builder: (context, member) => UserAvatar( borderRadius: borderRadius ?? previewTheme?.borderRadius, - user: user, + user: member.user!, constraints: constraints ?? previewTheme?.constraints, onTap: onTap != null ? (_) => onTap!() : null, selected: selected, diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index 62b8b7b5..13a3c5f7 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -38,7 +38,6 @@ class GroupImage extends StatelessWidget { @override Widget build(BuildContext context) { - final streamChat = StreamChat.of(context); final channel = StreamChannel.of(context).channel; assert(channel.state != null, 'Channel ${channel.id} is not initialized'); @@ -63,30 +62,34 @@ class GroupImage extends StatelessWidget { child: Flex( direction: Axis.horizontal, crossAxisAlignment: CrossAxisAlignment.stretch, - children: members.take(2).map((member) { - final user = member.user!; - return Flexible( - fit: FlexFit.tight, - child: FittedBox( - fit: BoxFit.cover, - clipBehavior: Clip.antiAlias, - child: Transform.scale( - scale: 1.2, - child: BetterStreamBuilder( - stream: streamChat.client.state.usersStream - .map((users) => - users[member.userId ?? user.id] ?? user) - .distinct(), - initialData: user, - builder: (context, user) => UserAvatar( - user: user, - borderRadius: BorderRadius.zero, + children: members + .take(2) + .map( + (member) => Flexible( + fit: FlexFit.tight, + child: FittedBox( + fit: BoxFit.cover, + clipBehavior: Clip.antiAlias, + child: Transform.scale( + scale: 1.2, + child: BetterStreamBuilder( + stream: channel.state!.membersStream.map( + (members) => members.firstWhere( + (it) => it.userId == member.userId, + orElse: () => member, + ), + ), + initialData: member, + builder: (context, member) => UserAvatar( + user: member.user!, + borderRadius: BorderRadius.zero, + ), + ), ), ), ), - ), - ); - }).toList(), + ) + .toList(), ), ), if (members.length > 2) @@ -95,30 +98,35 @@ class GroupImage extends StatelessWidget { child: Flex( direction: Axis.horizontal, crossAxisAlignment: CrossAxisAlignment.stretch, - children: members.skip(2).take(2).map((member) { - final user = member.user!; - return Flexible( - fit: FlexFit.tight, - child: FittedBox( - fit: BoxFit.cover, - clipBehavior: Clip.antiAlias, - child: Transform.scale( - scale: 1.2, - child: BetterStreamBuilder( - stream: streamChat.client.state.usersStream - .map((users) => - users[member.userId ?? user.id] ?? user) - .distinct(), - initialData: user, - builder: (context, user) => UserAvatar( - user: user, - borderRadius: BorderRadius.zero, + children: members + .skip(2) + .take(2) + .map( + (member) => Flexible( + fit: FlexFit.tight, + child: FittedBox( + fit: BoxFit.cover, + clipBehavior: Clip.antiAlias, + child: Transform.scale( + scale: 1.2, + child: BetterStreamBuilder( + stream: channel.state!.membersStream.map( + (members) => members.firstWhere( + (it) => it.userId == member.userId, + orElse: () => member, + ), + ), + initialData: member, + builder: (context, member) => UserAvatar( + user: member.user!, + borderRadius: BorderRadius.zero, + ), + ), ), ), ), - ), - ); - }).toList(), + ) + .toList(), ), ), ], diff --git a/packages/stream_chat_flutter/test/src/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel_header_test.dart index 87f3d028..bf259ae6 100644 --- a/packages/stream_chat_flutter/test/src/channel_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_header_test.dart @@ -14,10 +14,12 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -76,10 +78,12 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -141,10 +145,12 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -207,10 +213,12 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -281,10 +289,12 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -346,10 +356,12 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); diff --git a/packages/stream_chat_flutter/test/src/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel_image_test.dart index ba913a77..1f7623db 100644 --- a/packages/stream_chat_flutter/test/src/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_image_test.dart @@ -132,9 +132,10 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final currentUser = OwnUser(id: 'user-id'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(currentUser); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ @@ -143,36 +144,7 @@ void main() { when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ - Member( - userId: 'user-id', - user: User( - id: 'user-id', - extraData: { - 'image': 'testimage1', - }, - ), - ), - Member( - userId: 'user-id2', - user: User( - id: 'user-id2', - extraData: { - 'image': 'testimage2', - }, - ), - ), - Member( - userId: 'user-id3', - user: User( - id: 'user-id3', - extraData: { - 'image': 'testimage3', - }, - ), - ), - ])); - when(() => channelState.members).thenReturn([ + final members = [ Member( userId: 'user-id', user: User( @@ -200,7 +172,10 @@ void main() { }, ), ), - ]); + ]; + when(() => channelState.members).thenReturn(members); + when(() => channelState.membersStream) + .thenAnswer((_) => Stream.value(members)); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -215,10 +190,11 @@ void main() { )); final image = tester.widget(find.byType(GroupImage)); - expect(image.images, [ - 'testimage2', - 'testimage3', - ]); + final otherMembers = members.where((it) => it.userId != currentUser.id); + expect( + image.members.map((it) => it.user?.id), + otherMembers.map((it) => it.user?.id), + ); }, ); diff --git a/packages/stream_chat_flutter/test/src/channel_preview_test.dart b/packages/stream_chat_flutter/test/src/channel_preview_test.dart index baa82614..31a1710f 100644 --- a/packages/stream_chat_flutter/test/src/channel_preview_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_preview_test.dart @@ -13,11 +13,13 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => channel.cid).thenReturn('cid'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); From 8dcd5cde647329e49e982f05164570682095cfb8 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 16 Jul 2021 13:59:28 +0530 Subject: [PATCH 556/599] refactor!: rename ChannelImage -> ChannelAvatar, GroupImage -> GroupAvatar Signed-off-by: xsahil03x --- .../src/{channel_image.dart => channel_avatar.dart} | 8 ++++---- .../stream_chat_flutter/lib/src/channel_header.dart | 4 ++-- .../lib/src/channel_list_view.dart | 2 +- .../stream_chat_flutter/lib/src/channel_preview.dart | 4 ++-- .../lib/src/{group_image.dart => group_avatar.dart} | 6 +++--- .../stream_chat_flutter/lib/stream_chat_flutter.dart | 2 +- .../test/src/channel_header_test.dart | 6 +++--- .../test/src/channel_image_test.dart | 12 ++++++------ .../test/src/channel_preview_test.dart | 2 +- 9 files changed, 23 insertions(+), 23 deletions(-) rename packages/stream_chat_flutter/lib/src/{channel_image.dart => channel_avatar.dart} (97%) rename packages/stream_chat_flutter/lib/src/{group_image.dart => group_avatar.dart} (97%) diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_avatar.dart similarity index 97% rename from packages/stream_chat_flutter/lib/src/channel_image.dart rename to packages/stream_chat_flutter/lib/src/channel_avatar.dart index 803feed8..4ab6ba31 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_avatar.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/group_image.dart'; +import 'package:stream_chat_flutter/src/group_avatar.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -45,9 +45,9 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// The widget renders the ui based on the first ancestor of type /// [StreamChatTheme]. /// Modify it to change the widget appearance. -class ChannelImage extends StatelessWidget { +class ChannelAvatar extends StatelessWidget { /// Instantiate a new ChannelImage - const ChannelImage({ + const ChannelAvatar({ Key? key, this.channel, this.constraints, @@ -187,7 +187,7 @@ class ChannelImage extends StatelessWidget { } // Group conversation - return GroupImage( + return GroupAvatar( members: otherMembers, borderRadius: borderRadius ?? previewTheme?.borderRadius, constraints: constraints ?? previewTheme?.constraints, diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index 16c1f1b7..8f586516 100644 --- a/packages/stream_chat_flutter/lib/src/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_header.dart @@ -98,7 +98,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { final Widget? leading; /// AppBar actions - /// By default it shows the [ChannelImage] + /// By default it shows the [ChannelAvatar] final List? actions; @override @@ -147,7 +147,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { Padding( padding: const EdgeInsets.only(right: 10), child: Center( - child: ChannelImage( + child: ChannelAvatar( borderRadius: chatThemeData.channelTheme .channelHeaderTheme.avatarTheme?.borderRadius, constraints: chatThemeData.channelTheme 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 06f1aee9..83f196df 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -620,7 +620,7 @@ class _ChannelListViewState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - ChannelImage( + ChannelAvatar( channel: channel, borderRadius: BorderRadius.circular(32), selected: selected, diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index b6d89168..bf615248 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -55,7 +55,7 @@ class ChannelPreview extends StatelessWidget { final Widget? subtitle; /// Widget rendering the leading element, by default - /// it shows the [ChannelImage] + /// it shows the [ChannelAvatar] final Widget? leading; /// Widget rendering the trailing element, @@ -83,7 +83,7 @@ class ChannelPreview extends StatelessWidget { ), onTap: () => onTap?.call(channel), onLongPress: () => onLongPress?.call(channel), - leading: leading ?? ChannelImage(onTap: onImageTap), + leading: leading ?? ChannelAvatar(onTap: onImageTap), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_avatar.dart similarity index 97% rename from packages/stream_chat_flutter/lib/src/group_image.dart rename to packages/stream_chat_flutter/lib/src/group_avatar.dart index 13a3c5f7..4a16279e 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_avatar.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Widget for constructing a group of images -class GroupImage extends StatelessWidget { - /// Constructor for creating a [GroupImage] - const GroupImage({ +class GroupAvatar extends StatelessWidget { + /// Constructor for creating a [GroupAvatar] + const GroupAvatar({ Key? key, required this.members, this.constraints, diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 45c0f32e..2b2485c4 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -3,7 +3,7 @@ export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; export 'src/attachment/attachment.dart'; export 'src/back_button.dart'; export 'src/channel_header.dart'; -export 'src/channel_image.dart'; +export 'src/channel_avatar.dart'; export 'src/channel_list_header.dart'; export 'src/channel_list_view.dart'; export 'src/channel_name.dart'; diff --git a/packages/stream_chat_flutter/test/src/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel_header_test.dart index bf259ae6..211a7132 100644 --- a/packages/stream_chat_flutter/test/src/channel_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_header_test.dart @@ -65,7 +65,7 @@ void main() { )); expect(find.text('test'), findsOneWidget); - expect(find.byType(ChannelImage), findsOneWidget); + expect(find.byType(ChannelAvatar), findsOneWidget); expect(find.byType(StreamBackButton), findsOneWidget); expect(find.byType(ChannelInfo), findsOneWidget); }, @@ -271,7 +271,7 @@ void main() { expect(find.text('test'), findsNothing); expect(find.byType(StreamBackButton), findsNothing); - expect(find.byType(ChannelImage), findsNothing); + expect(find.byType(ChannelAvatar), findsNothing); expect(find.byType(ChannelInfo), findsNothing); expect(find.text('leading'), findsOneWidget); expect(find.text('title'), findsOneWidget); @@ -415,7 +415,7 @@ void main() { )); await tester.tap(find.byType(StreamBackButton)); - await tester.tap(find.byType(ChannelImage)); + await tester.tap(find.byType(ChannelAvatar)); await tester.tap(find.byType(ChannelName)); expect(backPressed, true); diff --git a/packages/stream_chat_flutter/test/src/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel_image_test.dart index 1f7623db..5eea5569 100644 --- a/packages/stream_chat_flutter/test/src/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_image_test.dart @@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/src/group_image.dart'; +import 'package:stream_chat_flutter/src/group_avatar.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -35,7 +35,7 @@ void main() { child: StreamChannel( channel: channel, child: const Scaffold( - body: ChannelImage(), + body: ChannelAvatar(), ), ), ), @@ -113,7 +113,7 @@ void main() { child: StreamChannel( channel: channel, child: const Scaffold( - body: ChannelImage(), + body: ChannelAvatar(), ), ), ), @@ -183,13 +183,13 @@ void main() { child: StreamChannel( channel: channel, child: const Scaffold( - body: ChannelImage(), + body: ChannelAvatar(), ), ), ), )); - final image = tester.widget(find.byType(GroupImage)); + final image = tester.widget(find.byType(GroupAvatar)); final otherMembers = members.where((it) => it.userId != currentUser.id); expect( image.members.map((it) => it.user?.id), @@ -225,7 +225,7 @@ void main() { child: StreamChannel( channel: channel, child: const Scaffold( - body: ChannelImage( + body: ChannelAvatar( selected: true, ), ), diff --git a/packages/stream_chat_flutter/test/src/channel_preview_test.dart b/packages/stream_chat_flutter/test/src/channel_preview_test.dart index 31a1710f..6add0c60 100644 --- a/packages/stream_chat_flutter/test/src/channel_preview_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_preview_test.dart @@ -80,7 +80,7 @@ void main() { expect(find.text('test name'), findsOneWidget); expect(find.text('1'), findsOneWidget); expect(find.text('hello'), findsOneWidget); - expect(find.byType(ChannelImage), findsOneWidget); + expect(find.byType(ChannelAvatar), findsOneWidget); }, ); } From b306978da6ac6aecf30b3a1ba199c2b92260a992 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Fri, 16 Jul 2021 04:49:51 -0400 Subject: [PATCH 557/599] chore: apply linter to the various example and test directories (#550) * chore: remove examples and tests from analysis exclusion * chore: apply linter fixes to stream_chat_flutter_core/example/main.dart * chore: apply linter fixes to stream_chat_flutter/example/main.dart * chore: apply linter fixes to stream_chat_persistence/example/main.dart * chore: apply linter fixes to stream_chat/example/main.dart * chore: apply linter fixes to stream_chat_flutter/example/split_view.dart * chore: renamte tutorial files per linter * chore: apply linter fixes to tutorial_part_1.dart * chore: apply linter fixes to tutorial_part_2.dart * chore: apply linter fixes to tutorial_part_3.dart * chore: apply linter fixes to tutorial_part_4.dart * chore: apply linter fixes to tutorial_part_5.dart * chore: make a widget const in tutorial_part_5.dart * chore: apply linter fixes to tutorial_part_6.dart * chore: sort and update dependencies in stream_chat_persistence/example/pubspec.yaml * chore: sort and update dependencies in stream_chat_flutter_core/example/pubspec.yaml * chore: sort and update dependencies in stream_chat_flutter/example/pubspec.yaml * chore: sort dependencies in stream_chat_flutter/example/pubspec.yaml * chore: apply linter fixes to channel_test.dart * chore: apply linter fixes to client_test.dart * chore: apply linter fixes to client_test.dart, stream_http_client_test.dart, and token_manager_test.dart * chore: apply linter fixes to filter_test.dart * chore: apply linter fixes to reaction_test.dart * chore: apply linter fixes to chat_persistence_client_test.dart * chore: apply linter fixes to channel_image_test.dart * chore: apply linter fixes to deleted_message_test.dart * chore: apply linter fixes to full_screen_media_test.dart * chore: apply linter fixes to gallery_footer_theme_test.dart * chore: apply linter fixes to gallery_header_theme_test.dart * chore: apply linter fixes to message_text_test.dart * chore: apply linter fixes to reaction_bubble_test.dart * chore: apply linter fixes to system_message_test.dart * chore: apply linter fixes to typing_indicator_test.dart * chore: apply linter fixes to channel_list_core_test.dart * chore: apply linter fixes to channels_bloc_test.dart * chore: apply linter fixes to lazy_load_scroll_view_test.dart * chore: apply linter fixes to channel_matcher.dart * chore: apply linter fixes to message_matcher.dart * chore: apply linter fixes to users_matcher.dart * chore: apply linter fixes to message_list_core_test.dart * chore: apply linter fixes to message_search_bloc_test.dart * chore: apply linter fixes to message_search_list_core_test.dart * chore: apply linter fixes to stream_channel_test.dart * chore: apply linter fixes to stream_chat_core_test.dart * chore: apply linter fixes to user_list_core_test.dart * chore: apply linter fixes to channel_query_dao_test.dart, message_mapper_test.dart, user_mapper_test.dart, and users_bloc_test.dart * chore: apply some missed dartfmt * test: regenerate failinggolden test --- analysis_options.yaml | 2 - packages/stream_chat/example/lib/main.dart | 156 +++++---- .../test/src/api/channel_test.dart | 6 +- .../stream_chat/test/src/api/client_test.dart | 32 +- .../core/http/stream_http_client_test.dart | 2 +- .../src/core/http/token_manager_test.dart | 2 +- .../test/src/core/models/filter_test.dart | 2 +- .../test/src/core/models/reaction_test.dart | 5 +- .../src/db/chat_persistence_client_test.dart | 1 - .../stream_chat_flutter/example/lib/main.dart | 104 +++--- .../example/lib/split_view.dart | 177 +++++------ ...orial-part-1.dart => tutorial_part_1.dart} | 46 ++- ...orial-part-2.dart => tutorial_part_2.dart} | 61 ++-- ...orial-part-3.dart => tutorial_part_3.dart} | 60 ++-- ...orial-part-4.dart => tutorial_part_4.dart} | 65 ++-- ...orial-part-5.dart => tutorial_part_5.dart} | 67 ++-- ...orial-part-6.dart => tutorial_part_6.dart} | 83 +++-- .../stream_chat_flutter/example/pubspec.yaml | 24 +- .../test/src/channel_image_test.dart | 18 +- .../test/src/deleted_message_test.dart | 6 +- .../test/src/full_screen_media_test.dart | 4 +- .../test/src/gallery_footer_theme_test.dart | 6 +- .../test/src/gallery_header_theme_test.dart | 6 +- .../test/src/goldens/message_text.png | Bin 10173 -> 8701 bytes .../test/src/message_text_test.dart | 3 +- .../test/src/reaction_bubble_test.dart | 4 +- .../test/src/system_message_test.dart | 4 +- .../test/src/typing_indicator_test.dart | 4 +- .../example/lib/main.dart | 296 +++++++++--------- .../example/pubspec.yaml | 6 +- .../test/channel_list_core_test.dart | 130 ++++---- .../test/channels_bloc_test.dart | 93 +++--- .../test/lazy_load_scroll_view_test.dart | 44 +-- .../test/matchers/channel_matcher.dart | 7 +- .../test/matchers/message_matcher.dart | 7 +- .../test/matchers/users_matcher.dart | 7 +- .../test/message_list_core_test.dart | 80 +++-- .../test/message_search_bloc_test.dart | 43 ++- .../test/message_search_list_core_test.dart | 56 ++-- .../test/stream_channel_test.dart | 19 +- .../test/stream_chat_core_test.dart | 116 ++++--- .../test/user_list_core_test.dart | 155 +++++---- .../test/users_bloc_test.dart | 48 ++- .../example/lib/main.dart | 163 +++++----- .../example/pubspec.yaml | 2 +- .../test/src/dao/channel_query_dao_test.dart | 4 +- .../test/src/mapper/message_mapper_test.dart | 4 +- .../test/src/mapper/user_mapper_test.dart | 2 +- 48 files changed, 1172 insertions(+), 1060 deletions(-) rename packages/stream_chat_flutter/example/lib/{tutorial-part-1.dart => tutorial_part_1.dart} (67%) rename packages/stream_chat_flutter/example/lib/{tutorial-part-2.dart => tutorial_part_2.dart} (54%) rename packages/stream_chat_flutter/example/lib/{tutorial-part-3.dart => tutorial_part_3.dart} (71%) rename packages/stream_chat_flutter/example/lib/{tutorial-part-4.dart => tutorial_part_4.dart} (61%) rename packages/stream_chat_flutter/example/lib/{tutorial-part-5.dart => tutorial_part_5.dart} (60%) rename packages/stream_chat_flutter/example/lib/{tutorial-part-6.dart => tutorial_part_6.dart} (62%) diff --git a/analysis_options.yaml b/analysis_options.yaml index 4b73086c..71604a87 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,10 +1,8 @@ analyzer: exclude: - packages/*/lib/**/*.g.dart - - packages/*/example/** - packages/*/lib/src/emoji - packages/*/lib/**/*.freezed.dart - - packages/*/test/** linter: rules: diff --git a/packages/stream_chat/example/lib/main.dart b/packages/stream_chat/example/lib/main.dart index bbcc86b8..167f5f6b 100644 --- a/packages/stream_chat/example/lib/main.dart +++ b/packages/stream_chat/example/lib/main.dart @@ -13,7 +13,7 @@ Future main() async { await client.connectUser( User( id: 'cool-shadow-7', - extraData: { + extraData: const { 'image': 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', }, @@ -57,12 +57,10 @@ class StreamExample extends StatelessWidget { final Channel channel; @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Stream Chat Dart Example', - home: HomeScreen(channel: channel), - ); - } + Widget build(BuildContext context) => MaterialApp( + title: 'Stream Chat Dart Example', + home: HomeScreen(channel: channel), + ); } /// Main screen of our application. The layout is comprised of an [AppBar] @@ -167,83 +165,81 @@ class _MessageViewState extends State { } @override - Widget build(BuildContext context) { - return Column( - children: [ - Expanded( - child: ListView.builder( - controller: _scrollController, - itemCount: _messages.length, - reverse: true, - itemBuilder: (BuildContext context, int index) { - final item = _messages[index]; - if (item.user?.id == widget.channel.client.uid) { - return Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(item.text ?? ''), - ), - ); - } else { - return Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(item.text ?? ''), - ), - ); - } - }, + Widget build(BuildContext context) => Column( + children: [ + Expanded( + child: ListView.builder( + controller: _scrollController, + itemCount: _messages.length, + reverse: true, + itemBuilder: (BuildContext context, int index) { + final item = _messages[index]; + if (item.user?.id == widget.channel.client.uid) { + return Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text ?? ''), + ), + ); + } else { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text ?? ''), + ), + ); + } + }, + ), ), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Row( - children: [ - Expanded( - child: TextField( - controller: _controller, - decoration: const InputDecoration( - hintText: 'Enter your message', - ), - ), - ), - Material( - type: MaterialType.circle, - color: Colors.blue, - clipBehavior: Clip.hardEdge, - child: InkWell( - onTap: () async { - // We can send a new message by calling `sendMessage` on - // the current channel. After sending a message, the - // TextField is cleared and the list view is scrolled - // to show the new item. - if (_controller.value.text.isNotEmpty) { - await widget.channel.sendMessage( - Message(text: _controller.value.text), - ); - _controller.clear(); - _updateList(); - } - }, - child: const Padding( - padding: EdgeInsets.all(8.0), - child: Center( - child: Icon( - Icons.send, - color: Colors.white, - ), + Padding( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _controller, + decoration: const InputDecoration( + hintText: 'Enter your message', ), ), ), - ) - ], - ), - ) - ], - ); - } + Material( + type: MaterialType.circle, + color: Colors.blue, + clipBehavior: Clip.hardEdge, + child: InkWell( + onTap: () async { + // We can send a new message by calling `sendMessage` on + // the current channel. After sending a message, the + // TextField is cleared and the list view is scrolled + // to show the new item. + if (_controller.value.text.isNotEmpty) { + await widget.channel.sendMessage( + Message(text: _controller.value.text), + ); + _controller.clear(); + _updateList(); + } + }, + child: const Padding( + padding: EdgeInsets.all(8), + child: Center( + child: Icon( + Icons.send, + color: Colors.white, + ), + ), + ), + ), + ) + ], + ), + ) + ], + ); } /// Helper extension for quickly retrieving diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 6883d3b1..39003fb7 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -555,7 +555,7 @@ void main() { }); test( - 'should directly update the state with message as deleted if the state is sending or failed', + '''should directly update the state with message as deleted if the state is sending or failed''', () async { const messageId = 'test-message-id'; final message = Message( @@ -981,7 +981,7 @@ void main() { ); test( - 'should override previous reaction if present and `enforceUnique` is true', + '''should override previous reaction if present and `enforceUnique` is true''', () async { const userId = 'test-user-id'; const messageId = 'test-message-id'; @@ -1875,7 +1875,7 @@ void main() { }); test( - 'should send `typingStart` event if there is not already a typingEvent or the difference between the two is >= 2 seconds', + '''should send `typingStart` event if there is not already a typingEvent or the difference between the two is >= 2 seconds''', () async { final typingEvent = Event(type: EventType.typingStart); diff --git a/packages/stream_chat/test/src/api/client_test.dart b/packages/stream_chat/test/src/api/client_test.dart index 72dec24e..423445ec 100644 --- a/packages/stream_chat/test/src/api/client_test.dart +++ b/packages/stream_chat/test/src/api/client_test.dart @@ -47,12 +47,6 @@ void main() { final user = User(id: 'test-user-id'); final token = Token.development(user.id).rawValue; - final event = Event( - type: EventType.healthCheck, - connectionId: 'fake-connection-id', - me: OwnUser.fromUser(user), - ); - expectLater( // skipping first seed status -> ConnectionStatus.disconnected client.wsConnectionStatusStream.skip(1), @@ -74,12 +68,6 @@ void main() { return Token.development(userId).rawValue; } - final event = Event( - type: EventType.healthCheck, - connectionId: 'fake-connection-id', - me: OwnUser.fromUser(user), - ); - expectLater( // skipping first seed status -> ConnectionStatus.disconnected client.wsConnectionStatusStream.skip(1), @@ -106,12 +94,6 @@ void main() { ..accessToken = token, ); - final event = Event( - type: EventType.healthCheck, - connectionId: 'fake-connection-id', - me: OwnUser.fromUser(user), - ); - expectLater( // skipping first seed status -> ConnectionStatus.disconnected client.wsConnectionStatusStream.skip(1), @@ -431,7 +413,7 @@ void main() { }); test( - '`.connectUser` should connect successfully if persistence contains event', + '''`.connectUser` should connect successfully if persistence contains event''', () async { final user = User(id: 'test-user-id'); final token = Token.development(user.id).rawValue; @@ -452,7 +434,7 @@ void main() { ); test( - '`.connectUserWithProvider` should connect successfully if persistence contains event', + '''`.connectUserWithProvider` should connect successfully if persistence contains event''', () async { final user = User(id: 'test-user-id'); Future tokenProvider(String userId) async { @@ -476,7 +458,7 @@ void main() { ); test( - '`.connectGuestUser` should connect successfully if persistence contains event', + '''`.connectGuestUser` should connect successfully if persistence contains event''', () async { final user = User(id: 'test-user-id'); final token = Token.development(user.id).rawValue; @@ -507,7 +489,7 @@ void main() { ); test( - '`.connectAnonymousUser` should connect successfully if persistence contains event', + '''`.connectAnonymousUser` should connect successfully if persistence contains event''', () async { final user = User(id: 'test-user-id'); @@ -561,7 +543,7 @@ void main() { group('`.sync`', () { test( - 'should update persistence connectionInfo and lastSync when sync succeeds', + '''should update persistence connectionInfo and lastSync when sync succeeds''', () async { const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; final lastSyncAt = DateTime.now(); @@ -735,7 +717,7 @@ void main() { ); test( - 'should never rethrow network call if persistence already emitted some channels', + '''should never rethrow network call if persistence already emitted some channels''', () async { final persistentChannelStates = List.generate( 3, @@ -943,7 +925,7 @@ void main() { }); test( - 'should rethrow if `.queryChannelsOnline` throws and persistence channels are empty', + '''should rethrow if `.queryChannelsOnline` throws and persistence channels are empty''', () async { when(() => api.channel.queryChannels( filter: any(named: 'filter'), diff --git a/packages/stream_chat/test/src/core/http/stream_http_client_test.dart b/packages/stream_chat/test/src/core/http/stream_http_client_test.dart index ece00efd..ab4c4984 100644 --- a/packages/stream_chat/test/src/core/http/stream_http_client_test.dart +++ b/packages/stream_chat/test/src/core/http/stream_http_client_test.dart @@ -57,7 +57,7 @@ void main() { }); test( - 'connectionIdInterceptor should be added if connectionIdManager is provided', + '''connectionIdInterceptor should be added if connectionIdManager is provided''', () { const apiKey = 'api-key'; final client = StreamHttpClient( diff --git a/packages/stream_chat/test/src/core/http/token_manager_test.dart b/packages/stream_chat/test/src/core/http/token_manager_test.dart index a1a38fa5..163bafff 100644 --- a/packages/stream_chat/test/src/core/http/token_manager_test.dart +++ b/packages/stream_chat/test/src/core/http/token_manager_test.dart @@ -45,7 +45,7 @@ void main() { }); test( - '`setTokenOrProvider` should throw if both token and provider is not provided', + '''`setTokenOrProvider` should throw if both token and provider is not provided''', () async { expect(tokenManager.userId, isNull); diff --git a/packages/stream_chat/test/src/core/models/filter_test.dart b/packages/stream_chat/test/src/core/models/filter_test.dart index 663ab702..d60ebb50 100644 --- a/packages/stream_chat/test/src/core/models/filter_test.dart +++ b/packages/stream_chat/test/src/core/models/filter_test.dart @@ -192,7 +192,7 @@ void main() { test('custom with no operator', () { const key = 'testKey'; const values = ['testValue']; - final filter = Filter.custom(key: key, value: values); + const filter = Filter.custom(key: key, value: values); final encoded = json.encode(filter); expect( encoded, diff --git a/packages/stream_chat/test/src/core/models/reaction_test.dart b/packages/stream_chat/test/src/core/models/reaction_test.dart index 518a223e..0891b548 100644 --- a/packages/stream_chat/test/src/core/models/reaction_test.dart +++ b/packages/stream_chat/test/src/core/models/reaction_test.dart @@ -13,7 +13,7 @@ void main() { expect(reaction.type, 'wow'); expect( reaction.user?.toJson(), - User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { + User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: const { 'image': 'https://randomuser.me/api/portraits/women/45.jpg', 'name': 'Daisy Morgan' }).toJson(), @@ -28,7 +28,8 @@ void main() { messageId: '76cd8c82-b557-4e48-9d12-87995d3a0e04', createdAt: DateTime.parse('2020-01-28T22:17:31.108742Z'), type: 'wow', - user: User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { + user: + User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: const { 'image': 'https://randomuser.me/api/portraits/women/45.jpg', 'name': 'Daisy Morgan' }), diff --git a/packages/stream_chat/test/src/db/chat_persistence_client_test.dart b/packages/stream_chat/test/src/db/chat_persistence_client_test.dart index 0ab7695f..22aee1a9 100644 --- a/packages/stream_chat/test/src/db/chat_persistence_client_test.dart +++ b/packages/stream_chat/test/src/db/chat_persistence_client_test.dart @@ -154,7 +154,6 @@ void main() { }); test('updateChannelState', () async { - const cid = 'test:cid'; final channelState = ChannelState(); persistenceClient.updateChannelState(channelState); }); diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index d3522886..89362d81 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_persistence/stream_chat_persistence.dart'; +/// A chat-persisted StreamChatClient final chatPersistentClient = StreamChatPersistenceClient( logLevel: Level.INFO, ); @@ -9,73 +10,84 @@ final chatPersistentClient = StreamChatPersistenceClient( void main() async { WidgetsFlutterBinding.ensureInitialized(); - /// Create a new instance of [StreamChatClient] passing the apikey obtained from your - /// project dashboard. + /// Create a new instance of [StreamChatClient] passing the apikey obtained + /// from your project dashboard. final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, )..chatPersistenceClient = chatPersistentClient; - /// Set the current user and connect the websocket. In a production scenario, this should be done using - /// a backend to generate a user token using our server SDK. + /// Set the current user and connect the websocket. In a production + /// scenario, this should be done using a backend to generate a user token + /// using our server SDK. + /// /// Please see the following for more information: /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ await client.connectUser( User(id: 'super-band-9'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.' + '0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); final channel = client.channel('messaging', id: 'godevs'); await channel.watch(); - runApp(MyApp(client, channel)); + runApp( + MyApp( + client: client, + channel: channel, + ), + ); } /// Example application using Stream Chat Flutter widgets. -/// Stream Chat Flutter is a set of Flutter widgets which provide full chat functionalities -/// for building Flutter applications using Stream. -/// If you'd prefer using minimal wrapper widgets for your app, please see our other +/// +/// Stream Chat Flutter is a set of Flutter widgets which provide full chat +/// functionalities for building Flutter applications using Stream. If you'd +/// prefer using minimal wrapper widgets for your app, please see our other /// package, `stream_chat_flutter_core`. class MyApp extends StatelessWidget { + /// Example using Stream's Flutter package. + /// + /// If you'd prefer using minimal wrapper widgets for your app, please see + /// our other package, `stream_chat_flutter_core`. + const MyApp({ + Key? key, + required this.client, + required this.channel, + }) : super(key: key); + /// Instance of Stream Client. - /// Stream's [StreamChatClient] can be used to connect to our servers and set the default - /// user for the application. Performing these actions trigger a websocket connection - /// allowing for real-time updates. + /// + /// Stream's [StreamChatClient] can be used to connect to our servers and + /// set the default user for the application. Performing these actions + /// trigger a websocket connection allowing for real-time updates. final StreamChatClient client; /// Instance of the Channel final Channel channel; - /// Example using Stream's Flutter package. - /// If you'd prefer using minimal wrapper widgets for your app, please see our other - /// package, `stream_chat_flutter_core`. - MyApp(this.client, this.channel); - @override - Widget build(BuildContext context) { - return MaterialApp( - theme: ThemeData.light(), - darkTheme: ThemeData.dark(), - themeMode: ThemeMode.system, - builder: (context, widget) { - return StreamChat( + Widget build(BuildContext context) => MaterialApp( + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + builder: (context, widget) => StreamChat( client: client, child: widget, - ); - }, - home: StreamChannel( - channel: channel, - child: ChannelPage(), - ), - ); - } + ), + home: StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ); } /// A list of messages sent in the current channel. /// -/// This is implemented using [MessageListView], a widget that provides query functionalities -/// fetching the messages from the api and showing them in a listView +/// This is implemented using [MessageListView], a widget that provides query +/// functionalities fetching the messages from the api and showing them in a +/// listView. class ChannelPage extends StatelessWidget { /// Creates the page that shows the list of messages const ChannelPage({ @@ -83,17 +95,15 @@ class ChannelPage extends StatelessWidget { }) : super(key: key); @override - Widget build(BuildContext context) { - return Scaffold( - appBar: ChannelHeader(), - body: Column( - children: [ - Expanded( - child: MessageListView(), - ), - MessageInput(), - ], - ), - ); - } + Widget build(BuildContext context) => Scaffold( + appBar: const ChannelHeader(), + body: Column( + children: const [ + Expanded( + child: MessageListView(), + ), + MessageInput(), + ], + ), + ); } diff --git a/packages/stream_chat_flutter/example/lib/split_view.dart b/packages/stream_chat_flutter/example/lib/split_view.dart index 1c833e23..454071a6 100644 --- a/packages/stream_chat_flutter/example/lib/split_view.dart +++ b/packages/stream_chat_flutter/example/lib/split_view.dart @@ -10,30 +10,39 @@ void main() async { await client.connectUser( User(id: 'super-band-9'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''', ); - runApp(MyApp(client)); + runApp( + MyApp( + client: client, + ), + ); } class MyApp extends StatelessWidget { + const MyApp({ + Key? key, + required this.client, + }) : super(key: key); + final StreamChatClient client; - MyApp(this.client); - @override - Widget build(BuildContext context) { - return MaterialApp( - builder: (context, child) => StreamChat( - client: client, - child: child, - ), - home: SplitView(), - ); - } + Widget build(BuildContext context) => MaterialApp( + builder: (context, child) => StreamChat( + client: client, + child: child, + ), + home: const SplitView(), + ); } class SplitView extends StatefulWidget { + const SplitView({ + Key? key, + }) : super(key: key); + @override _SplitViewState createState() => _SplitViewState(); } @@ -42,69 +51,67 @@ class _SplitViewState extends State { Channel? selectedChannel; @override - Widget build(BuildContext context) { - return Flex( - direction: Axis.horizontal, - children: [ - Flexible( - flex: 1, - child: ChannelListPage( - onTap: (channel) { - setState(() { - selectedChannel = channel; - }); - }, + Widget build(BuildContext context) => Flex( + direction: Axis.horizontal, + children: [ + Flexible( + child: ChannelListPage( + onTap: (channel) { + setState(() { + selectedChannel = channel; + }); + }, + ), ), - ), - Flexible( - flex: 2, - child: Scaffold( - body: selectedChannel != null - ? StreamChannel( - key: ValueKey(selectedChannel!.cid), - channel: selectedChannel!, - child: ChannelPage(), - ) - : Center( - child: Text( - 'Pick a channel to show the messages đŸ’Ŧ', - style: Theme.of(context).textTheme.headline5, + Flexible( + flex: 2, + child: Scaffold( + body: selectedChannel != null + ? StreamChannel( + key: ValueKey(selectedChannel!.cid), + channel: selectedChannel!, + child: const ChannelPage(), + ) + : Center( + child: Text( + 'Pick a channel to show the messages đŸ’Ŧ', + style: Theme.of(context).textTheme.headline5, + ), ), - ), + ), ), - ), - ], - ); - } + ], + ); } class ChannelListPage extends StatelessWidget { + const ChannelListPage({ + Key? key, + this.onTap, + }) : super(key: key); + final void Function(Channel)? onTap; - ChannelListPage({this.onTap}); - @override - Widget build(BuildContext context) { - return Scaffold( - body: ChannelsBloc( - child: ChannelListView( - onChannelTap: onTap != null - ? (channel, _) { - onTap!(channel); - } - : null, - filter: Filter.in_( - 'members', - [StreamChat.of(context).user!.id], - ), - sort: [SortOption('last_message_at')], - pagination: PaginationParams( - limit: 20, + Widget build(BuildContext context) => Scaffold( + body: ChannelsBloc( + child: ChannelListView( + onChannelTap: onTap != null + ? (channel, _) { + onTap!(channel); + } + : null, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user!.id], + ), + sort: const [SortOption('last_message_at')], + pagination: const PaginationParams( + limit: 20, + ), ), ), - ), - ); - } + ); } class ChannelPage extends StatelessWidget { @@ -113,27 +120,21 @@ class ChannelPage extends StatelessWidget { }) : super(key: key); @override - Widget build(BuildContext context) { - return Navigator( - onGenerateRoute: (settings) { - return MaterialPageRoute( - builder: (context) { - return Scaffold( - appBar: ChannelHeader( - showBackButton: false, - ), - body: Column( - children: [ - Expanded( - child: MessageListView(), - ), - MessageInput(), - ], - ), - ); - }, - ); - }, - ); - } + Widget build(BuildContext context) => Navigator( + onGenerateRoute: (settings) => MaterialPageRoute( + builder: (context) => Scaffold( + appBar: const ChannelHeader( + showBackButton: false, + ), + body: Column( + children: const [ + Expanded( + child: MessageListView(), + ), + MessageInput(), + ], + ), + ), + ), + ); } diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-1.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_1.dart similarity index 67% rename from packages/stream_chat_flutter/example/lib/tutorial-part-1.dart rename to packages/stream_chat_flutter/example/lib/tutorial_part_1.dart index 2a720c70..459d8690 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-1.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_1.dart @@ -4,25 +4,30 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// First step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) /// -/// There are three important things to notice that are common to all Flutter application using StreamChat: +/// There are three important things to notice that are common to all Flutter +/// application using StreamChat: /// /// 1. The Dart API [StreamChatClient] is initialized with your API Key /// 2. The current user is set by calling [StreamChatClient.connectUser] /// 3. The client is then passed to the top-level [StreamChat] widget -/// [StreamChat] is an inherited widget and must be the parent of all Chat related widgets. +/// [StreamChat] is an inherited widget and must be the parent of all +/// Chat related widgets. /// -/// Please note that while Flutter can be used to build both mobile and web applications; -/// in this tutorial we focus on mobile, make sure when running the app you use a mobile device. +/// Please note that while Flutter can be used to build both mobile and web +/// applications, in this tutorial we focus on mobile. Make sure when running +/// the app that you use a mobile device. /// /// Let's have a look at what we've built: /// /// - We set up the Chat [StreamChatClient] with the API key /// -/// - We set the the current user for Chat with [StreamChatClient.connectUser] and a pre-generated user token +/// - We set the the current user for Chat with [StreamChatClient.connectUser] +/// and a pre-generated user token /// /// - We make [StreamChat] the root Widget of our application /// -/// - We create a single [ChannelPage] widget under [StreamChat] with three widgets: [ChannelHeader], [MessageListView] and [MessageInput] +/// - We create a single [ChannelPage] widget under [StreamChat] with three +/// widgets: [ChannelHeader], [MessageListView] and [MessageInput] /// /// If you now run the simulator you will see a single channel UI. void main() async { @@ -33,26 +38,38 @@ void main() async { await client.connectUser( User(id: 'super-band-9'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''', ); final channel = client.channel('messaging', id: 'godevs'); - // ignore: unawaited_futures + // ignore: unawaited_futures, cascade_invocations channel.watch(); - runApp(MyApp(client, channel)); + runApp( + MyApp( + client: client, + channel: channel, + ), + ); } class MyApp extends StatelessWidget { + const MyApp({ + Key? key, + required this.client, + required this.channel, + }) : super(key: key); + final StreamChatClient client; + final Channel channel; - MyApp(this.client, this.channel); - @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return MaterialApp( + // ignore: prefer_expression_function_bodies builder: (context, widget) { return StreamChat( client: client, @@ -61,7 +78,7 @@ class MyApp extends StatelessWidget { }, home: StreamChannel( channel: channel, - child: ChannelPage(), + child: const ChannelPage(), ), ); } @@ -73,11 +90,12 @@ class ChannelPage extends StatelessWidget { }) : super(key: key); @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( - appBar: ChannelHeader(), + appBar: const ChannelHeader(), body: Column( - children: [ + children: const [ Expanded( child: MessageListView(), ), diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart similarity index 54% rename from packages/stream_chat_flutter/example/lib/tutorial-part-2.dart rename to packages/stream_chat_flutter/example/lib/tutorial_part_2.dart index 1e198d61..79f8674c 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart @@ -5,20 +5,29 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Second step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) /// /// Most chat applications handle more than just one single conversation. -/// Apps like Facebook Messenger, Whatsapp and Telegram allows you to have multiple one to one and group conversations. +/// Apps like Facebook Messenger, Whatsapp and Telegram allows you to have +/// multiple one-to-one and group conversations. /// -/// Let’s find out how we can change our application chat screen to display the list of conversations and navigate between them. +/// Let’s find out how we can change our application chat screen to display +/// the list of conversations and navigate between them. /// -/// > Note: the SDK uses Flutter’s [Navigator] to move from one route to another, this allows us to avoid any boiler-plate code. -/// > Of course you can take total control of how navigation works by customizing widgets like [Channel] and [ChannelList]. +/// > Note: the SDK uses Flutter’s [Navigator] to move from one route to +/// another. This allows us to avoid any boiler-plate code. +/// > Of course, you can take total control of how navigation works by +/// customizing widgets like [Channel] and [ChannelList]. /// -/// If you run the application, you will see that the first screen shows a list of conversations, you can open each by tapping and go back to the list. +/// If you run the application, you will see that the first screen shows a +/// list of conversations, you can open each by tapping and go back to the list. /// -/// Every single widget involved in this UI can be customized or swapped with your own. +/// Every single widget involved in this UI can be customized or swapped +/// with your own. /// -/// The [ChannelListPage] widget retrieves the list of channels based on a custom query and ordering. -/// In this case we are showing the list of channels the current user is a member and we order them based on the time they had a new message. -/// [ChannelListView] handles pagination and updates automatically out of the box when new channels are created or when a new message is added to a channel. +/// The [ChannelListPage] widget retrieves the list of channels based on a +/// custom query and ordering. In this case we are showing the list of +/// channels in which the current user is a member and we order them based +/// on the time they had a new message. [ChannelListView] handles pagination +/// and updates automatically when new channels are created or when a new +/// message is added to a channel. void main() async { final client = StreamChatClient( 's2dxdhpxd94g', @@ -27,31 +36,44 @@ void main() async { await client.connectUser( User(id: 'super-band-9'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''', ); - runApp(MyApp(client)); + runApp( + MyApp( + client: client, + ), + ); } class MyApp extends StatelessWidget { + const MyApp({ + Key? key, + required this.client, + }) : super(key: key); + final StreamChatClient client; - MyApp(this.client); - @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return MaterialApp( builder: (context, child) => StreamChat( client: client, child: child, ), - home: ChannelListPage(), + home: const ChannelListPage(), ); } } class ChannelListPage extends StatelessWidget { + const ChannelListPage({ + Key? key, + }) : super(key: key); + @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( body: ChannelsBloc( @@ -60,11 +82,11 @@ class ChannelListPage extends StatelessWidget { 'members', [StreamChat.of(context).user!.id], ), - sort: [SortOption('last_message_at')], - pagination: PaginationParams( + sort: const [SortOption('last_message_at')], + pagination: const PaginationParams( limit: 20, ), - channelWidget: ChannelPage(), + channelWidget: const ChannelPage(), ), ), ); @@ -77,11 +99,12 @@ class ChannelPage extends StatelessWidget { }) : super(key: key); @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( - appBar: ChannelHeader(), + appBar: const ChannelHeader(), body: Column( - children: [ + children: const [ Expanded( child: MessageListView(), ), diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart similarity index 71% rename from packages/stream_chat_flutter/example/lib/tutorial-part-3.dart rename to packages/stream_chat_flutter/example/lib/tutorial_part_3.dart index 6789a69d..99e9ceab 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart @@ -6,22 +6,30 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Third step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) /// /// So far you’ve learned how to use the default widgets. -/// The library has been designed with composition in mind and to allow all common customizations to be very easy. -/// This means that you can change any component in your application by swapping the default widgets with the ones you build yourself. +/// The library has been designed with composition in mind and to allow all +/// common customizations to be very easy. +/// This means that you can change any component in your application by +/// swapping the default widgets with the ones you build yourself. /// /// Let’s see how we can make some changes to the SDK’s UI components. -/// We start by changing how channel previews are shown in the channel list and include the number of unread messages for each. +/// We start by changing how channel previews are shown in the channel list +/// and include the number of unread messages for each. /// -/// We're passing a custom widget to [ChannelListView.channelPreviewBuilder], this will override the default [ChannelPreview] and allows you to create one yourself. +/// We're passing a custom widget to [ChannelListView.channelPreviewBuilder]; +/// this will override the default [ChannelPreview] and allows you to create +/// one yourself. /// /// There are a couple interesting things we do in this widget: /// -/// - Instead of creating a whole new style for the channel name, we inherit the text style from the parent theme ([StreamChatTheme.of]) and only change the color attribute +/// - Instead of creating a whole new style for the channel name, we inherit +/// the text style from the parent theme ([StreamChatTheme.of]) and only +/// change the color attribute /// -/// - We loop over the list of channel messages to search for the first not deleted message ([Channel.state.messages]) +/// - We loop over the list of channel messages to search for the first not +/// deleted message ([Channel.state.messages]) /// /// - We retrieve the count of unread messages from [Channel.state] -void main() async { +Future main() async { final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, @@ -29,31 +37,44 @@ void main() async { await client.connectUser( User(id: 'super-band-9'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''', ); - runApp(MyApp(client)); + runApp( + MyApp( + client: client, + ), + ); } class MyApp extends StatelessWidget { + const MyApp({ + Key? key, + required this.client, + }) : super(key: key); + final StreamChatClient client; - MyApp(this.client); - @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return MaterialApp( builder: (context, child) => StreamChat( client: client, child: child, ), - home: ChannelListPage(), + home: const ChannelListPage(), ); } } class ChannelListPage extends StatelessWidget { + const ChannelListPage({ + Key? key, + }) : super(key: key); + @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( body: ChannelsBloc( @@ -64,10 +85,10 @@ class ChannelListPage extends StatelessWidget { ), channelPreviewBuilder: _channelPreviewBuilder, // sort: [SortOption('last_message_at')], - pagination: PaginationParams( + pagination: const PaginationParams( limit: 20, ), - channelWidget: ChannelPage(), + channelWidget: const ChannelPage(), ), ), ); @@ -78,7 +99,7 @@ class ChannelListPage extends StatelessWidget { (message) => !message.isDeleted, ); - final subtitle = (lastMessage == null ? 'nothing yet' : lastMessage.text!); + final subtitle = lastMessage == null ? 'nothing yet' : lastMessage.text!; final opacity = (channel.state?.unreadCount ?? 0) > 0 ? 1.0 : 0.5; return ListTile( @@ -88,7 +109,7 @@ class ChannelListPage extends StatelessWidget { MaterialPageRoute( builder: (_) => StreamChannel( channel: channel, - child: ChannelPage(), + child: const ChannelPage(), ), ), ); @@ -111,7 +132,7 @@ class ChannelListPage extends StatelessWidget { radius: 10, child: Text(channel.state!.unreadCount.toString()), ) - : SizedBox(), + : const SizedBox(), ); } } @@ -122,11 +143,12 @@ class ChannelPage extends StatelessWidget { }) : super(key: key); @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( - appBar: ChannelHeader(), + appBar: const ChannelHeader(), body: Column( - children: [ + children: const [ Expanded( child: MessageListView(), ), diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart similarity index 61% rename from packages/stream_chat_flutter/example/lib/tutorial-part-4.dart rename to packages/stream_chat_flutter/example/lib/tutorial_part_4.dart index 5a85339f..07942302 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart @@ -4,13 +4,17 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Fourth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) /// -/// Stream Chat supports message threads out of the box. Threads allows users to create sub-conversations inside the same channel. +/// Stream Chat supports message threads out of the box. Threads allows users +/// to create sub-conversations inside the same channel. /// -/// Using threaded conversations is very simple and mostly a matter of plugging the [MessageListView] to another widget that renders the widget. -/// To make this simple, such a widget only needs to build [MessageListView] with the parent attribute set to the thread’s root message. +/// Using threaded conversations is very simple and mostly a matter of +/// plugging the [MessageListView] to another widget that renders the widget. +/// To make this simple, such a widget only needs to build [MessageListView] +/// with the parent attribute set to the thread’s root message. /// -/// Now we can open threads and create new ones as well, if you long press a message you can tap on Reply and it will open the same [ThreadPage]. -void main() async { +/// Now we can open threads and create new ones as well. If you long-press a +/// message, you can tap on "Reply" and it will open the same [ThreadPage]. +Future main() async { final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, @@ -18,33 +22,44 @@ void main() async { await client.connectUser( User(id: 'super-band-9'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''', ); - runApp(MyApp(client)); + runApp( + MyApp( + client: client, + ), + ); } class MyApp extends StatelessWidget { + const MyApp({ + Key? key, + required this.client, + }) : super(key: key); + final StreamChatClient client; - MyApp(this.client); - @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return MaterialApp( builder: (context, child) => StreamChat( client: client, child: child, ), - home: Container( - child: ChannelListPage(), - ), + home: const ChannelListPage(), ); } } class ChannelListPage extends StatelessWidget { + const ChannelListPage({ + Key? key, + }) : super(key: key); + @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( body: ChannelsBloc( @@ -53,11 +68,11 @@ class ChannelListPage extends StatelessWidget { 'members', [StreamChat.of(context).user!.id], ), - sort: [SortOption('last_message_at')], - pagination: PaginationParams( + sort: const [SortOption('last_message_at')], + pagination: const PaginationParams( limit: 20, ), - channelWidget: ChannelPage(), + channelWidget: const ChannelPage(), ), ), ); @@ -70,21 +85,20 @@ class ChannelPage extends StatelessWidget { }) : super(key: key); @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( - appBar: ChannelHeader(), + appBar: const ChannelHeader(), body: Column( children: [ Expanded( child: MessageListView( - threadBuilder: (_, parentMessage) { - return ThreadPage( - parent: parentMessage, - ); - }, + threadBuilder: (_, parentMessage) => ThreadPage( + parent: parentMessage, + ), ), ), - MessageInput(), + const MessageInput(), ], ), ); @@ -92,14 +106,15 @@ class ChannelPage extends StatelessWidget { } class ThreadPage extends StatelessWidget { - final Message? parent; - - ThreadPage({ + const ThreadPage({ Key? key, this.parent, }) : super(key: key); + final Message? parent; + @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( appBar: ThreadHeader( diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart similarity index 60% rename from packages/stream_chat_flutter/example/lib/tutorial-part-5.dart rename to packages/stream_chat_flutter/example/lib/tutorial_part_5.dart index 8f1118dc..dbbf31a0 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart @@ -4,18 +4,23 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Fifth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) /// -/// Customizing how messages are rendered is another very common use-case that the SDK supports easily. +/// Customizing how messages are rendered is another very common use-case that +/// the SDK supports easily. /// -/// Replace the built-in message component with your own is done by passing it as a builder function to the [MessageListView] widget. +/// Replacing the built-in message component with your own is done by passing +/// it as a builder function to the [MessageListView] widget. /// -/// The message builder function will get the usual [BuildContext] argument as well as the [Message] object and its position inside the list. +/// The message builder function will get the usual [BuildContext] argument +/// as well as the [Message] object and its position inside the list. /// -/// If you look at the code you can see that we use [StreamChat.of] to retrieve the current user so that we can style messages own messages in a different way. +/// If you look at the code you can see that we use [StreamChat.of] to +/// retrieve the current user so that we can style messages in a different way. /// -/// Since custom widgets and builders are always children of [StreamChat] or part of a [Channel], -/// you can use [StreamChat.of], [StreamChannel.of] and [StreamChatTheme.of] to use the API client directly -/// or to retrieve outer scope needed such as messages from the [Channel.state]. -void main() async { +/// Since custom widgets and builders are always children of [StreamChat] or +/// part of a [Channel], you can use [StreamChat.of], [StreamChannel.of], +/// and [StreamChatTheme.of] to use the API client directly or to retrieve +/// outer scope needed such as messages from the [Channel.state]. +Future main() async { final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, @@ -23,31 +28,44 @@ void main() async { await client.connectUser( User(id: 'super-band-9'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''', ); - runApp(MyApp(client)); + runApp( + MyApp( + client: client, + ), + ); } class MyApp extends StatelessWidget { + const MyApp({ + Key? key, + required this.client, + }) : super(key: key); + final StreamChatClient client; - MyApp(this.client); - @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return MaterialApp( builder: (context, child) => StreamChat( client: client, child: child, ), - home: ChannelListPage(), + home: const ChannelListPage(), ); } } class ChannelListPage extends StatelessWidget { + const ChannelListPage({ + Key? key, + }) : super(key: key); + @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( body: ChannelsBloc( @@ -56,11 +74,11 @@ class ChannelListPage extends StatelessWidget { 'members', [StreamChat.of(context).user!.id], ), - sort: [SortOption('last_message_at')], - pagination: PaginationParams( + sort: const [SortOption('last_message_at')], + pagination: const PaginationParams( limit: 20, ), - channelWidget: ChannelPage(), + channelWidget: const ChannelPage(), ), ), ); @@ -73,9 +91,10 @@ class ChannelPage extends StatelessWidget { }) : super(key: key); @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( - appBar: ChannelHeader(), + appBar: const ChannelHeader(), body: Column( children: [ Expanded( @@ -83,7 +102,7 @@ class ChannelPage extends StatelessWidget { messageBuilder: _messageBuilder, ), ), - MessageInput(), + const MessageInput(), ], ), ); @@ -101,12 +120,14 @@ class ChannelPage extends StatelessWidget { final color = isCurrentUser ? Colors.blueGrey : Colors.blue; return Padding( - padding: EdgeInsets.all(5.0), + padding: const EdgeInsets.all(5), child: Container( decoration: BoxDecoration( - border: Border.all(color: color, width: 1), - borderRadius: BorderRadius.all( - Radius.circular(5.0), + border: Border.all( + color: color, + ), + borderRadius: const BorderRadius.all( + Radius.circular(5), ), ), child: ListTile( @@ -115,7 +136,7 @@ class ChannelPage extends StatelessWidget { textAlign: textAlign, ), subtitle: Text( - message.user!.extraData['name'] as String, + message.user!.name, textAlign: textAlign, ), ), diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart similarity index 62% rename from packages/stream_chat_flutter/example/lib/tutorial-part-6.dart rename to packages/stream_chat_flutter/example/lib/tutorial_part_6.dart index e4f5b8a5..fcf86b5c 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart @@ -4,22 +4,29 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Sixth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) /// -/// The Flutter SDK comes with a fully designed set of widgets which you can customize to fit with your application style and typography. -/// Changing the theme of Chat widgets works in a very similar way that [MaterialApp] and [Theme] do. +/// The Flutter SDK comes with a fully designed set of widgets which you can +/// customize to fit with your application style and typography. +/// Changing the theme of Chat widgets works in a very similar way that +/// [MaterialApp] and [Theme] do. /// -/// Out of the box all chat widgets use their own default styling, there are two ways to change the styling: +/// All chat widgets use their own default styling out of the box. There are +/// two ways to change the styling: /// /// 1. Initialize the [StreamChatTheme] from your existing [MaterialApp] style /// 2. Construct a custom theme and provide all the customizations needed /// -/// First we create a new Material [Theme] and pick [Colors.green] as swatch color. The theme is then passed to [MaterialApp] as usual. +/// First, we create a new Material [Theme] and pick [Colors.green] as the +/// swatch color. The theme is then passed to [MaterialApp] as usual. /// -/// Then we create a new [StreamChatTheme] from the green theme we just created. -/// After saving the app you will see the UI will update several widgets to match with the new color. +/// Then, we create a new [StreamChatTheme] from the green theme we just +/// created. After saving the app you will see that several widgets have +/// been updated with the new color. /// /// We also change the message color posted by the current user. -/// You can perform these more granular style changes using [StreamChatTheme.copyWith]. -void main() async { +/// +/// You can perform these more granular style changes using +/// [StreamChatTheme.copyWith]. +Future main() async { final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, @@ -27,16 +34,23 @@ void main() async { await client.connectUser( User(id: 'super-band-9'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + '''eyJ0eXAiOÂĢiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''', ); - runApp(MyApp(client)); + runApp( + MyApp( + client: client, + ), + ); } class MyApp extends StatelessWidget { - final StreamChatClient client; + const MyApp({ + Key? key, + required this.client, + }) : super(key: key); - MyApp(this.client); + final StreamChatClient client; @override Widget build(BuildContext context) { @@ -62,20 +76,23 @@ class MyApp extends StatelessWidget { return MaterialApp( theme: themeData, - builder: (context, child) { - return StreamChat( - client: client, - streamChatThemeData: customTheme, - child: child, - ); - }, - home: ChannelListPage(), + builder: (context, child) => StreamChat( + client: client, + streamChatThemeData: customTheme, + child: child, + ), + home: const ChannelListPage(), ); } } class ChannelListPage extends StatelessWidget { + const ChannelListPage({ + Key? key, + }) : super(key: key); + @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( body: ChannelsBloc( @@ -84,11 +101,11 @@ class ChannelListPage extends StatelessWidget { 'members', [StreamChat.of(context).user!.id], ), - sort: [SortOption('last_message_at')], - pagination: PaginationParams( + sort: const [SortOption('last_message_at')], + pagination: const PaginationParams( limit: 20, ), - channelWidget: ChannelPage(), + channelWidget: const ChannelPage(), ), ), ); @@ -101,21 +118,20 @@ class ChannelPage extends StatelessWidget { }) : super(key: key); @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( - appBar: ChannelHeader(), + appBar: const ChannelHeader(), body: Column( children: [ Expanded( child: MessageListView( - threadBuilder: (_, parentMessage) { - return ThreadPage( - parent: parentMessage, - ); - }, + threadBuilder: (_, parentMessage) => ThreadPage( + parent: parentMessage, + ), ), ), - MessageInput(), + const MessageInput(), ], ), ); @@ -123,14 +139,15 @@ class ChannelPage extends StatelessWidget { } class ThreadPage extends StatelessWidget { - final Message? parent; - - ThreadPage({ + const ThreadPage({ Key? key, this.parent, }) : super(key: key); + final Message? parent; + @override + // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { return Scaffold( appBar: ThreadHeader( diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index 408bf9e1..d8b29d3f 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -21,21 +21,21 @@ environment: sdk: '>=2.12.0 <3.0.0' dependencies: - flutter: - sdk: flutter -# stream_chat: -# path: ../../stream_chat -# stream_chat_flutter_core: -# path: ../../stream_chat_flutter_core - stream_chat_flutter: - path: ../ - stream_chat_persistence: - path: ../../stream_chat_persistence - # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 collection: ^1.15.0 + cupertino_icons: ^1.0.3 + flutter: + sdk: flutter + # stream_chat: + # path: ../../stream_chat + # stream_chat_flutter_core: + # path: ../../stream_chat_flutter_core + stream_chat_flutter: + path: ../ + stream_chat_persistence: + path: ../../stream_chat_persistence + dev_dependencies: flutter_test: diff --git a/packages/stream_chat_flutter/test/src/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel_image_test.dart index ba913a77..b2eb725f 100644 --- a/packages/stream_chat_flutter/test/src/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_image_test.dart @@ -74,7 +74,7 @@ void main() { userId: 'user-id2', user: User( id: 'user-id2', - extraData: { + extraData: const { 'image': 'testimage', }, ), @@ -85,7 +85,7 @@ void main() { userId: 'user-id2', user: User( id: 'user-id2', - extraData: { + extraData: const { 'image': 'testimage', }, ), @@ -98,7 +98,7 @@ void main() { when(() => clientState.usersStream).thenAnswer((i) => Stream.value({ 'user-id2': User( id: 'user-id2', - extraData: { + extraData: const { 'image': 'testimage', }, ), @@ -148,7 +148,7 @@ void main() { userId: 'user-id', user: User( id: 'user-id', - extraData: { + extraData: const { 'image': 'testimage1', }, ), @@ -157,7 +157,7 @@ void main() { userId: 'user-id2', user: User( id: 'user-id2', - extraData: { + extraData: const { 'image': 'testimage2', }, ), @@ -166,7 +166,7 @@ void main() { userId: 'user-id3', user: User( id: 'user-id3', - extraData: { + extraData: const { 'image': 'testimage3', }, ), @@ -177,7 +177,7 @@ void main() { userId: 'user-id', user: User( id: 'user-id', - extraData: { + extraData: const { 'image': 'testimage1', }, ), @@ -186,7 +186,7 @@ void main() { userId: 'user-id2', user: User( id: 'user-id2', - extraData: { + extraData: const { 'image': 'testimage2', }, ), @@ -195,7 +195,7 @@ void main() { userId: 'user-id3', user: User( id: 'user-id3', - extraData: { + extraData: const { 'image': 'testimage3', }, ), diff --git a/packages/stream_chat_flutter/test/src/deleted_message_test.dart b/packages/stream_chat_flutter/test/src/deleted_message_test.dart index 206d92e3..0e30d2b3 100644 --- a/packages/stream_chat_flutter/test/src/deleted_message_test.dart +++ b/packages/stream_chat_flutter/test/src/deleted_message_test.dart @@ -70,6 +70,7 @@ void main() { StreamChat( streamChatThemeData: theme, client: client, + connectivityStream: Stream.value(ConnectivityResult.mobile), child: StreamChannel( showLoading: false, channel: channel, @@ -79,7 +80,6 @@ void main() { ), ), ), - connectivityStream: Stream.value(ConnectivityResult.mobile), ), ), surfaceSize: const Size.square(200), @@ -123,6 +123,7 @@ void main() { StreamChat( streamChatThemeData: theme, client: client, + connectivityStream: Stream.value(ConnectivityResult.mobile), child: StreamChannel( showLoading: false, channel: channel, @@ -132,7 +133,6 @@ void main() { ), ), ), - connectivityStream: Stream.value(ConnectivityResult.mobile), ), ), surfaceSize: const Size.square(200), @@ -176,6 +176,7 @@ void main() { StreamChat( streamChatThemeData: theme, client: client, + connectivityStream: Stream.value(ConnectivityResult.mobile), child: StreamChannel( showLoading: false, channel: channel, @@ -189,7 +190,6 @@ void main() { ), ), ), - connectivityStream: Stream.value(ConnectivityResult.mobile), ), ), surfaceSize: const Size.square(200), diff --git a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart index 3e6f1743..03f6ea65 100644 --- a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart +++ b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart @@ -54,12 +54,12 @@ void main() { ) ])); when(() => channelState.typingEvents).thenAnswer((i) => { - User(id: 'other-user', extraData: {'name': 'demo'}): + User(id: 'other-user', extraData: const {'name': 'demo'}): Event(type: EventType.typingStart), }); when(() => channelState.typingEventsStream) .thenAnswer((i) => Stream.value({ - User(id: 'other-user', extraData: {'name': 'demo'}): + User(id: 'other-user', extraData: const {'name': 'demo'}): Event(type: EventType.typingStart), })); diff --git a/packages/stream_chat_flutter/test/src/gallery_footer_theme_test.dart b/packages/stream_chat_flutter/test/src/gallery_footer_theme_test.dart index 8bb5146f..fb8d7035 100644 --- a/packages/stream_chat_flutter/test/src/gallery_footer_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/gallery_footer_theme_test.dart @@ -14,7 +14,7 @@ void main() { }); test( - 'Light GalleryFooterThemeData lerps completely to dark GalleryFooterThemeData', + '''Light GalleryFooterThemeData lerps completely to dark GalleryFooterThemeData''', () { expect( const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControl, @@ -23,7 +23,7 @@ void main() { }); test( - 'Light GalleryFooterThemeData lerps halfway to dark GalleryFooterThemeData', + '''Light GalleryFooterThemeData lerps halfway to dark GalleryFooterThemeData''', () { expect( const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControl, @@ -32,7 +32,7 @@ void main() { }); test( - 'Dark GalleryFooterThemeData lerps completely to light GalleryFooterThemeData', + '''Dark GalleryFooterThemeData lerps completely to light GalleryFooterThemeData''', () { expect( const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControlDark, diff --git a/packages/stream_chat_flutter/test/src/gallery_header_theme_test.dart b/packages/stream_chat_flutter/test/src/gallery_header_theme_test.dart index 9dca4ce5..cbcc07a6 100644 --- a/packages/stream_chat_flutter/test/src/gallery_header_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/gallery_header_theme_test.dart @@ -14,7 +14,7 @@ void main() { }); test( - 'Light GalleryHeaderThemeData lerps completely to dark GalleryHeaderThemeData', + '''Light GalleryHeaderThemeData lerps completely to dark GalleryHeaderThemeData''', () { expect( const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataControl, @@ -23,7 +23,7 @@ void main() { }); test( - 'Light GalleryHeaderThemeData lerps halfway to dark GalleryHeaderThemeData', + '''Light GalleryHeaderThemeData lerps halfway to dark GalleryHeaderThemeData''', () { expect( const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataControl, @@ -32,7 +32,7 @@ void main() { }); test( - 'Dark GalleryHeaderThemeData lerps completely to light GalleryHeaderThemeData', + '''Dark GalleryHeaderThemeData lerps completely to light GalleryHeaderThemeData''', () { expect( const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataDarkControl, diff --git a/packages/stream_chat_flutter/test/src/goldens/message_text.png b/packages/stream_chat_flutter/test/src/goldens/message_text.png index dda158b267997036a5c7573fc56fe52d0813412d..ecd937218fea6cc9d591342ac5eef2bac6e0afe1 100644 GIT binary patch literal 8701 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 literal 10173 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 channelState.typingEvents).thenAnswer((i) => { - User(id: 'other-user', extraData: {'name': 'demo'}): + User(id: 'other-user', extraData: const {'name': 'demo'}): Event(type: EventType.typingStart), }); when(() => channelState.typingEventsStream) .thenAnswer((i) => Stream.value({ - User(id: 'other-user', extraData: {'name': 'demo'}): + User(id: 'other-user', extraData: const {'name': 'demo'}): Event(type: EventType.typingStart), })); diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index 2de50c1a..4989f38f 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; Future main() async { - /// Create a new instance of [StreamChatClient] passing the apikey obtained from your - /// project dashboard. + /// Create a new instance of [StreamChatClient] passing the apikey obtained + /// from your project dashboard. final client = StreamChatClient('b67pax5b2wdq'); /// Set the current user. In a production scenario, this should be done using @@ -13,12 +13,13 @@ Future main() async { await client.connectUser( User( id: 'cool-shadow-7', - extraData: { + extraData: const { 'image': 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', }, ), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9' + '.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', ); runApp( @@ -29,36 +30,36 @@ Future main() async { } /// Example application using Stream Chat core widgets. -/// Stream Chat Core is a set of Flutter wrappers which provide basic functionality -/// for building Flutter applications using Stream. +/// Stream Chat Core is a set of Flutter wrappers which provide basic +/// functionality for building Flutter applications using Stream. +/// /// If you'd prefer using pre-made UI widgets for your app, please see our other /// package, `stream_chat_flutter`. class StreamExample extends StatelessWidget { /// Minimal example using Stream's core Flutter package. - /// If you'd prefer using pre-made UI widgets for your app, please see our other - /// package, `stream_chat_flutter`. + /// + /// If you'd prefer using pre-made UI widgets for your app, please see our + /// other package, `stream_chat_flutter`. const StreamExample({ Key? key, required this.client, }) : super(key: key); /// Instance of Stream Client. - /// Stream's [StreamChatClient] can be used to connect to our servers and set the default - /// user for the application. Performing these actions trigger a websocket connection - /// allowing for real-time updates. + /// Stream's [StreamChatClient] can be used to connect to our servers and + /// set the default user for the application. Performing these actions + /// trigger a websocket connection allowing for real-time updates. final StreamChatClient client; @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Stream Chat Core Example', - home: HomeScreen(), - builder: (context, child) => StreamChatCore( - client: client, - child: child!, - ), - ); - } + Widget build(BuildContext context) => MaterialApp( + title: 'Stream Chat Core Example', + home: HomeScreen(), + builder: (context, child) => StreamChatCore( + client: client, + child: child!, + ), + ); } /// Basic layout displaying a list of [Channel]s the user is a part of. @@ -67,104 +68,108 @@ class StreamExample extends StatelessWidget { /// [ChannelListCore] is a `builder` with callbacks for constructing UIs based /// on different scenarios. class HomeScreen extends StatelessWidget { + /// Builds a basic layout displaying a list of [Channel]s the user is a + /// part of. + HomeScreen({Key? key}) : super(key: key); + + /// Controller used for loading more data and controlling pagination in + /// [ChannelListCore]. final channelListController = ChannelListController(); @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Channels'), - ), - body: ChannelsBloc( - child: ChannelListCore( - channelListController: channelListController, - filter: Filter.and([ - Filter.equal('type', 'messaging'), - Filter.in_('members', [ - StreamChatCore.of(context).user!.id, - ]) - ]), - emptyBuilder: (BuildContext context) { - return Center( + Widget build(BuildContext context) => Scaffold( + appBar: AppBar( + title: const Text('Channels'), + ), + body: ChannelsBloc( + child: ChannelListCore( + channelListController: channelListController, + filter: Filter.and([ + Filter.equal('type', 'messaging'), + Filter.in_('members', [ + StreamChatCore.of(context).user!.id, + ]) + ]), + emptyBuilder: (BuildContext context) => const Center( child: Text('Looks like you are not in any channels'), - ); - }, - loadingBuilder: (BuildContext context) { - return Center( + ), + loadingBuilder: (BuildContext context) => const Center( child: SizedBox( - height: 100.0, - width: 100.0, + height: 100, + width: 100, child: CircularProgressIndicator(), ), - ); - }, - errorBuilder: ( - BuildContext context, - dynamic error, - ) { - return Center( + ), + errorBuilder: ( + BuildContext context, + dynamic error, + ) => + Center( child: Text( - 'Oh no, something went wrong. Please check your config. ${error}'), - ); - }, - listBuilder: ( - BuildContext context, - List channels, - ) => - LazyLoadScrollView( - onEndOfPage: () async { - channelListController.paginateData!(); - }, - child: ListView.builder( - itemCount: channels.length, - itemBuilder: (BuildContext context, int index) { - final _item = channels[index]; - return ListTile( - title: Text(_item.name!), - subtitle: StreamBuilder( - stream: _item.state!.lastMessageStream, - initialData: _item.state!.lastMessage, - builder: (context, snapshot) { - if (snapshot.hasData) { - return Text(snapshot.data!.text!); - } - - return SizedBox(); - }, - ), - onTap: () { - /// Display a list of messages when the user taps on an item. - /// We can use [StreamChannel] to wrap our [MessageScreen] screen - /// with the selected channel. - /// - /// This allows us to use a built-in inherited widget for accessing - /// our `channel` later on. - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: _item, - child: MessageScreen(), - ), - ), - ); - }, - ); + 'Oh no, something went wrong. ' + 'Please check your config. $error', + ), + ), + listBuilder: ( + BuildContext context, + List channels, + ) => + LazyLoadScrollView( + onEndOfPage: () async { + channelListController.paginateData!(); }, + child: ListView.builder( + itemCount: channels.length, + itemBuilder: (BuildContext context, int index) { + final _item = channels[index]; + return ListTile( + title: Text(_item.name!), + subtitle: StreamBuilder( + stream: _item.state!.lastMessageStream, + initialData: _item.state!.lastMessage, + builder: (context, snapshot) { + if (snapshot.hasData) { + return Text(snapshot.data!.text!); + } + + return const SizedBox(); + }, + ), + onTap: () { + /// Display a list of messages when the user taps on + /// an item. We can use [StreamChannel] to wrap our + /// [MessageScreen] screen with the selected channel. + /// + /// This allows us to use a built-in inherited widget + /// for accessing our `channel` later on. + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: _item, + child: const MessageScreen(), + ), + ), + ); + }, + ); + }, + ), ), ), ), - ), - ); - } + ); } /// A list of messages sent in the current channel. -/// When a user taps on a channel in [HomeScreen], a navigator push [MessageScreen] -/// to display the list of messages in the selected channel. +/// When a user taps on a channel in [HomeScreen], a navigator push +/// [MessageScreen] to display the list of messages in the selected channel. /// /// This is implemented using [MessageListCore], a convenience builder with /// callbacks for building UIs based on different api results. class MessageScreen extends StatefulWidget { + /// Build a MessageScreen + const MessageScreen({Key? key}) : super(key: key); + @override _MessageScreenState createState() => _MessageScreenState(); } @@ -198,8 +203,8 @@ class _MessageScreenState extends State { @override Widget build(BuildContext context) { - /// To access the current channel, we can use the `.of()` method on [StreamChannel] - /// to fetch the closest instance. + /// To access the current channel, we can use the `.of()` method on + /// [StreamChannel] to fetch the closest instance. final channel = StreamChannel.of(context).channel; return Scaffold( appBar: AppBar( @@ -210,7 +215,7 @@ class _MessageScreenState extends State { if (snapshot.hasData && snapshot.data!.isNotEmpty) { return Text('${snapshot.data!.first.name} is typing...'); } - return SizedBox(); + return const SizedBox(); }, ), ), @@ -224,57 +229,52 @@ class _MessageScreenState extends State { }, child: MessageListCore( messageListController: messageListController, - emptyBuilder: (BuildContext context) { - return Center( - child: Text('Nothing here yet'), - ); - }, - loadingBuilder: (BuildContext context) { - return Center( - child: SizedBox( - height: 100.0, - width: 100.0, - child: CircularProgressIndicator(), - ), - ); - }, + emptyBuilder: (BuildContext context) => const Center( + child: Text('Nothing here yet'), + ), + loadingBuilder: (BuildContext context) => const Center( + child: SizedBox( + height: 100, + width: 100, + child: CircularProgressIndicator(), + ), + ), messageListBuilder: ( BuildContext context, List messages, - ) { - return ListView.builder( - controller: _scrollController, - itemCount: messages.length, - reverse: true, - itemBuilder: (BuildContext context, int index) { - final item = messages[index]; - final client = StreamChatCore.of(context).client; - if (item.user!.id == client.uid) { - return Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(item.text!), - ), - ); - } else { - return Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(item.text!), - ), - ); - } - }, - ); - }, + ) => + ListView.builder( + controller: _scrollController, + itemCount: messages.length, + reverse: true, + itemBuilder: (BuildContext context, int index) { + final item = messages[index]; + final client = StreamChatCore.of(context).client; + if (item.user!.id == client.uid) { + return Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text!), + ), + ); + } else { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text!), + ), + ); + } + }, + ), errorBuilder: (BuildContext context, error) { print(error.toString()); - return Center( + return const Center( child: SizedBox( - height: 100.0, - width: 100.0, + height: 100, + width: 100, child: Text('Oh no, an error occured. Please see logs.'), ), diff --git a/packages/stream_chat_flutter_core/example/pubspec.yaml b/packages/stream_chat_flutter_core/example/pubspec.yaml index 889ad518..6390abc4 100644 --- a/packages/stream_chat_flutter_core/example/pubspec.yaml +++ b/packages/stream_chat_flutter_core/example/pubspec.yaml @@ -21,14 +21,14 @@ environment: sdk: '>=2.12.0 <3.0.0' dependencies: + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.3 flutter: sdk: flutter stream_chat_flutter_core: path: ../ - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.0 dev_dependencies: flutter_test: diff --git a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart index a69f81c9..47aa2bf9 100644 --- a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart @@ -9,26 +9,25 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'mocks.dart'; void main() { - const pagination = PaginationParams(offset: 0, limit: 3); + const pagination = PaginationParams(limit: 3); List _generateChannels( StreamChatClient client, { int count = 3, int offset = 0, - }) { - return List.generate( - count, - (index) { - index = index + offset; - return Channel( - client, - 'testType$index', - 'testId$index', - extraData: {'extra_data_key': 'extra_data_value_$index'}, - ); - }, - ); - } + }) => + List.generate( + count, + (index) { + index = index + offset; + return Channel( + client, + 'testType$index', + 'testId$index', + extraData: {'extra_data_key': 'extra_data_value_$index'}, + ); + }, + ); testWidgets( 'should throw if ChannelListCore is used where ChannelsBloc is not present ' @@ -37,10 +36,10 @@ void main() { const channelListCoreKey = Key('channelListCore'); final channelListCore = ChannelListCore( key: channelListCoreKey, - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + listBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), ); await tester.pumpWidget(channelListCore); @@ -56,16 +55,16 @@ void main() { const channelListCoreKey = Key('channelListCore'); final channelListCore = ChannelListCore( key: channelListCoreKey, - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + listBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), ); final mockClient = MockClient(); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -88,10 +87,10 @@ void main() { final controller = ChannelListController(); final channelListCore = ChannelListCore( key: channelListCoreKey, - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + listBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), channelListController: controller, ); @@ -101,7 +100,7 @@ void main() { final mockClient = MockClient(); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -125,9 +124,9 @@ void main() { const errorWidgetKey = Key('errorWidget'); final channelListCore = ChannelListCore( key: channelListCoreKey, - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), + listBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object error) => Container(key: errorWidgetKey), pagination: pagination, @@ -136,7 +135,7 @@ void main() { final mockClient = MockClient(); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); const error = 'Error! Error! Error!'; when(() => mockClient.queryChannels( @@ -177,23 +176,23 @@ void main() { ); testWidgets( - 'should build empty widget if channelsBlocState.channelsStream emits empty data', + '''should build empty widget if channelsBlocState.channelsStream emits empty data''', (tester) async { const channelListCoreKey = Key('channelListCore'); const emptyWidgetKey = Key('emptyWidget'); final channelListCore = ChannelListCore( key: channelListCoreKey, - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), + listBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), emptyBuilder: (BuildContext context) => Container(key: emptyWidgetKey), - errorBuilder: (BuildContext context, Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), pagination: pagination, ); final mockClient = MockClient(); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); const channels = []; when(() => mockClient.queryChannels( @@ -234,23 +233,23 @@ void main() { ); testWidgets( - 'should build list widget if channelsBlocState.channelsStream emits some data', + '''should build list widget if channelsBlocState.channelsStream emits some data''', (tester) async { const channelListCoreKey = Key('channelListCore'); const listWidgetKey = Key('listWidget'); final channelListCore = ChannelListCore( key: channelListCoreKey, listBuilder: (_, __) => Container(key: listWidgetKey), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), pagination: pagination, ); final mockClient = MockClient(); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); final channels = _generateChannels(mockClient); when(() => mockClient.queryChannels( @@ -298,24 +297,22 @@ void main() { const listWidgetKey = Key('listWidget'); final channelListCore = ChannelListCore( key: channelListCoreKey, - listBuilder: (_, channels) { - return Container( - key: listWidgetKey, - child: Text( - channels.map((e) => e.cid).join(','), - ), - ); - }, - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + listBuilder: (_, channels) => Container( + key: listWidgetKey, + child: Text( + channels.map((e) => e.cid).join(','), + ), + ), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), pagination: pagination, ); final mockClient = MockClient(); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); final channels = _generateChannels(mockClient); when(() => mockClient.queryChannels( @@ -413,24 +410,23 @@ void main() { ChannelListCore channelListCoreBuilder(int limit) => ChannelListCore( key: channelListCoreKey, - listBuilder: (_, channels) { - return Container( - key: listWidgetKey, - child: Text( - channels.map((e) => e.cid).join(','), - ), - ); - }, - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + listBuilder: (_, channels) => Container( + key: listWidgetKey, + child: Text( + channels.map((e) => e.cid).join(','), + ), + ), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => + const Offstage(), pagination: pagination.copyWith(limit: limit), ); final mockClient = MockClient(); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); final channels = _generateChannels(mockClient); when(() => mockClient.queryChannels( diff --git a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart index 3c0286a0..a237a23c 100644 --- a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart @@ -17,27 +17,26 @@ void main() { StreamChatClient client, { int count = 3, int offset = 0, - }) { - return List.generate( - count, - (index) { - index = index + offset; - return Channel( - client, - 'testType$index', - 'testId$index', - extraData: {'extra_data_key': 'extra_data_value_$index'}, - ); - }, - ); - } + }) => + List.generate( + count, + (index) { + index = index + offset; + return Channel( + client, + 'testType$index', + 'testId$index', + extraData: {'extra_data_key': 'extra_data_value_$index'}, + ); + }, + ); testWidgets( - 'should throw if ChannelsBloc is used where StreamChat is not present in the widget tree', + '''should throw if ChannelsBloc is used where StreamChat is not present in the widget tree''', (tester) async { const channelsBlocKey = Key('channelsBloc'); const childKey = Key('child'); - final channelsBloc = ChannelsBloc( + const channelsBloc = ChannelsBloc( key: channelsBlocKey, child: Offstage(key: childKey), ); @@ -55,7 +54,7 @@ void main() { (tester) async { const channelsBlocKey = Key('channelsBloc'); const childKey = Key('child'); - final channelsBloc = ChannelsBloc( + const channelsBloc = ChannelsBloc( key: channelsBlocKey, child: Offstage(key: childKey), ); @@ -63,7 +62,7 @@ void main() { final mockClient = MockClient(); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -86,16 +85,14 @@ void main() { key: channelsBlocKey, child: Builder( key: childKey, - builder: (context) { - return Offstage(); - }, + builder: (context) => const Offstage(), ), ); final mockClient = MockClient(); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -157,16 +154,14 @@ void main() { key: channelsBlocKey, child: Builder( key: childKey, - builder: (context) { - return Offstage(); - }, + builder: (context) => const Offstage(), ), ); final mockClient = MockClient(); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -218,7 +213,7 @@ void main() { 'through queryChannelsLoading', (tester) async { const channelsBlocKey = Key('channelsBloc'); - final channelsBloc = ChannelsBloc( + const channelsBloc = ChannelsBloc( key: channelsBlocKey, child: Offstage(), ); @@ -226,7 +221,7 @@ void main() { final mockClient = MockClient(); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -323,7 +318,7 @@ void main() { 'client.queryChannels() throws', (tester) async { const channelsBlocKey = Key('channelsBloc'); - final channelsBloc = ChannelsBloc( + const channelsBloc = ChannelsBloc( key: channelsBlocKey, child: Offstage(), ); @@ -331,7 +326,7 @@ void main() { final mockClient = MockClient(); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); await tester.pumpWidget( StreamChatCore( @@ -345,7 +340,7 @@ void main() { ); final channels = _generateChannels(mockClient); - final paginationParams = const PaginationParams( + const paginationParams = PaginationParams( limit: 3, ); @@ -380,7 +375,7 @@ void main() { paginationParams: paginationParams, )).called(1); - final error = 'Error! Error! Error!'; + const error = 'Error! Error! Error!'; when(() => mockClient.queryChannels( filter: any(named: 'filter'), @@ -424,13 +419,13 @@ void main() { (tester) async { final mockClient = MockClient(); const channelsBlocKey = Key('channelsBloc'); - final channelsBloc = ChannelsBloc( + const channelsBloc = ChannelsBloc( key: channelsBlocKey, child: Offstage(), ); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); when(() => mockClient.on( EventType.channelHidden, @@ -503,13 +498,13 @@ void main() { (tester) async { final mockClient = MockClient(); const channelsBlocKey = Key('channelsBloc'); - final channelsBloc = ChannelsBloc( + const channelsBloc = ChannelsBloc( key: channelsBlocKey, child: Offstage(), ); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); when(() => mockClient.on( EventType.channelDeleted, @@ -589,18 +584,18 @@ void main() { ); testWidgets( - 'event channel should be moved to top of the list if present when' + 'event channel should be moved to top of the list if present when ' 'EventType.messageNew event is received', (tester) async { final mockClient = MockClient(); const channelsBlocKey = Key('channelsBloc'); - final channelsBloc = ChannelsBloc( + const channelsBloc = ChannelsBloc( key: channelsBlocKey, child: Offstage(), ); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); when(() => mockClient.on( EventType.messageNew, @@ -678,21 +673,19 @@ void main() { (tester) async { final hiddenChannelEventController = StreamController(); - addTearDown(() { - hiddenChannelEventController.close(); - }); + addTearDown(hiddenChannelEventController.close); final mockClient = MockClient(); final channels = _generateChannels(mockClient); const channelsBlocKey = Key('channelsBloc'); final channelsBloc = ChannelsBloc( key: channelsBlocKey, - child: Offstage(), shouldAddChannel: (e) => channels.map((it) => it.cid).contains(e.cid), + child: const Offstage(), ); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); when(() => mockClient.on( EventType.channelHidden, @@ -789,14 +782,14 @@ void main() { const channelsBlocKey = Key('channelsBloc'); final channelsBloc = ChannelsBloc( key: channelsBlocKey, - child: Offstage(), shouldAddChannel: (_) => true, + child: const Offstage(), ); when(() => mockClient.state.channels).thenReturn(stateChannels); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); when(() => mockClient.on( EventType.messageNew, @@ -868,21 +861,21 @@ void main() { final mockClient = MockClient(); final channels = _generateChannels(mockClient); int channelComparator(Channel a, Channel b) { - final aData = a.extraData['extra_data_key'] as String; - final bData = b.extraData['extra_data_key'] as String; + final aData = a.extraData['extra_data_key'].toString(); + final bData = b.extraData['extra_data_key'].toString(); return bData.compareTo(aData); } const channelsBlocKey = Key('channelsBloc'); final channelsBloc = ChannelsBloc( key: channelsBlocKey, - child: Offstage(), shouldAddChannel: (_) => true, channelsComparator: channelComparator, + child: const Offstage(), ); when(() => mockClient.on(any(), any(), any(), any())) - .thenAnswer((_) => Stream.empty()); + .thenAnswer((_) => const Stream.empty()); when(() => mockClient.on( EventType.messageNew, diff --git a/packages/stream_chat_flutter_core/test/lazy_load_scroll_view_test.dart b/packages/stream_chat_flutter_core/test/lazy_load_scroll_view_test.dart index af59088f..86b39e23 100644 --- a/packages/stream_chat_flutter_core/test/lazy_load_scroll_view_test.dart +++ b/packages/stream_chat_flutter_core/test/lazy_load_scroll_view_test.dart @@ -7,7 +7,7 @@ void main() { 'should render LazyLoadScrollView if child is provided', (tester) async { const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); - final lazyLoadScrollView = LazyLoadScrollView( + const lazyLoadScrollView = LazyLoadScrollView( key: lazyLoadScrollViewKey, child: Offstage(), ); @@ -23,7 +23,7 @@ void main() { (tester) async { const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const childKey = Key('childKey'); - final lazyLoadScrollView = LazyLoadScrollView( + const lazyLoadScrollView = LazyLoadScrollView( key: lazyLoadScrollViewKey, child: Offstage(key: childKey), ); @@ -41,7 +41,7 @@ void main() { const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const childListViewKey = Key('childListView'); - bool onPageScrollStartCalled = false; + var onPageScrollStartCalled = false; final lazyLoadScrollView = LazyLoadScrollView( key: lazyLoadScrollViewKey, @@ -52,7 +52,7 @@ void main() { key: childListViewKey, children: List.generate( 12, - (index) => Container( + (index) => SizedBox( height: 100, child: Text('Item #$index'), ), @@ -71,7 +71,7 @@ void main() { expect(find.byKey(childListViewKey), findsOneWidget); expect(onPageScrollStartCalled, isFalse); - await tester.startGesture(const Offset(100.0, 100.0)); + await tester.startGesture(const Offset(100, 100)); await tester.pump(const Duration(seconds: 1)); expect(onPageScrollStartCalled, isTrue); @@ -85,8 +85,8 @@ void main() { const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const childListViewKey = Key('childListView'); - bool onPageScrollStartCalled = false; - bool onPageScrollEndCalled = false; + var onPageScrollStartCalled = false; + var onPageScrollEndCalled = false; final lazyLoadScrollView = LazyLoadScrollView( key: lazyLoadScrollViewKey, @@ -100,7 +100,7 @@ void main() { key: childListViewKey, children: List.generate( 12, - (index) => Container( + (index) => SizedBox( height: 100, child: Text('Item #$index'), ), @@ -122,7 +122,7 @@ void main() { final gesture = await tester.createGesture(); - await gesture.down(const Offset(100.0, 100.0)); + await gesture.down(const Offset(100, 100)); await tester.pump(const Duration(seconds: 1)); expect(onPageScrollStartCalled, isTrue); @@ -141,7 +141,7 @@ void main() { const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const childListViewKey = Key('childListView'); - bool onInBetweenOfPageCalled = false; + var onInBetweenOfPageCalled = false; final lazyLoadScrollView = LazyLoadScrollView( key: lazyLoadScrollViewKey, @@ -152,7 +152,7 @@ void main() { key: childListViewKey, children: List.generate( 12, - (index) => Container( + (index) => SizedBox( height: 100, child: Text('Item #$index'), ), @@ -173,9 +173,9 @@ void main() { final gesture = await tester.createGesture(); - await gesture.down(const Offset(100.0, 100.0)); + await gesture.down(const Offset(100, 100)); await tester.pump(const Duration(seconds: 1)); - await gesture.moveBy(const Offset(-200.0, -200.0)); + await gesture.moveBy(const Offset(-200, -200)); await tester.pump(const Duration(seconds: 1)); expect(onInBetweenOfPageCalled, isTrue); @@ -189,7 +189,7 @@ void main() { const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const childListViewKey = Key('childListView'); - bool onStartOfPageCalled = false; + var onStartOfPageCalled = false; final lazyLoadScrollView = LazyLoadScrollView( key: lazyLoadScrollViewKey, @@ -200,7 +200,7 @@ void main() { key: childListViewKey, children: List.generate( 12, - (index) => Container( + (index) => SizedBox( height: 100, child: Text('Item #$index'), ), @@ -221,11 +221,11 @@ void main() { final gesture = await tester.createGesture(); - await gesture.down(const Offset(100.0, 100.0)); + await gesture.down(const Offset(100, 100)); await tester.pump(const Duration(seconds: 1)); - await gesture.moveBy(const Offset(-200.0, -200.0)); + await gesture.moveBy(const Offset(-200, -200)); await tester.pump(const Duration(seconds: 1)); - await gesture.moveBy(const Offset(201.0, 201.0)); + await gesture.moveBy(const Offset(201, 201)); await tester.pump(const Duration(seconds: 1)); expect(onStartOfPageCalled, isTrue); @@ -239,7 +239,7 @@ void main() { const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const childListViewKey = Key('childListView'); - bool onEndOfPageCalled = false; + var onEndOfPageCalled = false; final lazyLoadScrollView = LazyLoadScrollView( key: lazyLoadScrollViewKey, @@ -250,7 +250,7 @@ void main() { key: childListViewKey, children: List.generate( 12, - (index) => Container( + (index) => SizedBox( height: 100, child: Text('Item #$index'), ), @@ -271,9 +271,9 @@ void main() { final gesture = await tester.createGesture(); - await gesture.down(const Offset(100.0, 100.0)); + await gesture.down(const Offset(100, 100)); await tester.pump(const Duration(seconds: 1)); - await gesture.moveBy(const Offset(-601.0, -601.0)); + await gesture.moveBy(const Offset(-601, -601)); await tester.pump(const Duration(seconds: 1)); expect(onEndOfPageCalled, isTrue); diff --git a/packages/stream_chat_flutter_core/test/matchers/channel_matcher.dart b/packages/stream_chat_flutter_core/test/matchers/channel_matcher.dart index f6d7fb50..514462e0 100644 --- a/packages/stream_chat_flutter_core/test/matchers/channel_matcher.dart +++ b/packages/stream_chat_flutter_core/test/matchers/channel_matcher.dart @@ -1,4 +1,3 @@ -import 'package:meta/meta.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -8,7 +7,7 @@ Matcher isSameChannelAs(Channel targetChannel) => class _IsSameChannelAs extends Matcher { const _IsSameChannelAs({ required this.targetChannel, - }) : assert(targetChannel != null, ''); + }); final Channel targetChannel; @@ -27,13 +26,13 @@ Matcher isSameChannelListAs(List targetChannelList) => class _IsSameChannelListAs extends Matcher { const _IsSameChannelListAs({ required this.targetChannelList, - }) : assert(targetChannelList != null, ''); + }); final List targetChannelList; @override bool matches(covariant List channelList, Map matchState) { - bool matches = true; + var matches = true; for (var i = 0; i < channelList.length; i++) { final channel = channelList[i]; final targetChannel = targetChannelList[i]; diff --git a/packages/stream_chat_flutter_core/test/matchers/message_matcher.dart b/packages/stream_chat_flutter_core/test/matchers/message_matcher.dart index 69a24f49..776665ff 100644 --- a/packages/stream_chat_flutter_core/test/matchers/message_matcher.dart +++ b/packages/stream_chat_flutter_core/test/matchers/message_matcher.dart @@ -1,4 +1,3 @@ -import 'package:meta/meta.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -8,7 +7,7 @@ Matcher isSameMessageAs(Message targetMessage) => class _IsSameMessageAs extends Matcher { const _IsSameMessageAs({ required this.targetMessage, - }) : assert(targetMessage != null, ''); + }); final Message targetMessage; @@ -27,13 +26,13 @@ Matcher isSameMessageListAs(List targetMessageList) => class _IsSameMessageListAs extends Matcher { const _IsSameMessageListAs({ required this.targetMessageList, - }) : assert(targetMessageList != null, ''); + }); final List targetMessageList; @override bool matches(covariant List messageList, Map matchState) { - bool matches = true; + var matches = true; for (var i = 0; i < messageList.length; i++) { final message = messageList[i]; final targetMessage = targetMessageList[i]; diff --git a/packages/stream_chat_flutter_core/test/matchers/users_matcher.dart b/packages/stream_chat_flutter_core/test/matchers/users_matcher.dart index 38e25a64..fa0f0c08 100644 --- a/packages/stream_chat_flutter_core/test/matchers/users_matcher.dart +++ b/packages/stream_chat_flutter_core/test/matchers/users_matcher.dart @@ -1,4 +1,3 @@ -import 'package:meta/meta.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -7,7 +6,7 @@ Matcher isSameUserAs(User targetUser) => _IsSameUserAs(targetUser: targetUser); class _IsSameUserAs extends Matcher { const _IsSameUserAs({ required this.targetUser, - }) : assert(targetUser != null, ''); + }); final User targetUser; @@ -25,13 +24,13 @@ Matcher isSameUserListAs(List targetUserList) => class _IsSameUserListAs extends Matcher { const _IsSameUserListAs({ required this.targetUserList, - }) : assert(targetUserList != null, ''); + }); final List targetUserList; @override bool matches(covariant List userList, Map matchState) { - bool matches = true; + var matches = true; for (var i = 0; i < userList.length; i++) { final user = userList[i]; final targetUser = targetUserList[i]; diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index 18e166be..b038411b 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -27,10 +27,9 @@ void main() { type: 'testType', user: users[index], createdAt: DateTime.now(), - shadowed: false, replyCount: index, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text #$index', pinned: true, pinnedAt: DateTime.now(), @@ -48,10 +47,9 @@ void main() { user: users[index], parentId: messages[0].id, createdAt: DateTime.now(), - shadowed: false, replyCount: index, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text #$index', pinned: true, pinnedAt: DateTime.now(), @@ -63,16 +61,15 @@ void main() { } testWidgets( - 'should throw if MessageListCore is used where StreamChannel is not present ' - 'in the widget tree', + '''should throw if MessageListCore is used where StreamChannel is not present in the widget tree''', (tester) async { const messageListCoreKey = Key('messageListCore'); final messageListCore = MessageListCore( key: messageListCoreKey, - messageListBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + messageListBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), ); await tester.pumpWidget(messageListCore); @@ -88,10 +85,10 @@ void main() { const messageListCoreKey = Key('messageListCore'); final messageListCore = MessageListCore( key: messageListCoreKey, - messageListBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + messageListBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), ); final mockChannel = MockChannel(); @@ -120,10 +117,10 @@ void main() { final controller = MessageListController(); final messageListCore = MessageListCore( key: messageListCoreKey, - messageListBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + messageListBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), messageListController: controller, ); @@ -150,16 +147,16 @@ void main() { ); testWidgets( - 'should assign paginateData callback and paginate data correctly if a MessageListController is passed', + '''should assign paginateData callback and paginate data correctly if a MessageListController is passed''', (tester) async { const messageListCoreKey = Key('messageListCore'); final controller = MessageListController(); final messageListCore = MessageListCore( key: messageListCoreKey, - messageListBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + messageListBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), messageListController: controller, ); @@ -207,10 +204,10 @@ void main() { const errorWidgetKey = Key('errorWidget'); final messageListCore = MessageListCore( key: messageListCoreKey, - messageListBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage( + messageListBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage( key: errorWidgetKey, ), ); @@ -249,10 +246,11 @@ void main() { const emptyWidgetKey = Key('emptyWidget'); final messageListCore = MessageListCore( key: messageListCoreKey, - messageListBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(key: emptyWidgetKey), - errorBuilder: (BuildContext context, Object error) => Offstage(), + messageListBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => + const Offstage(key: emptyWidgetKey), + errorBuilder: (BuildContext context, Object error) => const Offstage(), ); final mockChannel = MockChannel(); @@ -289,10 +287,10 @@ void main() { const listWidgetKey = Key('listWidget'); final messageListCore = MessageListCore( key: messageListCoreKey, - messageListBuilder: (_, __) => Offstage(key: listWidgetKey), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + messageListBuilder: (_, __) => const Offstage(key: listWidgetKey), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), ); final mockChannel = MockChannel(); @@ -344,9 +342,9 @@ void main() { messages.reversed.map((it) => it.id).join(','), ), ), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), ); final mockChannel = MockChannel(); @@ -391,9 +389,9 @@ void main() { messages.reversed.map((it) => '${it.parentId}-${it.id}').join(','), ), ), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), parentMessage: parentMessage, ); diff --git a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart index 4d67ebd9..979ca5b2 100644 --- a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart @@ -8,34 +8,33 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'matchers/get_message_response_matcher.dart'; import 'mocks.dart'; -final testFilter = Filter.custom(operator: '\$test', value: 'testValue'); +const testFilter = Filter.custom(operator: '\$test', value: 'testValue'); void main() { List _generateMessages({ int count = 3, int offset = 0, - }) { - return List.generate( - count, - (index) { - index = index + offset; - return GetMessageResponse() - ..message = Message( - id: 'testId$index', - text: 'testTextData$index', - ) - ..channel = ChannelModel( - cid: 'testCid:id', - ); - }, - ); - } + }) => + List.generate( + count, + (index) { + index = index + offset; + return GetMessageResponse() + ..message = Message( + id: 'testId$index', + text: 'testTextData$index', + ) + ..channel = ChannelModel( + cid: 'testCid:id', + ); + }, + ); testWidgets( 'messageSearchBlocState.search() should throw if used where ' 'StreamChat is not present in the widget tree', (tester) async { - final messageSearchBloc = MessageSearchBloc( + const messageSearchBloc = MessageSearchBloc( child: Offstage(), ); @@ -49,7 +48,7 @@ void main() { (tester) async { const messageSearchBlocKey = Key('messageSearchBloc'); const childKey = Key('child'); - final messageSearchBloc = MessageSearchBloc( + const messageSearchBloc = MessageSearchBloc( key: messageSearchBlocKey, child: Offstage(key: childKey), ); @@ -101,7 +100,7 @@ void main() { (tester) async { const messageSearchBlocKey = Key('messageSearchBloc'); const childKey = Key('child'); - final messageSearchBloc = MessageSearchBloc( + const messageSearchBloc = MessageSearchBloc( key: messageSearchBlocKey, child: Offstage(key: childKey), ); @@ -151,7 +150,7 @@ void main() { (tester) async { const messageSearchBlocKey = Key('messageSearchBloc'); const childKey = Key('child'); - final messageSearchBloc = MessageSearchBloc( + const messageSearchBloc = MessageSearchBloc( key: messageSearchBlocKey, child: Offstage(key: childKey), ); @@ -243,7 +242,7 @@ void main() { (tester) async { const messageSearchBlocKey = Key('messageSearchBloc'); const childKey = Key('child'); - final messageSearchBloc = MessageSearchBloc( + const messageSearchBloc = MessageSearchBloc( key: messageSearchBlocKey, child: Offstage(key: childKey), ); diff --git a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart index 932e35b4..6b700c76 100644 --- a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart @@ -6,7 +6,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'mocks.dart'; -final testFilter = Filter.custom(operator: '\$test', value: 'testValue'); +const testFilter = Filter.custom(operator: '\$test', value: 'testValue'); void main() { List _generateMessages({ @@ -56,10 +56,10 @@ void main() { const messageSearchListCoreKey = Key('messageSearchListCore'); final messageSearchListCore = MessageSearchListCore( key: messageSearchListCoreKey, - childBuilder: (List messages) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object? error) => Offstage(), + childBuilder: (List messages) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object? error) => const Offstage(), filters: testFilter, ); @@ -86,10 +86,10 @@ void main() { final controller = MessageSearchListController(); final messageSearchListCore = MessageSearchListCore( key: messageSearchListCoreKey, - childBuilder: (List messages) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + childBuilder: (List messages) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), messageSearchListController: controller, filters: testFilter, ); @@ -122,10 +122,10 @@ void main() { const errorWidgetKey = Key('errorWidget'); final messageSearchListCore = MessageSearchListCore( key: messageSearchListCoreKey, - childBuilder: (List messages) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage( + childBuilder: (List messages) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage( key: errorWidgetKey, ), filters: testFilter, @@ -173,10 +173,11 @@ void main() { const emptyWidgetKey = Key('emptyWidget'); final messageSearchListCore = MessageSearchListCore( key: messageSearchListCoreKey, - childBuilder: (List messages) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(key: emptyWidgetKey), - errorBuilder: (BuildContext context, Object error) => Offstage(), + childBuilder: (List messages) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => + const Offstage(key: emptyWidgetKey), + errorBuilder: (BuildContext context, Object error) => const Offstage(), filters: testFilter, ); @@ -223,12 +224,12 @@ void main() { const childWidgetKey = Key('childWidget'); final messageSearchListCore = MessageSearchListCore( key: messageSearchListCoreKey, - childBuilder: (List messages) => Offstage( + childBuilder: (List messages) => const Offstage( key: childWidgetKey, ), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), filters: testFilter, ); @@ -283,9 +284,9 @@ void main() { messages.map((e) => '${e.channel?.cid}-${e.message.id}').join(','), ), ), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), paginationParams: pagination, filters: testFilter, ); @@ -399,9 +400,10 @@ void main() { .join(','), ), ), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => + const Offstage(), paginationParams: pagination.copyWith(limit: limit), filters: testFilter, ); diff --git a/packages/stream_chat_flutter_core/test/stream_channel_test.dart b/packages/stream_chat_flutter_core/test/stream_channel_test.dart index 2b32cf2d..08763f9d 100644 --- a/packages/stream_chat_flutter_core/test/stream_channel_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_channel_test.dart @@ -27,10 +27,9 @@ void main() { type: 'testType', user: users[index], createdAt: DateTime.now(), - shadowed: false, replyCount: index, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text #$index', pinned: true, pinnedAt: DateTime.now(), @@ -48,10 +47,9 @@ void main() { user: users[index], parentId: messages[0].id, createdAt: DateTime.now(), - shadowed: false, replyCount: index, updatedAt: DateTime.now(), - extraData: {'extra_test_field': 'extraTestData'}, + extraData: const {'extra_test_field': 'extraTestData'}, text: 'Dummy text #$index', pinned: true, pinnedAt: DateTime.now(), @@ -72,7 +70,7 @@ void main() { final streamChannel = StreamChannel( key: streamChannelKey, channel: mockChannel, - child: Offstage(key: childKey), + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChannel); @@ -91,10 +89,10 @@ void main() { final streamChannel = StreamChannel( key: streamChannelKey, channel: mockChannel, - child: Offstage(key: childKey), + child: const Offstage(key: childKey), ); - final errorMessage = 'Error! Error! Error!'; + const errorMessage = 'Error! Error! Error!'; final error = DioError( type: DioErrorType.response, error: errorMessage, @@ -128,8 +126,7 @@ void main() { final streamChannel = StreamChannel( key: streamChannelKey, channel: mockChannel, - child: Offstage(key: childKey), - showLoading: true, + child: const Offstage(key: childKey), ); when(() => mockChannel.initialized).thenAnswer((_) async => false); @@ -158,8 +155,8 @@ void main() { final streamChannel = StreamChannel( key: streamChannelKey, channel: mockChannel, - child: Offstage(key: childKey), initialMessageId: 'testInitialMessageId', + child: const Offstage(key: childKey), ); when(() => mockChannel.initialized).thenAnswer((_) async => true); @@ -213,8 +210,8 @@ void main() { StreamChannel( key: streamChannelKey, channel: mockChannel, - child: Offstage(key: childKey), initialMessageId: initialMessageId, + child: const Offstage(key: childKey), ); final beforePagination = PaginationParams( diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index 178510c2..c6bb737a 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -23,7 +23,7 @@ void main() { final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -42,7 +42,7 @@ void main() { final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -63,7 +63,7 @@ void main() { final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -71,7 +71,8 @@ void main() { expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget); - when(() => mockClient.closeConnection()).thenAnswer((_) async { + // ignore: prefer_expression_function_bodies + when(mockClient.closeConnection).thenAnswer((_) async { return; }); @@ -79,9 +80,10 @@ void main() { find.byKey(streamChatCoreKey), ); + // ignore: cascade_invocations streamChatCoreState.didChangeAppLifecycleState(AppLifecycleState.paused); - verify(() => mockClient.closeConnection()).called(1); + verify(mockClient.closeConnection).called(1); }, ); @@ -93,16 +95,16 @@ void main() { await tester.runAsync(() async { final mockClient = MockClient(); final mockOnBackgroundEventReceived = MockOnBackgroundEventReceived(); - const backgroundKeepAlive = const Duration(seconds: 3); + const backgroundKeepAlive = Duration(seconds: 3); const streamChatCoreKey = Key('streamChatCore'); const childKey = Key('child'); final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), onBackgroundEventReceived: mockOnBackgroundEventReceived, backgroundKeepAlive: backgroundKeepAlive, connectivityStream: Stream.value(ConnectivityResult.mobile), + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -111,8 +113,9 @@ void main() { expect(find.byKey(childKey), findsOneWidget); final event = Event(type: EventType.any); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.closeConnection()).thenAnswer((_) async { + when(mockClient.on).thenAnswer((_) => Stream.value(event)); + // ignore: prefer_expression_function_bodies + when(mockClient.closeConnection).thenAnswer((_) async { return; }); @@ -120,6 +123,7 @@ void main() { find.byKey(streamChatCoreKey), ); + // ignore: cascade_invocations streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); @@ -129,7 +133,7 @@ void main() { await Future.delayed(backgroundKeepAlive); - verify(() => mockClient.closeConnection()).called(1); + verify(mockClient.closeConnection).called(1); verifyNever(() => mockOnBackgroundEventReceived.call(event)); }); }, @@ -143,15 +147,15 @@ void main() { await tester.runAsync(() async { final mockClient = MockClient(); final mockOnBackgroundEventReceived = MockOnBackgroundEventReceived(); - const backgroundKeepAlive = const Duration(seconds: 3); + const backgroundKeepAlive = Duration(seconds: 3); const streamChatCoreKey = Key('streamChatCore'); const childKey = Key('child'); final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), onBackgroundEventReceived: mockOnBackgroundEventReceived, backgroundKeepAlive: backgroundKeepAlive, + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -160,12 +164,13 @@ void main() { expect(find.byKey(childKey), findsOneWidget); final event = Event(type: EventType.any); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(mockClient.on).thenAnswer((_) => Stream.value(event)); final streamChatCoreState = tester.state( find.byKey(streamChatCoreKey), ); + // ignore: cascade_invocations streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); @@ -193,8 +198,8 @@ void main() { final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), connectivityStream: Stream.value(ConnectivityResult.mobile), + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -203,10 +208,14 @@ void main() { expect(find.byKey(childKey), findsOneWidget); final event = Event(type: EventType.any); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()) + when(mockClient.on).thenAnswer((_) => Stream.value(event)); + when(mockClient.openConnection) .thenAnswer((_) async => OwnUser(id: 'test')); - when(() => mockClient.closeConnection()).thenAnswer((_) async => null); + + // ignore: prefer_expression_function_bodies + when(mockClient.closeConnection).thenAnswer((_) async { + return; + }); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -214,6 +223,7 @@ void main() { find.byKey(streamChatCoreKey), ); + // ignore: cascade_invocations streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); @@ -222,7 +232,7 @@ void main() { streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.resumed); - verify(() => mockClient.openConnection()).called(1); + verify(mockClient.openConnection).called(1); }); }, ); @@ -238,18 +248,22 @@ void main() { const childKey = Key('child'); final event = Event(); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()) + when(mockClient.on).thenAnswer((_) => Stream.value(event)); + when(mockClient.openConnection) .thenAnswer((_) async => OwnUser(id: 'test')); - when(() => mockClient.closeConnection()).thenAnswer((_) async => null); + + // ignore: prefer_expression_function_bodies + when(mockClient.closeConnection).thenAnswer((_) async { + return; + }); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), connectivityStream: Stream.value(ConnectivityResult.none), + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -261,6 +275,7 @@ void main() { find.byKey(streamChatCoreKey), ); + // ignore: cascade_invocations streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); @@ -269,7 +284,7 @@ void main() { streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.resumed); - verifyNever(() => mockClient.openConnection()); + verifyNever(mockClient.openConnection); }); }, ); @@ -287,7 +302,7 @@ void main() { final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -310,9 +325,7 @@ void main() { emits(ownUser), ); - addTearDown(() { - userController.close(); - }); + addTearDown(userController.close); }); }, ); @@ -328,18 +341,22 @@ void main() { BehaviorSubject.seeded(ConnectivityResult.none); final event = Event(); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()) + when(mockClient.on).thenAnswer((_) => Stream.value(event)); + when(mockClient.openConnection) .thenAnswer((_) async => OwnUser(id: 'test')); - when(() => mockClient.closeConnection()).thenAnswer((_) async => null); + + // ignore: prefer_expression_function_bodies + when(mockClient.closeConnection).thenAnswer((_) async { + return; + }); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), connectivityStream: _connectivityController.stream, + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -351,7 +368,9 @@ void main() { await Future.delayed(const Duration(seconds: 1)); - verify(() => mockClient.openConnection()).called(1); + verify(mockClient.openConnection).called(1); + + addTearDown(_connectivityController.close); }); }, ); @@ -368,8 +387,8 @@ void main() { final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), connectivityStream: _connectivityController.stream, + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -378,10 +397,14 @@ void main() { expect(find.byKey(childKey), findsOneWidget); final event = Event(); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()) + when(mockClient.on).thenAnswer((_) => Stream.value(event)); + when(mockClient.openConnection) .thenAnswer((_) async => OwnUser(id: 'test')); - when(() => mockClient.closeConnection()).thenAnswer((_) async => null); + + // ignore: prefer_expression_function_bodies + when(mockClient.closeConnection).thenAnswer((_) async { + return; + }); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.connected); @@ -389,7 +412,9 @@ void main() { await Future.delayed(const Duration(seconds: 1)); - verify(() => mockClient.closeConnection()).called(1); + verify(mockClient.closeConnection).called(1); + + addTearDown(_connectivityController.close); }); }, ); @@ -405,18 +430,22 @@ void main() { BehaviorSubject.seeded(ConnectivityResult.none); final event = Event(); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()) + when(mockClient.on).thenAnswer((_) => Stream.value(event)); + when(mockClient.openConnection) .thenAnswer((_) async => OwnUser(id: 'test')); - when(() => mockClient.closeConnection()).thenAnswer((_) async => null); + + // ignore: prefer_expression_function_bodies + when(mockClient.closeConnection).thenAnswer((_) async { + return; + }); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), connectivityStream: _connectivityController.stream, + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -428,6 +457,7 @@ void main() { find.byKey(streamChatCoreKey), ); + // ignore: cascade_invocations streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); @@ -437,7 +467,9 @@ void main() { await Future.delayed(const Duration(seconds: 1)); - verifyNever(() => mockClient.closeConnection()); + verifyNever(mockClient.closeConnection); + + addTearDown(_connectivityController.close); }); }, ); diff --git a/packages/stream_chat_flutter_core/test/user_list_core_test.dart b/packages/stream_chat_flutter_core/test/user_list_core_test.dart index 95077e5c..eac8dc71 100644 --- a/packages/stream_chat_flutter_core/test/user_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/user_list_core_test.dart @@ -12,26 +12,24 @@ void main() { List _generateUsers({ int count = 3, int offset = 0, - }) { - return List.generate( - count, - (index) { - index = index + offset; - return User( - id: 'testId$index', - role: 'testRole$index', - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - lastActive: DateTime.now(), - online: true, - banned: false, - extraData: { - 'name': '${alphabets[index]}-testName', - }, - ); - }, - ); - } + }) => + List.generate( + count, + (index) { + index = index + offset; + return User( + id: 'testId$index', + role: 'testRole$index', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + lastActive: DateTime.now(), + online: true, + extraData: { + 'name': '${alphabets[index]}-testName', + }, + ); + }, + ); testWidgets( 'should throw if UserListCore is used where UsersBloc is not present ' @@ -40,10 +38,10 @@ void main() { const userListCoreKey = Key('userListCore'); final userListCore = UserListCore( key: userListCoreKey, - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + listBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), ); await tester.pumpWidget(userListCore); @@ -59,10 +57,10 @@ void main() { const userListCoreKey = Key('userListCore'); final userListCore = UserListCore( key: userListCoreKey, - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + listBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), ); final mockClient = MockClient(); @@ -88,10 +86,10 @@ void main() { final controller = UserListController(); final userListCore = UserListCore( key: userListCoreKey, - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + listBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), userListController: controller, ); @@ -122,9 +120,9 @@ void main() { const errorWidgetKey = Key('errorWidget'); final userListCore = UserListCore( key: userListCoreKey, - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), + listBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object error) => Container(key: errorWidgetKey), ); @@ -168,10 +166,10 @@ void main() { const emptyWidgetKey = Key('emptyWidget'); final userListCore = UserListCore( key: userListCoreKey, - listBuilder: (_, __) => Offstage(), - loadingBuilder: (BuildContext context) => Offstage(), + listBuilder: (_, __) => const Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), emptyBuilder: (BuildContext context) => Container(key: emptyWidgetKey), - errorBuilder: (BuildContext context, Object error) => Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), ); final mockClient = MockClient(); @@ -214,9 +212,9 @@ void main() { final userListCore = UserListCore( key: userListCoreKey, listBuilder: (_, __) => Container(key: listWidgetKey), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), ); final mockClient = MockClient(); @@ -261,20 +259,20 @@ void main() { listBuilder: (_, items) => Container( key: listWidgetKey, child: ListView( - children: items.map((e) { - return Container( - key: Key(e.key ?? ''), - child: e.when( - headerItem: (heading) => Text(heading), - userItem: (user) => Text(user.id), - ), - ); - }).toList(growable: false), + children: items + .map((e) => Container( + key: Key(e.key ?? ''), + child: e.when( + headerItem: (heading) => Text(heading), + userItem: (user) => Text(user.id), + ), + )) + .toList(growable: false), ), ), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), groupAlphabetically: true, ); @@ -329,20 +327,20 @@ void main() { listBuilder: (_, items) => Container( key: listWidgetKey, child: ListView( - children: items.map((e) { - return Container( - key: Key(e.key ?? ''), - child: e.when( - headerItem: (heading) => Text(heading), - userItem: (user) => Text(user.id), - ), - ); - }).toList(growable: false), + children: items + .map((e) => Container( + key: Key(e.key ?? ''), + child: e.when( + headerItem: (heading) => Text(heading), + userItem: (user) => Text(user.id), + ), + )) + .toList(growable: false), ), ), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => const Offstage(), pagination: pagination, groupAlphabetically: true, ); @@ -425,7 +423,7 @@ void main() { const pagination = PaginationParams(); StateSetter? _stateSetter; - int limit = pagination.limit; + var limit = pagination.limit; const userListCoreKey = Key('userListCore'); const listWidgetKey = Key('listWidget'); @@ -434,20 +432,21 @@ void main() { listBuilder: (_, items) => Container( key: listWidgetKey, child: ListView( - children: items.map((e) { - return Container( - key: Key(e.key ?? ''), - child: e.when( - headerItem: (heading) => Text(heading), - userItem: (user) => Text(user.id), - ), - ); - }).toList(growable: false), + children: items + .map((e) => Container( + key: Key(e.key ?? ''), + child: e.when( + headerItem: (heading) => Text(heading), + userItem: (user) => Text(user.id), + ), + )) + .toList(growable: false), ), ), - loadingBuilder: (BuildContext context) => Offstage(), - emptyBuilder: (BuildContext context) => Offstage(), - errorBuilder: (BuildContext context, Object error) => Offstage(), + loadingBuilder: (BuildContext context) => const Offstage(), + emptyBuilder: (BuildContext context) => const Offstage(), + errorBuilder: (BuildContext context, Object error) => + const Offstage(), pagination: pagination.copyWith(limit: limit), groupAlphabetically: true, ); diff --git a/packages/stream_chat_flutter_core/test/users_bloc_test.dart b/packages/stream_chat_flutter_core/test/users_bloc_test.dart index b659ca34..af088a76 100644 --- a/packages/stream_chat_flutter_core/test/users_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/users_bloc_test.dart @@ -12,30 +12,28 @@ void main() { List _generateUsers({ int count = 3, int offset = 0, - }) { - return List.generate( - count, - (index) { - index = index + offset; - return User( - id: 'testId$index', - role: 'testRole$index', - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - lastActive: DateTime.now(), - online: true, - banned: false, - extraData: {'extra_data_key': 'extraDataValue'}, - ); - }, - ); - } + }) => + List.generate( + count, + (index) { + index = index + offset; + return User( + id: 'testId$index', + role: 'testRole$index', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + lastActive: DateTime.now(), + online: true, + extraData: const {'extra_data_key': 'extraDataValue'}, + ); + }, + ); testWidgets( 'usersBlocState.queryUsers() should throw if used where ' 'StreamChat is not present in the widget tree', (tester) async { - final usersBloc = UsersBloc( + const usersBloc = UsersBloc( child: Offstage(), ); @@ -49,7 +47,7 @@ void main() { (tester) async { const usersBlocKey = Key('usersBloc'); const childKey = Key('child'); - final usersBloc = UsersBloc( + const usersBloc = UsersBloc( key: usersBlocKey, child: Offstage(key: childKey), ); @@ -98,7 +96,7 @@ void main() { (tester) async { const usersBlocKey = Key('usersBloc'); const childKey = Key('child'); - final usersBloc = UsersBloc( + const usersBloc = UsersBloc( key: usersBlocKey, child: Offstage(key: childKey), ); @@ -116,7 +114,7 @@ void main() { find.byKey(usersBlocKey), ); - final error = 'Error! Error! Error!'; + const error = 'Error! Error! Error!'; when(() => mockClient.queryUsers( filter: any(named: 'filter'), @@ -148,7 +146,7 @@ void main() { (tester) async { const usersBlocKey = Key('usersBloc'); const childKey = Key('child'); - final usersBloc = UsersBloc( + const usersBloc = UsersBloc( key: usersBlocKey, child: Offstage(key: childKey), ); @@ -231,7 +229,7 @@ void main() { (tester) async { const usersBlocKey = Key('usersBloc'); const childKey = Key('child'); - final usersBloc = UsersBloc( + const usersBloc = UsersBloc( key: usersBlocKey, child: Offstage(key: childKey), ); @@ -275,7 +273,7 @@ void main() { final offset = users.length; final pagination = PaginationParams(offset: offset); - final error = 'Error! Error! Error!'; + const error = 'Error! Error! Error!'; when(() => mockClient.queryUsers( filter: any(named: 'filter'), diff --git a/packages/stream_chat_persistence/example/lib/main.dart b/packages/stream_chat_persistence/example/lib/main.dart index f61bfed0..ab6dcd1c 100644 --- a/packages/stream_chat_persistence/example/lib/main.dart +++ b/packages/stream_chat_persistence/example/lib/main.dart @@ -3,8 +3,8 @@ import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/stream_chat_persistence.dart'; Future main() async { - /// Create a new instance of [StreamChatClient] passing the apikey obtained from your - /// project dashboard. + /// Create a new instance of [StreamChatClient] passing the apikey obtained + /// from your project dashboard. final client = StreamChatClient('b67pax5b2wdq'); WidgetsFlutterBinding.ensureInitialized(); @@ -22,12 +22,13 @@ Future main() async { await client.connectUser( User( id: 'cool-shadow-7', - extraData: { + extraData: const { 'image': 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', }, ), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.' + 'gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', ); /// Creates a channel using the type `messaging` and `godevs`. @@ -66,12 +67,10 @@ class StreamExample extends StatelessWidget { final Channel channel; @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Stream Chat Dart Example', - home: HomeScreen(channel: channel), - ); - } + Widget build(BuildContext context) => MaterialApp( + title: 'Stream Chat Dart Example', + home: HomeScreen(channel: channel), + ); } /// Main screen of our application. The layout is comprised of an [AppBar] @@ -176,83 +175,81 @@ class _MessageViewState extends State { } @override - Widget build(BuildContext context) { - return Column( - children: [ - Expanded( - child: ListView.builder( - controller: _scrollController, - itemCount: _messages.length, - reverse: true, - itemBuilder: (BuildContext context, int index) { - final item = _messages[index]; - if (item.user?.id == widget.channel.client.uid) { - return Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(item.text ?? ''), - ), - ); - } else { - return Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(item.text ?? ''), - ), - ); - } - }, + Widget build(BuildContext context) => Column( + children: [ + Expanded( + child: ListView.builder( + controller: _scrollController, + itemCount: _messages.length, + reverse: true, + itemBuilder: (BuildContext context, int index) { + final item = _messages[index]; + if (item.user?.id == widget.channel.client.uid) { + return Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text ?? ''), + ), + ); + } else { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text ?? ''), + ), + ); + } + }, + ), ), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Row( - children: [ - Expanded( - child: TextField( - controller: _controller, - decoration: const InputDecoration( - hintText: 'Enter your message', - ), - ), - ), - Material( - type: MaterialType.circle, - color: Colors.blue, - clipBehavior: Clip.hardEdge, - child: InkWell( - onTap: () async { - // We can send a new message by calling `sendMessage` on - // the current channel. After sending a message, the - // TextField is cleared and the list view is scrolled - // to show the new item. - if (_controller.value.text.isNotEmpty) { - await widget.channel.sendMessage( - Message(text: _controller.value.text), - ); - _controller.clear(); - _updateList(); - } - }, - child: const Padding( - padding: EdgeInsets.all(8.0), - child: Center( - child: Icon( - Icons.send, - color: Colors.white, - ), + Padding( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _controller, + decoration: const InputDecoration( + hintText: 'Enter your message', ), ), ), - ) - ], - ), - ) - ], - ); - } + Material( + type: MaterialType.circle, + color: Colors.blue, + clipBehavior: Clip.hardEdge, + child: InkWell( + onTap: () async { + // We can send a new message by calling `sendMessage` on + // the current channel. After sending a message, the + // TextField is cleared and the list view is scrolled + // to show the new item. + if (_controller.value.text.isNotEmpty) { + await widget.channel.sendMessage( + Message(text: _controller.value.text), + ); + _controller.clear(); + _updateList(); + } + }, + child: const Padding( + padding: EdgeInsets.all(8), + child: Center( + child: Icon( + Icons.send, + color: Colors.white, + ), + ), + ), + ), + ) + ], + ), + ) + ], + ); } /// Helper extension for quickly retrieving diff --git a/packages/stream_chat_persistence/example/pubspec.yaml b/packages/stream_chat_persistence/example/pubspec.yaml index e6c30c94..8dd6a719 100644 --- a/packages/stream_chat_persistence/example/pubspec.yaml +++ b/packages/stream_chat_persistence/example/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: ">=2.12.0 <3.0.0" dependencies: + cupertino_icons: ^1.0.3 flutter: sdk: flutter - cupertino_icons: ^1.0.0 stream_chat_persistence: path: ../ 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 a69a07ee..62a3df15 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 @@ -213,8 +213,8 @@ void main() { test('should return sorted channels using custom field', () async { int sortComparator(ChannelModel a, ChannelModel b) { - final aData = a.extraData['test_custom_field'] as int; - final bData = b.extraData['test_custom_field'] as int; + final aData = int.parse(a.extraData['test_custom_field'].toString()); + final bData = int.parse(b.extraData['test_custom_field'].toString()); return bData.compareTo(aData); } diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart index 2c852ff2..7d867deb 100644 --- a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -173,8 +173,8 @@ void main() { expect(entity.shadowed, message.shadowed); expect(entity.showInChannel, message.showInChannel); expect(entity.replyCount, message.replyCount); - expect(entity.mentionedUsers, - message.mentionedUsers.map((e) => jsonEncode(e)).toList()); + expect( + entity.mentionedUsers, message.mentionedUsers.map(jsonEncode).toList()); expect(entity.reactionScores, message.reactionScores); expect(entity.reactionCounts, message.reactionCounts); expect(entity.status, message.status); diff --git a/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart index adc05c58..4454ad73 100644 --- a/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart @@ -39,7 +39,7 @@ void main() { lastActive: DateTime.now(), online: math.Random().nextBool(), banned: math.Random().nextBool(), - extraData: {'test_extra_data': 'extraData'}, + extraData: const {'test_extra_data': 'extraData'}, ); final entity = user.toEntity(); expect(entity, isA()); From 2edbfca152eb0baa79deb4956e8229e84252aa24 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 14:31:11 +0530 Subject: [PATCH 558/599] feat: Added additional guide --- .../docs/Flutter/basics/choose_package.mdx | 54 +++++++++++++++++++ .../stream_chat_flutter/user_list_view.mdx | 6 +-- .../user_list_core.mdx | 13 +++-- 3 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 docusaurus/docs/Flutter/basics/choose_package.mdx diff --git a/docusaurus/docs/Flutter/basics/choose_package.mdx b/docusaurus/docs/Flutter/basics/choose_package.mdx new file mode 100644 index 00000000..7a4961c5 --- /dev/null +++ b/docusaurus/docs/Flutter/basics/choose_package.mdx @@ -0,0 +1,54 @@ +--- +id: choose_package +sidebar_position: 2 +title: Choosing The Right Flutter Package +--- + +### Why the SDK is split into different packages + +Different applications need different levels of customization and integration with the Stream Chat SDK. +To do this, the Flutter SDK is split into three different packages which build upon the last and give +varying levels of control to the developer. The higher level packages offer better compatibility out of the +box while the lower level SDKs offer fine grained control. There is also a separate package for persistence +which allows you persist data locally which works with all packages. + +### How do I choose? + +#### The case for stream_chat_flutter + +For the quickest way to integrate Stream Chat with your app, the UI SDK (`stream_chat_flutter`) is the +way to go. `stream_chat_flutter` contains prebuilt components that manage most operations like data +fetching, pagination, sending a message, and more. This ensures you have a nearly out-of-the-box +experience adding chat to your applications. It is also possible to use this in conjunction with +lower level operations of the SDK to get the best of both worlds. + +:::note +The package allows customization of components to a large extent making it easy to tweak the theme +to match your app colors and such. If you require any additional feature or customization, feel free +to request this through our support channels. +::: + +:::summary +For the quickest and easiest way to add Chat to your app with prebuilt UI components, use stream_chat_flutter +::: + +#### The case for stream_chat_flutter_core + +If your application involves UI that does not fit in with the stream_chat_flutter components, stream_chat_flutter_core +strips away the UI associated with the components and provides the data fetching and manipulation +capabilities while supplying builders for UI. This allows you to implement your own UI and themes +completely independently while not worrying about writing functions for data and pagination. + +:::summary +For implementing your own custom UI while not having to worry about lower level API calls, use stream_chat_flutter_core. +::: + +#### The case for stream_chat + +The stream_chat package is the Low-level Client (LLC) of Stream Chat in Flutter. This package wraps +the underlying functionality of Stream Chat and allows the most customization in terms of UI, data, +and architecture. + +:::summary +For the most control over the SDK and dealing with low level calls to the API, use stream_chat. +::: \ No newline at end of file diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx index 5cc1ae40..61dfbeb4 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx @@ -27,11 +27,7 @@ class UsersListPage extends StatelessWidget { return Scaffold( body: UsersBloc( child: UsersListView( - filter: Filter.and([ - Filter.autoComplete('name', 'search_here'), - Filter.notEqual( - 'id', StreamChat.of(context).user!.id), - ]), + filter: Filter.notEqual('id', StreamChat.of(context).user!.id), sort: [ SortOption( 'name', diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx index 16e39339..622d0025 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/user_list_core.mdx @@ -18,9 +18,9 @@ exposes builders to build the UI in situations such as loading, empty data, erro ### Basic Example -[UserListCore] is a simplified class that allows fetching users while +`UserListCore` is a simplified class that allows fetching users while exposing UI builders. -A [UserListController] is used to load and paginate data. +A `UserListController` is used to load and paginate data. ```dart class UsersListPage extends StatelessWidget { @@ -28,7 +28,6 @@ class UsersListPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: UsersListCore( - filter: Filter.autoComplete('name', 'search_here'), sort: [SortOption('last_active')], pagination: PaginationParams( limit: 20, @@ -57,9 +56,9 @@ class UsersListPage extends StatelessWidget { } ``` -[UsersBloc] must be the ancestor of this widget. This is necessary since -[UserListCore] depends on functionality contained within [UsersBloc]. +`UsersBloc` must be the ancestor of this widget. This is necessary since +`UserListCore` depends on functionality contained within `UsersBloc`. -The parameters [listBuilder], [loadingBuilder], [emptyBuilder] and -[errorBuilder] must all be supplied and not null. +The parameters `listBuilder`, `loadingBuilder`, `emptyBuilder` and +`errorBuilder` must all be supplied and not null. From 4533828fe83d1dd48b98b78c2928a4e5f4f49b47 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 16 Jul 2021 14:42:22 +0530 Subject: [PATCH 559/599] chore: fix analyzer issues Signed-off-by: xsahil03x --- packages/stream_chat_flutter/lib/src/message_input.dart | 6 +++--- packages/stream_chat_flutter/lib/stream_chat_flutter.dart | 2 +- packages/stream_chat_flutter/pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 9dca6c6b..5a4f6822 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1859,11 +1859,11 @@ class MessageInputState extends State { } if (camera) { - PickedFile? pickedFile; + XFile? pickedFile; if (fileType == DefaultAttachmentTypes.image) { - pickedFile = await _imagePicker.getImage(source: ImageSource.camera); + pickedFile = await _imagePicker.pickImage(source: ImageSource.camera); } else if (fileType == DefaultAttachmentTypes.video) { - pickedFile = await _imagePicker.getVideo(source: ImageSource.camera); + pickedFile = await _imagePicker.pickVideo(source: ImageSource.camera); } if (pickedFile == null) { return; diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 2b2485c4..39aabb85 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -2,8 +2,8 @@ export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; export 'src/attachment/attachment.dart'; export 'src/back_button.dart'; -export 'src/channel_header.dart'; export 'src/channel_avatar.dart'; +export 'src/channel_header.dart'; export 'src/channel_list_header.dart'; export 'src/channel_list_view.dart'; export 'src/channel_name.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 70a02f56..ce7ba0e5 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: flutter_svg: ^0.22.0 http_parser: ^4.0.0 image_gallery_saver: ^1.6.9 - image_picker: ^0.8.0 + image_picker: ^0.8.2 jiffy: ^4.1.0 lottie: ^1.0.1 meta: ^1.3.0 From cb0f47e02c3ee4fdd5f56bcb12b97b906d802797 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 16 Jul 2021 14:48:07 +0530 Subject: [PATCH 560/599] fix: tutorial_part_3.dart Signed-off-by: xsahil03x --- packages/stream_chat_flutter/example/lib/tutorial_part_3.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart index 99e9ceab..78081c85 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart @@ -114,7 +114,7 @@ class ChannelListPage extends StatelessWidget { ), ); }, - leading: ChannelImage( + leading: ChannelAvatar( channel: channel, ), title: ChannelName( From 9a3d094282d07f7ee2cd252dca90aab8147d0fca Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 16 Jul 2021 14:51:32 +0530 Subject: [PATCH 561/599] chore: fix analyzer warnings Signed-off-by: xsahil03x --- .../test/src/channel_image_test.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel_image_test.dart index 5eea5569..7c620774 100644 --- a/packages/stream_chat_flutter/test/src/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_image_test.dart @@ -74,7 +74,7 @@ void main() { userId: 'user-id2', user: User( id: 'user-id2', - extraData: { + extraData: const { 'image': 'testimage', }, ), @@ -85,7 +85,7 @@ void main() { userId: 'user-id2', user: User( id: 'user-id2', - extraData: { + extraData: const { 'image': 'testimage', }, ), @@ -98,7 +98,7 @@ void main() { when(() => clientState.usersStream).thenAnswer((i) => Stream.value({ 'user-id2': User( id: 'user-id2', - extraData: { + extraData: const { 'image': 'testimage', }, ), @@ -149,7 +149,7 @@ void main() { userId: 'user-id', user: User( id: 'user-id', - extraData: { + extraData: const { 'image': 'testimage1', }, ), @@ -158,7 +158,7 @@ void main() { userId: 'user-id2', user: User( id: 'user-id2', - extraData: { + extraData: const { 'image': 'testimage2', }, ), @@ -167,7 +167,7 @@ void main() { userId: 'user-id3', user: User( id: 'user-id3', - extraData: { + extraData: const { 'image': 'testimage3', }, ), From 35b969994e13d22e818970bf0dcda683f58e6104 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 12:15:16 +0200 Subject: [PATCH 562/599] update pubspecs --- packages/stream_chat/pubspec.yaml | 2 +- packages/stream_chat_flutter/pubspec.yaml | 4 ++-- packages/stream_chat_flutter_core/pubspec.yaml | 4 ++-- packages/stream_chat_persistence/pubspec.yaml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 2e1dbc88..1f145992 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat homepage: https://getstream.io/ description: The official Dart client for Stream Chat, a service for building chat applications. -version: 2.0.0-nullsafety.8 +version: 2.0.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index ce7ba0e5..590ed055 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.9 +version: 2.0.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -36,7 +36,7 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 shimmer: ^2.0.0 - stream_chat_flutter_core: ^2.0.0-nullsafety.9 + stream_chat_flutter_core: ^2.0.0 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 01b42243..cc59f9fe 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.9 +version: 2.0.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -16,7 +16,7 @@ dependencies: sdk: flutter meta: ^1.3.0 rxdart: ^0.27.0 - stream_chat: ^2.0.0-nullsafety.8 + stream_chat: ^2.0.0 dev_dependencies: fake_async: ^1.2.0 diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 64612e8b..3ad9b2e0 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: path: ^1.8.0 path_provider: ^2.0.1 sqlite3_flutter_libs: ^0.5.0 - stream_chat: ^2.0.0-nullsafety.8 + stream_chat: ^2.0.0 dev_dependencies: build_runner: ^2.0.1 From 1a55ae57e49135bc09b60aa3660e9ee1ca12a8d4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 12:15:31 +0200 Subject: [PATCH 563/599] update stream_chat changelog --- packages/stream_chat/CHANGELOG.md | 50 ++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 5e31e81e..24cccf6e 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,42 @@ +## 2.0.0 + +đŸ›‘ī¸ Breaking Changes from `1.5.3` + +- `ConnectUserWithProvider` now requires `tokenProvider` as a required param. (Removed from the constructor) +- `client.disconnect()` is now divided into two different functions + - `client.closeConnection()` -> for closing user websocket connection. + - `client.disconnectUser()` -> for disconnecting user and resetting client state. +- `client.devToken()` now returns a `Token` model instead of `String`. +- `ApiError` is removed in favor of `StreamChatError` + - `StreamChatError` -> parent type for all the stream errors. + - `StreamWebSocketError` -> for user websocket related errors. + - `StreamChatNetworkError` -> for network related errors. +- `client.queryChannels()`, `channel.query()` options param is removed in favor of individual params + - `option.state` -> bool state + - `option.watch` -> bool watch + - `option.presence` -> bool presence +- `client.queryUsers()` options param is removed in favor of individual params + - `option.presence` -> bool presence +- Migrate this package to null safety +- Added typed filters + +🐞 Fixed + +- [#369](https://github.com/GetStream/stream-chat-flutter/issues/369): Client does not return without internet connection +- several minor fixes + + +✅ Added + +- New `Location` enum is introduced for easily changing the client location/baseUrl. +- New `client.openConnection()` and `client.closeConnection()` is introduced to connect/disconnect user ws connection. +- New `client.partialUpdateMessage` and `channel.partialUpdateMessage` methods +- `connectWebSocket` parameter in connect user calls to use the client in "connection-less" mode. + +🔄 Changed + +- `baseURL` is now deprecated in favor of using `Location` to change data location. + ## 2.0.0-nullsafety.8 🐞 Fixed @@ -32,21 +71,10 @@ - `baseURL` is now deprecated in favor of using `Location` to change data location. -🐞 Fixed - -- [#369](https://github.com/GetStream/stream-chat-flutter/issues/369): Client does not return without internet - connection - ## 2.0.0-nullsafety.6 - Fix thread reply not working with attachments - Minor fixes - -## 2.0.0-nullsafety.6 - -- Fix thread reply not working with attachments -- Minor fixes - ## 2.0.0-nullsafety.5 - Minor fixes From 0a491eed7dddc1ec158bec9a1d7bf914d620238c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 12:16:15 +0200 Subject: [PATCH 564/599] update stream_chat_persistence changelog --- packages/stream_chat_persistence/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index 64923c2c..3a65db58 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.0 +* Migrate this package to null safety +* Minor fixes and improvements + ## 2.0.0-nullsafety.8 * Updated llc dependency * Upgraded moor dependencies and generated files with the latest dependency From 26dea38e339d189ebfe230308b57ac6db6302b31 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 12:20:59 +0200 Subject: [PATCH 565/599] update stream_chat_flutter_core changelog --- packages/stream_chat/CHANGELOG.md | 2 ++ .../stream_chat_flutter_core/CHANGELOG.md | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 24cccf6e..e05599e1 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -2,6 +2,7 @@ đŸ›‘ī¸ Breaking Changes from `1.5.3` +- migrate this package to null safety - `ConnectUserWithProvider` now requires `tokenProvider` as a required param. (Removed from the constructor) - `client.disconnect()` is now divided into two different functions - `client.closeConnection()` -> for closing user websocket connection. @@ -24,6 +25,7 @@ - [#369](https://github.com/GetStream/stream-chat-flutter/issues/369): Client does not return without internet connection - several minor fixes +- performance improvements ✅ Added diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 8b4b7fae..2f7abfe2 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,24 @@ +## 2.0.0 + +đŸ›‘ī¸ Breaking Changes from `1.5.3` + +- migrate this package to null safety +- `channelsBloc.queryChannels()`, `ChannelListCore` options param/property is removed in favor of individual params/properties + - `options.state` -> bool state + - `options.watch` -> bool watch + - `options.presence` -> bool presence +- `usersBloc.queryUsers()`, `UserListCore` options param/property is removed in favor of individual params/properties + - `options.presence` -> bool presence + +✅ Added + +- Monitor connection using `connectivity_plus` package + +🐞 Fixed + +- Minor fixes +- Performance improvements + ## 2.0.0-nullsafety.9 - Update llc dependency From 16c31231bc0b9318c3e3b1c4301d340e7fd52c30 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 12:26:36 +0200 Subject: [PATCH 566/599] update stream_chat_flutter changelog --- packages/stream_chat_flutter/CHANGELOG.md | 60 +++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index ad8beb25..060c30ef 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,63 @@ +## 2.0.0 + +đŸ›‘ī¸ Breaking Changes from `1.5.4` + +- Migrate this package to null safety +- Updated `StreamChatThemeData.reactionIcons` to accept custom builder +- Renamed `ColorTheme` properties to reflect the purpose of the colors + - `ColorTheme.black` -> `ColorTheme.textHighEmphasis` + - `ColorTheme.grey` -> `ColorTheme.textLowEmphasis` + - `ColorTheme.greyGainsboro` -> `ColorTheme.disabled` + - `ColorTheme.greyWhisper` -> `ColorTheme.borders` + - `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg` + - `ColorTheme.whiteSnow` -> `ColorTheme.appBg` + - `ColorTheme.white` -> `ColorTheme.barsBg` + - `ColorTheme.blueAlice` -> `ColorTheme.linkBg` + - `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary` + - `ColorTheme.accentRed` -> `ColorTheme.accentError` + - `ColorTheme.accentGreen` -> `ColorTheme.accentInfo` + +- `ChannelListCore` options property is removed in favor of individual properties + - `options.state` -> bool state + - `options.watch` -> bool watch + - `options.presence` -> bool presence +- `UserListView` options property is removed in favor of individual properties + - `options.presence` -> bool presence +- `MessageBuilder` and `ParentMessageBuilder` signature is now + +```dart +typedef MessageBuilder = Widget Function( + BuildContext, + MessageDetails, + List, + MessageWidget defaultMessageWidget, + ); +``` + +the last parameter is the default `MessageWidget` +You can call `.copyWith` to customize just a subset of properties + + +✅ Added + +- Added video compress options (frame and quality) to `MessageInput` +- TypingIndicator now has a property called `parentId` to show typing indicator specific to threads +- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView header/footer +- `MessageWidget` accepts a `userAvatarBuilder` +- Added pinMessage ui support +- Added `MessageListView.threadSeparatorBuilder` property +- Added `MessageInput.onError` property to allow error handling + +🐞 Fixed + +- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing + message +- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case +- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload +- `MessageListView` not rendering if the user is not a member of the channel +- Fix `MessageInput` overflow when there are no actions +- Minor fixes and improvements + ## 2.0.0-nullsafety.9 đŸ›‘ī¸ Breaking Changes from `2.0.0-nullsafety.8` From 1d79f9aac1c88dfcdec93398dabcadafc5f77d75 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 12:44:11 +0200 Subject: [PATCH 567/599] update stream_chat_flutter changelog --- packages/stream_chat_flutter/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 060c30ef..e606d32d 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -3,6 +3,7 @@ đŸ›‘ī¸ Breaking Changes from `1.5.4` - Migrate this package to null safety +- Renamed `ChannelImage` to `ChannelAvatar` - Updated `StreamChatThemeData.reactionIcons` to accept custom builder - Renamed `ColorTheme` properties to reflect the purpose of the colors - `ColorTheme.black` -> `ColorTheme.textHighEmphasis` @@ -23,6 +24,8 @@ - `options.presence` -> bool presence - `UserListView` options property is removed in favor of individual properties - `options.presence` -> bool presence +- Renamed `ImageHeader` to `GalleryHeader` +- Renamed `ImageFooter` to `GalleryFooter` - `MessageBuilder` and `ParentMessageBuilder` signature is now ```dart @@ -47,6 +50,7 @@ You can call `.copyWith` to customize just a subset of properties - Added pinMessage ui support - Added `MessageListView.threadSeparatorBuilder` property - Added `MessageInput.onError` property to allow error handling +- Added `GalleryHeader/GalleryFooter` theme classes 🐞 Fixed From 0e9ec8600a55edd5f9a96adbf41e10ce9c585c63 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 13:11:53 +0200 Subject: [PATCH 568/599] update version.dart --- packages/stream_chat/lib/version.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index 5f0f71c1..d09a2ccc 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header // ignore: constant_identifier_names -const PACKAGE_VERSION = '2.0.0-nullsafety.8'; +const PACKAGE_VERSION = '2.0.0'; From e0153b4852151f10b1ec48dff1878ed1262c3155 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 13:15:50 +0200 Subject: [PATCH 569/599] fix analyzer --- .../test/stream_chat_core_test.dart | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index eaa17117..ee9a86cb 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -296,15 +296,15 @@ void main() { await tester.runAsync(() async { final mockClient = MockClient(); final mockOnBackgroundEventReceived = MockOnBackgroundEventReceived(); - const backgroundKeepAlive = const Duration(seconds: 3); + const backgroundKeepAlive = Duration(seconds: 3); const streamChatCoreKey = Key('streamChatCore'); const childKey = Key('child'); final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), onBackgroundEventReceived: mockOnBackgroundEventReceived, backgroundKeepAlive: backgroundKeepAlive, + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -313,12 +313,13 @@ void main() { expect(find.byKey(childKey), findsOneWidget); final event = Event(type: EventType.any); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(mockClient.on).thenAnswer((_) => Stream.value(event)); final streamChatCoreState = tester.state( find.byKey(streamChatCoreKey), ); + // ignore: cascade_invocations streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); @@ -346,8 +347,8 @@ void main() { final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), connectivityStream: Stream.value(ConnectivityResult.mobile), + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -356,10 +357,10 @@ void main() { expect(find.byKey(childKey), findsOneWidget); final event = Event(type: EventType.any); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()) + when(mockClient.on).thenAnswer((_) => Stream.value(event)); + when(mockClient.openConnection) .thenAnswer((_) async => OwnUser(id: 'test')); - when(() => mockClient.closeConnection()).thenAnswer((_) async => null); + when(mockClient.closeConnection).thenAnswer((_) async {}); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -367,6 +368,7 @@ void main() { find.byKey(streamChatCoreKey), ); + // ignore: cascade_invocations streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); @@ -375,7 +377,7 @@ void main() { streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.inactive); - verify(() => mockClient.openConnection()).called(1); + verify(mockClient.openConnection).called(1); }); }, ); @@ -391,18 +393,18 @@ void main() { const childKey = Key('child'); final event = Event(); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()) + when(mockClient.on).thenAnswer((_) => Stream.value(event)); + when(mockClient.openConnection) .thenAnswer((_) async => OwnUser(id: 'test')); - when(() => mockClient.closeConnection()).thenAnswer((_) async => null); + when(mockClient.closeConnection).thenAnswer((_) async {}); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), connectivityStream: Stream.value(ConnectivityResult.none), + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -414,6 +416,7 @@ void main() { find.byKey(streamChatCoreKey), ); + // ignore: cascade_invocations streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); @@ -422,7 +425,7 @@ void main() { streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.inactive); - verifyNever(() => mockClient.openConnection()); + verifyNever(mockClient.openConnection); }); }, ); From 9a2ec7ca84324aee3090c3d5ffe519ba9324548a Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 16 Jul 2021 18:32:38 +0530 Subject: [PATCH 570/599] fix: sync events only in case persistence is enabled Signed-off-by: xsahil03x --- packages/stream_chat/lib/src/client/client.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index d9877a4b..050d36f1 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -426,7 +426,10 @@ class StreamChatClient { await queryChannelsOnline( filter: Filter.in_('cid', cids), paginationParams: const PaginationParams(limit: 30), - ).then((_) => sync(cids: cids, lastSyncAt: _lastSyncedAt)); + ); + if (persistenceEnabled) { + await sync(cids: cids, lastSyncAt: _lastSyncedAt); + } } handleEvent(Event( type: EventType.connectionRecovered, From 5ae99333219a95c5c24216dd4cfcaa54e1add535 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 18:47:02 +0530 Subject: [PATCH 571/599] feat: Added mig guide (scf) --- .../guides/adding_custom_attachments.mdx | 2 +- .../Flutter/guides/migration_guide_2_0.mdx | 146 ++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx diff --git a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx index fb1f7bbe..0c427713 100644 --- a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx +++ b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx @@ -1,6 +1,6 @@ --- id: adding_custom_attachments -sidebar_position: 3 +sidebar_position: 4 title: Adding Custom Attachments --- diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx new file mode 100644 index 00000000..58e4b828 --- /dev/null +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -0,0 +1,146 @@ +--- +id: mig_guide_2_0 +sidebar_position: 5 +title: Migrating to 2.0 (Null-safety) +--- + +A Migration Guide For Switching To v2.0 Of The Flutter SDK + +### Overview + +v2.0 of the Stream Chat Flutter SDK brings along several changes - primarily making the SDK null-safe. +Null safety allows your apps to run faster, with fewer errors, and with less code. + +Check [this link](https://flutter.dev/docs/null-safety) for more about Null Safety in Flutter. + +This guide is intended to enumerate and better explain the changes in the SDK. + +The changes will be listed by package and a concise changelog will be followed by more info. + +### Changelog of `stream_chat_flutter` + +#### Breaking Changes from 1.5.4 + +*Migrate this package to null safety + +*Renamed ChannelImage to ChannelAvatar + +*Updated StreamChatThemeData.reactionIcons to accept custom builder + +*Renamed ColorTheme properties to reflect the purpose of the colors + + *ColorTheme.black -> ColorTheme.textHighEmphasis + *ColorTheme.grey -> ColorTheme.textLowEmphasis + *ColorTheme.greyGainsboro -> ColorTheme.disabled + *ColorTheme.greyWhisper -> ColorTheme.borders + *ColorTheme.whiteSmoke -> ColorTheme.inputBg + *ColorTheme.whiteSnow -> ColorTheme.appBg + *ColorTheme.white -> ColorTheme.barsBg + *ColorTheme.blueAlice -> ColorTheme.linkBg + *ColorTheme.accentBlue -> ColorTheme.accentPrimary + *ColorTheme.accentRed -> ColorTheme.accentError + *ColorTheme.accentGreen -> ColorTheme.accentInfo + +*ChannelListCore options property is removed in favor of individual properties + + *options.state -> bool state + *options.watch -> bool watch + *options.presence -> bool presence + +*UserListView options property is removed in favor of individual properties + + *options.presence -> bool presence + +*Renamed ImageHeader to GalleryHeader + +*Renamed ImageFooter to GalleryFooter + +*MessageBuilder and ParentMessageBuilder signature is now + +``` +typedef MessageBuilder = Widget Function( + BuildContext, + MessageDetails, + List, + MessageWidget defaultMessageWidget, + ); +``` + +The last parameter is the default MessageWidget +You can call .copyWith to customize just a subset of properties + +#### ✅ Added + +Added video compress options (frame and quality) to MessageInput +TypingIndicator now has a property called parentId to show typing indicator specific to threads +#493: add support for messageListView header/footer +MessageWidget accepts a userAvatarBuilder +Added pinMessage ui support +Added MessageListView.threadSeparatorBuilder property +Added MessageInput.onError property to allow error handling +Added GalleryHeader/GalleryFooter theme classes + +#### 🐞 Fixed + +#483: Keyboard covers input text box when editing +message +Modals are shown using the nearest Navigator to make using the SDK easier in a nested navigator use case +#484: messages don't update without a reload +MessageListView not rendering if the user is not a member of the channel +Fix MessageInput overflow when there are no actions +Minor fixes and improvements + +### Migrating to 2.0 for `stream_chat_flutter` + +:::note +If you are migrating your full Flutter project to null-safety, first make sure you follow the +instructions from the [official Null Safety migration guide](https://dart.dev/null-safety/migration-guide). +::: + +To migrate to v2.0 for `stream_chat_flutter`, first change the version of the package to the latest +null-safe version. + +```yaml +dependencies: + stream_chat_flutter: ^2.0.0 +``` + +Upon doing this, all breaking changes from the package will take immediate effect. Here are steps to +remedy the issue: + +1) Replace the offending class names with the revised class names + + * ChannelImage -> ChannelAvatar + * ImageHeader -> GalleryHeader + * ImageFooter -> GalleryFooter + +2) The new version comes with revised color names since the previous names do not suit light/dark mode +nomenclature. Make sure any old colors used from theme are changed over to the new theme color names: + + *ColorTheme.black -> ColorTheme.textHighEmphasis + *ColorTheme.grey -> ColorTheme.textLowEmphasis + *ColorTheme.greyGainsboro -> ColorTheme.disabled + *ColorTheme.greyWhisper -> ColorTheme.borders + *ColorTheme.whiteSmoke -> ColorTheme.inputBg + *ColorTheme.whiteSnow -> ColorTheme.appBg + *ColorTheme.white -> ColorTheme.barsBg + *ColorTheme.blueAlice -> ColorTheme.linkBg + *ColorTheme.accentBlue -> ColorTheme.accentPrimary + *ColorTheme.accentRed -> ColorTheme.accentError + *ColorTheme.accentGreen -> ColorTheme.accentInfo + +3) We decided to make messages easier to customize and now supply the default implementation of the +messages in the builder - so you can now customize a single parameter without having to redo the +entire implementation. Please reform your builders to take into account the new format: + +``` +typedef MessageBuilder = Widget Function( + BuildContext, + MessageDetails, + List, + MessageWidget defaultMessageWidget, + ); +``` + +Use `defaultMessageWidget.copyWith()` to edit any properties to make it much simpler to tweak the +default implementation. \ No newline at end of file From 9bf0aac3fa80d93539873ca2153aad6f2c50deeb Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 19:12:30 +0530 Subject: [PATCH 572/599] feat: Added mig guide (scfc) --- .../Flutter/guides/migration_guide_2_0.mdx | 167 ++++++++++++++---- 1 file changed, 130 insertions(+), 37 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 58e4b828..b71c48b3 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -19,43 +19,43 @@ The changes will be listed by package and a concise changelog will be followed b ### Changelog of `stream_chat_flutter` -#### Breaking Changes from 1.5.4 +#### đŸ›‘ī¸ Breaking Changes from 1.5.4 -*Migrate this package to null safety +* Migrate this package to null safety -*Renamed ChannelImage to ChannelAvatar +* Renamed ChannelImage to ChannelAvatar -*Updated StreamChatThemeData.reactionIcons to accept custom builder +* Updated StreamChatThemeData.reactionIcons to accept custom builder -*Renamed ColorTheme properties to reflect the purpose of the colors +* Renamed ColorTheme properties to reflect the purpose of the colors - *ColorTheme.black -> ColorTheme.textHighEmphasis - *ColorTheme.grey -> ColorTheme.textLowEmphasis - *ColorTheme.greyGainsboro -> ColorTheme.disabled - *ColorTheme.greyWhisper -> ColorTheme.borders - *ColorTheme.whiteSmoke -> ColorTheme.inputBg - *ColorTheme.whiteSnow -> ColorTheme.appBg - *ColorTheme.white -> ColorTheme.barsBg - *ColorTheme.blueAlice -> ColorTheme.linkBg - *ColorTheme.accentBlue -> ColorTheme.accentPrimary - *ColorTheme.accentRed -> ColorTheme.accentError - *ColorTheme.accentGreen -> ColorTheme.accentInfo + * ColorTheme.black -> ColorTheme.textHighEmphasis + * ColorTheme.grey -> ColorTheme.textLowEmphasis + * ColorTheme.greyGainsboro -> ColorTheme.disabled + * ColorTheme.greyWhisper -> ColorTheme.borders + * ColorTheme.whiteSmoke -> ColorTheme.inputBg + * ColorTheme.whiteSnow -> ColorTheme.appBg + * ColorTheme.white -> ColorTheme.barsBg + * ColorTheme.blueAlice -> ColorTheme.linkBg + * ColorTheme.accentBlue -> ColorTheme.accentPrimary + * ColorTheme.accentRed -> ColorTheme.accentError + * ColorTheme.accentGreen -> ColorTheme.accentInfo -*ChannelListCore options property is removed in favor of individual properties +* ChannelListCore options property is removed in favor of individual properties - *options.state -> bool state - *options.watch -> bool watch - *options.presence -> bool presence + *options.state -> bool state + *options.watch -> bool watch + *options.presence -> bool presence -*UserListView options property is removed in favor of individual properties +* UserListView options property is removed in favor of individual properties - *options.presence -> bool presence + *options.presence -> bool presence -*Renamed ImageHeader to GalleryHeader +* Renamed ImageHeader to GalleryHeader -*Renamed ImageFooter to GalleryFooter +* Renamed ImageFooter to GalleryFooter -*MessageBuilder and ParentMessageBuilder signature is now +* MessageBuilder and ParentMessageBuilder signature is now ``` typedef MessageBuilder = Widget Function( @@ -117,17 +117,17 @@ remedy the issue: 2) The new version comes with revised color names since the previous names do not suit light/dark mode nomenclature. Make sure any old colors used from theme are changed over to the new theme color names: - *ColorTheme.black -> ColorTheme.textHighEmphasis - *ColorTheme.grey -> ColorTheme.textLowEmphasis - *ColorTheme.greyGainsboro -> ColorTheme.disabled - *ColorTheme.greyWhisper -> ColorTheme.borders - *ColorTheme.whiteSmoke -> ColorTheme.inputBg - *ColorTheme.whiteSnow -> ColorTheme.appBg - *ColorTheme.white -> ColorTheme.barsBg - *ColorTheme.blueAlice -> ColorTheme.linkBg - *ColorTheme.accentBlue -> ColorTheme.accentPrimary - *ColorTheme.accentRed -> ColorTheme.accentError - *ColorTheme.accentGreen -> ColorTheme.accentInfo + * ColorTheme.black -> ColorTheme.textHighEmphasis + * ColorTheme.grey -> ColorTheme.textLowEmphasis + * ColorTheme.greyGainsboro -> ColorTheme.disabled + * ColorTheme.greyWhisper -> ColorTheme.borders + * ColorTheme.whiteSmoke -> ColorTheme.inputBg + * ColorTheme.whiteSnow -> ColorTheme.appBg + * ColorTheme.white -> ColorTheme.barsBg + * ColorTheme.blueAlice -> ColorTheme.linkBg + * ColorTheme.accentBlue -> ColorTheme.accentPrimary + * ColorTheme.accentRed -> ColorTheme.accentError + * ColorTheme.accentGreen -> ColorTheme.accentInfo 3) We decided to make messages easier to customize and now supply the default implementation of the messages in the builder - so you can now customize a single parameter without having to redo the @@ -143,4 +143,97 @@ typedef MessageBuilder = Widget Function( ``` Use `defaultMessageWidget.copyWith()` to edit any properties to make it much simpler to tweak the -default implementation. \ No newline at end of file +default implementation. + +### Changelog of `stream_chat_flutter_core` + +#### đŸ›‘ī¸ Breaking Changes from 1.5.3 + +* Migrate this package to null safety +* channelsBloc.queryChannels(), ChannelListCore options param/property is removed in favor of individual params/properties + * options.state -> bool state + * options.watch -> bool watch + * options.presence -> bool presence +* usersBloc.queryUsers(), UserListCore options param/property is removed in favor of individual params/properties + * options.presence -> bool presence + +#### ✅ Added + +* Monitor connection using connectivity_plus package + +#### 🐞 Fixed + +* Minor fixes +* Performance improvements + +### Migrating to 2.0 for `stream_chat_flutter_core` + +If you are migrating your full Flutter project to null-safety, first make sure you follow the +instructions from the [official Null Safety migration guide](https://dart.dev/null-safety/migration-guide). +::: + +To migrate to v2.0 for `stream_chat_flutter_core`, first change the version of the package to the latest +null-safe version. + +```yaml +dependencies: + stream_chat_flutter_core: ^2.0.0 +``` + +Upon doing this, all breaking changes from the package will take immediate effect. Here are steps to +remedy the issue: + +:::note +The major changes in `stream_chat_flutter_core` consist of changing over from a map full of options +to a more type safe and sound approach by changing over to explicit parameters. +::: + +1) Change over Core widget implementations by using the explicit parameters instead of the options map. +Use these explicit parameters in the widget constructor instead of the option keys: + + * options.state -> bool state + * options.watch -> bool watch + * options.presence -> bool presence + +2) Change over query calls in the BLoCs in the same way (change from options map to explicit parameters +in the constructor) + +### Changelog of `stream_chat` + +#### đŸ›‘ī¸ Breaking Changes from 1.5.3 + +* Migrate this package to null safety +* ConnectUserWithProvider now requires tokenProvider as a required param. (Removed from the constructor) +* client.disconnect() is now divided into two different functions +* client.closeConnection() -> for closing user websocket connection. +* client.disconnectUser() -> for disconnecting user and resetting client state. +* client.devToken() now returns a Token model instead of String. +* ApiError is removed in favor of StreamChatError +* StreamChatError -> parent type for all the stream errors. +* StreamWebSocketError -> for user websocket related errors. +* StreamChatNetworkError -> for network related errors. +* client.queryChannels(), channel.query() options param is removed in favor of individual params +* option.state -> bool state +* option.watch -> bool watch +* option.presence -> bool presence +* client.queryUsers() options param is removed in favor of individual params +* option.presence -> bool presence +* Migrate this package to null safety +* Added typed filters + +#### 🐞 Fixed + +* #369: Client does not return without internet connection +* several minor fixes +* performance improvements + +#### ✅ Added + +* New Location enum is introduced for easily changing the client location/baseUrl. +* New client.openConnection() and client.closeConnection() is introduced to connect/disconnect user ws connection. +* New client.partialUpdateMessage and channel.partialUpdateMessage methods +* connectWebSocket parameter in connect user calls to use the client in "connection-less" mode. + +#### 🔄 Changed + +* baseURL is now deprecated in favor of using Location to change data location. From cc76c3b8ff5b24780f81cfdf0a6bd782c3d45336 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 19:59:11 +0530 Subject: [PATCH 573/599] feat: Added mig guide (sc) --- .../Flutter/guides/migration_guide_2_0.mdx | 79 ++++++++++++++++--- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index b71c48b3..a2f509a8 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -205,20 +205,19 @@ in the constructor) * Migrate this package to null safety * ConnectUserWithProvider now requires tokenProvider as a required param. (Removed from the constructor) * client.disconnect() is now divided into two different functions -* client.closeConnection() -> for closing user websocket connection. -* client.disconnectUser() -> for disconnecting user and resetting client state. + * client.closeConnection() -> for closing user websocket connection. + * client.disconnectUser() -> for disconnecting user and resetting client state. * client.devToken() now returns a Token model instead of String. * ApiError is removed in favor of StreamChatError -* StreamChatError -> parent type for all the stream errors. -* StreamWebSocketError -> for user websocket related errors. -* StreamChatNetworkError -> for network related errors. + * StreamChatError -> parent type for all the stream errors. + * StreamWebSocketError -> for user websocket related errors. + * StreamChatNetworkError -> for network related errors. * client.queryChannels(), channel.query() options param is removed in favor of individual params -* option.state -> bool state -* option.watch -> bool watch -* option.presence -> bool presence + * option.state -> bool state + * option.watch -> bool watch + * option.presence -> bool presence * client.queryUsers() options param is removed in favor of individual params -* option.presence -> bool presence -* Migrate this package to null safety + * option.presence -> bool presence * Added typed filters #### 🐞 Fixed @@ -237,3 +236,63 @@ in the constructor) #### 🔄 Changed * baseURL is now deprecated in favor of using Location to change data location. + +### Migrating to 2.0 for `stream_chat` + +If you are migrating your full Flutter project to null-safety, first make sure you follow the +instructions from the [official Null Safety migration guide](https://dart.dev/null-safety/migration-guide). +::: + +To migrate to v2.0 for `stream_chat`, first change the version of the package to the latest +null-safe version. + +```yaml +dependencies: + stream_chat: ^2.0.0 +``` + +Upon doing this, all breaking changes from the package will take immediate effect. Here are steps to +remedy the issue: + +1) Change over the constructor of `connectUserWithProvider()` to the new format. + +2) We added more nuance to the `disconnectUser` by adding two new methods - one to close the connection +and the other to disconnect the user. This allows more fine-grained control for diconnection. + + * client.closeConnection() -> for closing user websocket connection. + * client.disconnectUser() -> for disconnecting user and resetting client state. + +3) We refactored how we handle errors - new error types are now introduced instead of ApiError. + + * StreamChatError -> parent type for all the stream errors. + * StreamWebSocketError -> for user websocket related errors. + * StreamChatNetworkError -> for network related errors. + +4) We changed over from a map full of options to a more type safe and sound approach by changing over to explicit parameters. + +Use these explicit parameters in the query parameters instead of the option keys: + +* client.queryChannels(), channel.query() options param is removed in favor of individual params + * option.state -> bool state + * option.watch -> bool watch + * option.presence -> bool presence +* client.queryUsers() options param is removed in favor of individual params + * option.presence -> bool presence + +5) We added type safe filters to make filtering in the app easier. + +As an example, in the old app this filter: + +``` + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, +``` + +Would turn into: + +``` + filter: Filter.in_('members', [StreamChat.of(context).user.id]) +``` \ No newline at end of file From 9c4216d9767654c92565861d1f8e2ef8538906ad Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:00:22 +0530 Subject: [PATCH 574/599] fix: fmt --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index a2f509a8..470b1004 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -283,7 +283,7 @@ Use these explicit parameters in the query parameters instead of the option keys As an example, in the old app this filter: -``` +```dart filter: { 'members': { '\$in': [StreamChat.of(context).user.id], @@ -293,6 +293,6 @@ As an example, in the old app this filter: Would turn into: -``` +```dart filter: Filter.in_('members', [StreamChat.of(context).user.id]) ``` \ No newline at end of file From 4d1625c306ff0349803188c62a869924be7a3b62 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:01:35 +0530 Subject: [PATCH 575/599] fix: details --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 470b1004..79bbaeb5 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -279,7 +279,8 @@ Use these explicit parameters in the query parameters instead of the option keys * client.queryUsers() options param is removed in favor of individual params * option.presence -> bool presence -5) We added type safe filters to make filtering in the app easier. +5) We added type safe filters to make filtering in the app easier. Change over the filters to the +new implementation. As an example, in the old app this filter: From 0bc3efdeccdf86635e5161658b3ac13f33cecf8c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:02:01 +0530 Subject: [PATCH 576/599] fix: details --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 79bbaeb5..124fe247 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -256,7 +256,7 @@ remedy the issue: 1) Change over the constructor of `connectUserWithProvider()` to the new format. -2) We added more nuance to the `disconnectUser` by adding two new methods - one to close the connection +2) We added more nuance to the `disconnectUser()` by adding two new methods - one to close the connection and the other to disconnect the user. This allows more fine-grained control for diconnection. * client.closeConnection() -> for closing user websocket connection. From 1e54f1198b4b19c6d13e9857c706ec82bae9e27d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 16:48:10 +0200 Subject: [PATCH 577/599] replace summary with bold --- docusaurus/docs/Flutter/basics/choose_package.mdx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docusaurus/docs/Flutter/basics/choose_package.mdx b/docusaurus/docs/Flutter/basics/choose_package.mdx index 7a4961c5..8be73f01 100644 --- a/docusaurus/docs/Flutter/basics/choose_package.mdx +++ b/docusaurus/docs/Flutter/basics/choose_package.mdx @@ -28,9 +28,10 @@ to match your app colors and such. If you require any additional feature or cust to request this through our support channels. ::: -:::summary +Summary: + For the quickest and easiest way to add Chat to your app with prebuilt UI components, use stream_chat_flutter -::: + #### The case for stream_chat_flutter_core @@ -39,9 +40,9 @@ strips away the UI associated with the components and provides the data fetching capabilities while supplying builders for UI. This allows you to implement your own UI and themes completely independently while not worrying about writing functions for data and pagination. -:::summary +Summary: + For implementing your own custom UI while not having to worry about lower level API calls, use stream_chat_flutter_core. -::: #### The case for stream_chat @@ -49,6 +50,6 @@ The stream_chat package is the Low-level Client (LLC) of Stream Chat in Flutter. the underlying functionality of Stream Chat and allows the most customization in terms of UI, data, and architecture. -:::summary +Summary: + For the most control over the SDK and dealing with low level calls to the API, use stream_chat. -::: \ No newline at end of file From a9c9d40ba5924813e8274f7e5cb75ed4c5fe3828 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:24:50 +0530 Subject: [PATCH 578/599] fix: details --- .../Flutter/guides/migration_guide_2_0.mdx | 174 +++++++++--------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 124fe247..4b8662ac 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -23,39 +23,39 @@ The changes will be listed by package and a concise changelog will be followed b * Migrate this package to null safety -* Renamed ChannelImage to ChannelAvatar +* Renamed `ChannelImage` to `ChannelAvatar` -* Updated StreamChatThemeData.reactionIcons to accept custom builder +* Updated `StreamChatThemeData.reactionIcons` to accept custom builder * Renamed ColorTheme properties to reflect the purpose of the colors - * ColorTheme.black -> ColorTheme.textHighEmphasis - * ColorTheme.grey -> ColorTheme.textLowEmphasis - * ColorTheme.greyGainsboro -> ColorTheme.disabled - * ColorTheme.greyWhisper -> ColorTheme.borders - * ColorTheme.whiteSmoke -> ColorTheme.inputBg - * ColorTheme.whiteSnow -> ColorTheme.appBg - * ColorTheme.white -> ColorTheme.barsBg - * ColorTheme.blueAlice -> ColorTheme.linkBg - * ColorTheme.accentBlue -> ColorTheme.accentPrimary - * ColorTheme.accentRed -> ColorTheme.accentError - * ColorTheme.accentGreen -> ColorTheme.accentInfo + * `ColorTheme.black` -> `ColorTheme.textHighEmphasis` + * `ColorTheme.grey` -> `ColorTheme.textLowEmphasis` + * `ColorTheme.greyGainsboro` -> `ColorTheme.disabled` + * `ColorTheme.greyWhisper` -> `ColorTheme.borders` + * `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg` + * `ColorTheme.whiteSnow` -> `ColorTheme.appBg` + * `ColorTheme.white` -> `ColorTheme.barsBg` + * `ColorTheme.blueAlice` -> `ColorTheme.linkBg` + * `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary` + * `ColorTheme.accentRed` -> `ColorTheme.accentError` + * `ColorTheme.accentGreen` -> `ColorTheme.accentInfo` -* ChannelListCore options property is removed in favor of individual properties +* `ChannelListCore` options property is removed in favor of individual properties - *options.state -> bool state - *options.watch -> bool watch - *options.presence -> bool presence + * `options.state` -> `bool state` + * `options.watch` -> `bool watch` + * `options.presence` -> `bool presence` -* UserListView options property is removed in favor of individual properties +* `UserListView` options property is removed in favor of individual properties - *options.presence -> bool presence + * `options.presence` -> `bool presence` -* Renamed ImageHeader to GalleryHeader +* Renamed `ImageHeader` to `GalleryHeader` -* Renamed ImageFooter to GalleryFooter +* Renamed `ImageFooter` to `GalleryFooter` -* MessageBuilder and ParentMessageBuilder signature is now +* `MessageBuilder` and `ParentMessageBuilder` signature is now ``` typedef MessageBuilder = Widget Function( @@ -66,28 +66,28 @@ typedef MessageBuilder = Widget Function( ); ``` -The last parameter is the default MessageWidget -You can call .copyWith to customize just a subset of properties +The last parameter is the default `MessageWidget` +You can call `.copyWith` to customize just a subset of properties #### ✅ Added Added video compress options (frame and quality) to MessageInput -TypingIndicator now has a property called parentId to show typing indicator specific to threads -#493: add support for messageListView header/footer -MessageWidget accepts a userAvatarBuilder -Added pinMessage ui support -Added MessageListView.threadSeparatorBuilder property -Added MessageInput.onError property to allow error handling -Added GalleryHeader/GalleryFooter theme classes +`TypingIndicator` now has a property called `parentId` to show typing indicator specific to threads +#493: add support for `MessageListView` header/footer +`MessageWidget` accepts a `userAvatarBuilder` +Added `pinMessage` ui support +Added `MessageListView.threadSeparatorBuilder` property +Added `MessageInput.onError` property to allow error handling +Added `GalleryHeader`/`GalleryFooter` theme classes #### 🐞 Fixed #483: Keyboard covers input text box when editing message -Modals are shown using the nearest Navigator to make using the SDK easier in a nested navigator use case +`Modals` are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case #484: messages don't update without a reload -MessageListView not rendering if the user is not a member of the channel -Fix MessageInput overflow when there are no actions +`MessageListView` not rendering if the user is not a member of the channel +Fix `MessageInput` overflow when there are no actions Minor fixes and improvements ### Migrating to 2.0 for `stream_chat_flutter` @@ -110,24 +110,24 @@ remedy the issue: 1) Replace the offending class names with the revised class names - * ChannelImage -> ChannelAvatar - * ImageHeader -> GalleryHeader - * ImageFooter -> GalleryFooter + * `ChannelImage` -> `ChannelAvatar` + * `ImageHeader` -> `GalleryHeader` + * `ImageFooter` -> `GalleryFooter` 2) The new version comes with revised color names since the previous names do not suit light/dark mode nomenclature. Make sure any old colors used from theme are changed over to the new theme color names: - * ColorTheme.black -> ColorTheme.textHighEmphasis - * ColorTheme.grey -> ColorTheme.textLowEmphasis - * ColorTheme.greyGainsboro -> ColorTheme.disabled - * ColorTheme.greyWhisper -> ColorTheme.borders - * ColorTheme.whiteSmoke -> ColorTheme.inputBg - * ColorTheme.whiteSnow -> ColorTheme.appBg - * ColorTheme.white -> ColorTheme.barsBg - * ColorTheme.blueAlice -> ColorTheme.linkBg - * ColorTheme.accentBlue -> ColorTheme.accentPrimary - * ColorTheme.accentRed -> ColorTheme.accentError - * ColorTheme.accentGreen -> ColorTheme.accentInfo + * `ColorTheme.black` -> `ColorTheme.textHighEmphasis` + * `ColorTheme.grey` -> `ColorTheme.textLowEmphasis` + * `ColorTheme.greyGainsboro` -> `ColorTheme.disabled` + * `ColorTheme.greyWhisper` -> `ColorTheme.borders` + * `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg` + * `ColorTheme.whiteSnow` -> `ColorTheme.appBg` + * `ColorTheme.white` -> `ColorTheme.barsBg` + * `ColorTheme.blueAlice` -> `ColorTheme.linkBg` + * `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary` + * `ColorTheme.accentRed` -> `ColorTheme.accentError` + * `ColorTheme.accentGreen` -> `ColorTheme.accentInfo` 3) We decided to make messages easier to customize and now supply the default implementation of the messages in the builder - so you can now customize a single parameter without having to redo the @@ -151,15 +151,15 @@ default implementation. * Migrate this package to null safety * channelsBloc.queryChannels(), ChannelListCore options param/property is removed in favor of individual params/properties - * options.state -> bool state - * options.watch -> bool watch - * options.presence -> bool presence -* usersBloc.queryUsers(), UserListCore options param/property is removed in favor of individual params/properties - * options.presence -> bool presence + * `options.state` -> `bool state` + * `options.watch` -> `bool watch` + * `options.presence` -> `bool presence` +* `usersBloc.queryUsers()`, `UserListCore` options param/property is removed in favor of individual params/properties + * `options.presence` -> `bool presence` #### ✅ Added -* Monitor connection using connectivity_plus package +* Monitor connection using `connectivity_plus` package #### 🐞 Fixed @@ -191,9 +191,9 @@ to a more type safe and sound approach by changing over to explicit parameters. 1) Change over Core widget implementations by using the explicit parameters instead of the options map. Use these explicit parameters in the widget constructor instead of the option keys: - * options.state -> bool state - * options.watch -> bool watch - * options.presence -> bool presence + * `options.state` -> `bool state` + * `options.watch` -> `bool watch` + * `options.presence` -> `bool presence` 2) Change over query calls in the BLoCs in the same way (change from options map to explicit parameters in the constructor) @@ -203,21 +203,21 @@ in the constructor) #### đŸ›‘ī¸ Breaking Changes from 1.5.3 * Migrate this package to null safety -* ConnectUserWithProvider now requires tokenProvider as a required param. (Removed from the constructor) -* client.disconnect() is now divided into two different functions - * client.closeConnection() -> for closing user websocket connection. - * client.disconnectUser() -> for disconnecting user and resetting client state. -* client.devToken() now returns a Token model instead of String. -* ApiError is removed in favor of StreamChatError - * StreamChatError -> parent type for all the stream errors. - * StreamWebSocketError -> for user websocket related errors. - * StreamChatNetworkError -> for network related errors. -* client.queryChannels(), channel.query() options param is removed in favor of individual params - * option.state -> bool state - * option.watch -> bool watch - * option.presence -> bool presence -* client.queryUsers() options param is removed in favor of individual params - * option.presence -> bool presence +* `ConnectUserWithProvider` now requires `tokenProvider` as a required param. (Removed from the constructor) +* `client.disconnect()` is now divided into two different functions + * `client.closeConnection()` -> for closing user websocket connection. + * `client.disconnectUser()` -> for disconnecting user and resetting client state. +* `client.devToken()` now returns a Token model instead of String. +* `ApiError` is removed in favor of `StreamChatError` + * `StreamChatError` -> parent type for all the stream errors. + * `StreamWebSocketError` -> for user websocket related errors. + * `StreamChatNetworkError` -> for network related errors. +* `client.queryChannels()`, `channel.query()` options param is removed in favor of individual params + * `option.state` -> `bool state` + * `option.watch` -> `bool watch` + * `option.presence` -> `bool presence` +* `client.queryUsers()` options param is removed in favor of individual params + * `option.presence` -> `bool presence` * Added typed filters #### 🐞 Fixed @@ -229,13 +229,13 @@ in the constructor) #### ✅ Added * New Location enum is introduced for easily changing the client location/baseUrl. -* New client.openConnection() and client.closeConnection() is introduced to connect/disconnect user ws connection. -* New client.partialUpdateMessage and channel.partialUpdateMessage methods -* connectWebSocket parameter in connect user calls to use the client in "connection-less" mode. +* New `client.openConnection()` and `client.closeConnection()` is introduced to connect/disconnect user ws connection. +* New `client.partialUpdateMessage` and `channel.partialUpdateMessage` methods +* `connectWebSocket` parameter in connect user calls to use the client in "connection-less" mode. #### 🔄 Changed -* baseURL is now deprecated in favor of using Location to change data location. +* `baseURL` is now deprecated in favor of using Location to change data location. ### Migrating to 2.0 for `stream_chat` @@ -259,25 +259,25 @@ remedy the issue: 2) We added more nuance to the `disconnectUser()` by adding two new methods - one to close the connection and the other to disconnect the user. This allows more fine-grained control for diconnection. - * client.closeConnection() -> for closing user websocket connection. - * client.disconnectUser() -> for disconnecting user and resetting client state. + * `client.closeConnection()` -> for closing user websocket connection. + * `client.disconnectUser()` -> for disconnecting user and resetting client state. 3) We refactored how we handle errors - new error types are now introduced instead of ApiError. - * StreamChatError -> parent type for all the stream errors. - * StreamWebSocketError -> for user websocket related errors. - * StreamChatNetworkError -> for network related errors. + * `StreamChatError` -> parent type for all the stream errors. + * `StreamWebSocketError` -> for user websocket related errors. + * `StreamChatNetworkError` -> for network related errors. 4) We changed over from a map full of options to a more type safe and sound approach by changing over to explicit parameters. Use these explicit parameters in the query parameters instead of the option keys: -* client.queryChannels(), channel.query() options param is removed in favor of individual params - * option.state -> bool state - * option.watch -> bool watch - * option.presence -> bool presence -* client.queryUsers() options param is removed in favor of individual params - * option.presence -> bool presence +* `client.queryChannels()`, `channel.query()` options param is removed in favor of individual params + * `option.state` -> `bool state` + * `option.watch` -> `bool watch` + * `option.presence` -> `bool presence` +* `client.queryUsers()` options param is removed in favor of individual params + * `option.presence` -> `bool presence` 5) We added type safe filters to make filtering in the app easier. Change over the filters to the new implementation. From 18be18678b8cfa4946730b79aa462afbf13b3c19 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:35:21 +0530 Subject: [PATCH 579/599] fix: details --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 4b8662ac..38422e72 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -82,8 +82,7 @@ Added `GalleryHeader`/`GalleryFooter` theme classes #### 🐞 Fixed -#483: Keyboard covers input text box when editing -message +#483: Keyboard covers input text box when editing message `Modals` are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case #484: messages don't update without a reload `MessageListView` not rendering if the user is not a member of the channel From 6afe6a83f52c6700d352b8bd892d01c89ade7df8 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:39:06 +0530 Subject: [PATCH 580/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Gordon --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 38422e72..3e3bafd7 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -15,7 +15,7 @@ Check [this link](https://flutter.dev/docs/null-safety) for more about Null Safe This guide is intended to enumerate and better explain the changes in the SDK. -The changes will be listed by package and a concise changelog will be followed by more info. +The changes will be listed by package and a concise changelog will follow with more info. ### Changelog of `stream_chat_flutter` @@ -295,4 +295,4 @@ Would turn into: ```dart filter: Filter.in_('members', [StreamChat.of(context).user.id]) -``` \ No newline at end of file +``` From 9b6c2df1d7c68656c53e67b344e651d2fdc789f6 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:39:30 +0530 Subject: [PATCH 581/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Reuben Turner --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 3e3bafd7..e9c5ef15 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -105,7 +105,7 @@ dependencies: ``` Upon doing this, all breaking changes from the package will take immediate effect. Here are steps to -remedy the issue: +remedy the issues: 1) Replace the offending class names with the revised class names From 6ff32fb26c02038c3a598f3275c4f23be9e052cf Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:40:25 +0530 Subject: [PATCH 582/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Reuben Turner --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index e9c5ef15..8800df44 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -141,8 +141,7 @@ typedef MessageBuilder = Widget Function( ); ``` -Use `defaultMessageWidget.copyWith()` to edit any properties to make it much simpler to tweak the -default implementation. +To tweak any of the default properties individually, you can use `defaultMessageWidget.copyWith()`. ### Changelog of `stream_chat_flutter_core` From 8edf68b3b96fd65072553e2e96f4312650870cd4 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:40:40 +0530 Subject: [PATCH 583/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Reuben Turner --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 8800df44..f821217c 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -250,7 +250,7 @@ dependencies: ``` Upon doing this, all breaking changes from the package will take immediate effect. Here are steps to -remedy the issue: +remedy the issues: 1) Change over the constructor of `connectUserWithProvider()` to the new format. From d62e9a81b09ce8647960bbb9d8a97277e7aeb5a2 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:41:20 +0530 Subject: [PATCH 584/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Reuben Turner --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index f821217c..bf044e5b 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -254,7 +254,7 @@ remedy the issues: 1) Change over the constructor of `connectUserWithProvider()` to the new format. -2) We added more nuance to the `disconnectUser()` by adding two new methods - one to close the connection +2) We added more nuance to `disconnectUser()` by adding two new methods - one to close the connection and the other to disconnect the user. This allows more fine-grained control for diconnection. * `client.closeConnection()` -> for closing user websocket connection. From ac27b9bc2b41b8353bba38f3de768c3f3de582ea Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:41:33 +0530 Subject: [PATCH 585/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Reuben Turner --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index bf044e5b..ef290520 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -255,7 +255,7 @@ remedy the issues: 1) Change over the constructor of `connectUserWithProvider()` to the new format. 2) We added more nuance to `disconnectUser()` by adding two new methods - one to close the connection -and the other to disconnect the user. This allows more fine-grained control for diconnection. +and the other to disconnect the user. This allows more fine-grained control of disconnection. * `client.closeConnection()` -> for closing user websocket connection. * `client.disconnectUser()` -> for disconnecting user and resetting client state. From 00b1f21c635971fb9bc5d3ae54591b1e77e4b599 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:41:49 +0530 Subject: [PATCH 586/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Reuben Turner --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index ef290520..9a4b7a7d 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -260,7 +260,7 @@ and the other to disconnect the user. This allows more fine-grained control of d * `client.closeConnection()` -> for closing user websocket connection. * `client.disconnectUser()` -> for disconnecting user and resetting client state. -3) We refactored how we handle errors - new error types are now introduced instead of ApiError. +3) We refactored how we handle errors - new error types are now introduced that replace ApiError. * `StreamChatError` -> parent type for all the stream errors. * `StreamWebSocketError` -> for user websocket related errors. From 816f9ab7fa7ed9d19542c4dd819f1de6ff36749e Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:41:59 +0530 Subject: [PATCH 587/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Reuben Turner --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 9a4b7a7d..094abab1 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -266,7 +266,7 @@ and the other to disconnect the user. This allows more fine-grained control of d * `StreamWebSocketError` -> for user websocket related errors. * `StreamChatNetworkError` -> for network related errors. -4) We changed over from a map full of options to a more type safe and sound approach by changing over to explicit parameters. +4) We changed over from a map full of options to a more type-safe and sound approach by changing over to explicit parameters. Use these explicit parameters in the query parameters instead of the option keys: From 695760637b705f79493795431818c4ffd3c43df2 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:42:07 +0530 Subject: [PATCH 588/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Reuben Turner --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 094abab1..582617ff 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -277,7 +277,7 @@ Use these explicit parameters in the query parameters instead of the option keys * `client.queryUsers()` options param is removed in favor of individual params * `option.presence` -> `bool presence` -5) We added type safe filters to make filtering in the app easier. Change over the filters to the +5) We added type-safe filters to make filtering in the app easier. Change over the filters to the new implementation. As an example, in the old app this filter: From 3c394362cce79f0afc2ff9d42cafdebf9f25b8c7 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:43:46 +0530 Subject: [PATCH 589/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Reuben Turner --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 582617ff..b9ed7609 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -252,7 +252,7 @@ dependencies: Upon doing this, all breaking changes from the package will take immediate effect. Here are steps to remedy the issues: -1) Change over the constructor of `connectUserWithProvider()` to the new format. +1) Change over the constructor of `connectUserWithProvider()` to the new format which has `tokenProvider` as a required param. 2) We added more nuance to `disconnectUser()` by adding two new methods - one to close the connection and the other to disconnect the user. This allows more fine-grained control of disconnection. From 60cddfc09174c8a8bac5907322a8be501a3c94be Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:43:55 +0530 Subject: [PATCH 590/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Gordon --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index b9ed7609..4d3afe33 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -221,7 +221,7 @@ in the constructor) #### 🐞 Fixed * #369: Client does not return without internet connection -* several minor fixes +* Several minor fixes * performance improvements #### ✅ Added From c513bd06ffd2eff5a19f4d1e091a3df4c69bf67c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:44:00 +0530 Subject: [PATCH 591/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Gordon --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 4d3afe33..2b53cfb3 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -222,7 +222,7 @@ in the constructor) * #369: Client does not return without internet connection * Several minor fixes -* performance improvements +* Performance improvements #### ✅ Added From 5c12521a69e5a20f04f056ef36d16a7e8e333794 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 13:15:50 +0200 Subject: [PATCH 592/599] fix analyzer --- .../test/stream_chat_core_test.dart | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index eaa17117..ee9a86cb 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -296,15 +296,15 @@ void main() { await tester.runAsync(() async { final mockClient = MockClient(); final mockOnBackgroundEventReceived = MockOnBackgroundEventReceived(); - const backgroundKeepAlive = const Duration(seconds: 3); + const backgroundKeepAlive = Duration(seconds: 3); const streamChatCoreKey = Key('streamChatCore'); const childKey = Key('child'); final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), onBackgroundEventReceived: mockOnBackgroundEventReceived, backgroundKeepAlive: backgroundKeepAlive, + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -313,12 +313,13 @@ void main() { expect(find.byKey(childKey), findsOneWidget); final event = Event(type: EventType.any); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); + when(mockClient.on).thenAnswer((_) => Stream.value(event)); final streamChatCoreState = tester.state( find.byKey(streamChatCoreKey), ); + // ignore: cascade_invocations streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); @@ -346,8 +347,8 @@ void main() { final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), connectivityStream: Stream.value(ConnectivityResult.mobile), + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -356,10 +357,10 @@ void main() { expect(find.byKey(childKey), findsOneWidget); final event = Event(type: EventType.any); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()) + when(mockClient.on).thenAnswer((_) => Stream.value(event)); + when(mockClient.openConnection) .thenAnswer((_) async => OwnUser(id: 'test')); - when(() => mockClient.closeConnection()).thenAnswer((_) async => null); + when(mockClient.closeConnection).thenAnswer((_) async {}); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); @@ -367,6 +368,7 @@ void main() { find.byKey(streamChatCoreKey), ); + // ignore: cascade_invocations streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); @@ -375,7 +377,7 @@ void main() { streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.inactive); - verify(() => mockClient.openConnection()).called(1); + verify(mockClient.openConnection).called(1); }); }, ); @@ -391,18 +393,18 @@ void main() { const childKey = Key('child'); final event = Event(); - when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); - when(() => mockClient.openConnection()) + when(mockClient.on).thenAnswer((_) => Stream.value(event)); + when(mockClient.openConnection) .thenAnswer((_) async => OwnUser(id: 'test')); - when(() => mockClient.closeConnection()).thenAnswer((_) async => null); + when(mockClient.closeConnection).thenAnswer((_) async {}); when(() => mockClient.wsConnectionStatus) .thenReturn(ConnectionStatus.disconnected); final streamChatCore = StreamChatCore( key: streamChatCoreKey, client: mockClient, - child: Offstage(key: childKey), connectivityStream: Stream.value(ConnectivityResult.none), + child: const Offstage(key: childKey), ); await tester.pumpWidget(streamChatCore); @@ -414,6 +416,7 @@ void main() { find.byKey(streamChatCoreKey), ); + // ignore: cascade_invocations streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.paused); @@ -422,7 +425,7 @@ void main() { streamChatCoreState .didChangeAppLifecycleState(AppLifecycleState.inactive); - verifyNever(() => mockClient.openConnection()); + verifyNever(mockClient.openConnection); }); }, ); From cb5db2796cf240eebaaa98c99f36e4d1cb67ac07 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:44:40 +0530 Subject: [PATCH 593/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx Co-authored-by: Gordon --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 2b53cfb3..a9b64a57 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -148,7 +148,7 @@ To tweak any of the default properties individually, you can use `defaultMessage #### đŸ›‘ī¸ Breaking Changes from 1.5.3 * Migrate this package to null safety -* channelsBloc.queryChannels(), ChannelListCore options param/property is removed in favor of individual params/properties +* `channelsBloc.queryChannels()`, `ChannelListCore` options param/property is removed in favor of individual params/properties * `options.state` -> `bool state` * `options.watch` -> `bool watch` * `options.presence` -> `bool presence` From 9a36b961ba53e6cf87a16764dfa352d6f2296340 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:45:22 +0530 Subject: [PATCH 594/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index a9b64a57..58873822 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -27,7 +27,7 @@ The changes will be listed by package and a concise changelog will follow with m * Updated `StreamChatThemeData.reactionIcons` to accept custom builder -* Renamed ColorTheme properties to reflect the purpose of the colors +* Renamed `ColorTheme` properties to reflect the purpose of the colors * `ColorTheme.black` -> `ColorTheme.textHighEmphasis` * `ColorTheme.grey` -> `ColorTheme.textLowEmphasis` From 95febb00cfdc73e06d496c5fa4d49753bf620374 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 16 Jul 2021 20:47:37 +0530 Subject: [PATCH 595/599] Update docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 58873822..394137bd 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -166,6 +166,7 @@ To tweak any of the default properties individually, you can use `defaultMessage ### Migrating to 2.0 for `stream_chat_flutter_core` +:::note If you are migrating your full Flutter project to null-safety, first make sure you follow the instructions from the [official Null Safety migration guide](https://dart.dev/null-safety/migration-guide). ::: From f54e7ebfda01339f155c4f0506c3ae7b6334c499 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 17:24:59 +0200 Subject: [PATCH 596/599] update docusaurus action --- .github/workflows/docusaurus.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docusaurus.yml b/.github/workflows/docusaurus.yml index 03fc3d5f..4395bc35 100644 --- a/.github/workflows/docusaurus.yml +++ b/.github/workflows/docusaurus.yml @@ -13,5 +13,7 @@ jobs: - uses: actions/checkout@v2 - name: push uses: GetStream/push-stream-chat-docusaurus-action@main + with: + target-branch: ${{ github.ref == 'refs/heads/master' && 'production' || 'staging' }} env: DOCUSAURUS_GH_TOKEN: ${{ secrets.DOCUSAURUS_GH_TOKEN }} \ No newline at end of file From d1466bae8f21da5966ae16ef4c18716d081dfc12 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 17:26:04 +0200 Subject: [PATCH 597/599] update docusaurus action --- .github/workflows/docusaurus.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docusaurus.yml b/.github/workflows/docusaurus.yml index 4395bc35..f46fa893 100644 --- a/.github/workflows/docusaurus.yml +++ b/.github/workflows/docusaurus.yml @@ -3,7 +3,8 @@ name: docusaurus on: push: branches: - - docusaurus # default branch name for each repo that docs should be push from + - master + - develop paths: - docusaurus/** jobs: From ccb6a62a42d2743b1c0ad663f047c56664319b6c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 17:27:11 +0200 Subject: [PATCH 598/599] update stream workflow --- .github/workflows/stream_flutter_workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 6817b269..0baaaff7 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -105,7 +105,7 @@ jobs: uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat/coverage/lcov.info - min_coverage: 40 + min_coverage: 80 - name: "Stream Chat Persistence Coverage Check" uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: @@ -120,4 +120,4 @@ jobs: uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 with: path: packages/stream_chat_flutter/coverage/lcov.info - min_coverage: 35 \ No newline at end of file + min_coverage: 67 \ No newline at end of file From 9813f4084a790c232efc53b0e41519dc0862ae6f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Jul 2021 17:29:27 +0200 Subject: [PATCH 599/599] update persistence version --- packages/stream_chat_persistence/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 3ad9b2e0..08029e91 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_persistence homepage: https://github.com/GetStream/stream-chat-flutter description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. -version: 2.0.0-nullsafety.8 +version: 2.0.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues

    n|i;<^F)Gu?m#CRt@9};#_ve%as?(1wYsHSe_Qd6VS}spE9@*P* zGWWl~!#{fxGx@#E%&_sZxLKTKmvWOpxCs{F8yc}s@1FCRHv7CyQE2gK*MuhGvJ$3*!Lud*Y|^EF$A-QGcu@OJ6qv=Fa#KL0rK!0no|An zDvDm_Uu0{1NfWol*n$GlUoQCsGZHegSySGRCNp_VkQs)DCW`ux+y7*8X)2Z}=kDJJ zJ3RPU*3&pkhVie>!*)!oBDq;SFdbB5FuCbkNfi0q{Ih0E8_dEq)Ty%o-wXYvmG$N8j3`jSwKq}|3}K<6?} zWS$Sfh{voHx$RBZ5x$Upr0Os zYi9I%g!R$0`&AQF6hDVg$DVH2dpU^>jvhY!MOz@nB1~bJ^`Hm;MT)UbG6F=$CM&Eq_Mhrtf$Rm6>XEu)JQ#DE`PWg`$(HJ1LM!IeXg7A;* z26=vzxUsx^0)tu;T=sNAya^3+Xk+)mTuF{8QxAr`+Yg>m+7C`3@ttA~0TK6@^AE#h zuR+q^=)_BPQe9Mu1E;x4H*x8$738M?=LT&N$SCV%z)3zZBt^cN$9>rR#M>v-o z2L1E5nIjQVMf+vHiKsZjbh#o2S^Tl2!I7p%8oAWP-Em&s1xs4rkjs-_MY}r$k|3Jm zJUrwLHrDsOy44EyW{R9w@h7b4GRzC}7jf3xdrrt>yyDAAw46z0krVCSWgUosOot(>z-@8Mog~BO#*lW zgpy^HnzFCg)XC}zU@t}>PMan#aMhaV)7s5gv0n6Zb8H+%CfrMRI@Y=j^}FtYqgfr^Dv)j~ zeK7jy>pAS$@@jBz!qS}ZYvc&*;1L`cpCa$m6O*- z^T%3mEX(F-nVJ>Xd$-FbI?4E5Kf5{v{aP~j1WIM)_R4JYU8v<84+|cB05uk8ltc+5 zW9vAa)OGP*x3ErUSxTLi+-3aT(VeQJ=OEEL%}#3zaG*Or*;c*FoK*q-rv-4d)IznJ zV*(YIfe!AvYHl=BA-nf`QU=jH(8;L4hr&eDL|RoQB6|`= z>%WR7cLc{*Axv5KPOO$k-<@Lj-IO*n^5-9YbA9OL6_t&m7#F2fl=nAvjm^ls{x6|E zq(}91F!`YIYe}8gf!+L-4cxfeT)K_Q=1GN|DvfgAaESJg$YuM39Tld$y8W^DJ)DiK z!Z49Rj1pGaOF5+U$E(?3vS_|@jZBYX7;^|IN*i+iW+``WTsPXsz#WWvT;pritKa64 zRmpe=a@k-O9I7fy1mtbytF94sydrFl)CAF0X~0w%i|=T-Xh}J{L8JW=+InxJ?wu8O z2nZ$yr5h0XbXb<@=|EjYj4+izi)NW7bICZ;vq3Lc+e53sz&;Nq zh*7V96|7NSRkq3?oGlKtpB~+@dNC)Oe_yVpPI@XUB}*yO8@EUU=Kl2_hxC+Hb#7J7 z_gT*ms&bMk@Cw4WB1=WYa>UKdPL&EGBeux2y5m@_9ss*?VK0-TJ{FK|9(;rc^Qf`9 ztnp%0gQX(Pn5`=S=bhIdAy&_V)4$yK`0qstRMS8k635PJ%SOGojmAJeqRghx-c{-` zKn{Ga`eF-F^kv8>(RY2W4T0bsp%`lvvM2P8EMiDY8ZZ9nRv5^ocV9pPU|>5DY9#Ku zG@V9ms(ZfaF_o+|_nKyBUCxxaojTr#mZ2D^JW{3`*_jd^_zEt9K}b~HngaO-nTf&4 zv$q64Hx5=&RJ4$jHM}?@&tFPo3)g;rv3QS$i98B_W~RS#d#=L$n_?DqKLBf?S~J^T zQpuiLi~KJ_%}?|c*K^0#nC=Q|^tOk|PwYQ(Ni}Qd&%1@5x5W4&8c!l;8i7acxZ>`z zB0K@M1;in|;r7{7%B*ZoIT){QcLVYz;gg1L`RkH*7ypN^vy6%|+yXV-4Kg4tEhRPd zP*Q>*pduk4jnW{}DWY_DN_Tfl!_Y{>3_Wx+FvQ&PoOA!(b=UVBhFOdKz5CtqJbRuK z-o_8d;#UsZtH1x9tX7K{+bN}jVC(J=#L28P#VLg~gJoY>qGimeett^w0MWjGH5nYC zdeftg9jS8FR)8TW$b-EB54d>hH}rqxgNgr`^e&o4t;sa znD&WdNS>CtKop9mLyvWOD(`48T7%7%EHe3L`T3~GEaq&mB1!ETUqxO+d1-N-=6rCI z-}zAEqYm)MnL!B6B4zGewImdfol50IH%7>qn!u;GI21n(I1uCWu;I%-j|ij#nG^6R z0m(`(XnF!-1zR28ENY%t3JbUad6kuu9MdclN=k!qGA!>nVklC$9LYoqgK-yOH5#b$ zXd`Ryd3i7uYN+~rAXyS`;y07NoSOB={BhJZ)k0tPYeEU8>MD-T$XtcYyXC6C;lEnk zM(ahmQ5L=uIU>473g8f369tIbDS$xneW>s_qaZ4k7IhgP1!Nd<0L?^E5xOOpMXvi+ zpX+U_%jqD)h(S>iKMh^G{>wtyCm4eD&dVS^t^L^oDF*vL%I`LZ`xRMUn%-haQokLI zSLI(GFg zzOB{Ry$JR={(IB}$@;g1e3>@i&LIPj+f_!h#YuNEdROm=QUm-lS(^~wj1UjmZzihy z(BhALyhJHIWHDa+6W!3+uhv?sx8ClgZzdNtbsX8Nsk2+xBns!H(Z2ZSI-Tw%mCbN7 z_~j(aB`Q4nJVOMj7Dn3i$?4jO55RQth@RE`;{@2sHss!0`nCTGU;w7~B4CgL=? z{W)+eCtk8!5{hZfI0AD{t*`aTuXOEL#$$Ydbsz3%yNB`bgVom%V~VuZYeLv2`Ko-az!j3#8{Ysg6Ku0Y$rk^RcelZ^$Aeiu=f^C%O{&rYh%IR1AdK@y8|R@F4ochG>gC=HuH~^ zgd)_QF3orFf4puV)kKBv-PwOsfLOvQuU(!?oevW9O@?x2c|5Zk`KRE>%GF(8Ly9a{ zn-50)$Ljp+(?4E$eNT2&d--~OogBkYi6aNw)!cut&MZqp5llgfL-YsduP*||KtaRl zDLXf^;igdTrICUUlaOq$`W3E`7lJP$F+@g$71L|%Lbfd?fJqCA&s(RF_z(`fi-lyt zvwy8S3J2Yb*?b1G`vm?VVxow$q?cJj6sMc?M7bcx`h{{#>!+xTKu_>QDE)pl4&Y5T_H%RT1w2O_?Jc6qn>?bwhKTi*dWjrO~<-RTzARoMpuK z?Q9NsbkEskTq>AA`!Rd%wc019KMq!aBWNz&EsXT7%_~+#Fh#~zRc=LG0Z$8g^|cZJ zPzRTxJ>WLepzG=nYR!Pb69(yK6nvFzkK#ZZq}8jd$6vaKFx@l)9{YOH!3Zm&1=tp|e0*4**`LzeBJ=u16({x6bEm4Bf#El8UqZHTdL)-v3tm$!Dpx}xQ z2yTwle7|0UA-I!VV#wsFm+8K@GnPKX2|D}bHZHN7KhNDgKgLZWn&D1ej44>O%2y%v z_8*v}&JM+UEd3Gvdibx?<6fRuES{&(CbF>F6n~yw;Prlq58=eUwNd)EGB!VeVC$1$ zmKDyof%u&E=g)~{RURku2No`anBLJuqf~QuqQ{nX##VhkI>`_3L?kAmas-z568ndG zsOgp~kw0HYa7d)1lDA`@zVaQa?Xqb>jh1oyK+$hv@M%cLm2==|;xo^dk8;MT6*!Vd zHTkME#;j8lhB1elvX2|Jao60P!$=Oaf!&rmIa{MCwD$+f;GB^+A?rShBkhrxu@|%P zgF`f(SdcePooACVyFnA@e7(k*SFUwvBZUP-GIBDWTP({rLhO!yvh)g~m%0-nM3?D$ zi`>30hQ&pEXy+^ZyWECWYJ~oaval5^m<=nd&*=NcDaQSE|GXp&-lX0Zv&{kGT`ib> zJ!=2e4t($OJtlb~-&jMUs`lL3E#2xRY7w9mFIWyBPs?&6!1jqon)f^-%}uU|g9R5s z5#eeuCmOQ7bYn;kJU3%jU_h9@@3%t@E?lnW+CBmOY4!%7nR8AlG%x)Ohg7jA_g0Ia>~(JLF&M))HS!UaPux2;7+F9ybFg6 z!Qpxk==$g0bj_SS!IB0SSstE78qL(y-(K^*V#a{bCQu^i9>v4MWm+xVR-Xa?jEz8@ zebi1W7cOi;Jg{MTI$!yz^&2hHOK}u+tl6D`U1@dyD}<>IeZQg5PDxg62?f>_vC=I%sxcJ>oNGh85Ci34aVK`Nx`?bb@|+m}UhpNQhk zoWMwH*K<1__9-QYkj>q7noQC3bE0l+NjkaN-fGKQI5sMF-ydLkd+H|lI!hjN1D6|s zE^)+=RmYIfq<mUL=)89o3eNZ{FU z!U|}mp{uFGy&EL#o77yxUm$;-R9=TYQbrWx5@UvOOCG5;)RXodR0wq%ilVtHBhy}8B z?IJ3oujo^nH2=GlVM=)Fw^7C!&Fs40njz%zmQS8cyB`vk8>{#F;70b2i5Q>7zbDA= z0d)oPq5llUt=)}T&b?5om0aJoQPz;!w8ke~vaz`KC!>jfc!!?$20YEKUQhf?JsyaZ zE6yb7gC!q*NP9VnN#3k?(^M^<0&RbTUWiTVdw|VU+dM@1B5>e*-8H7aHg{8f1~I1I zaTS26aUeObtFm@A#2T$}I#*=}rM1z=5lK*M$@!XD8w*cv-3}I$`{LBGt&IZ}V<_3y zk3Qfw?5Y(e1fGLGk1ZJxqcw_P;q<2rq-NDRKE0)I+(cNVQ$IaN6G1(e`++*to|iP% zsq9&(G)ZzRv4_+m!O{jjdUh38rwLzwYh9GY%c9Nh>2#c4zw(P8lHG{cgdNU14bqNt zZ3@C>rr@kXXxhm2>UV3jQ6o-MCU^)S<%Gvf!!~K@GSe`oD|_ zg-~!f>7uKkiw>4%zal=Gv82{6JNoSJM;}AoemVo=;c-hN&#n(!?bK?gviW&K#ggO z61N||&=W~vz#wnC-W1;**&9jewx7#(zc-MXF zcjw7n8H`@AT&GSh#n7YJ39Dw#x;?%LqB`5j+pT!5A4W^TtS$0&U-*5G3;w}UBJWf( zaj5rZmfQflcLjPM6a7wunetEkk$39}Q$Rm*uVdHjbpBS@Zx6hkgE2zbf`PkNVZD0~%kRPe!-TF%P=wFnK@&FHyjK8G6TKQ4+BU ztf&K&RvJ{{N`dZ>YZN#w|t!P?%Dg-(GA@+9ATKV0IR+dQ? zCop3B+?t%gYR3ol*Q*0ee4J67Kw~!r+^21z>jm>qdYYf}NbA4+!pR07w`|3!KO>86 z*zH2;Q&8ZUC~x^i_y+2F6#i%${9f+51989Nk9@dglU+oUa)#8P!^v0KKL8rdDQ%9W zAXm&%6YNNeZI5Hl49t3xyf9 zx!W(KP0YEWE-kY9Sp&+QKF4_Xrj($%mE}?Xw!?feoMbM0j!vf~99zyu_ zkdyL`TcNbZWe@pM_&Z6?r#Nz9vxLtr&0;9xCq)qlIj?g!+PZ4K1_7BkixvZ#3{;=?MA zsP&qwHjVvJ>HMLllX5#ne$VcDOS)Lh7#+u_PQn+kF2UWxW8eRDj@umsqF`yt`xr=Y^fue502hHl4AELw** z4q(&fzq?0P&eq-j7VH)sm>^3&LY)(>~S0|nMWcR0Q{$}!8Y~#N<4Zf2q3G}27 zW~XJ1&)mym0;N4(D~c5|+>!1{z`#rz2a+SH`oCOG#-E82$>vY(5;ls7z) zaWR#FZ}7~RJa!>PQTwiv+8G9NblGamIGo4oCI}tzSRcVnJaR4;j~}ke&@D>=@5O}p zwC%m~r-81fH2EM8l&D$k1LbIev-uWGb~h4Y+(@W>9QhcyAisaAQ0 z83yz#dl}2qt>Tf8hvRRoFXUvM-#X_zO*)w^Hequnt(uC74H$R!XhXx z^2g$KI<|rHpd@W>Qy~CR`et)%Ggz-I@o(CDApp!7_7tBa5&^MjGpHVy? zGClBDd=X2rS{R*7s#E+1Y?o_4hSU+y0o&52?zwi!Pq+dw9Qw`YKFROCE8t@o^aY|#=!I*q^7$*V8-0+|= zyHW2zaMfO&9F)+oE?4lOPqJNLAJ21iy@FYY`bnqDhS?P zW_(r3olSmBI5B@z`?osiowYfSud>ISE!~wkFIK(r%^yJFm&z87wLNJ-ef;hZ%G{h> zNPRp?ND4gO>-S2koRg86lCo*tEg$%|4{>^78OW_3Ps6ptvV$M_g0Cd@mEi;F!0(5@ z5h-sd>cQs2d9!c0GCE%cCCl^3Hopi?=X+{E|2vG#A@=Qc6m=ivKqh9AJkmia)iyRTT>_QF&L9Q)|vH*(tviOQ;DzMD;zL&zugLxzq!Fw5@>sL zB9oB?YN!Qk{6W-rqly`j=F@upODy4YEWfmR>W@%2p9VwxywjU)Ea%@1&>;D{VVlWs zPDuG&SJOg|2T=L&`BDl$B3gomx8U5EeL;>;aBv@t(0q|n0m%fK{Rr!Wp?kbX{>7#|C z^)knR5&|r)&@o3pS8=fgd`?o_6qCw{ zsl#wUI~04pc?OtaXr;(}0q{Qq^e+Ahau!NF^p#$y@l|Qvix&%FeLeYIUs608ElTJ} zMWD{m>Her&icAshN5BLR>fe}eR^YZi(K;@b^t2KMBc}JaLzAA7BZ^`gmki{wT36Ku zyWJE5-*DV?+mT?rH6HJYV6YO8nQkQHZ-0^T^-u|Ii_qjo0jtRviI`W(z6H~hIwf`e zUQ|(`@@RQAs7^@~Ho<;gx+pE4+g0wZwt5x-baL);=)BeaXFvZL@T3O|By~S1Z-r@P z(SdkZHMiqWAG%0Q0)hvpz5&a9iZ{vPK?}XxWI}p?|3E!2eA#{m@Zq@x@QC5;82cWY zGlk173E29x1mO*0Vud6W{9hKpG3KRV_Db5?O`#a7Rv5^f;b-NCCasR8g?0)X{2{Ff z*f*XevFpD0w!G^QenN-wQd%ADB;32xTMMxP=qyG%m-AZvVv?LxHB|Qj%F6t8*}f@c zGkT1H&BC!RJwJRm4tQ!LuG;&sp2}7&hxUs(6jK(95p)_zTKbyuEW^2#T>Zdf{Klt- z2@oibp+8@eiPec7^jU1xO}9PS#QkbCXdVF*2&BfKTuAM_n`Hw{VA-Y;_d0K5Nd{@1 zpe5mxf8Uf!ff5InZK7Yx;o5JZoS&u9i%OrpvBEa=5kYp|4t6_7*Ulyjc|Vy+vU#jJ znn?^%%u$FcoIBuUceuMr-bCz)=v~~I4+Yk?@JqC*6IZ)G9r7SwWrq%pJ&$kIm1U^K zhX6P(#Me_pHd3ZINs)Y0tCX((U7_}PyLLvsn@7dos^*Gvig$uZB+d-A>&_EKtt*PU; zsFujaXxc<=4!fh54UG-!#Qz%^E4jZ)J(C0Q%y+1}!ha$;Ds=B4=6S&y{r_XeTA_-9 zC}ynU=e%IC$b%39RI>#4Ie0!TNv!`oW{j8xI|esp?rZ*ROQ&cE1fAqOVs3>)XJqL6 zgd|^Y?Yk%TEMM==iyfcK`WQ_n2(|IFUzO3=yGWG}UY#Ol^DPnYZhZ^}n4kaYnI@Y# z&Ea6e_vaQ0S6FWUd^++!RBT!y(I2kq4{Lh8sQt$tOwe=gZB0vJ%iL!v3VQ7c!87gH zv8VNCZ!vE-Z14QWKJ2AU3Ki@b?gsL8ut0lEuTiPMXT$^K4q`5B>GzdEulu{qd-_Y} z$Xc6a(TZB{EgMP4tFX)cUc6-GZTBPQUh-NRgXv{8 z7UtmbA3}bVqv*Dkn}i6Su8+a1n69UBYrWSmj56B=kd3pCbB|Ap&Kq6SFVc$#i>-N> zi|5gcjR_nDUie z-xfSxq_H@qT9rV-Np)|lg@xVmNM2yVS`RAIbsQHhpJwB(3WI|0&rR0UGd|L51#xy2 zcMmP@W`vXaD0F-73^`y>g5OtDf_dq((1S1?QL+GT;TL4DWa^kwu$8J&VgNh8UShF6 zBJ7~&SYxyAB(9o5^P(871jSJjSG>2rD84Q6ognMZ%Q}G5NUFpi*RhN4LqbgV0!Bk} zk389Sy5r8><7R-K(V35oxj=R;RkU@?;M4bi-@t*ce;&`z?2qOZ^}lVJ8Xf8#ZXiO7&iaUuN{&J z6PTbyiRZwz+*jJ9OzOvYkBNaapiv*ImQXN|b#l)-%-j9#ohbRGLRPjdJXchSw0`h+ z(FF2R3rXeTw<+eA=N!H2o)zvQk+Xeax8gF&wnIwnL2I-#cLwOcPD5;PO@e)+k@ioakao<_8$ervBe>HA*vEWU&&JlUu+rSj2Vv|Gix{p`9um6y;+H_-! zN8{~kqcPahN^1)`CCPW#_9O;USg_29kCK;8;hdi;juK{|@`IA)m-|AOXqLO9K&Xk1 zb?R1ZcnL}bjp!&1P9|q!1Cv(PNz>vqp#wNR#B73txq!3>YX=lpKcds zK5fU=Znty+iyO=N9dKpNGO~eZ&20%Zi-?~~xEx*u1uGkrSthk$KMSJ9C2=asn+zKM z`Z~<2awEbv==C_a1SL-<1y_oKNEO(gu0EO>YyVI0HL6DJ z88TAy+s3lw5YKFNCqunkcTcC>E&wjfJH-xc@&Iy?pcmQ{4q?83qiu%qt@_-GGwqKU zY>)H-1^=!ksIeFK&-RmsYXtP+))YT$h;DA3PQOS2`<1n6Is{unG)N_sqb4FajS# z9E;EoWpnQkEbBXxeLqqG&PKm}JK`<0{&w>|FS;AAF3jNSHRs8PKpHD5vNyR^SlKCG z-(WoS{<2zp;ypr!hUO!yKc|rWVFWvhmKW=H3cF%JC_4zN2+!jMPbl-y1=UY;E9Jd6 z$zLx0xf;AK_2W!YG#A4aIVXg)(15kD286*U85X}J~(pGXS_2e402Oh};<*~yvy z>>hL*b-giiCao4dQU*Mjt3B;W7i#{L>GM8iSpGB=ig|>K*GFdhl!oSKfbkc(uz0THxv2vP>=mZ6|3doyK&pSWdL@g60c&4~HWdlQvOr4BV6AT|Z}} zVc^L(5b`qnZkJZQ!tHCzfA`QnFn3+hZ1yGFZPV2!+4JC}{T|I%2I|&H6ci2(G75rS z$h}JOl&d7#;}6bFIw5A>0ZulipAoY9PDz-e)o!N01|SSrW&h#%uI5YoF#!BB-o?8J zDq=H7XR8xb9H>7%Emn;3H9I$&T^y{V_b9>4;1p%>xNIXl-5$60+DEqmD|Xxk3H*{C zL6v5=dhTj{ny~c;a%Z-6x+di@Gi@X4K=&Wd=gc8A8G(`q&qzOHFUQxQmb8ge(ckI5 zHRYm91}>0k3gRi}p=uxzwwpnmj+nYyv<7r_?~=B=4fkk+ij#nTNvep`g7|EUQM(aw=2hSp^~GV1sXEwfaQrRXbVa&pW`N{9?u`s z#fAIXx}9d#`R{hltG@>vAgPVRyEv0iZ>)a8Eh50bB+aYAqCRic<4KMkX{zO!MEGCF zTd_nEpoqPc7VX96bgzxZich)nM_wL6P>qw$@fD`V6mA&tI@*mD}L6j zk>_P~1v?)2s&ouJiL_qlKv1FB3q}v(5l=S~R}vpw5euRaU$kV# z+5J&Kd0*@Qf%D?>YK%BEZ5v+$IoRSrtc;3y4RHHpd9b~~PS1I<=nQXQ&Ix3a0y`r$PTJeH0cn; zIkf55gaOCukX8rdd@3Q&_FU?*8fbz%e#QP?8Ls~d>uXAEPdRpyJNfq<$UnczAAWGm zCr={yWZf%+t3=C^B;u@yU35dTp+7rd=Ji_(TcV3 z%fb)y3A^xU|HDnmFkfG+cZHk5O;~n+jKvRid+n0H9?T{FjkVrmAN6yXSIUXsR&SSO z_!+pHOhseADwiI>B+qD-|1eT@|LW56t^6se&nae&MpyuCU710kx5A6y3E^xef!9G% zrr6m$7%ugJ*0HqK3oZ>hNeFUYDOzZ1PUJ%BZk+d_b(|cFIEav>&oq>D|Kc{qs_k?! z+kqMYM;Jmzgd^~7ru>e1B%6zdymzG_5=V9b5YGysmB^>`z2pAj%Q6&307Yxt?Zwc& z{+U`i`cz8jUZi;;O|xR6p>X)Cz3qC?twGw`OvaxYlh3zRTOEze)byfMa>gZj3^qvnoWfu)YNYW2v@7cZfUWf3L2VXnsPN^_Achogi9x1%z8KClM=8}I?cwr zutbLjNee69cRydA+-&z;7tDzk0$e3js4V{Q+nS~x%^?XOWkh?Z54oOF;XiN*vpa7L ztkrJR+%hB;8Ddkav|pb+UuUWMSP(>_pD^k!?)Xq2cwj(Wn#A?7MjOQ~C;AacM4Z?1UfcCk9@>e$&#UbEb@n+%#1U%Z8V$W+{*a` zmV~cFe@M#j5i-HZByf*lkjd~`9-Q6=WW`tdZvWjQ!ZxF}=_WmjIbLjzD$D9MSR_%p ze`42@OUE=rUaY7>^)pq~|BMuwalCq;4|;>3h~v-trOeDxZ+!=}<9+6~dsXj5b6yvK zRcLoTZkY0Ta*n4Q6x%DBcCP=c|K-33y zqx4DPCv!#R-``oU%dyP1wuIUW{<38AlPL!Lk|VliZ=PIgtc_kscyM=m9*qlYQJ0Tu z6QRW3O$nqPRUUG!sS$+VEvArD%Hyp9j(LQC8{8Q+#;4+-w%&HBnRB{P)cf@I`~Bp> z5}^1UmVwjhVq2*1SG=_scflPYct&Iq8^&2gr-u|TyE~11^j`GhwJ-NF@=9}Jso*7? zMUWSZuTebNU+_uxUVm`#SWC5ck)~@NTVXwxf(NEPtr!i@$&19IUNjHAdt91t2d=#% z>6=uxn|X?s%1_q&a65>a9EL4dJ?>Wx?6f+B>t3E6LuH)CM0GI*Cnv447(fRLKM7lW zIvJ#SlBM(=QeTCvQ7%6#4(2@^>|j$>mbJkYj>EeYC&MH$B*)rVOyTz!f)gI?+_Kmx z-L(|KZn|+nWaz?HZ5s2SAX(|qXz5xt`7Vv(=8($nIX;aiWvr$ z6%jur1{;n>@$ceS?24aD3$2Fkh2k~danT4R-lJpQ+VYDgQuV$N?lJlpV}X|(h>!Eb zP`gp#`6|Y;kwuB+*Cx4rhZ3!c3w>{Y$hXXhWZ(~nP4LBui^>LI z(IFB_DkE(TrPqMB+kKDP!OM1+XnDeq2%9~u;TDMS1Ma;UnhGUlV4TbqXo~PPZ>+B= z)1rpK>u**L=8GuVkfGsBTf=9x-3Nn})Zev6{i2H;Cs;@h8$NLg;6&`;%MyG~&sNd6L^raxD+b9`MZGHe(lZ6r3lk)MPAwrC7o z7I>#?Sp61{)NcL@zQ!hjx!Q*~H}%+j@m14@liV{z!}C6)j_FJ48Z=TK?7c={uxM)y zFHGt8uL{s4b;`kuyKl-G_daJ;U5ih$m z($C~`7n`{QOJ%pENfI!mb|Cb*L|endhMUtJ;&KQR{u**kY$7Ium<(=^Kb-=yZhLi< zBD}^dBz3ClL2k{)+uvYLJp1jJW=Hlq__H&5*{HE4S>*a29P~I_-|K4@f#f+| z*R5fL#d`QMkE^{<+q-!-{8CF|iiI%+4NZs!x-bFNCn}3W`DgeeepG6ji@()%u8u_W zpzE(VWEo=EtV!|usnn1h;&6Zc(PY0mApYZkRVa6e#iGznSdfB|g)4l(h5$WS!wg&27b5J{SXf<)Ljnl`vW)+<2BZ?if z6zbsLO(YImKdN}vlSFTrl?O9@A3^)<(&9&i7@huai@Bi1_epzp)J$GdZCCyM-?Y}% z&w7Q5Ah$;lTgUwBh}SG^^9nr!-|U+7Sv!u62nA0Yd6lr0{^&i2z63QI!IOFTxm;SK ztVlXed{4fp2MwVjlcp6V6_AyD2wgH=Zb{9ozm=h68byL#4w5j`Mn`K9|glcS7B}4OT7ozw1czc2_ibvNtD=xKV0hm%$=A9DoLo%foIhO1sYepZ;m@> z1YYa#>R5cOFiKwyOIdro4z`>rr&lUn4Ku3|ULp_t_qd;2T6=j$*$ofG0kHU99cg)< znYIzP%3gRr4lYh!Y5Uhc%_#4xP>Evv*e7+g*XYhj)>7v25YTM+IFpbd_+GX8TdUvJ z>CAQrDvQ`fITUAIm{Z{0cyJ_H8zB&K?T7`i6uRYma4W02zUtHe@ zrQf~ct+4L8VJ1-P)G2CA4BPf<-5WXiOrOYx_e|Ye1fYy@!9LYhH-+wJXH@nf43)Vr=VMu=d-n9JnwhHY&j@=-eoK zAuKE73xPl6d2P!xQJPTk!I>Avk) zQi#|Z6!>FBqFN|soz7+9Q=NpZ=8jN^_|4%RSLjZhY}&mjrWnWfX&)}SM=0o~R*i~L zn1q13?%LfNyb%MlP3ETGxYpr4%C0C2FzXw^>5Buy=ax-7YyHL`8bW73MHlj~cB`T% zYN_tFQuu08o!WjRQFw};<349W+7SwN3RA3pNi;D+) zQ5Rcf&;HUK`}JPJ%%dA4zle#2uh##KrWAvWjh%%3$?}apOf(;krsceM<7=N99H?|k zuTCHD>rQXu^&jS#ime#c>(90}Kp)EH7fwJNR60Qyc=@^CF4k?EGu?SS(E?3crA*zX zQki9>TWW@VwBxq18+gg`XZpqH7(xN;u$$qk3#}0dSNQc1N!0+n`l0tlIsV}T_+IDo z#YF)w1Up0~q`8wU&&f*BqDJ_jxJyMs?DEk3FTkvJsyQfv*0hb!R#){xW;1+IEi4RE z8SCF;%#YzoxL$cVv2oiaqU^st}vP6oUyE=O|{Hf}7d%PXEPo|*D8Zrmm8 zZdGkKyI;pOnb=Sg_#tf7H=D!Lw@?mO{5tfSfe|!aq{;NXaN_Mm=nnep8YQ}=d*-?w zy*@cD?eAGb%GMT4M%i;=pG6hVw;?zZ82gNLSx{& zUowg@wmzit{HhHFdIGO*btVDEhzmZ^6>pu2kMsNNw zLg1#|b*9G4@!H*&5IoteuO3r~d8Ja)Au)`TFYr3_bIbI}L(gClQ*nWs|IOjW%-lLc zDaXnNjGc@)tPGWv6Yl(NP!las2El#mdZdttcR>sR`od-JtBM3Xc9*X=*%$tD?!rG4 z3R0+s&L`1qi)ILluL)L(bN0cw>%-CRlzaGa= z?#JJ`m{*1QUsdg5EtDq;JeoP0E?&KLP<++M7G_1iP(O3{lPZAi`+dDgkH8YeA6}Ei zi4Yo2?%TK0v3OzlNLS-HjbBar1aWUhKOywl)8%Vvue9a{3W1hSSiLK@S|_*DQYCV1 zwhjCnLuind?CWM#PM=uk{j%w>N$aTE6%eZ?h~vH7P{yV8^RB7_C)s!XIKr&F;hPtE z(P26_g>Gs2%#f1^XQSeQ-cFfHmf%`x2`(T7b1oYR&+RSc#|5^-b ze63UfBQ5pCHDhFy*=U!5k_yDSpHHdb28jyd6#%&NlK@26uP@6#nW>W4^U3GFLV>;% zL9?XPFMIbH-djFJM&6Da3c80w@QTc>fzY6z3K9Bm7FET0JL4;x#3koZ!OT@K`MbL~ z#q3wQ-ft-ZB@-lQi0ATBJ-$ynHr`mtq^^XwjVPtZ`reDe=pc=RhWy{-(Smn_ zes<;Ycye@02Xmh7cp)5S?X}IHNO3h9x0F4)aYYiICu+W87;lBNVjuQ@n!WK_MmZfj z*#8Q#PY7VFxWMVNOeI}BpzFVJE9d0} z3|u|;^kzJu3;~)hQ`pu-ze)KnCx=>o)-W-8kV&J?Z{rCEU-1~A`yV=k(~h;%d}2p8 zOM0eAIF=XgbsyC1`|5(byb!fWXm~u@`=-De42(4_S{8tiNd}BqHcReufYIkrDat{M z;EYhqhTA=-AHc^D1ta$NKseWkX5V5k$+|KQ9DeDge0pD~VMk?)D+>~-ouV|}yewNr ztUnxF#rT-4kIx1FHD|;c`?)^uLm_A)milK-#`?PWDW3Pp0=(dCF0`-U$ndwxHy#}x z;=e7|3MgcqEd?GZbA?Ol)#mb%`5ZWvggT@`A4C-f(vuS$$U@N@=w+k@?j%2?fA9F9 z9@~k$L*AvCJu1#z0UYZ8B!_y>zdFzJxs{no6i&g_Ol0j6ldPs zZ%F++RG(b8$ZqnCgbN(Vm|3-^zCgvH|MI0Ts^zB<-M)Ft9t<@v}D*aQe3x z5A(JRS3RgjuvHRpFIoq8AiJo|o2enq4Q--**lwt51CI1T(f{iil+L1I-!H3z>>S!|!sISvYX%Nf}j?Wau@sGb=d zl5axhbhUhIeMv!A;G)8BeveYiVB+CQORi4LIHTP>aiEr!k$2^dH8+8=cO+LmmpC?f zyjm>g%VC=x{!CHbWvFy!XExvVCNV=aa!22T$HAs=zXxlQ#KMk-d zd8>X?#&xQf`~{tqil%*PZ}>S@^`_)hMZjqrsxL6J*>Ex}^+n)mW3AS3As@sVNn*r< z+_6hr?woih?V&A z^mrQT-|#rrS@F;|TFwa3a(-KWi$Hu!o{z6HR&~E?_b^tHzJ1x(A07>uv+B@#_a&O9WoJj@2{u*B2Lg5q>cZ3M((Da& z*KfJGbntsoyJl#lVyJ5jChZL0#1Ww!it=M~h8;EjTE^nf%;+HK=?fBQUg!f%@nAc0 zY61JKWH&V0*VmNZcyq1O?}P`DrI4|xn-?x<(uh@0T2B~UxNU-w)%_V=IxCB`LLiCl zv_qq&K0Gdbj2cR4vBn7K?l7%V1dMYJuw@9$5MFBTcSYp+b}jg*1{sQ9Nff(;{@$; ze!!^&Iuw#GGer8(iIkF(dLAff%xF%L*obMQ6vQZA{8o1Sp>Ka6VEm&tIQb-7-Kisq zTgFh>yi`PJRhQ>S-yh6l*;`Iz^EJlJWc|mgtm~vw)MsfM7BrEz zi!c-`fYZYs=U!QK?)jR0tYUe*--F3C=xIFE`pV&VZ8bUa>s6PE3Hj!+Cy9GWB%DAsXw@Y0m0sn@T|1Xqh;jWCHyhGODfafMsr-@&d4US z%%V?VIq4f4?cycF8kL_g`sKstDv!;tt#RqBeordJ)py29W6DuSA?n(ufu=5H@a+As zSs$5Q%U6gvZDDetSkA*%e0GN4HRRq-{l*7C^$tw6pfN4x3+yyd%0d?1C)C%VeqRRr zJmUu{_l2WyNmQ_JwzK`V!uVO_=ZCHv5L5hWnmwjUgzg6;6q5brH*=nra>kxB-$#{@ zAM<9>G}nD5EMT|_1#uq)&NV-;L95jH+R6Xn%@OT+?DMzcvcpc}3{?jY{<9QfN@S3E zl#HfB;4X7*c1%Di#N;;7#J0kjAjpG#)k>ZV57CK3#o*r%MiPx|8#KJ%i2jn>ZNWig za(62!Y;D6v9#$Z=?rLnxqn?l09^s9TWrJ&9$U9Y%p4?k&&qg z3eBj=_q&L8UWKHIn<0xE=|>*IrxhIG^zt|rq!05r3G%_UhW?_kU>w1a-< zX05^D<$^B;0CxjXu=3+I1^Q*fvI9!Z94uz$>WAjRO|5Dp5rDc}aG3)ksMBb&lhpcf zNU8X^v^Y1L@OLz+O|TXuo{pg=I%)?R?W{EenMKFVP`5av!So865EnX>VruURk!|!c z@Tl+bzuXLV_4pE}0QS9_%O7k!U^&oXryjf7&aJ`0X18+xRVY1j|htfkGe83ZiR&MYoa@9c{e=Xz(Po9Nc&?ct;>S~uic zr-cqoh_Ut3Kg3h#r<*A6oIqzrv+gqKj}842 z2qUplK@)!WTfj}w|6D7}ZeA`@jk+aupszKGC%*YLjb-{yP-z%p zA}fJM&u>n(H8MDvC=DUBBD;$ax|?6~UtN4}p^#X!B>#i^6~3q~cg%AFy~Uibat zWGGVrqto#p$00>P%4tqAoQa2fbp(Qj?|!z#G;zp$^iSq{9oIF?;(Q>b6A(Zeg?Va_ zbHbugldoS&Eh|YIz)42evmVa-xG>1HIu+V9ACCK)Q7x3Z8@(Mh-8_YIFr8u*WxEzT zZ0>okd)=MA8z0fTUN`G%EfbVkkJdCNL&B5^`vxQ6`H{NbW_Ga!C>0d%HpX z^PrsAZ`u@cZbFv_a6s7)o(GE|sSWk7dL}frv0Ax7q$EN%0jn^DU{cL&0az^YY6O)W zK=Nlh2;`?^%=D3PGIDr#w|z2?Qov*~kB&skUEeAO8@0eai_M;b=Hm6xZV z8O!UdQryx?JR8S&*e9ect{3H2vRVw%cRTB4WG;rIwICO(NK*vLPkhwUt@EpLBN_Mj zy4sN4h%vgEsbu2}MoCW}uY+IzhpzVkXY=3t$IWW1 zs4A%%(Q5hDO6^e+rKno17PUfAd+(9Ns@hVtcFm%86;*q$qQnepE3p%T*uT5yd(QJe z&vVZI%5^2TadG9oKY8Ep_iKIp@|{SN2P17ehLiC7p>`AR6rwISR;fdD$v2P2=2unD zM+Y=u1piY7hc&;?34)_#gPRc_^hUi71Dl?2Qr4dB=8u3!N>ZNfDs7+f4||<9Y^The zk2e-xTS82yVokm?^pu|>HhOk#6rEhM zJ=b@;-|kGvIW~v=<53hYU#I7j?!Nn^F3VII(#||kSg|bh{)Ah)vk&tw_q=(jL^ae7 z{3sydiFzt~W8~?#sDdqr-P6iDzrG}%bfqVu5RQZIf!t~b<-Xdh&tabdYi&&dWxlLd zsq#yc_4{_p@H{kqx5cks6&@+`j=1Tid7L>vusVj+SRQd|Ia`qV&=|MqcztkNSAoNt zo0j^PR>y++q3|Pf7%{5n5V&p0HEaXu>|7j8*Lj*DZhkgC0rP`pb9u*UJP+`xUhQP1aJHJD_Xo=*<#GylA+knCKqJi|HT-Ciw!=}Q~ zp-;7#$Hi=ksXtxYCQ8}#J*Kd9HpjZm)N3WGqUPdsxAm}QGknm_022m{&>3bNo%!BW z13hv9u`NlsFVDB|I=P@x+A-kcE>lq+m&|RW3^Y?(6SqKt%iF=Ej^Oy$&Ccc zSz+Y3|Grn=z=MN?9rGIRq<_vCQ5Sy^F8ZLHQY;Tw>*jE{>nPo6@PLKDzg_4-n}J*-QHV_ymD@? zsP?lZCc)k*14Rbrpesi_%6R4dy9z_2?;JSneeVes+JoZL=Z?5 zKRC=o%Az(A&`=4|VBidGjATv5GN6TJaC@O>_WR0RY-dS?Tu_+bd zAAI`jGWT863V5Y*30p>}wCG>IsEX<+Cpm@1qbI}d-`$+$L2){njq{DauR4GU2yu@Cgk*&`(}+5|1zXKmyR^fvYe3qzFT4Viwn9l)7R_5bpIs^lI;h|`UIYT@p4gKh}o1Jf#Wy?v0Kf!z$ za&qLP1##f`xK~}IQ!Zdw>U28m9w{S*7;hM^YdVn|_CNAE-zPSf9Y(B1^Dh7XD_RQr zH@vJ&wXZ?-t4%QXYr!7)Y#u!NHM`Bh23((Xi|3KR-)3my>q);{C85(Txi=cO8fn(m!MQ zCSN8$!ZO5EA-xx=EqVi7pE)rq*$4<`~-zQ8W4Rx@5=C#!t3fu!!(oE zVb>zk!J4tbQIsY5S~^f3dLfA#a4`hp zg)_OkbSvAmfvmv^2DnfXjk#DwbZehvD&up>sAEA5+tfYisk(WS4r?vjv*60RN`sWC z3XwKF5&VJaooqUm-<1jlHBuzk9nvaZj!y@Er)vt9VUU1IDy3ww$z~L_@eefIKizNhBVMQDm46K-xi-U_B+i%5=hWQm~bnsWzs3+^|P(LJY{^;{znskgmN zkJcc^YB~baP!1VScNwFOq&Av$yH_U3C5pqM1G7nIUFUn}X{bfQWOLPJ;nFNtVbMM| zvGr6yBZHkDo*MAS5IJTEo5Qmw31u0a-_N5LvykB<)_*UVeK)ENLG!ZPek?xEhx?EG z92waC@UlDI+zveIai2~RoNd*AXvBf(iyQS_0$K#M3Ljlau%kUh%kcSfzi-1O#UO1n zyCD&QN2kqcCtaP5mPuXm<|aEC`!9{cJ~w-;5es}TKmkX%AJT)_tp4r3%GzW4KHH-m z5f$gjsORsR*^CA zFK0DWdpdOkhJY}Ea~QP9gh;sXrui&d2AP`ylr8HIn&d*H%JY>$=Jy2d1O&xHE~Ut4 z-Men>Mqs8r;^M(!6vseZi1O#3^SY!T_{th=*qMXVkypV2yWtt$=3s{AZE!P229Ke} zexhuGp8H+|`yy1f4_t478%W(!I7$pZbP3;ePGUt#;8j>M&Z=MBvP9WwJB-;;!ph2& zGwm-lw2m0(mxN!bH4qiKZnoTdrM0**aa@Z`l>5TU=GA+5sj60}gMLcQ2$H6{J3zlO z@)e?ny`WmK-Cso7rjzqAJ8OS?poKcAT79lH@%Y=m2YY#|_~|a&6wA|_1|c_wg-i;|#=-Df*;59cu)_we zkqgO}g$2`l1#)iQ8=FR+5Q(J_-vi>A)6w$ksmXczs)JKQnM7Q_UQGT!nJS=^_)(o` zluN#taw|UC|1ORAZ235aPuld+pU0d>OtVo~&+}uugOh;8K{gVGzk$%nDgoq`P5 z9b2GB;!uz#tl)XLW+f}V`b>&@ChBU52BAOf(83|={Kcv1X;yYG({J0MTU<_eaV`2; zR2pE8{@4={kr0E`#|BEe=_;_q`PI9CwwT0VSD8c$jZ-*1{vpfNfLP_A9o$o-R+em+rqnR#6)=#5IddUK68z-iw*1e}pQo0A_-9L#@g$~nI>2PTMM%h*< zZCggQg1Z0A!0E)^Y9i{lSDwjHM@9zf?EDN*)z_Pyg;y&wm+M=!n%rFPgqusN_F}mA-sYw_K;8P1HMsT0?Fj+3RVQ7k z*BipO!g+`cC=)6USt?CotbCF7wmweTn(F__r~MX~GY(^s48xfw9&%c)|fuxMnxD1&d!tguAz$>-}b*NxUJF6T~{ZXi*}z2ZTXNR-E}euDe}g(UpD zuyLbrjg&-ybn^&*>jEgfw2pJDe(%gjsQr$*BphLLv*#;K`5)E zU|)xw4_4_P9mjxpLTkyQ$5mB2yT))|l+1bF;_z|PTA4Z;>gp)txjkKV!84sP=W2zq zlloD22U_ENnBg>XbtpP`xSZ)=eBE_c_M_hlNW=0@y&!seBTg%JxG{{Ka4wgzBMn&|3 z0Zl_s+z@2s2|ZmqsDSO1>nw9VhvBExp@oyEXGHzNU8Z_y4em0X(8^zWzj?z>yICpg zNn}Q(o7qq^r+Aje^wQZWbj*0eYm(+e|Dl?9H8(EBk*&0LX;tEfW8J!`AM%2X@yR^Y zXjuKFS_�LC=oxI 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

  2. 5nx_&hPo$OOZ;fcukQ4VQKUey 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 { } ``` +![](../assets/message_input_quoted_message.png) + ### Adding Custom Actions By default, the `MessageInput` has two actions: one for attachments and one for commands like giphy. @@ -159,4 +163,6 @@ MessageInput( sendButtonLocation: SendButtonLocation.inside, actionsLocation: ActionsLocation.right, ), -``` \ No newline at end of file +``` + +![](../assets/message_input_change_position.png) \ No newline at end of file From cc92f4e815d463dbf3734316d2226d6079c99bc7 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 7 Jul 2021 16:35:58 +0530 Subject: [PATCH 521/599] feat: Added new docs and images --- .../assets/message_search_list_view.png | Bin 0 -> 247832 bytes .../stream_chat_flutter/channel_header.mdx | 2 ++ .../channel_list_header.mdx | 2 ++ .../stream_chat_flutter/message_input.mdx | 2 ++ .../message_search_list_view.mdx | 4 ++++ .../stream_chat_flutter/message_widget.mdx | 2 ++ .../stream_chat_flutter/user_list_view.mdx | 2 ++ 7 files changed, 14 insertions(+) create mode 100644 docusaurus/docs/Flutter/assets/message_search_list_view.png diff --git a/docusaurus/docs/Flutter/assets/message_search_list_view.png b/docusaurus/docs/Flutter/assets/message_search_list_view.png new file mode 100644 index 0000000000000000000000000000000000000000..b13f5bdc95d688cc94e2f0643c933fa8c93d2d92 GIT binary patch literal 247832 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Ⓢ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 Vq*@*-5uTxO${dt=#l@EkpA>y)L%6*}%-#l^UC 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%0DcpLw@IOP-0IJT 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 literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx index 0ced7f0b..bf65a863 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_header.mdx @@ -6,6 +6,8 @@ title: ChannelHeader A Widget To Display Common Channel Details +Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/ChannelHeader-class.html) + ![](../assets/channel_header.png) ### Background diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx index aa072ea8..dc160952 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/channel_list_header.mdx @@ -6,6 +6,8 @@ title: ChannelListHeader A Header Widget For A List Of Channels +Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/ChannelListHeader-class.html) + ![](../assets/channel_list_header.png) ### Background diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx index 2152a053..3f725bd5 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_input.mdx @@ -6,6 +6,8 @@ title: MessageInput A Widget Dealing With Everything Related To Sending A Message +Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageInput-class.html) + ![](../assets/message_input.png) ### Background diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx index 822d17f0..864b5e49 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx @@ -6,6 +6,10 @@ title: MessageSearchListView A Widget To Search For Messages Across Channels +Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageSearchListView-class.html) + +![](../assets/message_search_list_view.png) + ### Background Users in Stream Chat can have several channels and it can get hard to remember which channel has the diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx index 89a7cf0f..b1974f05 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_widget.mdx @@ -6,6 +6,8 @@ title: MessageWidget A Widget For Displaying Messages And Attachments +Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageWidget-class.html) + ### Background There are several things that need to be displayed with text in a message in a modern messaging app: diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx index e9891018..364f69f4 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/user_list_view.mdx @@ -6,6 +6,8 @@ title: UserListView A Widget For Displaying And Selecting Users +Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/UserListView-class.html) + ### Background A list of users is required for many different purposes: showing a list of users in a Channel, From b667f0f4aaf0b049b9a70a49eaabedef417865c9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 7 Jul 2021 17:20:26 +0530 Subject: [PATCH 522/599] feat: Added new docs and images --- .../docs/Flutter/assets/user_list_view.png | Bin 0 -> 1092242 bytes .../stream_chat_flutter/user_list_view.mdx | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 docusaurus/docs/Flutter/assets/user_list_view.png diff --git a/docusaurus/docs/Flutter/assets/user_list_view.png b/docusaurus/docs/Flutter/assets/user_list_view.png new file mode 100644 index 0000000000000000000000000000000000000000..4beffacc2859e71e1a2bc6d6705c9b7917dc765d GIT binary patch literal 1092242 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�?ZMd4}!`1=wDD9#!u^usoQ( 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

    Cb|pcC^PH2|7z)-Yn8_Vi#yLDFu=mb?#@a+ zjYArC0myUD| zw1q9=&>!zsAp+TttL4}lcE?uKRltmgnADdC(x3?m^iu*$mYyY3GmYq#ziwfLc^o%? zKDK>sOC>;kFOf7=c|6Y4hxF$tn4?T)C23;S8BGY|Jjf}kKDv{@lnA!LmvrrTd)P#- z+7eOcHP%uLt3E=%^L{hH_^qe)D46g+C?qNVp@&?RO2nYaykj7G%6+$3vq~VR&gx?K zyHM!+znaH5<-zi6GR&AF$19u?%Fg__P*+QTy=DvykdJ8{6}!LjhtRHp5Sj+dw-`(_ zBq|xsny_ghNmE)pAoj0^Wi^po<#(+_a`I9@em|+-(JL=^8=PP&q;Mx&A?R$peWL`x zuH|h=C$WH_DSnV2gt!hP2oi1bI(a$dr6B%by%LN48!f}(Wkc|3amQF(x~F3bx0?>b zT;G{SE@n1;kfT8cSqoX^$|1c&=W*92GSxQ1`FaX$EHWzsidRkHm1lH6Q0Px!SL{#Z zJknkNS4f$(8hJ_LteYE?i(XxWSbW8!@5!($XDu~}Lbqje`rb_c)8cJwhIFrBpFfH& z`(hZ)?2o5kR@Wx_q}|(aqTjyfUx26$yn5WMYbg|K07PegzWsE!_4|TAYZpb_ae4|4 zuHLWzM*e#|qT4TXVkM#HH@bXs6zV$eCZBbn_W z+r27)#RE;xzQnWBei?=Yx%2! z79c?r!6NhAgy|n<&F!d0PR^fXS#a+)raIndDLo&pgB*P7NQuxNzkBdZRO*Z=O^Os3 z7COdz8Q}>--eWu9zH2Tn6+l7QDG>*8;Tx;SaFKZ4`ZurYX^o_+%X(;{$|sfWH`11I z7?sR`e$fs>+5-xonWjtrPz!=(g*MmK#t4Fy$6-Vh*VB?d4xJ+n(A)8h^vPn)IF82c z?DG?tkBHmaf(k>cSreS4H9u(Yj z^~8%Ql*72|Kf3d**@=vywoQTA1wz5xSA~jFsPk7I{0_eXn_4nf)722ayRJgaiX$!W zoNuNM%Df+UuT-DBo`#YH>AfDEGLCBeJkLn;q8@4!TYtX5S%sdddY{IHswFsDadFR9*W$p}nN49IM(<^}W1b-NM z&{uM5bc}3=6ygpgIEqQPSY@&^X1beNrH-qTWaV5k`dz;BI`o8MSyJ+aLHF|$Mf)j|AQU1@E$^Y*&WI7NdNv~P_p3SRHLoi)lLZ3&#^)#|*(CjbfO$&rqd z;4~kz|7*A8nGFshn`M@145`)F4z$bSizKNKlkOZd~WL#x9 zUqH_HDcE+;<)LoG;5>0Qp1Ae2{Y4te%}2>6ltV`mQCO+kT}0-CJ8^oPCDqt9>v8Y* zoFyfHnYeRqD8f-rb2*-hm{<)2rWA@Ak(+0-N>PJSd~z_{ATkGRMIr1pHK zA_A)tkWaHDYF~>z^r=n&Ujv*o9DeqlpgLy}7-kRhRS`Hcu88CTNX$s(f4ca-6!+am6)|Fc^T~ z9Lpyv$dWMD1k&+x@J(j=p-!BG=iQYOk1QnK_9~ zajMsFacEXfhJYtS5>SYDiT=t^V$ppR{fJDr00r3{PDN;WFMKibqsVzKM@r^ub1(KL z!{623>mvzr+Rvtex>~sssoBxG>{jm40&A!Jz_YJ_`_8~fAY#`i0i_v4cI2nwr#-NYd61P(S zj9$AR+6zc-eJQijR&suPvNJ-Ys>+!50VMtz6@Z8F&_!@IJ&$^?gvHso)?m|Ic=s|S zcD^=%3n^!oI;I`k+<7$3`Phhy`~B0xE|s@8ZCc-<=4VvhS#0*%Jw4KC;plC=7fw_? zs#Q<;w(yw#dw2~|ma!%cM7;JhTS;{pT#(s9Hv_#)`5X||`Ub6$E(!o1E2weodgrlLW-3$^7K z*DWJ>cw(y~S#I~bv!p^6(^$*w7dcp(+W_K&r)9~VC&9rNDq@9fq+($HkV?YVL}JC+ zt62^lytGn#{#VD7k;NOzjJy)`${T+T1*1e;PnS!k48gQ|CdF|h;5CJ`ZTs0s{olw3 z1K0s(G;>6FOna} zAS~0YeVc*&NF>ljwzXQSd+T7na6I7rbdDZ#i7e1RE66RNS6P7O(jvcSS0m*$Tco(V zTTnXY+qX8leLswTwHp%r`GvGSBY}S<`|>4%aA}=+e9X-}JyAvA^n*=bDs0-H>WV4P zjl#5Li%ALhJH*<0w)*3P`${`x>Zc6&mzmA}Xr@S?g6t_KLeS)9qMS~bYYy}NUufJ| zHFMm!j-aV5%;=ZK&=2XihQfFer_X_=@nCE(J}_c z8L>5>d;NJC`Ip~V)Ly%#NyIr~?h{Wk+1SK&LWToI=J$S>W}`|;AJTho<_>iQo>UHM z>XaC*IR1^D(}t|r>O%GoB=`b{gAbSBo3NSKo-v8jPMUJw3jh8Ob6kEOnBMBzJhX$Q>7O<-Y zW*h}W5m-w)h+&IgpGa*B!t6~yyLEcmA#>V)X&@Da93bPqA%BeqjsF6=#w?^a(Lqg3 zt-tVwz~}Le(G|+arc`cY0V8PFy{sy)*Iwg^fVUYIg<6I6#zKudHcYys-1dw8uv&%P zD-(XeOOq*h7?sm`-P-H1L6s(E2Prj`Q3aZ1Vw$TjLfh`uIpx2)_u|c#kfvRV(lsbo z8It+7PeH|Fd&u4rKL>U4+s+rau?|`I9kCSrF{vfCWbvfO_i;=4ilO>kR|3?g)aO$* zT8O2hN-AVu2?RJrYUf2b@tZ1c0AcT&FFtV4I6Jdr@F!sQ>2R=qXv zpI=8!rW76RuOX2sa20_)IAj}M2n*%Ocj^2VxJ0-7Eg#Wxy{h3&vK~D1NWm@RF~}Y_ zJFM^No}8x{&{2iYt!5*hC>@;bK(O&4(Xlg}X$M~1kFfQ*IGZ}*8pHUC@VSKwLEX@( zaX8{mmsa!Cq_Z|mmX2s|>B~I3SdE`xhnAPNg$+?t3?qflr-_buGH#UI&r+A*B50FA`FyGfL2St*VR`f2D`AE|WUZ2ut zM3uw`Fc75eh=T9yu~U?JzTM%JHmhmBD3`U$x}><*52P|5RC?`bU=NS(UsI@D7HG;R zV!RTtN2}ankUo}>8gdPzfyzr&rT=-JZKTlT98!}NEa0wGg?Air1}B9U_g}8iiPHev zjm0d`xF=zFCVt#{eV<%sG86CW0CNn|nB}5p@nCD*mv7E6uyv+~__G{JHCWn-G4Vu* zwfM;R&8{shMh30CBwx_D*<VmBGdq}wcrSEi;8mdJ( zNt>)-jhO2Oq^n?33)&ys3ohl^X88lX&m{R_XZ_gnq&);Yim4kaX~I|09n}er`?EEB zV<@rUNLIu~ltjn(gYI`0Up4YH^*X`wgIlk57V!hibb6jL8H9z$EiX|g*QqD%fkR3E zem|S_T?)37h((WZvTN1KvS!s7id*QUHjJ(|R?ZP6@FT}UKpFBRd2Gap3sPVpW1yc) z<+D@fpLf@8^ca0;ELf;8L1DyXU|{346_y)|XMMhL6crcm<@nBFERosycJ-&bKBdWT zg9Y%DccV$Ap$FIf8NMtbTHsILLSxj-@~Vc#g_YqSgmCsd5H)r{S)frm-FA9P(g$0*}kl@;kCV)yAMv>Ekn&PNu zu#PibTnOtwQNvij{$=IAJfPsme+=by46S-@>blO~-N#cG<4M?bMz$d$G$^Xs#4H6J zpsdGskel@Tyqf9D3)7^>VS~Z^ok_ODd7s##AW~NP1kAEn$&=z2^1WHJga+-`*aNNl zK1ZTg^P=@dR-uRBWWMlJmPItyw`)65|yb0>ZsbnBzy1kl~)uqbcAl6-||K550Mk4egSg`wjRaGZ(p zVZkv^xtkpWaWKAZ5ua-lj!!=Kwfcop6+00-XjY`N-;=P8{nWNVky(*S3{5l_J-AkJ zv6mjIpTGlzcB_&JyX?m~E1k9FfMDs_%1W?_QQO-;2-$WHF$Ww+76jUvHr_`Qb-1J0 zvMDF5c2l=UjA8|4i(=c1?dB$1du|rs`p{TI*R2?vr)0?RlZB>kBn8N8f_iVi>X7|4 z{&PONGD}V4LHsl2*5l)uetdpEI=9JGe*H(Yj`d_#0fRr9Y%I>Fp=U3&l7s(}ibuD2 zJm7%~`skA4U#R$v1#zv681msNrnz)hvb!B$?T?M+(m6IpgyMPBg(v3R^V!YCG-5jE zjYo;|Q46ZyHRDb!@~+Gd1Cq-NE9rsgk*zl|y`QAUa%VsVJi~&TiW#Rg{K+??PbJh` zUWqHOsP(-T)iP2Jo6;h$Ct?d@|Ln+C$GAuvO1sI@ATCuo^J?`IpVlrOOXl<+?kA2i zK$nk(<71p&JBmB)IGq24;k0oSyM)tsgqg#Sg{odGy6!uvImF@0J(q&m%xf3?Pp)-};_OY@JM27<_=4OF1u%e- z0eHM}qu*|!+OR{xrl4G|0KCYwAU@GG{jxwxFsjt7t>BlK0 zP`tH%Tm`t+N(7?uMhzGJbWc@YCfN!GZ!$teo}XiB!h9;^7x6k3;b-R50n?1nmFnOJ z{;pJFb8o5vOBmF*7W2rEDzz&lsQoe#0@||0-Y@diIj|-gF~57+=hvl_qTy zeJcom0%IY8ttq|GuVcBfB|x{;4ozb5^uonA)W;c_&~E7`d=2+$&0`a}cug_a9U0fASc$ z?(6X8q+3fLQ%cUj-$SvJa{Ojk4r~_2{9WB5c zB$-Z*0R;CG#-i^*;3JhL05K@p-(*tHUMw0N8utwW0VE{7VwKmkWn{%u;84WF`ejU&eKsAXF%~0KdaL6W52WFSZ2!~b0g3^(6sc^w z8|ezM1IH55=6@Ln1&9#x1*!SrwU+Omydn3c5yUq9Ku#*6P#Q-RH0b5e`TN$tfS!^* zrh){YF=?QT4BGmN<#jKnK0P6v-KSd6!TqA>NsJn0G&%lL*gL(^#coQ8)GTwGxb*9* zV5LrN^UeULys?0spux_Tr??EOxWz8V)_q(Mu4(&@;K<)~3-atD?JDgcE0PEjjy#(= z*UdcD_NoRhPpOYqW{GBf`(r8UW^E!n^~Ph^AX+-cD86@Ay>2|8X#9{%KP#-x|3Nli zsLGG$Z$6l%e1(bp51tUtzinANuv1NJ*TGc#_`giq&MO3eV$#K@V1+XFHKZiREzz{A5VVV<*`rPh&|)!Hxp{_ zm1WI_+hxM-W>ALXj**5We|+xfD;5gF(ecUOJSQX4dLe*J$VHX9b?3epiz+0?n_qY# zv6p0{Wd`RzJF=bu?(#JgKcjwKXoxWH0$6e@QQ+pSjdss7q%o~F+Ifw= zWxX6_EhyJv?1_?yTf5CkfZX&^>+fyHmR<}1p`-;GWR6Eav}znP#gxtN?pTWcK!k-t zp#RevMb;j~EmQS}fYa{3LbY`|G3z}uYRwY*Lxb`RdJpwK#mpg76r8eM4wCz0Z1_>& zDCW?ihV@imHqGxnKE2j+E%YV2>VrXT8ZYcgdRtxEKUwVX$c)NY(mVNmdwnMryzm{Ir_4Gkc_;>SC)=`fOaZEB4(<&!?YG@1jb6$m!&wg1<6+Zk{r$Wv)&yXmv9Z zz-5$hmhEjqRo6tmwTW|(S#v}@k7>NOi7YtogISBxNPZKWs#cw$npPQYO9j2G1^L)W zwS8A#HFQEtIh85}FmiaPDt^Vr&PhSOfq%u8b7P*T(5oV`UJ_;=q0q#tERKq&+0^Xb zDf%=+#S#R<{}+|if`ivRmsn$XBtxqE8i0Jyc=5Hya#wzITk;ycNbZ;vj(4I`zf7F9 zQdMCB+I&`MVT0g!YJB((dc@dH0>!nXUL);dYDr6Svh> zp3m9X`QcR)2y_Z?mEA4p@B@8&mO>D_=(v-IV%t2HUj~~D&*IALePn6x-o5E2Afi+~ z)7x}&fF)MXibRo}26!Dk&jj|~Mi(r^gtLd$}~w&~T^U_A|wAs^iL$p$0H)A?+t zk5f&xa^CG5+05QK1y1c`qv3Cz3Je2Y4dmblW1l!S$1F;rT(&>Md;1S|Fa+a&(>OoPFcJPxGX1HDdiZ z6KEEB*8U?)^d|0^=)X3QGHpb5!IfFJGz-ge+BAY=rQgmQxJIBM{ye&)t(r6`oO5*5 zUhDFSd?EDy-s5ooEUj~Tpuy7p@^Uw(!QCc9=a&wDbMpN+7?C*S>unvzDcLB35R~0Y zcNy^SeY2{0oF3jAAe_f_h(OmL6J}EQd9hoXs?wJr;7s3Kjm*M*XC-<%+w`L!8oLN} zq{7QKwCmHa1#DFcE*dETw$rIG21jXpQVb)}Mbq@#Ap^J!*adw&85hOpc1K?Nx4o*~ zafi(ZvVtm=@@Cn%n=KSkWE(p75cj0}p1F)G_i=I_CieOxKNO!!akkt+O28Tkqew&c zx^cS=^zhn3T~h>HASt=m8a6G$E(1v#Si(lQ!ktI*!XJ6eiuh#A5J{zNx;u{>tm6fU zyPR|1C_qN;MI~vvQ+@(tWQ^(3ry-`o9>wJ%k9WolObt0!)Bf+ z>rvb@F2aTJHl36)3i+bCbLwr!jyBkT+INcvQr0U4gprExj1Iu`ES{W>pyb&W#Z0!c zs1w4O3O3fD$<|$`>C&9cgk01onsc=H2R`w9S3W54lB##zPD=7o7qmwRU1umaX#05{ zrrz`xMRnJ=bJ390z4&Q?r-fI8ka}e)ze~m$E%5!l3LO8EQVRRFHfAIPIc$)Bnh5*d zF@cC0m60W#WEFcRkQ>fa5MQZ2a47nhf5~aM88JqJZapik?;%_%g58ZfMCOS3=I2`1 z@`;HBacl*J0}Ml+XXsh0ppAFG6uqqo*tbF(OiLw%cDjD@&M`NO;4MsdEaD0cYAud` zi;>YC*=e(MYrV7vS}(_*j=J#o$E0yHfs3CR`3o3OogPbBVeQ{}&w(Gf4^sCMllSP= zzM&Dz1U3*dLDj>KdU?<2u<*pxc|~#;m_jge3+48lH;7VXw>^`9ZgZT@Gc<1KFx~Y~ zCr!Okw6O&A6^}R7UeFPYQXNU@*{hxVb}Vt;R`3=<3Of0@;SzLxxr=WqW;XXqgxw=q z&}<;77fVUdfCiBC(dtL|yS3Bbotup@?fnBqD(^oM1#Kj_)PBH_9=~1pb#Rf=^68}H z9DYUnDaxs{h7b9tC111jBZOt3JL4JAm%s0!6)f7#`+GlFTj~$qdas5u=A#C3B9Up+ zaV;k|oVnDL>So>H&qpSvf2$9Xbs8loee8cx)YL;4z$1Gyci1TWa6a^RjD&qSh1*+4 z$a1roSAoY+7PhTIB#Wnrr9zb`3!|X9>%^tyQ_?S!&9xOYS$7Lf=BbE~PrW!yFt)s# zwAuM{^!VXn_0VIZl{`@eH6BcIq2kNAJ+;|zWIiP%@8e}j_B_qt#3b4pP~3vMRwl@P zIe-ByK?DAYvQbpRt#6Zg5%{VGE1{o~KpyYwRNq48YE!~L%#;zWHw z+E02ZztHVW-oFKmUOWBz3TK;xJZjXF49C9}d|S5h_~)bbPEHdGQtcXKhAr;p6wHq< z6S-i6cXmx;k}gGM@GR&17|_>qFSja-hS;~~Pc!xIA!Zr;L0Th$!?vVPGVft#FBz5K z@j({v2TwQpZO2cGe>t|ZeWN`TVs7|Oy2gmqoVVQRK?_z3;`-$l;=h$WBA1Fqg~sVC zHa?=5647B!BSUYiTMOj$%#HC|5(%gc^y>Eo0rVs}`iWxA;2~_J;8wR2FLyLQF^eG{ z?#?zR^jw<`A!;pJIsvr1o)j!Nxbsf5e&p6mHXJwB{M7<6Hnxr?_DCVqS*(lBXb_tK z|EqCqboE@;`AfLVMn^dA{RrCXND0mzL;XrS%xesDPa>#4@*f>4BnyVO7UGm_t1J2O z^w~n?NSgW?p;mAFJu#4Zm>l}QxK zf!IGiFJAI+xxK6_Q>|NqlvKv+Z&uvP2}VY`{~Fdv>3py+lr|RrutKlgur2PP@c{hbYz?A(eD)*H5Fm!gQ3wNOnuaqLtYi%5zQLYax*f5wg3^8QBg=K+jqMFCj! zIo}^U&DRyKEuc=(J#VJ;PO-h+up1-uIuW`(mO`<3dDcK!Eoi@rDqs7=r$(qnuYJ!V zZSn_2shaE9w~5WLqDGPHtKQ{^M15~J6ys+xnu!%-;?tmXmrTHO;$)1k&C6{T%}I*V zc5RqJL27eluZsTb5DJ-j(}PdRzc!tWRIku-&uEwX6=oo^oA$PjKFw+5j6!VfhdnoW z#?8gfl5@v7gn9m3w$OaSwtu!uin#7N?^A!UabJGKqVWq_#H;h0>A#}Ad#eKPEGB!W z?Kf3ZVG8#a9!mW*Ys_V~r`}1m&Dm%|yY)3;D!(~L7I{`>vsc^opP=kvcWw#ceqnhT zvchClHf-eiUGKL^gefR6jb(WSBSeZZQgAg77YqZfyeJ-axJKV&I1enoDWTMxtHMQ% zrn7FyfLd5?0o?}P$Fs+;W)D#6gRKh`FBM1L`KEzt+jq$~9$ z1i{1X^E%eT-kpmI+cwIfFE8}`wm$pxF3OuN>6WwZ`SXjFR~a_f z@7tY}NbxuNY!}5}6^;$Y=F3?i;t@Fg%e@q2hYXDW7)be^yFuAXb*e;{GSEWwSA*D3 z1>@(Y^C?b&8i@yWEd3g0IZZ2(c1mSl2Ua!w{g>zZ8BHuU`W}eIK>_{hccC7*SP|7R zr3s7Qyx1XP=lowfKw(a z-|h!Fm`cAGu+w@}Tlw|;UK@+kxn3LA-gc&NrP@H~Yf$#@W=8TS7^P>MqWm%c+6@s13q zd(5Se6KUD^N|gzQV>v~GoXb$pE#al)#fQVIZIdide2`1n$VvRszT z`buD!4q~#54tGhaNJf=w#Feg8=1oTd4Xl^JzEK^c_AparDY~kCa1~&EkE2%?%jD9>?uj9}YjXC@x9z6Gf7D7QH zlHai3hlNG~rii_meP7Q#%m99AjVrgxqBL(W1hee5H_A)-+i^ zyItgjqd4H)+vN{&)oeRT55B`=X3av)?yIA7wH0oMLKWNJiPMc`PN?=?^e9Fq{%C5I z#;J5H_u9G?s1&M{ib|nDBV_6!edcJNX}2)zJp0=<^*F7S`xi?nKUc|#T$grz9F2Wm z?m$u1>43Qiee=`95iCz^O2-v+d%};%^q6?x(X{ToViG#@VcupV1lw~1UXg=|g#|sd zD}a9y|GPG|lZZpI`@MygTGYubMf3tKEtZ!llAKFg-8H2+(hU?zmCzUr9Ovz6)B5=? zHAPa;^ZYljbp5B@Zxy3OuX_lpK0a)wdyG$r*VEy}@oWu@tAvZ1)>E@>-bKm%!s=pX zJXJgWR{Z5f7%&xLFLZWawei{=l6pH4m85eCymTGUSKe6-*H0>KI;~RX+bytniu7_7;Ne8s=NcB ziW<_J3LVz3%`UDuS917ysXe<|^6}XxJ^O{?gx(fx*tI=EpIrAOB2Q-PQQpxX?n8&$ zx$=+C)B|^`A$XH3WJ37#Pg-2(1f9MNpdHQ_UVQ4ZgXckgD>=y@>a47SzH6mdOZ3Ni zvyE{=B@p;u1;?~1S9Vu7MOBC2?Pj^0ow3h+M@$P z23&e_Sz#lQt;F(ypJ%?k=RISgSPpx)&T%K-6$O5`)RD`Gto>ex8R<)=E5zE1?<|VbMh!*5jb%g(vrYH-By|F4@`GB z9yv4|M+v|rc?tepV*bEv9-48=gqvmFsr=OiHnd_p5JJE@p9-9vHcxuuH z=>Byn_+F&`U4-Dx=yPVThw`lk{JEB1tkND)e zAvkw*Hq-Suof@2EX}Y={1O8)H8$rph;L*{u^Y&3x_`A@Upd`&x7QL(`cuIv`aZF5D z$SusvsM=7Y@z$qQk+Nog4?eCsHR^fG@}_P7KIR#EZP63{+lAlM5!G#zqV!{$s>0%f zLf5{0eI1IEC`%{~&WGrJY$sFql`himmQU=2qiET?*$36*G)0$7ky3(#E8+zt)C&YvJa2_UWuEW(ig3-U&x3rGjO`e-%j~FOu1`*{lei4SxP`tgq9*6 z-Mzjw)=#Zq)V?l>6AxOQIb3k;`bKw`{cBjfc`sDRDCLp=Vc+q-O%0XxAY(!96Ji$- zRT*^>9w{w{bo3E*J*pCM7jUp1NU9KGwwxQ22`AwkBU=^|G!?M%)8z8}i1afwU_;6z zpsk{Tg+Ehq5AkMnmmf=F0i!A?9UQSGc+#T{u>{5yJtX- z-+*?}FJK~c&}(*_A+#a4IlyVs$Holpl^jO2 z61LH3kgn@wmainpqN1pv3dJ=6DeY7GJIV4ZCkJ52@(M{g^tKNo8if~Jt~{yLMK!wB zfW7@mxlV~@j}hZI-m~l2;9Se&qSofb)9PU%{D*$%2M)MOdaL29lmmT98J6wIV z_f}3zskV3D8y^;BcJ7^qCkM#b^;cESmn5|QHupEwuwvd(^4mm}kxg)RP=r(cY#Oo#J=$P-k4H{-j_ zYgR7`Jap;b%QS9ciH9U8PShm4*34B2j8x@?>E3nQ{_lwFWZ>6l)DHB{e7V`DKt=G4 z*D6tl9I4pDj;XPCJ95W(#AiCc1D}LL>$RdcEsAJg{X=oS?iFasAmCOsWi|~Uw4Dj* zp$pDMxi5EP6?-*Xhd2Enn$A0(&Hs(pRIL`Ns@hUj)UHutE4AmBs=a4w#NMH;5o*`o zYPR;K_NF$mSEv;;MyyyT{hf3E_TnGU^Ljp?`@Y`S+hM+%rOfCzKUv0VimfwWo{!Wt zPWxt7l^R0Pki^bs#OgqHU?%4J`6bA2-a5HJh3EGxAoxJn#=3A{H^F7Q3k2)i)|`VO z?<2=ygH?I`J#+6V85mNZ{1BD{X7ncmh2(NIO6?kKzQY0d+B^ytM|c{Q@wk1I-}7Rm z^vQtG&&v{lYq^GXY8{V8KAHUVyH9I8d|V~&DHD=O|I?gnV6%$S{d4SNMj0od^mCJ= z&pW|J$gj!ASsp=zwv6uMot_KlN{dO3k172J+)`(~VFM46`Xybkfgp$Lqs782rDPuu zEHn)(f9gP&u~`PV!^KIB4&cqj;mI zs311Lo2HSEv!6!z&^@R2-Q8dx#DFHZJwXoULE*JBE|)R%85*=RgAa5+>;+Bs+v!h= zV7`dz)mRkTfhQ+U9@^_qVoIrp3h-B4TX68Berj^@?w+mZQPGvogJpWEyXwt5%uQ#UE3v7aF?ENW!5Bg3ZjHeBw(CURg7Qqd5}G%@<`IDT z6!N@D8SNF`(4FWdDk{0S^(x)SBQZfvcTg1+{ zCseLcGUG@BQ%X)^j+$^tB{%riw}#;Y`LTqDVy#=IKwL2r@|3F7%(nc3X>ve z-xoQj#JI2;4zr8@TZ5L>5lSzGK<*dFWeJhYz*{?P0Zy~w`!UhIQuKD<-FB-ee)jd! z_M~n*X8JYk`4^btfO*P4i&DXFw{6LXw)ZdLD^ggCDm$t|mjUvVU*X`)2~o?*nD}#* z46W?V`_+zR@-zRUYnV3;q`2f@cGI+>y)8RtEB-{Y3Uf6CNx|I0{b!RlW=k0^_=_+AnDPuOC0?>uVO@apLToaYD6iu-i)u3f?=PWYkOhuj5J_2zuAf~j7AG);CWxDS)2Q-acKwK;nPT{SA* zRFZm@2#32prC?`eGy(soQ#P>}X4FBbkir7F2~oaE+md)ApJDn)_WfuQZ>5nhw9 zbSU@is<4*%d6?}(q0^3!u9e?90o3_|OUs;m2&Fo>B zLABs(?l^(abkX%N5pc1nrtpfdp{T>wJ8%%aNM5bH$JQ4@JA#qt3^Xf=0mT&YO&)#< z^!}P@o*fCbK9?za>MU=MgQQF|zwly{>N`ndC_5|-k?!@6atfeN+t9qR^5MK_h~F zzbhvHB?D7PM@gPR$5mzDUMJ&GrGVA4T>^17Wdoca9>ltZ73>d>n|~Y+XUn2i{|S|# zKoX|SUw@@)c>&%W4G6rCoQceiV^-KWhJ9;hqed}7kT+w8-w1$55;f*>Tb&-d%ES{;!0O;Vj>@1eDFC(93 z%m+vXXZZGP-m{5h;>(J`#rwCHIJc`9_Ml(s%d%NNVdYsl#BS=8e>* zQ>um{hQ*6~bBew~K1>z$k6R-l$NBu|?!sEN(IN&hp`tk1=E<*{_W9<{O-Hp9lT`}n zeAGR%Nzie!#~pP$1m|EdmM}KC$CHmygaIXm9haJz9ZlMWNGG%S^70{zjY^5kHkwV4 zKi|qfXP%wXoa1~c#X*b!8~|NO`!0| zK`MPUmwj$`Hx*@Dxr+t>A7l|w%BvlCv8!8zeJ}+?f!2h?3M4f#3k-b!)Df0c0isU; z_|0B!Uq3i`yV4e7GlODyljPm40Y|bx)e66ZJ&Q*k71ulqj^nTI8V@)o6>a}0)~}}d zXD$SmjF#qYR`hVs4lYhDdf)XvnfsP-CU{j^RW1p*(|JbnH_b73UVSN=acDaRfswj` zOR4?ObHJ`#W0M8qOX~7RH6xNV=S*GRzyuP9QgFFw2Rc$#>UM_sZ9M{&@9MP zaeBQLZa)2^LgYj5~`YbRMTF$JIrR+*;E6% zs}27`$k?di*2S)TzpVyQPBwP&@sK9OCX=pX@=DoMKhSvj-igLYHF@ywJjqmDVABls z53`QVdr&YYDQ;EHivM-zkt?<2~}X#AAnV%!i=-<9Ry4!mOtax=kLb>PQeKES=DJz>qDf zkRk!72!x@sN7WPT*}nC}l&;)WKBRK784U}CKY+oj8x&o+INOr zAUYgFPR$A%5fWiKcPCYA7J@{oL19t!nI8{^&r?GE&Su+^+Ax`rA-+6o#qUyg$UL}@ zt+)t7KF}n()~LKS!08f^U0@GsB1TCH$45)5?V(cT5#~JRs;S$FoJMio~ zcTAsf8@YN)JEo*v8EZUpTGAytJBERaK@F055}rRXY!W+4;JceEV)5~0=B++Dh^o}> zjz%67-x3&z{BVOk9oniYgccMPv9(23&7VjIv$$roB13pp5tm!Sr>zR>8|uZ1%&n-! zzz>>mEuOZ})`ftU4BE~AlJDCB?y)e{K^*hE9pvm3tCYduJJ!Nr0r!=)nE31)D@-Qh zDs!na%Pdf&V7nW5=ZwiA(=k)hVk0EEE&oAl1t_d2jMl&N?qTHi$$p*$m_Y15JOcV` z#OmTif-0 zJo;U9!7Xlq9dI4s5n9)fOV8?x&0rY*`W%Jf++Ak7cu4r76^l zQJHZi8-a0qlZ=y&5`tphh8s%#I5ih5FxgnEV%(l+AzS0?FqH9Y%0C+d3BMixEPkE) z017C#u3L{9bU${~@L0*OiguT$r>^4|44}ny0G^wyCjk zT7}LFq~w*zm3@nOP4MN2F#<+v(cm~y{R$y>WXpIb8pL7~e?BmmpZY9yX2Y(JLn*Rh z#~#U7)2*DJiLeUDUpbEU%Gdo!hc*}+oTi(^8VHKuS)>=j^xT-Yvo3Iu`K1e0(WGZ% z?3T(iUjd1+?_Xen_PV9&4J%hef-(YHWSIiO>Qc0Yq<&tIg;>@YU>;ii;JTW1#Pm07 z)a2|pw-||+YGTe>25bsLe_4hm&E0H54tOTqeRZ1b*DM_C#l+QSxTrE@(l6W#3g}Kk z-8pAV)giOh*@Q{fMg8pk4B2-#K7p}?SV@khP{bglj@}-4h0-XxZ%y1RV^c=VEw!`#MrJ04sIW zzbi0zxyMP`?nPVYX2zzCHwojts0>>mj1)dx95wKi|#oOt_S$`>KTw@Yhj}M zmEDuSin3_wP8f5bXDNIUry&I**obxn00dIN;)f|oJo*bR?1KCmq9L*b3;=wjZ452F z92X?*2UIU5vFDf09JJ+gz~Zx!{5JHY3jFa|6%1ejg4v8^Wsd3uI#t>zqEtGVJbJGy z6z?zl0#z4=>;3D5hifI_Iq50%_8NvCE(v$;Bk6mYo-Nc;3B{n(ic9k=lO~-Hu#K(S z(rR1;av*a}O(@UvFM=csNBXyi6M6DAG^hTK1S4E2A@(z+Yeajfy+=GDk+TE19K^UX zM>zgC?tMuF{he+!DKDLtBPGYXiReH%AnQ@Q=;6XwfvUQM+zrKg+XUu&Qa7GoEW$`C=2CWLJ*chJUSBI=QF1m_=b zj<7~+qF=9d%Xeh(fh|r0M``Noxt-4XRQ^YJt4gx#50s5(WQnrr;*JX<9lr!oy&=m? z!nLjBwlgBwo&d>gR02l47ujmLkw%v85(<7!Xe7+KNk~e<$dppP%%o1V?S&cW@ipai zPgugc*EZIb8xK}m9xC#+_7LdsHkg&ZKZdwPo)ssSpmlz93nV6f7)+G zhJK93eiV!G`G-k6JSD-{uzxx@_4Ix$e(ZKC`+q31)U(G|*;8&f`;rneqg z=)zKajE}8=P2%bj-FDH+d)tk;hYpv%OB%(`%)wSnw*4rU=t9p@wXEWY&vR!Cq8lnO zi)id8kI&e-6aUzn=&tQN3_Lr{J`y8FcN(CWzI2i&<#)dCe3Rj`m6|z6ND_UGDGJ0C zx%X1)_CVkAXQjJV}OQSmfoR4eKnBF;R?Vjs4NHsCAG%_SMCVA5Z#UK2z=5M=M%ok5OhzTILO^vay z5EJ?V&BX-?$B_<<(#&dcJ?^Ez64J?=y_sR{0gsGkfrD=-E!{n13Oe7*busZvj4uB|yeU zn-K5>TmkIsS-{&1t;uE_O)c7cUj}W}AQA2-tlpNozuI9-b6NIZnKCVx$Fs)06yb2; zJ{BTiTa>j*sghJ;qx&_PSS{8}Ylr5$GxHoW8_9Y^T=#ge_*Q%2DjdyZ$lYk;}e}FbI(^QcQb|e2`SS>zd zSf?$))`c_f_U`DV5bMjRDRu6CeQj>&Bdh0j{FGG$;mO=IPpw+7@G)kLZrcxO0$S#a zHvTnvS$@O^b#r7cp*x2pGdv-S&uUu$sw<_*BdyLk+GJQ_B%J3jhLF^MI03gPV)$U8 zinC0wUU}%VXFlJ0Uw&1!gnSW;8uhQU?&kU;{0nZS244RcY7$2;4i>9985Z3%X(hCz zp1AfzPG<2NFA&<)75?_Lr)YDbU@gE70y!az6zR9Fz&+0cC6yTm62FWOb;`|Wcyrk~ zn;OhL6d@mU2z+`+scJWnyA@R}I@&y&FD>_w^;fhkX4-+N0ucv9>pa%D-^}{DI@^ zi1L4Rd@88+zqeq_JGgA&EkGsolJ-IpO8hst5UwF5aeLi<@8@6eSK&U%(21B_R27B6 zwu%jmsx=2p&VCXa9x}Z<>G5@-Hb*;(OYL8!s;2z zvg*%XF9iNdKOg6RZ%@ts+qLCg@&6q76nP-qXY|M{5;GQMh_gN=*dtxq~Tgd+J4Yun_k+Tk54YF#3>B~wqCZ9A_6h;)Y) zD1h9Fe&J<^I(!={O~1q8lI>#eQGKZ{WYI2#aq}OxU^XQapVK`E^}8QXkbHV3cO|q| z6?h+&{YKp}N35xNZc`_t>?G+e>&s)9pUp=Spzw4~F7TQE+84gSnT|0AN2kg6iHS+l z%Yg`}a7kNoi7E;X4NzOU*gHvi0*~JAe6s7Bq?k?+4Wg1|mq9WnuMIFci3;us@UC~c z&P0B9**l8!>2h7#Zh0rbU|f%v3HogE7wJsIAj0_mZ=}XrcPRK-et5>kIle!J!RwXEcOkp^*cGX@bt2oxyzJ8I$>PGH?+cXooExx#yJ5F?iV3@; zamBer!dm`vb4_x*`_gZZM^Y4Za@l!Z@M<5O4EGMx+uiP1-DkLsPOE+!9dby?JdGQY zO!3^6tgew$iOO%!AeRm8I?mOi%LV$Fj2oGhKLh7KNyxKW$Bd*ERIB-6Lruq1-SEq} zg&WTllbeM=+uzZ7Cbh1z1{M5zFBea@hGXO9>B7MT6B!~k@E#?KT8-gQK^X;o0DL%6 zqPeb5pN-wosGUh&y9qLlsSP+iH^F>juNK>7yyS zHfISRGCk4-Zyb^Dyt%c@KH7j&nEG5!^f7fG-Uptr1%8jlRs}YWjqBh1IDG)T9(W^> z%*&(798fN*n32`1n;Xh3Z>q8q&Mcq)P>SAcW+0unRq)?}iil6-(K_LMLcB%*=o5yU z_snesQ2t*(3OMPXVXE9b`av$vxoML%N2jvkKuUO6Ih|Av`Tntjlc7b$L34gYCC|OQ z`P4y-?JFtS2`+RI9HlV?Pn_pWpx~y*-t8z=ZtuY2pP~kcprz&-^(uEe-&4XTUWj+T z+?P7}#DLSmmE<(SA$eL_^S2uy@Fivbi}t&I;(o5b>4J3Y^@~ms*6%>HIqUvrK?ayN zCQX7CJp7~kjZU8ae!io7d@BIlc{C`z3(}n$>J>#}r zJNjtqB(*BS7ek|O@!BjAhjD_;(ND0&GS@ZeZs4tRd)vLd-T7q-D9VkuALv`5IqQ(W zjC5un^b6G`i+l0zW5)4V%7i6cQDkSP%=CH)yOSu_LU^^==o#}mCB4Q4v5-XeQpY^? zH463RISM8iqOlDa>VlZH$fr5jj9Dda*4J83C}mUuR|U1Lqw^m~xX87dtKsp+_2!x#1@K-SbJ3vF z5d2spuVfD107!ko1^Xjh^o^5;!xi2)}oC! zJ{^d&a_r~UzSzF#ZFR{e$;S%b4O%Gd; za=hQ5=C#Le{vje4OK$NOVOIjT(gNBx5aF(xUnu|U#hnV?Hc*?vG0VD6g|Ci#0s+Q> z8UbWt5KS`vr$rBsFJp{22aXj-O-dU)VSSyi^lN0JD7vo>lGXm)?caRgkoNuZ9WBfM zcE{UHEA1_LF4RX8mKgM?fDYXO__G2s;*DR(?dn{^W&tl0Ec!Q$`EPlxhQj_t*PS~( zP>r?-kDp~+*y1CJf{Lm4TF6cec7SEMZji*ZhK)^N_VOTIlHMTboSO{Zf|(OK_{n!YsazkN)_ zy+OSvCwiPX^1Qt$f`+T|<=3F#V@j94g2)Mf@W!~_^M_=NvSRH%5^e8w*#b^QN{)Q| zZJP(k2?5JK_!>()-+`@1zW&4`nhX3uCNrA9$OcCl#AB@9YVTz<>H3;WRv8}ScH-sd z&nEB-jyZG1`O%t2)j1tBDDojJs>oSx6Ew$u)jAxkD=P*HHG zBWliuT7JpbYE%O#@HO9&>y4D8C-ut@L9}FI-dQ+9I{xb#syeu+e2JO>%<$aWRioMQ zQt`UTyspe}YP?_m{F^zqh&bDK=@OjG%F0sTU#O5*iN7-zu&9TGx2%6&BNT`9q}z7Q zkuz&?shzmK;tz^{A!d_n^->+T)Uf?Odv{^GqVq#Rh4f<{KePWZ(>HS98g+4t;&sj2 z*TZQbCWib}Uj9P%x^#~XwDSl(4U58{kPoK6D9FY>hWd8#hilvE>_y>3eBmHCPg(Qs zM&Kz{9W6^;7#^T&$`Vj~HlC4Nt6mfHaVNz&i7aY1=CDeXY4vN(Se$^3`i3?|+9Xpy zW3syBX>GJMocOnjkR+xmMy@|(bZkPjU`J|-N%S~B{%ht=rENCUbBK{IGrHn1e-Sj} zUSpC@QGOXGwGk_8v2NBDDlsA5d{pGsa>}rCGQ^|HW@CwH(5<^7D)wfxc^f5}CDmw% zuY?=3d78|932~G%vCwgu+RT2O>lDZV>qlOUX_%1hZC7w1zo2hiIL1XV2R5dB=ZaU3 zxHheogpYw{pxZF$&fM4`R=g1{p?O|Uk7TPtF$|?&h4nblV1A_wIb#2Dqp7VSxI1A! z=QWn6e`88eLhl74Q|gSe7@Ha{R;YkO)hQlsi;fM4vv>W$#OszmPp^D`9>aWBs_>;7 z$U1TUUg^@!l?Al6(Fl`kPJu3sX-ivQ7O7_CZ)7oG)na1@yC`0rd-pbaAJgwWzGxzG zm5|OSy6e-qpfud0fT3)s zNh&U=`^v@JPI~w``&p_y)-x!tz9bs=HsNM>Og`mf~xAIj|nS(eF}~) zxG4w??-x_XO&?kM1++W!W1@_FueDQCD+O%6uGnr^2LP!l(IQgK=BaCI^6 z+eN>X_)+f%0g*?icV#8459g}UBZ%206nj4~ zvXbBzV2taE#Aj*pCUFZ|^5F(t+2e_8Y%Gh7(gT)qV`4a``;Bw1Z$m+cC5?^d`Tl$> z*jR6OmCVOTVW1#YTqf^$$GadPvImmk!>tfT@7>``4ZvEi^s@YMN10u=e{B$IEZh{d ztQV;LH5zc9Z_6c>9>uIyiG%D`3Aiq82nIrMJ;4lKK`SeyvMP!x-WoC}pok`91Y_3j|( zHvHl-<7;pMx7|ka2owqk9vFky_SDopEMP+c1kQHyYm&Vv zLo3(tFK4ao&^7OM!f1%ejC0SM%l@^lEWUk83Uz90Y6X&hgMMRn$O?04Y>TVLTG)RU z-F+kAo$BedO@0Gz3k6Zn^V(YT+l5-QKTWlc$l9OGe6Vc)4>|7+}tTEWs!8h#a_N?25}fsk?-4WG={m2wHYw&UA-C8@g|jc(KK z$yd?D{qMRzEKOAVwAfT;1kQ-;#AY!)z(F5;+Q2H>E-cdCZvi1nS?s*ReKOEO%V2XYl+&x!_|ICayNn z9sT`r@9(nE-TtGi|A+kaRT!RaY<@<3!(3_WPoaRVc_nQs$89kma|NLhmUTtg2mAIA zoq%<4lZ%iU(5~R9Ra#+4A>OjXL>!PWcVo`nxIh6Lhm5d4^0jD>BzqdN8bM@Dt(fEG z=l8%u_!?=!VcTE4WpY`CvzyZ~#5R;8DeoTJ>hX>~{h->f6mc*5=e5w3Bl_Ow*9pB& z?7{Z20_y>GwJd;8+IES~k`lqVn$o_@Qh4Q-0kzO8Zg;vBJ)o$uNVKsTnN=;bmuuCh za-cqHjA=EEtZ~Dm#Uq>?Wnyy0abFKl27AYbkTK%e`Z$P;UQP(DF^yh65pmaB!NlTa z0JfVqg0_x1E+-jJkPn7!4adWn4JTuHtsS(H9o>8W^GOHEf|u8CW3RptV5#|+Ly!Ij zWE4LA{qq^D%~sccUb^QwaadfTFdx?Q}HQl+??^K1-2FB=f-# zcq4oP}Hh zb&<{?g|DFYom`(V^F?C!i2WKqRXkAxX)qm4Zz+pK) zwxS_ENPS#a^qSFp=|Pf5MVAU48!YGP5?70?H*D zzh?~JC#M6Qtgmc1N1_jIQ!O=VkXK1`igVjy14m`hZ=6+%xWRh^(f!~Bv_5$B&vw3m zx5vSc6)gG@Q^0sxI0iv;;>1BeJa%H+XzqQWsUyl;L3p`(W zs9cfPgdN>2@6L#;)K?Zb`ScJ(;+fZ0krU84$i7K=(xvL8*l13)#I-UQQd-&()coBG zkJvnmP=HpJnuI!?DTT?*^35u)1})cO^%QOCZ^KWm_mJ7EKw~JnZYphY%VEb|=U}1E zE7SNaQ`;|_)*{bBXxlpVsY=q^tBXk$UeepjB_jITVXjtLHj3&PhFPq;g34OrMWl><-M$evyQKC0zPc=M{DiY*Vn& zDSq$VrZzt8K|t#(URx*C5w9`+BR)w~Fl&tH4MFevdlvlhPo*U8_Sq@m1vYu;r`|zI zP87T`0czpg;|NtpJeKWjyH07xJZO|!vv3?8UZh3VK+@AaKj|HrDT2= zfc2*^7ikxM+luBAowsmOF79gQnm^7iDa2$vHA$Y0Ea57j9l$%(pz6$=s2~~r?6d2U zu;$9e#>vqGjfJanKW`8E zp-2J5*mU^g9{{i_j4f^sN&KA7Fpi*@mjBu!_E!1TU?K?~bJou_M$Hgcnr{xvO~$5d zwfU2EZbV}b_?reDCTd^GCdjNE*@=8j&>qbGA@QFimcC?-)>xr)SeVR~y!Z{t*J&`K z!ebEABN8c2i?Z(~&r9W7i=42~N@B&&>;i$q-(Vl!3zYHvY&jxfrWE?Z!=7~{V@Df( zWD88j>N`?ju?^3Z8BU(~eJo_lI(IHJl@#*Om@dCFs1AmIDiM-c`~m-tnX|X)H%sHU z^!%xA!BN&n3iC$*4H_gL=)R@#>+!$7MyTle?+vCfbgLj!RkD*zj+UtM1(Hot)TK64 zUTp7?3ux)tJmYQf*0(3Q}CMC+H&4|sb+3;pBQFNUB=-*xHIbK1vN zc>aDrjVq%>38-<^(>9ZzwKJ4j_^S8H@A}r7Eb4|SYTDg^1$N@bm1kcYbytv4&CAYC z`Suoq^g-+j_g;I=HFrRN?i#2I-9j1(41Dw3^>ta%48Tqd5dKpz%1pYXItp;!Z zIsE+`U;az)pY`<;AXoZXb>QQg;1uw%l9oy`k0Uo5u$Ma~?O|)nu1;W_kR9G8^Vv`gpY)%JYVRR%hY4N*Yr$(m2D$>wL?MjlrH;1>#Npmu-vi}p2yMt z8`xHozlC3Tad>CnKA8q?%WBYH1?RJ`i~iz)ChVJqUl;;$|V zam&`n<4~ht{ZEVm+t-pm>5iXjWbos-Y?F5-^OtinJueINACCp3Fcw8_HgoOx@YnKA z5DWMJiD=2Obs3`xN)xH7heRn4Cd_u*X_7^ISA|T+*dd;q69^?(S5W7#{rpp7=4w({ zKKP-NfadacKX7f~U9tCh@V0l(dyP1pA>JWS1&N$OtKnuszt(dbUHax2IKJmj;||K? z%yg%J)`(^M+cU0;$7IwpjDsNV6c&nTn%@4XF}kPy1a0Gg%;Bzmns|rd7^G?7eYtx# z$|(wW@d1NMRJ@JrY>!}pp9O6KPrZF!;~$Ftg81ja?84EEtfsp6;0#8L!&mW2%XFc| zW>hQNBdL=qSkY|z z*4d5^uTQw=bT++!W{S^A6vUqyOtGC3-evHqy%M_%MfI+{84g`5c}mxs>3{M^M=tb1 zA+GFDX!HN%)U|k?+5PN&?yGS)ge0Sk>hGlQ|IK;+3bGE~G2!;BR!;6qYD4Dh`16UMY4dUs-ZQsL%f$P~(V4pML2@G0OP z@M|0Ku+`K@x9w^K5^b$?kah#0>?ce!wcDv9Swjc2i8Jj4S^WIU*KO>zk~+g~r7DO; z-kj@Ir}qH?G9@3E83aDvA%_LvPY>d~-gFGd{ObjzMMyM8S8OrU2?Xiw_JOrvczPDl z<6f!;$({qI^*1f|te^ZrD*bwXVei8tL>V)5q$Ol(@7@TZW+eHV&nj86l;rdz6tFLd z+xBQgKpnPx$kaZv*#HF*&q!n+C z)F%eKpc#aW&LZAdCLPTy8-|kNjL%jFrCxKCC~KMW2)D>409K;b-7{CBNEvBq=Uw2F zxXb2)1S|;fhC$L$d??W8kmOrEazHo>`A)a^Z-FHog_ zvyqH0r}EIxg&hf4neon4c^a7WU&P(I7MIRWOs^N0b@Q$INOykzVs5AX1@{@-Ru6}Y zM2q(XDPBGi)jOSl1KP}io?5AQi44a-+!+=Jd5~^p&NnX-p5FW#>gPL|X=r)dx*2#6 z;l7~pAqZtLy2jpO8`@pv(%$^^dsL)Crw~bxx1UokEnwi>@snqII*ma#q!=cq(}QJF zr*C)8u_}-cB3i8W+-^qkP1=aG>&gA{(J8QGY6SMc*~DR*kuwrkZxZk5ONeFE{B>7{7$J;+sl#EFhTqx6W&Gn!e4@bi?r5V3Uf#d>yb`wh$!$0da^^^j3 zv88VDqmtMAmq({_{gOhG`}7Z7I=fIdeH~aH>HX6BI^#v7-Xo8nR`ev<{dV*N!%Q}y z>u%bjS3jsQ4}ibPsKy!Z(Hi&9){}l94xGUr{_3+;veu!l3-@=OpDKnV9=#VK;CcV( zg~)$3H8kCLbD#xjX=rL2EyP=kPrmxL8}%9RmA2^aBamixkk~6Z01u(_MJ!j7QEPtm z{inMh*4N{s5nN_rAFXi5w}FG2c16UAuf}ft6$*KGxPXPDuoc18HQgCrPNsFPi18LA z*U?{BeV7bsjpHw7Gz-v9`RdSrT!t{kl*WJ&Jri@9H?DWTo=&%SmLK3`#o26}34Vw} zdpJV+kj!~K7B#9&pX0DIQ0ZjU_hVBmyV^RVHuFUi%0HO}aiV5_Rp^1lBwGJqX>U;_ zB+JT=(-^>C5!`5YDx@sd89f#_)Aaey*cY)qmMsNYK!e%bI5b-H%D^rmd;mv@%<8;u z(ArzMinQgI`HFgh#84d$iz@)lf`213n_KTa`C| zQ$s~*8y)pcG1Y|*PRp7Ob0ybXy}c?Cgr56UL3ph6{945}U78n36!Jdev+G&}OaVo8 z(DX@TPTc~J1tJ;`SJCNwC1Y@e(BVO*m;i&Yt^DrH${UJ&wxq*Vi?*wB>zzyd)5%{8 z4&EgE84V1n>p=!lAG5~IuRFU7H7%$`19d-K^0x^v85 z^bDMXpW!+AXUFmW<_mg1VvM-|Y5MGKX5o#mBkI_7>gZ|URNjev+dqXTNcm$$8}DEY z8s=vu>WpR}|G{fBz8Ypud;E`EllUg{zbM+jtv6r1&DSK`)*ZEbP7`FHU5&Z&<{P^< zbyIAhrWFUO2O zZJ6jF;-IjWD8kz6&1qNgq_`TQ&=LhC8}%b6YS!BbWQXf9vUa2VjUhpG;2S^~R79N z_p1-yD{(Q&eD@6)y-wE4v?}peM=vagm(17!p}%nnAFaPyIqmTw+_P`*1MeA(QrNjv zqtCG;=GXpj^y@xNSCTsWDDaxpzE*>uSbpC8Gb9)~U%y-;Bxo|Jw>xJUt@b!t|A`=M z7r@5Vh80Q2T-U(+TjKsymjmoskJv?-b~JI<)BgeQKT1x=q@)&h|Lr&HdT!ld%00NJ zKiD}6&;P#*fLo;Zdx74t`fto`Nqqg6$Pu53{y3bZ&$sj0x2ZPVW$}%l@~dO+OeF^~PI{_O(&GDil_?x;-ny9s_ z@9Gz|hQaxUk3K^iu~*>(bDVwZ_@OB0AE5e_1HS%{NAy1w&aet`>)#8#j@xTfII4Rf zewWM7uo?fs#yy?T0T@9l&gN%+L5;yg^~4vYc5Cmlz<=B_Vk1t@#!osM@YdHfeD=y1 z4pO@QnBN};18C8o)O=aSdwG%yI-&1!G_3{qr9Dt41ZZUr- zDNVlam^Bm0ADQ*BhW?QyKp)|91%c_N@aK;TjffKp4#)rNyBZf$qZgG4+QZX8tZQ*g zh-mYc$$hmv3wbHDyBVDMx)`RhPi#qMJrJZcE>)>*`q48qC-RRB6wjLcQ~%?60d9Vl z0%xi@wI_f4*~+@6%SZtRzuy;^b|fAbs3iA=nm;~!KubzuSCJkQh(4)3sW%;}dbzo~ zmd?O4{PmZJ1)4uT`$dL83;Hk6eEgUC;E>+i*B5Qpw!UVkN$xRiQx1QzmL>ZQz4?Nm zg!-aX*A~?YhXimuYvGlfP2u%f0r+HteP?4mT8WKUlE*_Wt9ZS<;m?HDGQRNqw`diq z`)Sgh?(9}Bb@>O-K5$WyO4b?IL5IH)*Dsl7>eCDRc&l^Rm#of57w)6;8XBnC$z*MH zeR}%T!=cC&#LbrQnxMJ9aRlX`LwB&7YXCHq5gB=}6f- z=lSiSOGSVFl?3!;_vy9$21*St>h3hCZ6|O9RpE2@CL-8c&>`!a?Ao)W>rdh?ufL?y zH7vOfy&-&jBO-zPAi1Q-xc=4ftSa=vi#U=p=XjVViM3+Jbzanti{=7%Ma67aaJWNc z?L{*Fw9lO)FxUC3bD&4Z?=$piR@TbJ-9-U*z29X}Af4cv}Tk&La-&kyO^JpYR`u{H7$pA-%`#5}m9^3688d)p)iL?ILYnv`&Jlm$sY_e;3;Mml z(u67#ja^t5E^W^sn;M=o@J!xSHf84GlO&G{kp^-(g@yFW$@wn}H@KsPn}XhDHnG;n zAbKbkX5{5PIS$IkzK_9e8ghn$TaJw5B4oTB;+dW_cDP8=@grH^=$YxP*D*YFXIVL4 zIwCy`Ak8W@AFJ{Dc8m31|B0l+i+-f8i}kJxCy=mi*8gnjm$AzK!#wpLgnzXmf;G=v zSB}z6+5CN?&eNlI*W!FB{i4{IK_k=TTpqsqt&ZQ;%Ve2bQ!R2&%ebL?AGm)NJCtOO$ zXJY{4^Hg*R;+6-kVf>s2#3~_!7qtGIv7|T>{zt6@KGLw9^l{0pA%ow2IU#zY165&i zx`m#tfY#AVi<%gANbh5>`Vq-RHp&Iql4=b)B49F#_qF^5?AM4Y@m=Qb!$}W(LbCgY-eyTHWdK#rF9d#0z9Yp?NU0Ai}8Pz#KrfbaO89K zeT=)5yY{^kQVh0tF*^XM_#%I=c5fdLc-1f=9(Zz^3>5{*r^Gy=9^?uH(I07g_8gII z;CyLl+2r04igNSTco4aW&B7M-^tEtJhr28=Y4Px1z376mCw-UL3D;1q@o)vKQ}s2K zcQZW|{v@=-4E;9$0gAcwJcy}?DzMqka8n0$kQI=f>-@S`xTb4WiDtL4#D!hoQbj+_ zbeI~w8#t+=tNh53r*@mCVtH6;9Bln@cx4}W{w+Vs3vjsLjcu`BVKOeC(z_t5y8gyP zU}03*|3f+qj_VgGm<;qQq~z$ZR!kvek|qLpC%T+I6}5`<)W1w+69QHn&-es?zKTVG zyo-oc7^Bl?m^`rG5F8#AMaJ6CJBoA)3@VI;Y_+O#L47w$G2o+-s2%0!iFvr1PR*gK zCUzvkB!bf6Ui!|~*m;8Y;8p);x3AHpjFtYhOV+EhO{MWyL=q$rgQ1G#Aei-&mk0)p ziP#{c6CAjVZN_naTo4JK^$&bh~+r77>je&j!Ag_;U5*}KjtrwT> zUAZu@<%-f;thAcgE9iBwQ`u0`y1YQ#4Jo-q<^51~A}8E>0kQRwVcM?SKmU45PTan4 z*!}owOqKTCzQ}`mn9uO!shV;GcI3r5yvCLt}Tg$fU!h0$@IiI2YNgq z@SxaD>)|IBHcx@@%Qt=dMa;xp%a~IvUgrF;Zlj5Ytou49rpT>M{8#4f7ytM5P48Hw zF2F38lj4uAZA~2J(mfFY|A(pfjA#3O!@rRdYJ{p;Z$4z>WyMf!rVOgBcs3f2nKNX{r;Ut404eCOi zM5{ZSaI}uqpIm*+fj@ZH>)xv^hP-M`gFqFo2iU7v4OI=(T}iCd1Yxpr|tFVsa`ZTslG z%jw15RM5)*Y+Z@qek^SH6O^z_Qge*K@RnOxIVcl1fah5z&G-#C?$aK=i+q4PsfN8N zeu#x^#jRd2^y3)U^pGF<@I&)P@%?sRycg5F=jxEu$ew0vXl%TT71Xe5!8EN_*ptoJn|X@8wrJ?B@f3eC&2oCM;7C`+EO^ z>PFi(2IN6wsa#|HTuD%!D4X?UKUHJ(msn{;^2Hh6dOAhRQ^JVf zqXCXhHS37u#o-BhUK!36%{Wxl;F#idw&sx1rs3J1!^#ioa!RA$G7oyr?RPzCckjRL z>Mc?!q7st&Up~(xYsOglUzBuR(`_A;TJ}=uj_&c84i2yAN3J!2od?GC8Znluucc>H z1C)=^^DLPESV;Kw*Zce85zZ6U+#Bu>O=;2mkXZYCPp@!yhnX#L@yEcM$wm|9M-jfm zrj1KM0WU8IaHeRd~Z$y7ddM=74!jPEQq+ChC91Pq08?F=CH3I-Pb3E-us4q+(c9yM^ipW zmBm)qOS>oOl0;{*K>f;g^HYxa&6f3Vu@#NtvxOYexZchwSQ+BE^!sk!OfG#)d?~JY z{7x+);5z<({;bqb916}T{Y3!1g7fN6*Y#}PtJz2S$Oku1St56lp*E{?l zPU)4EdliXw4vjj6x{%}ZS7Rq_)1>~a;FSw+WRk)0=$V-HSK(`nh|SyeaOa%IyH@hC zY8vWu=w{Aa8Qxhba(a+8ip6u}X^sw0ig2Q(MiQT6t;0kjj!b!>mz=Ki;x9hSSBi(7uM$fD|oI1-l~o_xu0%kk$a6MR|#F_rf_1{K=W5!-&g9g*_n z`Yx5GwYPg2-2hpxDR?p8ZVV;y(cd6RDwt_dn$wKscI3lWs!y=PcI5$OLO*_xGW>45p(-|bR42%#WX^&K zbIa)g9)w6DVd}eiYeB6+v{#2i_au~<;dlSFNTF#`r?)XwCs|wck9WrP&8ak+YuS*G z<*!q(`^|H6c$B8^7RnV*04GO}9TJtFLR!aN|-3o^pmdH9Xk(Lq^>hAItkT%I2w4$YdG79Yz~{%7;; z2jaS@?!8L&-yIuPhqOh_h-a2`5jMW^{%9lOCk|eBFH_~bTj&tS(OqUfV&|VbXR6LT#MR;jmFqe`fW(g z(|%Y=rr7=I#GnT2!c&LPOSIXIB=`+Ki*w+6zQhE*He0P>fY9llqC?7ZxnZMm*x}}B z)rNjcc*`9h#V}DDhD+-enZ+syD6}6)8_Ufplf`zjEJ^82d_C*~3~K8#GUQOa>bhw? zhWLk))7IHOWcS3VPGtf&J`MDz>rM+6&AyOJD5F!XTnJ)Ni=YsCu8n}N6Ucn<=BYi) z*W|wcc+5VN?$j5j9If@hlI_oSq6<}QeQP*mm7_!qu`3@rU{1 z%k>pgW89|&_Y^R_M()+|cWNf{pi6J#B5u#QCzSJ~W3iTF>)yrza$*LSYSa{<&*|Lh zJ__*_i_VJ;+~xk%@2W^Y#w7|QE?$inoSpShrEfidjp$hlOH9YqNcCbA-Ps?`7;s}^ zhp2G6BgP$`&>a!@33St!zvp~pPhx4q2o_jXeU$LBdbvc-$NR-)njkCcc<%AHX>o%YA#(WuU!I6KQ}5R3aq|?%2}u!^h)RY4H)9yh@BjQW z6WL?HNSt9Nig%~4Lvj?0Ku!A-i+LX}=L*B)vR*43D+Sjt#V~A4N~5<(_{9_8eQ~Fo2R6_xMYh67fTi$QV2&U6xO>Tv6h=(4FXaYC!(LE zZXP#Lm9JR9=Nv0AJlvN6bn^f0n{r5%elFWW7R3AeO*Wh@z1q9%mE-?#S<* zvnivR^{jm)>R+WsNSA>e&#>oXYR4)H#->b`hz4lkF9>ei@2XnUBr`*0yFAMY`*4K# zum%aFo*PqbW$+RMH+49MC2b6Iu|YJ@0f6vTjQK;hC~qeeWkVYaSdyM|w@ywXugtb= z`F9u$oz3Nto>5`koOF$neRHQT7bx}jpzDI8BF@G`Lhy}Gub8%Xt!~Yy66!t|sul%w zc(m(Rt`&@R3pI|FuTUAlj!PeFU$QuMdSHMG+Gi&wk*Gln>8sE`{T|nMvJ8ZASTqCT zWGucNJ&<@a5LJKWP))r~Y?40xexa`%C@PJ!me~uN0 zGhe-KTcXGOx7k7ejiw7?qjJ-_*%TGWVX!lLOY^XiyS~kub}L0SSD9hgIz`#W_CiQu zjUZ9LS~iK>s$^dlbCtkfz6P?+8&$nL_m+~eg@hHHo)bb zKYvo1Y)2O8rMluEX=wTzd)&s}b)3K2PZH&HR7y;Y^ov`;`CGsVGar{u8Yi3S+g^XCeLN8_ebsTBX4JlQs)C&IYS?rhRG(zg4sN!o z)#M%KtwTp;p2}Z@b#6Rz3hqlclk+PvGrO2NZGLaO6u7o#xrx z4}2EQHAOa40&muvjV-(ZJL@f)3s7A4Su++mZ8rl^lqwZ<^srhS04NG^NseEy{=H#S zFAr$e#GxEcq7`-#R>q}(O%%l1)GCSMJlX+3I1&Y4*{E8d0%6~&8zXo{p+VA{gE9<0 zfF*M%pZ?O$NvYC2<@4}b6097R;I_829QVGz1e=MRF5a~@L`&7s-2iAWQW5}RL&kv` z4#~tKNHrpc&%Z^)1)`YStVVvkZeZ%(N*l?$H->Ptd^RdLW@N}vF!I|r#)Z>rBm4hC z=Z>eaSn{wsb)NsDn?{DgU!|4pPF!wVNnXzq{l1Yl_OUo^D+}>1Ec>;`gZI{yjL~Nm zL|u;TKYX9`)IGh~kXuV1=~#O|DgJZigAJl>E2;kntq=_HdB7n)SA2y)J^Z5w3Q&QCPh7=>@}g4qyAs8dcVu|57IonzMpGe(GgC4B)o>2EayDLe<+q%lqvEA zwqJ8gmRrA38VSYrZ0#x{?>UK&;0pQZ&z0_PPM73pHIe&oMkz8;vF7`D^iA+@D<|Py zO4Z2)F8o;G3UBTq9BZ~{V*?(ywsBzQc)a_?kui;6GFe0gsvDMY6|yC1*U%X_M&j#> z-*BKTc73}VwEU3RW74pS?jSS!Ft{)Iz$4_irP|QY(3UIa*5970_GPP^{H+B+e@H8R`>FTp z8*Y=mpqS62=WAC-*l{pjbZn_{hxk}HGDxu!X2*snC?%Oog~62aj(=1eaIWrODm_T5VAh$A?8pg z^xlobqcG5A2eTL?8Ds!5# zs$GdD5#yd8e9C}%gHd3UsLFRhb=B)N%bkmzf0tcFY%``XiATtGcFYR5W8l>YP*LKI zEQfHG>c~abvfjy(g-$B>a%`;h=#Y|2Irr6vf3D#>VdbN)93aAV9gUU+N;XM|_7d}S z!?};|`eJN6&Y7M~qD6i7<<=l&53G}u)TL_|9q632-jbsGwckFiu&$6*U?>k%y5_sc z$SdmVHR9#!i6r2_p=km%B^%F6Wci4j5|*spjgsppeZ5*I4qq{kUUQ>teZT;q5DJ_&pn$jgg%JF!z1|=DaQg|6Do=jV)9z*fUS;Ssnh}4`3cL1YP{#ERyu{~cKt$t|8uId~nPt?G3%#%()@0_`21)ZLE8i+jn;R3LID>{Z{NZlDDdp0X}; zB7CrE?Vr)*dtReqa|*X?XnndFPqNmepq2Inr?FWIOD+&I+?U_TwA^3JA!Exk=tj)lS1){>w~I5j`Jw z_~+ah(|B?Irwa86B5in(cb8PhP;*8<|D6|}e?+o1 zbmw~N%V{a^q7Tn}B2@j94{LJEz3M`6XAsVElsQ{?pLOSI*Zg_nD1E`cX^hu2XQ74l z!--;T<#xzt<|ljZrQx5M@LZ?9qxD2<45`pwVl2NoOn_OOa7QuA6J~h2(gzql-(VKh z<&>Ua5ZV^tZnB0Jh#c&*N}Q>eHoz1;nP?Aoz8fM3<6BC8-? z=$GYNp$Q*h-9$e#X`L44g>a2gdz;)m`yaq+dKOiocqXE1dh2T)vHEHYQ;RvtAiz<% z`{{R}!?4=KIitY@tfxDx-ZlA8A4%LDGiXqSQig#Lw3a~baCCAXm8d*0O{HeY z)>bJ8^L(+TxSHj(V|5_RZf>043XXu97W{h6(UQO7ZLa-*L#g2Et37nnOi425Xv&Kd z@M7qLY$o`*$f^3bn%=hroLkHKo&LdPxc!uFejizI-!gNBtgN}LE;iCT^??v?M7*O~ zF8+$wvD`lhHYwZZI`>&jz z52^HRjRO6#E$KNNzDdRHiikSH?^bKJOIDn+3$gxrxp`vuY}Ruk>ll1xhp)P?d~!1% z)Fyj)(i2GFT`~<7zgUI^@u?cHdSPz+e@mxnIc>pY6fW)4#GuuiZS)J13qBB_;Z?iIxu=8|mWE3%91<95^@4@;F5Mh86y46b94w^Q9k97(t{VI(zEosOUj- z_^NG^(KyWO{=5a~`y-SyPjj7b#cD@rGnF)e=_LR060Jmh0SmL6c^XJ!XIq52tT_Fl z!ezDX%R~2Y6eumw$xeieA{5?Ml3iyheys)x%jdmRc=Q%TD}VJU##pb%&-jr=AzJKjR0~cG2hjkw-4(0G{6T{VlTo-6+t6Mw(cQSb2{L zb0tKFk_?DqRn0r;L0q`DIMG~A3x*j|npKw!PHr>T?8MiNyrD=53gbxwXnLXM!#0HunR7}3LG73!;{fl#x;g9&nzb|-XZQT zt{NDU#q1ugHv^TxE~(4Xi|L2FR@gdecWX>-!J55bWp_q&3UKq*4Ww|MUk zkUzxE3snC_=i84d3obq<^7Qlt0`6#$O07*k|Ggfl2%+QrM;n4oLJhkHhZAnvVty2$ zuC!$&%d%0|;}n@~Dt|IVJ0-rBom5LZWfd!*SlM58Z-Kvbv~HNpgBlO^#6~(>3xhl|udzc58_^U?&=fBMb_DQl@ti zG^`;wDPuX%m0|mqx@@EYUQLvZ$cK><_JL47BhN-bsoqw)h2Q1tI+bOwhIpDai9G$z zYw^fVGI$9gD;Q@HKohoy<<3sw>f<7{y!ohcIuvya@lmL()8vI$78uuqBo53dmve+i;+KTqUqrLYbHS6>9|jbS&07o zF67LYxo#~@E;T|)d{U6yYV@BgTMhFA{z%D(rpMc+JgtVEkJ4vAl3lu~pI@tyuDVq_ zzxitH)vQ``41eW?H*tNCGH;d9{~6bSPBA+pB2F=US_?u9rLRskc&YVCnZ_zp%oXIk zuj3q`UzOV}6(4~JRK$)tJNotcL7{Ik(gX%hjTs{0D5 zrmCtc#rOW^CB`QNj#r|30l!thS!@PLrcuN|n7JOfOp7^9O(?uRMtq;GfDy{_LNQfb zO)9Z@ji%%&+G2!xV=z4-m}rG@hask3<(G&*mk`q{jiLLE`r{S=eAdy}3DH>a+Tk3IpwR`v&iaPic@N2BU@up67O~_FoM>-VIeK@fvrFNJqR1(*kpE zJ>4X9;EaM}ZFVkW@Vi3vt__CCk)B-kz%C3N=nZ=cyIUcEn!ro0{6#gn!^qbcxOp1x z2d8PF(;EFBj*1v&Fg8KdZ{Xu&@lf9&fpm7RqQrkKB)_u!M<6Ckstw#LLO)(yQ_SQr z;D|02WpJw7>}X&$(FnLd{Kr4^h)aBR*5M&!GWn55$86O&KEzU8EBW#QZiHmT19 zyrxBUOX=JQnO)B>*Jp_C@(jRjmKVU>-9|!a)=ITRQ+bU#EN*YavtAVgad9L9$C>l# zcAg)`g9VP*|F4w!sIH`ko0-Ip@dmKmQ&O+nKzqYSvBuJ2I`)2kT*U;V{8M^{GQE2U zu@JN2+b^ij6{p6R#Iz(wzsG39?b_Q@BYb2$U;FX!-v*u)B|^Tv*`aYcv_Z2d%Ra5X zr!yWH_YzUCRj969!pQcm^03Sc>LWI(6C(7`iL162cd?aR9X9j-ETkV414@YFb7b&K zJH=a;#AWB}KJ$RU^1A~&$swn9LM}pd;3YTS;`L~o2S0Bl2R>(Sv;eG?8~^uJLUHJMru)c;lVq11C;n9L$wTwF0gbZNokPT2B3`9VWTGg?ldL zwv7x;iy8{Z2-qke5_8M`ejC16?fUZmq7xl(tbF;oQ~9rR=mA2ISI0C=&V1O{EzqIN z9wQGA7CmjNRc>ZlPr&1k8>KN7MBesBeDm*RP*|94cJ~n`-OV^#@>Eb2PXO0D+m#YY zuDd-WA3W#5hvyXRPJB<&ezC2~+fWZH1-s&F9I1JaRnx@shwSj&bP;cU0FGOoAP9%YyGT`1@e)*cO*IdOz;-R`N#MDGdxn zWQjYW;huV%9TNdtV|&cx>@0P><5O`0`7lWWQ-L)22EIh{o^>R}1{G{84~|Iz0?giS0|4?rnL$*VdgO%mdHk;7HH7~%-6(kH=-fwMI#n|zn= zl1s9I=A>wv67D@XkO8W|6xKIV#Uibf3L3{V7*5-Q#cozPy|jJc9h_tlz=wC@H?0cO z?Sug$oibot__$Qy=5e&X;I;|=X3%G=vWQb<>c5m|>)Son?If41|DCh6Z>g?=J6XUc z%>fP4QC<80wi?QCy7>`7c0<}Sq>_|Fv18g!V3jkJmbxFI6jVC(J~E?Su0ds=ke})n z$ZAq)a~k;z%N0L@JFZlWrhZ%+XWHvw zO@mVakcRJwA+kbR24G`oHsfe4q3qi04Zx+TQtFct?L?P~<<|Q;FufTDy8F|ovxu_s zG^6ZFk<;nbd1KkfD}JgqF@=q8!7)3qJ{669DmfmmmfTCg07?J`uT{oMa86K_1Y?rY ztG=63m5y3YPfvPza((`+lLjKXzxCI%Y3fKS>Wq=d5kafvlWD0|^w(;_GK*_pk#3?j zWU`IBn`J8EvaoTwKavNT*fSsPcFwk$%ENp;-j$(EeR}@oRoWBZxpa4)Ei6OZOyW`8 z&?pz%QHVjhzjUIRp?KYY3&7fmKK zdBzsgTZe*1`iK&_md-g7Anh&!>71x{TUicY61_w# z7y>SuGqetBr0LppyfX$zDG{5{!ylX5AN-+(1rF_Mah{(z3V-m{zm9k%Ll?6+>%AJW3Y_rlxle2?9;y#FE7-~t;WX(8=d2pJkaj>baCvLV}}<-|KF1MIE& z&KI<3uJ3uNbJ4-`uxw?||EI)wsEi1LNN;GZrP6?Z{uOUi{t4U_JBU$-=W<^YBd)H* zHEx!Kbo#3DRQ{dZQZO+{voSySlHY1gHBQFt*-Q_^`?f*-{R3ZV1zv{#J>Wi;WoFB3 z$JF{m)Ahuy)}Mp1I+L1COL3BA!rGFWlI;_3aePN%Ug`saWBdr{TcFy6%OoxEX?;)O z*G#jzr)0>raZ9!syEXL}h7@EG(rsqU$4lM3HYXTTE{IjrEl>6~huGJHGPFk5?aTP# zr_G6iNbmZx2cL1eKHG2o{y7?5`s=@*{_J&>uKT@Kvy75Ul679K!`!`8m@y->9zoYF zpMEEapt$YbTlOJ`C8Qn1q07NwAch!2bH8m*4=Gz)6=PC2%H+9fxY&m$# zKz4*#k&5d+h9BZtLC`>JP301Xc*3@TPTduqIH5*#;0b$w zEO`49$$(Mger1yX)0^`Yg8fuLon!NpSE16__-Q_MS-?kxDhGE57*NM&8*nTf)ZTk5 zkSAhOBN~1PqvO@HtthF;Z}NJ%kIwt6zsPG*FH$%Qow* zEw1_+uFunLKI?BO{@&WPt4+f0ceJ>rpXPyX)zan8T9qilto_jDBX%D%1Jo1gh5~c( z6Gyu7WaS|4ab`hXIfA|{?H{ddcspvauhJr|@xrCCft11&eopONZcG0`Jo?v|ckv#4 z>F$h%x$*tJ{qu<__bi~q=#P~&Uk^(Z-uKDfwxXDJs>~2Xa4u`7hwJ2OP9DpbB@mth z3?hvSg5s*G7vs;iI(SWN+e zE&=;31U)vg%g}xflPb_(8J@;lk#&Yg%VkA@BtYRQAdW-m7N#>=^9I=@0m_Rkro`~M z%n!ukB}+P2`QJD+Ztjo0W|BFrxIdF09r$&6vmtBgOT?Ev}SnbDCvv{QR*RS3T=6r|^RPEOIcB@kCk>aWZ))Xx*-+R$= z5%idmN)hBD%)lwBa?btn7d9FCJ(v-JPtL}Za&0% zUH8FgBQWFGO55?oDC8jc1Q0{?lg=h+=|#2^;=Uh2XDulz&%65>%>vo!x1&TXm27X0 z#fcT~6TAdJNkdC(T74`Qy3Hdg&W?U*HGGcrP_}icN5>)bB|ovki%4;8jK|%!-C>5j zNh~N`N|5&YKFa0#@w=1B^mAT++t!NYaMJrY6@85R?_+^eR(bOyn^AU){ZC8+@FOS? zw@dZ6+e4mIy!PoWiP(|CYMd7aEz2z$#pV`03dK*)Ulkk#1&9C5(2(?;JxfDZ%hV-6 zPHURTQI0_IG$C605&nsyJS`!o-3AM+SOA?35k81y2zWapjzlc)S)#Zt*0uoqZfFlg zr(Ja|>BZLM?*hsHdlnJ>!J*J<*fZk{y`=o%vTJ2J`m}}6$F9x0`@?b7+u@MV>iH|W zR1CTc#L(y$&12&){qof5&?t7GovHgaNN&>p8)EIS?LplM71S+&3%I|(A^ixAG%~P|x z7;R(VG;=QxxKuQ41bp42Nx+RC!!-e_q zWE}qGlF{&Y3A-~ntA&_%+5++{bVB;S`ttey6+&-D{aw%GLy1HDKE+WPngm2)QleK_5xm(#HLvThq;AghOhh zKq*L-CUX%l7&B;j@hdF#TcY$&1P%$WmW9@Z-1hc1-UOs#>JY#rV`F1uWpBchDAQK( zzI=VyXlddr3t0ponEUR{7I2Q-8xWZTuV5M!wOOG~{6{wvvaZ;#wz9PsT9M6q0e>6( z87X370lLUGj${CoRbR2kmDi#c2T6R(qhHIjYW)tY%spJqhYNrP=`TL}Z2CY?9JW%2eL-X&t#d+3@lIJ9Gr=5FQQQqVc`AX9v!Y$xnEt!lS=_ z4CWF0z}bKW?-xNiF?-o?FQgSywoY*!mCb$DPuZpCSlh+7cfMqNR{LE$E=HlE)GMm5 zYgDLwvkbQJZQ#M+jVmiZdbZj>$>Uq1jO4-+?v1eF=_RuK!K1h%|4s%^EW4!zWkE1i(C(qatBE`B$(_Z0NFRw=v1i33B4djp( zyDN!kk<{x2+FXao(w=R+$0eqMJ1#W>M`<-S%9^3pA`x`0``t9-G=mNMv7aEXCz zU6!T^7tcKY?*HVju;{^4R2d-%;ka0sYET5xs7ofKX_|l7mHWV^y($dkY%wtu-{nIby(e8vfs! zlbhlDJ)f_x-Tg7<3 zdAEzmbRq;SjqhjqFI}fNb?%WUw{SBNR)~<6b+ZzvsdBpe~S`;eF!F3prgH+sX`c zmzT+Anr3u`V=-tkUCY{k>&7YDS{efpeGxf={c!iVSE8R3V^TA307T;? z(mDq*4Z#UC(lDE)?$6%lm2nRNS>(W?+so);8HVB!^vYirAO{14O)uOoT;>YaFWa#8 zrT-#rtIJqEFl3#H8<+QYyoC?y52*a(@p|x&!o7kdS;rH<0mI+*I;p0{vb+cKCTQ>x z7poOa@w}vaQR;TPUp4>W7Vhx;iDamc!By>8KxSQJ3C*#+})<(e=fP5InG$5O!xD1PIJVG;C-(dzxPZ^%1mqVo%sgb?5>af6A24$fxGD6x|Y-ap$OkIrY z4Zwkh$2zP?Zlp~`+ir!SrT8$CF$B%+AVoa#9$027t;W0!EBU)%EY0(JwQ|%@|%29PDGkq`T;*J zUQ;GnjQ7-M>|?^~mTf5rJ^FSO*5)ndbzb(&~fZBz;nep@Fm0WoRYiW#TW-Gc}(olbtn?kyN}0r70uSd zM-MU!L~w^G?WXq}(G{gG*ZbcYV%J*O==KqUobtlSlX4Y5Y9?v!$45+Yowj-e*ZX3k zoLEFq%PPgtvIggh37*d1T>`3-)zp8@)-(3|-e)R#)OY0+)D(ca15`ez^=!z#4Io^^7e2hWcGo z-RO!Pi8&V+Vy24u@QI~Bf)xoC>TljW95%Xn16iMa(l;ZoD~RDNE7a7Aj=v^52(>`- zYQuUxjdY{>4$q~if~%#SX$4=npv|sf$xIo2$yI8_5g$jRhs=P1=rD&o-ZTxObKdHO zY~|Hq2fuFccA5JLJ8UFMbw-8S*85s{H2p}GUk$2K&a%OIuaWaM5`l^3= zBF`6xFjAR7>JIx9LQhNrg*bXiRh&U;Z=d?`fxRu`Php7OAWZ8@>BxbWK}*XQa_{JP z4WPq)BbX?fNsDQaBD;&au~J%5B3|jkC&$hs(>BA#@$wikUJ2VEu0l*{lGW$ZGyrNi zj&zc!^QT^K#5G6-JM7^e*`B^YceiwlzPt2kFZqxRMh{Q0&R|8ZO6+=$T?;QG7;~TgBJ1go=W=i`aNu~v-g)7 z2@u>CEG@NQu4%entOrSQ2)CDFu=7Hv*2oM-ow+WM#K>KH}a0+6TB_ z#`vr({+ohJF>t^oT9U4nM4s-I@}o%)5pVL!bC$oF+g}Q`_ej7*=mVzhgn0&-f-H6A zDsLtL?gnwwrA}@o!xNAKqk-FF6)|btxz?>CR^H~oec2{NC%&6X>Dh4@?_iK})pj|$ za4h(S$C^7m{$Cb=Mf!-hfa=KTtRl?T(qs>VfV!dT*~jdkwD(tw1O~Clm3P)N$u!*4 zlw&tWMcf&*i`)+p+gu^4s3{t^j^;oTp-*zZH>2(WhZy0S8~rJHrPo&#?$amK=aR_3 zV)@0R5P+PLAy1UGB#P>J8^-|P2K)o3nLemu8@GiL%~n1eHgOj^uv-;XqhuDB~25Fcw=$dgeSLtn{L*7i@KI z=b^^v=m6xS)u*w^8S|OLoJ+{YJW)8h_sy$cmxP1)+Sy~!Tjm*7+=vKl8Xk7lkx$t}Kp-CMb^Ltpr4Rqt^i?N;R73*?g%rOlf0<}H3BCIS^zPc4Z0_UhSVM8qJ)T4bu8 z^fbD1&|O<#CC*e(nlqRVo2cum>HHa-pon~iFTDJXWgNa*#;epv0*{3OSE|TeMolr4 zCF8G?R@ndz|9fejg`cJ!37_CsAD#c)6n7S0S{41(SdIMi)+&VqW0pu{PzJXqfik88 zA)?t6=kdN?1wbUGKZQIxH=7-@V^*Y<07I92UO>L+G7<{F1mO5n`spkp5=wut|H!_ zH5vSzwy!0uQoAwriRyO)vT&|*frnN;xr+w*Evx8o%AR3&Mw~+c52{z8lQ+M%jo$r& zd9~U&KG#;nt;u#8& z)ecU>Us4HUQaD}a+tlgrRM_39IBWMUKPQQJRXPR4GawWi3aioE`1bC$DAV8`*$(dC z+hi0I6s#1fY6~G<2m_<2@;hJ_mGqpVflDdxsvkMS)o8+B zoJ#BYxZigZKZ7Z$Aa8aQ4V3~Az3cmaYarYIOuuQJgFh&e4@~ z|87F-%xY@H4}vv+QXnR8 z(2_E&Ri`<(cFSAYNp-B>H(Fpi2eka8UD7`W*JHSAn4kBDZB)jolS;bNc^kw$kFy${t5v^e{$>>i?j>1CLT*ylUMX&k#1KSC!1G^E91_3^c`(Q7IQTT?h2iN^0pnX_}C!LbK8{WO)A4^sg%`~)*yhSpppMWdoNL?`|m!Rh}dqh_FQ1a+i5@x#{F2;7A+q|b1_^L8% zrlz{BHmTv!W#vR5rw#8vnCU5shYqAnb5W#Rk2A*6S)LjUxKE2sMdvE-tE|@L_AYbr z52TLdOuu2Xln{ZFRtutVQ!%BfmJs5{p$MQN(6 zIJ^EhW*U(k#8y@Rk94pD1Loawpt}qGqI)#xWfKSRx_c>eVb1EFwuM)bV4-_Cw03*v zE(*Zz4!`HQ_j*Dcymk>76T&l2-Z)O$K%*j&SHa6GQtiB^5_4LlXuYv64}6t&+Zl~^ z{)pOkb4!2jU1M?U_bWKfV>3gm@Z>WFkmIEUA=|xf>Sie)x6+&f&B@I!PX*<4W>N%L zZrQ%ueqNnJynn^JqF^hYeQ(kAzv~3LD?-1|n|;**dqV*X8%(;Do{-K*#}uW}sn0Xx zmQq%>A215)%{Vs<_;W`S>dVnxjb+3~C*>)cmCQ@eFFnNTF=AgW*)p8JE!O^RnlJHe z6K>dy-(^_-*K3%8zk#!&Kjnz?MR0V=yxqD_vg>7xBsJvi)=7vg<>;Clg3!`i&mJ{= zk$$`Z9MX7$-10a*@^5mjhOX!HT3Q%*ZM~I|rCb>%rc#|bDIhoNPaB4k5jHk@NuBhA zbepep*ep81+L-X)CLjIhSqfNbT)@^tHZBon^D)a5h{{K9A*CL3TzQ>%=`e z5Znw!ZGIzp6?vk+R%h70>+L>v?WteM1|-U#oUB;#5a*CH=mx58)z;Da#R9xC8$YBK z2M(A}wxQk1DA$?F{n^o6pgJzZ?3FR zZr)oc-pHVwGMD(q60r3D`$zvbSNWgLk%F&Tjw~*={GG@i5a%M$_AWAq7~%6^`8y&n+^Wp+ZREgg`Wppa-VhlKXkoyP?TZYK1>Oc3nCp$uXIY6q(}(}NGpwWcZqa| zba%IOcf*26!@||}bx&L68`?}9K&NvPe_cM9njdeANl%R&TYD`39 zzs5rsuc5&q4vNbmj1jjrz*(0gVj->mDyIES3Yr&U(pw-@L3vj&2CYedIeIGvu*Zq6 zG4;8eJWLAgMf%k7mxL?t?t=v$37o}2@|AXnNs`G=3ZCtQ6vl02 zYdPDN+B*0s^oP@SaZIYUhY%f9dvkC03EF%XlL%(_@4;wU%ELQIL8XeggwLc`ts;Q$pI*XOFOcm@QNNJHVR1k12C9 zvVyt?aqgI!iz#wWWzKgk)6CDG6NWx*+_vrL#fe@oOdfG!=H!bk+S{a!d)&56wA(A+ zcDx7l3p>ZPe~7xk`z@;@3tzYXS9kHBHupD&3PP=>ubXUe??u z-p`fq&6Lk=r4l^Qs%(VhFw7Us+u@X;*dquNAyn)shO=r;C~#7GAb6w`Pc5W;rJN7x zV-1MmsXxu3Osp`+WXa2a%YKHHfEFX)ae#;;Xrm!@ z@u|6DN-kR91l39zrloT(KW6iDw$c|@;D|V#akF^(-~W2}nxuqcNFsfRiI4N*CnG`@ zCk{&OXTmOIH~x) z{|8dHerEkxQCO(*hr229tgpg$)zIfL`saR?9#vW;RSF!hjm@cu#GDx}*8xl3F{$w| z3+j;Hhgo*3#c$-(7!)(eNAnx4R=t+I%QUYo7-3xFcejC?E7_P!u{uzhX55#MdJY^? zXejao?GHjfc&dtk#kAx<;Lis*FG%lp+EzQGJN8Jpn2HN#IcLb?cQMf_+(e{=*I&B1 zx_@5e=b)kSvux=kj&?6yqePXJ=fxQzdvP1Vbv7yk$*==kkLfEnAu_xfGd2p)^%sEY zP4Gv4jt?XsQ0Kp{E1|w3-)cfQr&prwON_k8ygCroTGw^{(Y z*QkhQ@R;_6$eSD+hhqh*hX^mhcT4t;r>Bc7W@ZvAbdINTURW~Aj$MvCnyB|gz5ZY{ zx8VOh(t@1YqVy%UNvU>&#MyAbpDl$MHr84~3j40M8ZvqPZ3_Zw%vA(x_~pRYmKBvr z+bk&102C16o6kPRpM10HDloCz5D)sixOktPDdebU>IGb}9$A?MgGy&hF)r(RR@^k2 zeqvBkDK1C8$T$HAYYt-;$t+~|i3Gf${M(Sx&IoK03g3!nzyV~l0mylNhqf#%AmeaM z2*0OYGRcOC^z=Fhu3DIPrA@STOG+-fvnb}4-_in>p-$h2Q&Ptr+Q>OE85C2O>nvuF z7jZwy2lss+RL*ok6QUB!bzssg@C}Af4u+s#jZkw7GbKbzZSto@@-7tDR&Yy8L;cHf zUEPL2nG3k1AM-IwMb1{Qlx^j0-4AqhOVR&tvlt~xwFo7^pF?fSn7+AA(EB#_ajxC*b$*5ksw zhxpA90m*e(c$9%txyys-v414lhEwcm$Q#;#_s&?XZwCgsoKi=R`xxbGI?(}~g-4M@ z-8O}HD%R$gXbC;A5raCH3ezbp_tJ_$1r0n|IgA4_lVE)HlCVSsaZy#|jPoa`$cfzZ zx$P6f4xjJ;eLwr8ZYQ44w2$L!Z7w&-tG}i>p1uq`cQd3ReU<0X@jYI4moQ>BUJ_Zt zjLkTTxCvp&ldMlSPgb6J9glaIMNN|XbH;RFZ0{~+rPE<+gy5Rdwm`|^A+iS;G({bZ z+yfu_LMY-){UB_yRGBGaL{3fg1%zq8+iZCrG;n*`ATx%RhUYarX)+G~O*utcWS(HZ z*Z^t^X{DTMX5q@_OSt5Wnli}=L|r1F{EduY#u0g7di!ID%<&`BmvZ%}7Ok8pwZT_A zQ4&T?)alB!c0VHvjLRgaV%Pu~WFh=*5a^l9=a&TheC4CGSxpCWqWmK>{%-)upj&+( zDFIi8oq#+mtyK#NIPv=OwIM0=J#Dzx#*~icmR31s);M57ycLmCt0qNF>d zv@IG(G=S*AZ?Y5q`7ArEC~uk4!$4mAZ?rcHQ>VVH5g* zI-(6TK2Bi81Gxo^(LyH>V*~f+&a~IvOqSAL_O0--|9@X*CVHN75tq~kU)8b|%GM}> z7wda-)s=WLU4|cS&rRsQrfjzDkFu^s^_DX#VpwIeWz_!|=Z<^q9W>)Mx*CIv<~k7% zcfl9f*$wDrCq7vB`_XO>wp3#`BS>I42vnMrt3~Q<3){`t`;Epclj{Je)=WN_DXP~b zDq@LjeGDCCBITFpY70@LT&%^ixB4J~Y`-Zfr0PD>1!$hq_R%6r3Qla=l00x~JuBp9 zmq2zm!WqGcY91+(_+4dX%$(TLl6Ol<-tzT*YcIsyE*NEU3zfPa%dG{unLBnzNg`7y zJp;JCgR4CvexMXG^WqkgLkpn75osH7*oGO z?;gEI@?9<0s#5|-B*LSNKxLWZ#`ntuzI-1FxyVWW=hvU40Cbf60Uj}oq#R2Zqa{rtDK7#(`lWEgrOSsy?+k8IKf(^tiExDd&(bM=LJ&VE4jcDA`M}3>l#+BE zhBJ*w45IJR=JdH$BLabiKnc(H75LkC|K zz$N7qwYQr}`{VRu3hW0b@1qyNM#UkO=(-&UpaoLVWeL0D%*huM0?t^o_)QKuV2{a3 z2fI@bjDzZbB6+rQ-nP$7`6ItP;EDyTUYvTAl5-Y~ysIs4Y&26D{4vq&eK1#`x2QnW zb<6n%1})8tw=02U`y;ylwpzM6>gHs}oHh`6|2er&63aji-KFm%SX(1z7ABxKh+|Ja zGW_eTL=jT5G}rkQ>9ih5ghOC%`0t9zU%>evctWm@?RB2Era+|Ur1JZ#xo}ol!i@9b zN9SON#pvgQD>UA2VOGU6^vZmBs!*^ok8W!V=xFwdanuzOlJZvmYNl(k^;o+>x6WMS zTXgc0^!ZU3t0};RnmUPsflxjKW^x{an=UD>qlBZ$M`kH$l8x4NuVEK4pC~1Ype&>i zvedvMmc)918s&AKOa9P>58`QuK|)$14nUv6lePjJ1vcmn&RshWEZv>&RB_ymP@76> zFf_iExV^S$JJ4H2#zs!S*4-9T>JUnzAV^?^CPKM>nCyJ4oh?x`A~$JXrXJeiQr>Jj z$928s!eRXwG&JarlpsvuYK{gt8^;&bor*aDBHa$)cZvg|@M~!cUBN%1(Cg+dD|X)H zmUwtzY4zgtV+ZknIy7I1qGkB>Rv7S!|L0GgEpzmlFNb(gr&t2;1BecbmXWLO;gw2X zSsd%wTTO$EMA0LQ3MYJve}Mb{yz77TK;WPLH%O{wrQBZ`9hFc?z9*oepfSl<2A^gW zaf)cBp;&^t`>P*QSkG3^yYKhqSQJb78JUML@6n_nQqv`BDRADd0=2KM6$TB5a1}bM zKb}do9XX@A)iX^9cJrn`AR|+}Mj~rGg^g}YO`DF0Q!&xKFVZa{`Pfe(2s>IGw<$%E zGir&Lx7$_beL3B$bkPodh)}o@S1Fd0OWey<#fPS_4S_9CQk3pE#Si5_hENDzy z-E$7Ftzq;yH=+0BCkfTLG}e}*PF+RBE7{&~pxN!iQlmw5vKxp0aPxz+`^FoF9=!ky>ox6y zMSpqqRqJ@-BPY98*BG6$4Z9JoSBsW2@$fB0jbVoA|M|uL^A_MW7w~LXwC@Ad>5rCK z$t%iVz!sR8t7&D2yprO^MLx&Ju(23z#q;QeNA(38a!xyv?kDt)d9m*>hrTfa-wcda zn9{qqwA)H1-m*lzlu1W&OXe68`R=4>z7>~aTzN;j5&heBh;JD2K z4uCe%gIEN<_B5f-{25Z=Dux#SY7MuEz^-%(Soh0e(j^G*vW!l)9t!~*Dg0Qi<5R1N zkP|-4$BQ$n6zFI{so5qWLfBy{O;*F8EhW$6a)dKzCi#jRLH6+Dc3~Ss%_gn&3#> z)AK^>c}Hv5fzpL65}iM1TtJ_(9UWctkWvH%$Ua?)`rII+yI!2Ln^RppEOPqV3(M-`9hzm$#^ z5k!n0A)cfLLzgW>5Be1$&1GTquIkj=^T#W5ZP0~9g=>j2^Mv=F4R6%4Y(6uln@itr zd04*UsL#SGz~J|nYY~-)JTR+=+&&FRC_uvpnQKC-e$}bWSGJPcv|avXi_{|*^YKy~ zB#wF+@xRXDCzRvP=coQ)xE#jt1EKHp8b{FxQQO-+J-$v$d+Q2a^!f*5$tN%ewQ@AW z_;AqoagV3EG0D>Gx-yML@|N3dU^;=Jk_Pp5EBrrDV!Q`B(;3A|UP;Z;w%V9gURru{p=zK!XU6R_ zoo1-&1m-SCm}MOL3{<|^MFImCf3)|ALJtYH|4dVWay!4^zA}GI=jZpjxy&d|r*eB9 zN&o1?W#{IQ`zw3S8cP|F(#LccFe>fO?mAaGTzES}n^tuq7nu@in2HbC{%9oC z@R$->wqMzQg`JI>k(qhF9-I00BAxV`C7MGngUi!TO`Cn|YjG#vlyR0rVQz_+iPZ@SP#?X&=^BiGM`^>HY-!A{Zxr)m|r z@d>5$oRPH)X#3c624d*8sn0_(g+SlolTrfC;BSCbOd_@pul?U8XsraC&uitK>`p`A z#^T@Dx~hNO+K?_? z{vn*2Ac5sGv%p^xWX`7hx$SKKw%>8BN&>4`s9dwh-}Ku)@5C&>V43yGS(*0FJ=21> zj=qNGcV{Ymz5Oe$YZ>e2vM4hD>$vbEt@-Tc^sfv?DalFR)~+)@o(Xw>C0if&el}2K z73eX9sa)xbx%XIz=qg5x0wtB-hsHy0{xBTMbLT{vDR= z3KViFADM2g^eoiDoB1%j^{3ulJ#@}WVaV}kDM(`?UBt7{4qGR|;+2dwZ-o&D_T+3> z@=hYNVbcsJ51W^~rTqSOdVH6lwLs+r?V7u>{hXfbc&aS-|FQxOn(7%mw?nWjk!$;o zh&*4z+5Iz@|C=K<{c1~lND&Upm9FQ?h0$^4hXAH%7}TZ=Dxr`4eMUuEz8zFCq-`++dyY>W5IE; zJAu*V!T^dcs9G{Ow>LJ{e6N?p%tT>ecRjS6_ffxaN**|0L=*$)B&3XDnL6_?gVF_&w<*zcXbEpeG3loZdL%6(YQ21N98|CgGDjb`zB# z3X=$)3PN77L#H4#fFxhKI|Pxl8}R|B0b5pm*#}i(e*nPU$>{EvU3afZ<((UtCQCqS zmH49NsThOP74BoNj825^a+qEi9DwP>%}X2Wbv`n#y|o?~#VBq_gs@IDh!`dZ)@c{o z^FCSRt>!k@5Zo>|JBIIMdUg!+u1M{Fv7F^>dY$M+-HbfJ{EcCqMgd3DtoFKn5NNRM zvSb}gd=%34zYmTqWEhnBdD-RuNG$Q>nYAkB^AM)^+Y`al%6@0hsx;L=3D)zlKix&# zTXcO_`kjp!oDQk-=fJ(-tjMG+dXHTQYhd%~eo1`MYr{jl^Y@(dw5UUKer>VY%Q4*@ zZgFsSd?R#1tp_T11C8D(yu}t_eJ9yF?K=vmYu?<}DaE3iWfijbN~5CL0c=yFy(Z}b z=EK3h6w#f_cX6ZkfQYVVh|WYzNh&W#l#5Eom#AW`hY;7Hy}RN zNAu?QLx@dNr#;NNyMv-0RCnv-B4XOchI~)!(g&A+s@I1E5PV*cdNDS{V2j*ELM08% zLlrimK%d$L+U=-C+U?AhR+kf>{XLMG`YggL@G=GvHLz5)z-;2TPP}R6mNp@jiI%yK z2YlD`ajdFCH86R{;Ef?C6=Vs4qP9h?#`G9+a3eHP5ahhfJtS2sl{-_Rm6pdTU`r)t+ixPdXN( zzLF}7J=8{MhzTYcGHql|c2W zK?P{<&RRPRnBz3-9ahUV+TUqEh!_}y#odNSsA;2R2xAZjM4~!35y);LE@w1cEySMs zT#aNm=TEl2%_E4^a+luX-5=DS9=eUzFcYNbqp%@vGpE-;ki0=6ZHu~Zz$(Q9!G!L@ zUHqV*nYwQ-4ti^H0N)RedS{TQjKl;Xd9-J4L&SM|Lhv7Xh|t}zHbbnT<@G$kc>0i3 z<($fH!PERq+;gX;^q4j8fr!gt`}J>YlR32`FddCEX1Xm;Ij`({6bv)DOxgG;=)BV$ zb74u?bi^i@p4l+d*GC&~1|_%5G9??+YVjoYK%xB;Oyv4;hY$^i+D1`U$2E#379io3#;)M}Xe^hBmZZ+$hT!8QpdYo*b~(It z){>tRMGdYT_phvq^cIfILnc0eqF>y3wuolz!h*5LZ~GL; zgl6@&*X(e_Q7^pkw=r%a;x+x4$#iiyQN-R&$hk;JU@C!WK=j?pklw5wG|{sGw+Kg- zU1T02vx{keO)_s|Low;t)7K??Tk_iyL|T6AEs>YV#t~3jug&_J@L)>leAV%^tRs4- zs88EPJT0;r+!Gg(>XC3jWZ*$0tSo~jUw2i-S;6$|+Wzt!J*7P3EED?-1dwdk8&8n~?o z_ABzO)YC++{!gz%7ACHPav#KxHuLwXl1!KDRvETZmT3FD@0aHU_VOL1w<(Qhk7Dlq z3A8s~-D~DM;cxqJPc3^yJvCM!-h6zS-S_IKwIjC3*wpBW_RrCUO1m;Z@zduc|4A|3{dR-x(tvNI&Y=5 zpg?PsZixGIqq*|uWNUpsI@;|L%BOb&L#U5{@zX?aa~!+xMgU>hfM@V#!zNdy4Rp@b zW^t9To*9+jgwG_yAwiD5#YNER$nP#w}U6WyFq9geargHhR6I@tJjp* zZ@}EjV(QCVsw=u@<|_EEb-9J$;<9Y@--}_%4R`cv-}0~gnLj^j*3y^`a~QTQP26M`z{Y#XzF+gUkOOs@9-UzvWR<)R`k!Oa9b;ZZq@yq)wr9k4R@+~n|qTE&;ZO3BX1NB7r)6D=aJfo`M=kFES z>~2HS{>y6AGy>)rSlXEuTb7HNXsk{Xh!;=d(JX>O1E*jD3T+P$Pm)nd7!)yfb}$;p zqka8dFTUQeNM5+E6BHcXJ~X72$1WWBO&m`Da0gCn_^3%}cL7(YF4m=jsZrrypezng z&E@VPgGi-h7k4FQCY!|MlpNR?Zg$I$QSVQi-*4!!b?KUu40iDHUQ+l&?j_K*+2b)) z556=ucDv&Gat%oOS``8pBAy)y43A5{k5&vuQQlV*a8N2P>Kz5XnyVz-I5 zt*1dHQoF2H^$jpGA#|(~)bZ>S7CXV4iOsEFL?_c;ga{2Nws5;z7Cyn1uzUOAIJ^`6 zL_G38xb0z6rVGG|!A42aFqihHR2@zDpJAko87YhlgL7`yGL^#ffM6_2#Ph0tW9%nS zT9Sc}yL~S<-x!oH4r(ewJk6J;)NwePckSbt{00AqaUszy)>cglxM zCW{)`F+)v)y0{^pKar}e@-3flmD>aNzAVw2&Taf2M(fnpu@O~)EA>`M9pXt2Q3ofm z?_1I-L1)TBeYb?WfHtBlqA8`_ zfReG2m2(;;zn9atG1R_G*1%Z@7*+Y2Bi+l}%N$2sbHE%Ni8?j^MU0mhD)%mbo|L`$ zbk9s526}M&Zq&vrH^a8{z3T9KZF!Ih|4<_M2|q&!n#iIOO6qNIT28B0-x564HcH4gnA zqfQXLL-!7u=Zn;TPxh15WIvJECw`fA6I}vYB-^!@ZU>XcOk;2Q-gw;DJ4`yOM^vL9 z;q*3-pkMtFWsx5ux}!I&o7i9Snb%C7y?&OfePiAK#3Km5D#Pe>@i1^9a9E;Vw_rfs z568t;2hS(~wVLzUqFK?1<;g+nDi4>o9HgA-k!^Psq7rf&FUTbS%{_zASRuftQ(Ff3 zqmUhxkaQ-zdd=n31&xRL!-H=y$RiRwQ~=2Vw%5DOUp%||rp9d_rA2nqcY7j5|`XY#OOltN?vtYiG9E7AHGvmSNOvD|oV#X5$mp_|5!eF6t(TWr}yf});iF;V3I zVnClb+H3?Uk$LnXQ&)P`U<~Tt63K(HG}Q}|=%bT5l8SEwIQ}%Jt9Masw(|(*bGP!H zvqYNIYeU!1`UW#3FNz}mXFIq#oZ!jqLn(%GAQeB7O!E2FV$8C;%_&g_#RH0jgh}D zVjF3;;Yq=={oyxzTdSX>KlS`qd2<-WvEo!K%*0(` zy)ey0B~0^qEX6ULNJ(t%0&?PS(^M}~@inbBbWzl7z$A<1UU4*kSC=4^Wt{xKMUvBa zV#H`-#;$o{H6aWRu~5zK@Zw*v?R?gB9+;kRVcdiJTR zSghyznejQ9N4isDtV0_w{s$hVLQ3xi)V|%octQ`}AK6$@k7lq=e18A-p{s4o_rqv+ znr$U0#bu6?6V$2A9Gh7#F$OKrrckTeQo%85I_IulmPoEEv$GiJPIRs)-K2F`$eIWD|T{LA}DI4hpC7MolqcX#l zCn=@iFDn@t%qi6e@#x2!Fti`CopUA;NsmK4CQ25=yMvWJe#fi&1MAZAZ)qBGCn-4_ z8)f68C|d`K8q6|F2sqErqslmvK8Nf66xgqn428kbLAgpKX3pz!9a*#y`K}qbpPP0*SJ7#%T(P!0H+cGr+9NBoj`|~u4L4)Pe7X#{rpp1&D`3X;n#9+B;F%!* z9^Od;1-n+K@phPs<`BpSK=RmcD69p1>E9tO-6wQ)?_pAPrNz_VgkiIhTYt|AM=(%I zXb{tSL`OHM;qkGiasWs3)gE@YihFuIL{>@LaCKipPy2!+V7o#Hk6nT3;R~O?(%bKv z>|wHtdSCrYgSlHyTT8b-?t)uSnm@`LX1Z^uFlN)w9?QP|v_|{EGQ@*suV+J{A_Se| zw~gJFR>kkMvXQZd7he6IrZXajEzT_TzvuJL-YzL>1(aX?bgZf`eYRvG>H9BeY*ayc zDBg&q3*)%@z)ks2gQ?a+Y5O{y_hCjSaGURi^*~d9ljgy=mFNE8^wtXF+S;05#^1l^ zqK(H*{curs>Y&!naDlPATQdb!1=B@MezJf{b%gY2ciMur^BX+aL3Jik4l8+@sxTJ0vnwe={R^oS`FLAKig1 z&Ux*X!V`M&7eTzmA(HFIbn)6fcybqf68@(7%^o5wK-Y_kthHS@W!@l(T{Rw%ncmUB zo;!tk^V)N9OfgI0P@@qhW8n0ScJ3f_RX@|)D6QyW{lPqL+#}i`9g{QA;GMnF=-Eht zOEA;);cOsjmiO)0CTnE&qDDmM4z|U|`{CahKW8Sh*b+-PxYhH?dR2FX7v~dh?u=xu zQSFTbDzwzsWvES!hzHI873U#G6o{VPZdiM_J6Q(ro&FI;B3`z|zML1^(mV0cWFD~^ z%=@r&@bp`wqRKmX4RR2D^XNN*XP){~(-BhW;qB~cIbAvOe*1_}FcI6ul8Tb|t&MG< z-l%V?KL5gE`ZIak?H5n(*+4fG?Jw31PSA)1 zpsxi>TT$)|fy$K6=wj|fdW)+#-iX5fr`fUqvKn9up(u2p_Z|ik`~_p>a@*s+GM}cH zUG=zLaRm<4g)|wUqJn6 z!4VGr+czz4t~g%!+zb8drtDOM0i)B<(BF$fy5eNEc~wyzS-_UtfC< z;jnr=YM_e@%BlulbycomgzQhB<$mIO^@TE2tDCD1^0AC9;neXnMX6k4mkM(brQGCmLEBV)@|pJhp+!TWCCS|t=H730tZU%&P1 z_>S~G3(?zJx$J9i&Q=$`Xq$szMQ?fiD=uj=))0z|UAZha1VAnt<)q*WKbk(sUJul2 z?uC2yx+cO+0a`#3X57O0=LA>*6`3J1VmP-f)~nvr{IefZ94H%0~7rloeR*8D; z)*$b^1bA)hu9zUo03*c?cq5IjR zu$0)ywzAiI{*+B=OL@~>&{UDhxS-{A_T$oU-@rg5FJ!E`N1^@FesJ!jhrnquME!oB zIS)6!I>O~YCP9WMHx6g$o1>H^jizb$o;*)p2A#2CkX69W+Febh^L5O}Ra-HQlIxH26HP}J+jYN4$&;;Qs+y7UEkq>} zx$EM8Y{Ht``2z!V1iyBI6og#xtNQuxAp1^7N7YMfPlQIT&P6Ol+uqEac=x5<4p5Te z7{$!*^$sS&q9hpV|6vx{bj9!Q&`psj?mW8o#KYMfVgX7A62rsT?H{o{bli;cI-7FK z*nRp4y>W2$a=LTCd*89yy{W9N0*N{rUnrYtEMGiH6%HvHu=R9A`ZPhH;d7dutsV6% zs?iMI^O(_@m5mI|FfL-WC6Sl8UjnPrGzbh2@43{AK`oOOLMg-Jdya8}I<|^}dbWzG z>Z~uga_9})>jq-hJ;-X%JIi=MUcXrJv3%-jk^X&b8()4#KR>LSWYaH~jT7b){wF$t zR7iah3gbN1nbQF8ygm7Rn=7Ww(0j@2IDYNB@WG8?v9jO)@iM5m{l0KLA@OxI64r#e z4|jZ_Z2Ug!dxz9a(Y<4NKKvEa_Eva>t|VA##eg_4*`x0Z%wRueayqV!HxR4&vq6Yl zGOEc$C-#2$R1ia#hJ3W$)cW0XD;k?astB%~1QQUSog}uy;Mzl^mXrkHU zBoQp$2>NT?9f?*BU$6E6A|Asv%2{m*-e(658~CX{Cu#P(n>5kEisI}BQ&4!f^%3J{7yj zU7Jr^Q!1Vj(gh~-=4}`jqiYn+8h?X4{pM|E`jNJ~;E2Kba(3+1>Vr$o%C7^w!jwV| z$14SRd%AO~$xd0K+)2-aIIUTueIpaC4yA-3dz@qT>y2wi#WDK-V=GB7-B1{%iN%Dm zi1GfO`H`20B|U*ZDfyP|dNoR|Tb4T)qYG|x*A}sOjbGZ{-FK4b7QDEmenvpMI;kSy z^r=H-789woSH@ESEsPuB0)?{a6K zkQ}7k3LVO$VIQmL4_>l+U0eocN*Ji#P-oG)(~E#*QLERGNgmvwy|1&?VaOs5<~T;I zAyy9qKvFoP>2P;Wzt4q~hRvw&MIW`to&6!<(u8FCnuYQ!U7wdiG@p}_XK)=wdf0{jB)G)U}fxB%h1ayOl*hAX<0G@r4R zEJ%B^hfL3WU8nJL=?~gPf7F~NbKmcSvglQ4;f)i4A)NM{4W>by^7^tq{MYvZ zs0^74u&7Ontv+L5_|G%F?a#4=tGoS~qo7nWsBvDsjX2NYg-U)&wc(HlWBS)K`cdS7 zjONmB(Ayc{&UpoMcpl$>s`J(DcsJ_+-zK;HlA?j7*ap7C-o=nE>yT|oneR?5o2<-2saK}k0IcEyNkied+?a$JcR8yGDvw83JM0*Fk8JzkR|Ghf2eRJmDpE!ez7%V3^8myoW zPU;D1dFR-gSN(F%%PEC>sJpG z?+hhldkceS&tKoY%CK9PlLsu-1cEv?k-Eu%+-R&+!Gzv-R9rA3U`S^DVO~FB8>iQC zOt-~5ZQdc0oocNUR%hDg3JlJ{SqV7g*kcr*?fj^#(a3@)*w3@lyX;qr3$2iI#<#p5 z*|hmYBhu8~ERaz`@Zw>X_1|0ooa}9<63VIfy7D2WjvQp*v>}3T+ZN-bon)K;qiNNs zgQ5>7wZ2M}>`zAgXQ&aYe0k$zo3}JV)Np0*D;=?-nmp_3Yyj=nd3+MHND+-sHLhDF zP~0ofXme`ztTQ;)Eq^dOah}nMXLt^q-V%#ch7~&GUzw}H*bNN4&o+?>xT6_Fw3Klk z>=zC+%ZEzI8CYFs(HPrydjn3_3j(yz^!bB3(xW2msi5=bY($(6H$5}guzpb6Ks$wz z1ETj;9-m9Dbkuh2&b1eTATxk~W@M-ROVAgiPe{Jq+l zUXKD~?LJmU64qNk8N8!%oE(m%Y$4!q=;;Yp1|wxlyB-sR11MMEO=2?SrlCP)qc3AV z7lA~q()d?82{aZ&W|&lrWvJw;C!f9L{JNfqBH#8=ZO&*sAY#7BS2PF0YAoI9vg|-`nn4_zB z(E+o1qKQQ7FJXd_*NC=1ek*qO+*oR>3ENp>RV<8nV_j9HPiV-?SQx_; z8|vGj?4p{-_m1zli7h9Ry%>G|KVu}TYBi6Ikz8$l+U!`Dz<(Xzpm_ zq?jLp^QRX?QZL+wJ$QzL8pSL33R#2IHe<&Bk={}Q{Ib&1?@mdIw%9}w=FYQ8(!{OS z!DgLBv(>k`{m4^$R8=Pk!(9+?Z&n8(fGYk$$l(xlas-VLp9$vkn3?2v8AjsGO|$-j z6>;AzBTIYyFMl+SLZUpDzDXGb_qwj(gs_K3wx*$nB#x zULLNaW&7K`n_F$X^OZqLW~UTQJeaSuj-FO?Q+{HZeyV$GuiY==mAp`m%Ff@<7=RJ!%MtV4yBd8h9;3+zfR+tD!joTh)hequbgB`LT* zdfI+grKURIUm?8m<(t&`Ce3$7W!V)Wj3%Qew<-^WozHx49aCd;Hww=@ZQDCMd^#;v z0|kD)*=}Hwk?3hkkk#OX@(uf@e#uk5ooqeva9vW{-Yki~o@>iY2P)p#T6?JB3|4SW zIYXgYPv`d*k*T*xXrC*94+qHXRO8%%stz!4Dy-}`v6It65A3Yf4FOUzw8GAyqT zNp&;=JFKk*Jm|%dVj&h8R26<(bYwep^f7_o{7_MnM2$cyYjTq>UYv9l6|jT292Rw{ zatOZlzKF+Uyqg~Isgk6oBl>hxkJz$-CLnw9BS@?xyBGO!DiFt*# z;4#Sq@YsvT}D0Fb=K%TeuI>Hc-r74Cz0NVIjmU8 zQtRsSxD-aSZ_;BXzWv;392%V~e8b(A$*C=MkEEAeInGCK`kcCnSN>fpEE7a}DinFV zm`QpT@=%|&;r}~>XjdRBzP`DgW~=|Z0aa&XKS%;AQs?5)Aahz{&sG0;%~uUR?G+cg zNhqyMN;}!~vFy&3D0ho2(A<5}^j2B^!=m7WseeoBaZ8!CsMj~9KH(ZT4tMx;YvGp? zQ8E?m2uYgjwxr0hh-cPxz1@xXnl$e z=Wf;-m*inryG>v|ltuet8towr$EeLudDya-xieB!*p0TVKdbqZ>pk^^ZRgfcY0x{| z4S8S;3J<@q03v#!Mi+eA=Z5@I&X}D`DMs`$*16}@uO7zE>u@$L)3Gxs?|ShKwk{|3 zB7Qg2U>!Wr3GrwLE3@lGA&8<0WMgNErrk&as$}DBs@Po{e~B1Q80ZS0&C_n7sJ)<< zo+>M9ZeS_-jjO!RIa6F5cq(0EZ9RLJP@a8+r)H~|n=#=0c~BYZeE8v;^!m7QXT@r_ zs+8yaf1DEBEeT0=txbV4-3&Reg4~7D$2o)X{L8qJ_;h`HH~uWy=*@RsS4c4TCB)5M z-^xLG^am@;E8e8}K39D*rcLO2O1omenl){YlzE316jzw7lhJSP<9z7x1f5&I1syPI zGW`)cX|uFHdizUuY*@{p!d>Jok)d$v>lnwgu~=^-Lye^w%O;kFd~q-lNl!Gp;5tHw zUq?D1|BJua3w-aHKUWTKt@y3IBE0V)hU|hK9jm5n2~$Rx(LR%{!Y{ZhQU{RNcAhuk z#lG$A$c&3U<*el>M34(9T)E!O`{q)EN=<5h?Bw(sG0Nv*FYlU2*=ehf>_9fl{vq^n zce)TLD!l3F{{wUf2171svpVeEW^!Sm{>0OZiVkFezysK#95Cgm5|orq!09_Lmg>}@ z(dRW6MqC*) z3|II(d~1~+e6A7>iQrbFuKN5d>NQsVFxm|Jr(PB0BE^ZY#=&4pFNR>C0en`-nyINP zI4P>bpqHMV^rbRXpZj?}{snS$j>V8k{Ze_7S~gyTYHD%EP3< zE?-KoUV0z_H7p#WI(pkV#J$FH$W`?dM?U88#=XafN@ceWy(Sp?p}(^ASxg$83kNzM z9l6V_3VkGUeg!fWxt(QpXQTTH1Bq0Ul?KMe$Nh|-nD#*p&wmJ3gA`aW&uy68I z)|Op%72ax^joczkyVP&8^MRSeaqpXBt{SP*Ao5h3)S1XZ8I8#L&5Qw27}6t(AhRLq zdR+ATf(LGHl=jUmJhC);br6kB^8e_|R$jfF&xebg{v9YG;3SA91)*QHLkiPuJk&a4 z?nS32w}QxQ%tP-8IGfk|5~Uyt?JEZ-PfsUHc}~1;f2IDK3&`3%6i({09<6k!ui$cI zp714huMKO1h}$IJ+oTT{%D+xFu2(%zHg>^zIu4bq5wD(C*DfnvS($v@xDC_Jlmw(Z ze%Y>E`wry34Lbjm&zQn^m`=k}R&iEayJoCb%_v&#{lrc1O@pvJ%3b*gudX%h6G$08 zf|*%7?vYh|-_Xb~jKB2_M`Ga9$ZUCp|6CGVpypE|%?D>PNN;?77>g*^Mea=5i<{iW zBut7(Y#xvL8tSZwEGMC+))NvOg^LltCio6VWMM3um&PggEdlEb?|1L{vTBE#Xqx5}%ZB?xc3E)ZR zRC#SN!^hA(iXS7_>dYoL{@&EuX$Kx<#a;CCO?1C&$9V=XcIUotdEAJQWpp5-ZPrWS z%in6@osUcKBH-eR&;3#O{c)@9y7mIo&7a1OUpHdUJJ0ozWq+lU|39kE!Xc`5Yx^_; z!bl@EbP5P44I?ETq6{6<(kWfiIfT+7tBxi2Tk0kaJcb53z4)eq&eTWoBc!AX!mDGbG|7lfCg!t3EhIUq))De|&~_c( z8DYH4+N<*-fuh5?Iz3=qg+0l4>0!fUp%yr zUA!jVZJ4rhin+s5)s4a$yr5_b%Gz3b!E`(vK#usaVrs>h2I!MNng2NK*2 ztygU)4%}u*UQS`8(a!-;g7AWJ**`flg1)r!P;e`9VJyb z9B&36Hq`ddYGOJn$zOk2<6F5N^W+UExgTB9Jys72Jg3(6(on?Cq!0@pBbP{}~Dt$eJ^$jg>mrkx>gSQ>;kj7)UP zms6%Hi=0o3z0{!l?Xsm5Z9wbLZayyfIZhXQWig+($KJXnm=F-48A2%N)(2uhgkyQD zn1(w~s3qGB5d%2hO&@L_XIG2}ixmur_{})HGJ#9-+~r8c*as$mweQ#-%73*R+PTGo zPx7ekid1b4-48=a1D||P^nI6j9%4OWY5-S*NREx2^U^KBoT9V2wZag1kulB|F}F3FK~ndolBt>%JCpE*O)0{U_JoD0O5V$RhhK zx{Hk9g6IuzcYyO@pnj0=R3{EbU+rkW-R9$?_gV{Xu$9T%k_DqCQbdcXY&-@%J8qUg zE^}b5xD3!46z4K=`xx#*`*gj}xa$w>Rw<5R&I-gnROH?)?Nac~F`L{roy-L|3>!1w zS@|c?(2BW#lT1QdS^D13agDTnIe8d;&5Cyh<0PH0pAY=Hk}(anVD+7FB(j)}Y(<2C z53fOe^4}lWj!~Y=7k{rWW=d)RL~b8OH0>(v`H9hEqb2*qyv6v_h*3e<^%8{R$`yh!I-c4CDC|MH2G2+ z1ir{q#O0}ihEs$g(#J2a5iHPk+$f=^`UIN^{ZIpkw+>6V4vq_h@w)eg8%p3g=Lzlc z6HO$3l2f;^0eVVI%G}>48n)5AUF(zVph4=8I=>xpy?ZQ}uq$w3>`(zZeT2#>+s}D( z%rOxWb6jf8s?%AP%1TEVo1WwCd;x5%^rR{!q}tHG@>6Cj54FT-H1F_-R~4@vCZyno zxElaFVZTvNm1A(^#+tCz5)9XDEi$Bull*{}Ekd1+NjeU2ZB@;aL_6?o| z+b9!cn8|!%oPwuVij&R_P{PGYoDDL^99FbAp3-!j(l}uEK}Q=eS9Se%1atKyu9n=& zso>%r=i&y<`h8aluoX_fy;Wc;83XJ=GIz2C7nWz(A$0G|S|5+_cc@=69%vKkn;nTu)h^V_UyI)s3#gP)5=pofB$F$k>weOn3UK?bmUqH*5 zFwf-;OM?v3^8*4}%fQ*>0MK$`=@J|i_l^eW8TZU70F~b0LAOibMovU6{w>^k>>cAy zpU;c$>#13TDrt^kw!?x3_5OiFBA&A!hFv_C3~~Q|2+?qq*lT+U$S!*42aJgL%B%Kp z;bw-T#u$PVzv<7M8FXA2q%JF9j{ma^HYJOjLw+nY;O}lg1#9ym9^KQ9dzEH7DBHaG zKP{{yj9&@PhilPy7?yMsz3uu5e>m2t=F=#dpae!}b-q1kqzJV z8$IbtW|y@3U6BlIl)H;*IcabCe&zKW#jK1B)NL3k7@G5TYtI7DjU2WeG(k@Mwp-47 zb$kqQ??YA1twna8!3z6$gzpWx6WWD)0L#<>!6O59tjtz(mD^?(YvtGvL?*!WWR=aoDQXEnqQ~M{*5H!^Hwq`U1Nd~@X>qcQ6OrNadZ}Y2NytwYR_i93k3KccmQZXZ}|&#&d5_3+YFe>_$82|`uhGqIVxYF z2nNlUMc_)unAKbs8da_>our*k`gEvK z!RSDIgms8RiiT!25I7drwkC8+&$0?N&}H5S9ceruG)$6WxmR?(ozJ$uD{ln;G;ETE zZu~u^EM;byKVT?jc>=t4Ns|%JHk&QOfV)Ezr@6fQ z@o94xhykqc!tZ{RBVU9pIzP~}&s4+6*Dv2BQO*#<@7KM&1#OVV@i({A$pu~g_iSqm z>JkWpRwJjrQh0&sSun_NEonn-E8DXg;iS9{_sixx7XZ)wIo%vED6J|=Q`+?h3`amg z)_!bz{k~^qj$5ED1;TULy-E^1D$2(wZbF`mx8v4$c^8;ZK}i2@)9_@u@k-ztw_Qo+ za3;dtOdpRqH+@jV-G4qOSXJtz^<1)E=EFZOctRN49ZXNIUD@yuT-fuIZ$y>SfRO-k zs$5k!BI46Dgb1C2#T^KFi_JS1t{z51&Sa2#6^@6=(o>fWKDmg=SN0Q&7cIfGQ z@0o9%(dc+D*Z0C;KpOzgT!637!0zhc;uu+floU-jB7(LGSvhP(v(UV-vHXyjZ575) z2s(Gzl&%#o#xgRpQ?duEX(n6T>5f0UT!V<+slgCi1$1>uM zy47Ue7&s(`u_Ph>NxeHXoXQayy^l2Mv?YXnpD%l18V{PW3wx+HV|;Ca9MyQyJem!r z=e{0Pkos+tm>R3n+os0#aZsgys9@E(N1Wnn)Z{q65%0NL9eWJhh?!TP@DHi{3k+nzCf_QjeyVHpP!`?5=-Va{MkwOPiPkkW6o59yXcBA4 zL~(;OZvmWVkV}Ew!OgfhJqYw&^G31(PoF8o!D6%i^PSw?7rpqT>nKF6R34gW+R zg}ce0e}T=kDZi+O$P*K!GY|qJ;={=XSd$Z9HJS92r@I`2EmXu_9 zxr36RB%#({Bs!)A2$tW?xueM#ez@>|0^ zacUrYO@=}CjLaqIlT3$a1dBnQqS-TaC`S#39L6*LfU@Wp(LDAl7UaP-&GM%kg7qhn za`dBI1ouj1rz*e8s}jEY5=;Plp`*6BKrYuu){w>9+J%8=ywZXi-J-6SwU%A9K_b}p51{W6yuhg{umEP?`C%H8uFpCGSE*()|1$lZ%f`FDNhEWVPdm7Bm1Rz%O>*$%T5EFv*>=#O!uttk39~H; zG0F;Yd6H76-cDssd9=CwZrx~hzmIWwEn~f-7wcjJCrgps9g}1VSvsdrtQ+8)9+LjY zcEF!TG61&LMB!^H`+hi!<+O25v?O=cc`RL93@X5hO3Tep&M*12p#CXM)XAw2K`Ga4&2=y&IK2>s8 zPjP^~j_3Ex$6raa{VHLtE=QYPGyyzxNcbA|QHw#p)z;+T*FFUiF*C5!*1miXIJ5sK z6jI~0)n4fPGgofIlp)$do7U8TngAOQE+ZRBk7Th+RW}X<+j( zdz9;pQS@L~bGEjQ_8;)+ZZ5=VP|>*n^{)^p+v2=6%2jzvWZo?Meb_0k-T!hl%+R7& zPHIOsk-Q?|!MnUNN$lCA!{?9YNebY8%#w{hRM@zy2)#O9Zb3G287r%cA*r63^Bm7Dnx~)>I3%2rYh|hwRL;FbeLBE=jZ45MJ|Ho zUS5Rtn-lFM1%mG+jJU^Iyb!xa=o1A&W}x0?hiL=(@ob!SU2Q}PKEJUVi;3@AY*=!>l0B_znV`zuq5juY_tfDGLg?sExRFSllk=j5XPnoJH+F zuyq#?q%7>T5Rhw)4Pks*7w8R4@i? zk>~@CTcs@{tEW}y8+-xC`4YZOV{3a#?;A#SU}nf^D$4%}i^Tm73lHQ62DB@HMyC<5 z6Y^Is@z1kPo9gY*i$IrrW>+tBJ^_7!r&m%35s-7P=`##znEj3gZHx11OS1$0!eFnb zC(&gOtmI^KVDXR;#-xgJFI1Gh2OSdh@LUXR{L4{&rGQAcwsrd6G<^sgEUfBXD*HKM zGd=B88PUo1i^>t88w$O{ay$QU1_w!(Y@JmLG1x|3Cla{8C{} znX?y}c|eTMO~k^H^cEXe9t#_ToB1!$7+Nw#nV`RB`YvKxA2h=aMV~-N%~9VFoq>y1 zH$^R2iy&_YeDWB$1<9mXLRJQoztFXtb9iy|>hhe%)KkmWq?w^-9?eH!9$m&yQx1!B$E=Nv74j;>~u+Tff+-p^C)AYvB)nCybn%|Iz9ruS&D$`YSA*2b2Op}a{ z4BY$M`|Yd!P34IBCi*N-{x^HhY8-=TZt^;z;;vhY;*i74vxQ1HuvHD#*P-3@$V+0V zp41>Z7~k9MN5T#*H%?^WB_Go+YH38l4Zsz+3X$ws2hqz@^-=A<$xFzl&*DCHmfBxq z&M^e7Yad<5bRc6cIdZxabbl82_W$DRdwFSC0Bu6Zr|oIhX&qT4^Z4Br=&mpRoxS!N z4ogchP|e}%*m3H*dKfih{EN>|snQYWU6NHyy*&GF2xny$-@|uar__2n>Y#8|VVl(G zehQ_gn8VL0G7XjSez8A&7v@(39g=)|oIAa4Rj?NPW>s3Vbp9*0BZHR~a^ly_S`TT4 z3$k=^uYZzeVC|KA7*Pp$`RHK4*dtTD$r2HcOW0X}c|S1+wmB7VCYbR8OdnkpLYE}m z*E|w!fY)y;)|}E9+#9e3fEZ3f{}hjlw5#p#^7q{PR6S?i!}$EF=WzGa#AWbf6N@ZU zMC^t10zxppRfs+P8IVqJPnOBHHHgbZMwXi_nP^&D)F`w=nrE`MX)I)cHyPjgMQ2OL;AXN_F1Mz3&jIhPCsxOuIS$edmF)n$- zYPyl%PH}M|ECRLSug?&?pqe7Mpg`@!GO?j=_@&J_ysva)EsWQD8T0P5^@Ecn4aj6!N|ORh<=`r>$>}6^%q=@BA5fKi zIx0x1(|wJ=W^z)NEGQEK&vlFuV!xHCjj@upN&Hl)Y5;a$odlJ_0D1&+cPY4wq@H8! z#ryi`nHR_WiOQXSE^T5(xnkk_^_`wXPCvy=I`Ot<;Zo)UMxH=qtLP^*6E|I9%dE==}1WFR2Rcm%^K4?@G);SV>Ly@T{e1)<#Ro5 zqFWaDqvN+g&knu&pC3O`H#9sE z^modYx^cSeYMr&W&Y4BrDX~#s0*Oa_{-?uF5bSBt2aR-9R@E;>^QVs9xhe4`9gR1ZLMv#wI)p%p5u%h2w_!+;B7}6z9s?z`!#6a8r;i z*~Mc~mqr|gX5u&b3$I{~DnTjtK2$NcwYLAYduYma)zsC{ z)1Krgi1TD(gQkk%o{cz8);y|2vN6Da>6k1d&eCh@Njcr*r8Wt49RA?w2mLxVl|-Q+p(Nj-#(H_kjE@g1tiny$Qqb|}u5saO4w-uRKWSxs7V zXBW4BSK5Iph1NM6m%V0ad8+HpIEj0!`5aHNs@$du{74f^^K(Kg*Dn!dhjGGst%4^l z6pSWiXd(|YI6N(C`P*9Rpw1;~f_q*Q5kt;Q57Y8mH-C@}4Pe;sIBCDp$A}+~#*?Qg zWn?+A&#aZdL)FuNH43)wr5?bxFWqc=`j|l zPewm zxu403%I9-m-*Dgi9lJ19A55G!s*4ioHP0n?c;MUKhK5;)!YmGQ0;6Nl5 zO%V#-t-SU!6Kr2A;&$b{Un5v6B3K((9PSi_Zx;ta%pJVUaMi&RoF9H(c?hb@R z%>@ux@Wf$S!FUJ1YEa|x1g=L*ob|>jBfGU^0-iBgu^m4xsHCqkAc>r9ZNjXinb7B^ zn)rS%&d`sU)cJ*=B)drA92C+ptyf6w$Xm>JNM zE&jexd~_$^^Z+WABYVYClce17H}|_t+zeilQ>k_tLUW>ut9z4-gr?NIjO96dU-Er@ zKa@eSW=Qw6`j0vFA-}0fVu?oK-LXzx)h{_0xkMJFwyifz_JO3>=^myA{9(^b;>h0x z_2!PRpiM)>dQ+IfbO<*42Kex(eep3!9C6_yiC$>(nUIHbh|`O{FQn{3jvI?R(%o1ZS7Y;{bV%@eU z*{VSz56}k=ghRs#al1smb^DI=_+hZj$hh{K{eCeBeWjM+o55<%T8yRb+Y6_m>pna3 zNxoTg%|qB>Th1{U*Pk1oe75ES676-g&6?+Q7UvJg4-!;r zHWc%uyJR;kj&MT4`GSZ*5gLoFWfVM{bqK6AGY#?OPUydA4MBUqVNyS_7%$gpxAx0ts3pAb>hi3m2DHTDE_s@jcqDZrk|6c!k&nBO2|BMlZL${rLtfZMEz@q~u7oG*y zhcKGWUmWRGnd5!U{KOdv+J-LYWxHOBR&DZ~Ei@+FzsR~D!rp~{@5m_++))J{AK=x) z4Fq)+9e?1qB(3)!iyaQY`=%-E8yfR`C8Lf%+Wf`8lPAs>@~_UmKdGI9@LG@u$<5(o zbCa|D#(xa0(cO3c=Nu`ODTpupNcCK)Lr(Jr+Ujv6z6&Uh<{{Cg`OSr;xEHkb0*_tt za@mVHsyP%QhN|UGOu2>VRoDLE4%NKTY~Wm*JndrV_9zn|X@TkRZ{=(b zq@u{#kqqfNx(Q1i^l+0SW4`{4G=<;r^VNgm(O1EQmHF8-VjVXv9W93Y?`L35T)ny% zQyr%t1dY0)nlKTivSrXWkxIn9?Jvf{kh=AoD4({b$?qIwQZ8c$;5uK3&&fs)_!^!> zgqx)SqZQ3vp-5@-VLRA{m=AC2`FW@{S!G>~RHyEM#OW`7UpaNxlLq>(9RJO{fgTYH z24=6m5VkSdF%aN);psS6;j!(uWj4LLdH5VSQfIqGdvmq=jwbxXfX$)SWoDZAfk0pG+|(KUb<{mt=j<6LwasA&jtuDUF|vrDJ*p7$jWzDMnk z7IndQn;+z3g-ggk+uvEHHD5f0dA^eR&>mkSbOGO(7#we3v`@*8a|{^2yhZaT1ucA)4B}X9mmHHF;%__JH=Ii1!h%;;rJMxf^rN2R?<#nAkt^~ zrF{(&!|Tt+{;YyJHy*nw3~=@ymL= z_JEU+0E-_jb(H|Z!fW0$*jCzR9muxb2RsIi5K7(KO80M_%@eN}j(C;1zwap71m#i` z#%0I;S-Sy`a+^7plk;C0ev>r0O)TNSwuzmREUr4nsh69MP9}&Eya?l**i!z=6U`a( zR=)YeH>H_ME1=G}o-BOy^4Ghh58tBvmHf-cip!*-;($E}iokLBPe$A}GN?P0DXh?; zq%%5he6HYik`dky|CbUQm}IZczJ;IGf1v_KKQ*Cms#$OB6cbo~xp#Wo7p;Q&fMo-0 zWb9WzBHw_v6h}xSWE*QO5)?ffrlC*D28h9;vBl(gCTpyJ4-LA6}c~*FlzhMO$5*=Vtgw@9DPPGVPBr^a5e=>n!Y7gKkf1(Ba%=~ zK3AqUe)CJBRv6Z#D@jVTlQEjMJ|ST{MLD{+zKJMU&L_Xrs@sV8!U&B-gYC6;7|{Jb zX<-_x0GC?%RHdI6c-)EX9&zPt&&(m$j+bS00Gu|IvMHQgYh>T`W^iI#|Keo!f^CKK znZBhZgyyV*c`j~-^{&3+C;Q(Dem7zEu(XTA=G@hfmyP*PYS$+VQ7bDX3Bu2#{>A=~ zd`Pfd`iIT;n)(Exn7C9Vy?$_JSMw#K4&d;Xy~$2iInRxn)X&(cl=U5EbXozx0RN=H z#tKG#fD0F&PG6McQH-tcDk){obGKgpm&A;K>0|dW^r+=ME72YUYTrYS7a0%>kw7itXR7&$ z(+&1!_hRW0%=!zCF($-kxw8jnxv0l~6b$mjpxsxH;t{PwK53a$O7RakR2Z8l*7u-6 z_)MTT4PMCuS=O=nnGH*b)IbIFlod5NnTMSiRk0@@!*`@Ge!{2I`;PJTx(uTgCfP1R zSL?f@=ePXd#$GUiZ2zVl@AyV1N z1&DG!@htc8_z|_xK{0fO-4}*kL&TSOi;riatkqASh6D%JEs!@2$ydZKO|$eeE79Zi+Tl? zyxr2%FaRtomo=IDk3cP_vN6JfW^?Z!BmRIOE5=gooq54~6F6WReNj&Tvo{XNAB(7k z`8oC;+*_18wcQl#0pJp{zk)&5^Vm^!i$r%@4U0d6Se^iQi8{TiZkmisR{&ecskygX z$hzk*&2+iX9HT%$Qm`T=K|e8d!+$-0;!!i4(~t{{FPit`SKz(ut_=4P6|BiAYS<}g z_bcq}VU!xTZz9uIGTc1@(+kTjzr*(0@0FDG$^2F7-0-Qfi(R|@7)dkg3P(kuGX1a` z{su;c<;t;-fd5Yb9%u7H&8B7oWED>6w`}}eO`wME?fM-*&HpJgI#&g_*;oR_v5yQ! z6TW~+w#Z14g_>|-`M@R;&7m)a)5Q(m_E;dgBpsz6y%)e-g2zRgKv9)-N{=}2xfv^( zWYSx_XW;piUT&P;@Do1zRmNf5s0OCEIhl@k@(j&=ni!m2DM;li)?ZgT#OvH16K##| zzA8yr^lJRQw-YQ2(Cb1p1{a;LHmljTBzt;cU9i;X_7W1Y_7I8Lo`@hXKVbWfBexIB zsh===c{P>dbcaFT6YjVpg^k2?W8Bp;z2>RYncN!GUq)sXh<51KG#d zAQOI==!7p#v>WOuj{^y6Lxxahj<48EQ*P{0;)Ze0VMvY@h-5Nnm}N6XU2_Nh40p}G z2$SebCiS=jit4?$$QoDf2PkqE^`W7Z%8K4>Fh}7h*=LkUO*zK^q!Z-wPSNG>Ao}k1 zyYhF}TG_&Gl%T!=LqWb8Z7;vA^80(91tl-F=M*yx03N&y4Sk zt&buo9%9O`O95GG7FSg{PB%{^y8Iez9HqFY93fP*wxjvZ6^O*SJg0hfR z*fe*)a#KfVYnT`e-czKJ@V^}c(kQe5F4$`ss8MH9300@;7K$@mS_ro*-X8ZY8!toE z=fH-6K?0K`nb7ey&X{xalw``jP+^k_5jjU}?vZH&a6~P9Dx-%%sDZfjV_D=MstkVW z|0Em5m>Hf9+lCzH1Y^xr-at#yhN{2dB#j5jfwOayEK1)ep+yW>)stu_Sw84*PSh(E zlarG{$YgvHs8rg?O^Lu#OZc35Q+6*y$w8OXnhAT-8ytyHtc}2qe7R2*k&L2H-8BUe zG@kZb=)J6Vr>N`Jpnx~5gFb!TX{(p50dbrzJHTFwrnTDWGgDsqh*63@({{D=S9?=* zQ4<%!4;Xjb@i&T=$;^o*m=7r9%f}!|q&naR+V8(aYm47q6O7x{<7@4F@K6OCoNG5?3av8*A|3s|193ej5dkp#P>w!hl^yLoAtf*k{u|0 z!oiC#>feP6`{HO)>Gn74Vt3e^7&9!@F!jF3zZFW=WyD$K5k^&;dEcf6u!XD)k-`8b zE8Vp0UH#1)gYBBShTq)d>(Wl(6UUQ+6_$r|5%$WVppXbh$IKhRa~ggbS-rHcap+7e zx|9I5g8q~d_S^G1-5!LHn6DM5WxWG;G!!|xft`+^p5IL%yqkIKn+S7CzP(c~bB*?= zG!{wbJH>4kxO|Ut18xC;pX;mDu7I85xY2o7_N{q*wwXio9@-=g9qC6J$mXC>#*!%) zx*EgpOLV2L*l!t56Q&|!V)<7*vM0$U_qMWZ0BX}>kQBQBC5BjJ`TSfrcI@fSBPmpY z!r~!}s2yu*MiA`&Lcl&~@6+Kne5Z46SRV9eFG8@t#6y?Z&G&BoyUfVs=H*9k)PBhV zcvrR6tJrtqXcXT>xSXqZ{>t))=4O_uPgc@XZR_0R=|6(N0)>732C?&^bT5I0LL>7* z=eD@5(8|Rhc!d9xg8rBH{ulEaWkA%PPfa7;R8kg~v)9eUUuDB`S6vgeU4pynT^yx#kw57~t8S9mE`cuPJE8oa6{f9O9w>mIB z$s~G5r`~-a`!KQz{1b-BMcZ22MLFE7lr*5J@I_;8dQ5#jEZ!{RI?ST&E1T-{M_;) z&Q5(D7aqqWPy1lit4Iq79yROyKlbLoe+1+?n#;&P_DldRX-dG*iCJv@W}JMx^WJmz zpzeeGR94EncqZ8{KAm)u!n$&sw@%VYQ$cGNP;NIiJ@>1~<$9)_X;~mnR2z1`DaOLw zyPfeNIh9Lbgn}9z=De;(+?__h`Qg(H=OZasXQhBOtzJBOo(PEqsUS+@3dIskn{_60 zRU$bYLLwfKh`mp;AyeTHcaa7rQhL`Y9R%f%{Ru~LXR{Ip^e5kt ziC?MNgZ!UgXNdoNwIidL$Q1l>+7S+Rqn5TWLTPks7f zd2ZesbKiWj%?7gk>=odA|E=N^<@M90PFaLc|yRoNCy}Rj~vS(VvXQZ zz)MJw1d$qE)cVmmisdHHX?F~IQ@?BHR}mnOF(oJI86|c8^4Atavt>q!2|UZ)Fz4wH zz2#}EB@4&CiHnp%RL=sf?HSdQ*2|0iz%io#dvxc{PoOo8?Vr}Ta%T^lo!XPM8J;ee z1-Xi{Zqc?AD8>&}u>Z~++OXE)$AYw^a&sWgj7{OcxVP>6Up7@(7@rW6gyl%c6h!91 zYtjQrTwsQrWlM(G(I%CRylqg|advDBw+ZF6QU6*Dt&@k${g_gu*U=%h+lBv;0-6{y z-HnCF0NZ_s+DmY^x{)tn?ZWKpq1KcA{-cqOe`n=q-K1bebymHPGN0_9${Zzx$}qm_ zHhuIYJXV#bC@eti_6;EqnN~O!3C4@hzl9tW)9@`7yIDjTUu{aWHDvYH(5e`%`j4F~ z-Bsk^ENW=P*QEOYUGW{Y&MrM4)Au~_7wQn)Xwy?XxNy95YeRa!T+LRyKIC7Cl8HTU zqZof!?Az2F<*hxc!qw}pvve9ZX8Vn>b6qS0MtjF#cUE$(=u62|lX>ytC&_-*@7mpe z3LTaQ61^;~aG3QCB;TPgJ4F4>cchTrDxXp-7js+HH7=GLGC%pqQ=k8~UJ6*RmdctcPI_D(F#AwKi6AgNQl z05?@$1rLMj-Qwn`_M&2#kN*X~O0t9;M{n;`-}*sahUy3BKwE+o{B_U(=`NYslW*B9 z&y<=c?m6a2iqm&+E_?rQ^=5h3xvb}e4*m}>YWCl}sF(lZMfJy~^_u^etd_e<48NLv zh9$^`vqVMwytX$$B%0VNXx_I!DTjW9V(6Yf>Fyapi`M(EbNnb~j5#I@aqY+t*FB%5 zj^@w*i;YU5Sg8E)Ni^@dVq^VO(BNpF-FWKpS#EJbQrri}*vT;c4EH*cT1g<+{T-y! z)o+k&J2l$yg0t_-R_gTO@Z0)+BELNC#>s6Tq-!XpR3D7&{REmsB(IRGh_u3yLa}B{ z`x?B8RC1^^Zhdk;8a&fxL#yYoRm`AqmNPhw!@`0lx=V7!@6(I4;GQy8Qe~fVZ zD=8mQDZZ*>pcJ5_=9T_^a!raLgOK#X?XV$!?ihaQf6QpJQC@L91SQ~XNxl|@f=)Xc zk{NUEFf>91FJ*-uC4(;^cD-i-5lBxgAmqKZK%BBm>HDk+)E1}8-gKchE1$R-94iOH zAZH+LMmAM>zH-(TeCFi*9IJiU_G^QW;9U8!#((ppQbtJYWA`UBhzq_Z0R$;?(#fhR zg~a0SXvRZzYYgU}-`OPB*;HM=5Qi20FIR!E3>Mk@k$8BcPv>qR)=2o9$m%kAEj!L= zRs+udG<0*(q#|y7mlTRWz6z68fDi2=zB8W8OqH=|`tC?i;-S$nZ<%e*yQgcR>}m&7 zxIU|}k7zkIb-_wHznb@lYT|OBsmbDb-=o9NZmMDV;tcZ~gqrN_kP z1G`tVjGpd_%D+q00r2+tXIU3!uhwWDS4pvnZk=W92tN75JwObm+pNcn#(h>6AQjSq1D5CVJVV{apUq)}jm ziZySsaO9uLeFpY{AZ~&#{kxb5n37*P9gJZA!z3yn#dhW|;P%U;WJQ`89o6EF6Vb&R z9wC$Rl@qDk8ZigdqnWFvHT;SdOlW-fODvNCBp)fAbN_l6Za*5&<@yA0MOYX`p6-N1 z6r+7%-;J^R<1FkryKMDiSXEHHEW(3S(5Q0s)1RI2gUg*A_Xew80P|F{j6Roz!g~Gn zk6r=$*3Ry;g^Y6B1wz+k|JeVSV_#!QftlfkUU3(@=1V!lQj0Xbv6MHliU%x>M5bkE zo7c*T{cLUeo4`Ar6UPwOiv0eReQx8r0~Qy^t3`SP`g3JTsLc0r(^6@-SWZdIHE(Gl za0+PFhdPv$&T=pLG*63bY8vXkBU9&eawV=OQNpMp;A?8WziTf&vh2s7IunUFdad4M z1QFqx_}x`jz}H&{|HOVfp8`0iOdxPN(0(liUaQg+XYf(%(x(&+=D1 z1DBtxPgFL`L77(#agB)4Pw5vgs=KrOTk-kpUwofi3XNeD{#8FZH1aVw*-Br04NEfP z+T4D2vCrE5Kkr8Bgn4cBrBL3NLJ;y67IbKntmAZ0-DEFRQO~k*J)SG%FBx#_wfK0B ztpdpH=Wr9xr_01XJ$J>io}pji^YziWx#eSwY}nI@tbU;+QnzA%~yZ2 zU$kK|G{Y~Gc*_2vf8i|G!0tnmn%Q4*kAs4+JB#I#wwFWn{AY?`k6($IS9JCa+0og+ zQJn32;&d+}T=?Gh2oa5mNH`O@0-p8lrc(mQ1e+XZduJ_d_ApHNHRfdY1WifDkb>|H zJb17cL_%@Hm5vI;SisdcmDx1;l-tRs3HQ3G zmeLEF#ii$Io*;WUKL9Jv#fasT@ncSonLdo6HQK8#J-)X4iRO*_R3)S?F-9Xyzdh1s z61Ot1dPq;@8D5CKdkACEV-G>ex9 z`lma-AzSMV{2S#qvzb!Bp>{J}g1#~s@kj7bCRRYL;4ANhW7=ys#u67*TZ$Y#2O1Oi zw|~6`LJq_%{>#UMUxU$UT2~&1Zgh5Y>LNCRXjm}<9u4ob>1Sg44#P~~7p$TGydD+e z`R_8SV;|Ua9~!s7wZS8i>1W}b*~#XEul2{?JoM_1@$Z6fF106%0j5UR>}?6Fc)&%y zr_8X`rK(D@@}iJRvH*Yq`>yf5J(jiZph1F|p5vXWy1GBG8EF6c>~3g4{DF`mQcF{M zE)`sCnF}7ufd}oyRo+o)U|@naLQ;5x*iI}6AQ=H?jsa_3U@`h9khKfJ_}rlwW)0q& zYA0Hnw)?$jXMbgA7mZ2}`BXl!qXip|VDTYiNy`ie$w*wK;Zt_c=GzE=X1N`a*`_fNn{6S6Q-B>eGLy;0L*O#>4vffdT z?Q9R;ewuoM1uezYj{H;Gs8#B4mfJs|NLu0k!veOX@7p^a$gYtMfWuv_6gut+dgYD(S}?Bd>Psh{fA1Eky7}j;7&%kzkXUq* zOtn6{)#zao-hBp0rBnBubTW1wPln04e7kFq!O)2m{+Y_5#cjxCFJjXzq__;w8uXm* zQe{-hM<99#``OHmND0_iOh@Q%V^Ybc*5U{q#|TQgF)Aztg=kikq+}DL=$TFR(>r)# z(-8zOKYYu=AodJ-Ho9>9OV zwDz7=Mb+MW)u#3gO6(v=B=}wK=eh6a{vN->ABle)k?Xq7&$-_3*NN4nb!j@7%fl?2 zN4Rzvz1e2PVy)onqPH+9VKMh^WY@ii|{ge4}NN z)KNaAQR$I)3~L1onTF!v{TmB2-hD;=9zY)()_$+rdKGHLWkscg_{}O%f$S@=R1B{I zKJ!8RhJCEsu)KHDR^Sf--%)j%ZwMON`0Uyub#sha>Nn;`r{Y2CMh|(y*~S11JlQbj zdPu7E=3^6~6Us3{P*?In06Bo)N;Rw`N=#9*h+wE2yV<8_Dg53 zm-Er~8{Q~H$kT4z}!`djc>ju zGxB$BGS;&GlYw&4eJebrJQ}j~R_I^u!-`?eH~@K4EViw`-xa~M{jm#oA^krctCIt9d)CtgPe-I!>{=?joJF$5O+ooZOClb{I;NXHWq&;N%tzUtRz%ai+gWC z{g>C-D07;Qxu0}lTQK6g^?fHmgG#HHBn&c{mnhr2*Fk+suVSK=oTXA9Z?}C_D3%rK5nEEB#PS~?I?;=u0>2VxY2c;1@yzQV_^Vf6yuKNuI$7N$H$ zGtdIYbO=jBxIk)y7ls*yoXF=P?2#I1> z9gG*ON5k6db14NhTMgz6m1B2olMf*nd();LRkjkJ?5ATmCSgghGdort0iTS(OVSZy z-}>x@a`{w~L^o|xA_OT^=lDV*-!Z*M=$<5Vm@4+&NE&ARyN%q!VXQF`{N6~+Zu-Al z8$Xh-Pxi?6tsE2Rn{bby>A$&#B0MaolLP?=79o>qa1U1{cg5xBfZOk*9v|z^4OI<> zo=Y}c-^6j5HP>F=K}y0`?4#4n6;(XFT(m=;(L4YtLlPRVDmU3_L^aQhisA(j>2SbN z>I1!0cc5z-0z{Lu?e%f@@W@-zCh9mEZ%-kPHf>gE#w7%>?tOlKpW}%d3H)c2ZL9f$U z1=)`(O#E-*PlzHK>$k5&?o5i2b(*!AUp)aou~zxKE(CY}nRwvBN(^yBdJR0RN7ck( zJA%Ckdr*fVNwfm@nFXJ!q}Fy3;G$5w7X1F35Xn0_)Xh9wh>fb*(3oKYm4L=uGQSlJ zqdNmnMt^w1hV3PBZas}{cNm37bi#GL&5(WX?}wp^ zgy|nVuub6Z4dOe4*PJ&(Hg5RowjroiQlk!tMCA3a_7d0Q}zi>jQ3ZX+-c4 z=3zCG=217*19)tQCjj-K-$~0fkg>|wBn7-*mZ*6z!CSAdTKjP|YA`qC)f^ehEmo^$ zorkuANFB^t1jsPt2N*Is$fSo60}v<)1rIB^_t73wDVUtGjICiWe=Tg+0wXVmTd4vn z5nL#(I%Xe1L&(&w8m=IcuB^bKx^b^vk9~pYqjviW5$e)g<|bLO9S1M_HiTGcFeVLk=QTZ z7)A5WJtbxCKt>^xNFYP11O)1{0Vb9=;-=?vA%%RiuYK>VCd_@?!9AE&5i;3W)eP4n z$yC!Xi`0@%i<2~GS3-`gtbO<rL{2-Jb&d zqfrI8_`0DkL9+@NL_2ynUUmE|IOM> z@Ggd4_+kg70;7)xc`Q;UY9tKy2}lC102Wtsj2T1$iydOanbvf4(*-h_v}e z4wNmoZmSIK$4(KG5M|kA}tM6JG`#{vk#s=R*y?*6rWiwuvKVY7A{lIt*@i0^O z(6hb!_?3*Y(g0(2;z$V1$t^@{?`IQLcT<#w1nNQ?566bfO{{rQiepA-(HYUbj3Wp! z32xSE)hlV%yAP#@Z=N<#dn{8mSYWfVYx~nD2XRYO^5@)qCmywsIWOz1wWP|1wu#?E zqQCI5ANJWna#6lSYpr7PUKxP^UKEX_l?Gj%w?>>%(am2@Wi}`^0^A_H;Gb@uqKW2- zm9*|~V&)Ml`LYNW$t*T$8MdLNL3f5Oa>vd{Y^x#0?&JwnT32vS{+Twn}LfZYBE0dAkIxG1aoO!{ClaV#HlSnx8u<|TMvyyUvy+KbB{ za<0XGG)+G6xF{10{&FJvGg&6-tPB`dq^B~+NLy5q3yQH#8l`;Uw~0M(zTc=)_HOcr z@VdQ&r`O?q&vrMfk<;8&X_pr~Z=S1hD+R{hmJlBS$u6!G%dmZfk}^6w-ZyUUy)MTS z92cvSo@Q7j(3I6BE)~u9`jQ&}Rv-3+PAW|t)nk|0;67?1`unC{*H|5WS(Ka=p&nbK z6n4xK;*Q9f_R*@s=0qSAN%0hls5Og%72Vp}7BD@Og7*CWMPH-9w2-Kb<%OLT(|}_A zyN>LVvH>$&K!#vKjxQ~6X;(j4lAETN@9ykUm5|C*(9VooqGI+4@+SYZa>YC@6P}%JnZHkb)gR92**S~S z_Dop-dV&!bPeWfw4cXxNX7^ns6oCAW_PJ-Gjm-HO^2_RW1SU<4`7fbD*; z{@p-^8%n@gA#I|TDus*26+MgN20q<=hP&p!C7rM2pGp?kf_wZNX2BjGi{86$NPPW6Tx7b)p zzm$eC%sF3{o#_Urx>jj~C-m5GhHe>(xeGv#FjR;_Io@wDkD z!DCZp<`3U6_>ecfC_WVjs_D7ELK2}?vP}(g4yKS_Puu^foAg@9gl6mDWTSPf;PtaR z?`#@RiM%rRlO;L|a-RWagTEbPOuo&0eQdeU#vfbc%3D$2G07^R`3bPL3QSNR4`(RJ z>}M6YpO(Pb_P*2cv3s&87WRJCFYKxmB)DFUN{MyoQ`6d~tE}aH37=@Ns1R#F_>p&2 zig{`!*M6mEH3cn>y|NTQ5EU{-p`8Ap*vY#U$s2kivLDy*8vgvGw6f@lN8TCKUN#^P zDgpgSUCfC9ch<0zd=9(rgVguE3?}~Q^Ix{@NdVpm!y43h@UbYUR=< z^&;y?1&dn73cVM|4VST8%NolstNDO4sgrsNpNRg0dwvD7)>R3$?(RgG4KcAwj`RxQ z9$15jMeV2GEiEoxizYf)*`@A%-%q-aB=l2z=5c}ZUzbh)fmtq6age~;Qwo}@5Mzps z>e7E+zWuL!8#uAM0Fk8hqTS&h$G#@eJg?{IA#(||NIulHeeH!&u-m*WH2S`}1i&6v z_WJ002mu|x+~w}Kp6%b?E2`)v4xuRZtoTU%Y$O4%>GWUSg+7{WIk!7)A6LdA$Lj5<=JgX-M6M$Z?rJ4S%9vE(4b&^ zhtAC!lxjKT{ZG}0(^4L*0--Q3Am8rq{#dZ%T$b|q2WDW%flQ>PI=p&@!Wlcv^53!n zu+JOjG}c0yL-2`Xi9+UDhECFF-18`H_(inZ`Rg+D|Jf<nv7ddmW_*k*CPoTQe1y*TMBgw5N##CqiTn*~@TBtDXv9Na9Z!>6H5FTr zhbbd{xfOk7DWifVN!%hdem`yTV@?v4M3a{6_5mIrioN23&gkr}>=x4S;XhH+w}8E3 zcy|pzdEGj=#D~Ftx#a^aykSVqRcOFKoGPxRMoR*Ej|^~A2O_R8#VJfBx^hx%USx6d zkc60+!k*+xdKgIGKM0DU=e~hKzc=`*;v)EAMfM{?DgzAqQjE&ytlv5&)Kmu=@=Ho& zY&YHO#H^%*_bB;vx-mA0a4JG8E^)-xT(xd$3; zWEf-%f~O-rTn_wd`4hCWln$37=e8cJ0i!c<6T}YrLehK%nw7wws6FUeM%x~pbfd)+ zl%wg|M=?>lWRw{>LzmdT1i0KFK)l9^?6KS_#U_<3;K1)sCj~v=deR$6iD-_c27J@K zw=f`4U?fOR-45|`ZfiUAZVpsrA_amn~o6Rjna>(viHkmYt!^~I-%#W)RkmH-vyT63JLsg z4;SQk8Q@UhXJZQoy^qR@?k(@`>HNe55+WGZAROo|jvt(X3_@ztCG|`-GBromY3H%p zDvW+?aoU9e-4-#4`*tTypN8JPlbM;F$Os7r znz2F_akQdl_D&))d>>BV!x^zb(Vg*H8W<@7!=HZ4T`;`yVU_n!!qEp$O;S0 zyV=c;h@y=h{`U2`3Q2>{m-(I`17zCsFW+z3yc8O@nEg1bq_${xDP}z&*&D3PZxl0$ zP+dlas6gjel)}JX4mm@;?wO-E=eCaPo!b>%lI^Cu(Kj)H^aS>`Oa!aXANH&>+(+l3SGTDZ`UMjk(qDtD>c&46G2;i}{;y;yLU!~P= z*?2e&96qDmgs(P#bRx`^|sbF%Pk-&)YNR5_JNSyGSxnm{osx?fQt`_78| zJ%Yg{t6=|C_=V~|+*!lQdfbYvzW}f1m2>X`-{Ymt^8KFM5I3#Lo{UlH6Tp)zBlg*y ztThTgX9Bwi@~{G6sVlgm>`yRA+LC&Kecc*U>!AD>)Qhp6OD;qnEW--?Yd3DEcv~ZL z!AC>GF$itfz3bE&UEY}1%}cdtd61zmD**8sS7KfOa;`R8HA%owLfxNV1{p9h7`T3^ zD7{&+vm<+kp*^y4H@Hb)jGT#*t|p!K4vCcuJKQto^4@pi77C-17LS858Ai*Tq!S5HS7U6KBX%YW}pF9Ss}Se5M|lVIX`dW!VQ^V8z!zd*G3~Gl_K~8a`65 zkMShUd?N;N(lkh+!|{zdl~pAmYG;8M`Jgmr>P` zujXW<;dDPoo$!RaP{cU4*D5_%cl4O2044PrxL_r+DxJ?#T1EQT)9G5D>uy~KY@825 zn+Bnk&npOPvVWj}guD$%I%Mh16J<6_Jj`st zUS^m#EIdxDZe2N{4LYlQtUl}Ry4_gkfkKYPs<_Wlc8-3fko>Y5d5 z+X^`LtuMbyd6_Xh=uI=wvG_rhUmQ%7w%HgF;^j2X^)?6ICnN6iNB z)FqeM?V_&HV`HvkZMO9Xv0d)&n7(>fQkSj!c4Y>VJTp3iwP=Y%A4TC|$3~se0jWt7 zE62u&axK7nCa>DLBsXGNAD5<B-cyb>${yY*KppdWpB;5W~n{-zqHSid^D<6cl8YX zo*DA3?(tZa8J3i!4e2$~uT&BtWe56i-B>x9za3d_r9~U{FRS{$O>KcGtKM~VTxM9> z@3k&mSo%iqv8-YLfsap|s=602L_Hd;QGlIIL|sZq;`!41=*858-z5=GSV_6%(T`pq zbZ^8Fpxe!iuG*#q{dC z3Jb>kh%^q7n#o>q8%zhSX+3-GJr?x=rDO*erjJGs9dgJQsRne| zGI*L)&qB!t!KdZU*3-Q@R30@HWnt+KCLmQ&4UHg(K|*8tBIc}F;xeV(t`omSCa89C z*=4a`;hSxp`2AN=9kF4bBH38m!9|zAb8a4Nf;7?{pG~Y}$QIk$yjP}L(;eU;<~HMM#TJ{JL41YGU<^74xDYk18I zvAhD>Hyt;{2J=_@J;Hol!Hp`2`?JnvywCSG06=c`;rLw-^zk+Q>9p~Bi(lOD@hOb) z7xLYQc;S1kK(}N3UgYrK4&(oOb-F_Wi|s=kDA$dZWI+Pf{=)=6&y*wACVyWO%}m}L zaNj9`@b$lv)Ax#f!1TA=;EZ$3BK9=QXgrhNX$7!jOj~=CG_MvUS{X zQ_vcj%|p(5ZLZX^%n%6Wz<)Rgi%NkQkj}a{EVn~^%tc%#1Vj)w%pv{2l8^AR>~HD{ zmrYJ7jr`1++ZfBT`B^vfIll0_;ZGLQA5rU}v}zhJd-Y2k0^4x?A>n)f`>OwbcZg^% z7cTHw-Njp-=YZ7La6WuBI~9xq_$@rgOu_+z%Qf);@ylWBn1i-7z0Xj{BuaGuFY5Ow zto`#opz;I*bz{y}&pT@2&dBYFj@Y<8M(7qiu39t7Pi(o;ES!r`O+VW?J(f?QH~y zj*AN)0cbZINwZ)70fpV}r4)e-BQ`Z?sn96b(Xt-FxQ&>9OmX32Q%S%zy+qnbEgd3F znpqC)uisDH(E#0f-`Rs3o0?t(H~W1EcnG%2o7dM|HB4^Su0IM%jOSMPs#cQRC78;m zpiLaRO5Mp`$RcDSY9bW$x0A^KUI+cISied7ymnlOw$dACFHD8CtAv;_lCLoeIL5@i z8AA*tzTfn3 zQb`C6tZ?gQ315K_o6|`OyMZrKb)V$Skl8{<q}n;1>XB*XC>rrv8MACEL$*cIP;s+z;QNEM{~oy;NCnKAU!;td*T0&x zl~TwpMaKzB@3}QJCTZS&Qc=VpbfEGyU|KzIzT18^TBt+tCM@oqd_pB!{Z+5|uxLDs z>JcoHdP?*=0`M&75SRSl7ykFV!Ne=TdBQY^)=POfE#!Wd?sz!~_GfI?5gqrJplrJ! z+-%-9({#$Difk0tPz@dcDBcn;rzPPcN*AHPy{S|$rKr`J`;CG{<)1E@FVv^#I^w_m&z zex+hB@stSXtwP3jW|#(18i>$m_rhRfoxQ0mP}B=la(x^ zFwl_c_Xs#j+9x`m>-mZE&)5VD0C1@&=IwM77#DeN4FLF|;C49xkg)OjsPgKkTc_h# z;qM^532IcqH0|oF6emnQ!bV5LLA0{x;F)G2Qfe4TK}IxY2TWegs5$TO|5P@oT8O*- zK}a^L{d->V*L?aBpQ>&ehG!v830G4VzGpCZ;BzBXtz2|d^$zSzKwRGa z`Jvt~<0IwG38u{VjHB~w@5XyStKU8E3H4ij)bh(GlC?nEMk=c}b z@eIB-pWYzrlC+oPlIvD`K8ixURP@n@%YthAmdvNaMbwdJX<{B9ivDyT`d(%~qHDh# ztWF`Brhm_EnYktdP4Ocm4@Cr&#Y{CBElTSAueaLPmM~t9oml2lHfHHk@_bsL8@Nd{ z9H0*ExOtkQw9-0L<;D@~l-JuAH#wAty7Ab#SxMU^nH3(#%~IgsQA+1J=Q8*$?zXg! zkjW#p63gWGo!g?H>(nYhM`?DWXT0w`vM2?vcKC^UlBwTWeHTYpnt+B`-tcy`(lO1O zUlmUY+;v4#O4_u41H#UY&%=?xO2Gg9k#gmDbkk&iG0pj{6SEJM&zHe-W&cvGih7X( zx&F|JKdGNYy^^k%RW*#%c2ZXi_;9y>?qc`jRHCiw_X1je(k4J>CB6{sc{8a8ewfK~kEed3UNxhnCX@OS$(P0bJAO|e zvQs2M;64s?n9B?-J|t6{;c)xT6_W*V83ZRNMg`T9sbszd`Zgt|$hk&@j9}$6;IH&{ z{@C=Q$bJd#;blfLTwqVa!Q^dYA05WRSJVEm-dPeD3ILSVsv#GWO0~6M>hKPo}nMJ$i`hE$pWaLCi*r>!LKxT*4L@h5%G> zn0Gm|i)z8Oo|1qg$1Yz2Xm0U@d!}Y`pnle&YVAV;j+_IVK zl~3|kwJj-`H&p~KXXPpsM7dCDX0dZ=ugqs zXkc8N%E>TjK~`?pCNG@{+His%&|qScXlj2@S7>Wk@CNhR==g zMZr#{H&5~|WhjY)NR7}G9~xh~iEYP*(Ueb)kI0cf-BiAYm-cC7w(N;@kVin^E8jd{ zJ|UBaAj4?HWuR#?ZeBP)0ncgXQ}Lg=C6kSIjs(JMa5owNIL1Fm)vrDP6vkJ zC9WVVXUvlXn4zkB|FTGV#PMVE`lFdY8VISMCo=#pUI)!2L`TP?=$RYr}UWh|)dxd&auXSM9XS zHTy<3$J_-*k>23p2tjjScG|Dy`Hfgrtx2-v^CdcrlCiZg7F!dp5!v zQDJEXR&qO%3e8RDR(k|+rp39+CTCO6yd&yGf5;Rx{r*Po0qy7Hu!%Gia_Wj_8zQUpD<{lyw<5n(XG7yqiDfSqXxd+*2)VUFVim9%@cV!VbwBj3FLHtsQX8ix4!4(< zx8t|hm0Izb>hAD{eII(_GsuRwUQ+6czwzCHcKD{D@F{9`B{#kp)d4y~slom8IR)Ju zqfY$*deV$~inv2naAxqv6ZX(6YMTN)Dr(ipO}WfA$85ej#309-W>{+N`6fRMkcI%u zSLV}lDz@RO@l1QSQZgnMUE-v!s=i3wV@v)q*#E z!+r2T$7#D|EA_&W=CBmuWqnwn?)43K-T@fY7s(74!5=%V_HB^+pvh;BbA%*n z)5)(-W-q!?r=9B+4~+!IT`zyi{Jr194q~s-`|LK!e^e~RWq^vs510l0OU2^!fP*Jc z0+`5A1AYh6Cf4KlLI8!1mvfXgFw*feFiV`PN`7F}bV zA6)Y3zfDidn8q_3bV~bBGcD4l$59K`L?1#>N+5ivU$`k}{V>4k*dtq{OJ4+x44}ad zZilYafEJ7~h5C@EH+Y`btL0L%7cu8Q>XiU7rOp+M0T%V$c9}|j%c_k|8rV)|i4Ftb z_myX5RX01_V^9Jc495bSt!|6JG<@{B9+qQbGf3{z39(dWW|Pg-_hb2n##$+0?2`$L z(q0QQy%4Y5irsirrF#cZQ*CgMdyoB^yHazhN=z)jjJ2HAt2{eLIrEXq&~NQ3K)fP1 zKRu9DH1#cyP#1&N()`CE?7{;9m?faG%ZRE(${07F;F~wM($QIvE zMACJQG9vYf<5h!q>d_=mpRYLRlGh77g-Y2=+rercMS$3RgWEQTlwv{o)QvB~8iulX z2*Wp}646;UBe)nTdLuW~C5hl!Om@{onE@M3PA(!69drv`A|_?o#$>?QrL=6|H}7X4 zAkOqwtn%R9qg%qk&8>E_Bx&r~b(3bIIOHq)kR5#t2j7t?SjyR4ixP6p=q4YL-f(Iq z6oC$|oD&y5!QNcJ(%~f*y4%`18X?k|R!HUyaC=5zReadh+XN=yXz`RrF&Ey?KgPUVo{wsh}34mC1J>g+wx-CXHVPbP}>H|Fh~z;WIq47TuvUFg^Ab`LsdU}AQ z^>I6FzC`=owr;y}?AYi0cvoltT%WH!WVDSIT7NPdo9;P1VK3AfDiLZ0lTUn0kA>fB zPpCM&!0+*~2|51U{M!ovpMQ=?FK#6CHc9Ouj^4;OBdP&8mQj#EK53YDAcJ!3U8wTe zOU5|eAxvG-_dxw#T|ny7kQc%I+c{6jBx&2|s~~`vuK*Uy@2)m#lQ-*z15=UR5zLqV0Nuwjwh&B`&~>WwNCYRYd7U@n(%+WXliEZ zy{zLJo9l^i^k2Uv67lVVGM^@{T^}p|*r5G`66XS0#6~s{m-1Kx-gs^vvuy`m6#zL} zq>Z6e9~Ib~hGCq`1vt5hnf~z>3Tdv0@NEr~mQhQSdV?SaX@T8ZsHPh^z;GlLs0|&X zTV~1v%Y+a28H`$j3`)%wKiHToenzugbr zN*(lfm_5%Ln+)#cz1DQZzvk)@Sul1d+s@T0%opNZeP7(7wXLVrSayB}*j84i_an~A zfZKDw1RGj(l_`6*Y5fN2;9YK}jkLW-Yc{JrT&>t}m)(!XJ~poVI3MLRfyYUV0vhv+ z(>W_ky;_sM68?W}5_gB#sjT$y^hnM~P5;PUrg;)~V@h^Pe!)0;y>BypZI|jtK^7Zr z>T2rCE3W8S#om{5LD`p#tTW4aKp&}!YL)ZGWM7#6Jb{OU8Ldpy)vnD*M==Sq z3n}xy5tM5UpU$T=KT=K;>ek`TLBDkm>$kpXTFEcaeAw4g=I{0Vtm_=hR%qH4Su}rC zSyl1jaRlo_wN%NeP*HDvkKW{kdXElmud&t*!q4?$rz)k$HkCVp1=)v%HKsxm3&qz= z15AIyZZ-gRJM%YU8QM*QBHfm!eltNzZz+|%+Nv@wNmE4^o-M#lrW202D!_xFcmn}# zeT|R|A166|x8C?Z=f7Yk@Gt234_lQUeD2%Li)Shx^jCufK~fo0M=W$dQVRHsO++YP`&C z?%Aw4$Io6c$D?%b{&Z$dQ8Y#~1H20J;bdd?A60uGMm4t9Gb z@ZJY?2h4h>^zM&};?$Q-b11Z*YyMQSEdpR{O|kiEt3}Uf$C@51 z$KF$Gk+I^Tk&(?~Mo`}xaZ-|^CJ{hh3r97;?Tpyi459}a_%Yq1=~PVT zix^Jzffnr}A|-gpTNiq_6~IKJAFcz*VYr@NpVw&Ff*7sT5a{L`)odR6H?9G}Xw zQ$NG@mi8KP=k4Fru#3N zVSfpJ^?9?Dg*EMs82~}+Rd~UAGCGa**;k*QzyGZBrqh;yX-RyW5>1tt5lZ`tgZB+! zeI(EBeQ#Rc+P9ug*d4{IXG`ESlf#RuqUg$A8x$i~2(qSEM=f{lp3d)9EHzhoZ&vY_ z&SGZu5Y}<{VT5;i{}Ha!j?k$Zr!|DDNypcSD);O&kqUkyfPueNN%E1}p^TPl=7v)x z-+C6?fct~1lS+S>-XF|)Q;gW1j@wmGe`@-p*O-e--b|D26Z@=geKxR5P&){OEIbSh zFZpH?Up>rnZ9;kbdK)b%Jrnzk^Hg7;?Jo;j zkL~)ZNFSyF{IW}9(`gHB<$>!wJP@ShNeD4D3eiZx)1D)t5)33BaC7g3wj4bO$T zlhN9N1$+bWW?B-F>VIU%K8Udd{s~lKHJUe7#5HrDKr7)OY&Pe6^c`6D$~)B6jTiVzvRwV5}+h{F+JA3u+DoIrYc*`heoaZ!_@c-nZScMCY^JPHN7hmqhVy z>xk1KA?_}%tz%515ihkrCmsG1ZO{6~u+>!X5rk^vDP zm@E}1`{4Bo)%8;|zwvl~X+cy2bgN4Yi~RzAg~`oDs0{W(jQnZsGH(xk0l#(=q=~qy zJ5U2|{1F&L9@Qh!y!^)%ZQ)iylx7IGEBy6=@4f!qKN7z8mapcbbt7uulpt_HPK~yZ zF&k5{oC6S0=K;Ique4PIOcEMCWKZkCgPPgyH(j%)!UUC`6paU5H`FMPN~YTm()ma_ zHCYMAE{pw7LuM?$IISr2il5@k_me+HtrLUBzS@`3%Fw93q^X#@mB(uPbrKJQ&Jgew z&TWa-aH>!4B~9@@wO&G!XUjeW1RpX+X93{!YQNsQK))6O@NFZT9xSvl_rWu3E?}D! zc8{n?ZfQ$ZB~%(lpWtFDs$g7pZJ8hqK3zK}q|fw)u;HTF3f>tSL8JrvWl@woG2Vf6 zOt+0_tUu!~cq2toKnl}f*}HWMp$t^UO$(Hdi&Lv^&%qD+i>@6U04CwUi=5`wlb>Mx zPgtu8U2UJQhh=K;rh`-eFM-G8YMa~Yp>m0SdkbeXK2}1AK985-Q@rid!29A*$puTUD5p*X~Y;vT23;HPPQQ z!_J2uk{4DZBMd38Q@)24!WH;l-r#|nd86gntOLbr8#itmRd@`C8U3bYhrfJ%sl?}W zQ`-nHVpQf~sBK~WF@pAp-nXwEhh#fLRz7ha^arrX_%bT#iFwz)*)=$CmL5k&H~$yo@b4gJe5 zn>KB}`@Iu?Y3f01-NTFNZHgPoZu_z_+hyl#%&g)qqw3cV-ceHfm}P5+4U%r*qbbs< zZl$3@Aj@b4H6}?3fU$f;z{2k@q8D8;1UD0CeZ$%0Qzj@{vAA}dFx;B|j@-zM-q3$S z64)oR9I-hH5y0=X0E3N??Y!4JyNO1rIXSPg^QvzuK5vFKsFn6VDPT%pQkt3mb{O{C z5BlTm>s@x10vEBp3)y6xuEzM$&5%W}$+in?k^lWvg!wk%)3NP5y8aK{MNK>Uf>?RW zUVPo_m5Qo=K96UlLB`ALMvj7XyH5{|&Rvon+Je0F>ERzpu(pc6Bt3|8EW&84umHLz zmacO;J9w`Dkeg8Gb69fA=v4@^&I{I8|Fos$0P%4g)(P;^7rghrM;+UFS;bU)j7Dh# z4blDW<8h|I+IL(xtc2X@o##)+t11%=_*r39_qOgCyY)s*_>-S73K?ponkDyr2xRC8 z1mL9U?}bF@DNiU^MrmbZX7w zLU-@G?a#&*(w+{OX6rg?Y z+6F%C5T1yZj`D#naoCC`0mZN39L(F`SXKpB<(+1?Gq9|>C4W$pXMIjEGRiG_K@v>E zs>|}#Cq6>)33QQbpJ|oUkT3ko!aNV2A<+e54HdGiY z=GJNd&pIb?6s&P9zG>d4Z>rS`<-l#n=U@VpbTm`krg{y>^@uuZ0ZpuJJrjPUp=*oaimeA{i1IL?$k&sHq?IpZ>Tl0*qYnvcywcJ#{+f>e_ z8A^9SuWjlhbAasLdn`7l!WBqT_Np0>@y4CrYoC49@i34`NS>s)s+x$@AL|0##nMtg z?>?w4rzCAM?EQ9@12e0y95^@>Np9nf13c*I74(@F1W5Dy)I!xr1xi*#A9wb#Pb@vR zuaON`S^^$2+VWgkSaTdTgO$cTwNHI6yIm$y?90KWIi)V1{C`7m$w#?nuEhrzV9#OtE~93hawQ@Xw^ za-}(EBtBlke-xJDe|dh<;|$|O7Ru)v$?P_CQ-85lna4F75XPt9Vq@h?Lly3f(9$3{ zB}An{tkq5FQU@fy+UU299|`UWoS-iQGzO0)J2zBoa3tBH4Eh8k3ITPq}_OnoB(Ns@F9+z~kY;gOygzswY0BoS>vII(+F99_is!2kg>A77JhA1wP5G zZDdCqf5^Ok05&_qOBl|6a;y#Yz=g9JVOHv=c;rCc@j?WwhXlOSuE@wY4{+**H5eXm z9gbabFy6SCw<8ItfQLyy8+paomh+(Q@{SvdtlY$X;R&72!%E)=#dE zApHZYXr|PEk5yA6eY2X+anSR8uFjZ_lCzBxL)fJsT*RMWC%KORTr6e%e(&h-P|Y$T z>($WX$j3Vt3Q1`TKcmj^fcg9k9IzaS=}9FLvY~&>8~ZBcrBwBer%OFLiidXR+f4>> zI=R=fyE7-F-&`LycMpCe;5v?LD7t~1tOfAsdxH=|3Ie!1QHGK=k(%Syu3OjR=~sz9 zhMpnTx0p=q$|`uf9JvE|h2GHnOFcXtW;=bU_v=8U!g>||o-N9n#T|z+M}CyJkujk$C66 z@m7hdwx{^Ph^+$oplY3tc75tAHG*<|p(y^bk z9@U;*=&E;T51O1J$5YNdr7PF@0<#38$WAx8snzFJbRxBxbFT4>rEpNVYH-t4ek1DgS zs34u1gAu2b89Y*iw@`1bD%@?4-8#aJ^r*~$ax7|0aLnr%@iAyQM-4w@uz6hgCrsL|%Y+#wTEsYz+Zlhex%&UR``wu*5Kw8({7lVM^0fmvydI-I<}whZK*nV z?2L!qC#+Y;P#-tq+p0B&Vnd$9nVi|0L z{n2U7O*lGTI6eh6RD{|JrSrCNX8W3Rj~;^Cc`aMvCxd4=AA?tKl$`Ikw?FS?;(eTS zqu6Y14|d*|)URn3!A+x2c<|Ns|FLz}VNL&S-ye*SmXL;lNGUK%x{;Dn5RgVny1@}6 zC6opwq+6xCOIm7xw9+wpG>m$_-uLsnuj_j5=hz?H4tDUz_W7RQGv4RxJ+Eg?t5c~c zRb#;5=!`jUD1g?0*C?NlswOTN!?D5J4+uHKn<$8Yiuc%Q(Bfl^f&LPxu9G3X?f0O@fa9Q0eS-L8N>kg z-8Y`g$YC6A%+xvSxC(a}*Q~&L!Ml7FnQC?l*sWkAPZ4hj#}f#kemoo|h!$@U9sef|?M)Ji0y$jt=;*uU=412(P?=l?FF zGbT7y>pM^CY@be&B(d5J-qh7`inWeZ;?lGDCD=d#!cEOx!Ei^^3#RJp{>& z!?eTwYTPm`eBPwUlOF636_!Rhiew)asC>)vkKH`P0=A3%zh7E6Dq1rdJ?|Aj{B4}n zMxgr+>E>uKA*YO z6sgo1*IiSBx|gHS^@Q}z#NON!GW7mcjJHKH(PzgN0;KS~A~Q}5WWL8$m3ag3T6Eui zOu(ljb2Rm6aH~$deWP38;`M??UDxQHq0zShx`jfI3SjR(lc8tr=%8@cIYq(a?iLVW z>CJh#-cB4nxZC&=QSx?ldupy^`f^CXe3w)kZ}R&hiz0C{rktlCAB*C!69zKd`7nPb z7@!T>^vdkKc=X}j7L{H6;Gc(W%75G}Vx(;@kRbvZ{&VwD84sZ?mci5PRK_`->-5;E zSxjdvz(N{ZcqjpcHlkJC;KHZL-?^Q0CYl7;dOcy!AV`g5R+b5LUj}r}h#wFJmqsS^ zS}uV}&JXNRcCeCCa3-db<;PzaK9Xznuw3>tf=LfW5})g+oBmUw)8&D&2aCn?z1NC; z)AK+-D~F_CF;=LlW-M>daV7F*24)Xv52uK(>zX$TR7j*=sV2UL{?~54fjWTBRPQB( z{xVD)k&0grf3z`Shn-GqXOHXFLYVc>a{eo0R#cDtC}uU@YFTsKRgARSf9U$l9I;v^ zd!eUELn)!zFA`R;2Ju1G)hUv$jqSq;8_@E4@Soe3rr%EEHrwn5p1m~&l@#sZ-P@hhOetbX%#}*#+ zHwvf@U0M8$&-d^pvBu+;Zh*8D>g>@5r)Uhl#kyRlP-B0u7fw+2(J4 z^2joO+l1hijoW9iA#^ayeg-I0gt%>3(F?;AJYFXu#rwe|l4n~n{*Dfc^_9PcHVyxT;!1bW-I_@ItaM}{KN$HD zF0x5&>gG0sHCB8A$wvq5qRDx=?@6{S%c?>&@kvJ%P!Fiw4&D6cOf-q8ZqOblXDn0( zyEgSZO47|A87|2x@DNuk$}YCEMN}jmSuC5$lu~fu%5W#1O=A<0LZ18e#vpQIchi8F zX@5iNsG4}q2X9DX%YZsfS|IFbdL3UlKG5YcO8GIL#Kv7--n(2=II7Y_wA0nkU+BJ5 zpyC;R`dkE|rv9h5kx}VwZK+{>lil!;t*(KMTi<_H{XQ->it&HKJV9A2{}wXr(a zEk--sQtcNbrbe^B&Kzs?1|T}iD;AKenL4@_6Cl@|8r*ELZYfLE2gImnmeoei?OGE3 zCQ^C;yS!xDE!CpW!MFh@H281pycF>UXOyn(ipX)J6R6TY!mZ;`gIoy|!MS6nx@y%% z{ZCuIT$}<$VQxBkMnRd7`t1utsSV%bsrnWx?;ULi12Su&C=o+^t2)Fa$h7rl;M9M3 zy1j5A;UuAMt9({|Q9n)ayu7mxH*wvrW2o?NW6ocS;uS^k!rkq8eSkcSLcq8$>)TC? zlu7lMLU-TGqYy6r9-V3Jc*i~hzNu3qU}q%#I9;M<`3Suoeci=2bzA!6d%CmWE`YMM z20#Q|N0p-i`SgyTWzwOPzG|Gi8Dic?ECmNa-nLpMrWr;rnPpdUJvUGTp%b}|(+93( zd$1xSp6ld&^Lv=EEbI$Bj4-9L^uo)DRF zL1ApllANYHz zS{=r|4PPpRwW3yDs$raTtpda?o(9D75BVuZJ`+=T31iCsq0-ig{KA)Ko}EV6UhZ0Z zp^BA|$ZeJRlKLdJ9F4+4%{=QyPr}0G*~fDd=-1rpb1`qspH%kDbqfu8eYYO@<~lCv zNaH@8JN$3BUI6kcftfHL>wl8>WH9a#>>Yi(#pwzZewg%(D%MIZ!2_Ezl&HnOwSKo- zis0tS7j7%9mP&~}fscAmRlVNX38@ADpsF_F3KIRxC0*>P`Yffg8L7`prr-0Z{J|OR zwm)8f?c;6^r|Z8e1_O-2DhOVyJ_%fwY16Gh*UfniU>sVDYin~t0;fl12z&lsee`Bi@!MjAm~3gigeO-{G<>fz@6X&<=g$iB3zW5g48QwH5A$oeR0_w* zskY^{37cDOy^wUTZ}k?6luvf=c*+~8@Nznv1jwep-kf@8Kc|J}(Y&YFyQ$S{_q4Cf}h_9a=5Et#kM88|!?iWUl%v z1Z#N;@MQX#*UhbaT{tZ+vs={ZTGm)^wt<3n%YLUy#gZ}}` zz%${ASXzp052iFg!IrQXkV?eT8LcgqfIU0r7z0Bkl|O&ulE;sb8Gt>GUW-1xa)nZv zE}JJ4`uV+Qcm7S~*Wb~{zB&U8Us@%az#ZT{!=tHu+gNI6bsrd?*q^95?yoMM)jXO> zX}aF1S9_%H`*`IX>6)cjh4_b;y$8Jqg==NK%hhi;hkfi39#q5&cxB=^R9!!)$dr)I zf&YI%1~3w#Rp?ms!=~&Z6u38J^7kmJPFcQ>-1$*{ zO{Uqz`Wc_(rnvIm_A3dVYd{O;IBy|e^}{8OCPj@+md zRPB`OL|&c(yWxxQcWDnce4&*1dgzaW<=(^gJRgtOcdyQm+7@A^Pd;&krGGsr5OzB4 zDd-n>##Wdf_B>=bV|Xbdm^gUZKCbrt@%UOxk7|5x)RWE2Bed{_!39r>@fvM_r9ClKiONPSvI=DAsI$sI*^a zIJ?*L{H?rjIJZf-lo@mtiDz=F9K!%_34|Z*o*cRK9_BR>Ee$+V+GXCLmkWtB5pfG7 zZppD_0MHZua}S@&0Q&Zs{~A<~OD|zd!rHbrUB<9fFqo;>t3vGr#n3Wm*DzzQhW?$d zpqc4lM85(QLLpn@a;P3DUDerjs7H|O56t}x{2~|Ro~FCnxN!dH2o(kx;B5$Va&<^I z^ah!89-T_6ExhXk_XH^2a|>F(hT{Z+?1bAsSnH2zd%~CoW_jH8=VqtAA{+h)>?lR+ z0#u$M3kr2Mlw**!<&RhIn)F8)W@6e`jRYN5W+4A^b^_GV;sB*>I*Lj_h>J5`Fm20x zqkDprHbo~7bx5K!b4bamQnaGvzud0-mSyYH3*RU4tTcdIhH^&oEKP#903H{SC=B(& zw`~jl_uL9ytnDdU3&NV?Xhh*b#pkQf;i3G4@Y`&m>)QMu8gZObDRi^XrPoy7YE^YSRCdqn)eoD9Kqj@#>w9a=WIp7zov&%mOmi{Cj z^Fp2Peg92h$!-&vfN2Hemil{mY1-U`*}DQ7^Ssu6^ahK= z5_I{E<^IB2Dd(>^_v~{=l)>9x4R^DHopPXBDV zZ#+Nphxq+}mhph3K@c-R3Y%yk4`MH&s_f5Nl4O~(#l)#WiqVdxHGXsP7GnD6uH$A; z{~2?5$-^kq^DK?=YX^aD^eS{+a+%vE?vWgM?Q zgwG0LjG9=JGkmjB3g7IOs;OCX`0`U48>Cv4NmS8ShMC&xT_?`ogI)d5h_0uGOaIOE zQ|*%6F7HN@vd=4#xf)3u-AdIjD7=I$+?&kLTzZDi<%haQ8^=p+WRuNJtbaVqS_ztE zYm>Haao{Qb7*Lj%%5?F7g@UT?80rI^RLrt>_`tBZFXG>qH2o}+{O&LUkA`o#-TfHu z&&Jd%^ozCJX2TaI3Rq(+zf>3-eoFanvz4X~>00_A?P6b?O}C9|MPd4_9cMD&WjV`< zTpg1hah^Yjr=Ow6w&Otd)52!cx^dnaR!qGKybuR zA~HyH<(LLlF6a+6w~{B7_n9^VuWkBUEG~*=vrnpZ3<4Qj20JA5l|$NVE!SEroe5Dv z$SA4R3(BiyN6h9!QucRUQ-#q+$}l(p*2Na3V(EN9f1BILD;e9d+LZ`mPpJ%%-b{na z!5%`cdSSur;fl#DH5Vzl%DdsFbXS}$9q|JDdy^Fy{$5+-m?HNL)(%C=k;Eiz7j(uj zGO$~3pfqe{$8Z~*sN_o4MNuhj>LT} ziQ zWNm;N_2#D`hJEb8&Rf`G9_L7>qoaVqJd(GxIDO@Dqek&#raxM)$Ma8RzU@pzsTmpW znc67yaay~7S^tvIUG|`eR;`ObN2ayb+-qz+|D3WIVB#p3b|r_b?9za7wYYMGYL*Oi zbzm(+e0&o0&AgEf@uE zaj*&L8uS{$jUMeCaP89msyLxoPwDJOAO!$*uz_9f${md0lx$mU?0e7rf^qRoFq4Hk zmEJuwMLRsJYa+@Gn2<7NIx#7$r)mPIk3xs=h}B{lZ}n+k)-AKHk}#vKyS!`H|E|fz$$5`L<)%Yv#PQwtZZ?E1@;s1r`sJDvlv#?rKn>b5f7Kp9+bJ`(` zsO4T){-DlZ0^!!UJ%zblbkRxD2MO zzt?>N=KHW5`r`>kJ3gPTPb%rBMuZl=Ld3UwaU5jshHT<{<$*R1;UXiJ;*~f4()L-S ztyd<}w(@q1o32fIZu=&_(N`tzS*z7ke|#YxAOknutC*L@$Qcib84L$eD$AuObTt>_ z$Z{h3i#d9Y7{dFd4YG3Gk^9(g={Eqihseo{o9Ch+2p8EWRhNAVU&}VM9HVrL38FMb zb;KX6cLYryaHuUf@Y#(U2R2_F_$21!^u27t8IkZV88Cu43lkv@KF_ms& zA{g>>JRjIFe&fXzno^P*-Fb~p6!KgXyD*X=U?+K{X6m5TFnY3bixZx#674&^#bwcS z&?dxEXu&-+^=--ZP;dNKfk!S?PNLdVB1mXTWU%19f8F)$qd|9+)t^FG>9|tOHV9PM z21P_TS}M50te{-sW_b5i$OWv?&l~~P@WYXrXQxEYF!}j%HtG5RwX@JS(9eD1Eas8v zz0|$r5g=zK3(BD`PBZ?*P47`(lfA?VD2Xqob^WKGD+kSz^_rST#}a1oKCQ)XGz}(C zU=w2-3%jIUQPfx5iDNwa#O~;#_m5Qwe7haylO%K1ET4Pm7awI2jQuoBIV-Xmk}Gtw zCG+h@q3zrRDQpMy90vwC5TK39qwE22eReLrpk;i{dW{W+6^fI%wVo&=o%F^;_)jB) z-w*If5Tl?cl8(X(39dQ%^ezf<7)$b_Oambr;|SXh-Z5oBgZR-^p*%e8=iq?S4kjT2 z!W0bT)e?xRs4wR<6ztL3kBTdZjq(biBp%K#|0#2D^Q4$wsG$N8zq1~{WqZQy&(wzz zy@D@d)_zLMFc18Pa2CBjOCM8 z5vftSA>3>19P-AdXsu?%OFvm#_79-|WdMH;`7foagB=!3mCU!S;yME2F2eDnf-v~U zAV(XQDdX9$nLfU1xBpD$~7wMwTM4GuKwo0TysC=HX)af>nnx0Jttjm&dH_bx)3Y% zCE@$s&5lkkb^N{ll~R>=N{61J971t=Uw#L=n*~XXnBVhD!pAtu<^+#mkT4vH>~@3` z6RWqJ=cvuNOI0DR%p-cJ{LFA=a123$CxAyB`qB9_DsN)+k!>x(LadJH-a}lDgT}8# zj=gOC@_omXxDsZyi(l>0C;3xrPWw3RIOBZxnS$GBl`l~DKEV)85&hsJ1ZxGt*Y{|7 zE7t22b1N(NeHHqBc*gAy7TgnwlV3$v^fNMw0kXxK^RcDVA>oXEjvwW5x%SNrulnV1 zYk>EAqM2>jN(Up?M;;b{s?AQ6Gdxg;>As=1Powj} zwu`%!cdQdHcxk>1b`kF|wKM+6L0I6puKOk><)?C)P#mIbX%UrdOmv zPo7{(^u4HgI6Ge{**-FD@Zrf0eXmUx3~NTFzU=1Ytc=~s*52+YiTNmuHHC2#>i<3V zRYdIOWqZAz*U?xKo3ZgFfyw;PormEH*@L(>ei3D%df#`}qifD(W410*o)fy6i2y_^ ziRDe~aN*_E&`$AUf_<-b>k#0zNPMhA{~B2lCzth`6oB-)A(jR1dCNvxcgp$J$Q_Uv z&S2C;N?uRo;xfp@S`x<*4>P%(fds88qhoHP?+c0U&fAqcv*^0bJ=67_IXE`7j1O@# zau}P?L||Uk$dpfW*k60xxA)L5GYDJYQI4fc_m@D3v{c-rv}X4Y)?dg%Py|>bp;gL3 zJpBFOxm@gDd6Vr`F)9q`rGE5!+-C9v#@m}&IR?it9aAO)kYpgG@h&sLNRSj_73J&O zKhsCN85w|_6J$Jzf1^m~+x^>P-Z4DOSL$7!uU3R?JpV`bp5aY7C$*;$4+|T93?Kil z(RZeft7q9fNkPmF^QZR*A5Z090W-2)ro|A!`Aof{7rnIgQtX|gjM$HVeS+iv0NOqV zF)RPWZTz?EE%e?C%P)+Zn|ntMKj4_7cieY2Nj2^74l-x=xrF)OyeF<1%86zj-T%0>`-8VTAC{2=X@=P4)eqSfC28V+@}>W0G$W;W;DqA# zBI)N|#sr>G4Mj2>(d$}OV3^6Hg6;Iu&c>Es^tqbNuY^}2AHOYDd+jdXkI~<8?w2$6 z?)S-Qd5lzCPD3yeyfYD63cdG6HPxG$O~Bk~GdY=@U8^HwN?a(hA%f=1Z_ku3dWsXy z0_KInMK85Rh#sT}isknMF6ltKg|0jS9?9jE%?;itcDtdzwz9Q^)>S?h0BrC6_hTm- z6q>UB_Ez=yx_wL^a7;7ay^;&#TW2`_ZS|AtYl&q$Rd2UH|9mPb*q-&y_5)(gs_LbH zx%o9fA9kxYNn!&Sz8_gB33+1_(1{a|(*wJ}=|pnHQDUh05GAQ_1~i;leK92PBQ7`n zm(`#`>9rcX*5ol_kG^282Dl{_Ji+-fn1o)`prg_Tl7_^rK}i0}2@Q9&s-@Eks=A@B z1Ox7DICBOa7$rUWoB1S64YG*~A9%1sLEaegrz_c-Il<5vY=XVVN|9}1@WqZNbd`}j zRYw=N72uI%GWbUbn#92GhIvqS@o)k+-vYu2?~^5%nT~}!$`)0a(=QF?o!&FJP~`TH z_qDa|rcbf%{_QX2+Xier=i)eF(5~(=lGl5Nd?DvwUGpnFB`ydInk3?38B}Zi_T7lm zO>6$)AS2(6Vd9Grfvi5cdIjRC<{%;k))8sT0hR8U+KkQOj!W; z>I7*R@*56_g|s3rOX=4lBjV%}nuB~1o4oB*Pcc^Ln%oBr*UK)QQOzzHObDu-qK5JfEJc@8C1vpwV#!)6<$>gg^*29mtIpT%> zgfknUC6>*S2ZlFYT_j5lhUHsqXLuT0}HlP#r(t@%cG?C z+U%MN`Fl(gpG`M}(yo8L{iT=8@H8MXk<~Qu8$yxUnl&a*9M}o|&qoFJ!xsYPx|z$# zVGkC_y>FdGF_YL-IGfj=8Q;2_-385g zT=$Q$+%WRBw0$A8t&Nc53;j);y)IKGjgTdVE~Q5~B7WEo*iKGRRxSNZJ2#K@i=$+a zF*Y~>xp*M@r0eK$OD*E3=kGON#3;~Qo5xP^?LhO7RIl-RbGi8#j!JY0lJ=f@70s#`z?cGH$^qQc8$Z>_n)*n^ZEuvL=$jF~r`qO@ikO z`sXRP5N8Q!seg6RoWNot$#3tp?sHIBdMJg}+Ju{#y!Zu~FeQRM?TC4J4?UGn=iGWa zwfWGyMQDeJOtqE}&k<#Jk)K9oxQXMzbJdwXJWuj5Ns3PQ>A=I?w|vbpQM>`*C3OP6J>ss6@pu(q&7!kz5v)( z$ELgs8y$aiB)8mqxD)whA?U>VMQD$I!6umCAR^b>>(07ZMu zhbgHe5d6@eaHD1J8S(suaKI$HSEp#)nDs&Vd1O@ff*pj$dv02&RIsn0#A|AbKW-QV z&JMr?QPaCX8Tprfj{Cr50O2XB?~o|R zdP(7f_;iNBs1&WtDHXAIR(#yh`nti>PUn&X1%(YC#ty*2EI62bd7TScOAQ8;(Zj#Ae=Zog=P-i=-^}Zvs{2O-;9D zz$n((Bq<-GU6Z6kSI}JOfv8v_cAHSVHm(@lGhZHDBOl4VssPbHu}LL$x3C7}3+@-h zCNPidi^^9XhI@K>I{%3^w?{^giK#*4&Qe-)n4Op2qcGNk&42Tcl61h4v z$tqB3rhD_;y5Gwv-_qa0P&M(0@{t>@j8F9+!QqIdYW;(=p$JhQy$9wCsWg0d3`fQ8 zaZ%YCgR&YnCNogd;6Bc{>K{sd58sQ`Wcni-xouD&L$}sy1zQNZkB5KlJ!LQ=oP;P- z$fk1jVlVs^lkwfn3_{=zxDFUE5@JhkZ4dK^-L^YI_GKZNXvEgU*;zrjk>6Y`IAn&4 z7lifG^*#sK4s>P4uS-B+!~uO@T4luLf=}E^?|)(+$OKSbxDyVq%v=Q)*h^FEDyv(&JO(nOO^sH!0>Cxr*t;1Ba|Ia`kyuDjZ_thjCFE$?l|zYuB(6}>=3=!& z+K<@JJVcIIMDP-U;SiW;xSb`{W-~^rL>0JjOn>|x?uR+k(`J!7iEDuqhdoWjolVbu z>nr9pMNDk=Cq~pexPzIOvd@Aye^yD?xcxq2sd%SHb|liQ;zosODo;O-mTCZ$CZZ{nF}nO&9N-{Gjw^V`z)QT;q?Pma;HwiwB37i;xO;g|cRb3b+)zBQ1OKO>Y$SD&K!Vv;O-B zDWwQPznR9pvD@Rax_7s_8Q>Q2oZv|I7Grn5Sy;fl%qe3<%Ig+m^Gko#=Ah!GCXGr} zQ1TJ^ZbxT`3aH*HHbpmL;C;$!yoGH7Hu(|1=KXYu&y?@zDTT5zHfn$Vz>Vu6_REHz zl$@Og5fPt=>`vg%yz@gWr6#Xv2cWViiad>ElY12w-SZ|Hr9kLR0>PUu@KSu2T^^*^ zo5v?AWGe5Vn9eJR-^0f@+Ba3&wM}290|b^MfCbqWoXT#m6E*daV16yxR75Rbtp~=W zV@GCVqsI$ZLp~6%=1c}48jU|v2j0f(n3&eg{K64i3KTRL?Kza~N@|kb^cm^buMI6Z zilUO@7IeV)6j<5nx+*&oIt3iN}+j5&7Jazp~3QB+Ow4dWS`A7Pd7jxf)8p}Xrs2GSX zYYU>|l6XF>=1+z-_1i^K@+Otm7i!|xvAuH_z8T;T^QT|JB6{6}N7)o8hGUS15N(h# z;_%2Hn^Pj8PhDW+HxYCU6HI=YRChiR5cKPKFpOT1cr=JoCKmhx$B3aXGD>jWhUQ7j z0u}ru`x}_Z8)PJ-$atNcf4?g#g-$!dn@2c-oINI)lq)k9tJ-h_EAHOp`_D@{5r8*o zl z&?4A7>Ei6Rv?U89_8@isY7@MKUCoX~GK{HxB$GU*L z#5?+*cf6oxAu)!DG_?FZL2-N=U6kReBO_H7+fM>f1%Zt0Yhz_A8k>p zi@`4uA_(xVMVdrC6f_|43`I{M=ukE%tCX6}npYyRRRi+aT{88R-Lzog`DsSrqSh*L zvJeip+7b{7tPBy?Jpm%V^~@^wl@<^uI*d2E~duT!c-qDOB> z)4A>F3`DFL2#|X0*IK1|#!!4dD~1uWSF$)iU-fhTv9@NOK7~I1=^@c8^*7b(ee;JJ2RzZm@tDZ%is(OH60ce`Q6aeOqa-_fg2(UMpn%x+o!|e{um& z+X+w$C!9M}2o4BSEq}tir^xAs7#G&hVU?~?U<|bG4C4YoL<9QDK^vqVPV%PPr`Xg- z&(MLhi59N(h|WqPP0`))aN_==&*7Pu!h*0UDq4}-EemZ1cs9Y;aEa^y4~(^ZQ7<$I zz81H&XD_gJ8VoippH{d}M1gT1O~4jCWk$=z^M#5;{06%+*5N>Yo_Q3C*+5l39im~{ zA$@kDS^1ve1CQXv&-nxIr8z{bzbKWA*se=&gv1>Wr90kTYLMLJIX|GeL>Xew=(G6A@ zRybPE7zgN*G^Pv_pXaHV;ipZiF;E0~%-`~=$_-Xk1a9?y$a3qwr^s9=Cm{7N()mBH z>a?(c9dewSly?5;eM!Gt=o9U2GoH$#B(^<`wn~ z9Mj#KZUTr(QLgXOi?fJ<0Hqkl!%~&Lq5>6>8Z*$y8w|`Q!6><`C@PII#mao5>e--J z;=q<{65IZ7krF~ak`8$x>EYLL8{SZ%A!s~q9B$40>YNOY_jRBzv8*adQxzgkzgB7F z=j9RveuJBFUb)(6n<$kn>Ci>)(>~7QyOYh+oJT}|Uk0qk)PVt3YW~=D0RBjfTSv&` z$McaYBC3KnqQ8G< znYZ2yzCv${S-fYda%^hO8D-kWq08{G*TUzcw7suqPL}?T&iPQZUjClJ+nnb6PZ)0x z`SAJRW;jy+PkR9n={$Y-qA_&zr0$8l@59l%l$0HHQ37Z6rtR${;?8QaE`Qz#E;oV{ z{kjhRLJBLPp8+uDd(u)=pDKYNj&Zsb6$b!1%$>H35p5_8;%E$xLdnKKHdH)yr}@0&zyfe(r5;0 z$|e@-956gBmvW3PGGrQ-poXu81YVPwOyg<@gVE&dDJMic7Aq)%aa>(}p_-f@f-~48 zX3@}v^qDpE=*x($JU-O_PW?+sg8((CeABbhyA5vm#|of*yn9vE%u2pN=_;Bph%sCoLHlzty2uN^A9%cMQ7v@bwrR<+huDzwC?bpP3uoWWk(6S$Q zD^flj2tOm?;U%hB{pa1afD5a^k4T(s3jEzrJ)1(rO5bJrQQg+0 z%BNQx2(xT_766R>zF;FeA0>B~S6BB~BA6k4ptlVWEt2>3J}wzI4EWrHFmR!~O|1lH zkepTg2ek9Q3tr&l*FHp8r~P8N(lSA09#Cms7U!9`2>IV?KCU2qIn(_1z~P;BBLxYV z0l{jUpcYg8M1fA1P1blgU?aF9Xiehk9FkI5Fa*?Hi|NN=LJ!RS^YDIc#UJzW8@;SR zI3jZ66MwurX_XNC$cA+3*ty(FA~IO-9+vzZ?$p^m(d%#8ErY|D9p$$x+r?<%+EO>( z^_b>Z2N`@REEc1AHG{QeyXao1z?iv8(DY~%7LToDl1_>Aq_ZN*5;F}cW~DL$g&a_$ zJIZmI4(J?+zH6F_f_FppdMVqe^z=P;M(wM>3@^fUX>Lv=Tsfo3z)x;3#+t5?knxxAS0?m7KTP;8ic%A7n`Zg{3_wR~( zGl~Q~Uu+Z7h@JIw8y=oWczorTPpbjQAb@83QJO(G4=GPB)Yb6qy1S0mP=r1nBC(H8gQvCUX1$~M$ccP9dot7^Qw{%4(nIc|sD@{Jk~96SiQBXat|UALHCNJ@ zTJ98MA& zB0(hS89d^cqMM_d8BQ-B7Gr#Z;j)FQDWGqf$tEcyTQ?ga!2?G{@^O0;3P zAde8TrtNdctA4w@z8s9XiUXL zeWV6lfbmCg5ADxhN3BF6C#|pf%S5VN>f1qUgI6<;gevz6xINw!O*_Mm%NZ{7?pr)6 zyH2uKJzS4hVoa6%@`C^uJ7v;dD&eveUYcC4w@=z=Mq%l-|69LLN6+~4!|3{!QaF!= z$G=a*G$k-g?+a*>v&kP^9^d8WS5eQm+|Ns1m~%(bqRSH~>R zD96roskZk-yGms$_t~vGN(P_rGH0u-Ii~Gt$vX|zL=(F7V2q6cZC6=SBQfqk7Tc5f z5Nm?iWrO@q6VnKlcm9w2eQdzqd%klg%V3nA)z+X=Qn=u#icxC?{)xPj@rY~|qQiH7 zi*`-S^b!$2!6p?Q4rHPPCEIcn?3)#wOG{IGEhI4w7_2X_$kE$9`I;4IGc-0rW2QhA zQqj{^g&^`D?JK*r@v zJc0*wW`kCf^NNbZp+^zte!K00p(%7)6%8MTeG)f;Yo~9;me#G5+Z{bcTt(l;7T$B;|J``ql?snIDhA&z?WI`j#>J5>{Y<)uH$b^JAE{8Q$YPz`2WU0)B1$i3ccn85_^GNc_auov+1!9;eyl0B@6`L=*}?uz$Bu(vU#!eh^t?k zno3GyB0h5oxWn#`gssv+b?al}xN%RzLwkeNWdn*O)_tTJq<)#I&Xd5niN)D(RNRjO zsX~}Aux1D}Aq>!LIP`gdY_=_dk13)ftg7ENhua0_>X5^-bAbp|T09Jq?}8jH`Sa03 zZ!5v3wEXnjrE`U`nVYJMuGzTzIb~;8uDpzUh}=BQweB2?P1bK+^x5}U0sA<>56_#c zKIuEQh3A(#suGPR>z_cDRW#~yAOlJX)5T8ru&oA*R*6XYiUDC}G>W?CrMGa$>x5=* zGdGMJd8%t-y3iCJYCUQ@j4-{-EcluO>w((k@6=#|4`M3ln2gd3x6q6^YA3271B0X$ zgJHKX*5b0O2JRX9dFtXH?TryQV*hiCLOqJ;XhzrHNP6FvBOa-IHL}{?{gYhJwyu<^ za3(`p$rtBkRDHF*Mx&A})E|(|chEkZ#9aEucKlWuu$Il+|65g=CWGA{scu-VBfdy`FmXS6aeq%x2W)WECfM|rC#9?%dO^`IyWGk?b zc1y1!r0Z%mi!~i5lopPO3}hhQApP~zSVq6q4p>cIro}6#&oR-pnCz7_9UpwFa7F^h zFpoa7rl5ShU67znITWYR#rom3O|1n&{xJflj$2Kwr0QD>8s%ejhp1Uj#%^b9HIn9W zfklDu0{tAmzb*E|)9_nI9V`g`>;(7K2^tW4&ncddP?!_5ZMDW1s61*vx3nFBXUCdVdb)yT0_H|PR4d3y!9!sZf?P#I#A3uZz&BScac>eD<-mLU5%4iJ|BHM%} zD1T~&^*?!0gN}GG&#w>3yI&pA^Z9-Jr>ueEkyPw1obe%_#_jLMvz`8W^V*M8(1Glx zSUhV6(mwlRvn-AKdvo>r{Cex11HnUy2PJ(p|F~&J-D`QLm~Gu{6t;P%AV}5Nd3`$h zWka_jD-Z-28W&i6pjyG`_P-5>BO3vXmo6LE;KQ&e0;JSbA^O-9M55xkml< z+6PL)etjAW?zhAle#8}_^`r|Eu=|Bb3?gyHnO?SRxWU?qDx=MQ6qg^Y-)x%CR#nUm zLFomF#ryotDwz8*| z>;^(RSKo^gzNZ?@&cRv)b?PN01sb-<#6+&`#xCdv96r=KTkASr85{3|WJlc|a%${^ zDp8sdqQe(s|46xAbk@nV*DF9eAlW?J61(3+q#ZbP6e6CHI}AU6 zYdfxp$r}3mw;_3&hpJV=79E(`ae?H=uxH0-@}M)gn7H^2s;d_3!@A<5CC5~J(|L>o zZc;P6x!D%LZ{usntdjNl{vXcXb#9!z`{xTsPpLOW{|S4KVq~OQHngPfM#&?(%hs`h z0*i*P`Q0IU`$ZkA&hx?8W=v@rQ)PdF0+G|}Mi$lRlsy1xhkp$5Kn&YIeC(V9be_L; zqoy9=^8sb3Vd8I=F#05;s8l{!t)`Ung5r}_BXxe;jcTJfZAwEV)BKJdSkzM26Q$|@ zA;7?qDV&EJRQAU|De~->PEoQR&K=0&y`rh%31IdJ8#$>gF!rFgM_gW}49dwryE8Z0IwKCDuf<<&Rav;Wl%*i#rpW zplonW*0N#Z95Azgx|P{D;U)0h@P&=Hv8SR?yKT>WBcKO88`?@q)!@c2l}k6RIyN0v z9#H?#OdBE&kZb`Hn<9sBoD6akCJ&yl#&j+me$#3!VFV6+{$;T+lET8z;q+ddgLFD3 z;ec?(le^a@8MZkND|2`9t&^h`Q3vCSfwM1*ie_I@=%zmK9DX|e>wIP0hj!|pe$C(K zDViXkwq_(ofHxLZC8X~-VEq9bT?#>t(2iB(OP34x=1}ES*w+3>K(rct1Q{oJE_ggT zBT|tiX>OfLTD?5}=p!C1msN8uTl0;am?%Twn~QCkB4|ebAenF+G zr7TK)Vi!=rSuIP(HL2uvAPWIgI7y5NB<+Mj34w`>(x*dfLm0d{l*7Xq1BrT>W0SyN zJZ|R0bU=WZvqK0h;R;?A$@U#1#1XupHv$pk-@EBisRuEvO@`hplMrfnji>6x$p_FVdSg=Ir|x_;#8=U8DcZNg_I|B05$?%YB2}u}1HRYs+VMXPcn{ z=2icPvA+t7@@?C=;UPyQ1_TL-Ap}H8=`I10GC{gix}=7b?vR#{5)nZfBt^Q0E-9&@ zq=o^8VdlNK@8^F0_w#)V@4L+c3v9D6+jU;&d7Q_w|Ms0Oi1f=KR~8ReZP8`vIKfEw zV1)Gdw8?OwcQy~FhPFJpWzEul0kn1ZTf=(O#%$}^(yn!Dr2f6moARs#ERYR~R1vwl zXQ9ksOxp*o!OYdtaMAv<1R3qyO}X)W(m~ULNe?BRyk}4I6PQ;re0^X(%xHNU+?98c zS@{gR^J^$`e&G7A;x&^W#v4ogl0jGl=eRs_(>T7ZZ{l`Af^_!?v=djP+GG*%6)Hml zEfOObAGOJSN2F|ifioqbIt=pP3$>_$CJ3JgSp<>8Q6|)AG-yjc!kj#aX@5dB9WYtL z>y2R=HMqK+fjAs9i=q@%N>j?KMdBg-{NUSwpzo^5Oqv(Kg+_AuxwLXJIJ1ffA1&I7 zhdzo9r_cv1N)1v%+>^TjGm(!f?>EOet{XRiOIB(~eRF-k*w1ip?Z*yv{H)0D!eu0T zG>^-6KMtQoumP9jS(5enx8FYQw+zV5@QNb@i!@_E%5>3!)K-mRMt+N)bqUU56490N zjhR=tr&1Qh`-PAfwoy0Sm>5hxEXxZ!?F8mU7EbTMfWGIDiI(S=MhNvYV90y&GA;@g z=%hvOQPNbEO4vm<*|+VIZcaFN(J*DrmQA<=gx0Wr>7)qJt-)h<+D<$Fm6!KoS3M3v zNHUN?X`xYXJSg`*q3FiRN+_h{Fr9--FWO_jZf0nf_S#X=LK$fHhW*HjQWfMBvgp|> z>y4hyPx#-oDu=}zfSjsy3uR9;dU()(((a>N(*qf-nah>AJZ@Q;&T>gEPfh8!5uwWc z{T{ZhUl3`@38~n}yn}Y484HwS z*No-3=(Wxr$?86rjxL(xl?l@r6{zolwvmVTiq|OJl@b{4o*e2m(py&lWqkebsp4=~O#EEr&`>`9|siQ!sewayuK(m=ME2-J`(5 zNafKHRIx+nzg{=7);DnwS`OnE@aHQbO2{uRltG`XYBOCTY_;VMC+iLi0ui7`v-p!; zY5u)N_!n?~6eC#ra&`^mYjkCiu0*fz4H6gin@hJ;A+4-zJ8}Uw{F+HBU@CIwxtlwi zf)hd9U9pFg<)pEhhC`1}hUNgPm>QETOTiLIQBlatS|BxTQ!!=Or@cS@v{_={#)kRx z%!GcRQdf?wzw0IH4EIG8fXg!)q|}qn!Ojjpo2R~wPcuRSXZ9Gn_UN2QhGGlog#LSg zvoa9X5iVrT*Li|dxgMdth&0oC?kD8nMshoz`?(pXhU5Amaclq6cQy%hI@g-pSA6eN zob1W6PalPDB8*g*Z<7O?LkddFJ#kHc62wK@Ed=f+MuT{n3-D^!o#10i#(MiKkdFcU z1Z0b^4F)>ED=`+?zh6Mhuc|gJ9;?bCm-wW~F-kVEeOVDdLy6^eb^H*Z+D#m?u8_~) zVSL60C?Q<<(7HOZl5n7Vem>+jso#|3;?ogX%Ou=y$ zEkxns;Z@!29m}F&bW2s;@3y#SOicWG9FEKqR{yENV{d3szLCB(n%~0tSX0Zr^hzA| zT>x;8O?t=sufxk8mp?+?;{}qP9T(ye?A$zWUt`@_FSM6_*2)3?JAIS9n&t^~#wKxU z0PP|6|4Ms|;g5Y`JU;)$mk(Lb-%e;#C?G=Xa5AI2!y#Od%39qp>!A|`1}2wk^|TZ!`;l#EWX@CD=V|LE zI$A18aOIzA!`aUpJhucTe4vPBD@Gda%V_@lKp=}XTTa&ZWnaIuz5WI9cMYio#XeYt z_}lmsCM6$0maKJq5^v+43i~|u+ZSoYyfP{$2w}?$PP}3i&nQ+igX#fw>fa|Ve88)7 z{4|BZ<;3tNw0iRqj-7nJ^6ClM@6?-j)AR9AzfZ^hIwBFOWGp6e&5oxDPLX}QBUct<;XO#o<4_z5^o46<(!vJaqw?5fjWSXc+}`^d!)^ za&NtmS99Re<^XTbMc%ilCvo?hNXe5w?k}}ItSl5=jPB;cnK7=bEF^=C`rPh~`~d;! zUQzwm;Kv_QU>{`PtdkhhJ+dt5QoIFoQ}FJ?8&HTCWm%aj(-0=1nJ>KK8#w!uJl|+< zHBU$8Dw|y~-J0SMVwy*w=P)4|pI;oo7hzGaJbLn-tZcnD>g(E1SqZ^8d4iOgc+SKcd1XNsrN{P%_VTa>BMAX)+m5Ce@ig2CT~ z|8sQ@F(7MDOsA~{vNW#T#6>;u^f3m~3SjzmqMiql6Q5#uEL0O@Dl3@y3vA_%E+TTN&7T#;{!w#1X-Z_EdK#DU=a$&0J1{shWBI`( zQQG@{+9_@IF-;B-mH{+$%)l4SMRTYY$?iwfji(YfM~?14D=67}NznrYL%UIn{pe*@ zfLOX4V^^(EKVR#f2IgkVG_T^G#>pG(pybn5g_W@=4RyAnu-ozTF#faIp zQG4a0eFHk2i~=sjLJJ?E4t=g5g!~}my!dfI68AebR?eFGhU+``1*EJ8#W-o*Cwae{ zakf3M-Vv;x65l3K1nlhV>HGRw>#?+kDGZ+!)lH;rgR9}IXJBCxW>P2vGO3_aCWdET zWu%3|(CD05fh6^VR*VtIP!`p38l~x(pHeul3IHUk{Mk$d3DCuZcT-x4u8(|jckazzFQIlGYM(+<-3?0=5=O3QG!x(^woVIq*UynsIgX*OPY zP49U`WXoWPLwp+m^bOv9Y~Y}-cY8A0QnmB@7p$W(n(&Qnr7U`W4~T!8_x#th5+L*P zBRvV1R#A{t#XE;h@$QU*exryoAG+dYAb!|ctFS1CVdq!Ypz!~4AqSQ}Vhh!wh$9tl z`@Yvr0i;;PYBLL!A^XRomDbO)>yCp!5g$Hq}1-&MSW^4E!El zdGoyc@hXYQ>z^AepEe;7{DRGF@1>Phkz8{dx^`r(4xu8M#JandvF-Wdb5h)@@<`oAD zeXI3_pX_;E)>cWF2yiQ9at7Ktqwhx(Y`NM)OZ(#ZjxQ z0Pvf!9fAv(21Z}mvntxSa@amBJS+ovELZ-?8&q6cwLzOay8+bf`?8*ttU_8Nqgpm; zq4K6X#OcqiOO{@}wx$ya=C9tP6ynuW&FyT>z3T4| zDw&ot^#pj-?nWJR8pm=K?9Th$2Wo~MPfg=ct?!jGD>65=y>iC?xx=Qg;H#^r_y+_s zZq*fh{#EO!qQRbFwFg8pC#Jj#PlbNe1p>d{J;<~ZGJj$Xgi&*_K7}&a@{gw}fg!Je z+wRic^}j%_M!MpV^F~#^;*tt;L>3rE?hWs_WQ*a;*a#uL$gZ)j4fBWe;k9aBBIvkn zk$6_!N9+)vbH??Qcx?kXaR2Ecm7hr$mCk#uhuAR#$dV8Xl4k6%v(f`el=P&#KMd-# zu~K_CP6%_CP(Ng=+pyI;evC!A7XP;i^(kO55dSp@+Ciec95YY*90{oe+;G3M zM=*O$_KHTB#O9yGkdU*GcrVq$D7*P@dP&>(yUQ0UgC(N*R_XzY0)5rk3D8;b<)h-p zmaLnA{NQA;ZP@?>Gi!=`ePMNvH)B{`E&T!h!xGym5=&uq0~3YwQp%nTCd+1 zQTzny<0q|gB;{+oPt!%~yXOCSD#h{gSEkj+r0whMEnLT#Wj8_|&7O(9g&KZm#ep{d zi%Mh8PldgQ%c8r==s%d!y=1B+yQEAa@;$j{a*voLe2bRZ!+~b7!cmCfFxxHOCMNm8 zV8g)tP)1{(fX@J|X50T9fKP0Q;+e%Cp&OJ{;d0d=X%8exI~43DqixWKht^G?X-B{% zt*^pf$!@!wLYR6NcZJBwy$6VST^J8LB~r&K9l5`rga$j%P*4!8DhtZPs(vNyZ{!oq z+qMGwc@ol{F@52JQ_O}%sGy6&H76XwDBYohwU3d-;yHeT47+-h_XWKzr+;%N{rchWMgGN|oUv{Ei#w@0P#qh$>H5N{ zSJc$tc6Ul>akpF@Y^O@yqV!|r@qUI*0~i04uou^RN2g>0Q5pP}x$rpwOd;?{hF;Ag z)sKsqK!YjX#UjosjeO4QcbDt9_KTUhME|E#>Jp~f5m=a>yR`N3X6Bh1S=wgRp6Y-DCXg@d1i(O2{`^I;< zSGC)#`lYv8v1H>B%i{<^EG~efx%`PD&{gVMB?_B1k&Y&xFWUG-VB9xtFq`x@onYa- z_8?3V!VD-*AY^*$hcm_#LT?lpg88<@cQplyJ`)}(}Ie-qjTFt=#U6f`G-b< zJ&~^M_nbB7^HA{B1t&izuOHWB3SH{_+j;%rMkl2u&vE0~k@AJxK|}cxDT=XNck6)U z>P+vCX?pdPk8kLua|+%^(p2ROJJeNLA{<8w-f!sC)dGi$o2_VJuZZ{uvaHdKebJ#8 zbTww8YY$q^&MhIIF10@n$e0_nvYPP(`yDi))-?Xp<@xsYMy+Bju+2d6KfK>{EI)&n3s~RH>_9YEq<(ow&i95KP1<-q{RI%OK;#-3R<7%#ysDgiG;@>Y1kBfyap55SA`V(_uIT+8dL1g)h0 zqhT$#WzdW;XmbRs8_#A5C3ha`6vAYL6whZTKmhE@k6Lu697q#R4s($Yj4ypYb7;qM zJ%Atze$*J-DL;FgB6L%^D6Gc}PA1?B9}~(buSXIuZ2S-Fz^(0#I|!(( zXlm)5#3V4;(id1yC#(2dA|qHnyRAu9^FTQP6Fo+$P@5#qn;YsO>jgANQ#0AQe=)X& z(lRs21$+%q?um^5@)@7CD&K}!BiR83vumj8*{b=QlA6xGD4RW@kiV|IzzYo&Fnb>H7aeTQ++;fP4}9hOpZ zX#_KJwG8r6*3sF-x39a@NJ=1b7@t5spAj7VD4&odM~Shm8_btV`*XVEgagmU`i9$i zenQ?7zpw{@EMZtHwJ`=mL6CT9uVN|At3ra6Wq#2stYIe}25I4<0SBScot1-eRl7qB zy@bo|WRgE5BaT8NhOfV0AQ9fZTYfTqZz*I_bk~t6RkVU*{KRE5`~#&9k<@+@e00;1 z1JrY23371w7vfU${h;jf_^sz$b_Sr7Qysm@5Os&jgfQBqb&&G@mA|O3>_wge>1^!{ zhp?h^O=sVUz1Nk#uQ@h>lu&h>JWPLH%d~fu+7bWNIREf7cc%5N#uqxc-?R6-$=wTPZ5H=uIpy?e!pYtL z(N#Alv&+l6O620iqWhO%m)5$Kd(WSw!Rz-b_KM5uOj34Frxr3OU()xu0bK{AW4~Zl zz~keXQ-GlvbdFPvX9UvGfP6$B9{$^=HOA3Q`0;()eGiFTPIUhVpaF|s9Okbzr`P4P zLE6LtUsf7n55Fu_(nbfD=>7E|CHBCgF!Fo?EvKl!i`l7macPk6XlSMO>FQ--w9(%6 zb5Z31S$3W(lbEXBt^Y58hM>~9=a2V|aF~8*6c3F525-J}BW-U?gT0N&A;K2IEZ;uU z)#eMw!yxi>jJB`RshK0ZQpw6d*$D2^K!>k87s|CaLivsd%)$BHP>+PR10P`c?`%`D zzue@!+(w|5;S31VofW z2T#mfobqoRZveSnr$&I0_SMqd3~X_x!t+LHwZ>+7+fKQ4`*}?J8^5&ctcx7jc|_p3 ze(xa{5K&x1+kW2NL{n9GSEq6pECuk8qo^ofzhW>Y%1Nrckop#&1ma@116@(Mn zF&!;`Rkh2G|2!s|#)cEq-4EWJxd+qtn#F<%N_$ zy#?$#UwI)}ER{e<5FFpwlz_}pE zZ>iM}LL}#Xez>}j+36zf_X``NeMsbbC3bTCpqnk`NUL^IBU zstDpu&f^L0N@DTV+h?ZI&}Ou7FkCsJ>qNA@hdiH0T)0Q=q0wUEW_>>1K>bpdEYob( z@8Lxx*B~9*HHbmjmAdn0T6O)u2%eT%;y2Brwi1mQ^ZFkO4&GEj?khom^PA&{+;{@! z5<`1}wHNYFG6m+l<1Gxklisz|=PDhj%h;mB!%fiCCSEq&KRqR!o*Zq=*Z!FJJ4F1Y zVJrOzwkpRl$nkeY#qH0pqZj6{wk*urR=;({J}Vz|A$ZElIS6WudOr0{Pk>Gj&%Wn( zj+Tu|Z1yQ%jKEMBvrImQr!TG%Em5XZ<51$1SpKK5z3oP=<++A+h{KXGQz6C4l3t{% z&-iQI{ilM~z`FwrJnfqJr?y{<0SRgG0rT5~MMbp$Z{rKI@C%;Iqet3T`s0r!-B`Pt z(O*#0MWSu@=!~#R&Ck2DE#vBHxsL&L2UOyCdj0Xi(0CD5X6E2-`6d0s^-M-fvGvqH z)B^cx5XmitE|n(Da$~rJkrdpQVK%IkPdtdUQTmE~X0S-A0jw>H23f4KeIX#DM}Rym zlkg^Y_rLxbV1Yp33Dxk9;&K8_dIPI%l9l-9^H$E6c!pj!9|Re4UuBN$Zgv}o-I6oSP& zzXD7I${c&&OSdeEKAI1Y=c%vM{=QLm@%r2}nt#SQ&vE%(fEQNY>9)2S$2%KTSW|$( zlGGIzdWd}|@F9<0Fo&5q>%!~H>0F+t3n~>LqmqZg*=N%|1}j4$nR}`Fwxv)lR!;R4 z-@#%=Jm*xBJdXNR)tcBTaymjUTYv!)E{hq>8dI&-+abvO&{0V=b1+w9r*5Xgy4hMo z(N!(}SLAmBYE!qWe#CD$kg#jS+Mw;0rN4>szud9$| zGC+y*twLpW+JpIf&i1V;_FK=r@AbR8B!^6L#B8(}#Q!pWFi^Lym6sZ&psxGLaP~M}jVAIki0uTU%H|w%+8k*4Tsn1_*iS ztv1w3i5MZ+IxZ&Wi?6`6i18&qr1i|qL=PQHX1cc$f2XxBG*s`$n)toXgrh4IAQyas zQIG-wDUXM|9ziJ+300q(QaHg6ko2wF=VyNul^UF*AfmA;vfF3AC?!5g7>0G3K=L@_ z1JI;sp)t)bt0ecGFAO7R0YjJ;jLEaWCr6dqc$;NDR)@TqMD7;TQnD7yWMX?a4Lzsg z%fCfMe7rW3KigFCUMpga zq2|H@laGInpI27q(wfhcIX{wi-U-)mez0!p^N3(NKaa=rUSmpn0uFxez)lT@+NG#rPU!#xBp$>TiS(?pYO6b#LjKJ=v{}GPaJUAs{{#b zTpH&p6~nJ!U0IWNi=H(M&Mil-t;RX$&n=bmc8A8HJ)`0@XVG!J?R*a`)7YEhZ}tP2~D^k&QN8lw_ky@8;x12r+D8n%<^N?E5b){EaqtVgZvoNkM-` zt?lvNaaY%~GPFXGuou>^#+_g;+~>ZgleLv?Nj#UwCUtz4#`LNvENGs1i|B< z6=vqlYg+0@^U1~?FS|65#U;BeGK=CzFGuL`LYb<#DXRcoBSpaLS<*MWzx?~t{rmT> zbJ}g%j~LpYhf(xA##_C1PII)(ZcP?t#9gc-7twm|))ZMF5< z`uJ|TU&AxF>Dq4trzR`Zb{=-SW)%H(^yzBQRLG#b!s_22 z2f3}Ny2_7N*aIAPZ^`eVB7`E0?v^<8^f^@IGH3K6XAZCxPl8&eS+wd$0+E;(Npnz4 zCv+%al{UZ(1$^m1C|bQmKS%R@J0hmd7S##-cW|VX*$<1k4#C{J&V7DB(~_Cgp!hsi z(gOkCdntGN(kAa}F3%{ejEwASQs1)dcdH=>YRy>iKgLP1Hyl3RNYt(^z1YrNDk#3p zmByvhYiiY_CdZ2wk5nZ;unqlk)|7o{!@{b-s`lz%@YeiF)naxah)g!EE&TAPbyUg%d5a6gx4pPbH`A=vnt@A zrG+r!gd8e{+Mwe;{P}%X7)z*hK*vLUJ3Pz$ha{S~KD@&r+!4eGr?3Uv^LCowa}2Uz z$c0~i@R~CKd(JUAt^|SZn&RV;BZCRfLL_u9Y7s7`gCKJfCfgPNiaYAz)1e%<{b%$2*P^cztR?6&8Yp`Obz%c7`2E zbJ-?MdBD%FK`mF|V9o((Ua;5SSs!(4?mgJyYDE2(?%||nHTCuz(XF^*8<|Tzk-Be7 zS=3fR!PkgbG(Y9K%IkN%jy3L`Fqmf?`D&zP*~>(oXuCI*3kE>e*!L$d>t!l98^s5E zaso&81s+(@_8Ds7&gpolbYthly=%eJhWkj6{He{A_;yNg-f)S_8R) zLyV&FguDYT(gT~koqYxkc05-Qrxy7uN11sSnO2wd+**#DE=K3%xoUX(S=56%YfYIJ z3~m4EYhn>4$=Q;LtvQ#zXwNSnknx z`#ulu0{-B+8JKg!XFULfn|%u4S%Mee2Znw7H6;@7jk5DUrq4+W)e~CxUD<3z%pROh zC8^|@_p6}cRT2L~#x-L*3-AyiguAJ_KHO)7%;~#|(ZzZZ9NsI+TNUljHygqCxlq>U z8+NL6-_^4?lS?X~m&@d$u2y`J^e`sa8Gj``ph!YeQn5+#bfX>wfE9TwFEcgZR9TJwA=sA@8*7xp~^*f5S%&7UG^2$kZiH#N{zOjS^Fpj zD72r}8?5nOzmmmj1zR*iare41oy4~?Jzo?Cr5CHwK4Rx|*0c|?yERE7@^gW`_a@%Z z0~WzWimHF(ayPDVIg!X_)^I_twApdhJX?N2m91*k4#lCr)YKUw`%E3@ybX)CFA+#-~0W?GJFQO~Yqvx&zvR^9%D-N9G;V^R(xv1K? zD<91hU`NgyF+OXanQ9Vz7%}q_D(+(XZJE}*n)_Yr_{HW>$3fsz(f~HkQCFqi?b9IXepb>t2H!SelbsIy#6Ns~pB|I%7-H#IM?y zPh@H;uS{^|(Ml%@4){OE#oHpN%^P+8PaILb7U9m^;Uj5W3fCmh)!$dZ95Th`$0Dz@ zK*NndSs~6*$t|JsK))MBG{B)Y6xlgqkF61?exg`i&C)-Bni~TG=5py_FcFDT2{s}U zd1drU>W(-f3+Dt=bVfy6$w^k5I?td8IoZ_g-lbk+P zUc~*kF=P&A={iQh*IUmLV4!BvXaSgQ!nf-48D@{YFY3EZh=mhHFW#WFVUYIk4);It z-7J7V43d_%xwEhsSjHQ5;L3De^1F`=H9TsmYWe6qELX<9ltQi|UOzew%?ptS45JbNQ61oEoT{jHhY$}^Ya=gsGNnP*lKJ#~R#!_e6Gg~5c_5aw; zao}U0GqZGA1VA}Db*39bm&2D?_ z#BDNfs`WWr9G2k1%r7l-;(xJBi)0p7)~&+sJ47Ul#JqQv3C|hj`5_hjSpbyTXF>Z+ zD+fdIjQMxSP02&MOE#5ecfUira{rC*;Kif)r;u|0tV%`NsxT%G#ynKgnA5VouFUAW z*GosANa9<>dEvXF19xRT+pcY(8bQR%g2-Un(lF<@hkN|=`|F~&1iWgAOmG48$TCgK zAY(wqa9XLk;V`EBt^F|mCX217OtgG-!IZj-pG0 zFj_F12srN7X0zT#&M_(ZLSuC(~D5Q&-pMxYc9L4BwKVoWW606!n1W~ z6Ns{@$#!mQidLj924P3$CezehJF zKP7s3Mr-%D?T6Bl|M%%G8XFXr(UbFU?sT}9h4$>b4~f>oSHRXM$GqKCGs^hJMerOZ z0y}hT^&B1>0Xi3>Ka3TCCb!;oS_pK(O&qizV6*>9`7EPUM>PYfo~Tv> zkh@A(<#L^k#!}XMJTScKncGa6NYd)ff3v9a%I+70`QEHKlrI%&VF=GJPJ`(V%VV0z znAxNh(OZHi`4voa_F;5AUC9jA?c!A3`Bg1j6SDgg);yHvcJKqt$id_z&WnVp7xD!+ ze!4NCRLp#CGc01wM1L;I@sjNTONqrSfi4YF@e}AQo6vj|;|8V#0e|;!gSo=e!YvP1 zE{Cmaa~Nv8jIqb*>F1-Eq0yl>1WCI<`?|}eUM^)27 zRsYnJ2+(c8ah_wzTO5Z&>Z`7a%LS!c@Fuc}2Od6frFd4Q-oL4c;Zf>lRd^F)GmSkm zf$={etSqCH&qFt~Dh++m_m$8zf@ZgWk+3W7EA69kN!@v(wqxD=6-S}uvms~L0>9kZ zy^X}yy??W?hwm@trxTkNJ2 z|L`|Me}BXbqL*hfKp^l|;4+6>l(i$8%oP?BT*9u7`XCItmMJEPIY~F7FqUKv?i4=XDi^?R_jBz zss9Ik#;89;ONw=lHduw(%uMPZ*18o#efL*+QQra9qUFmYN1`mVl%SiY^M_p%>X7#SGtZam)94Tx&~612X{SYe;Mm|#rrIBHSRH=Be39F z*w7H%A;e}6Qge%1;Qpq-k8mrwy%XSgrNbQ!4XyI3_O)CLJAcEcP`=l5Sl`fELB5C8 z5WQo40appj;MWM=ydG+7sh6T%DYBv1Rp>W*G2p5!XCk7de^O|!+xUbu-CV~Hl?`^QmXWuO#+n((r_unV3II0*J-vT9a^`7SZs#7PK z1?#?>;==kZ^nC)8$sLE%Xl9=aGv|i-)^>d16W)8E9k_qZ_clV}#XCVt}Qs7ze~iZ~q_QSI_+Wtqd@YeMR8d zmM|ZJsTsky?Au_^x=ES#)E{xtA2NoJ4pjXI%*947ZeXpwc!o?}a?0FqU)8IDX*bChhy>9RIZWIz zsG#UNzPlv(%OFZ;HK`_HNcQ5TtWOuv%*nu=1TM*5UiVh!&)(Bp4uJ~J=>y6xr7R*sOrrZvY9=?xKi@g&`z*e;)XL#}m&x1C z@4JX!N?E&_3V8`(DU{YJ z4%}gdq762^S4e1v` z(1;i9LM74hkibKPRDg>u!X7L)pwN(y50$a)^A0%Fb)1O!n}MW%u|2kRm~$8*y&}%Y zFqrxca5`N8bh1i-?b=B26*$-RxD7D71bi-iCZ1YPONow?D|E|e8^~ehC%_vg7hdpUy_iL|uy#0dK{G7aIdEmMSGvmOBssbG4fXG4B z*5W7`Z#sg?F6(|umogH;9H<{0(*eQAV`=y&B?5kO`77?1b{UKBwf_{K?aOF*=J)~l zF%dx0v-P9z3WAl-{%>T7C)?q-fG7Uixwu`@lNH0Jfk}W^bJiSK5Zr1SNS$z?CL2$E z>t(_eCH>Jk5hq`GK5jjjI9(-d7g*#2JAHl@xq3QPIT$RF%)xxB7SZ>-AlUsdQoDdC zQAPF^9Gn;t&gTWN(`i1HL=5YIeZ!2L=&BU4n>ye>bg2lT2QkV3F03SyrGqnavJ&|d zJ1-ZI!wI1@_d}(Fz8<&2&U=E7Y*l4?KmUHfB=?p`%Umdk3V@xr83g8;Q(vq2=)0~< z(1vIWhQ#3K6Ld_Fi-Vi-X`kX`jAYg)!MetV*|Qz(Ciouf1q4qB4t&n*g7BH-b!5Ez zI(A*p;kK)lG|p9>N0Zd%xgQmWB8n;8cge&Uy?e@DazW~g)!skkbc~ktz1mhQ#LCrRhU;%bG7B!5MLe($scdBgfCP- z8H!9L8zSp6>*2q5#$ROe@lR=ZQ+51itXE{+1d@kFu)A_tGt@HfsI=zf47a$4Z1S_& zdi1cY-92+^?R>M|P^y0e;45a?jL9_5Xc+1DBsm?` zddWdhhVpKPb6y3~+xeB`6)1pcFpbF#sH)_({i8s#lEUCe9yc$0n!ulbRtdQpe4G6C z+~e6>m%8;3DzLa&sG2E#!c{&IYU51dWH2QKiTUvpFAN^Jc?5vJ!>^xTdf7$8pwtFiCjZ6I{kiI

9I<%+cGHX2g>g}FkM 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"zHzkPx0-vbBWUQqk2+*_Wi-^JJlz+2iHX8)9E@6 zbOH6nRfTnBllATUEg=N7s}?!&j|Q!J30R|`$RaiX1!glfpPOqixKp^fCUl2RWM_`a zBjKyJ)Y_>Ki+gR>npk z#ldY+T_nkQoSDv_!w`y&F{2w|5TLN(Vu~Qwgvq;h3yc}mKgQO;ronoBu4F9o;?Zr% z5#!~uz|B~_6U~`>C?8YhQh>QCq7%z$I_Uh&_7-2?n;1%THiJ^Wct=(5c&4fOk?6bB z!&zxRwe63JX)D98%+aZ6DBXJg2@3toXrcZoD!MoXN$NJfa(jsg_jNjtz4w80J1avi?yr9-k@TjKxs&xjM4g_t(NdT$0oOQHCz59VhN-{%;N^F>vN`zr zWT?pIbfJ#o(SyF{snX-f?sqMj>Bx@^V#h__J{~zirojdjGW;F+vZ2aq!i4LunOF^) zUv6xQSpvrLtu5wE4bp2}kEs!oLd;x)YGR@mh@Lfe9PxK{mTnd?PvYA=f~wc<4DjdV ztbJ6{5Ixs~6>4OhZ{Yj!r{0h=M@huWW1nR$BjjWyK_Q@KtI+%1E61Zsm?zEuJ~}}F zvox;um`|(+)5BiirXV$$fCJkaO)A#3Z7_D!TzOfpT51`gf}1bX;3_UEFtQR&YhcF?#`$_x9r2RAI^Vj!|Jdb`Otk8m=YflSc?w!ULpeUh6){_F#N0oM7lP#?Hioa3 zxdeXQH%&VpA$(`&SN>H5Mj!Za8Ptj0Duy=FO(&=`YeM<2LO?XTJcX@=q(RSQ)lmf6 z4qp;gv{4@;gzLKk>%vX8y+3syEpl;U2D&(j6SOdT&NSXdu42;X0SU`tty*ChmR(4; zX%FDLT3z^aB=$T27Ds>R_5M#c)g0S5Nr#jt-W9UZH2U^fZ?H8`erbYM8(5vrol}6* zdpqenxkjMUo!1Kc#y)vr=#VY_hv7E4KbZVi9oI1~#7DP@ADfyN&(^H>j25P*AbSiY zy|!s1Bb0+_fR82-U@=~UfBv-X_qWfYDLBL+48^4K)lM_;sy#roQydGA^b!(vM+H)T z*~s9P!o%1%!crxVZzSCPM@yE;y=y#sI<>vp0a|&qP~UI<9`ARBLsZA`^s|YH`j7yF zwqDYTxp^$Bv*)4HM}7}C%crJ64XZ(k9N!DUH@~Pl!~oeW@B8km4zNjS^YZzUG1y5Y zzTH`Xg-n_*u*SS>F+x52K5%`(sNGl~X9QzxxjpFN-W?=q*a!!bf>s)SKv2ISiKA@l zpO!7mV(Z7<;>hdn2vrKySQtwk(Ysf<78Rr_k^Xov@?-en9cSCQggbq`(Gnv+#h1Tx ztRd8Xy;lyp7m@JzVb2#jnL&eZqktt*F7rFR!0|jF{O9vOdxyLeH$8b}d zet`Y)bV1u6*}p{?tyWou5cTNYd;!wUTx~zNE|pZqDpqp#1~YOhap{y(@>c{K=~B!0 zDJoy7QWkPXMVTyL(!1f4fR^16@m~q#47#{kn4NWK(NEejLC(6dAgSf*Rp4uAQeC1C z4%0tWqN~}~WQ-)dlq(5dnsNt}QjSViI)sdw#4!YTu$6tYwBLVGj#sHr84;<4XC4Lw zcWRt&d`*!QiLAwE1+HwPd%8tWw?to?@QM5oC;-VtoQJx|c(`57=S2+vFykxMDqAYQ zXMHiVOvz$czf@|R>w36xpr5fQI+@#J2lgF|&P4%bgfz2LJpg+2d*G{2R#n*WBhTS}m>&!4;uby8qZ@Z2Y2S8C{&iQ{LC5m$4lhV`6LoHDENBg~d{ZV`H*zWd)3|tZ z-L)F;N!suBdZ#_jMRL5}Nw{Wtx=z;JKLYP`ekX|GT61SW(2YhzS6J5<(W-HH#p?xR zlqU&+syF!oBdN8U3?gVu1U~30VF)m8C~7tlKaSdUgd>|>ex1;{2_WEwY1zNXNMR$5 zaXjb@mrAx7QijYsRhSbe9~eqxz$EsaJ?cIpYG3V;t0;L1@4=uT|#1Da;Fjo+ygVV1tqH zy%UXqufd+uGU!V|dI1OBzQ&vBGa$*10F|-#py?cNKPl>?AV+ms?JL=b0j+)}l=Fqz zJ(joqieSargFJ|VUjsQRLmC*o4fCZ-*L z*MWk$<-r6@*Ky0s#K?GEEg3%6s1I3m>pPClG?$RNRB@oXEtxN|aSTXDiwdyQnj_zO zb4a)8EA|#vB=l%_a^^Vs$gXd(@N%&>v2p0=0^ZN$djWL#N368dY_tAoWAIlmH@o~O zvE_9AwE3FuQ+g$QUku0TZYSFgSTE)5ifW`CG#+P-v?8^^`IRH>mr<}+DOqv|R0~__ zhGTz)3c6*dEh5LT!EHr?)Ix(RoezE+Ea4zGqKcttK?i1A*^%1W_lNLNs0(wr>A2&? zA?c(W<1*KzRm}_UKI1X*-|$;}kI9jAiYJRk3;0Ao%@3q4v+5nslk3_y3gnub+JTa{gu*ed z^s~GtSxjoxUqxMA8*^)&a*@rpF+;tpJDzOEKj^U$f6HWwzt)OnnIYf|B|71}X^(xF31ll(pS0Ko z`UC*2_e-})23+2VmQyl{V)V?Vaw*AqzO{@^QF+DTz%=xWYL<$vk@LLcC>T7B(lxsb z^K7p(=AFh;dx4HY6C8wm^hqSp5kvTj9(g0=T`E{mBJ8Y86?o$I4dE-b{&%Zi$l0x@ za4p%{ZO9AFpV|4DsBb>!E*XQppTrgH<;rphZq5en{jiAaKC_Gu#mO?=ok!QXuY2qi zcy6Bn(uHzhXcUL!67fIA>=yhcq#QGaFI;W6N^MH})}JSd?xlb)LjrW2eLF{n%7kHQ zIf79Wfrtct&TQBlOKA1`h?F;cPz6)BA9qkQz;zgrRCM;KvyKQBGFz^L8q(Fu+-88s zwV8KRvmKTZZ>9r^p)p$}?=Wevw-l;roEYwcGljV3Rq{gXiIPN;|b% z?$(@U>~k`nHe?Lx%GKdlytXEFJ$Mt~TZ*n^GZUm3tbT9+tSlu>?-FUnj*DA5hk*;gE znh3`(4h6fEnB1_v6&zU_KJL>?o3N+OLiev$0UZiOYLmv! z{HA?_&p!Ax{vG8A4dz+vJg5gLFsdfE z|6dxb`(I;!Pe&`TN48&&ACJrdfr7f;bU4cj4O_l^weo8WTkp6p&TnmBTmKAZ$p1*u>0#w2=8m|Q-a>K zsW8xPCf!pZcwC^3Q1>NOT-$6>%-_X)nX_qxO2I)_Nwj%-4w|oxXLk=I(>-|q8vaaD zDDTK7?+ekOAAV^91b#qPI=< zOXuaeuF&i#VZ-)CGwuN<2|bUsm{7q9w{b%Ca{tPp-8oBbMn9bsg>6ljt1T}!V(Z~_ zvKCMbEcCz69Qoq%^mK|?{PO-OK?H6?Y`d-!379!;bFpn{w{m|(mEF-xJ>7dG1wVe& zUW@YgY$B&V0Uy5_~Q<5qjjZ@2Sm_*F$!^d!2?2O#QEFe>(8VaU9~Tq{iNEo zKc#?TYrA8+4>oK2TF!hpIY?JX^5tD%7vD9B)Hlk|j1cpZo}-zw^Jw*wv7o%#but)X2GUs^ucEa~~wbyG9UY58k^J{7nh z_^~{P$sA|V{Xm=|DPV0M-1%)sV~>iYpjw3O>f{j27}l<3&5On}gJ@b(`IXJyC4gqdU(H`p6TK#bu@AGkE;$Fq&*#9r`1W@o8? zaL|;_BW2d4d!#nX12!M}{;H)tXj!lW8cbX< zp71@|JdUe29@aoo#%6|M(mS9BuJNZJs~E?c_DE;KLA98uUT?Rov9d#CQb)U zp-_|GKborNdCcEA6)ot*=c5?9>k-rtMroYZ4|6cGD1aGT6ZuQs_Bc6v7mmP{u9Q;! z30yn28gz3rMZ59ecF_VD|FJGGOxqYk%3D{E&NUN8;O1tL{YKW!$vodEK7w;Mq5kj` zhwNmw1_{m5DC6dbDJ$VO&hC%gUsaaf$x&3a<;J9=6Qm89GVdtUN)G5hxlAuCm2%9+ z-75N|5TQ+c$rRWSNMf(%?n$29l|y66J*eD5ent@-JCqj9u~C)Bh?o&?tXiQ9AHbs+#81N~8= ztN(~WJGHy%h@Z-+JO5GHy8Z{kcmJG{K-MI8bk zOS=!+K6adDJ8M{!&Ym1}s;_m7m<2ru*nvjDf~0|#n-2!NYZsBFve7n5o2l>AFT~p3 zZyh$)cYh$NBN}R6IH1`)6yXD(1ogubT0f&@o@dCpFRXnGD0Og}-`{5sGL8uO7)pDB z7jCihoi&ZQh<`k4R+gR-Ktm{NjTW!Yp=?x-sdxV4nMHS;KD1fjNv zV(-nHE(&7_WtHt&w96QIp$7QV{l~u&OX*vZoqbB(zuZs$Dv0sl_6!s9UwbnCq)&Ew zi{@#3R1QIs*9BFvXoEjt6jgQ<&TI$}SCx-O5HNDmv$du@7b%w~PL90d$#QM?fLOvs4CKiFdC=dh~dxu;NjeItKB^z(o%kGS4 zO95^t}@Q4d&&*ubE{hYNP?q*iMa5AtbmlNl`% zBUk^xom}^{CeUV%D@T2*ialyviMco?aDd-cIFL&`O6kwRu22C)4ay|`3_iTf(jT3c zL{)I06ZhdSVkk+$Kr2exQpD-IH0?t4(t?}t!>-4NIis|yj$+`E3;M{N!nVZHzgvE7 zPhEtwt#)mVA%wHAL=-dhw(&4q&)IqB-# zJ9Q0!p}FwXNi^^T6VUwgocZ%hFm6wFU7{4|TFc(M!Kcp8AXcu!c!6E$zTR~l1!^_e z<8mUZIXQ!&BR&ZX8N(t=_6*`B-ecV=K^}z~TEuQs%@amOzIQQxE^RHvdW4s2M~;rz zeb4#msq3?@hrbvaxsMf!?VQfdo$v>*wH7|s*j9dGHEVpFch6MG%M}xnrkecib0Ci9 zYa`-7%~lxRHAa2Bw>XxikV*Vj2&(ryfCR;QHuY|#IMMTL_xpKpz(50qT@Y+|c1*+B zFxsR@Yn9E?#Q%3IhG5D5J2G5{IYH#4t5BcYjKeit`6@K2kgNhUslTii;_x|u*5C#miuDFz7&DQ4Lwr1jPoWQk; z=iJpA7#qCZq~|I4A+z0|mDlr#Nn~Q_&yP=Bhb|@L_2y1}8;%3`Si7kt2GQ*YFh48~;TXoFCDXn7MYpLpvUx#K0I#5Wcd7~b&2EqbEL-=$(8VxG?6 z;JAHAAu2UzP(%!?To+*}23`qR zh3Cs;eRYvps8X|j5;rLe_ofln4q^%^_?FeyskJ}OOm}SkyOjK`CBMqcM<2x069?Zn zMl~etPE-g8$u&2`M|B84QKL#qRcB`7eoQWYwuT}+=^2QKh^1%nauOHMd|GfwU{Ncs zPYJfIkG{%J%dXnD`ZC>*-@Xp_t4YC@^+VhNj>_|gNPv3plJHZ?Y zsZ*MBgAi`V6~2WB%&ZR&Z(_Gdv;Nwy&Cc(|ERY;)@@JzGe_@~`&}l-_oP3?WA=yO7&xC>kqlb>;;u3Z965X}sl{B3m9Wmi5{`nhisU z!P<57!;PNHZ{)-eA&3e2ur`g|Jsl^NCmqH}60d@s&zlwCa!U7|5_eJBeaPKiapD`V zDn)KB`3ET|St3v8z-4}beh3hYmk@V6g~wviK33J)nJ87jmB+0%XVEoU3I~?nDCG!E z?z3>-TW)<*>3qT@)uqDrh>=Ix-mpx-)P6`y^5qJbnmMu5i5g3_c6sZZN^zYOv&_BQ zaxpQ;OYN1)nSPVpu1-fI-zkMSJTQf<(S1Xnn42_8 z3+cw!NiE;s&K8ZGEGq^rgO|jm>E8-kR#nRFp92`IOUl^WJBmn}m{OB;;Tj3(%p8AX zzeu61EZhN4W2K_`Wv*zxqg=ZD6?l+R|HWG$u%>TKM}{IXiZN7ud4c;=Oyl8Ev}+D8 z4jIyiwBRLApHbp`lb>g4|KlDw3%ldc^k4j5dM3ueGevA0t;W*$!w}N_q&2Chp@|Zm zl%~dyoz2SLj>K553H$wef>QPTnJMbbDB`llZ+dyg0KnTyiAXPtkZ$M{LNzx)sELh@&s{#*IgOd@z3j~Xbx0@yysL%=pQ*Fb*r^N=UYRG zcuaT6&fXvg>s*}_iOJ%sI&LOA))P3UJuniMvI&;)m>!JEndMXovRRlf9!--~7nS^EolHD$q%Y>39?!J+_Yop=HJnJoQWV073N;B!IP#fF$c9Cr5UCR( zZ1o1u3beNicu$h$j)6p31v$k^J#%`|kf-LSTe8+XF0$X=0qEvOyJbASWBjN;k9E}8 z;woF<7mj1L1C2+f2Z(DX$-*ouGG?OF!{6TwMQ5JJ3Sd#pBhnti2X&e$Z9jaGuc`v8 zo3-n;7yVd;57y4~V3K5iZ@1PWyp(oz`eEr9U<0vQQ?}8rH>0gNB-}DB8DFhr>rOAE zV9A`3a&ywlwDdWjX_^D^nS__fG&1^c7JXgh$o|*q{olt~^PZC0j_lkD1?sf#-(+~; z6{BhqVX-X_0DDs_&j=(pq5pNtG6Tilna?h1+ZNF9m!;f7*PFjY1EJ1(E#Pu(sbXQ8 z6|DI?n$<#9?8~qqfS-F-P2B5~5Y8rroGRU%NGPCZ5^;mk6K43^?EYxL-d-n7VdV=3a?vs?rPlc`ukXi!AhE})Q{T9;6C_h8Pi{oMVHP4-NZy+<6TP9b z$^Nz;e8T_sm|>5$fbTUA^V|O3eNsi&&9n2CM~F$zh!Wm`<0)vvAf5K`6p6N$b@$9s zzZ$7BgVg$@p;usHVYZq=34Oqo?C=R*U9Nktmu>50HK-2Y<_vx@g^XjG4cj-|HDOr- zBZe{C*P}9EGt)|$XSpZbFKQ@tIKLs?!y1iUa%plKtc#|p6N;NOVVrOMdVyx|FOC<2 zUSBlFRoCm{&h4H<;*-s*1T77-Q@?NxI9hEK3}_Q=w045T&7k>B9(#jRs3*t#>d{&T z!8;yf_N8i-{_I*;g`V8vW&8C$GvRQK6%m7VolFCe)xN9EnE$)o%EkwEzx4`- zu1}f&;TW|j-_#3nY+jyDwto{Ee3g1f!m^VM7~B27(LOd3;NkfFQ|N{1=||?5F3Qoo z))gl~kNwjVzKNOojaBu(5wv_?J=)$V-Uc{G7ZQT;J7bt4q*QL-db$n=#A9nYj=6o= z$TSvAz}~<#&P@f%K1mbz zFFY{prV%vr|DeXKENgq4Z?HCa;aB?eTCu32wWU}Iqyj`uQ{yikfZNJ!~I8x3s(sY#CKDkytQ-b4BAXiM#&{IKl9Gf%OP?DBN@rPNJ zP6CW1h58Cnp;8rIsKC0p3bQl|M%VWf1=wJdY!C@n7N=Yl*%^U zC*85emN=k(_RShucg_&--m;Pk`q9v*oMz#p zS1BVOKy!)Y9Stl$l2W%7*Ojz|w{&F*N$&{JsCj-+n~785$GF9szP>V*Ql{{T)PRJN zBA+pV+`h#HUYTZX`x^+G*%vUy2#6DR#|FEFvYjAgJtDM`JEKGyr@`7LTkg45R2-&3 z;~0F*60>>e)3Ba$vyTdo%!BS#&X$4`_rwr*YTQb4ERKPGQR%Xb#4<9pz3{FuwXQ{*Kgfrp5qe4JE6; z=q;Z9KgaOjIfHe`5eWg;5t$${TreK`lcYY8`_?D+VVAgHe)hIpjlqE@6-o|PP79B) zR37cWP;>Onqr9Jpq`spzuvic_Y90#!M3&N)Cv&Ws=O_F0w?BS>bR*I^WOJY#N|~q} zoE!4sl<;*G^!k?uVHYGT`nYNwH&-N@U9 z=N%kxCG`HBm-p;G(sZ2~*hr3JJVbXz_nhJ-$>%+!sog&oB{KfU>CUGgT~5TWsEhMu zY70&y*x$#(gjsGB`_wNTPNGEvKI6n4{IDheTTR*&=FBf9HH>y%Np3wlwH@HuW~c<0^#BN=_P?{^e*Ys!ee3k*=v$58rRc zq46(gE~V+y|4+L1-?66=d;Wl|zKov+F@gN&3mJc9N}dW?t%bwOIH88>iA`~R>4Jhd z7O#13GmQ-hdav-OUq0CJ3)*eW9hw=|T4F0l~Ma1Z6xH1*s zXN9q{pmJX+Jo!&5%h0#St|!bK*8{Y+x8!$Q`EbsDonO_AJOZ36CtHPg$Rtdl^xhEh z_}j_C6hPdn8`reJWP(MN0~^}6370_?wco-B*3Q%y7=(W7$`N+GhNuMnwUqs_)hOJJ zOZ8Q5jy;)uMDwYOt*Ukjk8k4d=S;8?**20UB+rtQDq==^oh15EtxN&(9u4KICq z2*OC2?%KX?bx=H`JKvo4elh>G!r@Ed&M$M9yDv$o?mQ$8$lT4aEvq_9J2JL7RX1(h zt#C!O968F0DgCg)W!MNL(l8qDo4~5)Z)2+9l&c~a(zh`9FNv7O8pN#a zmCoAR{Oz)9(_f$O*;d&6`-#0@E;Y{;HmhZXO?Hrg*TTxj5SMuMyyztHPXH_g zmpDhlbJ(^W&r83lMa^F#&_;H>n>Vp64(#xN>*F2srTUepwz>*(PJEKh05>#BN(?oz zxU=(b76AR33Mktf+if~!wg*#S>pEqsS|c^7V(Tx)RR@nV@ZX(SW4LX@r(+S5wrIRe zX1b&j981zUZJrTA7VQ+0KFY3G4bKn1@Q13?ID5M*bd{3004za9|LE*Y{P)H(9fPmg zp<%a!k76>J50Cc3v?V7uHU08&LH@~R7z>DU_*tu zFwkVZX)Lh-{S`fjI_6d!4mCgWcj`1vfCD_a2x4Tj(4a29%N8Rg5$raOU9|x9>V*=? zfY9QYvb|K@&wvPq#L3g?>a3EzF2caUN~yCPt| zk*lV#Li>h3GIKG~yHIMl%FK+4Og3U}h#f1cBVtg#yec(96zz!qnYuCEq+*ZE- zY@m-nv+p`ksbX2?v;fkGOiqSB0NhPpNH=AWlA7l8r6;J=sbwl-Z2*rqMK8ZEV>9UQ z&F#w6#Wk{rJNH}chglun1!h{WTRj6S3#EjM$6{f%+?&0>V1+Sd`6BGGyqB8NbATW6 zzlzC!|Mma6!C|^@EKXD0!gzjNbzb9-nZ3Cpl60(JXIN<#f)-|%`wE&nWMaqmKl?85 z)^g>d(s_dFd11i>|9ni)$iw$0JGXmb+g|-Vg5Jf7OP75Zoy3*v5IH-EF1xmYc1i3* ze_led7QQ@hWFAP4?CLn2t!?;BCHx6-R|M6%9FOteFrz7zplUtP{l^WQ>%p%b^1`a( z67qD$5OT|gkOnW4{dHu34Kmn|Blum&QY-Y@%yW)Ap+|zZ19o~gQ@jv29VIPYXauN z2(yuy1}DknV2K8sskK2{&Rs8+3fka$UMwUu1FQiME=>C((>05WHCuMvD_sHcYordQUGVtA#=0{mh6V@*bw3 zaItJ|A;)Qtj`(^|c;}<&g+#eQU49|sPLVEyh>7cPT*g=)=a3SX^J#O#P_Er^oJ>W`M``P3Adc#VjMg|JZi=ONX=sQ20zG%OPZ2pyDJGD*4a3u%V7fyFyP+|NpQ+@u=75bE+L@h^d+-7< z4Aks7WY=yynV`ky%QRBX2bT0hkj`*|F=3raY9?GGOVZ9r@UY zg)64|#IfkKDHrNB&^iIQ;F^A$pJU)*v9&Q&1OR1|&7)+-HvY~xE7|wAHVKs$R=&wd z;-%7gi6;M@IYPgsG`q-O$%ez185&H)Yv**UnktKL8Vk@76pgM>XoQ_QG`)VWOs0vi z8kEcsg9ci31#~=~S6A7(VnWLV?Ao0I>F_W2gGAurDJG`$1K6)}!lWss&N>zW&Yv9s zF|K;P4{zW#A*sb8wTu`fn#QWdzQucMD>PR#z1EK*A%^Y0dEnsieH&I@7}*^pQ9@-I z;P_4uybXk9^UWpd4k3i(iP{+)~AZPszmT-v+;rQr(;lNS9rZEr8)nW>S z%Nip`ztu&8C!SF0GKmhvDLO*nvKXQG_F82j`MZAlHSwugDoC@&+G*#7iZo*|h?Jo_kAT-#z!y~aH+LifCEqB~@r*_{m@yYO zfwMYxbWoN&%YQttE~dETvjX{ggYdn~o$C}3$I`PVRf+{vZw3viB6E@!o@*A&D=mF{ zj1wI~BhT3DI=$V7TF>>Sx9$t}Hhm)2@E%0PjaI@~wR?0vQozD_%h6K=y0zZ^m7f@q zNFoD@@S1|sZ~&6?(bBlvNo7hOS7ebaND$&1C}odo`ot6)c!p-??NYCzkqq_OT&Hsp zX{-tm#xp>-Au0=lKr8x-3mn=v!{OH3r`O1qvu8_u`)kK~vltwGGaVWFA!%3;WjIS; zG#{ar!R1FDm2z7yGg~=h&$AhRY)eYpbhrO>5>y0P*K>t}CKrnR4~3)GwxFnLHs_^& z2HO3{Uuu~MzkPO@Qth|Vphz;_ez+2-03NFsFBfg<*HNjm8r+&d`suHL`wH~5lMvv& zzMCiP)HIjwN_3lG%7G2i(iD(~YB zlzHlG{oBMN3S0P-SQK)fi7IYwV(W@xFPk2BNS~EBA19>Kah!bn7Ew|%c90G@39X3+ zhlwaMlJp`}%*I_h)Zs#Y}$!2Hho(%Z?id zi0-*+@t>Ig4Si(=psy01t$;boDDi)AU*qPZ(|Q1z-skPkt?-(Y$_+(K-qKcmm7wxW z7M0ffqbHGjmHgZPDe+{ad_x{NRY)LoAGb06^O2ExOZ`Q_5=eslFZZi$e_IVQXqoXH z-O9-7a>YLrH|6Nyk-CR^(z;34e>j#7J?TGf1ua`Aj81_F`@boc_qqS3SQ&>HsifH&RzTM%WPD_=+oH`}xg97`*paY1@xZnRP2Q{G)qe;6 z|Isf>9miN=vE|QUS`kP|t&Q^HaWIT~7@>GW(B}$p_#zI0wr@F0E~e7B(WHM?B}GH6 z0G`*B2Li*X}oP&tfR z@tAQ6W?*n1n*wd?)^!LoKnLvEZ5TQVqc+GIA<#tjsN^&iw&xM#5c(KuWWv#mw_ut# z&pKbbwT;**RI_keP~HwS*V;eb%W_D%s>z2Pj~lF#{MJ zzY6q>IV?{X{snG;y18f#D=(sa7Gs^2f}Lxd6o~TZs*s-Te;6RyScPC&_T!8x{~Zat zHg=o{ecxzTZ88fCLV)+@f~{qd(m)WmhV(pC{60?G%;ik&`D=q1|D6ZcG;*@1)Xe9h;`iW>Wma(M zomEMxqkH`tU7P2X%|cQSF3F@)kVkxam&%q3&x-gUIuhO2xfxNj5J#zr7}6ORRs|^3 z)x->u{!DFgrq0T7JZ_Wq+84QMH&zjo1^d^N92-C%rXT17+qRdKW1d_)P(Zrcmge>w zgcA&?#H0y&f$L_m)>yjJpD>$-9}dn8ccfg?vYF&4@f5tJnNy-#eG-{Y=REvBp6-7I z`yWnRd^nZ@4xO%>LG09};!FO*kUueF^x4IY?LOv+fuD2Jh4>X|^7RJ~iaTQM%8(vd zxf&fXiNz(R=VRGf$7S?}S+v*Ouas?-oXq`D2;NYY1Z(Chn%(Mp5(m)3jMF)*Ldb7`jHG9VET^!0My^8$H-kReOm8FAk7h_+K|y)=WJ|q~ zJtSRpAv$fJGu~}&y}eOIWA07Hr$VofrT@l2MRI&UD8*lm7PKroefewIvUo05Uyz2| zS8Dp^*K`<=@JD345j+}D?U$5)mE7dSF2#E4F}v|>*4^|@Ub~PN8L#K{AD(>c5MW*P zXyj02kdwwMZU5WON2Od^E{u`sc~{&eb~oLwv10y01BpvbjU6@mjxqg2G|Cgj21ZSX zoHO{_Zz#wgb~DTt6K5hgo92$5|0CroymlHHk&AWo}x3|JlF}{ZCDdVQqspmu4# z)#7(34E21U-R9T_y1BxB)6lbuDSnn@^9Q?+Y4RquB*-T1Qe@If%N-MpaYEl7@_sO<mV)nG=PvZPVwsmMD$ z(@6;r(3<}~Lrj|H5M5$I$p8C^c!v4YOS@>C^Y$iaW~j&<=3IumZ`&?qKbbqvo{5Zm zm5?11uwvWZSoS3l{4gspAn_?&>+83~rHYRnX6X5N9cAl0p!|fRDj{78O{~Ff*;9wyMa=p#Xm4WaUQiZXivn~>V@vehQ z+C*}F=*g=W`fSo39PiJ&*SLbi4PniT52uyB#&bh9!~tAutfzD z6GScnAN%`>g3Hf@t&B*A2pyB?<#z`G&q{(2i`VwF{OtC3kfgk+he@-d#`LI>W4o(-;O5`u?IE40}x{zEbz;T)TJCZD=^Tb+;+l z9Wc_wq=D!#nP{m0pO3eg0PJg8W&X8S;DeDL$76>|0Zn3tag)T9|HasQ1~s*Y|Ds@k zsFZ9(qy>V4pn}vG2vS9ZC`Ca)KnR`CJJPEHA`lRe-UKPqq)P|sJ=9R7x6peF?X3O3 z=ghsc_r5b{-cK<3k~eETT}*`bjiVo$O>HsPD;X=}NEaaw}` zF*)v!&9^(~`I`iyuQ<&zWu;3l=wnAoT?sr}*&g(X$DOm&wPsQN;4mw*5a@1c=w{$x z5OO75o~i4*c)X+3fFstI_zSWp))Cv<*-DY=a`Qfqfk|N@%5Ts-MX_3GuQ8-8c4QgL zwMf&&GwZYZBTda-5^vHPyrdoz@%_W_OIEI$Yj51WLQo-bjPvac|2$2RzEyXY)Q-=k zq+~5V)I93IYw!=jSX>M4xmRTbKi5%p#qbYjD!Rk(4>phDrJ|ricMt|R(cX0VF^;7_ zV-n8ncnlL08~h^jHy@IED{4|Iy<>*-y1}g%EwXMnnz>bDlJSN9#G@z z8wQ1+;G$$=Bn(_U4|0S}d&OI{!}&%kxMqa}oO~<$aT|AUh>B~-A7xmNB*;b1AT%~2 z$9SJ?llbOknmHV}u_=zfNPTfzWN)OIYx0Ls$G-oka6|&Q^06-3N~={=NZ=o58)uY? zrrmpSagjp*7jjUYVWB@GFGy2!>0mgG_xqb~5my|pT#(d85KSJ~?P3O!Yr^8=IiK8r zk}aF({Ypk;>+KIGN{7QBdG3G;uKpl>EaO!C0uXh=hYa#U8gm`YAFm=%E5pN_k!c9= zncG>s+`IcxOmsICRN1ULS=#j_*OdcNGp+aH_Gb|3vW%$hGf@3qmDrAsJK<)}6jf=`$)Oo1 z)xyB06KzB2KVlCflM6=Z6^kJMiEll!Cgx}Tr|HYGf}f)e@2)zP=(34ir|~waxTC_Q zo7oldowh{nT(v8q*>6<5xqQMLtY%!{nA5UB-J7jO56A^4BP0D!5jj)CAG0T7RooRV3lA-CR znBg2{+S}q+C@Bh;z6>D|w}mVf4{qQiI;;qXlYf+nnw%z3DVvaHe9==!7 z6uK7#XjAmJq?^2LC7N4dxIT%~b>STEUUZ$uSdUvovAiFh4~jzCv-9>ni%Iiz3JIFy z^Lt4V!c7n%GD=0yb$Qu>EDpRy`_6Ik1egXqIGfjV{`~)^jS}QCuXXtyps_a{a`9XRgI8O+NnKU}(VA zS0<09sh;GzH*b$*%uuMubPvgMl% z=jBcA(zkEPOA_TGU04s)50pKt%|! z7Ss;fjq}gVk}$8isV_|MlrFs$K%)oxQ|aMC%+8{`O_G8X$EFHVgX2I-Pyj~4Vv-4nA zTMQxHux6XgsMM;2ocT~RFgZ|CoSC2a6y<7lJMf{yjCjV6Kt;pz_6QC+HpQmJW;*3W zudaa5Sb3jX^s08GNml&N@k@*Pj}pcs_)!kjD-OQOm$a{)FY;Me5-*5ux@gZutsrxS ztk;1Lx1Xy|K9Jlo&6-+L3DrxER~ZORjszqAkp=$F3fr%YX4?BX@^w@BvZV;i^{e$s z)0gAsBgakl_rVahg2D`IZW`{g7fwPac}*XxXpHcs2}SNtrBrR_ z(Cws6rmA6SCFTOMSQq-|N%otvFY6AUDzh5xgMdg4PKR5qF|Y|5rV!OGyGdPq%9&>M zA(|^PJTy9JTsb|Jn|4xb90Tbk*GIwSt?1)-A7Dg?@(IIl~qe~>=_*)KyQNwqo#(CS4 z@$`Z;{R=8IgdqOPCH^s(|P4PyHp9<(* zoq=1evIz=bQ}#r~-c6r`ewRD_{!OT!Z|nER#4ewImivGI9ryrbMGw5qhr%gt>CsI3 zH8s+`H7>Hh{ZHko7-}AHN8hR`}-1m05 z2XO2Fvo`;fEGJ(J>~Z5aK69Zlf0w{KgOC+KmHalj!qJ*a8Zu;#>zCEl++c?!x$=Xk zC!c9|Hh^bjn`p{&?j970LUMQa@58Dt8jJETrw}{sO%y)V#$|0+VLImOs0PO2k70^S ziodXmC7@j`C&U}Q`N3gOAwowJmPqu? z^u|;>uaEA`{)uIy^b*2y0jW3CUNSV6Ri1KUL#UTwWmb!%??xTm8pwn4vyz{QL>8zn z*P~6fT-T75)#36xnTBSi<4 ziFc)PLAsk;m%R;C5>XNchZ(f#SoMxrYPRQd_kD6wU-bWG6MXsdF~>9>mQZGXUP7B+ z^Ks=Zo8p{I=4L-9FrnrE*e8BZNTpCrC9*%2G!?Ylu;@GS>NO2>al3%MR)~+#@y)=g zi2$XZAg{uNm2MizSyG}jBwnmrF<7XZ6y@5w8!Ej=He30WT95vU^) zS8*`UxqUiilcPysB881M<4W;-T&fb#%6fsIoQPN;Zex=C$ls< zkLtTc!}J1=On?9KLp1z{l_ba0<=r#Ke@9=&X$1{ESLvEzryuEjq29Ni zY8iiLeHAvBiqLv-UJ}t3k?kI~hfxs|1o~oOv)-SY{5%F;g5WYvl&B?q^try01Q>#p zMiREKJ<$awvntJgmq4W*1wLGRwqXFd3?ayXaZOz8tnu>f(Ijx=A}i>&d6lq1WK(7O zAyLpkO(~(fk#t?#l!Zl{u+%~eBVN;LL~`KP9(5wTn41w*c?HiC88X@wm0%D!4ZkI< zgyE*^2`4NOq{**>bECD=P5n&8K!>}m%P zS6k*qM0D>}cK)!u!)?gq9w>0BwT}2Y8Cjecgp8B>!Y1Lcw6A*PAW{{DQg~^i9il<+ zus7BonlsyfT^Bv+Y%`uG62eSk&xgI1Kgru9ZwO8VI*A_b$e;cmkO8Q>uEd6Up559q zr)X2UbYOhB@<)MI?MQ^FT{nv2pNB1g>gOF3B2tP)q>X;F>-cB1Y8c4&+%j5bd zh#BldQ`(t!z9YKD5N93(Hz>Yi0+bEoKQ7XSvOk}5C=biKYiN39XAl`Py|hLz{nx_3qCoq^)of>QIgWK_#hZ(*I>!JovEOywL5`U z39#)0$fhq9^tQ+T_JQbbFn<#GhX9=dIP0y7HWl@*yMif=nr%hnga5i~XEa@lQ*d14vto+CA*G zSh#1lNft;ozWd~Y!l2@-$aR5=fMFUq&5Z|ajwoR^3{h4o0q4%NAuV#ts4w{;G$aTO9)_av?5vuurk(f|WzpDRB)Z=3Gk7dg)WrD!;czik{{K_x+l_ zI;Qf*ptb1E5{t3T_CFtX6a||C1wtZzVe81|PSnF^cTFsL444}q%+HkG35d@aX5NUn zV;*yzw(vmdp~-kKV{lylDlCCN5|f{xlq||7>eK7Lis2 z;y}Q16I72zAb3qq)4(@C>Rh`o;LXbD-mAW2B67FW)eE?J@f9n(5oBYbydI}3R~+rz zkRDFKFZXEpo)-4~`P_7mfURWsfzNU+BEXQOo>sC=+{E$XYfXu=y145EP)PeX2qVWu z|BCCVFE;Grds<(bII(;7B}k*Nf)xFiMv6&qpy~gF{)DZ8pfR$Yp|6W~Yuo~_GeFYz zfRSz+HE}`hpRTsQO%RM!;1KHY5?WhZ9FMl3H4BX!iLkygo@kN~n9VQgJane0ggA-+ zO=$%;KxF;@M42IaLCX4Bkrw)?u9Nd=b247VR$-{5i>S?$xqNxoHH&;%E@gC^_e37U zO3aAVFyEus8kbNhXK@~kb5zN4?;+3>63riEv~XSTxoIxr69KBckOm`beCTovalyhW zgFmt!8Z|~&Y__mH;@}YTsR|*G&Ja?u-j+<@^rH4Ps}DLSt>37;irO&UM9LiE`Z9q< z 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ϠnOd<8{Q3|O$Nb+{~L$LqgpjToBCX$c@ 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%0DRRvMxNP- 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